@atproto/oauth-provider 0.1.3 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +35 -0
- package/dist/account/account.d.ts +6 -2
- package/dist/account/account.d.ts.map +1 -1
- package/dist/assets/app/bundle-manifest.json +3 -3
- package/dist/assets/app/main.css +1 -1
- package/dist/assets/app/main.js +1 -1
- package/dist/assets/app/main.js.map +1 -1
- package/dist/assets/assets-middleware.d.ts +2 -1
- package/dist/assets/assets-middleware.d.ts.map +1 -1
- package/dist/assets/assets-middleware.js +7 -0
- package/dist/assets/assets-middleware.js.map +1 -1
- package/dist/client/client-manager.d.ts +4 -3
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +60 -37
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +1 -3
- package/dist/client/client.js.map +1 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/device/device-manager.d.ts +1 -1
- package/dist/device/device-manager.d.ts.map +1 -1
- package/dist/device/device-manager.js +2 -2
- package/dist/device/device-manager.js.map +1 -1
- package/dist/errors/invalid-authorization-details-error.d.ts +4 -3
- package/dist/errors/invalid-authorization-details-error.d.ts.map +1 -1
- package/dist/errors/invalid-authorization-details-error.js +4 -4
- package/dist/errors/invalid-authorization-details-error.js.map +1 -1
- package/dist/lib/http/request.d.ts +3 -0
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +24 -12
- package/dist/lib/http/request.js.map +1 -1
- package/dist/metadata/build-metadata.d.ts +0 -1
- package/dist/metadata/build-metadata.d.ts.map +1 -1
- package/dist/metadata/build-metadata.js +9 -35
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-hooks.d.ts +3 -10
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-provider.d.ts +8 -13
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +169 -109
- package/dist/oauth-provider.js.map +1 -1
- package/dist/oauth-verifier.d.ts +1 -2
- package/dist/oauth-verifier.d.ts.map +1 -1
- package/dist/oauth-verifier.js.map +1 -1
- package/dist/output/build-authorize-data.d.ts +6 -0
- package/dist/output/build-authorize-data.d.ts.map +1 -1
- package/dist/output/build-authorize-data.js +1 -0
- package/dist/output/build-authorize-data.js.map +1 -1
- package/dist/replay/replay-manager.d.ts +1 -0
- package/dist/replay/replay-manager.d.ts.map +1 -1
- package/dist/replay/replay-manager.js +3 -0
- package/dist/replay/replay-manager.js.map +1 -1
- package/dist/replay/replay-store.d.ts +1 -1
- package/dist/request/request-info.d.ts +2 -0
- package/dist/request/request-info.d.ts.map +1 -1
- package/dist/request/request-manager.d.ts +3 -9
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +52 -77
- package/dist/request/request-manager.js.map +1 -1
- package/dist/request/types.d.ts +10 -10
- package/dist/signer/signed-token-payload.d.ts +85 -85
- package/dist/signer/signer.d.ts +23 -30
- package/dist/signer/signer.d.ts.map +1 -1
- package/dist/signer/signer.js +0 -40
- package/dist/signer/signer.js.map +1 -1
- package/dist/token/token-claims.d.ts +81 -81
- package/dist/token/token-manager.d.ts +1 -2
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +10 -37
- package/dist/token/token-manager.js.map +1 -1
- package/dist/token/types.d.ts +10 -10
- package/package.json +2 -3
- package/src/account/account.ts +11 -7
- package/src/assets/app/backend-data.ts +9 -2
- package/src/assets/app/components/accept-form.tsx +65 -51
- package/src/assets/app/components/client-name.tsx +24 -16
- package/src/assets/app/views/accept-view.tsx +7 -4
- package/src/assets/app/views/authorize-view.tsx +2 -1
- package/src/assets/assets-middleware.ts +14 -2
- package/src/client/client-manager.ts +78 -60
- package/src/client/client.ts +1 -4
- package/src/constants.ts +3 -0
- package/src/device/device-manager.ts +7 -1
- package/src/errors/invalid-authorization-details-error.ts +9 -4
- package/src/lib/http/request.ts +61 -15
- package/src/metadata/build-metadata.ts +9 -42
- package/src/oauth-hooks.ts +3 -13
- package/src/oauth-provider.ts +181 -159
- package/src/oauth-verifier.ts +1 -2
- package/src/output/build-authorize-data.ts +8 -0
- package/src/replay/replay-manager.ts +9 -0
- package/src/replay/replay-store.ts +1 -1
- package/src/request/request-info.ts +2 -0
- package/src/request/request-manager.ts +81 -107
- package/src/signer/signer.ts +0 -63
- package/src/token/token-manager.ts +8 -41
- package/dist/oidc/claims.d.ts +0 -16
- package/dist/oidc/claims.d.ts.map +0 -1
- package/dist/oidc/claims.js +0 -29
- package/dist/oidc/claims.js.map +0 -1
- package/dist/oidc/userinfo.d.ts +0 -7
- package/dist/oidc/userinfo.d.ts.map +0 -1
- package/dist/oidc/userinfo.js +0 -3
- package/dist/oidc/userinfo.js.map +0 -1
- package/dist/parameters/claims-requested.d.ts +0 -3
- package/dist/parameters/claims-requested.d.ts.map +0 -1
- package/dist/parameters/claims-requested.js +0 -77
- package/dist/parameters/claims-requested.js.map +0 -1
- package/dist/parameters/oidc-payload.d.ts +0 -31
- package/dist/parameters/oidc-payload.d.ts.map +0 -1
- package/dist/parameters/oidc-payload.js +0 -25
- package/dist/parameters/oidc-payload.js.map +0 -1
- package/src/assets/app/components/client-identifier.tsx +0 -31
- package/src/oidc/claims.ts +0 -35
- package/src/oidc/userinfo.ts +0 -11
- package/src/parameters/claims-requested.ts +0 -106
- package/src/parameters/oidc-payload.ts +0 -28
@@ -17,6 +17,7 @@ import {
|
|
17
17
|
isLoopbackUrl,
|
18
18
|
isOAuthClientIdDiscoverable,
|
19
19
|
isOAuthClientIdLoopback,
|
20
|
+
OAuthAuthorizationServerMetadata,
|
20
21
|
OAuthClientIdDiscoverable,
|
21
22
|
OAuthClientIdLoopback,
|
22
23
|
OAuthClientMetadata,
|
@@ -55,9 +56,10 @@ export type LoopbackMetadataGetter = (
|
|
55
56
|
|
56
57
|
export class ClientManager {
|
57
58
|
protected readonly jwks: CachedGetter<string, Jwks>
|
58
|
-
protected readonly
|
59
|
+
protected readonly metadataGetter: CachedGetter<string, OAuthClientMetadata>
|
59
60
|
|
60
61
|
constructor(
|
62
|
+
protected readonly serverMetadata: OAuthAuthorizationServerMetadata,
|
61
63
|
protected readonly keyset: Keyset,
|
62
64
|
protected readonly hooks: OAuthHooks,
|
63
65
|
protected readonly store: ClientStore | null,
|
@@ -76,7 +78,7 @@ export class ClientManager {
|
|
76
78
|
return jwks
|
77
79
|
}, clientJwksCache)
|
78
80
|
|
79
|
-
this.
|
81
|
+
this.metadataGetter = new CachedGetter(async (uri, options) => {
|
80
82
|
const metadata = await fetch(buildJsonGetRequest(uri, options)).then(
|
81
83
|
fetchMetadataHandler,
|
82
84
|
)
|
@@ -159,7 +161,7 @@ export class ClientManager {
|
|
159
161
|
): Promise<OAuthClientMetadata> {
|
160
162
|
const metadataUrl = parseDiscoverableClientId(clientId)
|
161
163
|
|
162
|
-
const metadata = await this.
|
164
|
+
const metadata = await this.metadataGetter.get(metadataUrl.href)
|
163
165
|
|
164
166
|
// Note: we do *not* re-validate the metadata here, as the metadata is
|
165
167
|
// validated within the getter. This is to avoid double validation.
|
@@ -195,6 +197,18 @@ export class ClientManager {
|
|
195
197
|
)
|
196
198
|
}
|
197
199
|
|
200
|
+
// Known OIDC specific parameters
|
201
|
+
for (const k of [
|
202
|
+
'default_max_age',
|
203
|
+
'userinfo_signed_response_alg',
|
204
|
+
'id_token_signed_response_alg',
|
205
|
+
'userinfo_encrypted_response_alg',
|
206
|
+
] as const) {
|
207
|
+
if (metadata[k] != null) {
|
208
|
+
throw new InvalidClientMetadataError(`Unsupported "${k}" parameter`)
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
198
212
|
const clientUriUrl = metadata.client_uri
|
199
213
|
? new URL(metadata.client_uri)
|
200
214
|
: null
|
@@ -204,13 +218,27 @@ export class ClientManager {
|
|
204
218
|
throw new InvalidClientMetadataError('client_uri must be a valid URL')
|
205
219
|
}
|
206
220
|
|
207
|
-
const scopes = metadata.scope?.split(' ')
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
221
|
+
const scopes = metadata.scope?.split(' ').filter(Boolean)
|
222
|
+
|
223
|
+
const dupScope = scopes?.find(isDuplicate)
|
224
|
+
if (dupScope) {
|
225
|
+
throw new InvalidClientMetadataError(`Duplicate scope "${dupScope}"`)
|
226
|
+
}
|
227
|
+
|
228
|
+
if (scopes) {
|
229
|
+
for (const scope of scopes) {
|
230
|
+
// Note, once we have dynamic scopes, this check will need to be
|
231
|
+
// updated to check against the server's supported scopes.
|
232
|
+
if (!this.serverMetadata.scopes_supported?.includes(scope)) {
|
233
|
+
throw new InvalidClientMetadataError(`Unsupported scope "${scope}"`)
|
234
|
+
}
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
const dupGrantType = metadata.grant_types.find(isDuplicate)
|
239
|
+
if (dupGrantType) {
|
212
240
|
throw new InvalidClientMetadataError(
|
213
|
-
|
241
|
+
`Duplicate grant type "${dupGrantType}"`,
|
214
242
|
)
|
215
243
|
}
|
216
244
|
|
@@ -218,8 +246,8 @@ export class ClientManager {
|
|
218
246
|
switch (grantType) {
|
219
247
|
case 'authorization_code':
|
220
248
|
case 'refresh_token':
|
221
|
-
case 'implicit': // Required by OIDC (for id_token)
|
222
249
|
continue
|
250
|
+
case 'implicit':
|
223
251
|
case 'password':
|
224
252
|
throw new InvalidClientMetadataError(
|
225
253
|
`Grant type "${grantType}" is not allowed`,
|
@@ -241,35 +269,6 @@ export class ClientManager {
|
|
241
269
|
)
|
242
270
|
}
|
243
271
|
|
244
|
-
if (
|
245
|
-
metadata.userinfo_signed_response_alg &&
|
246
|
-
!this.keyset.signAlgorithms.includes(
|
247
|
-
metadata.userinfo_signed_response_alg,
|
248
|
-
)
|
249
|
-
) {
|
250
|
-
throw new InvalidClientMetadataError(
|
251
|
-
`Unsupported "userinfo_signed_response_alg" ${metadata.userinfo_signed_response_alg}`,
|
252
|
-
)
|
253
|
-
}
|
254
|
-
|
255
|
-
if (
|
256
|
-
metadata.id_token_signed_response_alg &&
|
257
|
-
!this.keyset.signAlgorithms.includes(
|
258
|
-
metadata.id_token_signed_response_alg,
|
259
|
-
)
|
260
|
-
) {
|
261
|
-
throw new InvalidClientMetadataError(
|
262
|
-
`Unsupported "id_token_signed_response_alg" ${metadata.id_token_signed_response_alg}`,
|
263
|
-
)
|
264
|
-
}
|
265
|
-
|
266
|
-
if (metadata.userinfo_encrypted_response_alg) {
|
267
|
-
// We only support signature for now.
|
268
|
-
throw new InvalidClientMetadataError(
|
269
|
-
'Encrypted userinfo response is not supported',
|
270
|
-
)
|
271
|
-
}
|
272
|
-
|
273
272
|
const method = metadata[`token_endpoint_auth_method`]
|
274
273
|
switch (method) {
|
275
274
|
case undefined:
|
@@ -338,37 +337,28 @@ export class ClientManager {
|
|
338
337
|
}
|
339
338
|
|
340
339
|
for (const responseType of metadata.response_types) {
|
341
|
-
|
340
|
+
if (responseType.includes('id_token')) {
|
341
|
+
throw new InvalidClientMetadataError(
|
342
|
+
`OpenID Connect response type "${responseType}" is not supported`,
|
343
|
+
)
|
344
|
+
}
|
342
345
|
|
343
346
|
// ATPROTO spec requires the use of PKCE
|
344
|
-
if (
|
347
|
+
if (responseType !== 'code') {
|
345
348
|
throw new InvalidClientMetadataError(
|
346
|
-
|
349
|
+
`Unsupported response type "${responseType}"`,
|
347
350
|
)
|
348
351
|
}
|
349
352
|
|
350
353
|
// Consistency check
|
351
354
|
if (
|
352
|
-
|
355
|
+
responseType === 'code' &&
|
353
356
|
!metadata.grant_types.includes('authorization_code')
|
354
357
|
) {
|
355
358
|
throw new InvalidClientMetadataError(
|
356
359
|
`Response type "${responseType}" requires the "authorization_code" grant type`,
|
357
360
|
)
|
358
361
|
}
|
359
|
-
|
360
|
-
// Asking for "code token" or "code id_token" is fine (as long as the
|
361
|
-
// grant_types includes "authorization_code" and the scope includes
|
362
|
-
// "openid"). Asking for "token" or "id_token" (without "code") requires
|
363
|
-
// the "implicit" grant type.
|
364
|
-
if (
|
365
|
-
(rt.includes('token') || rt.includes('id_token')) &&
|
366
|
-
!metadata.grant_types.includes('implicit')
|
367
|
-
) {
|
368
|
-
throw new InvalidClientMetadataError(
|
369
|
-
`Response type "${responseType}" requires the "implicit" grant type`,
|
370
|
-
)
|
371
|
-
}
|
372
362
|
}
|
373
363
|
|
374
364
|
if (metadata.application_type === 'native') {
|
@@ -383,11 +373,33 @@ export class ClientManager {
|
|
383
373
|
// > accordingly.
|
384
374
|
}
|
385
375
|
|
376
|
+
if (metadata.authorization_details_types?.length) {
|
377
|
+
const dupAuthDetailsType =
|
378
|
+
metadata.authorization_details_types.find(isDuplicate)
|
379
|
+
if (dupAuthDetailsType) {
|
380
|
+
throw new InvalidClientMetadataError(
|
381
|
+
`Duplicate authorization_details_type "${dupAuthDetailsType}"`,
|
382
|
+
)
|
383
|
+
}
|
384
|
+
|
385
|
+
const authorizationDetailsTypesSupported =
|
386
|
+
this.serverMetadata.authorization_details_types_supported
|
387
|
+
if (!authorizationDetailsTypesSupported) {
|
388
|
+
throw new InvalidClientMetadataError(
|
389
|
+
'authorization_details_types are not supported',
|
390
|
+
)
|
391
|
+
}
|
392
|
+
for (const type of metadata.authorization_details_types) {
|
393
|
+
if (!authorizationDetailsTypesSupported.includes(type)) {
|
394
|
+
throw new InvalidClientMetadataError(
|
395
|
+
`Unsupported authorization_details_type "${type}"`,
|
396
|
+
)
|
397
|
+
}
|
398
|
+
}
|
399
|
+
}
|
400
|
+
|
386
401
|
if (!metadata.redirect_uris?.length) {
|
387
|
-
//
|
388
|
-
//
|
389
|
-
// > OPs can require that request_uri values used be pre-registered with
|
390
|
-
// > the require_request_uri_registration discovery parameter.
|
402
|
+
// ATPROTO spec requires that at least one redirect URI is provided
|
391
403
|
|
392
404
|
throw new InvalidClientMetadataError(
|
393
405
|
'At least one redirect_uri is required',
|
@@ -786,6 +798,12 @@ export class ClientManager {
|
|
786
798
|
}
|
787
799
|
}
|
788
800
|
|
801
|
+
function isDuplicate<
|
802
|
+
T extends string | number | boolean | null | undefined | symbol,
|
803
|
+
>(value: T, index: number, array: T[]) {
|
804
|
+
return array.includes(value, index + 1)
|
805
|
+
}
|
806
|
+
|
789
807
|
function reverseDomain(domain: string) {
|
790
808
|
return domain.split('.').reverse().join('.')
|
791
809
|
}
|
package/src/client/client.ts
CHANGED
@@ -140,6 +140,7 @@ export class Client {
|
|
140
140
|
audience: checks.audience,
|
141
141
|
subject: this.id,
|
142
142
|
maxTokenAge: CLIENT_ASSERTION_MAX_AGE / 1000,
|
143
|
+
requiredClaims: ['jti'],
|
143
144
|
}).catch((err) => {
|
144
145
|
if (err instanceof JOSEError) {
|
145
146
|
const msg = `Validation of "client_assertion" failed: ${err.message}`
|
@@ -153,10 +154,6 @@ export class Client {
|
|
153
154
|
throw new InvalidClientError(`"kid" required in client_assertion`)
|
154
155
|
}
|
155
156
|
|
156
|
-
if (!result.payload.jti) {
|
157
|
-
throw new InvalidClientError(`"jti" required in client_assertion`)
|
158
|
-
}
|
159
|
-
|
160
157
|
const clientAuth: ClientAuth = {
|
161
158
|
method: CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
162
159
|
jkt: await authJwkThumbprint(result.key),
|
package/src/constants.ts
CHANGED
@@ -100,10 +100,16 @@ export class DeviceManager {
|
|
100
100
|
public async load(
|
101
101
|
req: IncomingMessage,
|
102
102
|
res: ServerResponse,
|
103
|
+
forceRotate = false,
|
103
104
|
): Promise<{ deviceId: DeviceId }> {
|
104
105
|
const cookie = await this.getCookie(req)
|
105
106
|
if (cookie) {
|
106
|
-
return this.refresh(
|
107
|
+
return this.refresh(
|
108
|
+
req,
|
109
|
+
res,
|
110
|
+
cookie.value,
|
111
|
+
forceRotate || cookie.mustRotate,
|
112
|
+
)
|
107
113
|
} else {
|
108
114
|
return this.create(req, res)
|
109
115
|
}
|
@@ -1,4 +1,5 @@
|
|
1
|
-
import {
|
1
|
+
import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types'
|
2
|
+
import { AccessDeniedError } from './access-denied-error.js'
|
2
3
|
|
3
4
|
/**
|
4
5
|
* @see
|
@@ -15,8 +16,12 @@ import { OAuthError } from './oauth-error.js'
|
|
15
16
|
* - contains fields with invalid values for the authorization details type, or
|
16
17
|
* - is missing required fields for the authorization details type.
|
17
18
|
*/
|
18
|
-
export class InvalidAuthorizationDetailsError extends
|
19
|
-
constructor(
|
20
|
-
|
19
|
+
export class InvalidAuthorizationDetailsError extends AccessDeniedError {
|
20
|
+
constructor(
|
21
|
+
parameters: OAuthAuthenticationRequestParameters,
|
22
|
+
error_description: string,
|
23
|
+
cause?: unknown,
|
24
|
+
) {
|
25
|
+
super(parameters, error_description, 'invalid_authorization_details', cause)
|
21
26
|
}
|
22
27
|
}
|
package/src/lib/http/request.ts
CHANGED
@@ -28,6 +28,27 @@ export async function validateRequestPayload<S extends z.ZodTypeAny>(
|
|
28
28
|
return schema.parseAsync(payload, { path: ['body'] })
|
29
29
|
}
|
30
30
|
|
31
|
+
export function validateHeaderValue(
|
32
|
+
req: IncomingMessage,
|
33
|
+
name: keyof IncomingMessage['headers'],
|
34
|
+
allowedValues: readonly (string | null)[],
|
35
|
+
) {
|
36
|
+
const value = req.headers[name] ?? null
|
37
|
+
|
38
|
+
if (Array.isArray(value)) {
|
39
|
+
throw createHttpError(400, `Invalid ${name} header`)
|
40
|
+
}
|
41
|
+
|
42
|
+
if (!allowedValues.includes(value)) {
|
43
|
+
throw createHttpError(
|
44
|
+
400,
|
45
|
+
value
|
46
|
+
? `Forbidden ${name} header "${value}" (expected ${allowedValues})`
|
47
|
+
: `Missing ${name} header`,
|
48
|
+
)
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
31
52
|
export function validateFetchMode(
|
32
53
|
req: IncomingMessage,
|
33
54
|
res: ServerResponse,
|
@@ -39,20 +60,45 @@ export function validateFetchMode(
|
|
39
60
|
| 'cors'
|
40
61
|
)[],
|
41
62
|
) {
|
42
|
-
|
63
|
+
validateHeaderValue(req, 'sec-fetch-mode', expectedMode)
|
64
|
+
}
|
43
65
|
|
44
|
-
|
45
|
-
|
46
|
-
|
66
|
+
export function validateFetchDest(
|
67
|
+
req: IncomingMessage,
|
68
|
+
res: ServerResponse,
|
69
|
+
expectedDest: readonly (
|
70
|
+
| null
|
71
|
+
| 'document'
|
72
|
+
| 'embed'
|
73
|
+
| 'font'
|
74
|
+
| 'image'
|
75
|
+
| 'manifest'
|
76
|
+
| 'media'
|
77
|
+
| 'object'
|
78
|
+
| 'report'
|
79
|
+
| 'script'
|
80
|
+
| 'serviceworker'
|
81
|
+
| 'sharedworker'
|
82
|
+
| 'style'
|
83
|
+
| 'worker'
|
84
|
+
| 'xslt'
|
85
|
+
)[],
|
86
|
+
) {
|
87
|
+
validateHeaderValue(req, 'sec-fetch-dest', expectedDest)
|
88
|
+
}
|
47
89
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
90
|
+
export function validateFetchSite(
|
91
|
+
req: IncomingMessage,
|
92
|
+
res: ServerResponse,
|
93
|
+
expectedSite: readonly (
|
94
|
+
| null
|
95
|
+
| 'same-origin'
|
96
|
+
| 'same-site'
|
97
|
+
| 'cross-site'
|
98
|
+
| 'none'
|
99
|
+
)[],
|
100
|
+
) {
|
101
|
+
validateHeaderValue(req, 'sec-fetch-site', expectedSite)
|
56
102
|
}
|
57
103
|
|
58
104
|
export function validateReferer(
|
@@ -64,7 +110,7 @@ export function validateReferer(
|
|
64
110
|
const referer = req.headers['referer']
|
65
111
|
const refererUrl = referer ? new URL(referer) : null
|
66
112
|
if (refererUrl ? !urlMatch(refererUrl, reference) : !allowNull) {
|
67
|
-
throw createHttpError(
|
113
|
+
throw createHttpError(400, `Invalid referer ${referer}`)
|
68
114
|
}
|
69
115
|
}
|
70
116
|
|
@@ -95,7 +141,7 @@ export function validateSameOrigin(
|
|
95
141
|
) {
|
96
142
|
const reqOrigin = req.headers['origin']
|
97
143
|
if (reqOrigin ? reqOrigin !== origin : !allowNull) {
|
98
|
-
throw createHttpError(
|
144
|
+
throw createHttpError(400, `Invalid origin ${reqOrigin}`)
|
99
145
|
}
|
100
146
|
}
|
101
147
|
|
@@ -113,7 +159,7 @@ export function validateCsrfToken(
|
|
113
159
|
!cookieName ||
|
114
160
|
cookies[cookieName] !== csrfToken
|
115
161
|
) {
|
116
|
-
throw createHttpError(
|
162
|
+
throw createHttpError(400, `Invalid CSRF token`)
|
117
163
|
}
|
118
164
|
|
119
165
|
if (clearCookie) {
|
@@ -2,11 +2,9 @@ import { Keyset } from '@atproto/jwk'
|
|
2
2
|
import { OAuthAuthorizationServerMetadata } from '@atproto/oauth-types'
|
3
3
|
|
4
4
|
import { Client } from '../client/client.js'
|
5
|
-
import { OIDC_STANDARD_CLAIMS } from '../oidc/claims.js'
|
6
5
|
import { VERIFY_ALGOS } from '../lib/util/crypto.js'
|
7
6
|
|
8
7
|
export type CustomMetadata = {
|
9
|
-
claims_supported?: string[]
|
10
8
|
scopes_supported?: string[]
|
11
9
|
authorization_details_types_supported?: string[]
|
12
10
|
protected_resources?: string[]
|
@@ -25,35 +23,10 @@ export function buildMetadata(
|
|
25
23
|
issuer,
|
26
24
|
|
27
25
|
scopes_supported: [
|
28
|
-
'
|
29
|
-
|
30
|
-
'email',
|
31
|
-
'phone',
|
32
|
-
'profile',
|
33
|
-
|
26
|
+
'atproto',
|
27
|
+
//
|
34
28
|
...(customMetadata?.scopes_supported ?? []),
|
35
29
|
],
|
36
|
-
claims_supported: [
|
37
|
-
/* IESG (Always provided) */
|
38
|
-
|
39
|
-
'sub', // did
|
40
|
-
'iss', // Authorization Server Origin
|
41
|
-
'aud',
|
42
|
-
'exp',
|
43
|
-
'iat',
|
44
|
-
'jti',
|
45
|
-
'client_id',
|
46
|
-
|
47
|
-
/* OpenID */
|
48
|
-
|
49
|
-
// 'acr', // "0"
|
50
|
-
// 'amr',
|
51
|
-
// 'azp',
|
52
|
-
'auth_time', // number - seconds since epoch
|
53
|
-
'nonce', // always required in "id_token", why would it not be supported?
|
54
|
-
|
55
|
-
...(customMetadata?.claims_supported ?? OIDC_STANDARD_CLAIMS),
|
56
|
-
],
|
57
30
|
subject_types_supported: [
|
58
31
|
//
|
59
32
|
'public', // The same "sub" is returned for all clients
|
@@ -62,15 +35,15 @@ export function buildMetadata(
|
|
62
35
|
response_types_supported: [
|
63
36
|
// OAuth
|
64
37
|
'code',
|
65
|
-
'token',
|
38
|
+
// 'token',
|
66
39
|
|
67
40
|
// OpenID
|
68
|
-
'none',
|
69
|
-
'code id_token token',
|
70
|
-
'code id_token',
|
71
|
-
'code token',
|
72
|
-
'id_token token',
|
73
|
-
'id_token',
|
41
|
+
// 'none',
|
42
|
+
// 'code id_token token',
|
43
|
+
// 'code id_token',
|
44
|
+
// 'code token',
|
45
|
+
// 'id_token token',
|
46
|
+
// 'id_token',
|
74
47
|
],
|
75
48
|
response_modes_supported: [
|
76
49
|
// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
|
@@ -93,7 +66,6 @@ export function buildMetadata(
|
|
93
66
|
//
|
94
67
|
'en-US',
|
95
68
|
],
|
96
|
-
id_token_signing_alg_values_supported: [...keyset.signAlgorithms],
|
97
69
|
display_values_supported: [
|
98
70
|
//
|
99
71
|
'page',
|
@@ -110,10 +82,6 @@ export function buildMetadata(
|
|
110
82
|
request_object_encryption_alg_values_supported: [], // None
|
111
83
|
request_object_encryption_enc_values_supported: [], // None
|
112
84
|
|
113
|
-
// No claim makes sense to be translated
|
114
|
-
claims_locales_supported: [],
|
115
|
-
|
116
|
-
claims_parameter_supported: true,
|
117
85
|
request_parameter_supported: true,
|
118
86
|
request_uri_parameter_supported: true,
|
119
87
|
require_request_uri_registration: true,
|
@@ -130,7 +98,6 @@ export function buildMetadata(
|
|
130
98
|
|
131
99
|
introspection_endpoint: new URL('/oauth/introspect', issuer).href,
|
132
100
|
|
133
|
-
userinfo_endpoint: new URL('/oauth/userinfo', issuer).href,
|
134
101
|
// end_session_endpoint: new URL('/oauth/logout', issuer).href,
|
135
102
|
|
136
103
|
// https://datatracker.ietf.org/doc/html/rfc9126#section-5
|
package/src/oauth-hooks.ts
CHANGED
@@ -11,6 +11,7 @@ import { ClientAuth } from './client/client-auth.js'
|
|
11
11
|
import { ClientId } from './client/client-id.js'
|
12
12
|
import { ClientInfo } from './client/client-info.js'
|
13
13
|
import { Client } from './client/client.js'
|
14
|
+
import { InvalidAuthorizationDetailsError } from './errors/invalid-authorization-details-error.js'
|
14
15
|
import { Awaitable } from './lib/util/type.js'
|
15
16
|
|
16
17
|
// Make sure all types needed to implement the OAuthHooks are exported
|
@@ -20,6 +21,7 @@ export type {
|
|
20
21
|
ClientAuth,
|
21
22
|
ClientId,
|
22
23
|
ClientInfo,
|
24
|
+
InvalidAuthorizationDetailsError,
|
23
25
|
Jwks,
|
24
26
|
OAuthAuthenticationRequestParameters,
|
25
27
|
OAuthAuthorizationDetails,
|
@@ -42,7 +44,7 @@ export type OAuthHooks = {
|
|
42
44
|
|
43
45
|
/**
|
44
46
|
* Allows enriching the authorization details with additional information
|
45
|
-
*
|
47
|
+
* when the tokens are issued.
|
46
48
|
*
|
47
49
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc9396 | RFC 9396}
|
48
50
|
*/
|
@@ -51,16 +53,4 @@ export type OAuthHooks = {
|
|
51
53
|
parameters: OAuthAuthenticationRequestParameters
|
52
54
|
account: Account
|
53
55
|
}) => Awaitable<undefined | OAuthAuthorizationDetails>
|
54
|
-
|
55
|
-
/**
|
56
|
-
* Allows altering the token response before it is sent to the client.
|
57
|
-
*/
|
58
|
-
onTokenResponse?: (
|
59
|
-
tokenResponse: OAuthTokenResponse,
|
60
|
-
data: {
|
61
|
-
client: Client
|
62
|
-
parameters: OAuthAuthenticationRequestParameters
|
63
|
-
account: Account
|
64
|
-
},
|
65
|
-
) => Awaitable<void>
|
66
56
|
}
|