@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.
- package/dist/build-client-metadata.d.ts +175 -2
- package/dist/build-client-metadata.d.ts.map +1 -1
- package/dist/build-client-metadata.js +71 -2
- package/dist/build-client-metadata.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/schemas/atcute-public-client-metadata.d.ts +55 -0
- package/dist/schemas/atcute-public-client-metadata.d.ts.map +1 -0
- package/dist/schemas/atcute-public-client-metadata.js +159 -0
- package/dist/schemas/atcute-public-client-metadata.js.map +1 -0
- package/lib/build-client-metadata.ts +86 -2
- package/lib/index.ts +9 -1
- package/lib/schemas/atcute-public-client-metadata.ts +197 -0
- package/package.json +3 -3
|
@@ -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;
|
|
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;
|
|
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;AAC1D,OAAO,EACN,0BAA0B,GAE1B,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AAG/E;;;;;;GAMG;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;AAEF;;;;;;GAMG;AACH,MAAM,qBAAqB,GAAG,CAAC,YAA+B,EAAE,KAAa,EAAU,EAAE,CAAC;IACzF,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IAErC,wCAAwC;IACxC,IAAI,KAAK,KAAK,2BAA2B,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,wBAAwB;IACxB,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,oBAAoB,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IAChD,CAAC;IAED,OAAO,kBAAkB,CAAC;AAAA,CAC1B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,KAA2B,EAAuB,EAAE,CAAC;IAC9F,MAAM,MAAM,GAAG,0BAA0B,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC;IAChF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;IAElF,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACpC,iEAAiE;QACjE,OAAO;YACN,SAAS,EAAE,qBAAqB,CAAC,MAAM,CAAC,aAAa,EAAE,KAAK,CAAC;YAC7D,aAAa,EAAE,MAAM,CAAC,aAAa;YACnC,KAAK;YAEL,gBAAgB,EAAE,QAAQ;YAC1B,cAAc,EAAE,CAAC,MAAM,CAAC;YACxB,WAAW,EAAE,CAAC,oBAAoB,EAAE,eAAe,CAAC;YAEpD,0BAA0B,EAAE,MAAM;YAClC,wBAAwB,EAAE,IAAI;SAC9B,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,OAAO;QACN,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,KAAK;QAEL,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,IAAI,KAAK;QAClD,YAAY,EAAE,QAAQ;QACtB,cAAc,EAAE,CAAC,MAAM,CAAC;QACxB,WAAW,EAAE,CAAC,oBAAoB,EAAE,eAAe,CAAC;QAEpD,0BAA0B,EAAE,MAAM;QAClC,wBAAwB,EAAE,IAAI;KAC9B,CAAC;AAAA,CACF,CAAC"}
|
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';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;
|
|
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;
|
|
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.
|
|
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/
|
|
28
|
-
"@atcute/
|
|
27
|
+
"@atcute/lexicons": "^1.2.7",
|
|
28
|
+
"@atcute/oauth-keyset": "^0.1.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"vitest": "^4.0.16"
|