@atproto/oauth-provider 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +46 -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 +3 -3
- 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 +91 -77
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client.d.ts +2 -3
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +6 -12
- 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/dpop/dpop-manager.d.ts +0 -1
- package/dist/dpop/dpop-manager.d.ts.map +1 -1
- package/dist/dpop/dpop-manager.js +1 -4
- package/dist/dpop/dpop-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/parser.d.ts +13 -7
- package/dist/lib/http/parser.d.ts.map +1 -1
- package/dist/lib/http/parser.js +29 -9
- package/dist/lib/http/parser.js.map +1 -1
- package/dist/lib/http/request.d.ts +8 -5
- 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/lib/http/stream.d.ts.map +1 -1
- package/dist/lib/http/stream.js +3 -2
- package/dist/lib/http/stream.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 -49
- 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 +10 -15
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +176 -114
- 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 +88 -88
- package/dist/signer/signer.d.ts +24 -31
- 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 +84 -84
- 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 +3 -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/components/url-viewer.tsx +3 -3
- 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 +124 -120
- package/src/client/client.ts +5 -17
- package/src/constants.ts +3 -0
- package/src/device/device-manager.ts +7 -1
- package/src/dpop/dpop-manager.ts +1 -6
- package/src/errors/invalid-authorization-details-error.ts +9 -4
- package/src/lib/http/parser.ts +37 -13
- package/src/lib/http/request.ts +61 -15
- package/src/lib/http/stream.ts +5 -2
- package/src/metadata/build-metadata.ts +9 -56
- package/src/oauth-hooks.ts +3 -13
- package/src/oauth-provider.ts +187 -177
- 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
package/src/client/client.ts
CHANGED
@@ -3,7 +3,6 @@ import {
|
|
3
3
|
CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
4
4
|
OAuthClientIdentification,
|
5
5
|
OAuthClientMetadata,
|
6
|
-
OAuthEndpointName,
|
7
6
|
} from '@atproto/oauth-types'
|
8
7
|
import {
|
9
8
|
UnsecuredJWT,
|
@@ -101,13 +100,6 @@ export class Client {
|
|
101
100
|
})
|
102
101
|
}
|
103
102
|
|
104
|
-
protected getAuthMethod(endpoint: OAuthEndpointName) {
|
105
|
-
return (
|
106
|
-
this.metadata[`${endpoint}_endpoint_auth_method`] ||
|
107
|
-
this.metadata[`token_endpoint_auth_method`]
|
108
|
-
)
|
109
|
-
}
|
110
|
-
|
111
103
|
/**
|
112
104
|
* @see {@link https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1}
|
113
105
|
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-bearer-11#section-3}
|
@@ -115,7 +107,6 @@ export class Client {
|
|
115
107
|
*/
|
116
108
|
public async verifyCredentials(
|
117
109
|
input: OAuthClientIdentification,
|
118
|
-
endpoint: OAuthEndpointName,
|
119
110
|
checks: {
|
120
111
|
audience: string
|
121
112
|
},
|
@@ -124,7 +115,7 @@ export class Client {
|
|
124
115
|
// for replay protection
|
125
116
|
nonce?: string
|
126
117
|
}> {
|
127
|
-
const method = this.
|
118
|
+
const method = this.metadata[`token_endpoint_auth_method`]
|
128
119
|
|
129
120
|
if (method === 'none') {
|
130
121
|
const clientAuth: ClientAuth = { method: 'none' }
|
@@ -149,6 +140,7 @@ export class Client {
|
|
149
140
|
audience: checks.audience,
|
150
141
|
subject: this.id,
|
151
142
|
maxTokenAge: CLIENT_ASSERTION_MAX_AGE / 1000,
|
143
|
+
requiredClaims: ['jti'],
|
152
144
|
}).catch((err) => {
|
153
145
|
if (err instanceof JOSEError) {
|
154
146
|
const msg = `Validation of "client_assertion" failed: ${err.message}`
|
@@ -162,10 +154,6 @@ export class Client {
|
|
162
154
|
throw new InvalidClientError(`"kid" required in client_assertion`)
|
163
155
|
}
|
164
156
|
|
165
|
-
if (!result.payload.jti) {
|
166
|
-
throw new InvalidClientError(`"jti" required in client_assertion`)
|
167
|
-
}
|
168
|
-
|
169
157
|
const clientAuth: ClientAuth = {
|
170
158
|
method: CLIENT_ASSERTION_TYPE_JWT_BEARER,
|
171
159
|
jkt: await authJwkThumbprint(result.key),
|
@@ -192,7 +180,7 @@ export class Client {
|
|
192
180
|
}
|
193
181
|
|
194
182
|
throw new InvalidClientMetadataError(
|
195
|
-
`Unsupported
|
183
|
+
`Unsupported token_endpoint_auth_method "${method}"`,
|
196
184
|
)
|
197
185
|
}
|
198
186
|
|
@@ -204,11 +192,11 @@ export class Client {
|
|
204
192
|
*/
|
205
193
|
public async validateClientAuth(clientAuth: ClientAuth): Promise<boolean> {
|
206
194
|
if (clientAuth.method === 'none') {
|
207
|
-
return this.
|
195
|
+
return this.metadata[`token_endpoint_auth_method`] === 'none'
|
208
196
|
}
|
209
197
|
|
210
198
|
if (clientAuth.method === CLIENT_ASSERTION_TYPE_JWT_BEARER) {
|
211
|
-
if (this.
|
199
|
+
if (this.metadata[`token_endpoint_auth_method`] !== 'private_key_jwt') {
|
212
200
|
return false
|
213
201
|
}
|
214
202
|
try {
|
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
|
}
|
package/src/dpop/dpop-manager.ts
CHANGED
@@ -52,13 +52,12 @@ export class DpopManager {
|
|
52
52
|
|
53
53
|
const { protectedHeader, payload } = await jwtVerify<{
|
54
54
|
iat: number
|
55
|
-
exp: number
|
56
55
|
jti: string
|
57
56
|
}>(proof, EmbeddedJWK, {
|
58
57
|
typ: 'dpop+jwt',
|
59
58
|
maxTokenAge: 10,
|
60
59
|
clockTolerance: DPOP_NONCE_MAX_AGE / 1e3,
|
61
|
-
requiredClaims: ['iat', '
|
60
|
+
requiredClaims: ['iat', 'jti'],
|
62
61
|
}).catch((err) => {
|
63
62
|
const message =
|
64
63
|
err instanceof JOSEError
|
@@ -71,10 +70,6 @@ export class DpopManager {
|
|
71
70
|
throw new InvalidDpopProofError('Invalid or missing jti property')
|
72
71
|
}
|
73
72
|
|
74
|
-
if (payload.exp - payload.iat > DPOP_NONCE_MAX_AGE / 3 / 1e3) {
|
75
|
-
throw new InvalidDpopProofError('DPoP proof validity too long')
|
76
|
-
}
|
77
|
-
|
78
73
|
// Note rfc9110#section-9.1 states that the method name is case-sensitive
|
79
74
|
if (!htm || htm !== payload['htm']) {
|
80
75
|
throw new InvalidDpopProofError('DPoP htm mismatch')
|
@@ -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/parser.ts
CHANGED
@@ -1,13 +1,33 @@
|
|
1
1
|
import { parse as parseJson } from '@hapi/bourne'
|
2
|
+
import { type as hapiContentType } from '@hapi/content'
|
2
3
|
import createHttpError from 'http-errors'
|
3
4
|
|
4
5
|
export type JsonScalar = string | number | boolean | null
|
5
6
|
export type Json = JsonScalar | Json[] | { [_ in string]?: Json }
|
6
7
|
|
8
|
+
export const parseContentType = (type: string): ContentType => {
|
9
|
+
try {
|
10
|
+
return hapiContentType(type)
|
11
|
+
} catch (err) {
|
12
|
+
// De-boomify the error
|
13
|
+
if (err?.['isBoom']) {
|
14
|
+
throw createHttpError(err['output']['statusCode'], err['message'])
|
15
|
+
}
|
16
|
+
|
17
|
+
throw err
|
18
|
+
}
|
19
|
+
}
|
20
|
+
|
21
|
+
export type ContentType = {
|
22
|
+
mime: string
|
23
|
+
charset?: string
|
24
|
+
boundary?: string
|
25
|
+
}
|
26
|
+
|
7
27
|
export type Parser<T extends string = string, R = unknown> = {
|
8
28
|
readonly name: string
|
9
|
-
readonly test: (
|
10
|
-
readonly parse: (buffer: Buffer) => R
|
29
|
+
readonly test: (mime: string) => mime is T
|
30
|
+
readonly parse: (buffer: Buffer, type: ContentType) => R
|
11
31
|
}
|
12
32
|
|
13
33
|
export type ParserName<P extends Parser> = P extends { readonly name: infer N }
|
@@ -22,12 +42,13 @@ export type ParserForType<P extends Parser, T> =
|
|
22
42
|
export const parsers = [
|
23
43
|
{
|
24
44
|
name: 'json',
|
25
|
-
test: (
|
26
|
-
|
27
|
-
): type is `application/json` | `application/${string}+json` => {
|
28
|
-
return /^application\/(?:.+\+)?json$/.test(type)
|
45
|
+
test: (mime): mime is `application/json` | `application/${string}+json` => {
|
46
|
+
return /^application\/(?:.+\+)?json$/.test(mime)
|
29
47
|
},
|
30
|
-
parse: (buffer
|
48
|
+
parse: (buffer, { charset }): Json => {
|
49
|
+
if (charset != null && !/^utf-?8$/i.test(charset)) {
|
50
|
+
throw createHttpError(415, 'Unsupported charset')
|
51
|
+
}
|
31
52
|
try {
|
32
53
|
return parseJson(buffer.toString())
|
33
54
|
} catch (err) {
|
@@ -37,10 +58,13 @@ export const parsers = [
|
|
37
58
|
},
|
38
59
|
{
|
39
60
|
name: 'urlencoded',
|
40
|
-
test: (
|
41
|
-
return
|
61
|
+
test: (mime): mime is 'application/x-www-form-urlencoded' => {
|
62
|
+
return mime === 'application/x-www-form-urlencoded'
|
42
63
|
},
|
43
|
-
parse: (buffer
|
64
|
+
parse: (buffer, { charset }): Partial<Record<string, string>> => {
|
65
|
+
if (charset != null && !/^utf-?8$/i.test(charset)) {
|
66
|
+
throw createHttpError(415, 'Unsupported charset')
|
67
|
+
}
|
44
68
|
try {
|
45
69
|
if (!buffer.length) return {}
|
46
70
|
return Object.fromEntries(new URLSearchParams(buffer.toString()))
|
@@ -51,10 +75,10 @@ export const parsers = [
|
|
51
75
|
},
|
52
76
|
{
|
53
77
|
name: 'bytes',
|
54
|
-
test: (
|
55
|
-
return
|
78
|
+
test: (mime): mime is 'application/octet-stream' => {
|
79
|
+
return mime === 'application/octet-stream'
|
56
80
|
},
|
57
|
-
parse: (buffer
|
81
|
+
parse: (buffer): Buffer => buffer,
|
58
82
|
},
|
59
83
|
] as const satisfies Parser[]
|
60
84
|
|
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) {
|
package/src/lib/http/stream.ts
CHANGED
@@ -7,6 +7,7 @@ import {
|
|
7
7
|
KnownNames,
|
8
8
|
KnownParser,
|
9
9
|
KnownTypes,
|
10
|
+
parseContentType,
|
10
11
|
ParserForType,
|
11
12
|
ParserResult,
|
12
13
|
parsers,
|
@@ -64,9 +65,11 @@ export async function parseStream(
|
|
64
65
|
throw createHttpError(400, 'Invalid content-type')
|
65
66
|
}
|
66
67
|
|
68
|
+
const type = parseContentType(contentType)
|
69
|
+
|
67
70
|
const parser = parsers.find(
|
68
71
|
(parser) =>
|
69
|
-
allow?.includes(parser.name) !== false && parser.test(
|
72
|
+
allow?.includes(parser.name) !== false && parser.test(type.mime),
|
70
73
|
)
|
71
74
|
|
72
75
|
if (!parser) {
|
@@ -74,5 +77,5 @@ export async function parseStream(
|
|
74
77
|
}
|
75
78
|
|
76
79
|
const buffer = await readStream(req)
|
77
|
-
return parser.parse(buffer)
|
80
|
+
return parser.parse(buffer, type)
|
78
81
|
}
|
@@ -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,
|
@@ -127,28 +95,13 @@ export function buildMetadata(
|
|
127
95
|
token_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS],
|
128
96
|
|
129
97
|
revocation_endpoint: new URL('/oauth/revoke', issuer).href,
|
130
|
-
revocation_endpoint_auth_methods_supported: [
|
131
|
-
...Client.AUTH_METHODS_SUPPORTED,
|
132
|
-
],
|
133
|
-
revocation_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS],
|
134
98
|
|
135
99
|
introspection_endpoint: new URL('/oauth/introspect', issuer).href,
|
136
|
-
introspection_endpoint_auth_methods_supported: [
|
137
|
-
...Client.AUTH_METHODS_SUPPORTED,
|
138
|
-
],
|
139
|
-
introspection_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS],
|
140
100
|
|
141
|
-
userinfo_endpoint: new URL('/oauth/userinfo', issuer).href,
|
142
101
|
// end_session_endpoint: new URL('/oauth/logout', issuer).href,
|
143
102
|
|
144
103
|
// https://datatracker.ietf.org/doc/html/rfc9126#section-5
|
145
104
|
pushed_authorization_request_endpoint: new URL('/oauth/par', issuer).href,
|
146
|
-
pushed_authorization_request_endpoint_auth_methods_supported: [
|
147
|
-
...Client.AUTH_METHODS_SUPPORTED,
|
148
|
-
],
|
149
|
-
pushed_authorization_request_endpoint_auth_signing_alg_values_supported: [
|
150
|
-
...VERIFY_ALGOS,
|
151
|
-
],
|
152
105
|
|
153
106
|
require_pushed_authorization_requests: true,
|
154
107
|
|
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
|
}
|