tangled
alpha
login
or
join now
hotsocket.fyi
/
microcosm-rs
forked from
microcosm.blue/microcosm-rs
0
fork
atom
Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
0
fork
atom
overview
issues
pulls
pipelines
serve jwks for token validation
bad-example.com
8 months ago
bf9a8fe5
65d7a109
+58
-17
4 changed files
expand all
collapse all
unified
split
who-am-i
.gitignore
src
jwt.rs
main.rs
server.rs
+2
-1
who-am-i/.gitignore
···
1
-
jwt-key.pem
0
···
1
+
*.pem
2
+
jwks.json
+28
-9
who-am-i/src/jwt.rs
···
3
use std::fs;
4
use std::io::Error as IOError;
5
use std::path::Path;
0
6
use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
use thiserror::Error;
8
9
#[derive(Debug, Error)]
10
pub enum TokensSetupError {
11
-
#[error(transparent)]
12
-
Io(#[from] IOError),
13
-
#[error("failed to retrieve ec key: {0}")]
14
-
FromEc(JWTError),
0
0
0
0
15
}
16
17
#[derive(Debug, Error)]
18
pub enum TokenMintingError {
19
#[error("failed to mint: {0}")]
20
-
FromEc(#[from] JWTError),
21
}
22
23
pub struct Tokens {
24
encoding_key: EncodingKey,
0
25
}
26
27
impl Tokens {
28
-
pub fn from_file(f: impl AsRef<Path>) -> Result<Self, TokensSetupError> {
29
-
let data: Vec<u8> = fs::read(f)?;
30
-
let encoding_key = EncodingKey::from_ec_pem(&data).map_err(TokensSetupError::FromEc)?;
31
-
Ok(Self { encoding_key })
0
0
0
0
0
0
0
0
0
32
}
33
34
pub fn mint(&self, t: impl ToString) -> Result<String, TokenMintingError> {
···
45
&Claims { sub, exp },
46
&self.encoding_key,
47
)?)
0
0
0
0
48
}
49
}
50
···
3
use std::fs;
4
use std::io::Error as IOError;
5
use std::path::Path;
6
+
use std::string::FromUtf8Error;
7
use std::time::{Duration, SystemTime, UNIX_EPOCH};
8
use thiserror::Error;
9
10
#[derive(Debug, Error)]
11
pub enum TokensSetupError {
12
+
#[error("failed to read private key")]
13
+
ReadPrivateKey(IOError),
14
+
#[error("failed to retrieve private key: {0}")]
15
+
PrivateKey(JWTError),
16
+
#[error("failed to read private key")]
17
+
ReadJwks(IOError),
18
+
#[error("failed to retrieve jwks: {0}")]
19
+
DecodeJwks(FromUtf8Error),
20
}
21
22
#[derive(Debug, Error)]
23
pub enum TokenMintingError {
24
#[error("failed to mint: {0}")]
25
+
EncodingError(#[from] JWTError),
26
}
27
28
pub struct Tokens {
29
encoding_key: EncodingKey,
30
+
jwks: String,
31
}
32
33
impl Tokens {
34
+
pub fn from_files(
35
+
priv_f: impl AsRef<Path>,
36
+
jwks_f: impl AsRef<Path>,
37
+
) -> Result<Self, TokensSetupError> {
38
+
let private_key_data: Vec<u8> =
39
+
fs::read(priv_f).map_err(TokensSetupError::ReadPrivateKey)?;
40
+
let encoding_key =
41
+
EncodingKey::from_ec_pem(&private_key_data).map_err(TokensSetupError::PrivateKey)?;
42
+
43
+
let jwks_data: Vec<u8> = fs::read(jwks_f).map_err(TokensSetupError::ReadJwks)?;
44
+
let jwks = String::from_utf8(jwks_data).map_err(TokensSetupError::DecodeJwks)?;
45
+
46
+
Ok(Self { encoding_key, jwks })
47
}
48
49
pub fn mint(&self, t: impl ToString) -> Result<String, TokenMintingError> {
···
60
&Claims { sub, exp },
61
&self.encoding_key,
62
)?)
63
+
}
64
+
65
+
pub fn jwks(&self) -> String {
66
+
self.jwks.clone()
67
}
68
}
69
+19
-7
who-am-i/src/main.rs
···
15
/// eg: `cat /dev/urandom | head -c 64 | base64`
16
#[arg(long, env)]
17
app_secret: String,
18
-
/// path to jwt key (PEM format)
19
///
20
/// generate with:
21
-
/// ```bash
22
-
/// openssl ecparam -genkey -noout -name prime256v1 \
23
-
/// | openssl pkcs8 -topk8 -nocrypt -out <PATH-TO-JWT-KEY>.pem
24
-
/// ```
25
#[arg(long)]
26
-
jwt_key: PathBuf,
0
0
0
0
0
0
0
0
0
0
0
0
0
27
/// Enable dev mode
28
///
29
/// enables automatic template reloading
···
54
println!(" - {host}");
55
}
56
57
-
let tokens = Tokens::from_file(args.jwt_key).unwrap();
58
59
if let Err(e) = install_metrics_server() {
60
eprintln!("failed to install metrics server: {e:?}");
···
15
/// eg: `cat /dev/urandom | head -c 64 | base64`
16
#[arg(long, env)]
17
app_secret: String,
18
+
/// path to jwt private key (PEM pk8 format)
19
///
20
/// generate with:
21
+
///
22
+
/// openssl ecparam -genkey -noout -name prime256v1 \
23
+
/// | openssl pkcs8 -topk8 -nocrypt -out <PATH-TO-PRIV-KEY>.pem
0
24
#[arg(long)]
25
+
jwt_private_key: PathBuf,
26
+
/// path to pubkeys file (jwks format)
27
+
///
28
+
/// get pem of pubkey from private key with:
29
+
///
30
+
/// openssl ec -in <PATH-TO-PRIV-KEY>.pem -pubout
31
+
///
32
+
/// then convert to a jwk, probably with something less sketchy than an [online tool](https://jwkset.com/generate)
33
+
///
34
+
/// wrap the jwk in an array, then in an object under "keys":
35
+
///
36
+
/// { "keys": [<JWK obj>] }
37
+
#[arg(long)]
38
+
jwks: PathBuf,
39
/// Enable dev mode
40
///
41
/// enables automatic template reloading
···
66
println!(" - {host}");
67
}
68
69
+
let tokens = Tokens::from_files(args.jwt_private_key, args.jwks).unwrap();
70
71
if let Err(e) = install_metrics_server() {
72
eprintln!("failed to install metrics server: {e:?}");
+9
who-am-i/src/server.rs
···
91
.route("/auth", get(start_oauth))
92
.route("/authorized", get(complete_oauth))
93
.route("/disconnect", post(disconnect))
0
94
.with_state(state);
95
96
let listener = TcpListener::bind("0.0.0.0:9997")
···
437
let jar = jar.remove(DID_COOKIE_KEY);
438
(jar, Json(json!({ "ok": true })))
439
}
0
0
0
0
0
0
0
0
···
91
.route("/auth", get(start_oauth))
92
.route("/authorized", get(complete_oauth))
93
.route("/disconnect", post(disconnect))
94
+
.route("/.well-known/jwks.json", get(jwks))
95
.with_state(state);
96
97
let listener = TcpListener::bind("0.0.0.0:9997")
···
438
let jar = jar.remove(DID_COOKIE_KEY);
439
(jar, Json(json!({ "ok": true })))
440
}
441
+
442
+
async fn jwks(State(AppState { tokens, .. }): State<AppState>) -> impl IntoResponse {
443
+
let headers = [
444
+
(CONTENT_TYPE, "application/json"),
445
+
// (CACHE_CONTROL, "") // TODO
446
+
];
447
+
(headers, tokens.jwks())
448
+
}