Auto-indexing service and GraphQL API for AT Protocol Records
quickslice.slices.network/
atproto
gleam
graphql
1/// Client metadata endpoint for ATProtocol OAuth
2/// GET /oauth-client-metadata.json
3import gleam/json
4import gleam/option.{type Option, None, Some}
5import wisp
6
7/// Client metadata response
8pub type ClientMetadataResponse {
9 ClientMetadataResponse(
10 client_id: String,
11 client_name: String,
12 redirect_uris: List(String),
13 grant_types: List(String),
14 response_types: List(String),
15 scope: String,
16 token_endpoint_auth_method: String,
17 token_endpoint_auth_signing_alg: String,
18 subject_type: String,
19 application_type: String,
20 dpop_bound_access_tokens: Bool,
21 jwks: Option(json.Json),
22 jwks_uri: Option(String),
23 )
24}
25
26/// Generate client metadata
27pub fn generate_metadata(
28 client_id: String,
29 client_name: String,
30 redirect_uris: List(String),
31 scope: String,
32 jwks: Option(json.Json),
33 jwks_uri: Option(String),
34) -> ClientMetadataResponse {
35 ClientMetadataResponse(
36 client_id: client_id,
37 client_name: client_name,
38 redirect_uris: redirect_uris,
39 grant_types: ["authorization_code", "refresh_token"],
40 response_types: ["code"],
41 scope: scope,
42 token_endpoint_auth_method: "private_key_jwt",
43 token_endpoint_auth_signing_alg: "ES256",
44 subject_type: "public",
45 application_type: "web",
46 dpop_bound_access_tokens: True,
47 jwks: jwks,
48 jwks_uri: jwks_uri,
49 )
50}
51
52/// Encode client metadata as JSON
53pub fn encode_metadata(metadata: ClientMetadataResponse) -> json.Json {
54 let base_fields = [
55 #("client_id", json.string(metadata.client_id)),
56 #("client_name", json.string(metadata.client_name)),
57 #("redirect_uris", json.array(metadata.redirect_uris, json.string)),
58 #("grant_types", json.array(metadata.grant_types, json.string)),
59 #("response_types", json.array(metadata.response_types, json.string)),
60 #("scope", json.string(metadata.scope)),
61 #(
62 "token_endpoint_auth_method",
63 json.string(metadata.token_endpoint_auth_method),
64 ),
65 #(
66 "token_endpoint_auth_signing_alg",
67 json.string(metadata.token_endpoint_auth_signing_alg),
68 ),
69 #("subject_type", json.string(metadata.subject_type)),
70 #("application_type", json.string(metadata.application_type)),
71 #("dpop_bound_access_tokens", json.bool(metadata.dpop_bound_access_tokens)),
72 ]
73
74 let with_jwks = case metadata.jwks {
75 Some(jwks) -> [#("jwks", jwks), ..base_fields]
76 None -> base_fields
77 }
78
79 let with_jwks_uri = case metadata.jwks_uri {
80 Some(uri) -> [#("jwks_uri", json.string(uri)), ..with_jwks]
81 None -> with_jwks
82 }
83
84 json.object(with_jwks_uri)
85}
86
87/// Handle GET /oauth-client-metadata.json
88pub fn handle(
89 base_url: String,
90 client_name: String,
91 redirect_uris: List(String),
92 scope: String,
93 jwks: Option(json.Json),
94 jwks_uri: Option(String),
95) -> wisp.Response {
96 let client_id = base_url <> "/oauth-client-metadata.json"
97 let metadata =
98 generate_metadata(
99 client_id,
100 client_name,
101 redirect_uris,
102 scope,
103 jwks,
104 jwks_uri,
105 )
106 let json_response = encode_metadata(metadata)
107
108 wisp.response(200)
109 |> wisp.set_header("content-type", "application/json")
110 |> wisp.set_body(wisp.Text(json.to_string(json_response)))
111}