@atproto/oauth-provider 0.19.7 → 0.19.8
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 +26 -0
- package/dist/errors/error-parser.js +5 -5
- package/dist/errors/error-parser.js.map +1 -1
- package/package.json +22 -18
- package/src/access-token/access-token-mode.ts +0 -4
- package/src/account/account-manager.ts +0 -591
- package/src/account/account-store.ts +0 -305
- package/src/account/sign-in-data.ts +0 -15
- package/src/account/sign-up-input.ts +0 -20
- package/src/client/client-auth.ts +0 -64
- package/src/client/client-data.ts +0 -9
- package/src/client/client-id.ts +0 -4
- package/src/client/client-info.ts +0 -13
- package/src/client/client-manager.ts +0 -772
- package/src/client/client-store.ts +0 -35
- package/src/client/client-utils.ts +0 -37
- package/src/client/client.ts +0 -394
- package/src/constants.ts +0 -80
- package/src/customization/branding.ts +0 -12
- package/src/customization/build-customization-css.ts +0 -55
- package/src/customization/build-customization-data.ts +0 -24
- package/src/customization/colors.ts +0 -48
- package/src/customization/customization.ts +0 -29
- package/src/customization/links.ts +0 -10
- package/src/device/device-data.ts +0 -11
- package/src/device/device-id.ts +0 -27
- package/src/device/device-manager.ts +0 -292
- package/src/device/device-store.ts +0 -31
- package/src/device/session-id.ts +0 -21
- package/src/dpop/dpop-manager.ts +0 -214
- package/src/dpop/dpop-nonce.ts +0 -121
- package/src/dpop/dpop-proof.ts +0 -6
- package/src/errors/access-denied-error.ts +0 -12
- package/src/errors/account-selection-required-error.ts +0 -12
- package/src/errors/authorization-error.ts +0 -45
- package/src/errors/consent-required-error.ts +0 -12
- package/src/errors/error-parser.ts +0 -147
- package/src/errors/handle-unavailable-error.ts +0 -23
- package/src/errors/invalid-authorization-details-error.ts +0 -27
- package/src/errors/invalid-client-error.ts +0 -20
- package/src/errors/invalid-client-id-error.ts +0 -31
- package/src/errors/invalid-client-metadata-error.ts +0 -68
- package/src/errors/invalid-credentials-error.ts +0 -29
- package/src/errors/invalid-dpop-key-binding-error.ts +0 -21
- package/src/errors/invalid-dpop-proof-error.ts +0 -13
- package/src/errors/invalid-grant-error.ts +0 -21
- package/src/errors/invalid-invite-code-error.ts +0 -10
- package/src/errors/invalid-redirect-uri-error.ts +0 -17
- package/src/errors/invalid-request-error.ts +0 -32
- package/src/errors/invalid-scope-error.ts +0 -15
- package/src/errors/invalid-token-error.ts +0 -60
- package/src/errors/login-required-error.ts +0 -12
- package/src/errors/oauth-error.ts +0 -28
- package/src/errors/second-authentication-factor-required-error.ts +0 -25
- package/src/errors/unauthorized-client-error.ts +0 -20
- package/src/errors/use-dpop-nonce-error.ts +0 -32
- package/src/errors/www-authenticate-error.ts +0 -64
- package/src/index.ts +0 -16
- package/src/lexicon/lexicon-data.ts +0 -11
- package/src/lexicon/lexicon-getter.ts +0 -55
- package/src/lexicon/lexicon-manager.ts +0 -116
- package/src/lexicon/lexicon-store.ts +0 -35
- package/src/lib/csp/index.ts +0 -101
- package/src/lib/hcaptcha.ts +0 -228
- package/src/lib/html/README.md +0 -9
- package/src/lib/html/build-document.ts +0 -151
- package/src/lib/html/escapers.ts +0 -66
- package/src/lib/html/html.ts +0 -56
- package/src/lib/html/hydration-data.ts +0 -19
- package/src/lib/html/index.ts +0 -5
- package/src/lib/html/tags.ts +0 -67
- package/src/lib/html/util.ts +0 -17
- package/src/lib/http/README.md +0 -11
- package/src/lib/http/accept.ts +0 -90
- package/src/lib/http/context.ts +0 -42
- package/src/lib/http/headers.ts +0 -15
- package/src/lib/http/index.ts +0 -10
- package/src/lib/http/method.ts +0 -18
- package/src/lib/http/middleware.ts +0 -169
- package/src/lib/http/parser.ts +0 -101
- package/src/lib/http/path.ts +0 -82
- package/src/lib/http/request.ts +0 -256
- package/src/lib/http/response.ts +0 -122
- package/src/lib/http/route.ts +0 -60
- package/src/lib/http/router.ts +0 -117
- package/src/lib/http/security-headers.ts +0 -100
- package/src/lib/http/stream.ts +0 -57
- package/src/lib/http/types.ts +0 -16
- package/src/lib/http/url.ts +0 -23
- package/src/lib/nsid.ts +0 -10
- package/src/lib/redis.ts +0 -25
- package/src/lib/util/authorization-header.ts +0 -28
- package/src/lib/util/cast.ts +0 -18
- package/src/lib/util/color.ts +0 -168
- package/src/lib/util/crypto.ts +0 -32
- package/src/lib/util/date.ts +0 -7
- package/src/lib/util/error.ts +0 -7
- package/src/lib/util/function.ts +0 -39
- package/src/lib/util/locale.ts +0 -12
- package/src/lib/util/object.ts +0 -23
- package/src/lib/util/redirect-uri.ts +0 -46
- package/src/lib/util/time.ts +0 -49
- package/src/lib/util/type.ts +0 -182
- package/src/lib/util/ui8.ts +0 -14
- package/src/lib/util/well-known.ts +0 -8
- package/src/lib/util/zod-error.ts +0 -26
- package/src/lib/write-form-redirect.ts +0 -58
- package/src/lib/write-html.ts +0 -70
- package/src/metadata/build-metadata.ts +0 -141
- package/src/oauth-client.ts +0 -3
- package/src/oauth-dpop.ts +0 -2
- package/src/oauth-errors.ts +0 -26
- package/src/oauth-hooks.ts +0 -519
- package/src/oauth-middleware.ts +0 -53
- package/src/oauth-provider.ts +0 -1110
- package/src/oauth-store.ts +0 -12
- package/src/oauth-verifier.ts +0 -260
- package/src/replay/replay-manager.ts +0 -51
- package/src/replay/replay-store-memory.ts +0 -36
- package/src/replay/replay-store-redis.ts +0 -30
- package/src/replay/replay-store.ts +0 -44
- package/src/request/code.ts +0 -23
- package/src/request/request-data.ts +0 -36
- package/src/request/request-id.ts +0 -22
- package/src/request/request-manager.ts +0 -521
- package/src/request/request-store.ts +0 -60
- package/src/request/request-uri.ts +0 -42
- package/src/result/authorization-redirect-parameters.ts +0 -24
- package/src/result/authorization-result-authorize-page.ts +0 -16
- package/src/result/authorization-result-redirect.ts +0 -8
- package/src/router/assets/assets-manifest.ts +0 -117
- package/src/router/assets/assets.ts +0 -124
- package/src/router/assets/csrf.ts +0 -79
- package/src/router/assets/send-account-page.ts +0 -23
- package/src/router/assets/send-authorization-page.ts +0 -42
- package/src/router/assets/send-cookie-error-page.ts +0 -21
- package/src/router/assets/send-error-page.ts +0 -25
- package/src/router/assets/send-redirect.ts +0 -140
- package/src/router/create-account-page-middleware.ts +0 -88
- package/src/router/create-api-middleware.ts +0 -1051
- package/src/router/create-authorization-page-middleware.ts +0 -281
- package/src/router/create-oauth-middleware.ts +0 -257
- package/src/router/error-handler.ts +0 -6
- package/src/router/middleware-options.ts +0 -9
- package/src/signer/access-token-payload.ts +0 -25
- package/src/signer/api-token-payload.ts +0 -18
- package/src/signer/signer.ts +0 -121
- package/src/token/refresh-token.ts +0 -30
- package/src/token/token-claims.ts +0 -22
- package/src/token/token-data.ts +0 -40
- package/src/token/token-id.ts +0 -25
- package/src/token/token-manager.ts +0 -427
- package/src/token/token-store.ts +0 -88
- package/src/types/authorization-response-error.ts +0 -27
- package/src/types/color-hue.ts +0 -3
- package/src/types/email-otp.ts +0 -3
- package/src/types/email.ts +0 -26
- package/src/types/handle.ts +0 -23
- package/src/types/invite-code.ts +0 -4
- package/src/types/par-response-error.ts +0 -25
- package/src/types/password.ts +0 -4
- package/src/types/rgb-color.ts +0 -21
- package/tsconfig.build.json +0 -8
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -4
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { OAuthError } from './oauth-error.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @see
|
|
5
|
-
* {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | RFC6749 - Issuing an Access Token}
|
|
6
|
-
* : The request is missing a required parameter, includes an unsupported
|
|
7
|
-
* parameter value (other than grant type), repeats a parameter, includes
|
|
8
|
-
* multiple credentials, utilizes more than one mechanism for authenticating the
|
|
9
|
-
* client, or is otherwise malformed.
|
|
10
|
-
*
|
|
11
|
-
* @see
|
|
12
|
-
* {@link https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1 | RFC6749 - Authorization Code Grant, Authorization Request}
|
|
13
|
-
* : The request is missing a required parameter, includes an invalid parameter
|
|
14
|
-
* value, includes a parameter more than once, or is otherwise malformed.
|
|
15
|
-
*
|
|
16
|
-
* @see
|
|
17
|
-
* {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3.1 | RFC6750 - The WWW-Authenticate Response Header Field}
|
|
18
|
-
* : The request is missing a required parameter, includes an unsupported
|
|
19
|
-
* parameter or parameter value, repeats the same parameter, uses more than one
|
|
20
|
-
* method for including an access token, or is otherwise malformed. The resource
|
|
21
|
-
* server SHOULD respond with the HTTP 400 (Bad Request) status code.
|
|
22
|
-
*/
|
|
23
|
-
export class InvalidRequestError extends OAuthError {
|
|
24
|
-
constructor(error_description: string, cause?: unknown) {
|
|
25
|
-
super('invalid_request', error_description, 400, cause)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
static from(err: unknown, message = 'Invalid request data'): OAuthError {
|
|
29
|
-
if (err instanceof OAuthError) return err
|
|
30
|
-
return new InvalidRequestError(message, err)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
|
2
|
-
import { AuthorizationError } from './authorization-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 AuthorizationError {
|
|
8
|
-
constructor(
|
|
9
|
-
parameters: OAuthAuthorizationRequestParameters,
|
|
10
|
-
error_description: string,
|
|
11
|
-
cause?: unknown,
|
|
12
|
-
) {
|
|
13
|
-
super(parameters, error_description, 'invalid_scope', cause)
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { errors } from 'jose'
|
|
2
|
-
import { ZodError } from 'zod'
|
|
3
|
-
import { JwtVerifyError } from '@atproto/jwk'
|
|
4
|
-
import { OAuthError } from './oauth-error.js'
|
|
5
|
-
import { WWWAuthenticateError } from './www-authenticate-error.js'
|
|
6
|
-
|
|
7
|
-
const { JOSEError } = errors
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @see
|
|
11
|
-
* {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3.1 | RFC6750 - The WWW-Authenticate Response Header Field }
|
|
12
|
-
*
|
|
13
|
-
* The access token provided is expired, revoked, malformed, or invalid for
|
|
14
|
-
* other reasons. The resource SHOULD respond with the HTTP 401 (Unauthorized)
|
|
15
|
-
* status code. The client MAY request a new access token and retry the
|
|
16
|
-
* protected resource request.
|
|
17
|
-
*/
|
|
18
|
-
export class InvalidTokenError extends WWWAuthenticateError {
|
|
19
|
-
static from(
|
|
20
|
-
err: unknown,
|
|
21
|
-
tokenType: string,
|
|
22
|
-
fallbackMessage = 'Invalid token',
|
|
23
|
-
): InvalidTokenError {
|
|
24
|
-
if (err instanceof InvalidTokenError) {
|
|
25
|
-
return err
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (err instanceof OAuthError) {
|
|
29
|
-
return new InvalidTokenError(tokenType, err.error_description, err)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (err instanceof JOSEError) {
|
|
33
|
-
return new InvalidTokenError(tokenType, err.message, err)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (err instanceof JwtVerifyError) {
|
|
37
|
-
return new InvalidTokenError(tokenType, err.message, err)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (err instanceof ZodError) {
|
|
41
|
-
return new InvalidTokenError(tokenType, err.message, err)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return new InvalidTokenError(tokenType, fallbackMessage, err)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
constructor(
|
|
48
|
-
readonly tokenType: string,
|
|
49
|
-
error_description: string,
|
|
50
|
-
cause?: unknown,
|
|
51
|
-
) {
|
|
52
|
-
const error = 'invalid_token'
|
|
53
|
-
super(
|
|
54
|
-
error,
|
|
55
|
-
error_description,
|
|
56
|
-
{ [tokenType]: { error, error_description } },
|
|
57
|
-
cause,
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
|
|
2
|
-
import { AuthorizationError } from './authorization-error.js'
|
|
3
|
-
|
|
4
|
-
export class LoginRequiredError extends AuthorizationError {
|
|
5
|
-
constructor(
|
|
6
|
-
parameters: OAuthAuthorizationRequestParameters,
|
|
7
|
-
error_description = 'Login is required',
|
|
8
|
-
cause?: unknown,
|
|
9
|
-
) {
|
|
10
|
-
super(parameters, error_description, 'login_required', cause)
|
|
11
|
-
}
|
|
12
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export class OAuthError extends Error {
|
|
2
|
-
public expose: boolean
|
|
3
|
-
|
|
4
|
-
constructor(
|
|
5
|
-
public readonly error: string,
|
|
6
|
-
public readonly error_description: string,
|
|
7
|
-
public readonly status = 400,
|
|
8
|
-
cause?: unknown,
|
|
9
|
-
) {
|
|
10
|
-
super(error_description, { cause })
|
|
11
|
-
|
|
12
|
-
Error.captureStackTrace?.(this, this.constructor)
|
|
13
|
-
|
|
14
|
-
this.name = this.constructor.name
|
|
15
|
-
this.expose = status < 500
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
get statusCode() {
|
|
19
|
-
return this.status
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
toJSON() {
|
|
23
|
-
return {
|
|
24
|
-
error: this.error,
|
|
25
|
-
error_description: this.error_description,
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { OAuthError } from './oauth-error.js'
|
|
2
|
-
|
|
3
|
-
export class SecondAuthenticationFactorRequiredError extends OAuthError {
|
|
4
|
-
constructor(
|
|
5
|
-
public type: 'emailOtp',
|
|
6
|
-
public hint: string,
|
|
7
|
-
cause?: unknown,
|
|
8
|
-
) {
|
|
9
|
-
const error = 'second_authentication_factor_required'
|
|
10
|
-
super(
|
|
11
|
-
error,
|
|
12
|
-
`${type} authentication factor required (hint: ${hint})`,
|
|
13
|
-
401,
|
|
14
|
-
cause,
|
|
15
|
-
)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
toJSON() {
|
|
19
|
-
return {
|
|
20
|
-
...super.toJSON(),
|
|
21
|
-
type: this.type,
|
|
22
|
-
hint: this.hint,
|
|
23
|
-
} as const
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { OAuthError } from './oauth-error.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @see
|
|
5
|
-
* {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | RFC6749 - Issuing an Access Token }
|
|
6
|
-
*
|
|
7
|
-
* The authenticated client is not authorized to use this authorization grant
|
|
8
|
-
* type.
|
|
9
|
-
*
|
|
10
|
-
* @see
|
|
11
|
-
* {@link https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1 | RFC6749 - Authorization Code Grant, Authorization Request}
|
|
12
|
-
*
|
|
13
|
-
* The client is not authorized to request an authorization code using this
|
|
14
|
-
* method.
|
|
15
|
-
*/
|
|
16
|
-
export class UnauthorizedClientError extends OAuthError {
|
|
17
|
-
constructor(error_description: string, cause?: unknown) {
|
|
18
|
-
super('unauthorized_client', error_description, 400, cause)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { OAuthError } from './oauth-error.js'
|
|
2
|
-
import { WWWAuthenticateError } from './www-authenticate-error.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @see
|
|
6
|
-
* {@link https://datatracker.ietf.org/doc/html/rfc9449#section-8 | RFC9449 - Section 8. Authorization Server-Provided Nonce}
|
|
7
|
-
*/
|
|
8
|
-
export class UseDpopNonceError extends OAuthError {
|
|
9
|
-
constructor(
|
|
10
|
-
error_description = 'Authorization server requires nonce in DPoP proof',
|
|
11
|
-
cause?: unknown,
|
|
12
|
-
) {
|
|
13
|
-
super('use_dpop_nonce', error_description, 400, cause)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Convert this error into an error meant to be used as "Resource
|
|
18
|
-
* Server-Provided Nonce" error.
|
|
19
|
-
*
|
|
20
|
-
* @see
|
|
21
|
-
* {@link https://datatracker.ietf.org/doc/html/rfc9449#section-9 | RFC9449 - Section 9. Resource Server-Provided Nonce}
|
|
22
|
-
*/
|
|
23
|
-
toWwwAuthenticateError(): WWWAuthenticateError {
|
|
24
|
-
const { error, error_description } = this
|
|
25
|
-
return new WWWAuthenticateError(
|
|
26
|
-
error,
|
|
27
|
-
error_description,
|
|
28
|
-
{ DPoP: { error, error_description } },
|
|
29
|
-
this,
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { VERIFY_ALGOS } from '../lib/util/crypto.js'
|
|
2
|
-
import { OAuthError } from './oauth-error.js'
|
|
3
|
-
|
|
4
|
-
export type WWWAuthenticateParams = Record<string, string | undefined>
|
|
5
|
-
export type WWWAuthenticate = Record<string, undefined | WWWAuthenticateParams>
|
|
6
|
-
|
|
7
|
-
export class WWWAuthenticateError extends OAuthError {
|
|
8
|
-
public readonly wwwAuthenticate: WWWAuthenticate
|
|
9
|
-
|
|
10
|
-
constructor(
|
|
11
|
-
error: string,
|
|
12
|
-
error_description: string,
|
|
13
|
-
wwwAuthenticate: WWWAuthenticate,
|
|
14
|
-
cause?: unknown,
|
|
15
|
-
) {
|
|
16
|
-
super(error, error_description, 401, cause)
|
|
17
|
-
|
|
18
|
-
this.wwwAuthenticate =
|
|
19
|
-
wwwAuthenticate['DPoP'] != null
|
|
20
|
-
? {
|
|
21
|
-
...wwwAuthenticate,
|
|
22
|
-
DPoP: { algs: VERIFY_ALGOS.join(' '), ...wwwAuthenticate['DPoP'] },
|
|
23
|
-
}
|
|
24
|
-
: wwwAuthenticate
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
get wwwAuthenticateHeader() {
|
|
28
|
-
return formatWWWAuthenticateHeader(this.wwwAuthenticate)
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function formatWWWAuthenticateHeader(wwwAuthenticate: WWWAuthenticate): string {
|
|
33
|
-
return Object.entries(wwwAuthenticate)
|
|
34
|
-
.filter(isWWWAuthenticateEntry)
|
|
35
|
-
.map(wwwAuthenticateEntryToString)
|
|
36
|
-
.join(', ')
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type WWWAuthenticateEntry = [type: string, params: WWWAuthenticateParams]
|
|
40
|
-
function isWWWAuthenticateEntry(
|
|
41
|
-
entry: [string, unknown],
|
|
42
|
-
): entry is WWWAuthenticateEntry {
|
|
43
|
-
const [, value] = entry
|
|
44
|
-
return value != null && typeof value === 'object'
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function wwwAuthenticateEntryToString([type, params]: WWWAuthenticateEntry) {
|
|
48
|
-
const paramsEnc = Object.entries(params)
|
|
49
|
-
.filter(isParamEntry)
|
|
50
|
-
.map(paramEntryToString)
|
|
51
|
-
|
|
52
|
-
return paramsEnc.length ? `${type} ${paramsEnc.join(', ')}` : type
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
type ParamEntry = [name: string, value: string]
|
|
56
|
-
|
|
57
|
-
function isParamEntry(entry: [string, unknown]): entry is ParamEntry {
|
|
58
|
-
const [, value] = entry
|
|
59
|
-
return typeof value === 'string' && value !== '' && !value.includes('"')
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function paramEntryToString([name, value]: ParamEntry): string {
|
|
63
|
-
return `${name}="${value}"`
|
|
64
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
// Avoid having to explicitly depend sub dependencies
|
|
2
|
-
export * from '@atproto-labs/fetch'
|
|
3
|
-
export * from '@atproto-labs/fetch-node'
|
|
4
|
-
export * from '@atproto/jwk'
|
|
5
|
-
export * from '@atproto/jwk-jose'
|
|
6
|
-
export * from '@atproto/oauth-types'
|
|
7
|
-
|
|
8
|
-
export * from './constants.js'
|
|
9
|
-
export * from './oauth-client.js'
|
|
10
|
-
export * from './oauth-dpop.js'
|
|
11
|
-
export * from './oauth-errors.js'
|
|
12
|
-
export * from './oauth-hooks.js'
|
|
13
|
-
export * from './oauth-middleware.js'
|
|
14
|
-
export * from './oauth-provider.js'
|
|
15
|
-
export * from './oauth-store.js'
|
|
16
|
-
export * from './oauth-verifier.js'
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { LexResolver, LexResolverError } from '@atproto/lex-resolver'
|
|
2
|
-
import { Nsid } from '@atproto/oauth-scopes'
|
|
3
|
-
import { CachedGetter } from '@atproto-labs/simple-store'
|
|
4
|
-
import { LEXICON_REFRESH_FREQUENCY } from '../constants.js'
|
|
5
|
-
import { LexiconData, LexiconStore } from './lexicon-store.js'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* This utility class handles the retrieval and caching of lexicon
|
|
9
|
-
* data. In particular, it handles failed retrieval attempts by returning cached
|
|
10
|
-
* data.
|
|
11
|
-
*
|
|
12
|
-
* @private
|
|
13
|
-
*/
|
|
14
|
-
export class LexiconGetter extends CachedGetter<Nsid, LexiconData> {
|
|
15
|
-
constructor(store: LexiconStore, lexResolver: LexResolver) {
|
|
16
|
-
super(
|
|
17
|
-
async (input, options, storedData) => {
|
|
18
|
-
const now = new Date()
|
|
19
|
-
const result = await lexResolver.get(input, options).catch((err) => {
|
|
20
|
-
// We swallow LexiconResolutionError errors, returning potentially
|
|
21
|
-
// "null" values here to avoid hammering the resolver with requests
|
|
22
|
-
// for the same lexicon that is known to be unavailable. The getter
|
|
23
|
-
// should be called again based on the isStale() function below.
|
|
24
|
-
if (err instanceof LexResolverError) return undefined
|
|
25
|
-
|
|
26
|
-
// Unexpected error are propagated
|
|
27
|
-
throw err
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
// Keep original createdAt, if available
|
|
32
|
-
createdAt: storedData?.createdAt ?? now,
|
|
33
|
-
// Always update updatedAt
|
|
34
|
-
updatedAt: now,
|
|
35
|
-
// Update the data with fresh data, if available, or keep cached
|
|
36
|
-
// values (if any) otherwise.
|
|
37
|
-
lastSucceededAt: result ? now : storedData?.lastSucceededAt ?? null,
|
|
38
|
-
uri: result ? result.uri.toString() : storedData?.uri ?? null,
|
|
39
|
-
lexicon: result ? result.lexicon : storedData?.lexicon ?? null,
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
set: async (nsid, data) => store.storeLexicon(nsid, data),
|
|
44
|
-
get: async (nsid) => (await store.findLexicon(nsid)) ?? undefined,
|
|
45
|
-
del: async (nsid) => store.deleteLexicon(nsid),
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
isStale: (nsid, data) => {
|
|
49
|
-
const timeSinceLastUpdate = Date.now() - data.updatedAt.getTime()
|
|
50
|
-
return timeSinceLastUpdate >= LEXICON_REFRESH_FREQUENCY
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { LexiconPermissionSet } from '@atproto/lex-document'
|
|
2
|
-
import { LexResolver, LexResolverError } from '@atproto/lex-resolver'
|
|
3
|
-
import { IncludeScope, Nsid } from '@atproto/oauth-scopes'
|
|
4
|
-
import { LexiconGetter } from './lexicon-getter.js'
|
|
5
|
-
import { LexiconStore } from './lexicon-store.js'
|
|
6
|
-
|
|
7
|
-
export * from './lexicon-store.js'
|
|
8
|
-
|
|
9
|
-
export class LexiconManager {
|
|
10
|
-
protected readonly lexiconGetter: LexiconGetter
|
|
11
|
-
|
|
12
|
-
constructor(store: LexiconStore, lexResolver: LexResolver) {
|
|
13
|
-
this.lexiconGetter = new LexiconGetter(store, lexResolver)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public async getPermissionSetsFromScope(scope?: string) {
|
|
17
|
-
const { includeScopes } = parseScope(scope)
|
|
18
|
-
return this.extractPermissionSets(includeScopes)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Transforms a scope string from an authorization request into a scope
|
|
23
|
-
* composed solely of granular permission scopes, transforming any NSID
|
|
24
|
-
* into its corresponding permission scopes.
|
|
25
|
-
*/
|
|
26
|
-
public async buildTokenScope(scope: string): Promise<string> {
|
|
27
|
-
const { includeScopes, otherScopes } = parseScope(scope)
|
|
28
|
-
|
|
29
|
-
// If the scope does not contain any "include:<nsid>" scopes, return it as-is.
|
|
30
|
-
if (!includeScopes.length) return scope
|
|
31
|
-
|
|
32
|
-
const permissionSets = await this.extractPermissionSets(includeScopes)
|
|
33
|
-
|
|
34
|
-
return Array.from(includeScopes)
|
|
35
|
-
.flatMap(nsidToPermissionScopes, permissionSets)
|
|
36
|
-
.concat(otherScopes)
|
|
37
|
-
.join(' ')
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Given a list of scope values, extract those that are NSIDs and return their
|
|
42
|
-
* corresponding permission sets.
|
|
43
|
-
*/
|
|
44
|
-
protected async extractPermissionSets(includeScopes: IncludeScope[]) {
|
|
45
|
-
const nsids = extractNsids(includeScopes)
|
|
46
|
-
return this.getPermissionSets(nsids)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
protected async getPermissionSets(nsids: Set<Nsid>) {
|
|
50
|
-
return new Map<string, LexiconPermissionSet>(
|
|
51
|
-
await Promise.all(Array.from(nsids, this.getPermissionSetEntry, this)),
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
protected async getPermissionSetEntry(
|
|
56
|
-
nsid: Nsid,
|
|
57
|
-
): Promise<[nsid: Nsid, permissionSet: LexiconPermissionSet]> {
|
|
58
|
-
const permissionSet = await this.getPermissionSet(nsid)
|
|
59
|
-
return [nsid, permissionSet]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
protected async getPermissionSet(nsid: Nsid): Promise<LexiconPermissionSet> {
|
|
63
|
-
const { lexicon } = await this.lexiconGetter.get(nsid)
|
|
64
|
-
|
|
65
|
-
if (!lexicon) {
|
|
66
|
-
throw LexResolverError.from(nsid)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (lexicon.defs.main?.type !== 'permission-set') {
|
|
70
|
-
const description = 'Lexicon document is not a permission set'
|
|
71
|
-
throw LexResolverError.from(nsid, description)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return lexicon.defs.main
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function parseScope(scope?: string) {
|
|
79
|
-
const includeScopes: IncludeScope[] = []
|
|
80
|
-
const otherScopes: string[] = []
|
|
81
|
-
|
|
82
|
-
if (scope) {
|
|
83
|
-
for (const scopeValue of scope.split(' ')) {
|
|
84
|
-
const parsed = IncludeScope.fromString(scopeValue)
|
|
85
|
-
if (parsed) {
|
|
86
|
-
includeScopes.push(parsed)
|
|
87
|
-
} else {
|
|
88
|
-
otherScopes.push(scopeValue)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
includeScopes,
|
|
95
|
-
otherScopes,
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function extractNsids(includeScopes: IncludeScope[]): Set<Nsid> {
|
|
100
|
-
return new Set(Array.from(includeScopes, extractNsid))
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function extractNsid(nsidScope: IncludeScope): Nsid {
|
|
104
|
-
return nsidScope.nsid
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function nsidToPermissionScopes(
|
|
108
|
-
this: Map<string, LexiconPermissionSet>,
|
|
109
|
-
includeScope: IncludeScope,
|
|
110
|
-
): string[] {
|
|
111
|
-
const permissionSet = this.get(includeScope.nsid)
|
|
112
|
-
if (permissionSet) return includeScope.toScopes(permissionSet)
|
|
113
|
-
|
|
114
|
-
// Should never happen (mostly there for type safety & future proofing)
|
|
115
|
-
throw new Error(`Missing permission set for NSID: ${includeScope.nsid}`)
|
|
116
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'
|
|
2
|
-
import { LexiconData, LexiconDocument } from './lexicon-data.js'
|
|
3
|
-
|
|
4
|
-
export type { Awaitable, LexiconData, LexiconDocument }
|
|
5
|
-
|
|
6
|
-
export interface LexiconStore {
|
|
7
|
-
findLexicon(nsid: string): Awaitable<LexiconData | null>
|
|
8
|
-
storeLexicon(nsid: string, data: LexiconData): Awaitable<void>
|
|
9
|
-
deleteLexicon(nsid: string): Awaitable<void>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const isLexiconStore = buildInterfaceChecker<LexiconStore>([
|
|
13
|
-
'deleteLexicon',
|
|
14
|
-
'findLexicon',
|
|
15
|
-
'storeLexicon',
|
|
16
|
-
])
|
|
17
|
-
|
|
18
|
-
export function ifLexiconStore<V extends Partial<LexiconStore>>(
|
|
19
|
-
implementation?: V,
|
|
20
|
-
): (V & LexiconStore) | undefined {
|
|
21
|
-
if (implementation && isLexiconStore(implementation)) {
|
|
22
|
-
return implementation
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return undefined
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function asLexiconStore<V extends Partial<LexiconStore>>(
|
|
29
|
-
implementation?: V,
|
|
30
|
-
): V & LexiconStore {
|
|
31
|
-
const store = ifLexiconStore(implementation)
|
|
32
|
-
if (store) return store
|
|
33
|
-
|
|
34
|
-
throw new Error('Invalid LexiconStore implementation')
|
|
35
|
-
}
|
package/src/lib/csp/index.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { CombinedTuple, Simplify } from '../util/type.js'
|
|
2
|
-
|
|
3
|
-
export type CspValue =
|
|
4
|
-
| `data:`
|
|
5
|
-
| `http:${string}`
|
|
6
|
-
| `https:${string}`
|
|
7
|
-
| `'none'`
|
|
8
|
-
| `'self'`
|
|
9
|
-
| `'sha256-${string}'`
|
|
10
|
-
| `'nonce-${string}'`
|
|
11
|
-
| `'unsafe-inline'`
|
|
12
|
-
| `'unsafe-eval'`
|
|
13
|
-
| `'strict-dynamic'`
|
|
14
|
-
| `'report-sample'`
|
|
15
|
-
| `'unsafe-hashes'`
|
|
16
|
-
|
|
17
|
-
const STRING_DIRECTIVES = ['base-uri'] as const
|
|
18
|
-
const BOOLEAN_DIRECTIVES = [
|
|
19
|
-
'upgrade-insecure-requests',
|
|
20
|
-
'block-all-mixed-content',
|
|
21
|
-
] as const
|
|
22
|
-
const ARRAY_DIRECTIVES = [
|
|
23
|
-
'connect-src',
|
|
24
|
-
'default-src',
|
|
25
|
-
'form-action',
|
|
26
|
-
'frame-ancestors',
|
|
27
|
-
'frame-src',
|
|
28
|
-
'img-src',
|
|
29
|
-
'script-src',
|
|
30
|
-
'style-src',
|
|
31
|
-
] as const
|
|
32
|
-
|
|
33
|
-
export type CspConfig = Simplify<
|
|
34
|
-
{
|
|
35
|
-
[K in (typeof BOOLEAN_DIRECTIVES)[number]]?: boolean
|
|
36
|
-
} & {
|
|
37
|
-
[K in (typeof STRING_DIRECTIVES)[number]]?: CspValue
|
|
38
|
-
} & {
|
|
39
|
-
[K in (typeof ARRAY_DIRECTIVES)[number]]?: Iterable<CspValue>
|
|
40
|
-
}
|
|
41
|
-
>
|
|
42
|
-
|
|
43
|
-
const NONE = "'none'"
|
|
44
|
-
|
|
45
|
-
export function buildCsp(config: CspConfig): string {
|
|
46
|
-
const values: string[] = []
|
|
47
|
-
|
|
48
|
-
for (const name of BOOLEAN_DIRECTIVES) {
|
|
49
|
-
if (config[name] === true) values.push(name)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
for (const name of STRING_DIRECTIVES) {
|
|
53
|
-
if (config[name]) values.push(`${name} ${config[name]}`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
for (const name of ARRAY_DIRECTIVES) {
|
|
57
|
-
// Remove duplicate values by using a Set
|
|
58
|
-
const val = config[name] ? new Set(config[name]) : undefined
|
|
59
|
-
if (val?.size) values.push(`${name} ${Array.from(val).join(' ')}`)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return values.join('; ')
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function mergeCsp<C extends (CspConfig | null | undefined)[]>(
|
|
66
|
-
...configs: C
|
|
67
|
-
) {
|
|
68
|
-
return configs.filter((v) => v != null).reduce(combineCsp) as CombinedTuple<C>
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function combineCsp(a: CspConfig, b: CspConfig): CspConfig {
|
|
72
|
-
const result: CspConfig = {}
|
|
73
|
-
|
|
74
|
-
for (const name of BOOLEAN_DIRECTIVES) {
|
|
75
|
-
// @NOTE b (if defined) takes precedence
|
|
76
|
-
const value = b[name] ?? a[name]
|
|
77
|
-
if (value != null) result[name] = value
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
for (const name of STRING_DIRECTIVES) {
|
|
81
|
-
if (a[name] || b[name]) {
|
|
82
|
-
const aNotNone = a[name] === NONE ? undefined : a[name]
|
|
83
|
-
const bNotNone = b[name] === NONE ? undefined : b[name]
|
|
84
|
-
// @NOTE b takes precedence
|
|
85
|
-
result[name] = bNotNone || aNotNone || NONE
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
for (const name of ARRAY_DIRECTIVES) {
|
|
90
|
-
if (a[name] && b[name]) {
|
|
91
|
-
const set = new Set(a[name])
|
|
92
|
-
if (b[name]) for (const value of b[name]) set.add(value)
|
|
93
|
-
if (set.size > 1 && set.has(NONE)) set.delete(NONE)
|
|
94
|
-
result[name] = [...set]
|
|
95
|
-
} else if (a[name] || b[name]) {
|
|
96
|
-
result[name] = a[name] || b[name]
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return result
|
|
101
|
-
}
|