+108
-3
Cargo.lock
+108
-3
Cargo.lock
···
18
18
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19
19
20
20
[[package]]
21
+
name = "ahash"
22
+
version = "0.8.11"
23
+
source = "registry+https://github.com/rust-lang/crates.io-index"
24
+
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
25
+
dependencies = [
26
+
"cfg-if",
27
+
"once_cell",
28
+
"version_check",
29
+
"zerocopy",
30
+
]
31
+
32
+
[[package]]
21
33
name = "aho-corasick"
22
34
version = "1.1.3"
23
35
source = "registry+https://github.com/rust-lang/crates.io-index"
···
152
164
"atrium-common",
153
165
"atrium-identity",
154
166
"atrium-xrpc",
155
-
"base64",
167
+
"base64 0.22.1",
156
168
"chrono",
157
169
"ecdsa",
158
170
"elliptic-curve",
···
279
291
280
292
[[package]]
281
293
name = "base64"
294
+
version = "0.21.7"
295
+
source = "registry+https://github.com/rust-lang/crates.io-index"
296
+
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
297
+
298
+
[[package]]
299
+
name = "base64"
282
300
version = "0.22.1"
283
301
source = "registry+https://github.com/rust-lang/crates.io-index"
284
302
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
···
409
427
version = "0.1.0"
410
428
dependencies = [
411
429
"anyhow",
430
+
"async-trait",
412
431
"atrium-api",
413
432
"atrium-common",
414
433
"atrium-identity",
···
417
436
"atrium-xrpc-client",
418
437
"axum",
419
438
"hickory-resolver",
439
+
"openssh-keys",
420
440
"serde",
441
+
"serde_json",
421
442
"tokio",
443
+
"tokio-rusqlite",
422
444
"tower-sessions",
423
445
]
424
446
···
721
743
]
722
744
723
745
[[package]]
746
+
name = "fallible-iterator"
747
+
version = "0.3.0"
748
+
source = "registry+https://github.com/rust-lang/crates.io-index"
749
+
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
750
+
751
+
[[package]]
752
+
name = "fallible-streaming-iterator"
753
+
version = "0.1.9"
754
+
source = "registry+https://github.com/rust-lang/crates.io-index"
755
+
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
756
+
757
+
[[package]]
724
758
name = "fastrand"
725
759
version = "1.9.0"
726
760
source = "registry+https://github.com/rust-lang/crates.io-index"
···
937
971
version = "0.14.5"
938
972
source = "registry+https://github.com/rust-lang/crates.io-index"
939
973
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
974
+
dependencies = [
975
+
"ahash",
976
+
]
940
977
941
978
[[package]]
942
979
name = "hashbrown"
···
947
984
"allocator-api2",
948
985
"equivalent",
949
986
"foldhash",
987
+
]
988
+
989
+
[[package]]
990
+
name = "hashlink"
991
+
version = "0.9.1"
992
+
source = "registry+https://github.com/rust-lang/crates.io-index"
993
+
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
994
+
dependencies = [
995
+
"hashbrown 0.14.5",
950
996
]
951
997
952
998
[[package]]
···
1451
1497
]
1452
1498
1453
1499
[[package]]
1500
+
name = "libsqlite3-sys"
1501
+
version = "0.30.1"
1502
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1503
+
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
1504
+
dependencies = [
1505
+
"cc",
1506
+
"pkg-config",
1507
+
"vcpkg",
1508
+
]
1509
+
1510
+
[[package]]
1454
1511
name = "libz-sys"
1455
1512
version = "1.1.21"
1456
1513
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1548
1605
version = "0.8.4"
1549
1606
source = "registry+https://github.com/rust-lang/crates.io-index"
1550
1607
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
1608
+
1609
+
[[package]]
1610
+
name = "md-5"
1611
+
version = "0.10.6"
1612
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1613
+
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
1614
+
dependencies = [
1615
+
"cfg-if",
1616
+
"digest",
1617
+
]
1551
1618
1552
1619
[[package]]
1553
1620
name = "memchr"
···
1681
1748
version = "1.20.2"
1682
1749
source = "registry+https://github.com/rust-lang/crates.io-index"
1683
1750
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
1751
+
1752
+
[[package]]
1753
+
name = "openssh-keys"
1754
+
version = "0.6.4"
1755
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1756
+
checksum = "abb830a82898b2ac17c9620ddce839ac3b34b9cb8a1a037cbdbfb9841c756c3e"
1757
+
dependencies = [
1758
+
"base64 0.21.7",
1759
+
"byteorder",
1760
+
"md-5",
1761
+
"sha2",
1762
+
"thiserror 1.0.69",
1763
+
]
1684
1764
1685
1765
[[package]]
1686
1766
name = "openssl"
···
1977
2057
checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
1978
2058
dependencies = [
1979
2059
"async-compression",
1980
-
"base64",
2060
+
"base64 0.22.1",
1981
2061
"bytes",
1982
2062
"futures-core",
1983
2063
"futures-util",
···
2033
2113
]
2034
2114
2035
2115
[[package]]
2116
+
name = "rusqlite"
2117
+
version = "0.32.1"
2118
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2119
+
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
2120
+
dependencies = [
2121
+
"bitflags 2.8.0",
2122
+
"fallible-iterator",
2123
+
"fallible-streaming-iterator",
2124
+
"hashlink",
2125
+
"libsqlite3-sys",
2126
+
"smallvec",
2127
+
]
2128
+
2129
+
[[package]]
2036
2130
name = "rustc-demangle"
2037
2131
version = "0.1.24"
2038
2132
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2516
2610
]
2517
2611
2518
2612
[[package]]
2613
+
name = "tokio-rusqlite"
2614
+
version = "0.6.0"
2615
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2616
+
checksum = "b65501378eb676f400c57991f42cbd0986827ab5c5200c53f206d710fb32a945"
2617
+
dependencies = [
2618
+
"crossbeam-channel",
2619
+
"rusqlite",
2620
+
"tokio",
2621
+
]
2622
+
2623
+
[[package]]
2519
2624
name = "tokio-util"
2520
2625
version = "0.7.13"
2521
2626
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2598
2703
dependencies = [
2599
2704
"async-trait",
2600
2705
"axum-core",
2601
-
"base64",
2706
+
"base64 0.22.1",
2602
2707
"futures",
2603
2708
"http 1.2.0",
2604
2709
"parking_lot",
+4
core/Cargo.toml
+4
core/Cargo.toml
+31
core/src/db.rs
+31
core/src/db.rs
···
1
+
use tokio_rusqlite::Connection;
2
+
3
+
pub struct Db {
4
+
pub conn: Connection,
5
+
}
6
+
7
+
impl Db {
8
+
pub fn new(conn: Connection) -> Self {
9
+
Self { conn }
10
+
}
11
+
12
+
pub async fn setup(&self) {
13
+
self.conn
14
+
.call(|c| {
15
+
c.execute(
16
+
"
17
+
CREATE TABLE IF NOT EXISTS keys (
18
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
19
+
did TEXT NOT NULL,
20
+
key TEXT NOT NULL,
21
+
UNIQUE(did, key)
22
+
)
23
+
",
24
+
[],
25
+
)?;
26
+
Ok(())
27
+
})
28
+
.await
29
+
.expect("failed to create keys table");
30
+
}
31
+
}
+30
core/src/dns.rs
+30
core/src/dns.rs
···
1
+
use atrium_identity::handle::DnsTxtResolver;
2
+
use hickory_resolver::TokioAsyncResolver;
3
+
4
+
pub struct Resolver {
5
+
resolver: TokioAsyncResolver,
6
+
}
7
+
8
+
impl Default for Resolver {
9
+
fn default() -> Self {
10
+
Self {
11
+
resolver: TokioAsyncResolver::tokio_from_system_conf()
12
+
.expect("failed to create resolver"),
13
+
}
14
+
}
15
+
}
16
+
17
+
impl DnsTxtResolver for Resolver {
18
+
async fn resolve(
19
+
&self,
20
+
query: &str,
21
+
) -> core::result::Result<Vec<String>, Box<dyn std::error::Error + Send + Sync + 'static>> {
22
+
Ok(self
23
+
.resolver
24
+
.txt_lookup(query)
25
+
.await?
26
+
.iter()
27
+
.map(|txt| txt.to_string())
28
+
.collect())
29
+
}
30
+
}
+19
-98
core/src/main.rs
+19
-98
core/src/main.rs
···
1
1
use std::sync::Arc;
2
2
3
-
use atrium_api::{
4
-
agent::{AtpAgent, store::MemorySessionStore},
5
-
did_doc::DidDocument,
6
-
types::string::AtIdentifier,
7
-
};
3
+
use atrium_api::{did_doc::DidDocument, types::string::AtIdentifier};
8
4
use atrium_common::resolver::Resolver;
9
5
use atrium_identity::{
10
6
did::{CommonDidResolver, CommonDidResolverConfig, DEFAULT_PLC_DIRECTORY_URL},
11
-
handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig, DnsTxtResolver},
7
+
handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig},
12
8
};
13
9
use atrium_oauth_client::DefaultHttpClient;
14
10
use atrium_xrpc::HttpClient;
15
-
use atrium_xrpc_client::isahc::IsahcClient;
16
-
use axum::{Router, routing};
17
-
use hickory_resolver::TokioAsyncResolver;
11
+
use axum::{Extension, Router, routing};
12
+
use tokio_rusqlite::Connection;
13
+
14
+
mod db;
15
+
mod dns;
16
+
mod routes;
17
+
18
+
use db::Db;
19
+
use routes::{index, keys, login};
18
20
19
21
#[tokio::main]
20
22
async fn main() {
···
22
24
let session_layer = tower_sessions::SessionManagerLayer::new(session_store)
23
25
.with_secure(false)
24
26
.with_expiry(tower_sessions::Expiry::OnSessionEnd);
27
+
let db = Db::new(Connection::open("bild.db").await.unwrap());
28
+
db.setup().await;
25
29
26
30
let app_state = AppState::new();
27
31
let service = Router::new()
28
32
.route("/", routing::get(index::get))
29
-
// .route("/test-auth", routing::get(test_atproto::get))
30
33
.route("/login", routing::get(login::get).post(login::post))
31
-
// .route("/callback", routing::get(callback::get))
34
+
.route("/keys", routing::get(keys::get).put(keys::put))
32
35
.layer(session_layer)
33
-
.with_state(app_state);
36
+
.with_state(app_state)
37
+
.layer(Extension(Arc::new(db)));
34
38
35
39
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
36
40
axum::serve(listener, service).await.unwrap();
···
39
43
#[derive(Clone)]
40
44
struct AppState {
41
45
did_resolver: Arc<CommonDidResolver<DefaultHttpClient>>,
42
-
handle_resolver: Arc<AtprotoHandleResolver<HickoryDnsTxtResolver, DefaultHttpClient>>,
46
+
handle_resolver: Arc<AtprotoHandleResolver<dns::Resolver, DefaultHttpClient>>,
43
47
}
44
48
45
49
impl AppState {
···
72
76
})
73
77
}
74
78
75
-
fn handle_resolver<H: HttpClient>(
76
-
http_client: Arc<H>,
77
-
) -> AtprotoHandleResolver<HickoryDnsTxtResolver, H> {
79
+
fn handle_resolver<H: HttpClient>(http_client: Arc<H>) -> AtprotoHandleResolver<dns::Resolver, H> {
78
80
AtprotoHandleResolver::new(AtprotoHandleResolverConfig {
79
-
dns_txt_resolver: HickoryDnsTxtResolver::default(),
81
+
dns_txt_resolver: dns::Resolver::default(),
80
82
http_client,
81
83
})
82
84
}
83
-
84
-
mod login {
85
-
use axum::{extract::Form, http::StatusCode, response::IntoResponse};
86
-
use serde::Deserialize;
87
-
88
-
use super::*;
89
-
90
-
pub async fn get() -> String {
91
-
"hello world".to_owned()
92
-
}
93
-
94
-
#[derive(Deserialize, Debug)]
95
-
pub struct Req {
96
-
handle: AtIdentifier,
97
-
app_password: String,
98
-
}
99
-
100
-
pub async fn post(session: tower_sessions::Session, Form(req): Form<Req>) -> impl IntoResponse {
101
-
let agent = AtpAgent::new(
102
-
IsahcClient::new("https://dummy.example"),
103
-
MemorySessionStore::default(),
104
-
);
105
-
let res = agent.login(req.handle, req.app_password).await.unwrap();
106
-
println!("logged in as {:?} ({:?})", res.handle, res.did);
107
-
session.insert("at_session", res).await.unwrap();
108
-
println!("stored session");
109
-
StatusCode::OK
110
-
}
111
-
}
112
-
113
-
// dummy endpoint to test if sessions are working
114
-
mod index {
115
-
use super::*;
116
-
117
-
pub async fn get(session: tower_sessions::Session) -> &'static str {
118
-
match session
119
-
.get::<atrium_api::agent::Session>("at_session")
120
-
.await
121
-
.unwrap()
122
-
{
123
-
None => "no session",
124
-
Some(s) => {
125
-
let agent = AtpAgent::new(
126
-
IsahcClient::new("https://dummy.example"),
127
-
MemorySessionStore::default(),
128
-
);
129
-
println!("resuming session of {:?} ({:?})", s.handle, s.did);
130
-
agent.resume_session(s).await.unwrap();
131
-
"resuming session"
132
-
}
133
-
}
134
-
}
135
-
}
136
-
137
-
struct HickoryDnsTxtResolver {
138
-
resolver: TokioAsyncResolver,
139
-
}
140
-
141
-
impl Default for HickoryDnsTxtResolver {
142
-
fn default() -> Self {
143
-
Self {
144
-
resolver: TokioAsyncResolver::tokio_from_system_conf()
145
-
.expect("failed to create resolver"),
146
-
}
147
-
}
148
-
}
149
-
150
-
impl DnsTxtResolver for HickoryDnsTxtResolver {
151
-
async fn resolve(
152
-
&self,
153
-
query: &str,
154
-
) -> core::result::Result<Vec<String>, Box<dyn std::error::Error + Send + Sync + 'static>> {
155
-
Ok(self
156
-
.resolver
157
-
.txt_lookup(query)
158
-
.await?
159
-
.iter()
160
-
.map(|txt| txt.to_string())
161
-
.collect())
162
-
}
163
-
}
+135
core/src/routes.rs
+135
core/src/routes.rs
···
1
+
pub mod login {
2
+
use atrium_api::{
3
+
agent::{AtpAgent, store::MemorySessionStore},
4
+
types::string::AtIdentifier,
5
+
};
6
+
use atrium_xrpc_client::isahc::IsahcClient;
7
+
use axum::{extract::Form, http::StatusCode, response::IntoResponse};
8
+
use serde::Deserialize;
9
+
10
+
pub async fn get() -> String {
11
+
"hello world".to_owned()
12
+
}
13
+
14
+
#[derive(Deserialize, Debug)]
15
+
pub struct Req {
16
+
handle: AtIdentifier,
17
+
app_password: String,
18
+
}
19
+
20
+
pub async fn post(session: tower_sessions::Session, Form(req): Form<Req>) -> impl IntoResponse {
21
+
let agent = AtpAgent::new(
22
+
IsahcClient::new("https://dummy.example"),
23
+
MemorySessionStore::default(),
24
+
);
25
+
let res = agent.login(req.handle, req.app_password).await.unwrap();
26
+
println!("logged in as {:?} ({:?})", res.handle, res.did);
27
+
session.insert("bild_session", res).await.unwrap();
28
+
println!("stored session");
29
+
StatusCode::OK
30
+
}
31
+
}
32
+
33
+
pub mod index {
34
+
use atrium_api::agent::{AtpAgent, store::MemorySessionStore};
35
+
use atrium_xrpc_client::isahc::IsahcClient;
36
+
37
+
pub async fn get(session: tower_sessions::Session) -> &'static str {
38
+
match session
39
+
.get::<atrium_api::agent::Session>("bild_session")
40
+
.await
41
+
.unwrap()
42
+
{
43
+
None => "no session",
44
+
Some(s) => {
45
+
let agent = AtpAgent::new(
46
+
IsahcClient::new("https://dummy.example"),
47
+
MemorySessionStore::default(),
48
+
);
49
+
println!("resuming session of {:?} ({:?})", s.handle, s.did);
50
+
agent.resume_session(s).await.unwrap();
51
+
"resuming session"
52
+
}
53
+
}
54
+
}
55
+
}
56
+
57
+
pub mod keys {
58
+
use crate::{AppState, db};
59
+
use atrium_api::types::string::AtIdentifier;
60
+
use axum::http::StatusCode;
61
+
use axum::{
62
+
extract::{Extension, Json, Query, State},
63
+
response::IntoResponse,
64
+
};
65
+
use serde::{Deserialize, Serialize};
66
+
use std::sync::Arc;
67
+
68
+
#[derive(Deserialize)]
69
+
pub struct GetReq {
70
+
id: AtIdentifier,
71
+
}
72
+
73
+
#[derive(Serialize)]
74
+
pub struct GetRes {
75
+
id: AtIdentifier,
76
+
keys: Vec<String>,
77
+
}
78
+
79
+
pub async fn get(
80
+
Extension(db): Extension<Arc<db::Db>>,
81
+
State(state): State<AppState>,
82
+
Query(req): Query<GetReq>,
83
+
) -> impl IntoResponse {
84
+
let did_doc = state.resolve_did_document(&req.id).await.unwrap();
85
+
let keys = db
86
+
.conn
87
+
.call(|c| {
88
+
let mut stmt = c.prepare("SELECT key FROM keys WHERE did = ?1")?;
89
+
let keys = stmt
90
+
.query_map([did_doc.id], |row| Ok(row.get::<usize, String>(0)?))?
91
+
.filter_map(|r| r.ok())
92
+
.collect::<Vec<_>>();
93
+
Ok(keys)
94
+
})
95
+
.await
96
+
.unwrap();
97
+
Json(GetRes {
98
+
id: req.id.clone(),
99
+
keys,
100
+
})
101
+
}
102
+
103
+
#[derive(Deserialize)]
104
+
pub struct PutReq {
105
+
key: String,
106
+
}
107
+
108
+
pub async fn put(
109
+
Extension(db): Extension<Arc<db::Db>>,
110
+
session: tower_sessions::Session,
111
+
Json(req): Json<PutReq>,
112
+
) -> impl IntoResponse {
113
+
if openssh_keys::PublicKey::parse(&req.key).is_err() {
114
+
return StatusCode::BAD_REQUEST;
115
+
}
116
+
match session
117
+
.get::<atrium_api::agent::Session>("bild_session")
118
+
.await
119
+
.unwrap()
120
+
{
121
+
None => StatusCode::UNAUTHORIZED,
122
+
Some(sess) => {
123
+
let did = sess.did.clone().into();
124
+
db.conn
125
+
.call(|c| {
126
+
c.execute("INSERT INTO keys VALUES (?1, ?2)", [did, req.key])?;
127
+
Ok(())
128
+
})
129
+
.await
130
+
.unwrap();
131
+
StatusCode::OK
132
+
}
133
+
}
134
+
}
135
+
}