@atproto/oauth-provider 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +36 -0
- package/dist/account/account-store.d.ts +2 -2
- 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.map +1 -1
- package/dist/assets/assets-middleware.js +4 -2
- package/dist/assets/assets-middleware.js.map +1 -1
- package/dist/client/client-manager.d.ts.map +1 -1
- package/dist/client/client-manager.js +127 -118
- package/dist/client/client-manager.js.map +1 -1
- package/dist/client/client-utils.d.ts +1 -2
- package/dist/client/client-utils.d.ts.map +1 -1
- package/dist/client/client-utils.js +3 -12
- package/dist/client/client-utils.js.map +1 -1
- package/dist/client/client.d.ts +8 -3
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +70 -1
- package/dist/client/client.js.map +1 -1
- package/dist/constants.d.ts +0 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -2
- package/dist/constants.js.map +1 -1
- package/dist/errors/access-denied-error.d.ts +4 -4
- package/dist/errors/access-denied-error.d.ts.map +1 -1
- package/dist/errors/access-denied-error.js +2 -2
- package/dist/errors/access-denied-error.js.map +1 -1
- package/dist/errors/account-selection-required-error.d.ts +2 -2
- package/dist/errors/account-selection-required-error.d.ts.map +1 -1
- package/dist/errors/account-selection-required-error.js.map +1 -1
- package/dist/errors/consent-required-error.d.ts +2 -2
- package/dist/errors/consent-required-error.d.ts.map +1 -1
- package/dist/errors/consent-required-error.js.map +1 -1
- package/dist/errors/invalid-authorization-details-error.d.ts +2 -2
- package/dist/errors/invalid-authorization-details-error.d.ts.map +1 -1
- package/dist/errors/invalid-authorization-details-error.js.map +1 -1
- package/dist/errors/invalid-client-id-error.d.ts +1 -1
- package/dist/errors/invalid-client-id-error.d.ts.map +1 -1
- package/dist/errors/invalid-client-id-error.js +12 -6
- package/dist/errors/invalid-client-id-error.js.map +1 -1
- package/dist/errors/invalid-client-metadata-error.d.ts +1 -1
- package/dist/errors/invalid-client-metadata-error.d.ts.map +1 -1
- package/dist/errors/invalid-client-metadata-error.js +11 -3
- package/dist/errors/invalid-client-metadata-error.js.map +1 -1
- package/dist/errors/invalid-parameters-error.d.ts +2 -2
- package/dist/errors/invalid-parameters-error.d.ts.map +1 -1
- package/dist/errors/invalid-parameters-error.js.map +1 -1
- package/dist/errors/invalid-scope-error.d.ts +9 -0
- package/dist/errors/invalid-scope-error.d.ts.map +1 -0
- package/dist/errors/invalid-scope-error.js +14 -0
- package/dist/errors/invalid-scope-error.js.map +1 -0
- package/dist/errors/login-required-error.d.ts +2 -2
- package/dist/errors/login-required-error.d.ts.map +1 -1
- package/dist/errors/login-required-error.js.map +1 -1
- package/dist/lib/html/html.d.ts +1 -1
- package/dist/lib/html/html.d.ts.map +1 -1
- package/dist/lib/html/html.js +14 -11
- package/dist/lib/html/html.js.map +1 -1
- package/dist/lib/http/parser.d.ts +9 -2
- package/dist/lib/http/parser.d.ts.map +1 -1
- package/dist/lib/http/parser.js +15 -7
- package/dist/lib/http/parser.js.map +1 -1
- package/dist/lib/http/request.d.ts +0 -23
- package/dist/lib/http/request.d.ts.map +1 -1
- package/dist/lib/http/request.js +1 -11
- package/dist/lib/http/request.js.map +1 -1
- package/dist/lib/http/stream.d.ts +28 -6
- package/dist/lib/http/stream.d.ts.map +1 -1
- package/dist/lib/http/stream.js +21 -32
- package/dist/lib/http/stream.js.map +1 -1
- package/dist/lib/util/authorization-header.d.ts.map +1 -1
- package/dist/lib/util/authorization-header.js +1 -1
- package/dist/lib/util/authorization-header.js.map +1 -1
- package/dist/lib/util/hostname.d.ts +3 -2
- package/dist/lib/util/hostname.d.ts.map +1 -1
- package/dist/lib/util/hostname.js +12 -8
- package/dist/lib/util/hostname.js.map +1 -1
- package/dist/metadata/build-metadata.d.ts.map +1 -1
- package/dist/metadata/build-metadata.js +2 -1
- package/dist/metadata/build-metadata.js.map +1 -1
- package/dist/oauth-errors.d.ts +1 -0
- package/dist/oauth-errors.d.ts.map +1 -1
- package/dist/oauth-errors.js +3 -1
- package/dist/oauth-errors.js.map +1 -1
- package/dist/oauth-hooks.d.ts +3 -3
- package/dist/oauth-hooks.d.ts.map +1 -1
- package/dist/oauth-provider.d.ts +20 -22
- package/dist/oauth-provider.d.ts.map +1 -1
- package/dist/oauth-provider.js +234 -176
- package/dist/oauth-provider.js.map +1 -1
- package/dist/oauth-verifier.d.ts +2 -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 +2 -2
- package/dist/output/build-authorize-data.d.ts.map +1 -1
- package/dist/output/send-authorize-redirect.d.ts +2 -4
- package/dist/output/send-authorize-redirect.d.ts.map +1 -1
- package/dist/output/send-authorize-redirect.js +5 -2
- package/dist/output/send-authorize-redirect.js.map +1 -1
- package/dist/request/request-data.d.ts +2 -2
- package/dist/request/request-data.d.ts.map +1 -1
- package/dist/request/request-info.d.ts +2 -2
- package/dist/request/request-info.d.ts.map +1 -1
- package/dist/request/request-manager.d.ts +4 -4
- package/dist/request/request-manager.d.ts.map +1 -1
- package/dist/request/request-manager.js +94 -60
- package/dist/request/request-manager.js.map +1 -1
- package/dist/signer/signed-token-payload.d.ts +122 -122
- package/dist/signer/signer.d.ts +41 -40
- package/dist/signer/signer.d.ts.map +1 -1
- package/dist/signer/signer.js +13 -15
- package/dist/signer/signer.js.map +1 -1
- package/dist/token/token-claims.d.ts +121 -121
- package/dist/token/token-data.d.ts +3 -3
- package/dist/token/token-data.d.ts.map +1 -1
- package/dist/token/token-manager.d.ts +4 -5
- package/dist/token/token-manager.d.ts.map +1 -1
- package/dist/token/token-manager.js +96 -72
- package/dist/token/token-manager.js.map +1 -1
- package/dist/token/verify-token-claims.d.ts +3 -3
- package/dist/token/verify-token-claims.d.ts.map +1 -1
- package/dist/token/verify-token-claims.js.map +1 -1
- package/package.json +7 -6
- package/src/assets/app/components/sign-in-form.tsx +31 -2
- package/src/assets/assets-middleware.ts +4 -2
- package/src/client/client-manager.ts +163 -161
- package/src/client/client-utils.ts +7 -12
- package/src/client/client.ts +112 -3
- package/src/constants.ts +0 -2
- package/src/errors/access-denied-error.ts +10 -4
- package/src/errors/account-selection-required-error.ts +2 -2
- package/src/errors/consent-required-error.ts +2 -2
- package/src/errors/invalid-authorization-details-error.ts +2 -2
- package/src/errors/invalid-client-id-error.ts +15 -4
- package/src/errors/invalid-client-metadata-error.ts +15 -3
- package/src/errors/invalid-parameters-error.ts +2 -2
- package/src/errors/invalid-scope-error.ts +15 -0
- package/src/errors/login-required-error.ts +2 -2
- package/src/lib/html/html.ts +14 -12
- package/src/lib/http/parser.ts +21 -8
- package/src/lib/http/request.ts +1 -23
- package/src/lib/http/stream.ts +29 -60
- package/src/lib/util/authorization-header.ts +5 -2
- package/src/lib/util/hostname.ts +9 -5
- package/src/metadata/build-metadata.ts +3 -1
- package/src/oauth-errors.ts +1 -0
- package/src/oauth-hooks.ts +3 -3
- package/src/oauth-provider.ts +368 -269
- package/src/oauth-verifier.ts +2 -2
- package/src/output/build-authorize-data.ts +2 -2
- package/src/output/send-authorize-redirect.ts +7 -6
- package/src/request/request-data.ts +2 -2
- package/src/request/request-info.ts +2 -2
- package/src/request/request-manager.ts +129 -103
- package/src/signer/signer.ts +24 -25
- package/src/token/token-data.ts +3 -3
- package/src/token/token-manager.ts +141 -99
- package/src/token/verify-token-claims.ts +3 -3
- package/dist/request/types.d.ts +0 -328
- package/dist/request/types.d.ts.map +0 -1
- package/dist/request/types.js +0 -27
- package/dist/request/types.js.map +0 -1
- package/dist/token/types.d.ts +0 -250
- package/dist/token/types.d.ts.map +0 -1
- package/dist/token/types.js +0 -36
- package/dist/token/types.js.map +0 -1
- package/src/request/types.ts +0 -48
- package/src/token/types.ts +0 -86
package/src/constants.ts
CHANGED
@@ -18,8 +18,6 @@ export const REQUEST_ID_BYTES_LENGTH = 16 // 128 bits
|
|
18
18
|
export const CODE_PREFIX = 'cod-'
|
19
19
|
export const CODE_BYTES_LENGTH = 32
|
20
20
|
|
21
|
-
export const ALLOW_LOOPBACK_CLIENT_REFRESH_TOKEN = true
|
22
|
-
|
23
21
|
const SECOND = 1e3
|
24
22
|
const MINUTE = 60 * SECOND
|
25
23
|
const HOUR = 60 * MINUTE
|
@@ -1,10 +1,10 @@
|
|
1
|
-
import {
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
2
2
|
import { buildErrorPayload } from '../output/build-error-payload.js'
|
3
3
|
import { OAuthError } from './oauth-error.js'
|
4
4
|
|
5
5
|
export class AccessDeniedError extends OAuthError {
|
6
6
|
constructor(
|
7
|
-
public readonly parameters:
|
7
|
+
public readonly parameters: OAuthAuthorizationRequestParameters,
|
8
8
|
error_description: string,
|
9
9
|
error = 'access_denied',
|
10
10
|
cause?: unknown,
|
@@ -13,14 +13,20 @@ export class AccessDeniedError extends OAuthError {
|
|
13
13
|
}
|
14
14
|
|
15
15
|
static from(
|
16
|
-
parameters:
|
16
|
+
parameters: OAuthAuthorizationRequestParameters,
|
17
17
|
cause?: unknown,
|
18
|
+
fallbackError?: string,
|
18
19
|
) {
|
19
20
|
if (cause && cause instanceof AccessDeniedError) {
|
20
21
|
return cause
|
21
22
|
}
|
22
23
|
|
23
24
|
const { error, error_description } = buildErrorPayload(cause)
|
24
|
-
return new AccessDeniedError(
|
25
|
+
return new AccessDeniedError(
|
26
|
+
parameters,
|
27
|
+
error_description,
|
28
|
+
fallbackError ?? error,
|
29
|
+
cause,
|
30
|
+
)
|
25
31
|
}
|
26
32
|
}
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import {
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
2
2
|
import { AccessDeniedError } from './access-denied-error.js'
|
3
3
|
|
4
4
|
export class AccountSelectionRequiredError extends AccessDeniedError {
|
5
5
|
constructor(
|
6
|
-
parameters:
|
6
|
+
parameters: OAuthAuthorizationRequestParameters,
|
7
7
|
error_description = 'Account selection required',
|
8
8
|
cause?: unknown,
|
9
9
|
) {
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import {
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
2
2
|
import { AccessDeniedError } from './access-denied-error.js'
|
3
3
|
|
4
4
|
export class ConsentRequiredError extends AccessDeniedError {
|
5
5
|
constructor(
|
6
|
-
parameters:
|
6
|
+
parameters: OAuthAuthorizationRequestParameters,
|
7
7
|
error_description = 'User consent required',
|
8
8
|
cause?: unknown,
|
9
9
|
) {
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
2
2
|
import { AccessDeniedError } from './access-denied-error.js'
|
3
3
|
|
4
4
|
/**
|
@@ -18,7 +18,7 @@ import { AccessDeniedError } from './access-denied-error.js'
|
|
18
18
|
*/
|
19
19
|
export class InvalidAuthorizationDetailsError extends AccessDeniedError {
|
20
20
|
constructor(
|
21
|
-
parameters:
|
21
|
+
parameters: OAuthAuthorizationRequestParameters,
|
22
22
|
error_description: string,
|
23
23
|
cause?: unknown,
|
24
24
|
) {
|
@@ -12,9 +12,20 @@ export class InvalidClientIdError extends OAuthError {
|
|
12
12
|
super('invalid_client_id', error_description, 400, cause)
|
13
13
|
}
|
14
14
|
|
15
|
-
static from(
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
static from(
|
16
|
+
cause: unknown,
|
17
|
+
fallbackMessage = 'Invalid client identifier',
|
18
|
+
): InvalidClientIdError {
|
19
|
+
if (cause instanceof InvalidClientIdError) {
|
20
|
+
return cause
|
21
|
+
}
|
22
|
+
if (cause instanceof TypeError) {
|
23
|
+
// This method is meant to be used in the context of parsing & validating
|
24
|
+
// a client client metadata. In that context, a TypeError would more
|
25
|
+
// likely represent a problem with the data (e.g. invalid URL constructor
|
26
|
+
// arg) and not a programming error.
|
27
|
+
return new InvalidClientIdError(cause.message, cause)
|
28
|
+
}
|
29
|
+
return new InvalidClientIdError(fallbackMessage, cause)
|
19
30
|
}
|
20
31
|
}
|
@@ -12,8 +12,20 @@ export class InvalidClientMetadataError extends OAuthError {
|
|
12
12
|
super('invalid_client_metadata', error_description, 400, cause)
|
13
13
|
}
|
14
14
|
|
15
|
-
static from(
|
16
|
-
|
17
|
-
|
15
|
+
static from(
|
16
|
+
cause: unknown,
|
17
|
+
fallbackMessage = 'Invalid client metadata document',
|
18
|
+
): InvalidClientMetadataError {
|
19
|
+
if (cause instanceof InvalidClientMetadataError) {
|
20
|
+
return cause
|
21
|
+
}
|
22
|
+
if (cause instanceof TypeError) {
|
23
|
+
// This method is meant to be used in the context of parsing & validating
|
24
|
+
// a client client metadata. In that context, a TypeError would more
|
25
|
+
// likely represent a problem with the data (e.g. invalid URL constructor
|
26
|
+
// arg) and not a programming error.
|
27
|
+
return new InvalidClientMetadataError(cause.message, cause)
|
28
|
+
}
|
29
|
+
return new InvalidClientMetadataError(fallbackMessage, cause)
|
18
30
|
}
|
19
31
|
}
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import {
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
2
2
|
import { AccessDeniedError } from './access-denied-error.js'
|
3
3
|
|
4
4
|
export class InvalidParametersError extends AccessDeniedError {
|
5
5
|
constructor(
|
6
|
-
parameters:
|
6
|
+
parameters: OAuthAuthorizationRequestParameters,
|
7
7
|
error_description: string,
|
8
8
|
cause?: unknown,
|
9
9
|
) {
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
2
|
+
import { AccessDeniedError } from './access-denied-error.js'
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-4.1.2.1}
|
6
|
+
*/
|
7
|
+
export class InvalidScopeError extends AccessDeniedError {
|
8
|
+
constructor(
|
9
|
+
parameters: OAuthAuthorizationRequestParameters,
|
10
|
+
error_description: string,
|
11
|
+
cause?: unknown,
|
12
|
+
) {
|
13
|
+
super(parameters, error_description, 'invalid_scope', cause)
|
14
|
+
}
|
15
|
+
}
|
@@ -1,9 +1,9 @@
|
|
1
|
-
import {
|
1
|
+
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
2
2
|
import { AccessDeniedError } from './access-denied-error.js'
|
3
3
|
|
4
4
|
export class LoginRequiredError extends AccessDeniedError {
|
5
5
|
constructor(
|
6
|
-
parameters:
|
6
|
+
parameters: OAuthAuthorizationRequestParameters,
|
7
7
|
error_description = 'Login is required',
|
8
8
|
cause?: unknown,
|
9
9
|
) {
|
package/src/lib/html/html.ts
CHANGED
@@ -6,7 +6,7 @@ const symbol = Symbol('Html.dangerouslyCreate')
|
|
6
6
|
* This class represents trusted HTML that can be safely embedded in a web page,
|
7
7
|
* or used as fragments to build a larger HTML document.
|
8
8
|
*/
|
9
|
-
export class Html {
|
9
|
+
export class Html implements Iterable<string> {
|
10
10
|
#fragments: Iterable<Html | string>
|
11
11
|
|
12
12
|
private constructor(fragments: Iterable<Html | string>, guard: symbol) {
|
@@ -22,22 +22,19 @@ export class Html {
|
|
22
22
|
}
|
23
23
|
|
24
24
|
toString(): string {
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
let result = ''
|
26
|
+
for (const fragment of this) result += fragment
|
27
|
+
|
28
|
+
// Cache result for future calls
|
28
29
|
if (
|
29
30
|
!Array.isArray(this.#fragments) ||
|
30
31
|
this.#fragments.length > 1 ||
|
31
32
|
!this.#fragments.every(isString)
|
32
33
|
) {
|
33
|
-
|
34
|
-
// results.
|
35
|
-
const fragment = Array.from(this.#fragments, String).join('')
|
36
|
-
this.#fragments = [fragment] // Cache result for future calls
|
37
|
-
return fragment
|
34
|
+
this.#fragments = result ? [result] : []
|
38
35
|
}
|
39
36
|
|
40
|
-
return
|
37
|
+
return result
|
41
38
|
}
|
42
39
|
|
43
40
|
[Symbol.toPrimitive](hint): string {
|
@@ -51,8 +48,13 @@ export class Html {
|
|
51
48
|
}
|
52
49
|
|
53
50
|
*[Symbol.iterator](): IterableIterator<string> {
|
54
|
-
|
55
|
-
|
51
|
+
for (const fragment of this.#fragments) {
|
52
|
+
if (typeof fragment === 'string') {
|
53
|
+
yield fragment
|
54
|
+
} else {
|
55
|
+
yield* fragment
|
56
|
+
}
|
57
|
+
}
|
56
58
|
}
|
57
59
|
|
58
60
|
static dangerouslyCreate(fragments: Iterable<Html | string>): Html {
|
package/src/lib/http/parser.ts
CHANGED
@@ -5,16 +5,27 @@ import createHttpError from 'http-errors'
|
|
5
5
|
export type JsonScalar = string | number | boolean | null
|
6
6
|
export type Json = JsonScalar | Json[] | { [_ in string]?: Json }
|
7
7
|
|
8
|
-
|
8
|
+
/**
|
9
|
+
* Parse a content-type string into its components.
|
10
|
+
*
|
11
|
+
* @throws {TypeError} If the content-type is invalid.
|
12
|
+
*/
|
13
|
+
export function parseContentType(type: unknown): ContentType {
|
14
|
+
if (typeof type !== 'string') {
|
15
|
+
throw createHttpError(
|
16
|
+
415,
|
17
|
+
`Invalid content-type: ${type == null ? String(type) : typeof type}`,
|
18
|
+
)
|
19
|
+
}
|
20
|
+
|
9
21
|
try {
|
10
22
|
return hapiContentType(type)
|
11
23
|
} catch (err) {
|
12
24
|
// De-boomify the error
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
throw err
|
25
|
+
throw createHttpError(
|
26
|
+
415,
|
27
|
+
err instanceof Error ? err.message : 'Invalid content-type',
|
28
|
+
)
|
18
29
|
}
|
19
30
|
}
|
20
31
|
|
@@ -61,13 +72,15 @@ export const parsers = [
|
|
61
72
|
test: (mime): mime is 'application/x-www-form-urlencoded' => {
|
62
73
|
return mime === 'application/x-www-form-urlencoded'
|
63
74
|
},
|
64
|
-
parse: (buffer, { charset }):
|
75
|
+
parse: (buffer, { charset }): { [_ in string]?: string } => {
|
65
76
|
if (charset != null && !/^utf-?8$/i.test(charset)) {
|
66
77
|
throw createHttpError(415, 'Unsupported charset')
|
67
78
|
}
|
68
79
|
try {
|
69
80
|
if (!buffer.length) return {}
|
70
|
-
|
81
|
+
const params = new URLSearchParams(buffer.toString())
|
82
|
+
if (params.has('__proto__')) throw new TypeError('Invalid key')
|
83
|
+
return Object.fromEntries(params)
|
71
84
|
} catch (err) {
|
72
85
|
throw createHttpError(400, 'Invalid URL-encoded data', { cause: err })
|
73
86
|
}
|
package/src/lib/http/request.ts
CHANGED
@@ -1,32 +1,10 @@
|
|
1
1
|
import { parse as parseCookie, serialize as serializeCookie } from 'cookie'
|
2
2
|
import { randomBytes } from 'crypto'
|
3
3
|
import createHttpError from 'http-errors'
|
4
|
-
import { z } from 'zod'
|
5
4
|
|
6
|
-
import { KnownNames } from './parser.js'
|
7
5
|
import { appendHeader } from './response.js'
|
8
|
-
import { decodeStream, parseStream } from './stream.js'
|
9
6
|
import { IncomingMessage, ServerResponse } from './types.js'
|
10
|
-
import {
|
11
|
-
|
12
|
-
export function parseRequestPayload<
|
13
|
-
A extends readonly KnownNames[] = readonly KnownNames[],
|
14
|
-
>(req: IncomingMessage, allow?: A) {
|
15
|
-
return parseStream(
|
16
|
-
decodeStream(req, req.headers['content-encoding']),
|
17
|
-
req.headers['content-type'],
|
18
|
-
allow,
|
19
|
-
)
|
20
|
-
}
|
21
|
-
|
22
|
-
export async function validateRequestPayload<S extends z.ZodTypeAny>(
|
23
|
-
req: IncomingMessage,
|
24
|
-
schema: S,
|
25
|
-
allow: readonly KnownNames[] = ['json', 'urlencoded'],
|
26
|
-
): Promise<z.infer<S>> {
|
27
|
-
const payload = await parseRequestPayload(req, allow)
|
28
|
-
return schema.parseAsync(payload, { path: ['body'] })
|
29
|
-
}
|
7
|
+
import { urlMatch, UrlReference } from './url.js'
|
30
8
|
|
31
9
|
export function validateHeaderValue(
|
32
10
|
req: IncomingMessage,
|
package/src/lib/http/stream.ts
CHANGED
@@ -1,81 +1,50 @@
|
|
1
|
-
import {
|
2
|
-
import { createGunzip, createInflate } from 'node:zlib'
|
3
|
-
|
1
|
+
import { decodeStream, streamToNodeBuffer } from '@atproto/common'
|
4
2
|
import createHttpError from 'http-errors'
|
3
|
+
import { IncomingMessage } from 'node:http'
|
4
|
+
import { Readable } from 'node:stream'
|
5
5
|
|
6
6
|
import {
|
7
7
|
KnownNames,
|
8
8
|
KnownParser,
|
9
|
-
KnownTypes,
|
10
9
|
parseContentType,
|
11
|
-
ParserForType,
|
12
10
|
ParserResult,
|
13
11
|
parsers,
|
14
12
|
} from './parser.js'
|
15
13
|
|
16
|
-
export
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
totalLength += chunk.length
|
22
|
-
}
|
23
|
-
return Buffer.concat(chunks, totalLength)
|
24
|
-
}
|
25
|
-
|
26
|
-
export function decodeStream(
|
27
|
-
req: Readable,
|
28
|
-
encoding: string = 'identity',
|
29
|
-
): Readable {
|
30
|
-
switch (encoding) {
|
31
|
-
case 'deflate':
|
32
|
-
return req.compose(createInflate())
|
33
|
-
case 'gzip':
|
34
|
-
return req.compose(createGunzip())
|
35
|
-
case 'identity':
|
36
|
-
return req.compose(new PassThrough())
|
37
|
-
default:
|
38
|
-
throw createHttpError(415, 'Unsupported content-encoding')
|
14
|
+
export function decodeHttpRequest(req: IncomingMessage): Readable {
|
15
|
+
try {
|
16
|
+
return decodeStream(req, req.headers['content-encoding'])
|
17
|
+
} catch (err) {
|
18
|
+
throw createHttpError(415, err, { expose: err instanceof TypeError })
|
39
19
|
}
|
40
20
|
}
|
41
21
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
contentType: unknown,
|
57
|
-
allow?: A,
|
58
|
-
): Promise<ParserResult<Extract<KnownParser, { name: A[number] }>>>
|
59
|
-
export async function parseStream(
|
60
|
-
req: Readable,
|
61
|
-
contentType: unknown = 'application/octet-stream',
|
62
|
-
allow?: string[],
|
63
|
-
): Promise<ParserResult<KnownParser>> {
|
64
|
-
if (typeof contentType !== 'string') {
|
65
|
-
throw createHttpError(400, 'Invalid content-type')
|
66
|
-
}
|
67
|
-
|
68
|
-
const type = parseContentType(contentType)
|
22
|
+
/**
|
23
|
+
* Generic method that parses a stream of unknown nature (HTTP request/response,
|
24
|
+
* socket, file, etc.), but of known mime type, into a parsed object.
|
25
|
+
*
|
26
|
+
* @throws {TypeError} If the content-type is not valid or supported.
|
27
|
+
*/
|
28
|
+
|
29
|
+
export async function parseHttpRequest<A extends readonly KnownNames[]>(
|
30
|
+
req: IncomingMessage,
|
31
|
+
allow: A,
|
32
|
+
) {
|
33
|
+
const type = parseContentType(
|
34
|
+
req.headers['content-type'] ?? 'application/octet-stream',
|
35
|
+
)
|
69
36
|
|
70
37
|
const parser = parsers.find(
|
71
|
-
(parser) =>
|
72
|
-
allow?.includes(parser.name) !== false && parser.test(type.mime),
|
38
|
+
(parser) => allow.includes(parser.name) && parser.test(type.mime),
|
73
39
|
)
|
74
40
|
|
75
41
|
if (!parser) {
|
76
|
-
throw createHttpError(
|
42
|
+
throw createHttpError(415, `Unsupported content-type: ${type.mime}`)
|
77
43
|
}
|
78
44
|
|
79
|
-
const
|
80
|
-
|
45
|
+
const stream = decodeHttpRequest(req)
|
46
|
+
const buffer = await streamToNodeBuffer(stream)
|
47
|
+
return parser.parse(buffer, type) as ParserResult<
|
48
|
+
Extract<KnownParser, { name: A[number] }>
|
49
|
+
>
|
81
50
|
}
|
@@ -1,4 +1,7 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
oauthAccessTokenSchema,
|
3
|
+
oauthTokenTypeSchema,
|
4
|
+
} from '@atproto/oauth-types'
|
2
5
|
import { z } from 'zod'
|
3
6
|
|
4
7
|
import { InvalidRequestError } from '../../errors/invalid-request-error.js'
|
@@ -6,7 +9,7 @@ import { WWWAuthenticateError } from '../../errors/www-authenticate-error.js'
|
|
6
9
|
|
7
10
|
export const authorizationHeaderSchema = z.tuple([
|
8
11
|
oauthTokenTypeSchema,
|
9
|
-
|
12
|
+
oauthAccessTokenSchema,
|
10
13
|
])
|
11
14
|
|
12
15
|
export const parseAuthorizationHeader = (header?: string) => {
|
package/src/lib/util/hostname.ts
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
import { parse, ParsedDomain } from 'psl'
|
2
2
|
|
3
|
+
export function isInternetUrl(url: URL): boolean {
|
4
|
+
return parseUrlPublicSuffix(url) !== null
|
5
|
+
}
|
6
|
+
|
3
7
|
export function isInternetHost(host: string): boolean {
|
4
|
-
return
|
8
|
+
return parseDomainPublicSuffix(host) !== null
|
5
9
|
}
|
6
10
|
|
7
|
-
export function
|
8
|
-
const
|
9
|
-
return
|
11
|
+
export function parseUrlPublicSuffix(input: string | URL): ParsedDomain | null {
|
12
|
+
const { hostname } = new URL(input)
|
13
|
+
return parseDomainPublicSuffix(hostname)
|
10
14
|
}
|
11
15
|
|
12
|
-
export function
|
16
|
+
export function parseDomainPublicSuffix(domain: string): ParsedDomain | null {
|
13
17
|
const parsed = parse(domain)
|
14
18
|
if ('listed' in parsed && parsed.listed && parsed.domain) {
|
15
19
|
return parsed
|
@@ -60,7 +60,9 @@ export function buildMetadata(
|
|
60
60
|
code_challenge_methods_supported: [
|
61
61
|
// https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#pkce-code-challenge-method
|
62
62
|
'S256',
|
63
|
-
|
63
|
+
|
64
|
+
// atproto does not allow "plain"
|
65
|
+
// 'plain',
|
64
66
|
],
|
65
67
|
ui_locales_supported: [
|
66
68
|
//
|
package/src/oauth-errors.ts
CHANGED
@@ -14,6 +14,7 @@ export { InvalidGrantError } from './errors/invalid-grant-error.js'
|
|
14
14
|
export { InvalidParametersError } from './errors/invalid-parameters-error.js'
|
15
15
|
export { InvalidRedirectUriError } from './errors/invalid-redirect-uri-error.js'
|
16
16
|
export { InvalidRequestError } from './errors/invalid-request-error.js'
|
17
|
+
export { InvalidScopeError } from './errors/invalid-scope-error.js'
|
17
18
|
export { InvalidTokenError } from './errors/invalid-token-error.js'
|
18
19
|
export { LoginRequiredError } from './errors/login-required-error.js'
|
19
20
|
export { SecondAuthenticationFactorRequiredError } from './errors/second-authentication-factor-required-error.js'
|
package/src/oauth-hooks.ts
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { Jwks } from '@atproto/jwk'
|
2
2
|
import {
|
3
|
-
OAuthAuthenticationRequestParameters,
|
4
3
|
OAuthAuthorizationDetails,
|
4
|
+
OAuthAuthorizationRequestParameters,
|
5
5
|
OAuthClientMetadata,
|
6
6
|
OAuthTokenResponse,
|
7
7
|
} from '@atproto/oauth-types'
|
@@ -23,8 +23,8 @@ export type {
|
|
23
23
|
ClientInfo,
|
24
24
|
InvalidAuthorizationDetailsError,
|
25
25
|
Jwks,
|
26
|
-
OAuthAuthenticationRequestParameters,
|
27
26
|
OAuthAuthorizationDetails,
|
27
|
+
OAuthAuthorizationRequestParameters,
|
28
28
|
OAuthClientMetadata,
|
29
29
|
OAuthTokenResponse,
|
30
30
|
}
|
@@ -50,7 +50,7 @@ export type OAuthHooks = {
|
|
50
50
|
*/
|
51
51
|
onAuthorizationDetails?: (data: {
|
52
52
|
client: Client
|
53
|
-
parameters:
|
53
|
+
parameters: OAuthAuthorizationRequestParameters
|
54
54
|
account: Account
|
55
55
|
}) => Awaitable<undefined | OAuthAuthorizationDetails>
|
56
56
|
}
|