@atproto/oauth-provider 0.19.6 → 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 +33 -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
package/src/lib/http/stream.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage } from 'node:http'
|
|
2
|
-
import { Readable } from 'node:stream'
|
|
3
|
-
import createHttpError from 'http-errors'
|
|
4
|
-
import { decodeStream, streamToNodeBuffer } from '@atproto/common'
|
|
5
|
-
import {
|
|
6
|
-
KnownNames,
|
|
7
|
-
KnownParser,
|
|
8
|
-
ParserResult,
|
|
9
|
-
parseContentType,
|
|
10
|
-
parsers,
|
|
11
|
-
} from './parser.js'
|
|
12
|
-
|
|
13
|
-
export function decodeHttpRequest(req: IncomingMessage): Readable {
|
|
14
|
-
try {
|
|
15
|
-
return decodeStream(req, req.headers['content-encoding'])
|
|
16
|
-
} catch (cause) {
|
|
17
|
-
const message =
|
|
18
|
-
cause instanceof TypeError ? cause.message : `Invalid content-encoding`
|
|
19
|
-
throw createHttpError(415, message, { cause })
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Generic method that parses a stream of unknown nature (HTTP request/response,
|
|
25
|
-
* socket, file, etc.), but of known mime type, into a parsed object.
|
|
26
|
-
*
|
|
27
|
-
* @throws {TypeError} If the content-type is not valid or supported.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
export async function parseHttpRequest<A extends readonly KnownNames[]>(
|
|
31
|
-
req: IncomingMessage,
|
|
32
|
-
allow: A,
|
|
33
|
-
) {
|
|
34
|
-
const type = parseContentType(
|
|
35
|
-
req.headers['content-type'] ?? 'application/octet-stream',
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
const parser = parsers.find(
|
|
39
|
-
(parser) => allow.includes(parser.name) && parser.test(type.mime),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
if (!parser) {
|
|
43
|
-
throw createHttpError(415, `Unsupported content-type: ${type.mime}`)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const stream = decodeHttpRequest(req)
|
|
47
|
-
const buffer = await streamToNodeBuffer(stream)
|
|
48
|
-
return parser.parse(buffer, type) as ParserResult<
|
|
49
|
-
Extract<KnownParser, { name: A[number] }>
|
|
50
|
-
>
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function flushStream(stream: AsyncIterable<any>): Promise<void> {
|
|
54
|
-
for await (const _ of stream) {
|
|
55
|
-
// Consume the stream to completion
|
|
56
|
-
}
|
|
57
|
-
}
|
package/src/lib/http/types.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
|
-
|
|
3
|
-
export type NextFunction = (err?: unknown) => void
|
|
4
|
-
|
|
5
|
-
export type Middleware<
|
|
6
|
-
T = void,
|
|
7
|
-
Req = IncomingMessage,
|
|
8
|
-
Res = ServerResponse,
|
|
9
|
-
> = (this: T, req: Req, res: Res, next: NextFunction) => void
|
|
10
|
-
|
|
11
|
-
export type Handler<T = void, Req = IncomingMessage, Res = ServerResponse> = (
|
|
12
|
-
this: T,
|
|
13
|
-
req: Req,
|
|
14
|
-
res: Res,
|
|
15
|
-
next?: NextFunction,
|
|
16
|
-
) => void
|
package/src/lib/http/url.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export type UrlReference = {
|
|
2
|
-
origin?: string
|
|
3
|
-
pathname?: string
|
|
4
|
-
searchParams?: Iterable<readonly [string, string]> // compatible with URLSearchParams
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function urlMatch(url: URL, reference: UrlReference) {
|
|
8
|
-
if (reference.origin !== undefined) {
|
|
9
|
-
if (url.origin !== reference.origin) return false
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (reference.pathname !== undefined) {
|
|
13
|
-
if (url.pathname !== reference.pathname) return false
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (reference.searchParams !== undefined) {
|
|
17
|
-
for (const [key, value] of reference.searchParams) {
|
|
18
|
-
if (url.searchParams.get(key) !== value) return false
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return true
|
|
23
|
-
}
|
package/src/lib/nsid.ts
DELETED
package/src/lib/redis.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Redis, type RedisOptions } from 'ioredis'
|
|
2
|
-
|
|
3
|
-
export type { Redis, RedisOptions }
|
|
4
|
-
|
|
5
|
-
export type CreateRedisOptions = Redis | RedisOptions | string
|
|
6
|
-
|
|
7
|
-
export function createRedis(options: CreateRedisOptions): Redis {
|
|
8
|
-
if (typeof options === 'string') {
|
|
9
|
-
const url = new URL(
|
|
10
|
-
options.startsWith('redis://') ? options : `redis://${options}`,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
return new Redis({
|
|
14
|
-
host: url.hostname,
|
|
15
|
-
port: parseInt(url.port, 10),
|
|
16
|
-
password: url.password,
|
|
17
|
-
})
|
|
18
|
-
} else if ('on' in options && 'call' in options && 'acl' in options) {
|
|
19
|
-
// Not using "instanceof" here in case the options is an instance of another
|
|
20
|
-
// version of ioredis (Redis is both a class and an interface).
|
|
21
|
-
return options
|
|
22
|
-
} else {
|
|
23
|
-
return new Redis(options)
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
import {
|
|
3
|
-
oauthAccessTokenSchema,
|
|
4
|
-
oauthTokenTypeSchema,
|
|
5
|
-
} from '@atproto/oauth-types'
|
|
6
|
-
import { InvalidRequestError } from '../../errors/invalid-request-error.js'
|
|
7
|
-
import { WWWAuthenticateError } from '../../errors/www-authenticate-error.js'
|
|
8
|
-
|
|
9
|
-
export const authorizationHeaderSchema = z.tuple([
|
|
10
|
-
oauthTokenTypeSchema,
|
|
11
|
-
oauthAccessTokenSchema,
|
|
12
|
-
])
|
|
13
|
-
|
|
14
|
-
export const parseAuthorizationHeader = (header: unknown) => {
|
|
15
|
-
if (typeof header !== 'string') {
|
|
16
|
-
throw new WWWAuthenticateError(
|
|
17
|
-
'invalid_request',
|
|
18
|
-
'Authorization header required',
|
|
19
|
-
{ Bearer: {}, DPoP: {} },
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const parsed = authorizationHeaderSchema.safeParse(header.split(' '))
|
|
24
|
-
if (!parsed.success) {
|
|
25
|
-
throw new InvalidRequestError('Invalid authorization header')
|
|
26
|
-
}
|
|
27
|
-
return parsed.data
|
|
28
|
-
}
|
package/src/lib/util/cast.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export function asArray<T>(value: T | T[]): T[] {
|
|
2
|
-
if (value == null) return []
|
|
3
|
-
return Array.isArray(value) ? value : [value]
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export function asURL(value: string | { toString: () => string }): URL {
|
|
7
|
-
return new URL(value)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function ifURL(
|
|
11
|
-
value: string | { toString: () => string },
|
|
12
|
-
): URL | undefined {
|
|
13
|
-
try {
|
|
14
|
-
return asURL(value)
|
|
15
|
-
} catch {
|
|
16
|
-
return undefined
|
|
17
|
-
}
|
|
18
|
-
}
|
package/src/lib/util/color.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import { parseUi8Dec, parseUi8Hex } from './ui8.js'
|
|
2
|
-
|
|
3
|
-
export type RgbColor = { r: number; g: number; b: number }
|
|
4
|
-
export type HslColor = { h: number; s: number; l: number }
|
|
5
|
-
export type RgbaColor = { r: number; g: number; b: number; a: number }
|
|
6
|
-
export type HslaColor = { h: number; s: number; l: number; a: number }
|
|
7
|
-
|
|
8
|
-
export type Color = RgbColor | HslColor | RgbaColor | HslaColor
|
|
9
|
-
|
|
10
|
-
export function parseColor(color: string): RgbColor | RgbaColor {
|
|
11
|
-
if (color.startsWith('#')) {
|
|
12
|
-
return parseHexColor(color)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
if (color.startsWith('rgba(')) {
|
|
16
|
-
return parseRgbaColor(color)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (color.startsWith('rgb(')) {
|
|
20
|
-
return parseRgbColor(color)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Should never happen (as long as the input is a validated WebColor)
|
|
24
|
-
throw new TypeError(`Invalid color value: ${color}`)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function parseHexColor(v: string): RgbColor | RgbaColor {
|
|
28
|
-
// parseInt('az', 16) does not return NaN so we need to check the format
|
|
29
|
-
if (!/^#[0-9a-f]+$/i.test(v)) {
|
|
30
|
-
throw new TypeError(`Invalid hex color value: ${v}`)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (v.length === 4 || v.length === 5) {
|
|
34
|
-
const r = parseUi8Hex(v[1].repeat(2))
|
|
35
|
-
const g = parseUi8Hex(v[2].repeat(2))
|
|
36
|
-
const b = parseUi8Hex(v[3].repeat(2))
|
|
37
|
-
const a = v.length > 4 ? parseUi8Hex(v[4].repeat(2)) : undefined
|
|
38
|
-
return a == null ? { r, g, b } : { r, g, b, a }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (v.length === 7 || v.length === 9) {
|
|
42
|
-
const r = parseUi8Hex(v.slice(1, 3))
|
|
43
|
-
const g = parseUi8Hex(v.slice(3, 5))
|
|
44
|
-
const b = parseUi8Hex(v.slice(5, 7))
|
|
45
|
-
const a = v.length > 8 ? parseUi8Hex(v.slice(7, 9)) : undefined
|
|
46
|
-
return a == null ? { r, g, b } : { r, g, b, a }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
throw new TypeError(`Invalid hex color value: ${v}`)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function parseRgbColor(v: string): RgbColor {
|
|
53
|
-
const matches = v.match(/^\s*rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/)
|
|
54
|
-
if (!matches) throw new TypeError(`Invalid rgb color value: ${v}`)
|
|
55
|
-
|
|
56
|
-
const r = parseUi8Dec(matches[1])
|
|
57
|
-
const g = parseUi8Dec(matches[2])
|
|
58
|
-
const b = parseUi8Dec(matches[3])
|
|
59
|
-
return { r, g, b }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function parseRgbaColor(v: string): RgbaColor {
|
|
63
|
-
const matches = v.match(
|
|
64
|
-
/^\s*rgba\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/,
|
|
65
|
-
)
|
|
66
|
-
if (!matches) throw new TypeError(`Invalid rgba color value: ${v}`)
|
|
67
|
-
|
|
68
|
-
const r = parseUi8Dec(matches[1])
|
|
69
|
-
const g = parseUi8Dec(matches[2])
|
|
70
|
-
const b = parseUi8Dec(matches[3])
|
|
71
|
-
const a = parseUi8Dec(matches[4])
|
|
72
|
-
return { r, g, b, a }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Return the color that has the best contrast with the reference color.
|
|
77
|
-
*/
|
|
78
|
-
export function pickContrastColor(ref: RgbColor, a: RgbColor, b: RgbColor) {
|
|
79
|
-
return computeContrastRatio(ref, a) > computeContrastRatio(ref, b) ? a : b
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function hslToRgb({ h, s, l }: HslColor): RgbColor
|
|
83
|
-
export function hslToRgb({ h, s, l, a }: HslaColor): RgbaColor
|
|
84
|
-
export function hslToRgb(input: HslaColor | HslColor): RgbColor | RgbaColor {
|
|
85
|
-
const { h, s, l } = input
|
|
86
|
-
|
|
87
|
-
// Achromatic (gray)
|
|
88
|
-
if (s === 0) {
|
|
89
|
-
const gray = Math.round(l * 255)
|
|
90
|
-
return 'a' in input
|
|
91
|
-
? { r: gray, g: gray, b: gray, a: input.a }
|
|
92
|
-
: { r: gray, g: gray, b: gray }
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const hueToRgb = (p: number, q: number, t: number): number => {
|
|
96
|
-
if (t < 0) t += 1
|
|
97
|
-
if (t > 1) t -= 1
|
|
98
|
-
if (t < 1 / 6) return p + (q - p) * 6 * t
|
|
99
|
-
if (t < 1 / 2) return q
|
|
100
|
-
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
|
|
101
|
-
return p
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
|
105
|
-
const p = 2 * l - q
|
|
106
|
-
const hNorm = h / 360
|
|
107
|
-
|
|
108
|
-
const r = Math.round(hueToRgb(p, q, hNorm + 1 / 3) * 255)
|
|
109
|
-
const g = Math.round(hueToRgb(p, q, hNorm) * 255)
|
|
110
|
-
const b = Math.round(hueToRgb(p, q, hNorm - 1 / 3) * 255)
|
|
111
|
-
|
|
112
|
-
return 'a' in input ? { r, g, b, a: input.a } : { r, g, b }
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* @see {@link https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef}
|
|
117
|
-
*/
|
|
118
|
-
function relativeLuminance(color: HslColor | RgbColor) {
|
|
119
|
-
const { r, g, b } = 'h' in color ? hslToRgb(color) : color
|
|
120
|
-
return rgbLum(r) * 0.2126 + rgbLum(g) * 0.7152 + rgbLum(b) * 0.0722
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function rgbLum(value) {
|
|
124
|
-
const rgb = value / 255
|
|
125
|
-
return rgb < 0.03928 ? rgb / 12.92 : Math.pow((rgb + 0.055) / 1.055, 2.4)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* @see {@link https://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef}
|
|
130
|
-
*/
|
|
131
|
-
function computeContrastRatio(a: RgbColor, b: RgbColor) {
|
|
132
|
-
const aLum = relativeLuminance(a)
|
|
133
|
-
const bLum = relativeLuminance(b)
|
|
134
|
-
const [lighter, darker] = aLum > bLum ? [aLum, bLum] : [bLum, aLum]
|
|
135
|
-
return (lighter + 0.05) / (darker + 0.05)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export function extractHue(input: RgbColor): number {
|
|
139
|
-
const r = input.r / 255
|
|
140
|
-
const g = input.g / 255
|
|
141
|
-
const b = input.b / 255
|
|
142
|
-
|
|
143
|
-
const max = Math.max(r, g, b)
|
|
144
|
-
const min = Math.min(r, g, b)
|
|
145
|
-
|
|
146
|
-
const chroma = max - min
|
|
147
|
-
|
|
148
|
-
switch (max) {
|
|
149
|
-
case min:
|
|
150
|
-
return 0 // Achromatic
|
|
151
|
-
case r: {
|
|
152
|
-
const segment = (g - b) / chroma
|
|
153
|
-
const shift = segment < 0 ? 360 / 60 : 0 / 60
|
|
154
|
-
return 60 * (segment + shift)
|
|
155
|
-
}
|
|
156
|
-
case g: {
|
|
157
|
-
const segment = (b - r) / chroma
|
|
158
|
-
const shift = 120 / 60
|
|
159
|
-
return 60 * (segment + shift)
|
|
160
|
-
}
|
|
161
|
-
// "default" needed for type safety. In practice, should be same as "case b:"
|
|
162
|
-
default: {
|
|
163
|
-
const segment = (r - g) / chroma
|
|
164
|
-
const shift = 240 / 60
|
|
165
|
-
return 60 * (segment + shift)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
package/src/lib/util/crypto.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from 'node:crypto'
|
|
2
|
-
|
|
3
|
-
export async function randomBuffer(bytesLength = 16) {
|
|
4
|
-
return new Promise<Buffer>((resolve, reject) => {
|
|
5
|
-
randomBytes(bytesLength, (err, buf) => {
|
|
6
|
-
if (err) return reject(err)
|
|
7
|
-
resolve(buf)
|
|
8
|
-
})
|
|
9
|
-
})
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function randomHexId(bytesLength = 16) {
|
|
13
|
-
const buffer = await randomBuffer(bytesLength)
|
|
14
|
-
return buffer.toString('hex')
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Basically all algorithms supported by "jose"'s jwtVerify().
|
|
18
|
-
// @TODO Is there a way to get this list from the runtime instead of hardcoding it?
|
|
19
|
-
export const VERIFY_ALGOS = [
|
|
20
|
-
'RS256',
|
|
21
|
-
'RS384',
|
|
22
|
-
'RS512',
|
|
23
|
-
|
|
24
|
-
'PS256',
|
|
25
|
-
'PS384',
|
|
26
|
-
'PS512',
|
|
27
|
-
|
|
28
|
-
'ES256',
|
|
29
|
-
'ES256K',
|
|
30
|
-
'ES384',
|
|
31
|
-
'ES512',
|
|
32
|
-
] as const
|
package/src/lib/util/date.ts
DELETED
package/src/lib/util/error.ts
DELETED
package/src/lib/util/function.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This function serves two purposes:
|
|
3
|
-
* - It ensures that the return value is a Promise, even if the function returns
|
|
4
|
-
* a "thenable" (i.e. a Promise-like object).
|
|
5
|
-
* - It allows to avoid assigning a `this` context to the function, which is
|
|
6
|
-
* particularly useful when the function is a member of a "private" object.
|
|
7
|
-
*/
|
|
8
|
-
export async function callAsync<F extends (...args: any[]) => unknown>(
|
|
9
|
-
fn: F,
|
|
10
|
-
...args: Parameters<F>
|
|
11
|
-
): Promise<Awaited<ReturnType<F>>>
|
|
12
|
-
export async function callAsync<F extends (...args: any[]) => unknown>(
|
|
13
|
-
fn?: F,
|
|
14
|
-
...args: Parameters<F>
|
|
15
|
-
): Promise<Awaited<ReturnType<F>> | undefined>
|
|
16
|
-
export async function callAsync<F extends (...args: any[]) => unknown>(
|
|
17
|
-
fn?: F,
|
|
18
|
-
...args: Parameters<F>
|
|
19
|
-
): Promise<unknown> {
|
|
20
|
-
return fn?.(...args)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function invokeOnce<T extends (this: any, ...a: any[]) => any>(
|
|
24
|
-
fn: T,
|
|
25
|
-
): T {
|
|
26
|
-
let fnNullable: T | null = fn
|
|
27
|
-
return function (...args) {
|
|
28
|
-
if (fnNullable) {
|
|
29
|
-
const fn = fnNullable
|
|
30
|
-
fnNullable = null
|
|
31
|
-
return fn.call(this, ...args)
|
|
32
|
-
}
|
|
33
|
-
throw new Error('Function called multiple times')
|
|
34
|
-
} as T
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function includedIn<T>(this: readonly T[], value: T): boolean {
|
|
38
|
-
return this.includes(value)
|
|
39
|
-
}
|
package/src/lib/util/locale.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
|
|
3
|
-
export const localeSchema = z
|
|
4
|
-
.string()
|
|
5
|
-
.regex(/^[a-z]{2,3}(-[A-Z]{2})?$/, 'Invalid locale')
|
|
6
|
-
export type Locale = z.infer<typeof localeSchema>
|
|
7
|
-
|
|
8
|
-
export const multiLangStringSchema = z.record(
|
|
9
|
-
localeSchema,
|
|
10
|
-
z.string().optional(),
|
|
11
|
-
)
|
|
12
|
-
export type MultiLangString = z.infer<typeof multiLangStringSchema>
|
package/src/lib/util/object.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export function mergeDefaults<T extends object>(
|
|
2
|
-
defaults: T,
|
|
3
|
-
...overrides: (T | undefined)[]
|
|
4
|
-
): T {
|
|
5
|
-
// @NOTE Not using the spread operator here because TS allows "undefined"
|
|
6
|
-
// values to be spread, which can lead to defaults being overwritten with
|
|
7
|
-
// "undefined". This function ensures that only defined values in "options"
|
|
8
|
-
// will overwrite the corresponding values in "defaults".
|
|
9
|
-
if (!overrides.length) return defaults
|
|
10
|
-
if (!overrides.some(Boolean)) return defaults
|
|
11
|
-
const result: T = { ...defaults } as T
|
|
12
|
-
for (const options of overrides) {
|
|
13
|
-
if (options) {
|
|
14
|
-
for (const key in options) {
|
|
15
|
-
const value = options[key]
|
|
16
|
-
if (value !== undefined) {
|
|
17
|
-
result[key] = value
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
return result
|
|
23
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { isLoopbackHost } from '@atproto/oauth-types'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* @see {@link https://datatracker.ietf.org/doc/html/rfc8252#section-8.4}
|
|
6
|
-
* @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-11#section-8.4.2}
|
|
7
|
-
*/
|
|
8
|
-
export function compareRedirectUri(
|
|
9
|
-
allowed_uri: string,
|
|
10
|
-
request_uri: string,
|
|
11
|
-
): boolean {
|
|
12
|
-
// https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
|
|
13
|
-
//
|
|
14
|
-
// > Authorization servers MUST require clients to register their complete
|
|
15
|
-
// > redirect URI (including the path component) and reject authorization
|
|
16
|
-
// > requests that specify a redirect URI that doesn't exactly match the
|
|
17
|
-
// > one that was registered; the exception is loopback redirects, where
|
|
18
|
-
// > an exact match is required except for the port URI component.
|
|
19
|
-
if (allowed_uri === request_uri) return true
|
|
20
|
-
|
|
21
|
-
// https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
|
|
22
|
-
const allowedUri = new URL(allowed_uri)
|
|
23
|
-
if (isLoopbackHost(allowedUri.hostname)) {
|
|
24
|
-
const requestUri = new URL(request_uri)
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
// > The authorization server MUST allow any port to be specified at the
|
|
28
|
-
// > time of the request for loopback IP redirect URIs, to accommodate
|
|
29
|
-
// > clients that obtain an available ephemeral port from the operating
|
|
30
|
-
// > system at the time of the request
|
|
31
|
-
//
|
|
32
|
-
// Note: We only apply this rule if the allowed URI does not have a port
|
|
33
|
-
// specified.
|
|
34
|
-
(!allowedUri.port || allowedUri.port === requestUri.port) &&
|
|
35
|
-
allowedUri.hostname === requestUri.hostname &&
|
|
36
|
-
allowedUri.pathname === requestUri.pathname &&
|
|
37
|
-
allowedUri.protocol === requestUri.protocol &&
|
|
38
|
-
allowedUri.search === requestUri.search &&
|
|
39
|
-
allowedUri.hash === requestUri.hash &&
|
|
40
|
-
allowedUri.username === requestUri.username &&
|
|
41
|
-
allowedUri.password === requestUri.password
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return false
|
|
46
|
-
}
|
package/src/lib/util/time.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { setTimeout as sleep } from 'node:timers/promises'
|
|
2
|
-
import { Awaitable } from './type.js'
|
|
3
|
-
|
|
4
|
-
export function onOvertimeDefault(options: {
|
|
5
|
-
start: number
|
|
6
|
-
end: number
|
|
7
|
-
elapsed: number
|
|
8
|
-
time: number
|
|
9
|
-
}): void {
|
|
10
|
-
console.warn(
|
|
11
|
-
`constantTime: execution time was ${options.elapsed}ms (which is greater than ${options.time}ms). You should increase the "time" to properly defend against timing attacks.`,
|
|
12
|
-
)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Utility function to protect against timing attacks.
|
|
17
|
-
*/
|
|
18
|
-
export async function constantTime<R>(
|
|
19
|
-
time: number,
|
|
20
|
-
fn: () => Awaitable<R>,
|
|
21
|
-
onOvertime = onOvertimeDefault,
|
|
22
|
-
): Promise<R> {
|
|
23
|
-
if (!Number.isFinite(time) || time <= 0) {
|
|
24
|
-
throw new TypeError(`"time" must be a positive number`)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const start = Date.now()
|
|
28
|
-
try {
|
|
29
|
-
return await fn()
|
|
30
|
-
} finally {
|
|
31
|
-
const end = Date.now()
|
|
32
|
-
const elapsed = end - start
|
|
33
|
-
|
|
34
|
-
const remaining = time - elapsed
|
|
35
|
-
if (remaining >= 0) {
|
|
36
|
-
// Happy path, execution time was smaller than "time"
|
|
37
|
-
await sleep(remaining)
|
|
38
|
-
} else {
|
|
39
|
-
// The function execution took longer than "time"
|
|
40
|
-
onOvertime({ start, end, elapsed, time })
|
|
41
|
-
|
|
42
|
-
// Sleep until the next multiple of "time" to mitigate any attack
|
|
43
|
-
const multiplier = Math.ceil(elapsed / time)
|
|
44
|
-
const remaining = multiplier * time - elapsed
|
|
45
|
-
|
|
46
|
-
await sleep(remaining)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|