@atcute/oauth-types 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
1
  import type { Keyset } from '@atcute/oauth-keyset';
2
2
  /**
3
- * builds an atproto client metadata
4
- *
3
+ * builds an atproto client metadata for a confidential client.
5
4
  *
6
5
  * @param input client metadata
7
6
  * @param keyset available keys
@@ -165,4 +164,178 @@ export declare const buildClientMetadata: (input: {
165
164
  dpop_bound_access_tokens?: boolean | undefined;
166
165
  authorization_details_types?: string[] | undefined;
167
166
  };
167
+ /**
168
+ * builds an atproto client metadata for a public client.
169
+ *
170
+ * public clients use `token_endpoint_auth_method: 'none'` and don't require a keyset.
171
+ * per AT Protocol spec, they have shorter token lifetimes and cannot use silent sign-in.
172
+ *
173
+ * - if `client_id` is omitted: loopback client (client_id built from redirect_uris/scope)
174
+ * - if `client_id` is provided: discoverable public client
175
+ *
176
+ * @param input public client metadata
177
+ * @returns built client metadata
178
+ */
179
+ export declare const buildPublicClientMetadata: (input: {
180
+ client_id?: undefined;
181
+ redirect_uris: string[];
182
+ scope: string | string[];
183
+ } | {
184
+ client_id: string;
185
+ redirect_uris: string[];
186
+ scope: string | string[];
187
+ application_type?: "native" | "web" | undefined;
188
+ client_uri?: string | undefined;
189
+ client_name?: string | undefined;
190
+ policy_uri?: string | undefined;
191
+ tos_uri?: string | undefined;
192
+ logo_uri?: string | undefined;
193
+ }) => {
194
+ redirect_uris: string[];
195
+ response_types?: ("code" | "code id_token" | "code id_token token" | "code token" | "id_token" | "id_token token" | "none" | "token")[] | undefined;
196
+ grant_types?: ("authorization_code" | "client_credentials" | "implicit" | "password" | "refresh_token" | "urn:ietf:params:oauth:grant-type:jwt-bearer" | "urn:ietf:params:oauth:grant-type:saml2-bearer")[] | undefined;
197
+ scope?: string | undefined;
198
+ token_endpoint_auth_method?: "client_secret_basic" | "client_secret_jwt" | "client_secret_post" | "none" | "private_key_jwt" | "self_signed_tls_client_auth" | "tls_client_auth" | undefined;
199
+ token_endpoint_auth_signing_alg?: string | undefined;
200
+ userinfo_signed_response_alg?: string | undefined;
201
+ userinfo_encrypted_response_alg?: string | undefined;
202
+ jwks_uri?: string | undefined;
203
+ jwks?: {
204
+ keys: ({
205
+ kid?: string | undefined;
206
+ use?: "enc" | "sig" | undefined;
207
+ key_ops?: ("decrypt" | "deriveBits" | "deriveKey" | "encrypt" | "sign" | "unwrapKey" | "verify" | "wrapKey")[] | undefined;
208
+ x5c?: string[] | undefined;
209
+ x5t?: string | undefined;
210
+ 'x5t#S256'?: string | undefined;
211
+ x5u?: string | undefined;
212
+ ext?: boolean | undefined;
213
+ iat?: number | undefined;
214
+ exp?: number | undefined;
215
+ nbf?: number | undefined;
216
+ revoked?: {
217
+ revoked_at: number;
218
+ reason?: string | undefined;
219
+ } | undefined;
220
+ kty: "RSA";
221
+ alg?: "PS256" | "PS384" | "PS512" | "RS256" | "RS384" | "RS512" | undefined;
222
+ n: string;
223
+ e: string;
224
+ d?: string | undefined;
225
+ p?: string | undefined;
226
+ q?: string | undefined;
227
+ dp?: string | undefined;
228
+ dq?: string | undefined;
229
+ qi?: string | undefined;
230
+ oth?: {
231
+ r?: string | undefined;
232
+ d?: string | undefined;
233
+ t?: string | undefined;
234
+ }[] | undefined;
235
+ } | {
236
+ kid?: string | undefined;
237
+ use?: "enc" | "sig" | undefined;
238
+ key_ops?: ("decrypt" | "deriveBits" | "deriveKey" | "encrypt" | "sign" | "unwrapKey" | "verify" | "wrapKey")[] | undefined;
239
+ x5c?: string[] | undefined;
240
+ x5t?: string | undefined;
241
+ 'x5t#S256'?: string | undefined;
242
+ x5u?: string | undefined;
243
+ ext?: boolean | undefined;
244
+ iat?: number | undefined;
245
+ exp?: number | undefined;
246
+ nbf?: number | undefined;
247
+ revoked?: {
248
+ revoked_at: number;
249
+ reason?: string | undefined;
250
+ } | undefined;
251
+ kty: "EC";
252
+ alg?: "ES256" | "ES384" | "ES512" | undefined;
253
+ crv: "P-256" | "P-384" | "P-521";
254
+ x: string;
255
+ y: string;
256
+ d?: string | undefined;
257
+ } | {
258
+ kid?: string | undefined;
259
+ use?: "enc" | "sig" | undefined;
260
+ key_ops?: ("decrypt" | "deriveBits" | "deriveKey" | "encrypt" | "sign" | "unwrapKey" | "verify" | "wrapKey")[] | undefined;
261
+ x5c?: string[] | undefined;
262
+ x5t?: string | undefined;
263
+ 'x5t#S256'?: string | undefined;
264
+ x5u?: string | undefined;
265
+ ext?: boolean | undefined;
266
+ iat?: number | undefined;
267
+ exp?: number | undefined;
268
+ nbf?: number | undefined;
269
+ revoked?: {
270
+ revoked_at: number;
271
+ reason?: string | undefined;
272
+ } | undefined;
273
+ kty: "EC";
274
+ alg?: "ES256K" | undefined;
275
+ crv: "secp256k1";
276
+ x: string;
277
+ y: string;
278
+ d?: string | undefined;
279
+ } | {
280
+ kid?: string | undefined;
281
+ use?: "enc" | "sig" | undefined;
282
+ key_ops?: ("decrypt" | "deriveBits" | "deriveKey" | "encrypt" | "sign" | "unwrapKey" | "verify" | "wrapKey")[] | undefined;
283
+ x5c?: string[] | undefined;
284
+ x5t?: string | undefined;
285
+ 'x5t#S256'?: string | undefined;
286
+ x5u?: string | undefined;
287
+ ext?: boolean | undefined;
288
+ iat?: number | undefined;
289
+ exp?: number | undefined;
290
+ nbf?: number | undefined;
291
+ revoked?: {
292
+ revoked_at: number;
293
+ reason?: string | undefined;
294
+ } | undefined;
295
+ kty: "OKP";
296
+ alg?: "EdDSA" | undefined;
297
+ crv: "Ed25519" | "Ed448";
298
+ x: string;
299
+ d?: string | undefined;
300
+ } | {
301
+ kid?: string | undefined;
302
+ use?: "enc" | "sig" | undefined;
303
+ key_ops?: ("decrypt" | "deriveBits" | "deriveKey" | "encrypt" | "sign" | "unwrapKey" | "verify" | "wrapKey")[] | undefined;
304
+ x5c?: string[] | undefined;
305
+ x5t?: string | undefined;
306
+ 'x5t#S256'?: string | undefined;
307
+ x5u?: string | undefined;
308
+ ext?: boolean | undefined;
309
+ iat?: number | undefined;
310
+ exp?: number | undefined;
311
+ nbf?: number | undefined;
312
+ revoked?: {
313
+ revoked_at: number;
314
+ reason?: string | undefined;
315
+ } | undefined;
316
+ kty: "oct";
317
+ alg?: "HS256" | "HS384" | "HS512" | undefined;
318
+ k: string;
319
+ })[];
320
+ } | undefined;
321
+ application_type?: "native" | "web" | undefined;
322
+ subject_type?: "pairwise" | "public" | undefined;
323
+ request_object_signing_alg?: string | undefined;
324
+ id_token_signed_response_alg?: string | undefined;
325
+ authorization_signed_response_alg?: string | undefined;
326
+ authorization_encrypted_response_enc?: "A128CBC-HS256" | undefined;
327
+ authorization_encrypted_response_alg?: string | undefined;
328
+ client_id?: string | undefined;
329
+ client_name?: string | undefined;
330
+ client_uri?: string | undefined;
331
+ policy_uri?: string | undefined;
332
+ tos_uri?: string | undefined;
333
+ logo_uri?: string | undefined;
334
+ default_max_age?: number | undefined;
335
+ require_auth_time?: boolean | undefined;
336
+ contacts?: string[] | undefined;
337
+ tls_client_certificate_bound_access_tokens?: boolean | undefined;
338
+ dpop_bound_access_tokens?: boolean | undefined;
339
+ authorization_details_types?: string[] | undefined;
340
+ };
168
341
  //# sourceMappingURL=build-client-metadata.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-client-metadata.d.ts","sourceRoot":"","sources":["../lib/build-client-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AASnD;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsD/B,CAAC"}
