alpha
Login
or
Join now
microcosm.blue
/
microcosm-rs
Star
0
Fork
3
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
Star
0
Fork
3
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
wip, prompting flow with cookie
author
phil
date
1 year ago
(Jun 25, 2025, 8:56 PM -0400)
commit
b9be4800
b9be4800d85e9a6f1468ca0dba2ad77ecf73094c
parent
56812cb0
56812cb05590435148bbf12a10cbb3ba622829f9
+70
-11
4 changed files
Expand all
Collapse all
Unified
Split
Cargo.lock
who-am-i
Cargo.toml
src
server.rs
static
login.html
+18
Cargo.lock
Reviewed
···
469
469
"axum",
470
470
"axum-core",
471
471
"bytes",
472
472
+
"cookie",
472
473
"futures-util",
473
474
"headers",
474
475
"http",
···
918
919
"tower-http",
919
920
"tungstenite 0.26.2",
920
921
"zstd",
922
922
+
]
923
923
+
924
924
+
[[package]]
925
925
+
name = "cookie"
926
926
+
version = "0.18.1"
927
927
+
source = "registry+https://github.com/rust-lang/crates.io-index"
928
928
+
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
929
929
+
dependencies = [
930
930
+
"base64 0.22.1",
931
931
+
"hmac",
932
932
+
"percent-encoding",
933
933
+
"rand 0.8.5",
934
934
+
"sha2",
935
935
+
"subtle",
936
936
+
"time",
937
937
+
"version_check",
921
938
]
922
939
923
940
[[package]]
···
4851
4868
"atrium-identity",
4852
4869
"atrium-oauth",
4853
4870
"axum",
4871
4871
+
"axum-extra",
4854
4872
"clap",
4855
4873
"hickory-resolver",
4856
4874
"metrics",
+1
who-am-i/Cargo.toml
Reviewed
···
8
8
atrium-identity = "0.1.5"
9
9
atrium-oauth = "0.1.3"
10
10
axum = "0.8.4"
11
11
+
axum-extra = { version = "0.10.1", features = ["cookie-signed", "typed-header"] }
11
12
clap = { version = "4.5.40", features = ["derive"] }
12
13
hickory-resolver = "0.25.2"
13
14
metrics = "0.24.2"
+34
-11
who-am-i/src/server.rs
Reviewed
···
2
2
use atrium_oauth::CallbackParams;
3
3
use axum::{
4
4
Router,
5
5
-
extract::{Query, State},
5
5
+
extract::{FromRef, Query, State},
6
6
response::{Html, Redirect},
7
7
routing::get,
8
8
};
9
9
+
use axum_extra::extract::cookie::{Cookie, Key, SignedCookieJar};
9
10
10
11
use serde::Deserialize;
11
12
use std::sync::Arc;
···
14
15
15
16
use crate::{Client, authorize, client};
16
17
18
18
+
const FAVICON: &[u8] = include_bytes!("../static/favicon.ico");
17
19
const INDEX_HTML: &str = include_str!("../static/index.html");
18
18
-
const FAVICON: &[u8] = include_bytes!("../static/favicon.ico");
20
20
+
const LOGIN_HTML: &str = include_str!("../static/login.html");
19
21
20
22
pub async fn serve(shutdown: CancellationToken) {
21
23
let state = AppState {
24
24
+
key: Key::generate(), // TODO: via config
22
25
client: Arc::new(client()),
23
26
};
24
27
25
28
let app = Router::new()
26
29
.route("/", get(|| async { Html(INDEX_HTML) }))
27
30
.route("/favicon.ico", get(|| async { FAVICON })) // todo MIME
31
31
+
.route("/prompt", get(prompt))
28
32
.route("/auth", get(start_oauth))
29
33
.route("/authorized", get(complete_oauth))
30
34
.with_state(state);
···
41
45
42
46
#[derive(Clone)]
43
47
struct AppState {
48
48
+
pub key: Key,
44
49
pub client: Arc<Client>,
45
50
}
46
51
52
52
+
impl FromRef<AppState> for Key {
53
53
+
fn from_ref(state: &AppState) -> Self {
54
54
+
state.key.clone()
55
55
+
}
56
56
+
}
57
57
+
58
58
+
async fn prompt(jar: SignedCookieJar) -> (SignedCookieJar, Html<String>) {
59
59
+
let m = if let Some(did) = jar.get("did") {
60
60
+
format!("oh i know you: {did}")
61
61
+
} else {
62
62
+
LOGIN_HTML.into()
63
63
+
};
64
64
+
(jar, Html(m))
65
65
+
}
66
66
+
47
67
#[derive(Debug, Deserialize)]
48
68
struct BeginOauthParams {
49
69
handle: String,
···
51
71
async fn start_oauth(
52
72
State(state): State<AppState>,
53
73
Query(params): Query<BeginOauthParams>,
54
54
-
) -> Redirect {
55
55
-
let AppState { client } = state;
56
56
-
let BeginOauthParams { handle } = params;
57
57
-
let auth_url = authorize(&client, &handle).await;
58
58
-
Redirect::to(&auth_url)
74
74
+
jar: SignedCookieJar,
75
75
+
) -> (SignedCookieJar, Redirect) {
76
76
+
// if any existing session was active, clear it first
77
77
+
let jar = jar.remove("did");
78
78
+
79
79
+
let auth_url = authorize(&state.client, ¶ms.handle).await;
80
80
+
(jar, Redirect::to(&auth_url))
59
81
}
60
82
61
83
async fn complete_oauth(
62
84
State(state): State<AppState>,
63
85
Query(params): Query<CallbackParams>,
64
64
-
) -> Html<String> {
65
65
-
let AppState { client } = state;
66
66
-
let Ok((oauth_session, _)) = client.callback(params).await else {
86
86
+
jar: SignedCookieJar,
87
87
+
) -> (SignedCookieJar, Html<String>) {
88
88
+
let Ok((oauth_session, _)) = state.client.callback(params).await else {
67
89
panic!("failed to do client callback");
68
90
};
69
91
let did = oauth_session.did().await.expect("a did to be present");
70
70
-
Html(format!("sup: {did:?}"))
92
92
+
let jar = jar.add(Cookie::new("did", did.to_string()));
93
93
+
(jar, Html(format!("sup: {did:?}")))
71
94
}
+17
who-am-i/static/login.html
Reviewed
···
1
1
+
<!doctype html>
2
2
+
<html lang="en">
3
3
+
<head>
4
4
+
<meta charset="utf-8" />
5
5
+
<title>Who-am-i</title>
6
6
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7
7
+
<meta name="description" content="Log in" />
8
8
+
</head>
9
9
+
<body>
10
10
+
<form action="/auth" method="GET">
11
11
+
<label>
12
12
+
@<input name="handle" placeholder="example.bsky.social" />
13
13
+
</label>
14
14
+
<button type="submit">log in</button>
15
15
+
</form>
16
16
+
</body>
17
17
+
</html>