tangled
alpha
login
or
join now
tjh.dev
/
core
forked from
tangled.org/core
0
fork
atom
this repo has no description
0
fork
atom
overview
issues
pulls
pipelines
implement GET /keys and PUT /keys
oppi.li
1 year ago
e5a13e80
53f1f29c
+327
-101
6 changed files
expand all
collapse all
unified
split
Cargo.lock
core
Cargo.toml
src
db.rs
dns.rs
main.rs
routes.rs
+108
-3
Cargo.lock
···
18
18
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19
19
20
20
[[package]]
21
21
+
name = "ahash"
22
22
+
version = "0.8.11"
23
23
+
source = "registry+https://github.com/rust-lang/crates.io-index"
24
24
+
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
25
25
+
dependencies = [
26
26
+
"cfg-if",
27
27
+
"once_cell",
28
28
+
"version_check",
29
29
+
"zerocopy",
30
30
+
]
31
31
+
32
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
155
-
"base64",
167
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
294
+
version = "0.21.7"
295
295
+
source = "registry+https://github.com/rust-lang/crates.io-index"
296
296
+
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
297
297
+
298
298
+
[[package]]
299
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
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
439
+
"openssh-keys",
420
440
"serde",
441
441
+
"serde_json",
421
442
"tokio",
443
443
+
"tokio-rusqlite",
422
444
"tower-sessions",
423
445
]
424
446
···
721
743
]
722
744
723
745
[[package]]
746
746
+
name = "fallible-iterator"
747
747
+
version = "0.3.0"
748
748
+
source = "registry+https://github.com/rust-lang/crates.io-index"
749
749
+
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
750
750
+
751
751
+
[[package]]
752
752
+
name = "fallible-streaming-iterator"
753
753
+
version = "0.1.9"
754
754
+
source = "registry+https://github.com/rust-lang/crates.io-index"
755
755
+
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
756
756
+
757
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
974
+
dependencies = [
975
975
+
"ahash",
976
976
+
]
940
977
941
978
[[package]]
942
979
name = "hashbrown"
···
947
984
"allocator-api2",
948
985
"equivalent",
949
986
"foldhash",
987
987
+
]
988
988
+
989
989
+
[[package]]
990
990
+
name = "hashlink"
991
991
+
version = "0.9.1"
992
992
+
source = "registry+https://github.com/rust-lang/crates.io-index"
993
993
+
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
994
994
+
dependencies = [
995
995
+
"hashbrown 0.14.5",
950
996
]
951
997
952
998
[[package]]
···
1451
1497
]
1452
1498
1453
1499
[[package]]
1500
1500
+
name = "libsqlite3-sys"
1501
1501
+
version = "0.30.1"
1502
1502
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1503
1503
+
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
1504
1504
+
dependencies = [
1505
1505
+
"cc",
1506
1506
+
"pkg-config",
1507
1507
+
"vcpkg",
1508
1508
+
]
1509
1509
+
1510
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
1608
+
1609
1609
+
[[package]]
1610
1610
+
name = "md-5"
1611
1611
+
version = "0.10.6"
1612
1612
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1613
1613
+
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
1614
1614
+
dependencies = [
1615
1615
+
"cfg-if",
1616
1616
+
"digest",
1617
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
1751
+
1752
1752
+
[[package]]
1753
1753
+
name = "openssh-keys"
1754
1754
+
version = "0.6.4"
1755
1755
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1756
1756
+
checksum = "abb830a82898b2ac17c9620ddce839ac3b34b9cb8a1a037cbdbfb9841c756c3e"
1757
1757
+
dependencies = [
1758
1758
+
"base64 0.21.7",
1759
1759
+
"byteorder",
1760
1760
+
"md-5",
1761
1761
+
"sha2",
1762
1762
+
"thiserror 1.0.69",
1763
1763
+
]
1684
1764
1685
1765
[[package]]
1686
1766
name = "openssl"
···
1977
2057
checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
1978
2058
dependencies = [
1979
2059
"async-compression",
1980
1980
-
"base64",
2060
2060
+
"base64 0.22.1",
1981
2061
"bytes",
1982
2062
"futures-core",
1983
2063
"futures-util",
···
2033
2113
]
2034
2114
2035
2115
[[package]]
2116
2116
+
name = "rusqlite"
2117
2117
+
version = "0.32.1"
2118
2118
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2119
2119
+
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
2120
2120
+
dependencies = [
2121
2121
+
"bitflags 2.8.0",
2122
2122
+
"fallible-iterator",
2123
2123
+
"fallible-streaming-iterator",
2124
2124
+
"hashlink",
2125
2125
+
"libsqlite3-sys",
2126
2126
+
"smallvec",
2127
2127
+
]
2128
2128
+
2129
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
2613
+
name = "tokio-rusqlite"
2614
2614
+
version = "0.6.0"
2615
2615
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2616
2616
+
checksum = "b65501378eb676f400c57991f42cbd0986827ab5c5200c53f206d710fb32a945"
2617
2617
+
dependencies = [
2618
2618
+
"crossbeam-channel",
2619
2619
+
"rusqlite",
2620
2620
+
"tokio",
2621
2621
+
]
2622
2622
+
2623
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
2601
-
"base64",
2706
2706
+
"base64 0.22.1",
2602
2707
"futures",
2603
2708
"http 1.2.0",
2604
2709
"parking_lot",
+4
core/Cargo.toml
···
16
16
anyhow = "1.0.95"
17
17
tower-sessions = "0.14.0"
18
18
serde = "1.0.217"
19
19
+
tokio-rusqlite = { version = "0.6.0", features = ["bundled"] }
20
20
+
async-trait = "0.1.85"
21
21
+
serde_json = "1.0.137"
22
22
+
openssh-keys = "0.6.4"
19
23
+31
core/src/db.rs
···
1
1
+
use tokio_rusqlite::Connection;
2
2
+
3
3
+
pub struct Db {
4
4
+
pub conn: Connection,
5
5
+
}
6
6
+
7
7
+
impl Db {
8
8
+
pub fn new(conn: Connection) -> Self {
9
9
+
Self { conn }
10
10
+
}
11
11
+
12
12
+
pub async fn setup(&self) {
13
13
+
self.conn
14
14
+
.call(|c| {
15
15
+
c.execute(
16
16
+
"
17
17
+
CREATE TABLE IF NOT EXISTS keys (
18
18
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
19
19
+
did TEXT NOT NULL,
20
20
+
key TEXT NOT NULL,
21
21
+
UNIQUE(did, key)
22
22
+
)
23
23
+
",
24
24
+
[],
25
25
+
)?;
26
26
+
Ok(())
27
27
+
})
28
28
+
.await
29
29
+
.expect("failed to create keys table");
30
30
+
}
31
31
+
}
+30
core/src/dns.rs
···
1
1
+
use atrium_identity::handle::DnsTxtResolver;
2
2
+
use hickory_resolver::TokioAsyncResolver;
3
3
+
4
4
+
pub struct Resolver {
5
5
+
resolver: TokioAsyncResolver,
6
6
+
}
7
7
+
8
8
+
impl Default for Resolver {
9
9
+
fn default() -> Self {
10
10
+
Self {
11
11
+
resolver: TokioAsyncResolver::tokio_from_system_conf()
12
12
+
.expect("failed to create resolver"),
13
13
+
}
14
14
+
}
15
15
+
}
16
16
+
17
17
+
impl DnsTxtResolver for Resolver {
18
18
+
async fn resolve(
19
19
+
&self,
20
20
+
query: &str,
21
21
+
) -> core::result::Result<Vec<String>, Box<dyn std::error::Error + Send + Sync + 'static>> {
22
22
+
Ok(self
23
23
+
.resolver
24
24
+
.txt_lookup(query)
25
25
+
.await?
26
26
+
.iter()
27
27
+
.map(|txt| txt.to_string())
28
28
+
.collect())
29
29
+
}
30
30
+
}
+19
-98
core/src/main.rs
···
1
1
use std::sync::Arc;
2
2
3
3
-
use atrium_api::{
4
4
-
agent::{AtpAgent, store::MemorySessionStore},
5
5
-
did_doc::DidDocument,
6
6
-
types::string::AtIdentifier,
7
7
-
};
3
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
11
-
handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig, DnsTxtResolver},
7
7
+
handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig},
12
8
};
13
9
use atrium_oauth_client::DefaultHttpClient;
14
10
use atrium_xrpc::HttpClient;
15
15
-
use atrium_xrpc_client::isahc::IsahcClient;
16
16
-
use axum::{Router, routing};
17
17
-
use hickory_resolver::TokioAsyncResolver;
11
11
+
use axum::{Extension, Router, routing};
12
12
+
use tokio_rusqlite::Connection;
13
13
+
14
14
+
mod db;
15
15
+
mod dns;
16
16
+
mod routes;
17
17
+
18
18
+
use db::Db;
19
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
27
+
let db = Db::new(Connection::open("bild.db").await.unwrap());
28
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
29
-
// .route("/test-auth", routing::get(test_atproto::get))
30
33
.route("/login", routing::get(login::get).post(login::post))
31
31
-
// .route("/callback", routing::get(callback::get))
34
34
+
.route("/keys", routing::get(keys::get).put(keys::put))
32
35
.layer(session_layer)
33
33
-
.with_state(app_state);
36
36
+
.with_state(app_state)
37
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
42
-
handle_resolver: Arc<AtprotoHandleResolver<HickoryDnsTxtResolver, DefaultHttpClient>>,
46
46
+
handle_resolver: Arc<AtprotoHandleResolver<dns::Resolver, DefaultHttpClient>>,
43
47
}
44
48
45
49
impl AppState {
···
72
76
})
73
77
}
74
78
75
75
-
fn handle_resolver<H: HttpClient>(
76
76
-
http_client: Arc<H>,
77
77
-
) -> AtprotoHandleResolver<HickoryDnsTxtResolver, H> {
79
79
+
fn handle_resolver<H: HttpClient>(http_client: Arc<H>) -> AtprotoHandleResolver<dns::Resolver, H> {
78
80
AtprotoHandleResolver::new(AtprotoHandleResolverConfig {
79
79
-
dns_txt_resolver: HickoryDnsTxtResolver::default(),
81
81
+
dns_txt_resolver: dns::Resolver::default(),
80
82
http_client,
81
83
})
82
84
}
83
83
-
84
84
-
mod login {
85
85
-
use axum::{extract::Form, http::StatusCode, response::IntoResponse};
86
86
-
use serde::Deserialize;
87
87
-
88
88
-
use super::*;
89
89
-
90
90
-
pub async fn get() -> String {
91
91
-
"hello world".to_owned()
92
92
-
}
93
93
-
94
94
-
#[derive(Deserialize, Debug)]
95
95
-
pub struct Req {
96
96
-
handle: AtIdentifier,
97
97
-
app_password: String,
98
98
-
}
99
99
-
100
100
-
pub async fn post(session: tower_sessions::Session, Form(req): Form<Req>) -> impl IntoResponse {
101
101
-
let agent = AtpAgent::new(
102
102
-
IsahcClient::new("https://dummy.example"),
103
103
-
MemorySessionStore::default(),
104
104
-
);
105
105
-
let res = agent.login(req.handle, req.app_password).await.unwrap();
106
106
-
println!("logged in as {:?} ({:?})", res.handle, res.did);
107
107
-
session.insert("at_session", res).await.unwrap();
108
108
-
println!("stored session");
109
109
-
StatusCode::OK
110
110
-
}
111
111
-
}
112
112
-
113
113
-
// dummy endpoint to test if sessions are working
114
114
-
mod index {
115
115
-
use super::*;
116
116
-
117
117
-
pub async fn get(session: tower_sessions::Session) -> &'static str {
118
118
-
match session
119
119
-
.get::<atrium_api::agent::Session>("at_session")
120
120
-
.await
121
121
-
.unwrap()
122
122
-
{
123
123
-
None => "no session",
124
124
-
Some(s) => {
125
125
-
let agent = AtpAgent::new(
126
126
-
IsahcClient::new("https://dummy.example"),
127
127
-
MemorySessionStore::default(),
128
128
-
);
129
129
-
println!("resuming session of {:?} ({:?})", s.handle, s.did);
130
130
-
agent.resume_session(s).await.unwrap();
131
131
-
"resuming session"
132
132
-
}
133
133
-
}
134
134
-
}
135
135
-
}
136
136
-
137
137
-
struct HickoryDnsTxtResolver {
138
138
-
resolver: TokioAsyncResolver,
139
139
-
}
140
140
-
141
141
-
impl Default for HickoryDnsTxtResolver {
142
142
-
fn default() -> Self {
143
143
-
Self {
144
144
-
resolver: TokioAsyncResolver::tokio_from_system_conf()
145
145
-
.expect("failed to create resolver"),
146
146
-
}
147
147
-
}
148
148
-
}
149
149
-
150
150
-
impl DnsTxtResolver for HickoryDnsTxtResolver {
151
151
-
async fn resolve(
152
152
-
&self,
153
153
-
query: &str,
154
154
-
) -> core::result::Result<Vec<String>, Box<dyn std::error::Error + Send + Sync + 'static>> {
155
155
-
Ok(self
156
156
-
.resolver
157
157
-
.txt_lookup(query)
158
158
-
.await?
159
159
-
.iter()
160
160
-
.map(|txt| txt.to_string())
161
161
-
.collect())
162
162
-
}
163
163
-
}
+135
core/src/routes.rs
···
1
1
+
pub mod login {
2
2
+
use atrium_api::{
3
3
+
agent::{AtpAgent, store::MemorySessionStore},
4
4
+
types::string::AtIdentifier,
5
5
+
};
6
6
+
use atrium_xrpc_client::isahc::IsahcClient;
7
7
+
use axum::{extract::Form, http::StatusCode, response::IntoResponse};
8
8
+
use serde::Deserialize;
9
9
+
10
10
+
pub async fn get() -> String {
11
11
+
"hello world".to_owned()
12
12
+
}
13
13
+
14
14
+
#[derive(Deserialize, Debug)]
15
15
+
pub struct Req {
16
16
+
handle: AtIdentifier,
17
17
+
app_password: String,
18
18
+
}
19
19
+
20
20
+
pub async fn post(session: tower_sessions::Session, Form(req): Form<Req>) -> impl IntoResponse {
21
21
+
let agent = AtpAgent::new(
22
22
+
IsahcClient::new("https://dummy.example"),
23
23
+
MemorySessionStore::default(),
24
24
+
);
25
25
+
let res = agent.login(req.handle, req.app_password).await.unwrap();
26
26
+
println!("logged in as {:?} ({:?})", res.handle, res.did);
27
27
+
session.insert("bild_session", res).await.unwrap();
28
28
+
println!("stored session");
29
29
+
StatusCode::OK
30
30
+
}
31
31
+
}
32
32
+
33
33
+
pub mod index {
34
34
+
use atrium_api::agent::{AtpAgent, store::MemorySessionStore};
35
35
+
use atrium_xrpc_client::isahc::IsahcClient;
36
36
+
37
37
+
pub async fn get(session: tower_sessions::Session) -> &'static str {
38
38
+
match session
39
39
+
.get::<atrium_api::agent::Session>("bild_session")
40
40
+
.await
41
41
+
.unwrap()
42
42
+
{
43
43
+
None => "no session",
44
44
+
Some(s) => {
45
45
+
let agent = AtpAgent::new(
46
46
+
IsahcClient::new("https://dummy.example"),
47
47
+
MemorySessionStore::default(),
48
48
+
);
49
49
+
println!("resuming session of {:?} ({:?})", s.handle, s.did);
50
50
+
agent.resume_session(s).await.unwrap();
51
51
+
"resuming session"
52
52
+
}
53
53
+
}
54
54
+
}
55
55
+
}
56
56
+
57
57
+
pub mod keys {
58
58
+
use crate::{AppState, db};
59
59
+
use atrium_api::types::string::AtIdentifier;
60
60
+
use axum::http::StatusCode;
61
61
+
use axum::{
62
62
+
extract::{Extension, Json, Query, State},
63
63
+
response::IntoResponse,
64
64
+
};
65
65
+
use serde::{Deserialize, Serialize};
66
66
+
use std::sync::Arc;
67
67
+
68
68
+
#[derive(Deserialize)]
69
69
+
pub struct GetReq {
70
70
+
id: AtIdentifier,
71
71
+
}
72
72
+
73
73
+
#[derive(Serialize)]
74
74
+
pub struct GetRes {
75
75
+
id: AtIdentifier,
76
76
+
keys: Vec<String>,
77
77
+
}
78
78
+
79
79
+
pub async fn get(
80
80
+
Extension(db): Extension<Arc<db::Db>>,
81
81
+
State(state): State<AppState>,
82
82
+
Query(req): Query<GetReq>,
83
83
+
) -> impl IntoResponse {
84
84
+
let did_doc = state.resolve_did_document(&req.id).await.unwrap();
85
85
+
let keys = db
86
86
+
.conn
87
87
+
.call(|c| {
88
88
+
let mut stmt = c.prepare("SELECT key FROM keys WHERE did = ?1")?;
89
89
+
let keys = stmt
90
90
+
.query_map([did_doc.id], |row| Ok(row.get::<usize, String>(0)?))?
91
91
+
.filter_map(|r| r.ok())
92
92
+
.collect::<Vec<_>>();
93
93
+
Ok(keys)
94
94
+
})
95
95
+
.await
96
96
+
.unwrap();
97
97
+
Json(GetRes {
98
98
+
id: req.id.clone(),
99
99
+
keys,
100
100
+
})
101
101
+
}
102
102
+
103
103
+
#[derive(Deserialize)]
104
104
+
pub struct PutReq {
105
105
+
key: String,
106
106
+
}
107
107
+
108
108
+
pub async fn put(
109
109
+
Extension(db): Extension<Arc<db::Db>>,
110
110
+
session: tower_sessions::Session,
111
111
+
Json(req): Json<PutReq>,
112
112
+
) -> impl IntoResponse {
113
113
+
if openssh_keys::PublicKey::parse(&req.key).is_err() {
114
114
+
return StatusCode::BAD_REQUEST;
115
115
+
}
116
116
+
match session
117
117
+
.get::<atrium_api::agent::Session>("bild_session")
118
118
+
.await
119
119
+
.unwrap()
120
120
+
{
121
121
+
None => StatusCode::UNAUTHORIZED,
122
122
+
Some(sess) => {
123
123
+
let did = sess.did.clone().into();
124
124
+
db.conn
125
125
+
.call(|c| {
126
126
+
c.execute("INSERT INTO keys VALUES (?1, ?2)", [did, req.key])?;
127
127
+
Ok(())
128
128
+
})
129
129
+
.await
130
130
+
.unwrap();
131
131
+
StatusCode::OK
132
132
+
}
133
133
+
}
134
134
+
}
135
135
+
}