@atproto/oauth-provider 0.1.2 → 0.2.0
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/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
|
}
|