Auto-indexing service and GraphQL API for AT Protocol Records
quickslice.slices.network/
atproto
gleam
graphql
1/// OAuth server metadata endpoint handler
2/// GET /.well-known/oauth-authorization-server
3import gleam/json
4import wisp
5
6pub type ServerMetadata {
7 ServerMetadata(
8 issuer: String,
9 authorization_endpoint: String,
10 token_endpoint: String,
11 jwks_uri: String,
12 registration_endpoint: String,
13 scopes_supported: List(String),
14 response_types_supported: List(String),
15 grant_types_supported: List(String),
16 token_endpoint_auth_methods_supported: List(String),
17 code_challenge_methods_supported: List(String),
18 pushed_authorization_request_endpoint: String,
19 dpop_signing_alg_values_supported: List(String),
20 )
21}
22
23/// Generate server metadata for the given base URL
24pub fn generate_metadata(
25 base_url: String,
26 scopes_supported: List(String),
27) -> ServerMetadata {
28 ServerMetadata(
29 issuer: base_url,
30 authorization_endpoint: base_url <> "/oauth/authorize",
31 token_endpoint: base_url <> "/oauth/token",
32 jwks_uri: base_url <> "/.well-known/jwks.json",
33 registration_endpoint: base_url <> "/oauth/register",
34 scopes_supported: scopes_supported,
35 response_types_supported: ["code"],
36 grant_types_supported: ["authorization_code", "refresh_token"],
37 token_endpoint_auth_methods_supported: [
38 "client_secret_basic",
39 "client_secret_post",
40 "none",
41 ],
42 code_challenge_methods_supported: ["S256"],
43 pushed_authorization_request_endpoint: base_url <> "/oauth/par",
44 dpop_signing_alg_values_supported: ["ES256"],
45 )
46}
47
48/// Encode metadata as JSON
49pub fn encode_metadata(meta: ServerMetadata) -> json.Json {
50 json.object([
51 #("issuer", json.string(meta.issuer)),
52 #("authorization_endpoint", json.string(meta.authorization_endpoint)),
53 #("token_endpoint", json.string(meta.token_endpoint)),
54 #("jwks_uri", json.string(meta.jwks_uri)),
55 #("registration_endpoint", json.string(meta.registration_endpoint)),
56 #("scopes_supported", json.array(meta.scopes_supported, json.string)),
57 #(
58 "response_types_supported",
59 json.array(meta.response_types_supported, json.string),
60 ),
61 #(
62 "grant_types_supported",
63 json.array(meta.grant_types_supported, json.string),
64 ),
65 #(
66 "token_endpoint_auth_methods_supported",
67 json.array(meta.token_endpoint_auth_methods_supported, json.string),
68 ),
69 #(
70 "code_challenge_methods_supported",
71 json.array(meta.code_challenge_methods_supported, json.string),
72 ),
73 #(
74 "pushed_authorization_request_endpoint",
75 json.string(meta.pushed_authorization_request_endpoint),
76 ),
77 #(
78 "dpop_signing_alg_values_supported",
79 json.array(meta.dpop_signing_alg_values_supported, json.string),
80 ),
81 ])
82}
83
84/// Handle GET /.well-known/oauth-authorization-server
85pub fn handle(base_url: String, scopes_supported: List(String)) -> wisp.Response {
86 let meta = generate_metadata(base_url, scopes_supported)
87 let json_response = encode_metadata(meta)
88
89 wisp.response(200)
90 |> wisp.set_header("content-type", "application/json")
91 |> wisp.set_body(wisp.Text(json.to_string(json_response)))
92}