this repo has no description
1use crate::oauth::jwks::{JwkSet, create_jwk_set};
2use crate::state::AppState;
3use axum::{Json, extract::State};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Serialize, Deserialize)]
7pub struct ProtectedResourceMetadata {
8 pub resource: String,
9 pub authorization_servers: Vec<String>,
10 pub bearer_methods_supported: Vec<String>,
11 pub scopes_supported: Vec<String>,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub resource_documentation: Option<String>,
14}
15
16#[derive(Debug, Serialize, Deserialize)]
17pub struct AuthorizationServerMetadata {
18 pub issuer: String,
19 pub authorization_endpoint: String,
20 pub token_endpoint: String,
21 pub jwks_uri: String,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub registration_endpoint: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 pub scopes_supported: Option<Vec<String>>,
26 pub response_types_supported: Vec<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub response_modes_supported: Option<Vec<String>>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub grant_types_supported: Option<Vec<String>>,
31 #[serde(skip_serializing_if = "Option::is_none")]
32 pub token_endpoint_auth_methods_supported: Option<Vec<String>>,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<String>>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub code_challenge_methods_supported: Option<Vec<String>>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub pushed_authorization_request_endpoint: Option<String>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub require_pushed_authorization_requests: Option<bool>,
41 #[serde(skip_serializing_if = "Option::is_none")]
42 pub require_request_uri_registration: Option<bool>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub dpop_signing_alg_values_supported: Option<Vec<String>>,
45 #[serde(skip_serializing_if = "Option::is_none")]
46 pub authorization_response_iss_parameter_supported: Option<bool>,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub revocation_endpoint: Option<String>,
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub introspection_endpoint: Option<String>,
51 #[serde(skip_serializing_if = "Option::is_none")]
52 pub client_id_metadata_document_supported: Option<bool>,
53}
54
55pub async fn oauth_protected_resource(
56 State(_state): State<AppState>,
57) -> Json<ProtectedResourceMetadata> {
58 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
59 let public_url = format!("https://{}", pds_hostname);
60 Json(ProtectedResourceMetadata {
61 resource: public_url.clone(),
62 authorization_servers: vec![public_url],
63 bearer_methods_supported: vec!["header".to_string()],
64 scopes_supported: vec![],
65 resource_documentation: Some("https://atproto.com".to_string()),
66 })
67}
68
69pub async fn oauth_authorization_server(
70 State(_state): State<AppState>,
71) -> Json<AuthorizationServerMetadata> {
72 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
73 let issuer = format!("https://{}", pds_hostname);
74 Json(AuthorizationServerMetadata {
75 issuer: issuer.clone(),
76 authorization_endpoint: format!("{}/oauth/authorize", issuer),
77 token_endpoint: format!("{}/oauth/token", issuer),
78 jwks_uri: format!("{}/oauth/jwks", issuer),
79 registration_endpoint: None,
80 scopes_supported: Some(vec![
81 "atproto".to_string(),
82 "transition:generic".to_string(),
83 "transition:chat.bsky".to_string(),
84 "repo:*".to_string(),
85 "repo:*?action=create".to_string(),
86 "repo:*?action=read".to_string(),
87 "repo:*?action=update".to_string(),
88 "repo:*?action=delete".to_string(),
89 "blob:*/*".to_string(),
90 "rpc:*".to_string(),
91 "account:*".to_string(),
92 "account:*?action=read".to_string(),
93 "account:*?action=write".to_string(),
94 "identity:*".to_string(),
95 ]),
96 response_types_supported: vec!["code".to_string()],
97 response_modes_supported: Some(vec!["query".to_string(), "fragment".to_string()]),
98 grant_types_supported: Some(vec![
99 "authorization_code".to_string(),
100 "refresh_token".to_string(),
101 ]),
102 token_endpoint_auth_methods_supported: Some(vec![
103 "none".to_string(),
104 "private_key_jwt".to_string(),
105 ]),
106 token_endpoint_auth_signing_alg_values_supported: Some(vec![
107 "ES256".to_string(),
108 "ES384".to_string(),
109 "ES512".to_string(),
110 "EdDSA".to_string(),
111 ]),
112 code_challenge_methods_supported: Some(vec!["S256".to_string()]),
113 pushed_authorization_request_endpoint: Some(format!("{}/oauth/par", issuer)),
114 require_pushed_authorization_requests: Some(true),
115 require_request_uri_registration: Some(true),
116 dpop_signing_alg_values_supported: Some(vec![
117 "ES256".to_string(),
118 "ES384".to_string(),
119 "ES512".to_string(),
120 "EdDSA".to_string(),
121 ]),
122 authorization_response_iss_parameter_supported: Some(true),
123 revocation_endpoint: Some(format!("{}/oauth/revoke", issuer)),
124 introspection_endpoint: Some(format!("{}/oauth/introspect", issuer)),
125 client_id_metadata_document_supported: Some(true),
126 })
127}
128
129pub async fn oauth_jwks(State(_state): State<AppState>) -> Json<JwkSet> {
130 use crate::config::AuthConfig;
131 use crate::oauth::jwks::Jwk;
132 let config = AuthConfig::get();
133 let server_key = Jwk {
134 kty: "EC".to_string(),
135 key_use: Some("sig".to_string()),
136 kid: Some(config.signing_key_id.clone()),
137 alg: Some("ES256".to_string()),
138 crv: Some("P-256".to_string()),
139 x: Some(config.signing_key_x.clone()),
140 y: Some(config.signing_key_y.clone()),
141 };
142 Json(create_jwk_set(vec![server_key]))
143}
144
145#[derive(Debug, Serialize, Deserialize)]
146pub struct FrontendClientMetadata {
147 pub client_id: String,
148 pub client_name: String,
149 pub client_uri: String,
150 pub redirect_uris: Vec<String>,
151 pub grant_types: Vec<String>,
152 pub response_types: Vec<String>,
153 pub scope: String,
154 pub token_endpoint_auth_method: String,
155 pub application_type: String,
156 pub dpop_bound_access_tokens: bool,
157}
158
159pub async fn frontend_client_metadata(
160 State(_state): State<AppState>,
161) -> Json<FrontendClientMetadata> {
162 let pds_hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
163 let base_url = format!("https://{}", pds_hostname);
164 let client_id = format!("{}/oauth/client-metadata.json", base_url);
165 Json(FrontendClientMetadata {
166 client_id,
167 client_name: "PDS Account Manager".to_string(),
168 client_uri: base_url.clone(),
169 redirect_uris: vec![format!("{}/", base_url)],
170 grant_types: vec![
171 "authorization_code".to_string(),
172 "refresh_token".to_string(),
173 ],
174 response_types: vec!["code".to_string()],
175 scope: "atproto repo:*?action=create repo:*?action=update repo:*?action=delete blob:*/*".to_string(),
176 token_endpoint_auth_method: "none".to_string(),
177 application_type: "web".to_string(),
178 dpop_bound_access_tokens: true,
179 })
180}