1
+ {"version":3,"file":"build-client-metadata.d.ts","sourceRoot":"","sources":["../lib/build-client-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAcnD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsD/B,CAAC;AA6BF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCrC,CAAC"}
@@ -1,8 +1,9 @@
1
1
  import { FALLBACK_ALG } from './constants.js';
2
2
  import { confidentialClientMetadataSchema, } from './schemas/atcute-confidential-client-metadata.js';
3
+ import { publicClientMetadataSchema, } from './schemas/atcute-public-client-metadata.js';
4
+ import { DEFAULT_ATPROTO_OAUTH_SCOPE } from './schemas/atproto-oauth-scope.js';
3
5
  /**
4
- * builds an atproto client metadata
5
- *
6
+ * builds an atproto client metadata for a confidential client.
6
7
  *
7
8
  * @param input client metadata
8
9
  * @param keyset available keys
@@ -50,4 +51,72 @@ export const buildClientMetadata = (input, keyset) => {
50
51
  }
51
52
  return metadata;
52
53
  };
54
+ /**
55
+ * builds a loopback client_id from redirect_uris and scope.
56
+ *
57
+ * @param redirectUris loopback redirect URIs
58
+ * @param scope OAuth scope string
59
+ * @returns loopback client_id URL
60
+ */
61
+ const buildLoopbackClientId = (redirectUris, scope) => {
62
+ const params = new URLSearchParams();
63
+ // only include scope if not the default
64
+ if (scope !== DEFAULT_ATPROTO_OAUTH_SCOPE) {
65
+ params.set('scope', scope);
66
+ }
67
+ // include redirect URIs
68
+ for (const uri of redirectUris) {
69
+ params.append('redirect_uri', uri);
70
+ }
71
+ if (params.size > 0) {
72
+ return `http://localhost?${params.toString()}`;
73
+ }
74
+ return 'http://localhost';
75
+ };
76
+ /**
77
+ * builds an atproto client metadata for a public client.
78
+ *
79
+ * public clients use `token_endpoint_auth_method: 'none'` and don't require a keyset.
80
+ * per AT Protocol spec, they have shorter token lifetimes and cannot use silent sign-in.
81
+ *
82
+ * - if `client_id` is omitted: loopback client (client_id built from redirect_uris/scope)
83
+ * - if `client_id` is provided: discoverable public client
84
+ *
85
+ * @param input public client metadata
86
+ * @returns built client metadata
87
+ */
88
+ export const buildPublicClientMetadata = (input) => {
89
+ const parsed = publicClientMetadataSchema.parse(input, { mode: 'passthrough' });
90
+ const scope = Array.isArray(parsed.scope) ? parsed.scope.join(' ') : parsed.scope;
91
+ if (parsed.client_id === undefined) {
92
+ // loopback client - server generates metadata from client_id URL
93
+ return {
94
+ client_id: buildLoopbackClientId(parsed.redirect_uris, scope),
95
+ redirect_uris: parsed.redirect_uris,
96
+ scope,
97
+ application_type: 'native',
98
+ response_types: ['code'],
99
+ grant_types: ['authorization_code', 'refresh_token'],
100
+ token_endpoint_auth_method: 'none',
101
+ dpop_bound_access_tokens: true,
102
+ };
103
+ }
104
+ // discoverable public client
105
+ return {
106
+ client_id: parsed.client_id,
107
+ client_name: parsed.client_name,
108
+ client_uri: parsed.client_uri,
109
+ policy_uri: parsed.policy_uri,
110
+ tos_uri: parsed.tos_uri,
111
+ logo_uri: parsed.logo_uri,
112
+ redirect_uris: parsed.redirect_uris,
113
+ scope,
114
+ application_type: parsed.application_type ?? 'web',
115
+ subject_type: 'public',
116
+ response_types: ['code'],
117
+ grant_types: ['authorization_code', 'refresh_token'],
118
+ token_endpoint_auth_method: 'none',
119
+ dpop_bound_access_tokens: true,
120
+ };
121
+ };
53
122
  //# sourceMappingURL=build-client-metadata.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-client-metadata.js","sourceRoot":"","sources":["../lib/build-client-metadata.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EACN,gCAAgC,GAEhC,MAAM,kDAAkD,CAAC;AAG1D;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAClC,KAAiC,EACjC,MAAc,EACQ,EAAE,CAAC;IACzB,yCAAyC;IACzC,MAAM,IAAI,GAAG,gCAAgC,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;IAEpF,uEAAuE;IACvE,MAAM,QAAQ,GAAwB;QACrC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK;QAEpE,gBAAgB,EAAE,KAAK;QACvB,YAAY,EAAE,QAAQ;QACtB,cAAc,EAAE,CAAC,MAAM,CAAC;QACxB,WAAW,EAAE,CAAC,oBAAoB,EAAE,eAAe,CAAC;QAEpD,0BAA0B,EAAE,iBAAiB;QAC7C,+BAA+B,EAAE,YAAY;QAC7C,wBAAwB,EAAE,IAAI;QAE9B,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,MAAM,CAAC,UAA0C;KACpF,CAAC;IAEF,0DAA0D;IAC1D,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,YAAY,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,SAAS,CAAC,4CAA4C,YAAY,eAAe,CAAC,CAAC;IAC9F,CAAC;IAED,+DAA+D;IAC/D,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,IAAI,GAAG,CACvB,QAAQ,CAAC,IAAI,CAAC,IAAI;aAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;aACzB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;aACjB,MAAM,CAAC,OAAO,CAAC,CACjB,CAAC;QAEF,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;YAC/B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,SAAS,CAAC,gBAAgB,GAAG,CAAC,GAAG,qBAAqB,CAAC,CAAC;YACnE,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,QAAQ,CAAC;AAAA,CAChB,CAAC"}
1
+ {"version":3,"file":"build-client-metadata.js","sourceRoot":"","sources":["../lib/build-client-metadata.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- export { buildClientMetadata } from './build-client-metadata.js';
1
+ export { buildClientMetadata, buildPublicClientMetadata } from './build-client-metadata.js';
2
2
  export { CLIENT_ASSERTION_TYPE_JWT_BEARER, FALLBACK_ALG } from './constants.js';
3
3
  export * as scope from './scope.js';
4
4
  export { confidentialClientMetadataSchema, type ConfidentialClientMetadata, } from './schemas/atcute-confidential-client-metadata.js';
5
+ export { discoverablePublicClientMetadataSchema, loopbackClientMetadataSchema, publicClientMetadataSchema, type DiscoverablePublicClientMetadata, type LoopbackClientMetadata, type PublicClientMetadata, } from './schemas/atcute-public-client-metadata.js';
5
6
  export { atprotoOAuthScopeSchema, ATPROTO_SCOPE_VALUE, DEFAULT_ATPROTO_OAUTH_SCOPE, type AtprotoOAuthScope, } from './schemas/atproto-oauth-scope.js';
6
7
  export { jwkPubSchema, jwkSchema, keyUsageSchema, publicKeyUsageSchema, type Jwk, type JwkPub, type KeyUsage, } from './schemas/jwk.js';
7
8
  export { jwksPubSchema, jwksSchema, type Jwks, type JwksPub } from './schemas/jwks.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
- export { buildClientMetadata } from './build-client-metadata.js';
1
+ export { buildClientMetadata, buildPublicClientMetadata } from './build-client-metadata.js';
2
2
  export { CLIENT_ASSERTION_TYPE_JWT_BEARER, FALLBACK_ALG } from './constants.js';
3
3
  export * as scope from './scope.js';
4
4
  // schemas
5
5
  export { confidentialClientMetadataSchema, } from './schemas/atcute-confidential-client-metadata.js';
6
+ export { discoverablePublicClientMetadataSchema, loopbackClientMetadataSchema, publicClientMetadataSchema, } from './schemas/atcute-public-client-metadata.js';
6
7
  export { atprotoOAuthScopeSchema, ATPROTO_SCOPE_VALUE, DEFAULT_ATPROTO_OAUTH_SCOPE, } from './schemas/atproto-oauth-scope.js';
7
8
  export { jwkPubSchema, jwkSchema, keyUsageSchema, publicKeyUsageSchema, } from './schemas/jwk.js';
8
9
  export { jwksPubSchema, jwksSchema } from './schemas/jwks.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,gCAAgC,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhF,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAEpC,UAAU;AACV,OAAO,EACN,gCAAgC,GAEhC,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EACN,uBAAuB,EACvB,mBAAmB,EACnB,2BAA2B,GAE3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,YAAY,EACZ,SAAS,EACT,cAAc,EACd,oBAAoB,GAIpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,UAAU,EAA2B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,EAAE,mBAAmB,EAAsB,MAAM,8BAA8B,CAAC;AACvF,OAAO,EAAE,yBAAyB,EAA4B,MAAM,oCAAoC,CAAC;AACzG,OAAO,EACN,6BAA6B,GAE7B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAuB,MAAM,+BAA+B,CAAC;AAC1F,OAAO,EACN,yBAAyB,EACzB,sBAAsB,GAGtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAA0B,MAAM,kCAAkC,CAAC;AACnG,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,GAEhB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,SAAS,EACT,YAAY,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,qBAAqB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,gBAAgB;AAChB,OAAO,EAAE,oBAAoB,EAAuB,MAAM,+BAA+B,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAA2B,MAAM,mCAAmC,CAAC;AACtG,OAAO,EACN,+BAA+B,GAE/B,MAAM,2CAA2C,CAAC;AAEnD,cAAc;AACd,OAAO,EAAE,sBAAsB,EAAyB,MAAM,iCAAiC,CAAC;AAChG,OAAO,EACN,8BAA8B,GAE9B,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAA0B,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAoB,MAAM,2BAA2B,CAAC;AAEhF,wBAAwB;AACxB,OAAO,EACN,8BAA8B,EAC9B,+BAA+B,GAG/B,MAAM,0CAA0C,CAAC;AAElD,kBAAkB;AAClB,OAAO,EACN,2BAA2B,GAE3B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACN,sCAAsC,EACtC,yCAAyC,GAEzC,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EACN,2CAA2C,GAE3C,MAAM,oDAAoD,CAAC;AAE5D,8BAA8B;AAC9B,OAAO,EACN,uBAAuB,EACvB,oCAAoC,EACpC,uCAAuC,GAGvC,MAAM,gDAAgD,CAAC;AACxD,OAAO,EACN,yCAAyC,GAEzC,MAAM,kDAAkD,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAC5F,OAAO,EAAE,gCAAgC,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEhF,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AAEpC,UAAU;AACV,OAAO,EACN,gCAAgC,GAEhC,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EACN,sCAAsC,EACtC,4BAA4B,EAC5B,0BAA0B,GAI1B,MAAM,4CAA4C,CAAC;AACpD,OAAO,EACN,uBAAuB,EACvB,mBAAmB,EACnB,2BAA2B,GAE3B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACN,YAAY,EACZ,SAAS,EACT,cAAc,EACd,oBAAoB,GAIpB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,UAAU,EAA2B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,+BAA+B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,EAAE,mBAAmB,EAAsB,MAAM,8BAA8B,CAAC;AACvF,OAAO,EAAE,yBAAyB,EAA4B,MAAM,oCAAoC,CAAC;AACzG,OAAO,EACN,6BAA6B,GAE7B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAuB,MAAM,+BAA+B,CAAC;AAC1F,OAAO,EACN,yBAAyB,EACzB,sBAAsB,GAGtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAA0B,MAAM,kCAAkC,CAAC;AACnG,OAAO,EACN,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,GAEhB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACN,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,mBAAmB,EACnB,SAAS,EACT,YAAY,GACZ,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,qBAAqB,GACrB,MAAM,oBAAoB,CAAC;AAE5B,gBAAgB;AAChB,OAAO,EAAE,oBAAoB,EAAuB,MAAM,+BAA+B,CAAC;AAC1F,OAAO,EAAE,wBAAwB,EAA2B,MAAM,mCAAmC,CAAC;AACtG,OAAO,EACN,+BAA+B,GAE/B,MAAM,2CAA2C,CAAC;AAEnD,cAAc;AACd,OAAO,EAAE,sBAAsB,EAAyB,MAAM,iCAAiC,CAAC;AAChG,OAAO,EACN,8BAA8B,GAE9B,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,uBAAuB,EAA0B,MAAM,kCAAkC,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAoB,MAAM,2BAA2B,CAAC;AAEhF,wBAAwB;AACxB,OAAO,EACN,8BAA8B,EAC9B,+BAA+B,GAG/B,MAAM,0CAA0C,CAAC;AAElD,kBAAkB;AAClB,OAAO,EACN,2BAA2B,GAE3B,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EACN,sCAAsC,EACtC,yCAAyC,GAEzC,MAAM,kDAAkD,CAAC;AAC1D,OAAO,EACN,2CAA2C,GAE3C,MAAM,oDAAoD,CAAC;AAE5D,8BAA8B;AAC9B,OAAO,EACN,uBAAuB,EACvB,oCAAoC,EACpC,uCAAuC,GAGvC,MAAM,gDAAgD,CAAC;AACxD,OAAO,EACN,yCAAyC,GAEzC,MAAM,kDAAkD,CAAC"}
@@ -0,0 +1,55 @@
1
+ import * as v from '@badrap/valita';
2
+ /**
3
+ * user-facing client metadata for configuring a loopback public OAuth client.
4
+ *
5
+ * loopback clients are for localhost development and CLI tools. they use
6
+ * `http://localhost` as the client_id origin, which is built automatically
7
+ * from the redirect_uris and scope.
8
+ */
9
+ export declare const loopbackClientMetadataSchema: v.Type<{
10
+ client_id?: undefined;
11
+ redirect_uris: string[];
12
+ scope: string | string[];
13
+ }>;
14
+ export type LoopbackClientMetadata = v.Infer<typeof loopbackClientMetadataSchema>;
15
+ /**
16
+ * user-facing client metadata for configuring a discoverable public OAuth client.
17
+ *
18
+ * discoverable public clients have an HTTPS client_id URL where metadata is hosted,
19
+ * but don't use a keyset (token_endpoint_auth_method: 'none').
20
+ */
21
+ export declare const discoverablePublicClientMetadataSchema: v.Type<{
22
+ client_id: string;
23
+ redirect_uris: string[];
24
+ scope: string | string[];
25
+ application_type?: "native" | "web" | undefined;
26
+ client_uri?: string | undefined;
27
+ client_name?: string | undefined;
28
+ policy_uri?: string | undefined;
29
+ tos_uri?: string | undefined;
30
+ logo_uri?: string | undefined;
31
+ }>;
32
+ export type DiscoverablePublicClientMetadata = v.Infer<typeof discoverablePublicClientMetadataSchema>;
33
+ /**
34
+ * user-facing client metadata for configuring a public OAuth client.
35
+ *
36
+ * - if `client_id` is omitted: loopback client (for localhost dev / CLI tools)
37
+ * - if `client_id` is provided: discoverable public client (HTTPS URL)
38
+ */
39
+ export declare const publicClientMetadataSchema: v.UnionType<[v.Type<{
40
+ client_id?: undefined;
41
+ redirect_uris: string[];
42
+ scope: string | string[];
43
+ }>, v.Type<{
44
+ client_id: string;
45
+ redirect_uris: string[];
46
+ scope: string | string[];
47
+ application_type?: "native" | "web" | undefined;
48
+ client_uri?: string | undefined;
49
+ client_name?: string | undefined;
50
+ policy_uri?: string | undefined;
51
+ tos_uri?: string | undefined;
52
+ logo_uri?: string | undefined;
53
+ }>]>;
54
+ export type PublicClientMetadata = v.Infer<typeof publicClientMetadataSchema>;
55
+ //# sourceMappingURL=atcute-public-client-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atcute-public-client-metadata.d.ts","sourceRoot":"","sources":["../../lib/schemas/atcute-public-client-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AAgEpC;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B;;;;EAuCtC,CAAC;AAEJ,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAElF;;;;;GAKG;AACH,eAAO,MAAM,sCAAsC;;;;;;;;;;EA6DhD,CAAC;AAEJ,MAAM,MAAM,gCAAgC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sCAAsC,CAAC,CAAC;AAEtG;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;;;;;IAGtC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC"}
@@ -0,0 +1,159 @@
1
+ import * as v from '@badrap/valita';
2
+ import { atprotoOAuthScopeSchema } from './atproto-oauth-scope.js';
3
+ import { oauthClientIdDiscoverableSchema } from './oauth-client-id-discoverable.js';
4
+ import { loopbackRedirectUriSchema, oauthRedirectUriSchema } from './oauth-redirect-uri.js';
5
+ import { nonLocalWebUriSchema, privateUseUriSchema, webUriSchema } from './uri.js';
6
+ import { isLoopbackHost } from './utils.js';
7
+ const SINGLE_SCOPE_RE = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
8
+ const singleScopeSchema = v.string().assert((input) => SINGLE_SCOPE_RE.test(input), `invalid OAuth scope`);
9
+ const scopeSchema = v.union(atprotoOAuthScopeSchema.chain((input) => {
10
+ const scopes = input.split(/\s+/);
11
+ for (let i = 0, len = scopes.length; i < len; i++) {
12
+ const aka = scopes[i];
13
+ for (let j = 0; j < i; j++) {
14
+ if (aka === scopes[j]) {
15
+ return v.err(`duplicate "${aka}" scope`);
16
+ }
17
+ }
18
+ }
19
+ return v.ok(input);
20
+ }), v.array(singleScopeSchema).chain((input) => {
21
+ if (!input.includes('atproto')) {
22
+ input = ['atproto', ...input];
23
+ }
24
+ for (let i = 0, len = input.length; i < len; i++) {
25
+ const aka = input[i];
26
+ for (let j = 0; j < i; j++) {
27
+ if (aka === input[j]) {
28
+ return v.err(`duplicate "${aka}" scope`);
29
+ }
30
+ }
31
+ }
32
+ return v.ok(input);
33
+ }));
34
+ const redirectUrisSchema = v
35
+ .array(oauthRedirectUriSchema)
36
+ .assert((arr) => arr.length > 0, `must have at least one redirect URI`)
37
+ .assert((arr) => {
38
+ for (const uri of arr) {
39
+ // private-use URIs don't have URL-style credentials
40
+ if (!uri.includes('://')) {
41
+ continue;
42
+ }
43
+ const url = new URL(uri);
44
+ if (url.username || url.password) {
45
+ return false;
46
+ }
47
+ }
48
+ return true;
49
+ }, `redirect URIs must not contain credentials`);
50
+ /**
51
+ * user-facing client metadata for configuring a loopback public OAuth client.
52
+ *
53
+ * loopback clients are for localhost development and CLI tools. they use
54
+ * `http://localhost` as the client_id origin, which is built automatically
55
+ * from the redirect_uris and scope.
56
+ */
57
+ export const loopbackClientMetadataSchema = v
58
+ .object({
59
+ /** must not be provided for loopback clients */
60
+ client_id: v.undefined().optional(),
61
+ /**
62
+ * redirect URIs for authorization responses.
63
+ *
64
+ * must be loopback IP addresses (127.0.0.1 or [::1]).
65
+ * per RFC 8252, port numbers are ignored during redirect URI matching,
66
+ * allowing ephemeral ports.
67
+ */
68
+ redirect_uris: redirectUrisSchema,
69
+ /** OAuth scope (must include "atproto") */
70
+ scope: scopeSchema,
71
+ })
72
+ .chain((input) => {
73
+ // validate all redirect URIs are loopback
74
+ for (let i = 0; i < input.redirect_uris.length; i++) {
75
+ const uri = input.redirect_uris[i];
76
+ const result = loopbackRedirectUriSchema.try(uri, { mode: 'strict' });
77
+ if (!result.ok) {
78
+ return v.err({
79
+ message: `loopback clients require loopback redirect URIs (127.0.0.1 or [::1]): ${result.message}`,
80
+ path: ['redirect_uris', i],
81
+ });
82
+ }
83
+ const url = new URL(uri);
84
+ if (!isLoopbackHost(url.hostname) || url.hostname === 'localhost') {
85
+ return v.err({
86
+ message: `loopback redirect URIs must use 127.0.0.1 or [::1], not ${url.hostname}`,
87
+ path: ['redirect_uris', i],
88
+ });
89
+ }
90
+ }
91
+ return v.ok(input);
92
+ });
93
+ /**
94
+ * user-facing client metadata for configuring a discoverable public OAuth client.
95
+ *
96
+ * discoverable public clients have an HTTPS client_id URL where metadata is hosted,
97
+ * but don't use a keyset (token_endpoint_auth_method: 'none').
98
+ */
99
+ export const discoverablePublicClientMetadataSchema = v
100
+ .object({
101
+ /** discoverable HTTPS client_id URL */
102
+ client_id: oauthClientIdDiscoverableSchema,
103
+ /** redirect URIs for authorization responses */
104
+ redirect_uris: redirectUrisSchema,
105
+ /** OAuth scope (must include "atproto") */
106
+ scope: scopeSchema,
107
+ /**
108
+ * application type - defaults to 'web'.
109
+ */
110
+ application_type: v.union(v.literal('web'), v.literal('native')).optional(),
111
+ /** optional client homepage */
112
+ client_uri: webUriSchema.optional(),
113
+ /** optional display name */
114
+ client_name: v.string().optional(),
115
+ /** optional policy url */
116
+ policy_uri: nonLocalWebUriSchema.optional(),
117
+ /** optional terms of service url */
118
+ tos_uri: nonLocalWebUriSchema.optional(),
119
+ /** optional logo url */
120
+ logo_uri: nonLocalWebUriSchema.optional(),
121
+ })
122
+ .chain((input) => {
123
+ // validate redirect URIs are HTTPS, loopback, or private-use
124
+ for (let i = 0; i < input.redirect_uris.length; i++) {
125
+ const uri = input.redirect_uris[i];
126
+ // private-use URIs are allowed
127
+ if (!uri.includes('://')) {
128
+ const result = privateUseUriSchema.try(uri, { mode: 'strict' });
129
+ if (!result.ok) {
130
+ return v.err({
131
+ message: `invalid redirect URI: ${result.message}`,
132
+ path: ['redirect_uris', i],
133
+ });
134
+ }
135
+ continue;
136
+ }
137
+ const url = new URL(uri);
138
+ // loopback http URIs are allowed for native apps
139
+ if (url.protocol === 'http:' && isLoopbackHost(url.hostname)) {
140
+ continue;
141
+ }
142
+ // otherwise must be https
143
+ if (url.protocol !== 'https:') {
144
+ return v.err({
145
+ message: `redirect URI must use https:, http: loopback, or private-use scheme`,
146
+ path: ['redirect_uris', i],
147
+ });
148
+ }
149
+ }
150
+ return v.ok(input);
151
+ });
152
+ /**
153
+ * user-facing client metadata for configuring a public OAuth client.
154
+ *
155
+ * - if `client_id` is omitted: loopback client (for localhost dev / CLI tools)
156
+ * - if `client_id` is provided: discoverable public client (HTTPS URL)
157
+ */
158
+ export const publicClientMetadataSchema = v.union(loopbackClientMetadataSchema, discoverablePublicClientMetadataSchema);
159
+ //# sourceMappingURL=atcute-public-client-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atcute-public-client-metadata.js","sourceRoot":"","sources":["../../lib/schemas/atcute-public-client-metadata.ts"],"names":[],"mappings":"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"}
@@ -5,11 +5,15 @@ import {
5
5
  confidentialClientMetadataSchema,
6
6
  type ConfidentialClientMetadata,
7
7
  } from './schemas/atcute-confidential-client-metadata.js';
8
+ import {
9
+ publicClientMetadataSchema,
10
+ type PublicClientMetadata,
11
+ } from './schemas/atcute-public-client-metadata.js';
12
+ import { DEFAULT_ATPROTO_OAUTH_SCOPE } from './schemas/atproto-oauth-scope.js';
8
13
  import type { OAuthClientMetadata } from './schemas/oauth-client-metadata.js';
9
14
 
10
15
  /**
11
- * builds an atproto client metadata
12
- *
16
+ * builds an atproto client metadata for a confidential client.
13
17
  *
14
18
  * @param input client metadata
15
19
  * @param keyset available keys
@@ -70,3 +74,83 @@ export const buildClientMetadata = (
70
74
 
71
75
  return metadata;
72
76
  };
77
+
78
+ /**
79
+ * builds a loopback client_id from redirect_uris and scope.
80
+ *
81
+ * @param redirectUris loopback redirect URIs
82
+ * @param scope OAuth scope string
83
+ * @returns loopback client_id URL
84
+ */
85
+ const buildLoopbackClientId = (redirectUris: readonly string[], scope: string): string => {
86
+ const params = new URLSearchParams();
87
+
88
+ // only include scope if not the default
89
+ if (scope !== DEFAULT_ATPROTO_OAUTH_SCOPE) {
90
+ params.set('scope', scope);
91
+ }
92
+
93
+ // include redirect URIs
94
+ for (const uri of redirectUris) {
95
+ params.append('redirect_uri', uri);
96
+ }
97
+
98
+ if (params.size > 0) {
99
+ return `http://localhost?${params.toString()}`;
100
+ }
101
+
102
+ return 'http://localhost';
103
+ };
104
+
105
+ /**
106
+ * builds an atproto client metadata for a public client.
107
+ *
108
+ * public clients use `token_endpoint_auth_method: 'none'` and don't require a keyset.
109
+ * per AT Protocol spec, they have shorter token lifetimes and cannot use silent sign-in.
110
+ *
111
+ * - if `client_id` is omitted: loopback client (client_id built from redirect_uris/scope)
112
+ * - if `client_id` is provided: discoverable public client
113
+ *
114
+ * @param input public client metadata
115
+ * @returns built client metadata
116
+ */
117
+ export const buildPublicClientMetadata = (input: PublicClientMetadata): OAuthClientMetadata => {
118
+ const parsed = publicClientMetadataSchema.parse(input, { mode: 'passthrough' });
119
+ const scope = Array.isArray(parsed.scope) ? parsed.scope.join(' ') : parsed.scope;
120
+
121
+ if (parsed.client_id === undefined) {
122
+ // loopback client - server generates metadata from client_id URL
123
+ return {
124
+ client_id: buildLoopbackClientId(parsed.redirect_uris, scope),
125
+ redirect_uris: parsed.redirect_uris,
126
+ scope,
127
+
128
+ application_type: 'native',
129
+ response_types: ['code'],
130
+ grant_types: ['authorization_code', 'refresh_token'],
131
+
132
+ token_endpoint_auth_method: 'none',
133
+ dpop_bound_access_tokens: true,
134
+ };
135
+ }
136
+
137
+ // discoverable public client
138
+ return {
139
+ client_id: parsed.client_id,
140
+ client_name: parsed.client_name,
141
+ client_uri: parsed.client_uri,
142
+ policy_uri: parsed.policy_uri,
143
+ tos_uri: parsed.tos_uri,
144
+ logo_uri: parsed.logo_uri,
145
+ redirect_uris: parsed.redirect_uris,
146
+ scope,
147
+
148
+ application_type: parsed.application_type ?? 'web',
149
+ subject_type: 'public',
150
+ response_types: ['code'],
151
+ grant_types: ['authorization_code', 'refresh_token'],
152
+
153
+ token_endpoint_auth_method: 'none',
154
+ dpop_bound_access_tokens: true,
155
+ };
156
+ };
package/lib/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { buildClientMetadata } from './build-client-metadata.js';
1
+ export { buildClientMetadata, buildPublicClientMetadata } from './build-client-metadata.js';
2
2
  export { CLIENT_ASSERTION_TYPE_JWT_BEARER, FALLBACK_ALG } from './constants.js';
3
3
 
4
4
  export * as scope from './scope.js';
@@ -8,6 +8,14 @@ export {
8
8
  confidentialClientMetadataSchema,
9
9
  type ConfidentialClientMetadata,
10
10
  } from './schemas/atcute-confidential-client-metadata.js';
11
+ export {
12
+ discoverablePublicClientMetadataSchema,
13
+ loopbackClientMetadataSchema,
14
+ publicClientMetadataSchema,
15
+ type DiscoverablePublicClientMetadata,
16
+ type LoopbackClientMetadata,
17
+ type PublicClientMetadata,
18
+ } from './schemas/atcute-public-client-metadata.js';
11
19
  export {
12
20
  atprotoOAuthScopeSchema,
13
21
  ATPROTO_SCOPE_VALUE,
@@ -0,0 +1,197 @@
1
+ import * as v from '@badrap/valita';
2
+
3
+ import { atprotoOAuthScopeSchema } from './atproto-oauth-scope.js';
4
+ import { oauthClientIdDiscoverableSchema } from './oauth-client-id-discoverable.js';
5
+ import { loopbackRedirectUriSchema, oauthRedirectUriSchema } from './oauth-redirect-uri.js';
6
+ import { nonLocalWebUriSchema, privateUseUriSchema, webUriSchema } from './uri.js';
7
+ import { isLoopbackHost } from './utils.js';
8
+
9
+ const SINGLE_SCOPE_RE = /^[\x21\x23-\x5B\x5D-\x7E]+$/;
10
+
11
+ const singleScopeSchema = v.string().assert((input) => SINGLE_SCOPE_RE.test(input), `invalid OAuth scope`);
12
+
13
+ const scopeSchema = v.union(
14
+ atprotoOAuthScopeSchema.chain((input) => {
15
+ const scopes = input.split(/\s+/);
16
+
17
+ for (let i = 0, len = scopes.length; i < len; i++) {
18
+ const aka = scopes[i];
19
+
20
+ for (let j = 0; j < i; j++) {
21
+ if (aka === scopes[j]) {
22
+ return v.err(`duplicate "${aka}" scope`);
23
+ }
24
+ }
25
+ }
26
+
27
+ return v.ok(input);
28
+ }),
29
+ v.array(singleScopeSchema).chain((input) => {
30
+ if (!input.includes('atproto')) {
31
+ input = ['atproto', ...input];
32
+ }
33
+
34
+ for (let i = 0, len = input.length; i < len; i++) {
35
+ const aka = input[i];
36
+
37
+ for (let j = 0; j < i; j++) {
38
+ if (aka === input[j]) {
39
+ return v.err(`duplicate "${aka}" scope`);
40
+ }
41
+ }
42
+ }
43
+
44
+ return v.ok(input);
45
+ }),
46
+ );
47
+
48
+ const redirectUrisSchema = v
49
+ .array(oauthRedirectUriSchema)
50
+ .assert((arr) => arr.length > 0, `must have at least one redirect URI`)
51
+ .assert((arr) => {
52
+ for (const uri of arr) {
53
+ // private-use URIs don't have URL-style credentials
54
+ if (!uri.includes('://')) {
55
+ continue;
56
+ }
57
+ const url = new URL(uri);
58
+ if (url.username || url.password) {
59
+ return false;
60
+ }
61
+ }
62
+ return true;
63
+ }, `redirect URIs must not contain credentials`);
64
+
65
+ /**
66
+ * user-facing client metadata for configuring a loopback public OAuth client.
67
+ *
68
+ * loopback clients are for localhost development and CLI tools. they use
69
+ * `http://localhost` as the client_id origin, which is built automatically
70
+ * from the redirect_uris and scope.
71
+ */
72
+ export const loopbackClientMetadataSchema = v
73
+ .object({
74
+ /** must not be provided for loopback clients */
75
+ client_id: v.undefined().optional(),
76
+
77
+ /**
78
+ * redirect URIs for authorization responses.
79
+ *
80
+ * must be loopback IP addresses (127.0.0.1 or [::1]).
81
+ * per RFC 8252, port numbers are ignored during redirect URI matching,
82
+ * allowing ephemeral ports.
83
+ */
84
+ redirect_uris: redirectUrisSchema,
85
+
86
+ /** OAuth scope (must include "atproto") */
87
+ scope: scopeSchema,
88
+ })
89
+ .chain((input) => {
90
+ // validate all redirect URIs are loopback
91
+ for (let i = 0; i < input.redirect_uris.length; i++) {
92
+ const uri = input.redirect_uris[i];
93
+ const result = loopbackRedirectUriSchema.try(uri, { mode: 'strict' });
94
+ if (!result.ok) {
95
+ return v.err({
96
+ message: `loopback clients require loopback redirect URIs (127.0.0.1 or [::1]): ${result.message}`,
97
+ path: ['redirect_uris', i],
98
+ });
99
+ }
100
+
101
+ const url = new URL(uri);
102
+ if (!isLoopbackHost(url.hostname) || url.hostname === 'localhost') {
103
+ return v.err({
104
+ message: `loopback redirect URIs must use 127.0.0.1 or [::1], not ${url.hostname}`,
105
+ path: ['redirect_uris', i],
106
+ });
107
+ }
108
+ }
109
+
110
+ return v.ok(input);
111
+ });
112
+
113
+ export type LoopbackClientMetadata = v.Infer<typeof loopbackClientMetadataSchema>;
114
+
115
+ /**
116
+ * user-facing client metadata for configuring a discoverable public OAuth client.
117
+ *
118
+ * discoverable public clients have an HTTPS client_id URL where metadata is hosted,
119
+ * but don't use a keyset (token_endpoint_auth_method: 'none').
120
+ */
121
+ export const discoverablePublicClientMetadataSchema = v
122
+ .object({
123
+ /** discoverable HTTPS client_id URL */
124
+ client_id: oauthClientIdDiscoverableSchema,
125
+
126
+ /** redirect URIs for authorization responses */
127
+ redirect_uris: redirectUrisSchema,
128
+
129
+ /** OAuth scope (must include "atproto") */
130
+ scope: scopeSchema,
131
+
132
+ /**
133
+ * application type - defaults to 'web'.
134
+ */
135
+ application_type: v.union(v.literal('web'), v.literal('native')).optional(),
136
+
137
+ /** optional client homepage */
138
+ client_uri: webUriSchema.optional(),
139
+ /** optional display name */
140
+ client_name: v.string().optional(),
141
+ /** optional policy url */
142
+ policy_uri: nonLocalWebUriSchema.optional(),
143
+ /** optional terms of service url */
144
+ tos_uri: nonLocalWebUriSchema.optional(),
145
+ /** optional logo url */
146
+ logo_uri: nonLocalWebUriSchema.optional(),
147
+ })
148
+ .chain((input) => {
149
+ // validate redirect URIs are HTTPS, loopback, or private-use
150
+ for (let i = 0; i < input.redirect_uris.length; i++) {
151
+ const uri = input.redirect_uris[i];
152
+
153
+ // private-use URIs are allowed
154
+ if (!uri.includes('://')) {
155
+ const result = privateUseUriSchema.try(uri, { mode: 'strict' });
156
+ if (!result.ok) {
157
+ return v.err({
158
+ message: `invalid redirect URI: ${result.message}`,
159
+ path: ['redirect_uris', i],
160
+ });
161
+ }
162
+ continue;
163
+ }
164
+
165
+ const url = new URL(uri);
166
+
167
+ // loopback http URIs are allowed for native apps
168
+ if (url.protocol === 'http:' && isLoopbackHost(url.hostname)) {
169
+ continue;
170
+ }
171
+
172
+ // otherwise must be https
173
+ if (url.protocol !== 'https:') {
174
+ return v.err({
175
+ message: `redirect URI must use https:, http: loopback, or private-use scheme`,
176
+ path: ['redirect_uris', i],
177
+ });
178
+ }
179
+ }
180
+
181
+ return v.ok(input);
182
+ });
183
+
184
+ export type DiscoverablePublicClientMetadata = v.Infer<typeof discoverablePublicClientMetadataSchema>;
185
+
186
+ /**
187
+ * user-facing client metadata for configuring a public OAuth client.
188
+ *
189
+ * - if `client_id` is omitted: loopback client (for localhost dev / CLI tools)
190
+ * - if `client_id` is provided: discoverable public client (HTTPS URL)
191
+ */
192
+ export const publicClientMetadataSchema = v.union(
193
+ loopbackClientMetadataSchema,
194
+ discoverablePublicClientMetadataSchema,
195
+ );
196
+
197
+ export type PublicClientMetadata = v.Infer<typeof publicClientMetadataSchema>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atcute/oauth-types",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OAuth types and schemas for AT Protocol",
5
5
  "license": "0BSD",
6
6
  "repository": {
@@ -24,8 +24,8 @@
24
24
  "dependencies": {
25
25
  "@badrap/valita": "^0.4.6",
26
26
  "@atcute/identity": "^1.1.3",
27
- "@atcute/oauth-keyset": "^0.1.0",
28
- "@atcute/lexicons": "^1.2.7"
27
+ "@atcute/lexicons": "^1.2.7",
28
+ "@atcute/oauth-keyset": "^0.1.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "vitest": "^4.0.16"