@atproto/oauth-provider 0.2.0 → 0.2.2
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 +42 -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/app/components/url-viewer.tsx +3 -3
- 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
|
}
|