diff --git a/.gitignore b/.gitignore index 3648e5a3f73..dccb907cf77 100644 --- a/.gitignore +++ b/.gitignore @@ -2,14 +2,14 @@ node_modules lerna-debug.log npm-debug.log yarn-error.log -packages/*/dist +packages/**/dist .idea packages/*/coverage .vscode/ test.sqlite .DS_Store *.log -tsconfig.build.tsbuildinfo +*.tsbuildinfo .*.env .env \#*\# diff --git a/packages/README.md b/packages/README.md index 68835de45f7..7e433ff8000 100644 --- a/packages/README.md +++ b/packages/README.md @@ -13,6 +13,7 @@ - [Crypto](./crypto): Atproto's common cryptographic operations. - [Syntax](./syntax): A library for identifier syntax: NSID, AT URI, handles, etc. - [Lexicon](./lexicon): A library for validating data using atproto's schema system. +- [OAuth Provider](./oauth/oauth-provider): A library for supporting ATPROTO's OAuth. - [Repo](./repo): The "atproto repository" core implementation (a Merkle Search Tree). - [XRPC](./xrpc): An XRPC client implementation. - [XRPC Server](./xrpc-server): An XRPC server implementation. diff --git a/packages/dev-env/src/pds.ts b/packages/dev-env/src/pds.ts index 0828f2f3f03..2eda6695a32 100644 --- a/packages/dev-env/src/pds.ts +++ b/packages/dev-env/src/pds.ts @@ -45,6 +45,17 @@ export class TestPds { modServiceDid: 'did:example:invalid', plcRotationKeyK256PrivateKeyHex: plcRotationPriv, inviteRequired: false, + fetchDisableSsrf: true, + oauthProviderName: 'PDS (dev)', + oauthProviderPrimaryColor: '#ffcb1e', + oauthProviderLogo: + 'https://uxwing.com/wp-content/themes/uxwing/download/animals-and-birds/bee-icon.png', + oauthProviderErrorColor: undefined, + oauthProviderHomeLink: 'https://bsky.social/', + oauthProviderTosLink: 'https://bsky.social/about/support/tos', + oauthProviderPrivacyPolicyLink: + 'https://bsky.social/about/support/privacy-policy', + oauthProviderSupportLink: 'https://blueskyweb.zendesk.com/hc/en-us', ...config, } const cfg = pds.envToCfg(env) diff --git a/packages/did/package.json b/packages/did/package.json new file mode 100644 index 00000000000..97c7cd32834 --- /dev/null +++ b/packages/did/package.json @@ -0,0 +1,37 @@ +{ + "name": "@atproto/did", + "version": "0.0.1", + "license": "MIT", + "description": "DID resolution and verification library", + "keywords": [ + "atproto", + "did", + "validation", + "types" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/did" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "tslib": "^2.6.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/did/src/did-document.ts b/packages/did/src/did-document.ts new file mode 100644 index 00000000000..ce0493fce89 --- /dev/null +++ b/packages/did/src/did-document.ts @@ -0,0 +1,127 @@ +import { z } from 'zod' + +import { Did, didSchema } from './did.js' + +/** + * RFC3968 compliant URI + * + * @see {@link https://www.rfc-editor.org/rfc/rfc3986} + */ +const rfc3968UriSchema = z.string().refine((data) => { + try { + new URL(data) + return true + } catch { + return false + } +}, 'RFC3968 compliant URI') + +const didControllerSchema = z.union([didSchema, z.array(didSchema)]) + +/** + * @note this schema might be too permissive + */ +const didRelativeUriSchema = z.union([ + rfc3968UriSchema, + z.string().regex(/^#[^#]+$/), +]) + +const didVerificationMethodSchema = z.object({ + id: didRelativeUriSchema, + type: z.string().min(1), + controller: didControllerSchema, + publicKeyJwk: z.record(z.string(), z.unknown()).optional(), + publicKeyMultibase: z.string().optional(), +}) + +/** + * The value of the id property MUST be a URI conforming to [RFC3986]. A + * conforming producer MUST NOT produce multiple service entries with the same + * id. A conforming consumer MUST produce an error if it detects multiple + * service entries with the same id. + * + * @note Normally, only rfc3968UriSchema should be allowed here. However, the + * did:plc uses relative URI. For this reason, we also allow relative URIs + * here. + */ +const didServiceIdSchema = didRelativeUriSchema + +/** + * The value of the type property MUST be a string or a set of strings. In order + * to maximize interoperability, the service type and its associated properties + * SHOULD be registered in the DID Specification Registries + * [DID-SPEC-REGISTRIES]. + */ +const didServiceTypeSchema = z.union([z.string(), z.array(z.string())]) + +/** + * The value of the serviceEndpoint property MUST be a string, a map, or a set + * composed of one or more strings and/or maps. All string values MUST be valid + * URIs conforming to [RFC3986] and normalized according to the Normalization + * and Comparison rules in RFC3986 and to any normalization rules in its + * applicable URI scheme specification. + */ +const didServiceEndpointSchema = z.union([ + rfc3968UriSchema, + z.record(z.string(), rfc3968UriSchema), + z.array(z.union([rfc3968UriSchema, z.record(z.string(), rfc3968UriSchema)])), +]) + +/** + * Each service map MUST contain id, type, and serviceEndpoint properties. + * @see {@link https://www.w3.org/TR/did-core/#services} + */ +const didServiceSchema = z.object({ + id: didServiceIdSchema, + type: didServiceTypeSchema, + serviceEndpoint: didServiceEndpointSchema, +}) + +export type DidService = z.infer + +const didAuthenticationSchema = z.union([ + // + didRelativeUriSchema, + didVerificationMethodSchema, +]) + +/** + * @note This schema is incomplete + * @see {@link https://www.w3.org/TR/did-core/#production-0} + */ +export const didDocumentSchema = z.object({ + '@context': z.union([ + z.literal('https://www.w3.org/ns/did/v1'), + z + .array(z.string().url()) + .nonempty() + .refine((data) => data[0] === 'https://www.w3.org/ns/did/v1'), + ]), + id: didSchema, + controller: didControllerSchema.optional(), + alsoKnownAs: z.array(rfc3968UriSchema).optional(), + service: z.array(didServiceSchema).optional(), + authentication: z.array(didAuthenticationSchema).optional(), + verificationMethod: z + .array(z.union([didVerificationMethodSchema, didRelativeUriSchema])) + .optional(), +}) + +export type DidDocument = z.infer< + typeof didDocumentSchema +> & { id: Did } + +// TODO: add other refinements ? +export const didDocumentValidator = didDocumentSchema.refinement( + (data) => + !data.service?.some((s, i, a) => { + for (let j = i + 1; j < a.length; j++) { + if (s.id === a[j]!.id) return true + } + return false + }), + { + code: z.ZodIssueCode.custom, + message: 'Duplicate service id', + }, +) diff --git a/packages/did/src/did-error.ts b/packages/did/src/did-error.ts new file mode 100644 index 00000000000..e3b1b355af2 --- /dev/null +++ b/packages/did/src/did-error.ts @@ -0,0 +1,51 @@ +export class DidError extends Error { + constructor( + public readonly did: string, + message: string, + public readonly code: string, + public readonly status = 400, + cause?: unknown, + ) { + super(message, { cause }) + } + + /** + * For compatibility with error handlers in common HTTP frameworks. + */ + get statusCode() { + return this.status + } + + override toString() { + return `${this.constructor.name} ${this.code} (${this.did}): ${this.message}` + } + + static from(cause: unknown, did: string): DidError { + if (cause instanceof DidError) { + return cause + } + + const message = + cause instanceof Error + ? cause.message + : typeof cause === 'string' + ? cause + : 'An unknown error occurred' + + const status = + (typeof (cause as any)?.statusCode === 'number' + ? (cause as any).statusCode + : undefined) ?? + (typeof (cause as any)?.status === 'number' + ? (cause as any).status + : undefined) + + return new DidError(did, message, 'did-unknown-error', status, cause) + } +} + +export class InvalidDidError extends DidError { + constructor(did: string, message: string, cause?: unknown) { + super(did, message, 'did-invalid', 400, cause) + } +} diff --git a/packages/did/src/did.ts b/packages/did/src/did.ts new file mode 100644 index 00000000000..e570c5e44e3 --- /dev/null +++ b/packages/did/src/did.ts @@ -0,0 +1,248 @@ +import { z } from 'zod' +import { DidError, InvalidDidError } from './did-error.js' + +const DID_PREFIX = 'did:' +const DID_PREFIX_LENGTH = DID_PREFIX.length +export { DID_PREFIX } + +/** + * Type representation of a Did, with method. + * + * ```bnf + * did = "did:" method-name ":" method-specific-id + * method-name = 1*method-char + * method-char = %x61-7A / DIGIT + * method-specific-id = *( *idchar ":" ) 1*idchar + * idchar = ALPHA / DIGIT / "." / "-" / "_" / pct-encoded + * pct-encoded = "%" HEXDIG HEXDIG + * ``` + * + * @example + * ```ts + * type DidWeb = Did<'web'> // `did:web:${string}` + * type DidCustom = Did<'web' | 'plc'> // `did:${'web' | 'plc'}:${string}` + * type DidNever = Did<' invalid 🥴 '> // never + * type DidFoo = Did<'foo' | ' invalid 🥴 '> // `did:foo:${string}` + * ``` + * + * @see {@link https://www.w3.org/TR/did-core/#did-syntax} + */ +export type Did = `did:${AsDidMethod}:${string}` + +/** + * DID Method + */ +export type AsDidMethod = string extends M + ? string // can't know... + : AsDidMethodInternal + +type AlphanumericChar = DigitChar | LowerAlphaChar +type DigitChar = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' +type LowerAlphaChar = + | 'a' + | 'b' + | 'c' + | 'd' + | 'e' + | 'f' + | 'g' + | 'h' + | 'i' + | 'j' + | 'k' + | 'l' + | 'm' + | 'n' + | 'o' + | 'p' + | 'q' + | 'r' + | 's' + | 't' + | 'u' + | 'v' + | 'w' + | 'x' + | 'y' + | 'z' + +type AsDidMethodInternal< + S, + Acc extends string, +> = S extends `${infer H}${infer T}` + ? H extends AlphanumericChar + ? AsDidMethodInternal + : never + : Acc extends '' + ? never + : Acc + +/** + * DID Method-name check function. + * + * Check if the input is a valid DID method name, at the position between + * `start` (inclusive) and `end` (exclusive). + */ +export function checkDidMethod( + input: string, + start = 0, + end = input.length, +): void { + if (!(end >= start)) { + throw new TypeError('end < start') + } + if (end === start) { + throw new InvalidDidError(input, `Empty method name`) + } + + let c: number + for (let i = start; i < end; i++) { + c = input.charCodeAt(i) + if ( + (c < 0x61 || c > 0x7a) && // a-z + (c < 0x30 || c > 0x39) // 0-9 + ) { + throw new InvalidDidError( + input, + `Invalid character at position ${i} in DID method name`, + ) + } + } +} + +/** + * This method assumes the input is a valid Did + */ +export function extractDidMethod(did: D) { + const msidSep = did.indexOf(':', DID_PREFIX_LENGTH) + const method = did.slice(DID_PREFIX_LENGTH, msidSep) + return method as D extends Did ? M : string +} + +/** + * DID Method-specific identifier check function. + * + * Check if the input is a valid DID method-specific identifier, at the position + * between `start` (inclusive) and `end` (exclusive). + */ +export function checkDidMsid( + input: string, + start = 0, + end = input.length, +): void { + if (!(end >= start)) { + throw new TypeError('end < start') + } + if (end === start) { + throw new InvalidDidError(input, `DID method-specific id must not be empty`) + } + + let c: number + for (let i = start; i < end; i++) { + c = input.charCodeAt(i) + + // Check for frequent chars first + if ( + (c < 0x61 || c > 0x7a) && // a-z + (c < 0x41 || c > 0x5a) && // A-Z + (c < 0x30 || c > 0x39) && // 0-9 + c !== 0x2e && // . + c !== 0x2d && // - + c !== 0x5f // _ + ) { + // Less frequent chars are checked here + + // ":" + if (c === 0x3a) { + if (i === end - 1) { + throw new InvalidDidError(input, `DID cannot end with ":"`) + } + continue + } + + // pct-encoded + if (c === 0x25) { + c = input.charCodeAt(++i) + if ((c < 0x30 || c > 0x39) && (c < 0x41 || c > 0x46)) { + throw new InvalidDidError( + input, + `Invalid pct-encoded character at position ${i}`, + ) + } + + c = input.charCodeAt(++i) + if ((c < 0x30 || c > 0x39) && (c < 0x41 || c > 0x46)) { + throw new InvalidDidError( + input, + `Invalid pct-encoded character at position ${i}`, + ) + } + + // There must always be 2 HEXDIG after a "%" + if (i >= end) { + throw new InvalidDidError( + input, + `Incomplete pct-encoded character at position ${i - 2}`, + ) + } + + continue + } + + throw new InvalidDidError( + input, + `Disallowed character in DID at position ${i}`, + ) + } + } +} + +export function checkDid(input: unknown): asserts input is Did { + if (typeof input !== 'string') { + throw new InvalidDidError(typeof input, `DID must be a string`) + } + + const { length } = input + if (length > 2048) { + throw new InvalidDidError(input, `DID is too long (2048 chars max)`) + } + + if (!input.startsWith(DID_PREFIX)) { + throw new InvalidDidError(input, `DID requires "${DID_PREFIX}" prefix`) + } + + const idSep = input.indexOf(':', DID_PREFIX_LENGTH) + if (idSep === -1) { + throw new InvalidDidError(input, `Missing colon after method name`) + } + + checkDidMethod(input, DID_PREFIX_LENGTH, idSep) + checkDidMsid(input, idSep + 1, length) +} + +export function isDid(input: string): input is Did { + try { + checkDid(input) + return true + } catch (err) { + if (err instanceof DidError) { + return false + } + throw err + } +} + +export const didSchema = z + .string() + .superRefine((value: string, ctx: z.RefinementCtx): value is Did => { + try { + checkDid(value) + return true + } catch (err) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: err instanceof Error ? err.message : 'Unexpected error', + }) + return false + } + }) diff --git a/packages/did/src/index.ts b/packages/did/src/index.ts new file mode 100644 index 00000000000..26c57de9942 --- /dev/null +++ b/packages/did/src/index.ts @@ -0,0 +1,4 @@ +export * from './did-document.js' +export * from './did-error.js' +export * from './did.js' +export * from './methods.js' diff --git a/packages/did/src/methods.ts b/packages/did/src/methods.ts new file mode 100644 index 00000000000..9fb8254af2d --- /dev/null +++ b/packages/did/src/methods.ts @@ -0,0 +1,2 @@ +export * from './methods/plc.js' +export * from './methods/web.js' diff --git a/packages/did/src/methods/plc.ts b/packages/did/src/methods/plc.ts new file mode 100644 index 00000000000..c50fc3c8498 --- /dev/null +++ b/packages/did/src/methods/plc.ts @@ -0,0 +1,30 @@ +import { InvalidDidError } from '../did-error.js' +import { Did } from '../did.js' + +const DID_PLC_PREFIX = `did:plc:` +const DID_PLC_PREFIX_LENGTH = DID_PLC_PREFIX.length +const DID_PLC_LENGTH = 32 + +export { DID_PLC_PREFIX } + +export function checkDidPlc(input: string): asserts input is Did<'plc'> { + if (input.length !== DID_PLC_LENGTH) { + throw new InvalidDidError( + input, + `did:plc must be ${DID_PLC_LENGTH} characters long`, + ) + } + + if (!input.startsWith(DID_PLC_PREFIX)) { + throw new InvalidDidError(input, `Invalid did:plc prefix`) + } + + let c: number + for (let i = DID_PLC_PREFIX_LENGTH; i < DID_PLC_LENGTH; i++) { + c = input.charCodeAt(i) + // Base32 encoding ([a-z2-7]) + if ((c < 0x61 || c > 0x7a) && (c < 0x32 || c > 0x37)) { + throw new InvalidDidError(input, `Invalid character at position ${i}`) + } + } +} diff --git a/packages/did/src/methods/web.ts b/packages/did/src/methods/web.ts new file mode 100644 index 00000000000..4d1e04e6d1d --- /dev/null +++ b/packages/did/src/methods/web.ts @@ -0,0 +1,38 @@ +import { InvalidDidError } from '../did-error.js' +import { Did, checkDidMsid } from '../did.js' + +export const DID_WEB_PREFIX = `did:web:` + +export function checkDidWeb(input: string): asserts input is Did<'web'> { + didWebToUrl(input) +} + +export function didWebToUrl(did: string): URL { + if (!did.startsWith(DID_WEB_PREFIX)) { + throw new InvalidDidError(did, `did:web must start with ${DID_WEB_PREFIX}`) + } + + if (did.charAt(DID_WEB_PREFIX.length) === ':') { + throw new InvalidDidError(did, 'did:web MSID must not start with a colon') + } + + // Make sure every char is valid + checkDidMsid(did, DID_WEB_PREFIX.length) + + try { + const msid = did.slice(DID_WEB_PREFIX.length) + const parts = msid.split(':').map(decodeURIComponent) + return new URL(`https://${parts.join('/')}`) + } catch (cause) { + throw new InvalidDidError(did, 'Invalid Web DID', cause) + } +} + +export function urlToDidWeb(url: URL): Did<'web'> { + const path = + url.pathname === '/' + ? '' + : url.pathname.slice(1).split('/').map(encodeURIComponent).join(':') + + return `did:web:${encodeURIComponent(url.host)}${path ? `:${path}` : ''}` +} diff --git a/packages/did/tsconfig.build.json b/packages/did/tsconfig.build.json new file mode 100644 index 00000000000..436d8ecb628 --- /dev/null +++ b/packages/did/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig/isomorphic.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/did/tsconfig.json b/packages/did/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/did/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/did-resolver/package.json b/packages/internal/did-resolver/package.json new file mode 100644 index 00000000000..677b619bacb --- /dev/null +++ b/packages/internal/did-resolver/package.json @@ -0,0 +1,41 @@ +{ + "name": "@atproto-labs/did-resolver", + "version": "0.0.1", + "license": "MIT", + "description": "DID resolution and verification library", + "keywords": [ + "atproto", + "did", + "resolver" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/did-resolver" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/pipe": "workspace:*", + "@atproto-labs/simple-store": "workspace:*", + "@atproto-labs/simple-store-memory": "workspace:*", + "@atproto/did": "workspace:*", + "tslib": "^2.6.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/internal/did-resolver/src/did-cache-memory.ts b/packages/internal/did-resolver/src/did-cache-memory.ts new file mode 100644 index 00000000000..edbafb6cc7c --- /dev/null +++ b/packages/internal/did-resolver/src/did-cache-memory.ts @@ -0,0 +1,25 @@ +import { Did, DidDocument } from '@atproto/did' +import { + SimpleStoreMemory, + SimpleStoreMemoryOptions, +} from '@atproto-labs/simple-store-memory' + +import { DidCache } from './did-cache.js' + +export type DidCacheMemoryOptions = SimpleStoreMemoryOptions + +export const DEFAULT_TTL = 3600 * 1000 // 1 hour +export const DEFAULT_MAX_SIZE = 50 * 1024 * 1024 // ~50MB + +export class DidCacheMemory + extends SimpleStoreMemory + implements DidCache +{ + constructor(options?: DidCacheMemoryOptions) { + super( + options?.max == null + ? { ttl: DEFAULT_TTL, maxSize: DEFAULT_MAX_SIZE, ...options } + : { ttl: DEFAULT_TTL, ...options }, + ) + } +} diff --git a/packages/internal/did-resolver/src/did-cache.ts b/packages/internal/did-resolver/src/did-cache.ts new file mode 100644 index 00000000000..0f830626e13 --- /dev/null +++ b/packages/internal/did-resolver/src/did-cache.ts @@ -0,0 +1,4 @@ +import { Did, DidDocument } from '@atproto/did' +import { SimpleStore } from '@atproto-labs/simple-store' + +export type DidCache = SimpleStore diff --git a/packages/internal/did-resolver/src/did-method.ts b/packages/internal/did-resolver/src/did-method.ts new file mode 100644 index 00000000000..3b45ec1bd06 --- /dev/null +++ b/packages/internal/did-resolver/src/did-method.ts @@ -0,0 +1,17 @@ +import { Did, DidDocument } from '@atproto/did' + +export type ResolveOptions = { + signal?: AbortSignal + noCache?: boolean +} + +export interface DidMethod { + resolve: ( + did: Did, + options?: ResolveOptions, + ) => DidDocument | PromiseLike +} + +export type DidMethods = { + [K in M]: DidMethod +} diff --git a/packages/internal/did-resolver/src/did-resolver-base.ts b/packages/internal/did-resolver/src/did-resolver-base.ts new file mode 100644 index 00000000000..0b8cdb11f52 --- /dev/null +++ b/packages/internal/did-resolver/src/did-resolver-base.ts @@ -0,0 +1,123 @@ +import { + Did, + DidDocument, + DidError, + DidService, + extractDidMethod, +} from '@atproto/did' +import { FetchError } from '@atproto-labs/fetch' +import { ZodError } from 'zod' + +import { DidMethod, DidMethods, ResolveOptions } from './did-method.js' + +export type { DidMethod, ResolveOptions } +export type ResolvedDocument = + D extends Did ? DidDocument : never + +export class DidResolverBase { + protected readonly methods: Map> + + constructor(methods: DidMethods) { + this.methods = new Map(Object.entries(methods)) + } + + async resolve( + did: D, + options?: ResolveOptions, + ): Promise> { + const method = extractDidMethod(did) + const resolver = this.methods.get(method) + if (!resolver) { + throw new DidError( + did, + `Unsupported DID method`, + 'did-method-invalid', + 400, + ) + } + + try { + const document = await resolver.resolve(did as Did, options) + if (document.id !== did) { + throw new DidError( + did, + `DID document id (${document.id}) does not match DID`, + 'did-document-id-mismatch', + 400, + ) + } + + return document as ResolvedDocument + } catch (err) { + if (err instanceof FetchError) { + throw new DidError(did, err.message, 'did-fetch-error', 400, err) + } + + if (err instanceof ZodError) { + throw new DidError( + did, + err.message, + 'did-document-format-error', + 503, + err, + ) + } + + throw DidError.from(err, did) + } + } + + async resolveService( + did: Did, + filter: { id?: string; type?: string }, + options?: ResolveOptions, + ): Promise { + if (!filter.id && !filter.type) { + throw new DidError( + did, + 'Either "filter.id" or "filter.type" must be specified', + 'did-service-filter-invalid', + ) + } + const document = await this.resolve(did, options) + const service = document.service?.find( + (s) => + (!filter.id || s.id === filter.id) && + (!filter.type || s.type === filter.type), + ) + if (service) return service + + throw new DidError( + did, + `No service matching "${filter.id || filter.type}" in DID Document`, + 'did-service-not-found', + 404, + ) + } + + async resolveServiceEndpoint( + did: Did, + filter: { id?: string; type?: string }, + options?: ResolveOptions, + ): Promise { + const service = await this.resolveService(did, filter, options) + if (typeof service.serviceEndpoint === 'string') { + return new URL(service.serviceEndpoint) + } else if (Array.isArray(service.serviceEndpoint)) { + // set of strings or maps + if (service.serviceEndpoint.length === 1) { + const first = service.serviceEndpoint[0]! + if (typeof first === 'string') return new URL(first) + } + } else { + // map + } + + throw new DidError( + did, + `Unable to determine serviceEndpoint for "${filter.id || filter.type}"`, + 'did-service-endpoint-not-found', + 404, + ) + } +} diff --git a/packages/internal/did-resolver/src/did-resolver.ts b/packages/internal/did-resolver/src/did-resolver.ts new file mode 100644 index 00000000000..b2d1e3a1ed8 --- /dev/null +++ b/packages/internal/did-resolver/src/did-resolver.ts @@ -0,0 +1,34 @@ +import { Did, DidDocument } from '@atproto/did' +import { CachedGetter } from '@atproto-labs/simple-store' + +import { DidCacheMemory } from './did-cache-memory.js' +import { DidCache } from './did-cache.js' +import { DidMethod, DidMethods, ResolveOptions } from './did-method.js' +import { DidResolverBase, ResolvedDocument } from './did-resolver-base.js' + +export type { DidMethod, ResolveOptions, ResolvedDocument } + +export type DidResolverOptions = { + cache?: DidCache +} + +export class DidResolver extends DidResolverBase { + readonly #getter: CachedGetter + + constructor(methods: DidMethods, options?: DidResolverOptions) { + super(methods) + + this.#getter = new CachedGetter( + (did, options) => super.resolve(did, options), + options?.cache ?? new DidCacheMemory(), + ) + } + + async resolve( + did: D, + options?: ResolveOptions, + ): Promise> + async resolve(did: Did, options?: ResolveOptions): Promise { + return this.#getter.get(did, options) + } +} diff --git a/packages/internal/did-resolver/src/index.ts b/packages/internal/did-resolver/src/index.ts new file mode 100644 index 00000000000..0b078d62a2a --- /dev/null +++ b/packages/internal/did-resolver/src/index.ts @@ -0,0 +1,9 @@ +export * from '@atproto/did' + +export * from './isomorphic-did-resolver.js' +export * from './did-cache-memory.js' +export * from './did-cache.js' +export * from './did-method.js' +export * from './did-resolver.js' +export * from './methods.js' +export * from './util.js' diff --git a/packages/internal/did-resolver/src/isomorphic-did-resolver.ts b/packages/internal/did-resolver/src/isomorphic-did-resolver.ts new file mode 100644 index 00000000000..e969d4a01b7 --- /dev/null +++ b/packages/internal/did-resolver/src/isomorphic-did-resolver.ts @@ -0,0 +1,23 @@ +import { DidCache } from './did-cache.js' +import { DidResolver } from './did-resolver.js' + +import { DidPlcMethod, DidPlcMethodOptions } from './methods/plc.js' +import { DidWebMethod, DidWebMethodOptions } from './methods/web.js' +import { Simplify } from './util.js' + +export type { DidCache } +export type IsomorphicDidResolverOptions = Simplify< + { cache?: DidCache } & DidPlcMethodOptions & DidWebMethodOptions +> + +export class IsomorphicDidResolver extends DidResolver<'plc' | 'web'> { + constructor({ cache, ...options }: IsomorphicDidResolverOptions = {}) { + super( + { + plc: new DidPlcMethod(options), + web: new DidWebMethod(options), + }, + { cache }, + ) + } +} diff --git a/packages/internal/did-resolver/src/methods.ts b/packages/internal/did-resolver/src/methods.ts new file mode 100644 index 00000000000..9fb8254af2d --- /dev/null +++ b/packages/internal/did-resolver/src/methods.ts @@ -0,0 +1,2 @@ +export * from './methods/plc.js' +export * from './methods/web.js' diff --git a/packages/internal/did-resolver/src/methods/plc.ts b/packages/internal/did-resolver/src/methods/plc.ts new file mode 100644 index 00000000000..052a494caeb --- /dev/null +++ b/packages/internal/did-resolver/src/methods/plc.ts @@ -0,0 +1,57 @@ +import { + Fetch, + fetchFailureHandler, + fetchJsonProcessor, + fetchJsonZodProcessor, + fetchOkProcessor, +} from '@atproto-labs/fetch' +import { pipe } from '@atproto-labs/pipe' +import { Did, checkDidPlc, didDocumentValidator } from '@atproto/did' + +import { DidMethod, ResolveOptions } from '../did-method.js' + +const fetchSuccessHandler = pipe( + fetchOkProcessor(), + fetchJsonProcessor(/^application\/(did\+ld\+)?json$/), + fetchJsonZodProcessor(didDocumentValidator), +) + +export type DidPlcMethodOptions = { + /** + * @default globalThis.fetch + */ + fetch?: Fetch + + /** + * @default 'https://plc.directory/' + */ + plcDirectoryUrl?: string | URL +} + +export class DidPlcMethod implements DidMethod<'plc'> { + readonly plcDirectoryUrl: URL + protected readonly fetch: Fetch + + constructor({ + plcDirectoryUrl = 'https://plc.directory/', + fetch = globalThis.fetch, + }: DidPlcMethodOptions = {}) { + this.plcDirectoryUrl = new URL(plcDirectoryUrl) + this.fetch = fetch + } + + async resolve(did: Did<'plc'>, options?: ResolveOptions) { + checkDidPlc(did) + + const url = new URL(`/${did}`, this.plcDirectoryUrl) + + const request = new Request(url, { + redirect: 'error', + headers: { accept: 'application/did+ld+json,application/json' }, + signal: options?.signal, + }) + + const { fetch } = this + return fetch(request).then(fetchSuccessHandler, fetchFailureHandler) + } +} diff --git a/packages/internal/did-resolver/src/methods/web.ts b/packages/internal/did-resolver/src/methods/web.ts new file mode 100644 index 00000000000..ce150a8def3 --- /dev/null +++ b/packages/internal/did-resolver/src/methods/web.ts @@ -0,0 +1,44 @@ +import { + Fetch, + fetchFailureHandler, + fetchJsonProcessor, + fetchJsonZodProcessor, + fetchOkProcessor, +} from '@atproto-labs/fetch' +import { pipe } from '@atproto-labs/pipe' +import { Did, didDocumentValidator, didWebToUrl } from '@atproto/did' + +import { DidMethod, ResolveOptions } from '../did-method.js' +import { wellKnownUrl } from '../util.js' + +const fetchSuccessHandler = pipe( + fetchOkProcessor(), + fetchJsonProcessor(/^application\/(did\+ld\+)?json$/), + fetchJsonZodProcessor(didDocumentValidator), +) + +export type DidWebMethodOptions = { + fetch?: Fetch +} + +export class DidWebMethod implements DidMethod<'web'> { + protected readonly fetch: Fetch + + constructor({ fetch = globalThis.fetch }: DidWebMethodOptions = {}) { + this.fetch = fetch + } + + async resolve(did: Did<'web'>, options?: ResolveOptions) { + const url = didWebToUrl(did) // Will throw if the DID is invalid + const didDocumentUrl = wellKnownUrl(url, 'did.json') + + const request = new Request(didDocumentUrl, { + redirect: 'error', + headers: { accept: 'application/did+ld+json,application/json' }, + signal: options?.signal, + }) + + const { fetch } = this + return fetch(request).then(fetchSuccessHandler, fetchFailureHandler) + } +} diff --git a/packages/internal/did-resolver/src/util.ts b/packages/internal/did-resolver/src/util.ts new file mode 100644 index 00000000000..9ed73937161 --- /dev/null +++ b/packages/internal/did-resolver/src/util.ts @@ -0,0 +1,50 @@ +export type Simplify = { [K in keyof T]: T[K] } & NonNullable + +export function wellKnownUrl(base: URL, path: string) { + const lastSlash = base.pathname.lastIndexOf('/') + + const prefix = + lastSlash <= 0 ? `/.well-known/` : base.pathname.slice(0, lastSlash) + + return new URL(`${prefix}/${path}`, base) +} + +export type DigitChar = + | '0' + | '1' + | '2' + | '3' + | '4' + | '5' + | '6' + | '7' + | '8' + | '9' + +export type LowerAlphaChar = + | 'a' + | 'b' + | 'c' + | 'd' + | 'e' + | 'f' + | 'g' + | 'h' + | 'i' + | 'j' + | 'k' + | 'l' + | 'm' + | 'n' + | 'o' + | 'p' + | 'q' + | 'r' + | 's' + | 't' + | 'u' + | 'v' + | 'w' + | 'x' + | 'y' + | 'z' diff --git a/packages/internal/did-resolver/tsconfig.build.json b/packages/internal/did-resolver/tsconfig.build.json new file mode 100644 index 00000000000..0e5790a2e3f --- /dev/null +++ b/packages/internal/did-resolver/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/isomorphic.json"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/internal/did-resolver/tsconfig.json b/packages/internal/did-resolver/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/did-resolver/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/fetch-node/package.json b/packages/internal/fetch-node/package.json new file mode 100644 index 00000000000..90b4aad5f90 --- /dev/null +++ b/packages/internal/fetch-node/package.json @@ -0,0 +1,39 @@ +{ + "name": "@atproto-labs/fetch-node", + "version": "0.0.1", + "license": "MIT", + "description": "SSRF protection for fetch() in Node.js", + "keywords": [ + "atproto", + "fetch", + "node" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/fetch-node" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/pipe": "workspace:*", + "ipaddr.js": "^2.1.0", + "tslib": "^2.6.2", + "undici": "^6.14.1" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.json" + } +} diff --git a/packages/internal/fetch-node/src/index.ts b/packages/internal/fetch-node/src/index.ts new file mode 100644 index 00000000000..b7022c681b9 --- /dev/null +++ b/packages/internal/fetch-node/src/index.ts @@ -0,0 +1,4 @@ +export * from '@atproto-labs/fetch' + +export * from './safe.js' +export * from './ssrf.js' diff --git a/packages/internal/fetch-node/src/safe.ts b/packages/internal/fetch-node/src/safe.ts new file mode 100644 index 00000000000..6b2329663a8 --- /dev/null +++ b/packages/internal/fetch-node/src/safe.ts @@ -0,0 +1,74 @@ +import { + DEFAULT_FORBIDDEN_DOMAIN_NAMES, + Fetch, + fetchMaxSizeProcessor, + forbiddenDomainNameRequestTransform, + protocolCheckRequestTransform, + requireHostHeaderTranform, + timeoutFetchWrap, +} from '@atproto-labs/fetch' +import { pipe } from '@atproto-labs/pipe' + +import { ssrfFetchWrap } from './ssrf.js' + +export type SafeFetchWrapOptions = NonNullable< + Parameters[0] +> + +/** + * Wrap a fetch function with safety checks so that it can be safely used + * with user provided input (URL). + */ +export const safeFetchWrap = ({ + fetch = globalThis.fetch as Fetch, + responseMaxSize = 512 * 1024, // 512kB + allowHttp = false, + allowData = false, + ssrfProtection = true, + timeout = 10e3 as number, + forbiddenDomainNames = DEFAULT_FORBIDDEN_DOMAIN_NAMES as Iterable, +} = {}): Fetch => + pipe( + /** + * Prevent using http:, file: or data: protocols. + */ + protocolCheckRequestTransform( + ['https:'] + .concat(allowHttp ? ['http:'] : []) + .concat(allowData ? ['data:'] : []), + ), + + /** + * Only requests that will be issued with a "Host" header are allowed. + */ + requireHostHeaderTranform(), + + /** + * Disallow fetching from domains we know are not atproto/OIDC client + * implementation. Note that other domains can be blocked by providing a + * custom fetch function combined with another + * forbiddenDomainNameRequestTransform. + */ + forbiddenDomainNameRequestTransform(forbiddenDomainNames), + + /** + * Since we will be fetching from the network based on user provided + * input, let's mitigate resource exhaustion attacks by setting a timeout. + */ + timeoutFetchWrap({ + timeout, + + /** + * Since we will be fetching from the network based on user provided + * input, we need to make sure that the request is not vulnerable to SSRF + * attacks. + */ + fetch: ssrfProtection ? ssrfFetchWrap({ fetch }) : fetch, + }), + + /** + * Since we will be fetching user owned data, we need to make sure that an + * attacker cannot force us to download a large amounts of data. + */ + fetchMaxSizeProcessor(responseMaxSize), + ) diff --git a/packages/internal/fetch-node/src/ssrf.ts b/packages/internal/fetch-node/src/ssrf.ts new file mode 100644 index 00000000000..0f170121b17 --- /dev/null +++ b/packages/internal/fetch-node/src/ssrf.ts @@ -0,0 +1,189 @@ +import dns, { LookupAddress } from 'node:dns' +import { LookupFunction } from 'node:net' + +import { Fetch, FetchError } from '@atproto-labs/fetch' +import ipaddr from 'ipaddr.js' +import { Agent } from 'undici' + +const { IPv4, IPv6 } = ipaddr + +const [NODE_VERSION] = process.versions.node.split('.').map(Number) + +export type SsrfSafeFetchWrapOptions = NonNullable< + Parameters[0] +> +export const ssrfAgent = new Agent({ connect: { lookup } }) + +/** + * @see {@link https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/} + */ +export const ssrfFetchWrap = ({ + allowCustomPort = false, + fetch = globalThis.fetch as ( + this: void | null | typeof globalThis, + input: Request, + init?: RequestInit, + ) => Promise, +} = {}): Fetch => { + const ssrfSafeFetch: Fetch = async (request) => { + const { protocol, hostname, port } = new URL(request.url) + + if (protocol === 'data:') { + // No SSRF issue + return fetch(request) + } + + if (protocol === 'http:' || protocol === 'https:') { + // @ts-expect-error non-standard option + if (request.dispatcher) { + throw new FetchError( + 500, + 'SSRF protection cannot be used with a custom request dispatcher', + { request }, + ) + } + + // Check port (OWASP) + if (!allowCustomPort && port !== '') { + throw new FetchError( + 400, + 'Request port must be omitted or standard when SSRF is enabled', + { request }, + ) + } + + // Disable HTTP redirections (OWASP) + if (request.redirect === 'follow') { + throw new FetchError( + 500, + 'Request redirect must be "error" or "manual" when SSRF is enabled', + { request }, + ) + } + + // If the hostname is an IP address, it must be a unicast address. + const ip = parseIpHostname(hostname) + if (ip) { + if (ip.range() !== 'unicast') { + throw new FetchError(400, 'Hostname resolved to non-unicast address') + } + // No additional check required + return fetch(request) + } + + // Else hostname is a domain name, use DNS lookup to check if it resolves + // to a unicast address + + if (NODE_VERSION < 21) { + // Note: due to the issue nodejs/undici#2828 (fixed in undici >=6.7.0, + // Node >=21), the "dispatcher" property of the request object will not + // be used by fetch(). As a workaround, we pass the dispatcher as second + // argument to fetch() here, and make sure it is used (which might not be + // the case if a custom fetch() function is used). + + if (fetch === globalThis.fetch) { + // If the global fetch function is used, we can pass the dispatcher + // singleton directly to the fetch function as we know it will be + // used. + + // @ts-expect-error non-standard option + return fetch(request, { dispatcher: ssrfAgent }) + } + + let didLookup = false + const dispatcher = new Agent({ + connect: { + lookup(...args) { + didLookup = true + lookup(...args) + }, + }, + }) + + try { + // @ts-expect-error non-standard option + return await fetch(request, { dispatcher }) + } finally { + // Free resources (we cannot await here since the response was not + // consumed yet). + void dispatcher.close().catch((err) => { + // No biggie, but let's still log it + console.warn('Failed to close dispatcher', err) + }) + + if (!didLookup) { + // If you encounter this error, either upgrade to Node.js >=21 or + // make sure that the requestInit object is passed as second + // argument to the global fetch function. + + // eslint-disable-next-line no-unsafe-finally + throw new FetchError(500, 'Unable to enforce SSRF protection', { + request, + }) + } + } + } + + // @ts-expect-error non-standard option + return fetch(new Request(request, { dispatcher: ssrfAgent })) + } + + // blob: about: file: all should be rejected + throw new FetchError(400, `Forbidden protocol "${protocol}"`, { request }) + } + + return ssrfSafeFetch +} + +function parseIpHostname( + hostname: string, +): ipaddr.IPv4 | ipaddr.IPv6 | undefined { + if (IPv4.isIPv4(hostname)) { + return IPv4.parse(hostname) + } + + if (hostname.startsWith('[') && hostname.endsWith(']')) { + return IPv6.parse(hostname.slice(1, -1)) + } + + return undefined +} + +export function lookup( + hostname: string, + options: dns.LookupOptions, + callback: Parameters[2], +) { + dns.lookup(hostname, options, (err, address, family) => { + if (err) { + callback(err, address, family) + } else { + const ips = Array.isArray(address) + ? address.map(parseLookupAddress) + : [parseLookupAddress({ address, family })] + + if (ips.some((ip) => ip.range() !== 'unicast')) { + callback( + new Error('Hostname resolved to non-unicast address'), + address, + family, + ) + } else { + callback(null, address, family) + } + } + }) +} + +function parseLookupAddress({ + address, + family, +}: LookupAddress): ipaddr.IPv4 | ipaddr.IPv6 { + const ip = family === 4 ? IPv4.parse(address) : IPv6.parse(address) + + if (ip instanceof IPv6 && ip.isIPv4MappedAddress()) { + return ip.toIPv4Address() + } else { + return ip + } +} diff --git a/packages/internal/fetch-node/tsconfig.build.json b/packages/internal/fetch-node/tsconfig.build.json new file mode 100644 index 00000000000..ea00aba058e --- /dev/null +++ b/packages/internal/fetch-node/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/node.json"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/internal/fetch-node/tsconfig.json b/packages/internal/fetch-node/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/fetch-node/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/fetch/package.json b/packages/internal/fetch/package.json new file mode 100644 index 00000000000..bdd0e31cc80 --- /dev/null +++ b/packages/internal/fetch/package.json @@ -0,0 +1,36 @@ +{ + "name": "@atproto-labs/fetch", + "version": "0.0.1", + "license": "MIT", + "description": "Isomorphic wrapper utilities for fetch API", + "keywords": [ + "atproto", + "fetch" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/fetch" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/pipe": "workspace:*", + "tslib": "^2.6.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.json" + } +} diff --git a/packages/internal/fetch/src/fetch-error.ts b/packages/internal/fetch/src/fetch-error.ts new file mode 100644 index 00000000000..c50b2f42810 --- /dev/null +++ b/packages/internal/fetch/src/fetch-error.ts @@ -0,0 +1,73 @@ +export type FetchErrorOptions = { + cause?: unknown + request?: Request + response?: Response +} + +export class FetchError extends Error { + public readonly request?: Request + public readonly response?: Response + + constructor( + public readonly statusCode: number, + message?: string, + { cause, request, response }: FetchErrorOptions = {}, + ) { + super(message, { cause }) + this.request = request + this.response = response + } + + static async from(err: unknown) { + const cause = extractCause(err) + const [statusCode, message] = extractInfo(cause) + return new FetchError(statusCode, message, { cause }) + } +} + +export const fetchFailureHandler = async (err: unknown): Promise => { + throw await FetchError.from(err) +} + +function extractCause(err: unknown): unknown { + // Unwrap the Network error from undici (i.e. Node's internal fetch() implementation) + // https://github.com/nodejs/undici/blob/3274c975947ce11a08508743df026f73598bfead/lib/web/fetch/index.js#L223-L228 + if ( + err instanceof TypeError && + err.message === 'fetch failed' && + err.cause instanceof Error + ) { + return err.cause + } + + return err +} + +export function extractInfo( + err: unknown, +): [statusCode: number, message: string] { + if (typeof err === 'string' && err.length > 0) { + return [500, err] + } + + if (!(err instanceof Error)) { + return [500, 'Unable to fetch'] + } + + if ('code' in err && typeof err.code === 'string') { + switch (true) { + case err.code === 'ENOTFOUND': + return [400, 'Invalid hostname'] + case err.code === 'ECONNREFUSED': + return [502, 'Connection refused'] + case err.code === 'DEPTH_ZERO_SELF_SIGNED_CERT': + return [502, 'Self-signed certificate'] + case err.code.startsWith('ERR_TLS'): + return [502, 'TLS error'] + case err.code.startsWith('ECONN'): + return [502, 'Connection error'] + } + } + + return [500, err.message] +} diff --git a/packages/internal/fetch/src/fetch-request.ts b/packages/internal/fetch/src/fetch-request.ts new file mode 100644 index 00000000000..87535cbb6ff --- /dev/null +++ b/packages/internal/fetch/src/fetch-request.ts @@ -0,0 +1,86 @@ +import { Transformer } from '@atproto-labs/pipe' + +import { FetchError } from './fetch-error.js' +import { isIp } from './util.js' + +export type RequestTranformer = Transformer + +export function protocolCheckRequestTransform( + protocols: Iterable, +): RequestTranformer { + const allowedProtocols = new Set(protocols) + + return async (request) => { + const { protocol } = new URL(request.url) + + if (!allowedProtocols.has(protocol)) { + throw new FetchError(400, `${protocol} is not allowed`, { request }) + } + + return request + } +} + +export function requireHostHeaderTranform(): RequestTranformer { + return async (request) => { + // Note that fetch() will automatically add the Host header from the URL and + // discard any Host header manually set in the request. + + const { protocol, hostname } = new URL(request.url) + + // "Host" header only makes sense in the context of an HTTP request + if (protocol !== 'http:' && protocol !== 'https') { + throw new FetchError(400, `Forbidden protocol ${protocol}`, { request }) + } + + if (!hostname || isIp(hostname)) { + throw new FetchError(400, 'Invalid hostname', { request }) + } + + return request + } +} + +export const DEFAULT_FORBIDDEN_DOMAIN_NAMES = [ + 'example.com', + '*.example.com', + 'example.org', + '*.example.org', + 'example.net', + '*.example.net', + 'googleusercontent.com', + '*.googleusercontent.com', +] + +export function forbiddenDomainNameRequestTransform( + denyList: Iterable = DEFAULT_FORBIDDEN_DOMAIN_NAMES, +): RequestTranformer { + const denySet = new Set(denyList) + + // Optimization: if no forbidden domain names are provided, we can skip the + // check entirely. + if (denySet.size === 0) { + return async (request) => request + } + + return async (request) => { + const { hostname } = new URL(request.url) + + // Full domain name check + if (denySet.has(hostname)) { + throw new FetchError(403, 'Forbidden hostname', { request }) + } + + // Sub domain name check + let curDot = hostname.indexOf('.') + while (curDot !== -1) { + const subdomain = hostname.slice(curDot + 1) + if (denySet.has(`*.${subdomain}`)) { + throw new FetchError(403, 'Forbidden hostname', { request }) + } + curDot = hostname.indexOf('.', curDot + 1) + } + + return request + } +} diff --git a/packages/internal/fetch/src/fetch-response.ts b/packages/internal/fetch/src/fetch-response.ts new file mode 100644 index 00000000000..afb550c5898 --- /dev/null +++ b/packages/internal/fetch/src/fetch-response.ts @@ -0,0 +1,322 @@ +import { Transformer, pipe } from '@atproto-labs/pipe' +import { z } from 'zod' + +import { FetchError, FetchErrorOptions } from './fetch-error.js' +import { TransformedResponse } from './transformed-response.js' +import { Json, MaxBytesTransformStream, ifObject, ifString } from './util.js' + +export type ResponseTranformer = Transformer +export type ResponseMessageGetter = Transformer + +export async function peekJson( + response: Response, + maxSize = Infinity, +): Promise { + const type = extractMime(response) + if (type !== 'application/json') return undefined + checkLength(response, maxSize) + + // 1) Clone the request so we can consume the body + const clonedResponse = response.clone() + + // 2) Make sure the request's body is not too large + const limitedResponse = + response.body && maxSize < Infinity + ? new TransformedResponse( + clonedResponse, + new MaxBytesTransformStream(maxSize), + ) + : // Note: some runtimes (e.g. react-native) don't expose a body property + clonedResponse + + // 3) Parse the JSON + return limitedResponse.json() +} + +export function checkLength(response: Response, maxBytes: number) { + if (!(maxBytes >= 0)) { + throw new TypeError('maxBytes must be a non-negative number') + } + const length = extractLength(response) + if (length != null && length > maxBytes) { + throw new FetchResponseError(response, 502, 'Response too large') + } + return length +} + +export function extractLength(response: Response) { + const contentLength = response.headers.get('Content-Length') + if (contentLength == null) return undefined + if (!/^\d+$/.test(contentLength)) { + throw new FetchResponseError(response, 502, 'Invalid Content-Length') + } + const length = Number(contentLength) + if (!Number.isSafeInteger(length)) { + throw new FetchResponseError(response, 502, 'Content-Length too large') + } + return length +} + +export function extractMime(response: Response) { + const contentType = response.headers.get('Content-Type') + if (contentType == null) return undefined + + return contentType.split(';', 1)[0]!.trim() +} + +const extractResponseMessage: ResponseMessageGetter = async (response) => { + const mimeType = extractMime(response) + if (!mimeType) return undefined + + try { + if (mimeType === 'text/plain') { + return await response.text() + } else if (/^application\/(?:[^+]+\+)?json$/i.test(mimeType)) { + const json = await response.json() + + if (typeof json === 'string') return json + + const errorDescription = ifString(ifObject(json)?.['error_description']) + if (errorDescription) return errorDescription + + const error = ifString(ifObject(json)?.['error']) + if (error) return error + + const message = ifString(ifObject(json)?.['message']) + if (message) return message + } + } catch { + // noop + } + + return undefined +} + +export class FetchResponseError extends FetchError { + constructor( + response: Response, + statusCode: number = response.status, + message: string = response.statusText, + options?: Omit, + ) { + super(statusCode, message, { response, ...options }) + } + + static async from( + response: Response, + statusCode = response.status, + customMessage: string | ResponseMessageGetter = extractResponseMessage, + options?: Omit, + ) { + const message = + typeof customMessage === 'string' + ? customMessage + : typeof customMessage === 'function' + ? await customMessage(response) + : undefined + + return new FetchResponseError(response, statusCode, message, options) + } +} + +export function logCancellationError(err: unknown): void { + console.warn('Failed to cancel response body', err) +} + +/** + * If the transformer results in an error, ensure that the response body is + * consumed as, in some environments (Node 👀), the response will not + * automatically be GC'd. + * + * @see {@link https://undici.nodejs.org/#/?id=garbage-collection} + * @param [onCancellationError] - Callback to handle any async body cancelling + * error. Defaults to logging the error. Do not use `null` if the request is + * cloned. + */ +export function cancelBodyOnError( + transformer: Transformer, + onCancellationError: null | ((err: unknown) => void) = logCancellationError, +): (response: Response) => Promise { + return async (response) => { + try { + return await transformer(response) + } catch (err) { + await cancelBody(response, onCancellationError ?? undefined) + throw err + } + } +} + +/** + * @param [onCancellationError] - Callback that will trigger to asynchronously + * handle any error that occurs while cancelling the response body. Providing + * this will speed up the process and avoid potential deadlocks. Defaults to + * awaiting the cancellation operation. + * @see {@link https://undici.nodejs.org/#/?id=garbage-collection} + * @note awaiting this function's result, when no `onCancellationError` is + * provided, might result in a dead lock. Indeed, if the response was cloned(), + * the response.body.cancel() method will not resolve until the other response's + * body is consumed/cancelled. + * + * @example + * ```ts + * // Make sure response was not cloned, or that every cloned response was + * // consumed/cancelled before awaiting this function's result. + * await cancelBody(response) + * ``` + * @example + * ```ts + * await cancelBody(response, (err) => { + * // No biggie, let's just log the error + * console.warn('Failed to cancel response body', err) + * }) + * ``` + * @example + * ```ts + * // Will generate an "unhandledRejection" if an error occurs while cancelling + * // the response body. This will likely crash the process. + * await cancelBody(response, (err) => { throw err }) + * ``` + */ +export async function cancelBody( + input: Response | Request, + onCancellationError?: (err: unknown) => void, +): Promise { + if ( + input.body && + !input.bodyUsed && + !input.body.locked && + // Support for alternative fetch implementations + typeof input.body.cancel === 'function' + ) { + if (typeof onCancellationError === 'function') { + void input.body.cancel().catch(onCancellationError) + } else { + await input.body.cancel() + } + } +} + +export function fetchOkProcessor( + customMessage?: string | ResponseMessageGetter, +): ResponseTranformer { + return cancelBodyOnError((response) => { + return fetchOkTransformer(response, customMessage) + }) +} + +export async function fetchOkTransformer( + response: Response, + customMessage?: string | ResponseMessageGetter, +) { + if (response.ok) return response + throw await FetchResponseError.from(response, undefined, customMessage) +} + +export function fetchMaxSizeProcessor(maxBytes: number): ResponseTranformer { + if (maxBytes === Infinity) return (response) => response + if (!Number.isFinite(maxBytes) || maxBytes < 0) { + throw new TypeError('maxBytes must be a 0, Infinity or a positive number') + } + return cancelBodyOnError((response) => { + return fetchResponseMaxSizeChecker(response, maxBytes) + }) +} + +export function fetchResponseMaxSizeChecker( + response: Response, + maxBytes: number, +): Response { + if (maxBytes === Infinity) return response + checkLength(response, maxBytes) + + // Some engines (react-native 👀) don't expose a body property. In that case, + // we will only rely on the Content-Length header. + if (!response.body) return response + + const transform = new MaxBytesTransformStream(maxBytes) + return new TransformedResponse(response, transform) +} + +export type MimeTypeCheckFn = (mimeType: string) => boolean +export type MimeTypeCheck = string | RegExp | MimeTypeCheckFn + +export function fetchTypeProcessor( + expectedMime: MimeTypeCheck, + contentTypeRequired = true, +): ResponseTranformer { + const isExpected: MimeTypeCheckFn = + typeof expectedMime === 'string' + ? (mimeType) => mimeType === expectedMime + : expectedMime instanceof RegExp + ? (mimeType) => expectedMime.test(mimeType) + : expectedMime + + return cancelBodyOnError((response) => { + return fetchResponseTypeChecker(response, isExpected, contentTypeRequired) + }) +} + +export async function fetchResponseTypeChecker( + response: Response, + isExpectedMime: MimeTypeCheckFn, + contentTypeRequired = true, +): Promise { + const mimeType = extractMime(response) + if (mimeType) { + if (!isExpectedMime(mimeType)) { + throw await FetchResponseError.from( + response, + 502, + `Unexpected response Content-Type (${mimeType})`, + ) + } + } else if (contentTypeRequired) { + throw await FetchResponseError.from( + response, + 502, + 'Missing response Content-Type header', + ) + } + + return response +} + +export type ParsedJsonResponse = { + response: Response + json: T +} + +export async function fetchResponseJsonTranformer( + response: Response, +): Promise> { + try { + const json = (await response.json()) as T + return { response, json } + } catch (cause) { + throw new FetchResponseError( + response, + 502, + 'Unable to parse response as JSON', + { cause }, + ) + } +} + +export function fetchJsonProcessor( + expectedMime: MimeTypeCheck = /^application\/(?:[^+]+\+)?json$/, + contentTypeRequired = true, +): Transformer> { + return pipe( + fetchTypeProcessor(expectedMime, contentTypeRequired), + cancelBodyOnError(fetchResponseJsonTranformer), + ) +} + +export function fetchJsonZodProcessor( + schema: S, + params?: Partial, +): Transformer> { + return async (jsonResponse: ParsedJsonResponse): Promise> => + schema.parseAsync(jsonResponse.json, params) +} diff --git a/packages/internal/fetch/src/fetch-wrap.ts b/packages/internal/fetch/src/fetch-wrap.ts new file mode 100644 index 00000000000..3680d25f24b --- /dev/null +++ b/packages/internal/fetch/src/fetch-wrap.ts @@ -0,0 +1,102 @@ +import { Fetch } from './fetch.js' +import { TransformedResponse } from './transformed-response.js' + +export const loggedFetchWrap = ({ + fetch = globalThis.fetch as Fetch, +} = {}): Fetch => { + return async function (request) { + return fetchLog.call(this, request, fetch) + } +} + +async function fetchLog( + this: ThisParameterType, + request: Request, + fetch: Fetch = globalThis.fetch, +) { + console.info( + `> ${request.method} ${request.url}\n` + + stringifyPayload(request.headers, await request.clone().text()), + ) + + try { + const response = await fetch(request) + + console.info( + `< HTTP/1.1 ${response.status} ${response.statusText}\n` + + stringifyPayload(response.headers, await response.clone().text()), + ) + + return response + } catch (error) { + console.error(`< Error:`, error) + + throw error + } +} + +const stringifyPayload = (headers: Headers, body: string) => + [stringifyHeaders(headers), stringifyBody(body)] + .filter(Boolean) + .join('\n ') + '\n ' + +const stringifyHeaders = (headers: Headers) => + Array.from(headers) + .map(([name, value]) => ` ${name}: ${value}\n`) + .join('') + +const stringifyBody = (body: string) => + body ? `\n ${body.replace(/\r?\n/g, '\\n')}` : '' + +export const timeoutFetchWrap = ({ + fetch = globalThis.fetch as Fetch, + timeout = 60e3, +} = {}): Fetch => { + if (timeout === Infinity) return fetch + if (!Number.isFinite(timeout) || timeout <= 0) { + throw new TypeError('Timeout must be positive') + } + return async function (request) { + return fetchTimeout.call(this, request, timeout, fetch) + } +} + +export async function fetchTimeout( + this: ThisParameterType, + request: Request, + timeout = 30e3, + fetch: Fetch = globalThis.fetch, +): Promise { + if (timeout === Infinity) return fetch(request) + if (!Number.isFinite(timeout) || timeout <= 0) { + throw new TypeError('Timeout must be positive') + } + + const controller = new AbortController() + const signal = controller.signal + + const abort = () => { + controller.abort() + } + const cleanup = () => { + clearTimeout(timer) + request.signal?.removeEventListener('abort', abort) + } + + const timer = setTimeout(abort, timeout) + if (typeof timer === 'object') timer.unref?.() // only on node + request.signal?.addEventListener('abort', abort) + + signal.addEventListener('abort', cleanup) + + const response = await fetch(new Request(request, { signal })) + + if (!response.body) { + cleanup() + return response + } else { + // Cleanup the timer & event listeners when the body stream is closed + const transform = new TransformStream({ flush: cleanup }) + return new TransformedResponse(response, transform) + } +} diff --git a/packages/internal/fetch/src/fetch.ts b/packages/internal/fetch/src/fetch.ts new file mode 100644 index 00000000000..204a20ccdda --- /dev/null +++ b/packages/internal/fetch/src/fetch.ts @@ -0,0 +1,4 @@ +export type Fetch = ( + this: void | null | typeof globalThis, + input: Request, +) => Promise diff --git a/packages/internal/fetch/src/index.ts b/packages/internal/fetch/src/index.ts new file mode 100644 index 00000000000..d8fa808f3ec --- /dev/null +++ b/packages/internal/fetch/src/index.ts @@ -0,0 +1,6 @@ +export * from './fetch-error.js' +export * from './fetch-request.js' +export * from './fetch-response.js' +export * from './fetch-wrap.js' +export * from './fetch.js' +export * from './util.js' diff --git a/packages/internal/fetch/src/transformed-response.ts b/packages/internal/fetch/src/transformed-response.ts new file mode 100644 index 00000000000..f0d11389bec --- /dev/null +++ b/packages/internal/fetch/src/transformed-response.ts @@ -0,0 +1,36 @@ +export class TransformedResponse extends Response { + #response: Response + + constructor(response: Response, transform: TransformStream) { + if (!response.body) { + throw new TypeError('Response body is not available') + } + if (response.bodyUsed) { + throw new TypeError('Response body is already used') + } + + super(response.body.pipeThrough(transform), { + status: response.status, + statusText: response.statusText, + headers: response.headers, + }) + + this.#response = response + } + + /** + * Some props can't be set through ResponseInit, so we need to proxy them + */ + get url() { + return this.#response.url + } + get redirected() { + return this.#response.redirected + } + get type() { + return this.#response.type + } + get statusText() { + return this.#response.statusText + } +} diff --git a/packages/internal/fetch/src/util.ts b/packages/internal/fetch/src/util.ts new file mode 100644 index 00000000000..ccd9eee42b7 --- /dev/null +++ b/packages/internal/fetch/src/util.ts @@ -0,0 +1,85 @@ +// TODO: Move to a shared package ? + +export type JsonScalar = string | number | boolean | null +export type Json = JsonScalar | Json[] | { [key: string]: undefined | Json } +export type JsonObject = { [key: string]: Json } +export type JsonArray = Json[] + +export function isIp(hostname: string) { + // IPv4 + if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/)) return true + + // IPv6 + if (hostname.startsWith('[') && hostname.endsWith(']')) return true + + return false +} + +// TODO: Move to a shared package ? + +const plainObjectProto = Object.prototype +export const ifObject = (v?: V) => { + if (typeof v === 'object' && v != null && !Array.isArray(v)) { + const proto = Object.getPrototypeOf(v) + if (proto === null || proto === plainObjectProto) { + // eslint-disable-next-line @typescript-eslint/ban-types + return v as V extends JsonScalar | JsonArray | Function | symbol + ? never + : V extends Json + ? V + : // Plain object are (mostly) safe to access as Json + { [key: string]: unknown } + } + } + + return undefined +} + +export const ifArray = (v?: V) => (Array.isArray(v) ? v : undefined) +export const ifScalar = (v?: V) => { + switch (typeof v) { + case 'string': + case 'number': + case 'boolean': + return v + default: + if (v === null) return null as V & null + return undefined as V extends JsonScalar ? never : undefined + } +} +export const ifBoolean = (v?: V) => (typeof v === 'boolean' ? v : undefined) +export const ifString = (v?: V) => (typeof v === 'string' ? v : undefined) +export const ifNumber = (v?: V) => (typeof v === 'number' ? v : undefined) +export const ifNull = (v?: V) => (v === null ? v : undefined) + +export const noop = () => {} + +export function thrower(err: unknown) { + throw err +} + +export class MaxBytesTransformStream extends TransformStream< + Uint8Array, + Uint8Array +> { + constructor(maxBytes: number) { + if (!(maxBytes >= 0)) { + throw new TypeError('maxBytes must be a non-negative number') + } + + let bytesRead = 0 + + super({ + transform: ( + chunk: Uint8Array, + ctrl: TransformStreamDefaultController, + ) => { + if ((bytesRead += chunk.length) <= maxBytes) { + ctrl.enqueue(chunk) + } else { + ctrl.error(new Error('Response too large')) + } + }, + }) + } +} diff --git a/packages/internal/fetch/tsconfig.build.json b/packages/internal/fetch/tsconfig.build.json new file mode 100644 index 00000000000..9f3c5252d4c --- /dev/null +++ b/packages/internal/fetch/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/isomorphic.json"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/internal/fetch/tsconfig.json b/packages/internal/fetch/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/fetch/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/handle-resolver-node/package.json b/packages/internal/handle-resolver-node/package.json new file mode 100644 index 00000000000..cb76b7a3589 --- /dev/null +++ b/packages/internal/handle-resolver-node/package.json @@ -0,0 +1,41 @@ +{ + "name": "@atproto-labs/handle-resolver-node", + "version": "0.0.1", + "license": "MIT", + "description": "Node specific ATProto handle to DID resolver", + "keywords": [ + "atproto", + "oauth", + "handle", + "identity", + "node" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/handle-resolver-node" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/fetch-node": "workspace:*", + "@atproto-labs/handle-resolver": "workspace:*", + "@atproto/did": "workspace:*", + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/internal/handle-resolver-node/src/dns-handle-resolver.ts b/packages/internal/handle-resolver-node/src/dns-handle-resolver.ts new file mode 100644 index 00000000000..005e3f0748b --- /dev/null +++ b/packages/internal/handle-resolver-node/src/dns-handle-resolver.ts @@ -0,0 +1,76 @@ +import { Resolver, lookup, resolveTxt } from 'node:dns/promises' + +import { + HandleResolveOptions, + HandleResolver, + ResolvedHandle, + isResolvedHandle, +} from '@atproto-labs/handle-resolver' + +const SUBDOMAIN = '_atproto' +const PREFIX = 'did=' + +export type DnsHandleResolverOptions = { + /** + * Nameservers to use in place of the system's default nameservers. + */ + nameservers?: string[] +} + +export class DnsHandleResolver implements HandleResolver { + protected resolveTxt: typeof resolveTxt + + constructor(options?: DnsHandleResolverOptions) { + this.resolveTxt = options?.nameservers + ? buildResolveTxt(options.nameservers) + : resolveTxt.bind(null) + } + + public async resolve( + handle: string, + _options?: HandleResolveOptions, + ): Promise { + try { + return parseDnsResult(await this.resolveTxt(`${SUBDOMAIN}.${handle}`)) + } catch (err) { + return null + } + } +} + +function buildResolveTxt(nameservers: string[]): typeof resolveTxt { + // Build the resolver asynchronously + const resolverPromise: Promise = Promise.allSettled( + nameservers.map((h) => lookup(h)), + ) + .then((responses) => + responses.flatMap((r) => + r.status === 'fulfilled' ? [r.value.address] : [], + ), + ) + .then((backupIps) => { + if (!backupIps.length) return null + const resolver = new Resolver() + resolver.setServers(backupIps) + return resolver + }) + + resolverPromise.catch(() => { + // Should never happen + }) + + return async (hostname) => { + const resolver = await resolverPromise + return resolver ? resolver.resolveTxt(hostname) : [] + } +} + +function parseDnsResult(chunkedResults: string[][]): ResolvedHandle { + const results = chunkedResults.map((chunks) => chunks.join('')) + const found = results.filter((i) => i.startsWith(PREFIX)) + if (found.length !== 1) { + return null + } + const did = found[0].slice(PREFIX.length) + return isResolvedHandle(did) ? did : null +} diff --git a/packages/internal/handle-resolver-node/src/index.ts b/packages/internal/handle-resolver-node/src/index.ts new file mode 100644 index 00000000000..2403167966a --- /dev/null +++ b/packages/internal/handle-resolver-node/src/index.ts @@ -0,0 +1,6 @@ +// Main export +export * from './node-handle-resolver.js' +export { NodeHandleResolver as default } from './node-handle-resolver.js' + +// Utilities +export * from './dns-handle-resolver.js' diff --git a/packages/internal/handle-resolver-node/src/node-handle-resolver.ts b/packages/internal/handle-resolver-node/src/node-handle-resolver.ts new file mode 100644 index 00000000000..2c2cc0957bb --- /dev/null +++ b/packages/internal/handle-resolver-node/src/node-handle-resolver.ts @@ -0,0 +1,96 @@ +import { Fetch } from '@atproto-labs/fetch' +import { SafeFetchWrapOptions, safeFetchWrap } from '@atproto-labs/fetch-node' +import { + CachedHandleResolver, + CachedHandleResolverOptions, + HandleResolver, + WellKnownHandleResolver, +} from '@atproto-labs/handle-resolver' + +import { + DnsHandleResolver, + DnsHandleResolverOptions, +} from './dns-handle-resolver.js' + +export type NodeHandleResolverOptions = { + /** + * List of domain names that are forbidden to be resolved using the + * well-known/atproto-did method. + */ + wellKnownExclude?: SafeFetchWrapOptions['forbiddenDomainNames'] + + /** + * List of backup nameservers to use for DNS resolution. + */ + fallbackNameservers?: DnsHandleResolverOptions['nameservers'] + + cache?: CachedHandleResolverOptions['cache'] + + /** + * Fetch function to use for HTTP requests. Allows customizing the request + * behavior, e.g. adding headers, setting a timeout, mocking, etc. The + * provided fetch function will be wrapped with a safeFetchWrap function that + * adds SSRF protection. + * + * @default `globalThis.fetch` + */ + fetch?: Fetch +} + +export class NodeHandleResolver + extends CachedHandleResolver + implements HandleResolver +{ + constructor({ + cache, + fetch = globalThis.fetch, + fallbackNameservers, + wellKnownExclude, + }: NodeHandleResolverOptions = {}) { + const safeFetch = safeFetchWrap({ + fetch, + timeout: 3000, // 3 seconds + forbiddenDomainNames: wellKnownExclude, + ssrfProtection: true, + responseMaxSize: 10 * 1048, // DID are max 2048 characters, 10kb for safety + }) + + const httpResolver = new WellKnownHandleResolver({ + fetch: safeFetch, + }) + + const dnsResolver = new DnsHandleResolver() + + const fallbackResolver = new DnsHandleResolver({ + nameservers: fallbackNameservers, + }) + + super({ + cache, + resolver: { + resolve: async (handle) => { + const abortController = new AbortController() + + const dnsPromise = dnsResolver.resolve(handle) + const httpPromise = httpResolver.resolve(handle, { + signal: abortController.signal, + }) + + // Will be awaited later + httpPromise.catch(() => {}) + + const dnsRes = await dnsPromise + if (dnsRes) { + abortController.abort() + return dnsRes + } + + const res = await httpPromise + if (res) return res + + return fallbackResolver.resolve(handle) + }, + }, + }) + } +} diff --git a/packages/internal/handle-resolver-node/tsconfig.build.json b/packages/internal/handle-resolver-node/tsconfig.build.json new file mode 100644 index 00000000000..b60d50d0927 --- /dev/null +++ b/packages/internal/handle-resolver-node/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/node.json"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/internal/handle-resolver-node/tsconfig.json b/packages/internal/handle-resolver-node/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/handle-resolver-node/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/handle-resolver/package.json b/packages/internal/handle-resolver/package.json new file mode 100644 index 00000000000..1a83c0f48a8 --- /dev/null +++ b/packages/internal/handle-resolver/package.json @@ -0,0 +1,44 @@ +{ + "name": "@atproto-labs/handle-resolver", + "version": "0.0.1", + "license": "MIT", + "description": "Isomorphic ATProto handle to DID resolver", + "keywords": [ + "atproto", + "oauth", + "handle", + "identity", + "browser", + "node", + "isomorphic" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/handle-resolver" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/simple-store": "workspace:*", + "@atproto-labs/simple-store-memory": "workspace:*", + "@atproto/did": "workspace:*", + "tslib": "^2.6.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/internal/handle-resolver/src/atproto-lexicon-handle-resolver.ts b/packages/internal/handle-resolver/src/atproto-lexicon-handle-resolver.ts new file mode 100644 index 00000000000..2ef1418b43d --- /dev/null +++ b/packages/internal/handle-resolver/src/atproto-lexicon-handle-resolver.ts @@ -0,0 +1,88 @@ +import { Fetch } from '@atproto-labs/fetch' +import z from 'zod' + +import { + HandleResolveOptions, + HandleResolver, + ResolvedHandle, + isResolvedHandle, +} from './handle-resolver.js' + +export const xrpcErrorSchema = z.object({ + error: z.string(), + message: z.string().optional(), +}) + +export type AtprotoLexiconHandleResolverOptions = { + /** + * Fetch function to use for HTTP requests. Allows customizing the request + * behavior, e.g. adding headers, setting a timeout, mocking, etc. + * + * @default globalThis.fetch + */ + fetch?: Fetch + + /** + * URL of the atproto lexicon server. This is the base URL where the + * `com.atproto.identity.resolveHandle` XRPC method is located. + * + * @default 'https://bsky.social' + */ + url?: URL | string +} + +export class AtprotoLexiconHandleResolver implements HandleResolver { + protected readonly url: URL + protected readonly fetch: Fetch + + constructor({ + url = 'https://bsky.social/', + fetch = globalThis.fetch, + }: AtprotoLexiconHandleResolverOptions = {}) { + this.url = new URL(url) + this.fetch = fetch + } + + public async resolve( + handle: string, + options?: HandleResolveOptions, + ): Promise { + const url = new URL('/xrpc/com.atproto.identity.resolveHandle', this.url) + url.searchParams.set('handle', handle) + + const headers = new Headers() + if (options?.noCache) headers.set('cache-control', 'no-cache') + + const request = new Request(url, { headers, signal: options?.signal }) + + const response = await this.fetch.call(null, request) + const payload = await response.json() + + // The response should either be + // - 400 Bad Request with { error: 'InvalidRequest', message: 'Unable to resolve handle' } + // - 200 OK with { did: NonNullable } + // Any other response is considered unexpected behavior an should throw an error. + + if (response.status === 400) { + const data = xrpcErrorSchema.parse(payload) + if ( + data.error === 'InvalidRequest' && + data.message === 'Unable to resolve handle' + ) { + return null + } + } + + if (!response.ok) { + throw new Error('Invalid response from resolveHandle method') + } + + const value: unknown = payload?.did + + if (!value || !isResolvedHandle(value)) { + throw new Error('Invalid DID returned from resolveHandle method') + } + + return value + } +} diff --git a/packages/internal/handle-resolver/src/cached-handle-resolver.ts b/packages/internal/handle-resolver/src/cached-handle-resolver.ts new file mode 100644 index 00000000000..e076b404563 --- /dev/null +++ b/packages/internal/handle-resolver/src/cached-handle-resolver.ts @@ -0,0 +1,41 @@ +import { CachedGetter, SimpleStore } from '@atproto-labs/simple-store' +import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory' +import { + HandleResolveOptions, + HandleResolver, + ResolvedHandle, +} from './handle-resolver.js' + +export type CachedHandleResolverOptions = { + /** + * The resolver that will be used to resolve handles. + */ + resolver: HandleResolver + + /** + * A store that will be used to cache resolved values. + */ + cache?: SimpleStore +} + +export class CachedHandleResolver + extends CachedGetter + implements HandleResolver +{ + constructor({ + resolver, + cache = new SimpleStoreMemory({ + max: 1000, + ttl: 10 * 60e3, + }), + }: CachedHandleResolverOptions) { + super((handle, options) => resolver.resolve(handle, options), cache) + } + + async resolve( + handle: string, + options?: HandleResolveOptions, + ): Promise { + return this.get(handle, options) + } +} diff --git a/packages/internal/handle-resolver/src/handle-resolver.ts b/packages/internal/handle-resolver/src/handle-resolver.ts new file mode 100644 index 00000000000..dfa05c9c117 --- /dev/null +++ b/packages/internal/handle-resolver/src/handle-resolver.ts @@ -0,0 +1,27 @@ +import { Did, isDid } from '@atproto/did' + +export type HandleResolveOptions = { + signal?: AbortSignal + noCache?: boolean +} +export type ResolvedHandle = null | Did + +export function isResolvedHandle( + value: T, +): value is T & ResolvedHandle { + return value === null || (typeof value === 'string' && isDid(value)) +} + +export interface HandleResolver { + /** + * @returns the DID that corresponds to the given handle, or `null` if no DID + * is found. `null` should only be returned if no unexpected behavior occurred + * during the resolution process. + * @throws Error if the resolution method fails due to an unexpected error, or + * if the resolution is aborted ({@link HandleResolveOptions.signal}). + */ + resolve( + handle: string, + options?: HandleResolveOptions, + ): Promise +} diff --git a/packages/internal/handle-resolver/src/index.ts b/packages/internal/handle-resolver/src/index.ts new file mode 100644 index 00000000000..ffd297b5063 --- /dev/null +++ b/packages/internal/handle-resolver/src/index.ts @@ -0,0 +1,11 @@ +export * from './handle-resolver.js' + +// Main export +export * from './universal-handle-resolver.js' +export { UniversalHandleResolver as default } from './universal-handle-resolver.js' + +// Utilities +export * from './cached-handle-resolver.js' +export * from './atproto-lexicon-handle-resolver.js' +export * from './serial-handle-resolver.js' +export * from './well-known-handler-resolver.js' diff --git a/packages/internal/handle-resolver/src/serial-handle-resolver.ts b/packages/internal/handle-resolver/src/serial-handle-resolver.ts new file mode 100644 index 00000000000..2a2bf6a5077 --- /dev/null +++ b/packages/internal/handle-resolver/src/serial-handle-resolver.ts @@ -0,0 +1,29 @@ +import { + HandleResolveOptions, + HandleResolver, + ResolvedHandle, +} from './handle-resolver.js' + +export class SerialHandleResolver implements HandleResolver { + constructor(protected readonly resolvers: readonly HandleResolver[]) { + if (!resolvers.length) { + throw new TypeError('At least one resolver is required') + } + } + + public async resolve( + handle: string, + options?: HandleResolveOptions, + ): Promise { + for (const resolver of this.resolvers) { + options?.signal?.throwIfAborted() + + const value = await resolver.resolve(handle, options) + if (value) return value + } + + // If no resolver was able to resolve the handle, assume there is no DID + // corresponding to the handle. + return null + } +} diff --git a/packages/internal/handle-resolver/src/universal-handle-resolver.ts b/packages/internal/handle-resolver/src/universal-handle-resolver.ts new file mode 100644 index 00000000000..4a5e27ef726 --- /dev/null +++ b/packages/internal/handle-resolver/src/universal-handle-resolver.ts @@ -0,0 +1,58 @@ +import { SimpleStore } from '@atproto-labs/simple-store' +import { Fetch } from '@atproto-labs/fetch' + +import { CachedHandleResolver } from './cached-handle-resolver.js' +import { HandleResolver, ResolvedHandle } from './handle-resolver.js' +import { + AtprotoLexiconHandleResolver, + AtprotoLexiconHandleResolverOptions, +} from './atproto-lexicon-handle-resolver.js' +import { SerialHandleResolver } from './serial-handle-resolver.js' +import { WellKnownHandleResolver } from './well-known-handler-resolver.js' + +export type HandleResolverCache = SimpleStore + +export type UniversalHandleResolverOptions = { + cache?: HandleResolverCache + + /** + * Fetch function to use for HTTP requests. Allows customizing the request + * behavior, e.g. adding headers, setting a timeout, mocking, etc. + * + * When using this library from a Node.js environment, you may want to use + * `safeFetchWrap()` from `@atproto-labs/fetch-node` to add SSRF protection. + * + * @default `globalThis.fetch` + */ + fetch?: Fetch + + /** + * @see {@link AtprotoLexiconHandleResolverOptions.url} + */ + atprotoLexiconUrl?: AtprotoLexiconHandleResolverOptions['url'] +} + +/** + * A handle resolver that works in any environment that supports `fetch()`. This + * relies on the a public XRPC implementing "com.atproto.identity.resolveHandle" + * to resolve handles. + */ +export class UniversalHandleResolver + extends CachedHandleResolver + implements HandleResolver +{ + constructor({ + fetch = globalThis.fetch, + cache, + atprotoLexiconUrl, + }: UniversalHandleResolverOptions = {}) { + const resolver = new SerialHandleResolver([ + // Try the well-known method first, allowing to reduce the load on the + // XRPC. + new WellKnownHandleResolver({ fetch }), + new AtprotoLexiconHandleResolver({ fetch, url: atprotoLexiconUrl }), + ]) + + super({ resolver, cache }) + } +} diff --git a/packages/internal/handle-resolver/src/well-known-handler-resolver.ts b/packages/internal/handle-resolver/src/well-known-handler-resolver.ts new file mode 100644 index 00000000000..65452f808b7 --- /dev/null +++ b/packages/internal/handle-resolver/src/well-known-handler-resolver.ts @@ -0,0 +1,51 @@ +import { Fetch } from '@atproto-labs/fetch' + +import { + HandleResolveOptions, + HandleResolver, + ResolvedHandle, + isResolvedHandle, +} from './handle-resolver.js' + +export type WellKnownHandleResolverOptions = { + fetch?: Fetch +} + +export class WellKnownHandleResolver implements HandleResolver { + protected readonly fetch: Fetch + + constructor({ + fetch = globalThis.fetch, + }: WellKnownHandleResolverOptions = {}) { + this.fetch = fetch + } + + public async resolve( + handle: string, + options?: HandleResolveOptions, + ): Promise { + const url = new URL('/.well-known/atproto-did', `https://${handle}`) + + const headers = new Headers() + if (options?.noCache) headers.set('cache-control', 'no-cache') + const request = new Request(url, { headers, signal: options?.signal }) + + try { + const response = await (0, this.fetch)(request) + const text = await response.text() + const firstLine = text.split('\n')[0]!.trim() + + if (isResolvedHandle(firstLine)) return firstLine + + return null + } catch (err) { + // The the request failed, assume the handle does not resolve to a DID, + // unless the failure was due to the signal being aborted. + options?.signal?.throwIfAborted() + + // TODO: propagate some errors as-is (?) + + return null + } + } +} diff --git a/packages/internal/handle-resolver/tsconfig.build.json b/packages/internal/handle-resolver/tsconfig.build.json new file mode 100644 index 00000000000..2ef4f334355 --- /dev/null +++ b/packages/internal/handle-resolver/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig/isomorphic.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/internal/handle-resolver/tsconfig.json b/packages/internal/handle-resolver/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/handle-resolver/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/identity-resolver/package.json b/packages/internal/identity-resolver/package.json new file mode 100644 index 00000000000..a16940fcbaf --- /dev/null +++ b/packages/internal/identity-resolver/package.json @@ -0,0 +1,41 @@ +{ + "name": "@atproto-labs/identity-resolver", + "version": "0.0.1", + "license": "MIT", + "description": "A library resolving ATPROTO identities", + "keywords": [ + "atproto", + "identity", + "isomorphic", + "resolver" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/identity-resolver" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/did-resolver": "workspace:*", + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/handle-resolver": "workspace:*", + "@atproto/did": "workspace:*", + "@atproto/syntax": "workspace:*", + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.json" + } +} diff --git a/packages/internal/identity-resolver/src/identity-resolver.ts b/packages/internal/identity-resolver/src/identity-resolver.ts new file mode 100644 index 00000000000..8543a396725 --- /dev/null +++ b/packages/internal/identity-resolver/src/identity-resolver.ts @@ -0,0 +1,35 @@ +import { DidResolver } from '@atproto-labs/did-resolver' +import { + HandleResolver, + ResolvedHandle, + isResolvedHandle, +} from '@atproto-labs/handle-resolver' +import { normalizeAndEnsureValidHandle } from '@atproto/syntax' + +export type ResolvedIdentity = { + did: NonNullable + url: URL +} + +export class IdentityResolver { + constructor( + readonly handleResolver: HandleResolver, + readonly didResolver: DidResolver<'plc' | 'web'>, + ) {} + + public async resolve( + input: string, + serviceType = 'AtprotoPersonalDataServer', + ): Promise { + const did = isResolvedHandle(input) + ? input // Already a did + : await this.handleResolver.resolve(normalizeAndEnsureValidHandle(input)) + if (!did) throw new Error(`Handle ${input} does not resolve to a DID`) + + const url = await this.didResolver.resolveServiceEndpoint(did, { + type: serviceType, + }) + + return { did, url } + } +} diff --git a/packages/internal/identity-resolver/src/index.ts b/packages/internal/identity-resolver/src/index.ts new file mode 100644 index 00000000000..57008b96e4d --- /dev/null +++ b/packages/internal/identity-resolver/src/index.ts @@ -0,0 +1,2 @@ +export * from './identity-resolver.js' +export * from './universal-identity-resolver.js' diff --git a/packages/internal/identity-resolver/src/universal-identity-resolver.ts b/packages/internal/identity-resolver/src/universal-identity-resolver.ts new file mode 100644 index 00000000000..2ca3fce744d --- /dev/null +++ b/packages/internal/identity-resolver/src/universal-identity-resolver.ts @@ -0,0 +1,58 @@ +import { + DidCache, + IsomorphicDidResolver, + IsomorphicDidResolverOptions, +} from '@atproto-labs/did-resolver' +import { Fetch } from '@atproto-labs/fetch' +import UniversalHandleResolver, { + HandleResolverCache, + UniversalHandleResolverOptions, +} from '@atproto-labs/handle-resolver' +import { IdentityResolver } from './identity-resolver.js' + +export type { DidDocument } from '@atproto/did' +export type { DidCache } from '@atproto-labs/did-resolver' +export type { + HandleResolverCache, + ResolvedHandle, +} from '@atproto-labs/handle-resolver' + +export type UniversalIdentityResolverOptions = { + fetch?: Fetch + + didCache?: DidCache + handleCache?: HandleResolverCache + + /** + * @see {@link IsomorphicDidResolverOptions.plcDirectoryUrl} + */ + plcDirectoryUrl?: IsomorphicDidResolverOptions['plcDirectoryUrl'] + + /** + * @see {@link UniversalHandleResolverOptions.atprotoLexiconUrl} + */ + atprotoLexiconUrl?: UniversalHandleResolverOptions['atprotoLexiconUrl'] +} + +export class UniversalIdentityResolver extends IdentityResolver { + static from({ + fetch = globalThis.fetch, + didCache, + handleCache, + plcDirectoryUrl, + atprotoLexiconUrl, + }: UniversalIdentityResolverOptions) { + return new this( + new UniversalHandleResolver({ + fetch, + cache: handleCache, + atprotoLexiconUrl, + }), + new IsomorphicDidResolver({ + fetch, // + cache: didCache, + plcDirectoryUrl, + }), + ) + } +} diff --git a/packages/internal/identity-resolver/tsconfig.build.json b/packages/internal/identity-resolver/tsconfig.build.json new file mode 100644 index 00000000000..9f3c5252d4c --- /dev/null +++ b/packages/internal/identity-resolver/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/isomorphic.json"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/internal/identity-resolver/tsconfig.json b/packages/internal/identity-resolver/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/identity-resolver/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/pipe/package.json b/packages/internal/pipe/package.json new file mode 100644 index 00000000000..b2f9c0993ad --- /dev/null +++ b/packages/internal/pipe/package.json @@ -0,0 +1,34 @@ +{ + "name": "@atproto-labs/pipe", + "version": "0.0.1", + "license": "MIT", + "description": "Library for combining multiple functions into a single function.", + "keywords": [ + "atproto", + "transformer" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/pipe" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.json" + } +} diff --git a/packages/internal/pipe/src/index.ts b/packages/internal/pipe/src/index.ts new file mode 100644 index 00000000000..014b49a2af3 --- /dev/null +++ b/packages/internal/pipe/src/index.ts @@ -0,0 +1,60 @@ +export type Transformer = (input: I) => O | PromiseLike + +type FirstPipelineInput[]> = T extends [ + Transformer, + ...any[], +] + ? I + : T extends Transformer[] + ? I + : never + +type LastPipelineOutput[]> = T extends [ + ...any[], + Transformer, +] + ? O + : T extends Transformer[] + ? O + : never + +type Pipeline< + F extends Transformer[], + Acc extends Transformer[] = [], +> = F extends [Transformer] + ? [...Acc, Transformer] + : F extends [Transformer, ...infer Tail] + ? Tail extends [Transformer, ...any[]] + ? Pipeline]> + : Acc + : Acc + +export function pipe(): (v: V) => Promise +export function pipe[]>( + ...pipeline: Pipeline extends T ? T : Pipeline +): (input: FirstPipelineInput) => Promise> +export function pipe[]>( + ...pipeline: Pipeline extends T ? T : Pipeline +): (input: FirstPipelineInput) => Promise> { + const { length, 0: a, 1: b, 2: c, 3: d } = pipeline + switch (length) { + case 0: + throw new TypeError('pipe requires at least one argument') + case 1: + return async (v) => a!(v) + case 2: + return async (v) => b!(await a!(v)) + case 3: + return async (v) => c!(await b!(await a!(v))) + case 4: + return async (v) => d!(await c!(await b!(await a!(v)))) + default: { + return async (v: any) => { + for (let i = 0; i < length; i++) { + v = await pipeline[i]!.call(null, v) + } + return v + } + } + } +} diff --git a/packages/internal/pipe/tsconfig.build.json b/packages/internal/pipe/tsconfig.build.json new file mode 100644 index 00000000000..9f3c5252d4c --- /dev/null +++ b/packages/internal/pipe/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/isomorphic.json"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/internal/pipe/tsconfig.json b/packages/internal/pipe/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/pipe/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/rollup-plugin-bundle-manifest/package.json b/packages/internal/rollup-plugin-bundle-manifest/package.json new file mode 100644 index 00000000000..28f7a9ee26b --- /dev/null +++ b/packages/internal/rollup-plugin-bundle-manifest/package.json @@ -0,0 +1,40 @@ +{ + "name": "@atproto-labs/rollup-plugin-bundle-manifest", + "version": "0.0.1", + "license": "MIT", + "description": "Library for generating a manifest of bundled files from a Rollup build", + "keywords": [ + "atproto", + "rollup", + "manifest" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/rollup-plugin-bundle-manifest" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "mime": "^3.0.0", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "rollup": "^4.0.0" + }, + "devDependencies": { + "rollup": "^4.10.0", + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.json" + } +} diff --git a/packages/internal/rollup-plugin-bundle-manifest/src/index.ts b/packages/internal/rollup-plugin-bundle-manifest/src/index.ts new file mode 100644 index 00000000000..83c4b0266cd --- /dev/null +++ b/packages/internal/rollup-plugin-bundle-manifest/src/index.ts @@ -0,0 +1,76 @@ +import { createHash } from 'node:crypto' +import { extname } from 'node:path' + +import mime from 'mime' +import { Plugin } from 'rollup' + +type AssetItem = { + type: 'asset' + mime?: string + sha256: string + data?: string +} + +type ChunkItem = { + type: 'chunk' + mime: string + sha256: string + dynamicImports: string[] + isDynamicEntry: boolean + isEntry: boolean + isImplicitEntry: boolean + name: string + data?: string +} + +export type ManifestItem = AssetItem | ChunkItem + +export type Manifest = Record + +export default function bundleManifest({ + name = 'bundle-manifest.json', + data = false, +}: { + name?: string + data?: boolean +} = {}): Plugin { + return { + name: 'bundle-manifest', + generateBundle(outputOptions, bundle) { + const manifest: Manifest = {} + + for (const [fileName, chunk] of Object.entries(bundle)) { + if (chunk.type === 'asset') { + manifest[fileName] = { + type: chunk.type, + data: data + ? Buffer.from(chunk.source).toString('base64') + : undefined, + mime: mime.getType(extname(fileName)) || undefined, + sha256: createHash('sha256').update(chunk.source).digest('base64'), + } + } + + if (chunk.type === 'chunk') { + manifest[fileName] = { + type: chunk.type, + data: data ? Buffer.from(chunk.code).toString('base64') : undefined, + mime: 'application/javascript', + sha256: createHash('sha256').update(chunk.code).digest('base64'), + dynamicImports: chunk.dynamicImports, + isDynamicEntry: chunk.isDynamicEntry, + isEntry: chunk.isEntry, + isImplicitEntry: chunk.isImplicitEntry, + name: chunk.name, + } + } + } + + this.emitFile({ + type: 'asset', + fileName: name, + source: JSON.stringify(manifest, null, 2), + }) + }, + } +} diff --git a/packages/internal/rollup-plugin-bundle-manifest/tsconfig.build.json b/packages/internal/rollup-plugin-bundle-manifest/tsconfig.build.json new file mode 100644 index 00000000000..ea00aba058e --- /dev/null +++ b/packages/internal/rollup-plugin-bundle-manifest/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/node.json"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/internal/rollup-plugin-bundle-manifest/tsconfig.json b/packages/internal/rollup-plugin-bundle-manifest/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/rollup-plugin-bundle-manifest/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/simple-store-memory/package.json b/packages/internal/simple-store-memory/package.json new file mode 100644 index 00000000000..41793bb6779 --- /dev/null +++ b/packages/internal/simple-store-memory/package.json @@ -0,0 +1,37 @@ +{ + "name": "@atproto-labs/simple-store-memory", + "version": "0.0.1", + "license": "MIT", + "description": "Memory based simple-store implementation", + "keywords": [ + "cache", + "isomorphic", + "memory" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/simple-store-memory" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/simple-store": "workspace:*", + "lru-cache": "^10.2.0", + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/internal/simple-store-memory/src/index.ts b/packages/internal/simple-store-memory/src/index.ts new file mode 100644 index 00000000000..b2150c969a8 --- /dev/null +++ b/packages/internal/simple-store-memory/src/index.ts @@ -0,0 +1,99 @@ +import { SimpleStore, Key, Value } from '@atproto-labs/simple-store' +import { LRUCache } from 'lru-cache' + +import { roughSizeOfObject } from './util.js' + +export type SimpleStoreMemoryOptions = { + /** + * The maximum number of entries in the cache. + */ + max?: number + + /** + * The time-to-live of a cache entry, in milliseconds. + */ + ttl?: number + + /** + * Whether to automatically prune expired entries. + */ + ttlAutopurge?: boolean + + /** + * The maximum total size of the cache, in units defined by the sizeCalculation + * function. + * + * @default No limit + */ + maxSize?: number + + /** + * The maximum size of a single cache entry, in units defined by the + * sizeCalculation function. + * + * @default No limit + */ + maxEntrySize?: number + + /** + * A function that returns the size of a value. The size is used to determine + * when the cache should be pruned, based on `maxSize`. + * + * @default The (rough) size in bytes used in memory. + */ + sizeCalculation?: (value: V, key: K) => number +} & ( // Memory is not infinite, so at least one pruning option is required. + | { max: number } + | { maxSize: number } + | { ttl: number; ttlAutopurge: boolean } +) + +// LRUCache does not allow storing "null", so we use a symbol to represent it. +const nullSymbol = Symbol('nullItem') +type AsLruValue = V extends null + ? typeof nullSymbol + : Exclude +const toLruValue = (value: V) => + (value === null ? nullSymbol : value) as AsLruValue +const fromLruValue = (value: AsLruValue) => + (value === nullSymbol ? null : value) as V + +export class SimpleStoreMemory + implements SimpleStore +{ + #cache: LRUCache> + + constructor({ sizeCalculation, ...options }: SimpleStoreMemoryOptions) { + this.#cache = new LRUCache>({ + ...options, + allowStale: false, + updateAgeOnGet: false, + updateAgeOnHas: false, + sizeCalculation: sizeCalculation + ? (value, key) => sizeCalculation(fromLruValue(value), key) + : options.maxEntrySize != null || options.maxSize != null + ? // maxEntrySize and maxSize require a size calculation function. + roughSizeOfObject + : undefined, + }) + } + + get(key: K): V | undefined { + const value = this.#cache.get(key) + if (value === undefined) return undefined + + return fromLruValue(value) + } + + set(key: K, value: V): void { + this.#cache.set(key, toLruValue(value)) + } + + del(key: K): void { + this.#cache.delete(key) + } + + clear(): void { + this.#cache.clear() + } +} diff --git a/packages/internal/simple-store-memory/src/util.ts b/packages/internal/simple-store-memory/src/util.ts new file mode 100644 index 00000000000..0fb05fee470 --- /dev/null +++ b/packages/internal/simple-store-memory/src/util.ts @@ -0,0 +1,77 @@ +const knownSizes = new WeakMap() + +/** + * @see {@link https://stackoverflow.com/a/11900218/356537} + */ +export function roughSizeOfObject(value: unknown): number { + const objectList = new Set() + const stack = [value] // This would be more efficient using a circular buffer + let bytes = 0 + + while (stack.length) { + const value = stack.pop() + + // > All objects on the heap start with a shape descriptor, which takes one + // > pointer size (usually 4 bytes these days, thanks to "pointer + // > compression" on 64-bit platforms). + + switch (typeof value) { + // Types are ordered by frequency + case 'string': + // https://stackoverflow.com/a/68791382/356537 + bytes += 12 + 4 * Math.ceil(value.length / 4) + break + case 'number': + bytes += 12 // Shape descriptor + double + break + case 'boolean': + bytes += 4 // Shape descriptor + break + case 'object': + bytes += 4 // Shape descriptor + + if (value === null) { + break + } + + if (knownSizes.has(value)) { + bytes += knownSizes.get(value)! + break + } + + if (objectList.has(value)) continue + objectList.add(value) + + if (Array.isArray(value)) { + bytes += 4 + stack.push(...value) + } else { + bytes += 8 + const keys = Object.getOwnPropertyNames(value) + for (let i = 0; i < keys.length; i++) { + bytes += 4 + const key = keys[i] + const val = value[key] + if (val !== undefined) stack.push(val) + stack.push(key) + } + } + break + case 'function': + bytes += 8 // Shape descriptor + pointer (assuming functions are shared) + break + case 'symbol': + bytes += 8 // Shape descriptor + pointer + break + case 'bigint': + bytes += 16 // Shape descriptor + BigInt + break + } + } + + if (typeof value === 'object' && value !== null) { + knownSizes.set(value, bytes) + } + + return bytes +} diff --git a/packages/internal/simple-store-memory/tsconfig.build.json b/packages/internal/simple-store-memory/tsconfig.build.json new file mode 100644 index 00000000000..2ef4f334355 --- /dev/null +++ b/packages/internal/simple-store-memory/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig/isomorphic.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/internal/simple-store-memory/tsconfig.json b/packages/internal/simple-store-memory/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/simple-store-memory/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/internal/simple-store/package.json b/packages/internal/simple-store/package.json new file mode 100644 index 00000000000..c6263d0bce8 --- /dev/null +++ b/packages/internal/simple-store/package.json @@ -0,0 +1,34 @@ +{ + "name": "@atproto-labs/simple-store", + "version": "0.0.1", + "license": "MIT", + "description": "Simple store interfaces & utilities", + "keywords": [ + "cache", + "isomorphic" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/internal/simple-store" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/internal/simple-store/src/cached-getter.ts b/packages/internal/simple-store/src/cached-getter.ts new file mode 100644 index 00000000000..0caa1930c0d --- /dev/null +++ b/packages/internal/simple-store/src/cached-getter.ts @@ -0,0 +1,128 @@ +import { Awaitable, SimpleStore, Key, Value } from './simple-store.js' + +export type GetOptions = { + signal?: AbortSignal + noCache?: boolean + allowStale?: boolean +} + +export type Getter = ( + key: K, + options: undefined | GetOptions, + storedValue: undefined | V, +) => Awaitable + +export type CachedGetterOptions = { + isStale?: (key: K, value: V) => boolean | PromiseLike + onStoreError?: (err: unknown, key: K, value: V) => void | PromiseLike + deleteOnError?: ( + err: unknown, + key: K, + value: V, + ) => boolean | PromiseLike +} + +type PendingItem = Promise<{ value: V; isFresh: boolean }> + +/** + * Wrapper utility that uses a store to speed up the retrieval of values from an + * (expensive) getter function. + */ +export class CachedGetter { + private pending = new Map>() + + constructor( + readonly getter: Getter, + readonly store: SimpleStore, + readonly options?: CachedGetterOptions, + ) {} + + async get(key: K, options?: GetOptions): Promise { + const allowCached = options?.noCache !== true + const allowStale = + this.options?.isStale == null ? true : options?.allowStale ?? false + + const checkCached = async (value: V) => + allowCached && + (allowStale || (await this.options?.isStale?.(key, value)) !== true) + + // As long as concurrent requests are made for the same key, only one + // request will be made to the cache & getter function at a time. This works + // because there is no async operation between the while() loop and the + // pending.set() call. Because of the "single threaded" nature of + // JavaScript, the pending item will be set before the next iteration of the + // while loop. + let pending: undefined | PendingItem + while ((pending = this.pending.get(key))) { + options?.signal?.throwIfAborted() + + try { + const { isFresh, value } = await pending + + if (isFresh) return value + if (await checkCached(value)) return value + } catch { + // Ignore errors from pending promises. + } + } + + options?.signal?.throwIfAborted() + + try { + const pending: PendingItem = Promise.resolve().then(async () => { + const storedValue = await this.getStored(key, options) + if (storedValue !== undefined) { + if (await checkCached(storedValue)) { + return { isFresh: false, value: storedValue } + } + } + + return Promise.resolve() + .then(async () => (0, this.getter)(key, options, storedValue)) + .catch(async (err) => { + if (storedValue !== undefined && this.options?.deleteOnError) { + if (await this.options.deleteOnError(err, key, storedValue)) { + await this.delStored(key) + } + } + throw err + }) + .then(async (value) => { + await this.setStored(key, value) + return { value, isFresh: true } + }) + }) + + this.pending.set(key, pending) + + const { value } = await pending + return value + } finally { + this.pending.delete(key) + } + } + + bind(key: K): (options?: GetOptions) => Promise { + return async (options) => this.get(key, options) + } + + async getStored(key: K, options?: GetOptions): Promise { + try { + return await this.store.get(key, options) + } catch (err) { + return undefined + } + } + + async setStored(key: K, value: V): Promise { + try { + await this.store.set(key, value) + } catch (err) { + await this.options?.onStoreError?.(err, key, value) + } + } + + async delStored(key: K): Promise { + await this.store.del(key) + } +} diff --git a/packages/internal/simple-store/src/index.ts b/packages/internal/simple-store/src/index.ts new file mode 100644 index 00000000000..c217e9d27fd --- /dev/null +++ b/packages/internal/simple-store/src/index.ts @@ -0,0 +1,2 @@ +export * from './cached-getter.js' +export * from './simple-store.js' diff --git a/packages/internal/simple-store/src/simple-store.ts b/packages/internal/simple-store/src/simple-store.ts new file mode 100644 index 00000000000..3e8b0e3a9b1 --- /dev/null +++ b/packages/internal/simple-store/src/simple-store.ts @@ -0,0 +1,14 @@ +export type Awaitable = V | PromiseLike + +export type Key = string | number +export type Value = NonNullable | null + +export interface SimpleStore { + /** + * @return undefined if the key is not in the store (which is why Value cannot contain "undefined"). + */ + get: (key: K, options?: { signal?: AbortSignal }) => Awaitable + set: (key: K, value: V) => Awaitable + del: (key: K) => Awaitable + clear?: () => Awaitable +} diff --git a/packages/internal/simple-store/tsconfig.build.json b/packages/internal/simple-store/tsconfig.build.json new file mode 100644 index 00000000000..2ef4f334355 --- /dev/null +++ b/packages/internal/simple-store/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig/isomorphic.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/internal/simple-store/tsconfig.json b/packages/internal/simple-store/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/internal/simple-store/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/oauth/jwk-jose/package.json b/packages/oauth/jwk-jose/package.json new file mode 100644 index 00000000000..3cacfcd41c4 --- /dev/null +++ b/packages/oauth/jwk-jose/package.json @@ -0,0 +1,37 @@ +{ + "name": "@atproto/jwk-jose", + "version": "0.0.1", + "license": "MIT", + "description": "`jose` based implementation of @atproto/jwk Key's", + "keywords": [ + "atproto", + "jwk", + "jose" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/oauth/jwk-jose" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto/jwk": "workspace:*", + "jose": "^5.2.0", + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.json" + } +} diff --git a/packages/oauth/jwk-jose/src/index.ts b/packages/oauth/jwk-jose/src/index.ts new file mode 100644 index 00000000000..1de544a0a4e --- /dev/null +++ b/packages/oauth/jwk-jose/src/index.ts @@ -0,0 +1,2 @@ +export * from './jose-key.js' +export * from './jose-keyset.js' diff --git a/packages/oauth/jwk-jose/src/jose-key.ts b/packages/oauth/jwk-jose/src/jose-key.ts new file mode 100644 index 00000000000..bee340f13b1 --- /dev/null +++ b/packages/oauth/jwk-jose/src/jose-key.ts @@ -0,0 +1,156 @@ +import { + GenerateKeyPairOptions, + JWK, + JWTVerifyOptions, + KeyLike, + SignJWT, + exportJWK, + generateKeyPair, + importJWK, + importPKCS8, + jwtVerify, +} from 'jose' + +import { + Jwk, + Jwt, + JwtHeader, + JwtPayload, + Key, + VerifyOptions, + VerifyPayload, + VerifyResult, + jwkValidator, +} from '@atproto/jwk' +import { either } from './util' + +export type Importable = string | KeyLike | Jwk + +export type { GenerateKeyPairOptions } + +export class JoseKey extends Key { + #keyObj?: KeyLike | Uint8Array + + protected async getKey() { + return (this.#keyObj ||= await importJWK(this.jwk as JWK)) + } + + async createJwt(header: JwtHeader, payload: JwtPayload) { + if (header.kid && header.kid !== this.kid) { + throw new TypeError( + `Invalid "kid" (${header.kid}) used to sign with key "${this.kid}"`, + ) + } + + if (!header.alg || !this.algorithms.includes(header.alg)) { + throw new TypeError( + `Invalid "alg" (${header.alg}) used to sign with key "${this.kid}"`, + ) + } + + const keyObj = await this.getKey() + return new SignJWT(payload) + .setProtectedHeader({ ...header, kid: this.kid }) + .sign(keyObj) as Promise + } + + async verifyJwt< + P extends VerifyPayload = JwtPayload, + C extends string = string, + >(token: Jwt, options?: VerifyOptions): Promise> { + const keyObj = await this.getKey() + const result = await jwtVerify(token, keyObj, { + ...options, + algorithms: this.algorithms, + } as JWTVerifyOptions) + return result as VerifyResult + } + + static async generateKeyPair( + allowedAlgos: string[] = ['ES256'], + options?: GenerateKeyPairOptions, + ) { + const errors: unknown[] = [] + for (const alg of allowedAlgos) { + try { + return await generateKeyPair(alg, options) + } catch (err) { + errors.push(err) + } + } + throw new AggregateError(errors, 'Failed to generate key pair') + } + + static async generate( + kid: string, + allowedAlgos: string[] = ['ES256'], + options?: GenerateKeyPairOptions, + ) { + const kp = await this.generateKeyPair(allowedAlgos, options) + return this.fromImportable(kp.privateKey, kid) + } + + static async fromImportable( + input: Importable, + kid?: string, + ): Promise { + if (typeof input === 'string') { + // PKCS8 + if (input.startsWith('-----')) { + if (!kid) throw new TypeError('Missing "kid" for PKCS8 key') + return this.fromPKCS8(input, kid) + } + + // Jwk (string) + if (input.startsWith('{')) { + return this.fromJWK(input, kid) + } + + throw new TypeError('Invalid input') + } + + if (typeof input === 'object') { + // Jwk + if ('kty' in input || 'alg' in input) { + return this.fromJWK(input, kid) + } + + // KeyLike + if (!kid) throw new TypeError('Missing "kid" for KeyLike key') + return this.fromKeyLike(input, kid) + } + + throw new TypeError('Invalid input') + } + + static async fromKeyLike( + keyLike: KeyLike, + kid: string, + alg?: string, + ): Promise { + const jwk = await exportJWK(keyLike) + if (alg) { + if (!jwk.alg) jwk.alg = alg + else if (jwk.alg !== alg) throw new TypeError('Invalid "alg" in JWK') + } + return this.fromJWK(jwk, kid) + } + + static async fromPKCS8(pem: string, kid: string): Promise { + const keyLike = await importPKCS8(pem, '', { extractable: true }) + return this.fromKeyLike(keyLike, kid) + } + + static async fromJWK( + input: string | Record, + inputKid?: string, + ): Promise { + const jwk = typeof input === 'string' ? JSON.parse(input) : input + if (!jwk || typeof jwk !== 'object') throw new TypeError('Invalid JWK') + + const kid = either(jwk.kid, inputKid) + const use = jwk.use || 'sig' + + return new JoseKey(jwkValidator.parse({ ...jwk, kid, use })) + } +} diff --git a/packages/oauth/jwk-jose/src/jose-keyset.ts b/packages/oauth/jwk-jose/src/jose-keyset.ts new file mode 100644 index 00000000000..9d4eec71642 --- /dev/null +++ b/packages/oauth/jwk-jose/src/jose-keyset.ts @@ -0,0 +1,23 @@ +import { Key, Keyset } from '@atproto/jwk' +import { Importable, JoseKey } from './jose-key.js' + +export class JoseKeyset extends Keyset { + static async fromImportables( + input: Record, + ) { + return new JoseKeyset( + await Promise.all( + Object.entries(input).map(([kid, secret]) => { + if (secret instanceof Key) { + if (secret.kid !== kid) { + throw new TypeError(`Key ID mismatch: ${kid} !== ${secret.kid}`) + } + return secret + } + + return JoseKey.fromImportable(secret, kid) + }), + ), + ) + } +} diff --git a/packages/oauth/jwk-jose/src/util.ts b/packages/oauth/jwk-jose/src/util.ts new file mode 100644 index 00000000000..f75cdb66718 --- /dev/null +++ b/packages/oauth/jwk-jose/src/util.ts @@ -0,0 +1,9 @@ +export function either( + a?: T, + b?: T, +): T | undefined { + if (a != null && b != null && a !== b) { + throw new TypeError(`Expected "${b}", got "${a}"`) + } + return a ?? b ?? undefined +} diff --git a/packages/oauth/jwk-jose/tsconfig.build.json b/packages/oauth/jwk-jose/tsconfig.build.json new file mode 100644 index 00000000000..10eec9ceea2 --- /dev/null +++ b/packages/oauth/jwk-jose/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/nodenext.json"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/oauth/jwk-jose/tsconfig.json b/packages/oauth/jwk-jose/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/oauth/jwk-jose/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/oauth/jwk-webcrypto/package.json b/packages/oauth/jwk-webcrypto/package.json new file mode 100644 index 00000000000..1a1f6594f2a --- /dev/null +++ b/packages/oauth/jwk-webcrypto/package.json @@ -0,0 +1,37 @@ +{ + "name": "@atproto/jwk-webcrypto", + "version": "0.0.1", + "license": "MIT", + "description": "Webcrypto based implementation of @atproto/jwk Key's", + "keywords": [ + "atproto", + "jwk", + "webcrypto" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/oauth/jwk-webcrypto" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto/jwk": "workspace:*", + "@atproto/jwk-jose": "workspace:*", + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/oauth/jwk-webcrypto/src/index.ts b/packages/oauth/jwk-webcrypto/src/index.ts new file mode 100644 index 00000000000..fd2837bd208 --- /dev/null +++ b/packages/oauth/jwk-webcrypto/src/index.ts @@ -0,0 +1 @@ +export * from './webcrypto-key.js' diff --git a/packages/oauth/jwk-webcrypto/src/util.ts b/packages/oauth/jwk-webcrypto/src/util.ts new file mode 100644 index 00000000000..64574108988 --- /dev/null +++ b/packages/oauth/jwk-webcrypto/src/util.ts @@ -0,0 +1,122 @@ +export type JWSAlgorithm = + // HMAC + | 'HS256' + | 'HS384' + | 'HS512' + // RSA + | 'PS256' + | 'PS384' + | 'PS512' + | 'RS256' + | 'RS384' + | 'RS512' + // EC + | 'ES256' + | 'ES256K' + | 'ES384' + | 'ES512' + // OKP + | 'EdDSA' + +export type SubtleAlgorithm = RsaHashedKeyGenParams | EcKeyGenParams + +export function toSubtleAlgorithm( + alg: string, + crv?: string, + options?: { modulusLength?: number }, +): SubtleAlgorithm { + switch (alg) { + case 'PS256': + case 'PS384': + case 'PS512': + return { + name: 'RSA-PSS', + hash: `SHA-${alg.slice(-3) as '256' | '384' | '512'}`, + modulusLength: options?.modulusLength ?? 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + } + case 'RS256': + case 'RS384': + case 'RS512': + return { + name: 'RSASSA-PKCS1-v1_5', + hash: `SHA-${alg.slice(-3) as '256' | '384' | '512'}`, + modulusLength: options?.modulusLength ?? 2048, + publicExponent: new Uint8Array([0x01, 0x00, 0x01]), + } + case 'ES256': + case 'ES384': + return { + name: 'ECDSA', + namedCurve: `P-${alg.slice(-3) as '256' | '384'}`, + } + case 'ES512': + return { + name: 'ECDSA', + namedCurve: 'P-521', + } + default: + // https://github.com/w3c/webcrypto/issues/82#issuecomment-849856773 + + throw new TypeError(`Unsupported alg "${alg}"`) + } +} + +export function fromSubtleAlgorithm(algorithm: KeyAlgorithm): JWSAlgorithm { + switch (algorithm.name) { + case 'RSA-PSS': + case 'RSASSA-PKCS1-v1_5': { + const hash = (algorithm).hash.name + switch (hash) { + case 'SHA-256': + case 'SHA-384': + case 'SHA-512': { + const prefix = algorithm.name === 'RSA-PSS' ? 'PS' : 'RS' + return `${prefix}${hash.slice(-3) as '256' | '384' | '512'}` + } + default: + throw new TypeError('unsupported RsaHashedKeyAlgorithm hash') + } + } + case 'ECDSA': { + const namedCurve = (algorithm).namedCurve + switch (namedCurve) { + case 'P-256': + case 'P-384': + case 'P-512': + return `ES${namedCurve.slice(-3) as '256' | '384' | '512'}` + case 'P-521': + return 'ES512' + default: + throw new TypeError('unsupported EcKeyAlgorithm namedCurve') + } + } + case 'Ed448': + case 'Ed25519': + return 'EdDSA' + default: + // https://github.com/w3c/webcrypto/issues/82#issuecomment-849856773 + + throw new TypeError(`Unexpected algorithm "${algorithm.name}"`) + } +} + +export function isSignatureKeyPair( + v: unknown, + extractable?: boolean, +): v is CryptoKeyPair { + return ( + typeof v === 'object' && + v !== null && + 'privateKey' in v && + v.privateKey instanceof CryptoKey && + v.privateKey.type === 'private' && + (extractable == null || v.privateKey.extractable === extractable) && + v.privateKey.usages.includes('sign') && + 'publicKey' in v && + v.publicKey instanceof CryptoKey && + v.publicKey.type === 'public' && + v.publicKey.extractable === true && + v.publicKey.usages.includes('verify') + ) +} diff --git a/packages/oauth/jwk-webcrypto/src/webcrypto-key.ts b/packages/oauth/jwk-webcrypto/src/webcrypto-key.ts new file mode 100644 index 00000000000..beea03602d9 --- /dev/null +++ b/packages/oauth/jwk-webcrypto/src/webcrypto-key.ts @@ -0,0 +1,77 @@ +import { Jwk, jwkSchema } from '@atproto/jwk' +import { GenerateKeyPairOptions, JoseKey } from '@atproto/jwk-jose' + +import { fromSubtleAlgorithm, isSignatureKeyPair } from './util.js' + +export class WebcryptoKey extends JoseKey { + // We need to override the static method generate from JoseKey because + // the browser needs both the private and public keys + static override async generate( + kid: string = crypto.randomUUID(), + allowedAlgos: string[] = ['ES256'], + options?: GenerateKeyPairOptions, + ) { + const { privateKey, publicKey } = await this.generateKeyPair( + allowedAlgos, + options, + ) + // Type safety only: in the browser, "jose" generates a CryptoKeyPair + if ( + !(privateKey instanceof CryptoKey) || + !(publicKey instanceof CryptoKey) + ) { + throw new TypeError('Invalid CryptoKeyPair') + } + return this.fromKeypair(kid, { privateKey, publicKey }) + } + + static async fromKeypair(kid: string, cryptoKeyPair: CryptoKeyPair) { + if (!isSignatureKeyPair(cryptoKeyPair)) { + throw new TypeError('CryptoKeyPair must be compatible with sign/verify') + } + + // https://datatracker.ietf.org/doc/html/rfc7517 + // > The "use" and "key_ops" JWK members SHOULD NOT be used together; [...] + // > Applications should specify which of these members they use. + + const { key_ops: _, ...jwk } = await crypto.subtle.exportKey( + 'jwk', + cryptoKeyPair.privateKey.extractable + ? cryptoKeyPair.privateKey + : cryptoKeyPair.publicKey, + ) + + const use = jwk.use ?? 'sig' + const alg = + jwk.alg ?? fromSubtleAlgorithm(cryptoKeyPair.privateKey.algorithm) + + if (use !== 'sig') { + throw new TypeError('Unsupported JWK use') + } + + return new WebcryptoKey( + jwkSchema.parse({ ...jwk, use, kid, alg }), + cryptoKeyPair, + ) + } + + constructor( + jwk: Jwk, + readonly cryptoKeyPair: CryptoKeyPair, + ) { + super(jwk) + } + + get isPrivate() { + return true + } + + get privateJwk(): Jwk | undefined { + if (super.isPrivate) return this.jwk + throw new Error('Private Webcrypto Key not exportable') + } + + protected override async getKey() { + return this.cryptoKeyPair.privateKey + } +} diff --git a/packages/oauth/jwk-webcrypto/tsconfig.build.json b/packages/oauth/jwk-webcrypto/tsconfig.build.json new file mode 100644 index 00000000000..d37ddd8d394 --- /dev/null +++ b/packages/oauth/jwk-webcrypto/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/isomorphic.json"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/oauth/jwk-webcrypto/tsconfig.json b/packages/oauth/jwk-webcrypto/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/oauth/jwk-webcrypto/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/oauth/jwk/package.json b/packages/oauth/jwk/package.json new file mode 100644 index 00000000000..ed6931c21d8 --- /dev/null +++ b/packages/oauth/jwk/package.json @@ -0,0 +1,39 @@ +{ + "name": "@atproto/jwk", + "version": "0.0.1", + "license": "MIT", + "description": "A library for working with JSON Web Keys (JWKs) in TypeScript. This is meant to be extended by environment-specific libraries like @atproto/jwk-jose.", + "keywords": [ + "atproto", + "jwk", + "jwks", + "jwt", + "json web key" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/oauth/jwk" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "multiformats": "^9.9.0", + "tslib": "^2.6.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.json" + } +} diff --git a/packages/oauth/jwk/src/alg.ts b/packages/oauth/jwk/src/alg.ts new file mode 100644 index 00000000000..226a1bb6624 --- /dev/null +++ b/packages/oauth/jwk/src/alg.ts @@ -0,0 +1,97 @@ +import { Jwk } from './jwk.js' + +declare const process: undefined | { versions?: { node?: string } } +const IS_NODE_RUNTIME = + typeof process !== 'undefined' && typeof process?.versions?.node === 'string' + +export function* jwkAlgorithms(jwk: Jwk): Generator { + // Ed25519, Ed448, and secp256k1 always have "alg" + // OKP always has "use" + if (jwk.alg) { + yield jwk.alg + return + } + + switch (jwk.kty) { + case 'EC': { + if (jwk.use === 'enc' || jwk.use === undefined) { + yield 'ECDH-ES' + yield 'ECDH-ES+A128KW' + yield 'ECDH-ES+A192KW' + yield 'ECDH-ES+A256KW' + } + + if (jwk.use === 'sig' || jwk.use === undefined) { + const crv = 'crv' in jwk ? jwk.crv : undefined + switch (crv) { + case 'P-256': + case 'P-384': + yield `ES${crv.slice(-3)}`.replace('21', '12') + break + case 'P-521': + yield 'ES512' + break + case 'secp256k1': + if (IS_NODE_RUNTIME) yield 'ES256K' + break + default: + throw new TypeError(`Unsupported crv "${crv}"`) + } + } + + return + } + + case 'OKP': { + if (!jwk.use) throw new TypeError('Missing "use" Parameter value') + yield 'ECDH-ES' + yield 'ECDH-ES+A128KW' + yield 'ECDH-ES+A192KW' + yield 'ECDH-ES+A256KW' + return + } + + case 'RSA': { + if (jwk.use === 'enc' || jwk.use === undefined) { + yield 'RSA-OAEP' + yield 'RSA-OAEP-256' + yield 'RSA-OAEP-384' + yield 'RSA-OAEP-512' + if (IS_NODE_RUNTIME) yield 'RSA1_5' + } + + if (jwk.use === 'sig' || jwk.use === undefined) { + yield 'PS256' + yield 'PS384' + yield 'PS512' + yield 'RS256' + yield 'RS384' + yield 'RS512' + } + + return + } + + case 'oct': { + if (jwk.use === 'enc' || jwk.use === undefined) { + yield 'A128GCMKW' + yield 'A192GCMKW' + yield 'A256GCMKW' + yield 'A128KW' + yield 'A192KW' + yield 'A256KW' + } + + if (jwk.use === 'sig' || jwk.use === undefined) { + yield 'HS256' + yield 'HS384' + yield 'HS512' + } + + return + } + + default: + throw new Error(`Unsupported kty "${jwk.kty}"`) + } +} diff --git a/packages/oauth/jwk/src/index.ts b/packages/oauth/jwk/src/index.ts new file mode 100644 index 00000000000..3e1c678233c --- /dev/null +++ b/packages/oauth/jwk/src/index.ts @@ -0,0 +1,9 @@ +export * from './alg.js' +export * from './jwk.js' +export * from './jwks.js' +export * from './jwt.js' +export * from './jwt-decode.js' +export * from './jwt-verify.js' +export * from './key.js' +export * from './keyset.js' +export * from './util.js' diff --git a/packages/oauth/jwk/src/jwk.ts b/packages/oauth/jwk/src/jwk.ts new file mode 100644 index 00000000000..e6548564400 --- /dev/null +++ b/packages/oauth/jwk/src/jwk.ts @@ -0,0 +1,155 @@ +import { z } from 'zod' + +export const keyUsageSchema = z.enum([ + 'sign', + 'verify', + 'encrypt', + 'decrypt', + 'wrapKey', + 'unwrapKey', + 'deriveKey', + 'deriveBits', +]) + +export type KeyUsage = z.infer + +/** + * The "use" and "key_ops" JWK members SHOULD NOT be used together; + * however, if both are used, the information they convey MUST be + * consistent. Applications should specify which of these members they + * use, if either is to be used by the application. + * + * @todo Actually check that "use" and "key_ops" are consistent when both are present. + * @see {@link https://datatracker.ietf.org/doc/html/rfc7517#section-4.3} + */ +export const jwkBaseSchema = z.object({ + kty: z.string().min(1), + alg: z.string().min(1).optional(), + kid: z.string().min(1).optional(), + ext: z.boolean().optional(), + use: z.enum(['sig', 'enc']).optional(), + key_ops: z.array(keyUsageSchema).readonly().optional(), + + x5c: z.array(z.string()).readonly().optional(), // X.509 Certificate Chain + x5t: z.string().min(1).optional(), // X.509 Certificate SHA-1 Thumbprint + 'x5t#S256': z.string().min(1).optional(), // X.509 Certificate SHA-256 Thumbprint + x5u: z.string().url().optional(), // X.509 URL +}) + +/** + * @todo: properly implement this + */ +export const jwkRsaKeySchema = jwkBaseSchema + .extend({ + kty: z.literal('RSA'), + alg: z + .enum(['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']) + .optional(), + + n: z.string().min(1), // Modulus + e: z.string().min(1), // Exponent + + d: z.string().min(1).optional(), // Private Exponent + p: z.string().min(1).optional(), // First Prime Factor + q: z.string().min(1).optional(), // Second Prime Factor + dp: z.string().min(1).optional(), // First Factor CRT Exponent + dq: z.string().min(1).optional(), // Second Factor CRT Exponent + qi: z.string().min(1).optional(), // First CRT Coefficient + oth: z + .array( + z + .object({ + r: z.string().optional(), + d: z.string().optional(), + t: z.string().optional(), + }) + .readonly(), + ) + .nonempty() + .readonly() + .optional(), // Other Primes Info + }) + .readonly() + +export const jwkEcKeySchema = jwkBaseSchema + .extend({ + kty: z.literal('EC'), + alg: z.enum(['ES256', 'ES384', 'ES512']).optional(), + crv: z.enum(['P-256', 'P-384', 'P-521']), + + x: z.string().min(1), + y: z.string().min(1), + + d: z.string().min(1).optional(), // ECC Private Key + }) + .readonly() + +export const jwkEcSecp256k1KeySchema = jwkBaseSchema + .extend({ + kty: z.literal('EC'), + alg: z.enum(['ES256K']).optional(), + crv: z.enum(['secp256k1']), + + x: z.string().min(1), + y: z.string().min(1), + + d: z.string().min(1).optional(), // ECC Private Key + }) + .readonly() + +export const jwkOkpKeySchema = jwkBaseSchema + .extend({ + kty: z.literal('OKP'), + alg: z.enum(['EdDSA']).optional(), + crv: z.enum(['Ed25519', 'Ed448']), + + x: z.string().min(1), + d: z.string().min(1).optional(), // ECC Private Key + }) + .readonly() + +export const jwkSymKeySchema = jwkBaseSchema + .extend({ + kty: z.literal('oct'), // Octet Sequence (used to represent symmetric keys) + alg: z.enum(['HS256', 'HS384', 'HS512']).optional(), + + k: z.string(), // Key Value (base64url encoded) + }) + .readonly() + +export const jwkUnknownKeySchema = jwkBaseSchema + .extend({ + kty: z + .string() + .refine((v) => v !== 'RSA' && v !== 'EC' && v !== 'OKP' && v !== 'oct'), + }) + .readonly() + +export const jwkSchema = z.union([ + jwkUnknownKeySchema, + jwkRsaKeySchema, + jwkEcKeySchema, + jwkEcSecp256k1KeySchema, + jwkOkpKeySchema, + jwkSymKeySchema, +]) + +export type Jwk = z.infer + +export const jwkValidator = jwkSchema + .refine((k) => k.use != null || k.key_ops != null, 'use or key_ops required') + .refine( + (k) => + !k.use || + !k.key_ops || + k.key_ops.every((o) => + k.use === 'sig' + ? o === 'sign' || o === 'verify' + : o === 'encrypt' || o === 'decrypt', + ), + 'use and key_ops must be consistent', + ) + +export const jwkPubSchema = jwkValidator + .refine((k) => k.kid != null, 'kid is required') + .refine((k) => !('k' in k) && !('d' in k), 'private key not allowed') diff --git a/packages/oauth/jwk/src/jwks.ts b/packages/oauth/jwk/src/jwks.ts new file mode 100644 index 00000000000..1ec8d382ba8 --- /dev/null +++ b/packages/oauth/jwk/src/jwks.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' + +import { jwkPubSchema, jwkSchema } from './jwk.js' + +export const jwksSchema = z + .object({ + keys: z.array(jwkSchema).readonly(), + }) + .readonly() + +export type Jwks = z.infer + +export const jwksPubSchema = z + .object({ + keys: z.array(jwkPubSchema).readonly(), + }) + .readonly() + +export type JwksPub = z.infer diff --git a/packages/oauth/jwk/src/jwt-decode.ts b/packages/oauth/jwk/src/jwt-decode.ts new file mode 100644 index 00000000000..8f3f6d1a2d4 --- /dev/null +++ b/packages/oauth/jwk/src/jwt-decode.ts @@ -0,0 +1,34 @@ +import { base64url } from 'multiformats/bases/base64' + +import { + JwtHeader, + JwtPayload, + jwtHeaderSchema, + jwtPayloadSchema, +} from './jwt.js' + +export function unsafeDecodeJwt(jwt: string): { + header: JwtHeader + payload: JwtPayload +} { + const { 0: headerEnc, 1: payloadEnc, length } = jwt.split('.') + if (length > 3 || length < 2) { + throw new TypeError('invalid JWT input') + } + + const header = jwtHeaderSchema.parse(parseB64uJson(headerEnc!)) + if (length === 2 && header?.alg !== 'none') { + throw new TypeError('invalid JWT input') + } + + const payload = jwtPayloadSchema.parse(parseB64uJson(payloadEnc!)) + + return { header, payload } +} + +const decoder = new TextDecoder() +function parseB64uJson(input: string): unknown { + const inputBytes = base64url.baseDecode(input) + const json = decoder.decode(inputBytes) + return JSON.parse(json) +} diff --git a/packages/oauth/jwk/src/jwt-verify.ts b/packages/oauth/jwk/src/jwt-verify.ts new file mode 100644 index 00000000000..8c80c510d81 --- /dev/null +++ b/packages/oauth/jwk/src/jwt-verify.ts @@ -0,0 +1,22 @@ +import { JwtHeader, JwtPayload } from './jwt.js' +import { RequiredKey } from './util.js' + +export type VerifyOptions = { + audience?: string | readonly string[] + /** in seconds */ + clockTolerance?: number + issuer?: string | readonly string[] + /** in seconds */ + maxTokenAge?: number + subject?: string + typ?: string + currentDate?: Date + requiredClaims?: readonly C[] +} + +export type VerifyPayload = Record + +export type VerifyResult

= { + payload: RequiredKey

+ protectedHeader: JwtHeader +} diff --git a/packages/oauth/jwk/src/jwt.ts b/packages/oauth/jwk/src/jwt.ts new file mode 100644 index 00000000000..c07af8cb735 --- /dev/null +++ b/packages/oauth/jwk/src/jwt.ts @@ -0,0 +1,172 @@ +import { z } from 'zod' + +import { jwkPubSchema } from './jwk.js' + +export const JWT_REGEXP = /^[A-Za-z0-9_-]{2,}(?:\.[A-Za-z0-9_-]{2,}){1,2}$/ +export const jwtSchema = z + .string() + .min(5) + .refinement( + (data: string): data is `${string}.${string}.${string}` => + JWT_REGEXP.test(data), + { + code: z.ZodIssueCode.custom, + message: 'Must be a JWT', + }, + ) + +export const isJwt = (data: unknown): data is Jwt => + jwtSchema.safeParse(data).success + +export type Jwt = z.infer + +/** + * @see {@link https://www.rfc-editor.org/rfc/rfc7515.html#section-4} + */ +export const jwtHeaderSchema = z.object({ + /** "alg" (Algorithm) Header Parameter */ + alg: z.string(), + /** "jku" (JWK Set URL) Header Parameter */ + jku: z.string().url().optional(), + /** "jwk" (JSON Web Key) Header Parameter */ + jwk: z + .object({ + kty: z.string(), + crv: z.string().optional(), + x: z.string().optional(), + y: z.string().optional(), + e: z.string().optional(), + n: z.string().optional(), + }) + .optional(), + /** "kid" (Key ID) Header Parameter */ + kid: z.string().optional(), + /** "x5u" (X.509 URL) Header Parameter */ + x5u: z.string().optional(), + /** "x5c" (X.509 Certificate Chain) Header Parameter */ + x5c: z.array(z.string()).optional(), + /** "x5t" (X.509 Certificate SHA-1 Thumbprint) Header Parameter */ + x5t: z.string().optional(), + /** "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) Header Parameter */ + 'x5t#S256': z.string().optional(), + /** "typ" (Type) Header Parameter */ + typ: z.string().optional(), + /** "cty" (Content Type) Header Parameter */ + cty: z.string().optional(), + /** "crit" (Critical) Header Parameter */ + crit: z.array(z.string()).optional(), +}) + +export type JwtHeader = z.infer + +// https://www.iana.org/assignments/jwt/jwt.xhtml +export const jwtPayloadSchema = z.object({ + iss: z.string().optional(), + aud: z.union([z.string(), z.array(z.string()).nonempty()]).optional(), + sub: z.string().optional(), + exp: z.number().int().optional(), + nbf: z.number().int().optional(), + iat: z.number().int().optional(), + jti: z.string().optional(), + htm: z.string().optional(), + htu: z.string().optional(), + ath: z.string().optional(), + acr: z.string().optional(), + azp: z.string().optional(), + amr: z.array(z.string()).optional(), + // https://datatracker.ietf.org/doc/html/rfc7800 + cnf: z + .object({ + kid: z.string().optional(), // Key ID + jwk: jwkPubSchema.optional(), // JWK + jwe: z.string().optional(), // Encrypted key + jku: z.string().url().optional(), // JWK Set URI ("kid" should also be provided) + + // https://datatracker.ietf.org/doc/html/rfc9449#section-6.1 + jkt: z.string().optional(), + + // https://datatracker.ietf.org/doc/html/rfc8705 + 'x5t#S256': z.string().optional(), // X.509 Certificate SHA-256 Thumbprint + + // https://datatracker.ietf.org/doc/html/rfc9203 + osc: z.string().optional(), // OSCORE_Input_Material carrying the parameters for using OSCORE per-message security with implicit key confirmation + }) + .optional(), + + client_id: z.string().optional(), + + scope: z.string().optional(), + nonce: z.string().optional(), + + at_hash: z.string().optional(), + c_hash: z.string().optional(), + s_hash: z.string().optional(), + auth_time: z.number().int().optional(), + + // https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims + + // OpenID: "profile" scope + name: z.string().optional(), + family_name: z.string().optional(), + given_name: z.string().optional(), + middle_name: z.string().optional(), + nickname: z.string().optional(), + preferred_username: z.string().optional(), + gender: z.string().optional(), // OpenID only defines "male" and "female" without forbidding other values + picture: z.string().url().optional(), + profile: z.string().url().optional(), + website: z.string().url().optional(), + birthdate: z + .string() + .regex(/\d{4}-\d{2}-\d{2}/) // YYYY-MM-DD + .optional(), + zoneinfo: z + .string() + .regex(/^[A-Za-z0-9_/]+$/) + .optional(), + locale: z + .string() + .regex(/^[a-z]{2}(-[A-Z]{2})?$/) + .optional(), + updated_at: z.number().int().optional(), + + // OpenID: "email" scope + email: z.string().optional(), + email_verified: z.boolean().optional(), + + // OpenID: "phone" scope + phone_number: z.string().optional(), + phone_number_verified: z.boolean().optional(), + + // OpenID: "address" scope + // https://openid.net/specs/openid-connect-core-1_0.html#AddressClaim + address: z + .object({ + formatted: z.string().optional(), + street_address: z.string().optional(), + locality: z.string().optional(), + region: z.string().optional(), + postal_code: z.string().optional(), + country: z.string().optional(), + }) + .optional(), + + // https://datatracker.ietf.org/doc/html/rfc9396#section-14.2 + authorization_details: z + .array( + z + .object({ + type: z.string(), + // https://datatracker.ietf.org/doc/html/rfc9396#section-2.2 + locations: z.array(z.string()).optional(), + actions: z.array(z.string()).optional(), + datatypes: z.array(z.string()).optional(), + identifier: z.string().optional(), + privileges: z.array(z.string()).optional(), + }) + .passthrough(), + ) + .optional(), +}) + +export type JwtPayload = z.infer diff --git a/packages/oauth/jwk/src/key.ts b/packages/oauth/jwk/src/key.ts new file mode 100644 index 00000000000..71c418a179a --- /dev/null +++ b/packages/oauth/jwk/src/key.ts @@ -0,0 +1,88 @@ +import { jwkAlgorithms } from './alg.js' +import { Jwk, jwkSchema } from './jwk.js' +import { VerifyOptions, VerifyPayload, VerifyResult } from './jwt-verify.js' +import { Jwt, JwtHeader, JwtPayload } from './jwt.js' +import { cachedGetter } from './util.js' + +export abstract class Key { + constructor(protected jwk: Jwk) { + // A key should always be used either for signing or encryption. + if (!jwk.use) throw new TypeError('Missing "use" Parameter value') + } + + get isPrivate(): boolean { + const { jwk } = this + if ('d' in jwk && jwk.d !== undefined) return true + if ('k' in jwk && jwk.k !== undefined) return true + return false + } + + get isSymetric(): boolean { + const { jwk } = this + if ('k' in jwk && jwk.k !== undefined) return true + return false + } + + get privateJwk(): Jwk | undefined { + return this.isPrivate ? this.jwk : undefined + } + + @cachedGetter + get publicJwk(): Jwk | undefined { + if (this.isSymetric) return undefined + if (this.isPrivate) { + const { d: _, ...jwk } = this.jwk as any + return jwk + } + return this.jwk + } + + @cachedGetter + get bareJwk(): Jwk | undefined { + if (this.isSymetric) return undefined + const { kty, crv, e, n, x, y } = this.jwk as any + return jwkSchema.parse({ crv, e, kty, n, x, y }) + } + + get use() { + return this.jwk.use! + } + + /** + * The (forced) algorithm to use. If not provided, the key will be usable with + * any of the algorithms in {@link algorithms}. + */ + get alg() { + return this.jwk.alg + } + + get kid() { + return this.jwk.kid + } + + get crv() { + return (this.jwk as undefined | Extract)?.crv + } + + /** + * All the algorithms that this key can be used with. If `alg` is provided, + * this set will only contain that algorithm. + */ + @cachedGetter + get algorithms(): readonly string[] { + return Array.from(jwkAlgorithms(this.jwk)) + } + + /** + * Create a signed JWT + */ + abstract createJwt(header: JwtHeader, payload: JwtPayload): Promise + + /** + * Verify the signature, headers and payload of a JWT + */ + abstract verifyJwt< + P extends VerifyPayload = JwtPayload, + C extends string = string, + >(token: Jwt, options?: VerifyOptions): Promise> +} diff --git a/packages/oauth/jwk/src/keyset.ts b/packages/oauth/jwk/src/keyset.ts new file mode 100644 index 00000000000..0e4935aa079 --- /dev/null +++ b/packages/oauth/jwk/src/keyset.ts @@ -0,0 +1,202 @@ +import { Jwk } from './jwk.js' +import { Jwks } from './jwks.js' +import { unsafeDecodeJwt } from './jwt-decode.js' +import { VerifyOptions } from './jwt-verify.js' +import { Jwt, JwtHeader, JwtPayload } from './jwt.js' +import { Key } from './key.js' +import { + Override, + cachedGetter, + isDefined, + matchesAny, + preferredOrderCmp, +} from './util.js' + +export type JwtSignHeader = Override> + +export type JwtPayloadGetter

= ( + header: JwtHeader, + key: Key, +) => P | PromiseLike

+ +export type KeySearch = { + use?: 'sig' | 'enc' + kid?: string | string[] + alg?: string | string[] +} + +const extractPrivateJwk = (key: Key): Jwk | undefined => key.privateJwk +const extractPublicJwk = (key: Key): Jwk | undefined => key.publicJwk + +export class Keyset implements Iterable { + constructor( + private readonly keys: readonly K[], + /** + * The preferred algorithms to use when signing a JWT using this keyset. + */ + readonly preferredSigningAlgorithms: readonly string[] = [ + 'EdDSA', + 'ES256K', + 'ES256', + // https://datatracker.ietf.org/doc/html/rfc7518#section-3.5 + 'PS256', + 'PS384', + 'PS512', + 'HS256', + 'HS384', + 'HS512', + ], + ) { + if (!keys.length) throw new Error('Keyset is empty') + + const kids = new Set() + for (const { kid } of keys) { + if (!kid) continue + + if (kids.has(kid)) throw new Error(`Duplicate key id: ${kid}`) + else kids.add(kid) + } + } + + @cachedGetter + get signAlgorithms(): readonly string[] { + const algorithms = new Set() + for (const key of this) { + if (key.use !== 'sig') continue + for (const alg of key.algorithms) { + algorithms.add(alg) + } + } + return Object.freeze( + [...algorithms].sort(preferredOrderCmp(this.preferredSigningAlgorithms)), + ) + } + + @cachedGetter + get publicJwks(): Jwks { + return { + keys: Array.from(this, extractPublicJwk).filter(isDefined), + } + } + + @cachedGetter + get privateJwks(): Jwks { + return { + keys: Array.from(this, extractPrivateJwk).filter(isDefined), + } + } + + has(kid: string): boolean { + return this.keys.some((key) => key.kid === kid) + } + + get(search: KeySearch): K { + for (const key of this.list(search)) { + return key + } + + throw new TypeError( + `Key not found ${search.kid || search.alg || ''}`, + ) + } + + *list(search: KeySearch): Generator { + // Optimization: Empty string or empty array will not match any key + if (search.kid?.length === 0) return + if (search.alg?.length === 0) return + + for (const key of this) { + if (search.use && key.use !== search.use) continue + + if (Array.isArray(search.kid)) { + if (!key.kid || !search.kid.includes(key.kid)) continue + } else if (search.kid) { + if (key.kid !== search.kid) continue + } + + if (Array.isArray(search.alg)) { + if (!search.alg.some((a) => key.algorithms.includes(a))) continue + } else if (typeof search.alg === 'string') { + if (!key.algorithms.includes(search.alg)) continue + } + + yield key + } + } + + findKey({ kid, alg, use }: KeySearch): [key: Key, alg: string] { + const matchingKeys: Key[] = [] + + for (const key of this.list({ kid, alg, use })) { + // Not a signing key + if (!key.isPrivate) continue + + // Skip negotiation if a specific "alg" was provided + if (typeof alg === 'string') return [key, alg] + + matchingKeys.push(key) + } + + const isAllowedAlg = matchesAny(alg) + const candidates = matchingKeys.map( + (key) => [key, key.algorithms.filter(isAllowedAlg)] as const, + ) + + // Return the first candidates that matches the preferred algorithms + for (const prefAlg of this.preferredSigningAlgorithms) { + for (const [matchingKey, matchingAlgs] of candidates) { + if (matchingAlgs.includes(prefAlg)) return [matchingKey, prefAlg] + } + } + + // Return any candidate + for (const [matchingKey, matchingAlgs] of candidates) { + for (const alg of matchingAlgs) { + return [matchingKey, alg] + } + } + + throw new TypeError( + `No singing key found for ${kid || alg || use || ''}`, + ) + } + + [Symbol.iterator](): IterableIterator { + return this.keys.values() + } + + async createJwt( + { alg: sAlg, kid: sKid, ...header }: JwtSignHeader, + payload: JwtPayload | JwtPayloadGetter, + ) { + const [key, alg] = this.findKey({ alg: sAlg, kid: sKid, use: 'sig' }) + const protectedHeader = { ...header, alg, kid: key.kid } + + if (typeof payload === 'function') { + payload = await payload(protectedHeader, key) + } + + return key.createJwt(protectedHeader, payload) + } + + async verifyJwt< + P extends Record = JwtPayload, + C extends string = string, + >(token: Jwt, options?: VerifyOptions) { + const { header } = unsafeDecodeJwt(token) + const { kid, alg } = header + + const errors: unknown[] = [] + + for (const key of this.list({ kid, alg })) { + try { + const result = await key.verifyJwt(token, options) + return { ...result, key } + } catch (err) { + errors.push(err) + } + } + + throw new AggregateError(errors, 'Unable to verify signature') + } +} diff --git a/packages/oauth/jwk/src/util.ts b/packages/oauth/jwk/src/util.ts new file mode 100644 index 00000000000..8060448c2af --- /dev/null +++ b/packages/oauth/jwk/src/util.ts @@ -0,0 +1,52 @@ +// eslint-disable-next-line @typescript-eslint/ban-types +export type Simplify = { [K in keyof T]: T[K] } & {} +export type Override = Simplify> + +export type RequiredKey = Simplify< + string extends K + ? T + : { + [L in K]: Exclude + } & Omit +> + +export const isDefined = (i: T | undefined): i is T => i !== undefined + +export const preferredOrderCmp = + (order: readonly T[]) => + (a: T, b: T) => { + const aIdx = order.indexOf(a) + const bIdx = order.indexOf(b) + if (aIdx === bIdx) return 0 + if (aIdx === -1) return 1 + if (bIdx === -1) return -1 + return aIdx - bIdx + } + +export function matchesAny( + value: null | undefined | T | readonly T[], +): (v: unknown) => v is T { + return value == null + ? (v): v is T => true + : Array.isArray(value) + ? (v): v is T => value.includes(v) + : (v): v is T => v === value +} + +/** + * Decorator to cache the result of a getter on a class instance. + */ +export const cachedGetter = ( + target: (this: T) => V, + _context: ClassGetterDecoratorContext, +) => { + return function (this: T) { + const value = target.call(this) + Object.defineProperty(this, target.name, { + get: () => value, + enumerable: true, + configurable: true, + }) + return value + } +} diff --git a/packages/oauth/jwk/tsconfig.build.json b/packages/oauth/jwk/tsconfig.build.json new file mode 100644 index 00000000000..9f3c5252d4c --- /dev/null +++ b/packages/oauth/jwk/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/isomorphic.json"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/packages/oauth/jwk/tsconfig.json b/packages/oauth/jwk/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/oauth/jwk/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/oauth/oauth-client-browser/example/.gitignore b/packages/oauth/oauth-client-browser/example/.gitignore new file mode 100644 index 00000000000..a24d0aef96d --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/.gitignore @@ -0,0 +1 @@ +adist diff --git a/packages/oauth/oauth-client-browser/example/.postcssrc.yml b/packages/oauth/oauth-client-browser/example/.postcssrc.yml new file mode 100644 index 00000000000..0114fbc9e78 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/.postcssrc.yml @@ -0,0 +1,3 @@ +plugins: + tailwindcss: {} + autoprefixer: {} diff --git a/packages/oauth/oauth-client-browser/example/package.json b/packages/oauth/oauth-client-browser/example/package.json new file mode 100644 index 00000000000..3dbc1ca591c --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/oauth/oauth-client-browser/example/rollup.config.js b/packages/oauth/oauth-client-browser/example/rollup.config.js new file mode 100644 index 00000000000..dbe5b35f96b --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/rollup.config.js @@ -0,0 +1,89 @@ +/* eslint-env node */ + +import { defineConfig } from 'rollup' + +import commonjs from '@rollup/plugin-commonjs' +import nodeResolve from '@rollup/plugin-node-resolve' +import replace from '@rollup/plugin-replace' +import typescript from '@rollup/plugin-typescript' +import html, { makeHtmlAttributes } from '@rollup/plugin-html' +import postcss from 'rollup-plugin-postcss' +import serve from 'rollup-plugin-serve' + +export default defineConfig((commandLineArguments) => { + const NODE_ENV = + process.env['NODE_ENV'] ?? + (commandLineArguments.watch ? 'development' : 'production') + + return { + input: 'src/main.tsx', + output: { dir: 'dist', sourcemap: true }, + plugins: [ + nodeResolve({ preferBuiltins: false, browser: true }), + commonjs(), + postcss({ config: true, extract: true, minimize: false }), + typescript({ + tsconfig: './tsconfig.build.json', + outputToFilesystem: true, + }), + replace({ + preventAssignment: true, + values: { 'process.env.NODE_ENV': JSON.stringify(NODE_ENV) }, + }), + html({ + title: 'OAuth Client Example', + template: (templateOptions) => { + // https://github.com/rollup/plugins/pull/1718 + if (!templateOptions) throw new Error('No template options provided') + const { attributes, files, meta, publicPath, title } = templateOptions + + return ` + + + + ${meta + .map((attrs) => ``) + .join('\n')} + + ${title} + ${files.css + .map( + (asset) => + ``, + ) + .join('\n')} + + +

+ ${files.js + .map( + (asset) => + ``, + ) + .join('\n')} + + + ` + }, + }), + commandLineArguments.watch && + serve({ + contentBase: 'dist', + port: 8080, + headers: { 'Cache-Control': 'no-store' }, + }), + ], + onwarn(warning, warn) { + // 'use client' directives are fine + if (warning.code === 'MODULE_LEVEL_DIRECTIVE') return + warn(warning) + }, + } +}) diff --git a/packages/oauth/oauth-client-browser/example/src/app.tsx b/packages/oauth/oauth-client-browser/example/src/app.tsx new file mode 100644 index 00000000000..884418de487 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/app.tsx @@ -0,0 +1,82 @@ +import { useCallback, useState } from 'react' + +import LoginForm from './login-form' +import { useOAuth } from './oauth' + +/** + * State data that we want to persist across the OAuth flow, when the user is + * "logging in". + */ +export type AppState = { + foo: string +} + +/** + * The {@link OAuthFrontendXrpcAgent} provides the hostname of the PDS, as + * defined during the authentication flow and credentials (`Authorization` + + * `DPoP`) form the XRPC calls. It will also handle transparently refreshing + * the credentials when they expire. + */ + +function App() { + const { initialized, client, signedIn, signOut, error, loading, signIn } = + useOAuth() + const [profile, setProfile] = useState<{ + value: { displayName?: string } + } | null>(null) + + const loadProfile = useCallback(async () => { + if (!client) return + + const info = await client.getUserinfo() + console.log('info', info) + + if (!client) return + + // A call that requires to be authenticated + console.log( + await client + .request( + '/xrpc/com.atproto.server.getServiceAuth?' + + new URLSearchParams({ aud: info.sub }).toString(), + ) + .then((r) => r.json()), + ) + + // This call does not require authentication + const profile = await client + .request( + '/xrpc/com.atproto.repo.getRecord?' + + new URLSearchParams({ + repo: info.sub, + collection: 'app.bsky.actor.profile', + rkey: 'self', + }).toString(), + ) + .then((r) => r.json()) + + console.log(profile) + + setProfile(profile.data) + }, [client]) + + if (!initialized) { + return

{error || 'Loading...'}

+ } + + return signedIn ? ( +
+

Logged in!

+ + +
{profile ? JSON.stringify(profile, undefined, 2) : null}
+
+ + +
+ ) : ( + + ) +} + +export default App diff --git a/packages/oauth/oauth-client-browser/example/src/index.css b/packages/oauth/oauth-client-browser/example/src/index.css new file mode 100644 index 00000000000..b5c61c95671 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/oauth/oauth-client-browser/example/src/login-form.tsx b/packages/oauth/oauth-client-browser/example/src/login-form.tsx new file mode 100644 index 00000000000..6e8b325e560 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/login-form.tsx @@ -0,0 +1,108 @@ +import { FormEvent, useEffect, useState } from 'react' + +/** + * @returns Nice tailwind css form asking to enter either a handle or the host + * to use to login. + */ +export default function LoginForm({ + onLogin, + loading, + error = null, + ...props +}: { + loading?: boolean + error?: null | string + onLogin: (input: string, options?: { display?: 'popup' | 'page' }) => void +} & React.HTMLAttributes) { + const [value, setValue] = useState('') + const [display, setDisplay] = useState<'popup' | 'page'>('popup') + const [localError, setLocalError] = useState(error) + + useEffect(() => { + setLocalError(null) + }, [value]) + + useEffect(() => { + setLocalError(error) + }, [error]) + + const onSubmit = (e: FormEvent) => { + e.preventDefault() + if (loading) return + + if (value.startsWith('did:')) { + if (value.length > 5) onLogin(value, { display }) + else setLocalError('DID must be at least 6 characters') + return + } + + if (value.startsWith('https://')) { + try { + const url = new URL(value) + if (value !== url.origin) throw new Error('PDS URL must be a origin') + onLogin(value, { display }) + } catch (err) { + setLocalError((err as any)?.message || String(err)) + } + return + } + + if (value.startsWith('http://')) { + setLocalError('PDS URL must be a secure origin') + return + } + + if (value.includes('.') && value.length > 3) { + const handle = value.startsWith('@') ? value.slice(1) : value + if (handle.length > 3) onLogin(handle, { display }) + else setLocalError('Handle must be at least 4 characters') + return + } + + setLocalError('Please provide a valid handle, DID or PDS URL') + } + + return ( +
+
+
+ +
+ + {/*
*/} + +
+ setValue(e.target.value)} + /> + +
+
+ + {localError ? ( +
{localError}
+ ) : null} +
+ ) +} diff --git a/packages/oauth/oauth-client-browser/example/src/main.tsx b/packages/oauth/oauth-client-browser/example/src/main.tsx new file mode 100644 index 00000000000..d8db51390fa --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/main.tsx @@ -0,0 +1,12 @@ +import './index.css' + +import React from 'react' +import ReactDOM from 'react-dom/client' + +import App from './app' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/packages/oauth/oauth-client-browser/example/src/oauth.ts b/packages/oauth/oauth-client-browser/example/src/oauth.ts new file mode 100644 index 00000000000..fc8770d4cc7 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/src/oauth.ts @@ -0,0 +1,134 @@ +import { OAuthAuthorizeOptions, OAuthClient } from '@atproto/oauth-client' +import { + BrowserOAuthClientFactory, + LoginContinuedInParentWindowError, +} from '@atproto/oauth-client-browser' +import { oauthClientMetadataSchema } from '@atproto/oauth-types' +import { useCallback, useEffect, useState } from 'react' + +const CURRENT_SESSION_ID_KEY = 'CURRENT_SESSION_ID_KEY' + +export const factory = new BrowserOAuthClientFactory({ + clientMetadata: oauthClientMetadataSchema.parse({ + client_id: 'http://localhost/', + redirect_uris: ['http://127.0.0.1:8080/'], + response_types: ['code id_token', 'code'], + }), + responseMode: 'fragment', + plcDirectoryUrl: 'http://localhost:2582', // dev-env + atprotoLexiconUrl: 'http://localhost:2584', // dev-env (bsky appview) +}) + +// Initializing here since useEffect would execute this twice +const factoryInitPromise = factory.init( + localStorage.getItem(CURRENT_SESSION_ID_KEY) || undefined, +) + +export function useOAuth() { + const [initialized, setInitialized] = useState(false) + const [client, setClient] = useState(void 0) + const [clients, setClients] = useState<{ [_: string]: OAuthClient }>({}) + const [error, setError] = useState(null) + const [loading, setLoading] = useState(true) + const [state, setState] = useState(undefined) + + useEffect(() => { + if (client != null) { + localStorage.setItem(CURRENT_SESSION_ID_KEY, client.sessionId) + } else if (client === null) { + localStorage.removeItem(CURRENT_SESSION_ID_KEY) + } + }, [client]) + + useEffect(() => { + let cleanup = false + setInitialized(false) + setClient(undefined) + setClients({}) + setError(null) + setLoading(true) + setState(undefined) + + factoryInitPromise + .then(async (r) => { + if (cleanup) return + + const clients = await factory.restoreAll().catch((err) => { + console.error('Failed to restore clients:', err) + return {} + }) + + const sessionId = localStorage.getItem(CURRENT_SESSION_ID_KEY) + setInitialized(true) + setClients(clients) + setClient(r?.client || (sessionId && clients[sessionId]) || null) + setState(r?.state) + }) + .catch((err) => { + if (cleanup) return + + localStorage.removeItem(CURRENT_SESSION_ID_KEY) + console.error('Failed to init:', err) + setError(String(err)) + setInitialized(!(err instanceof LoginContinuedInParentWindowError)) + }) + .finally(() => { + if (cleanup) return + + setLoading(false) + }) + + return () => { + cleanup = true + } + }, [factory]) + + const signOut = useCallback(async () => { + if (!client) return + + setClient(null) + setError(null) + setLoading(true) + setState(undefined) + + try { + await client.signOut() + } catch (err) { + console.error('Failed to clear credentials', err) + setError(String(err)) + } finally { + setLoading(false) + } + }, [client]) + + const signIn = useCallback( + async (input: string, options?: OAuthAuthorizeOptions) => { + if (client) return + + setLoading(true) + + try { + const client = await factory.signIn(input, options) + setClient(client) + } catch (err) { + console.error('Failed to login', err) + setError(String(err)) + } finally { + setLoading(false) + } + }, + [client, factory], + ) + + return { + initialized, + clients, + client: client ?? null, + state, + loading, + error, + signedIn: client != null, + signIn, + signOut, + } +} diff --git a/packages/oauth/oauth-client-browser/example/tailwind.config.js b/packages/oauth/oauth-client-browser/example/tailwind.config.js new file mode 100644 index 00000000000..7141e4528c6 --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/packages/oauth/oauth-client-browser/example/tsconfig.build.json b/packages/oauth/oauth-client-browser/example/tsconfig.build.json new file mode 100644 index 00000000000..5d2476f34aa --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": [ + "../../../../tsconfig/browser.json", + "../../../../tsconfig/bundler.json" + ], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src/**/*.ts", "./src/**/*.tsx"] +} diff --git a/packages/oauth/oauth-client-browser/example/tsconfig.json b/packages/oauth/oauth-client-browser/example/tsconfig.json new file mode 100644 index 00000000000..ad9365d269b --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/tsconfig.json @@ -0,0 +1,7 @@ +{ + "include": [], + "references": [ + { "path": "./tsconfig.build.json" }, + { "path": "./tsconfig.tools.json" } + ] +} diff --git a/packages/oauth/oauth-client-browser/example/tsconfig.tools.json b/packages/oauth/oauth-client-browser/example/tsconfig.tools.json new file mode 100644 index 00000000000..1588d29506e --- /dev/null +++ b/packages/oauth/oauth-client-browser/example/tsconfig.tools.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../../tsconfig/node.json", + "compilerOptions": { + "rootDir": ".", + "noEmit": true + }, + "include": ["./*.js", "./*.ts"] +} diff --git a/packages/oauth/oauth-client-browser/package.json b/packages/oauth/oauth-client-browser/package.json new file mode 100644 index 00000000000..1d02b3bd3d9 --- /dev/null +++ b/packages/oauth/oauth-client-browser/package.json @@ -0,0 +1,73 @@ +{ + "name": "@atproto/oauth-client-browser", + "version": "0.0.1", + "license": "MIT", + "description": "ATPROTO OAuth client for the browser (relies on WebCrypto & Indexed DB)", + "keywords": [ + "atproto", + "oauth", + "client", + "browser", + "webcrypto", + "indexed", + "db" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/oauth/oauth-client-browser" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "dependencies": { + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/handle-resolver": "workspace:*", + "@atproto-labs/identity-resolver": "workspace:*", + "@atproto-labs/simple-store": "workspace:*", + "@atproto/did": "workspace:*", + "@atproto/jwk": "workspace:*", + "@atproto/jwk-webcrypto": "workspace:*", + "@atproto/oauth-client": "workspace:*", + "@atproto/oauth-types": "workspace:*", + "tslib": "^2.6.2" + }, + "devDependencies": { + "@atproto/api": "workspace:*", + "@atproto/oauth-client": "workspace:*", + "@atproto/oauth-client-browser": "workspace:*", + "@atproto/oauth-types": "workspace:*", + "@atproto/xrpc": "workspace:*", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-html": "^1.0.3", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^11.1.6", + "@types/react": "^18.2.50", + "@types/react-dom": "^18.2.18", + "autoprefixer": "^10.4.17", + "postcss": "^8.4.33", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rollup": "^4.13.0", + "rollup-plugin-postcss": "^4.0.2", + "rollup-plugin-serve": "^1.1.1", + "tailwindcss": "^3.4.1", + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json", + "dev": "cd ./example && rollup --config rollup.config.js --watch" + } +} diff --git a/packages/oauth/oauth-client-browser/src/browser-oauth-client-factory.ts b/packages/oauth/oauth-client-browser/src/browser-oauth-client-factory.ts new file mode 100644 index 00000000000..59eee1ea054 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/browser-oauth-client-factory.ts @@ -0,0 +1,279 @@ +import { Fetch } from '@atproto-labs/fetch' +import { + UniversalIdentityResolver, + UniversalIdentityResolverOptions, +} from '@atproto-labs/identity-resolver' +import { + OAuthAuthorizeOptions, + OAuthCallbackError, + OAuthClient, + OAuthClientFactory, + Session, +} from '@atproto/oauth-client' +import { + OAuthClientMetadata, + OAuthResponseMode, + oauthClientMetadataSchema, +} from '@atproto/oauth-types' + +import { + BrowserOAuthDatabase, + DatabaseStore, + PopupStateData, +} from './browser-oauth-database.js' +import { CryptoSubtle } from './crypto-subtle.js' +import { LoginContinuedInParentWindowError } from './errors.js' + +export type BrowserOAuthClientFactoryOptions = { + responseMode?: OAuthResponseMode + clientMetadata: OAuthClientMetadata + plcDirectoryUrl?: UniversalIdentityResolverOptions['plcDirectoryUrl'] + atprotoLexiconUrl?: UniversalIdentityResolverOptions['atprotoLexiconUrl'] + fetch?: Fetch + crypto?: Crypto +} + +const POPUP_STATE_PREFIX = '@@oauth-popup-callback:' + +export class BrowserOAuthClientFactory extends OAuthClientFactory { + static async load( + options?: Omit, + ) { + const fetch = options?.fetch ?? globalThis.fetch + const request = new Request('/.well-known/oauth-client-metadata', { + redirect: 'error', + }) + const response = await fetch(request) + const clientMetadata = oauthClientMetadataSchema.parse( + await response.json(), + ) + return new BrowserOAuthClientFactory({ clientMetadata, ...options }) + } + + readonly popupStore: DatabaseStore + readonly sessionStore: DatabaseStore + + private database: BrowserOAuthDatabase + + constructor({ + clientMetadata, + // "fragment" is safer as it is not sent to the server + responseMode = 'fragment', + plcDirectoryUrl, + atprotoLexiconUrl, + crypto = globalThis.crypto, + fetch = globalThis.fetch, + }: BrowserOAuthClientFactoryOptions) { + const database = new BrowserOAuthDatabase() + + super({ + clientMetadata, + responseMode, + fetch, + cryptoImplementation: new CryptoSubtle(crypto), + sessionStore: database.getSessionStore(), + stateStore: database.getStateStore(), + identityResolver: UniversalIdentityResolver.from({ + fetch, + plcDirectoryUrl, + atprotoLexiconUrl, + didCache: database.getDidCache(), + handleCache: database.getHandleCache(), + }), + metadataCache: database.getMetadataCache(), + dpopNonceCache: database.getDpopNonceCache(), + }) + + this.sessionStore = database.getSessionStore() + this.popupStore = database.getPopupStore() + + this.database = database + } + + async restoreAll() { + const sessionIds = await this.sessionStore.getKeys() + return Object.fromEntries( + await Promise.all( + sessionIds.map(async (sessionId) => { + return [sessionId, await this.restore(sessionId, false)] as const + }), + ), + ) + } + + async init(sessionId?: string, refresh?: boolean) { + const signInResult = await this.signInCallback() + if (signInResult) { + return signInResult + } else if (sessionId) { + const client = await this.restore(sessionId, refresh) + return { client } + } else { + // TODO: we could restore any session from the store ? + } + } + + async signIn( + input: string, + options?: OAuthAuthorizeOptions & { signal?: AbortSignal }, + ) { + if (options?.display === 'popup') { + return this.signInPopup(input, options) + } else { + return this.signInRedirect(input, options) + } + } + + async signInRedirect(input: string, options?: OAuthAuthorizeOptions) { + const url = await this.authorize(input, options) + + window.location.href = url.href + + // back-forward cache + return new Promise((resolve, reject) => { + setTimeout(() => reject(new Error('User navigated back')), 5e3) + }) + } + + async signInPopup( + input: string, + options?: Omit & { signal?: AbortSignal }, + ): Promise { + // Open new window asap to prevent popup busting by browsers + const popupFeatures = 'width=600,height=600,menubar=no,toolbar=no' + let popup = window.open('about:blank', '_blank', popupFeatures) + + const stateKey = `${Math.random().toString(36).slice(2)}` + + const url = await this.authorize(input, { + ...options, + state: `${POPUP_STATE_PREFIX}${stateKey}`, + display: options?.display ?? 'popup', + }) + + try { + options?.signal?.throwIfAborted() + + if (popup) { + popup.window.location.href = url.href + } else { + popup = window.open(url.href, '_blank', popupFeatures) + } + + popup?.focus() + + return await new Promise((resolve, reject) => { + const cleanup = () => { + clearInterval(interval) + clearTimeout(timeout) + void this.popupStore.del(stateKey) + options?.signal?.removeEventListener('abort', cancel) + } + + const cancel = () => { + // TODO: Store fact that the request was cancelled, allowing any + // callback to not request credentials (or revoke those obtained) + + reject(new Error(options?.signal?.aborted ? 'Aborted' : 'Timeout')) + cleanup() + } + + options?.signal?.addEventListener('abort', cancel) + + const timeout = setTimeout(cancel, 5 * 60e3) + + const interval = setInterval(async () => { + const result = await this.popupStore.get(stateKey) + if (!result) return + + cleanup() + + if (result.status === 'fulfilled') { + const { sessionId } = result.value + try { + options?.signal?.throwIfAborted() + resolve(await this.restore(sessionId)) + } catch (err) { + reject(err) + void this.revoke(sessionId) + } + } else { + const { message, params } = result.reason + reject(new OAuthCallbackError(new URLSearchParams(params), message)) + } + }, 500) + }) + } finally { + popup?.close() + } + } + + async signInCallback() { + // Only if the current URL is a redirect URI + if ( + this.clientMetadata.redirect_uris.every( + (uri) => new URL(uri).pathname !== location.pathname, + ) + ) { + return null + } + + const params = + this.responseMode === 'fragment' + ? new URLSearchParams(location.hash.slice(1)) + : new URLSearchParams(location.search) + + // Only if the current URL contain oauth response params + if (!params.has('state') || !(params.has('code') || params.has('error'))) { + return null + } + + // Replace the current history entry without the query string (this will + // prevent the following code to run again if the user refreshes the page) + history.replaceState(null, '', location.pathname) + + return this.callback(params) + .then(async (result) => { + if (result.state?.startsWith(POPUP_STATE_PREFIX)) { + const stateKey = result.state.slice(POPUP_STATE_PREFIX.length) + + await this.popupStore.set(stateKey, { + status: 'fulfilled', + value: { + sessionId: result.client.sessionId, + }, + }) + + throw new LoginContinuedInParentWindowError() // signInPopup + } + + return result + }) + .catch(async (err) => { + if ( + err instanceof OAuthCallbackError && + err.state?.startsWith(POPUP_STATE_PREFIX) + ) { + const stateKey = err.state.slice(POPUP_STATE_PREFIX.length) + + await this.popupStore.set(stateKey, { + status: 'rejected', + reason: { + message: err.message, + params: Array.from(err.params.entries()), + }, + }) + + throw new LoginContinuedInParentWindowError() // signInPopup + } + + // Most probable cause at this point is that the "state" parameter is + // invalid. + throw err + }) + } + + async [Symbol.asyncDispose]() { + await this.database[Symbol.asyncDispose]() + } +} diff --git a/packages/oauth/oauth-client-browser/src/browser-oauth-database.ts b/packages/oauth/oauth-client-browser/src/browser-oauth-database.ts new file mode 100644 index 00000000000..0ffb59e0771 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/browser-oauth-database.ts @@ -0,0 +1,265 @@ +import { DidDocument } from '@atproto/did' +import { ResolvedHandle } from '@atproto-labs/handle-resolver' +import { Key } from '@atproto/jwk' +import { WebcryptoKey } from '@atproto/jwk-webcrypto' +import { InternalStateData, Session, TokenSet } from '@atproto/oauth-client' +import { OAuthServerMetadata } from '@atproto/oauth-types' +import { SimpleStore, Value } from '@atproto-labs/simple-store' + +import { DB, DBObjectStore } from './indexed-db/index.js' + +type Item = { + value: V + expiresAt?: string // ISO Date +} + +type EncodedKey = { + keyId: string + keyPair: CryptoKeyPair +} + +function encodeKey(key: Key): EncodedKey { + if (!(key instanceof WebcryptoKey) || !key.kid) { + throw new Error('Invalid key object') + } + return { + keyId: key.kid, + keyPair: key.cryptoKeyPair, + } +} + +async function decodeKey(encoded: EncodedKey): Promise { + return WebcryptoKey.fromKeypair(encoded.keyId, encoded.keyPair) +} + +export type PopupStateData = + | PromiseRejectedResult + | PromiseFulfilledResult<{ + sessionId: string + }> + +export type Schema = { + popup: Item + state: Item<{ + dpopKey: EncodedKey + + iss: string + nonce: string + verifier?: string + appState?: string + }> + session: Item<{ + dpopKey: EncodedKey + + tokenSet: TokenSet + }> + + didCache: Item + dpopNonceCache: Item + handleCache: Item + metadataCache: Item +} + +export type DatabaseStore = SimpleStore & { + getKeys: () => Promise +} + +const STORES = [ + 'popup', + 'state', + 'session', + + 'didCache', + 'dpopNonceCache', + 'handleCache', + 'metadataCache', +] as const + +export type BrowserOAuthDatabaseOptions = { + name?: string + durability?: 'strict' | 'relaxed' + cleanupInterval?: number +} + +export class BrowserOAuthDatabase { + #dbPromise: Promise> + #cleanupInterval?: ReturnType + + constructor(options?: BrowserOAuthDatabaseOptions) { + this.#dbPromise = DB.open( + options?.name ?? '@atproto-oauth-client', + [ + (db) => { + for (const name of STORES) { + const store = db.createObjectStore(name, { autoIncrement: true }) + store.createIndex('expiresAt', 'expiresAt', { unique: false }) + } + }, + ], + { durability: options?.durability ?? 'strict' }, + ) + + this.#cleanupInterval = setInterval(() => { + void this.cleanup() + }, options?.cleanupInterval ?? 30e3) + } + + protected async run( + storeName: N, + mode: 'readonly' | 'readwrite', + fn: (s: DBObjectStore) => R | Promise, + ): Promise { + const db = await this.#dbPromise + return await db.transaction([storeName], mode, (tx) => + fn(tx.objectStore(storeName)), + ) + } + + protected createStore( + name: N, + { + encode, + decode, + expiresAt, + }: { + encode: (value: V) => Schema[N]['value'] | PromiseLike + decode: (encoded: Schema[N]['value']) => V | PromiseLike + expiresAt: (value: V) => null | Date + }, + ): DatabaseStore { + return { + get: async (key) => { + // Find item in store + const item = await this.run(name, 'readonly', (store) => store.get(key)) + + // Not found + if (item === undefined) return undefined + + // Too old (delete) + if (item.expiresAt != null && new Date(item.expiresAt) < new Date()) { + await this.run(name, 'readwrite', (store) => store.delete(key)) + return undefined + } + + // Item found and valid. Decode + return decode(item.value) + }, + + getKeys: async () => { + const keys = await this.run(name, 'readonly', (store) => + store.getAllKeys(), + ) + return keys.filter((key): key is string => typeof key === 'string') + }, + + set: async (key, value) => { + // Create encoded item record + const item = { + value: await encode(value), + expiresAt: expiresAt(value)?.toISOString(), + } as Schema[N] + + // Store item record + await this.run(name, 'readwrite', (store) => store.put(item, key)) + }, + + del: async (key) => { + // Delete + await this.run(name, 'readwrite', (store) => store.delete(key)) + }, + } + } + + getSessionStore(): DatabaseStore { + return this.createStore('session', { + expiresAt: ({ tokenSet }) => + tokenSet.refresh_token || tokenSet.expires_at == null + ? null + : new Date(tokenSet.expires_at), + encode: ({ dpopKey, ...session }) => ({ + ...session, + dpopKey: encodeKey(dpopKey), + }), + decode: async ({ dpopKey, ...encoded }) => ({ + ...encoded, + dpopKey: await decodeKey(dpopKey), + }), + }) + } + + getStateStore(): DatabaseStore { + return this.createStore('state', { + expiresAt: (_value) => new Date(Date.now() + 10 * 60e3), + encode: ({ dpopKey, ...session }) => ({ + ...session, + dpopKey: encodeKey(dpopKey), + }), + decode: async ({ dpopKey, ...encoded }) => ({ + ...encoded, + dpopKey: await decodeKey(dpopKey), + }), + }) + } + + getPopupStore(): DatabaseStore { + return this.createStore('popup', { + expiresAt: (_value) => new Date(Date.now() + 600e3), + encode: (value) => value, + decode: (encoded) => encoded, + }) + } + + getDpopNonceCache(): undefined | DatabaseStore { + return this.createStore('dpopNonceCache', { + expiresAt: (_value) => new Date(Date.now() + 600e3), + encode: (value) => value, + decode: (encoded) => encoded, + }) + } + + getDidCache(): undefined | DatabaseStore { + return this.createStore('didCache', { + expiresAt: (_value) => new Date(Date.now() + 60e3), + encode: (value) => value, + decode: (encoded) => encoded, + }) + } + + getHandleCache(): undefined | DatabaseStore { + return this.createStore('handleCache', { + expiresAt: (_value) => new Date(Date.now() + 60e3), + encode: (value) => value, + decode: (encoded) => encoded, + }) + } + + getMetadataCache(): undefined | DatabaseStore { + return this.createStore('metadataCache', { + expiresAt: (_value) => new Date(Date.now() + 60e3), + encode: (value) => value, + decode: (encoded) => encoded, + }) + } + + async cleanup() { + const db = await this.#dbPromise + + for (const name of STORES) { + await db.transaction([name], 'readwrite', (tx) => + tx + .objectStore(name) + .index('expiresAt') + .deleteAll(IDBKeyRange.upperBound(Date.now())), + ) + } + } + + async [Symbol.asyncDispose]() { + clearInterval(this.#cleanupInterval) + const dbPromise = this.#dbPromise + this.#dbPromise = Promise.reject(new Error('Database has been disposed')) + + const db = await dbPromise + await (db[Symbol.asyncDispose] || db[Symbol.dispose]).call(db) + } +} diff --git a/packages/oauth/oauth-client-browser/src/crypto-subtle.ts b/packages/oauth/oauth-client-browser/src/crypto-subtle.ts new file mode 100644 index 00000000000..28c34adf6a1 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/crypto-subtle.ts @@ -0,0 +1,50 @@ +import { WebcryptoKey } from '@atproto/jwk-webcrypto' +import { + Key, + CryptoImplementation, + DigestAlgorithm, +} from '@atproto/oauth-client' + +export class CryptoSubtle implements CryptoImplementation { + constructor(private crypto: Crypto = globalThis.crypto) { + if (!crypto?.subtle) { + throw new Error( + 'Crypto with CryptoSubtle is required. If running in a browser, make sure the current page is loaded over HTTPS.', + ) + } + } + + async createKey(algs: string[]): Promise { + return WebcryptoKey.generate(undefined, algs) + } + + getRandomValues(byteLength: number): Uint8Array { + const bytes = new Uint8Array(byteLength) + this.crypto.getRandomValues(bytes) + return bytes + } + + async digest( + bytes: Uint8Array, + algorithm: DigestAlgorithm, + ): Promise { + const buffer = await this.crypto.subtle.digest( + digestAlgorithmToSubtle(algorithm), + bytes, + ) + return new Uint8Array(buffer) + } +} + +function digestAlgorithmToSubtle({ + name, +}: DigestAlgorithm): AlgorithmIdentifier { + switch (name) { + case 'sha256': + case 'sha384': + case 'sha512': + return `SHA-${name.slice(-3)}` + default: + throw new Error(`Unknown hash algorithm ${name}`) + } +} diff --git a/packages/oauth/oauth-client-browser/src/disposable-polyfill/README.md b/packages/oauth/oauth-client-browser/src/disposable-polyfill/README.md new file mode 100644 index 00000000000..663d468ebd4 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/disposable-polyfill/README.md @@ -0,0 +1,9 @@ +# Ppolyfill for Symbol.dispose and Symbol.asyncDispose + +While typescript does transpile `using` to `try`/`finally` blocks, it does not +provide a polyfill for the `Symbol.dispose` and `Symbol.asyncDispose` symbols. +This package provides a polyfill for these symbols. + +This _could_ be used as a standalone library, but the Bluesky dev team does not +want to maintain it as such. As it is currently only used by the +`@bluesky/oauth-client-browser` package, it is included here. diff --git a/packages/oauth/oauth-client-browser/src/disposable-polyfill/index.ts b/packages/oauth/oauth-client-browser/src/disposable-polyfill/index.ts new file mode 100644 index 00000000000..ddb9073b163 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/disposable-polyfill/index.ts @@ -0,0 +1,10 @@ +// Code compiled with tsc supports "using" and "await using" syntax. This +// features is supported by downleveling the code to ES2017. The downleveling +// relies on `Symbol.dispose` and `Symbol.asyncDispose` symbols. These symbols +// might not be available in all environments. This package provides a polyfill +// for these symbols. + +// @ts-expect-error +Symbol.dispose ??= Symbol('@@dispose') +// @ts-expect-error +Symbol.asyncDispose ??= Symbol('@@asyncDispose') diff --git a/packages/oauth/oauth-client-browser/src/errors.ts b/packages/oauth/oauth-client-browser/src/errors.ts new file mode 100644 index 00000000000..3059781366e --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/errors.ts @@ -0,0 +1,9 @@ +/** + * Special error class destined to be thrown when the login process was + * performed in a popup and should be continued in the parent/initiating window. + */ +export class LoginContinuedInParentWindowError extends Error { + constructor() { + super('Login complete, please close the popup window.') + } +} diff --git a/packages/oauth/oauth-client-browser/src/index.ts b/packages/oauth/oauth-client-browser/src/index.ts new file mode 100644 index 00000000000..c3620d63434 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/index.ts @@ -0,0 +1,4 @@ +import './disposable-polyfill/index.js' + +export * from './browser-oauth-client-factory.js' +export * from './errors.js' diff --git a/packages/oauth/oauth-client-browser/src/indexed-db-store.ts b/packages/oauth/oauth-client-browser/src/indexed-db-store.ts new file mode 100644 index 00000000000..53980b811e8 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db-store.ts @@ -0,0 +1,79 @@ +import { SimpleStore, Key, Value } from '@atproto-labs/simple-store' +import { DB, DBObjectStore } from './indexed-db/index.js' + +const storeName = 'store' +type Item = { + value: V + createdAt: Date +} + +export class IndexedDBStore< + K extends Extract, + V extends Value, +> implements SimpleStore +{ + constructor( + private dbName: string, + protected maxAge = 600e3, + ) {} + + protected async run( + mode: 'readonly' | 'readwrite', + fn: (s: DBObjectStore>) => R | Promise, + ): Promise { + const db = await DB.open<{ store: Item }>( + this.dbName, + [ + (db) => { + const store = db.createObjectStore(storeName) + store.createIndex('createdAt', 'createdAt', { unique: false }) + }, + ], + { durability: 'strict' }, + ) + try { + return await db.transaction([storeName], mode, (tx) => + fn(tx.objectStore(storeName)), + ) + } finally { + await db[Symbol.dispose]() + } + } + + async get(key: K): Promise { + const item = await this.run('readonly', (store) => store.get(key)) + + if (!item) return undefined + + const age = Date.now() - item.createdAt.getTime() + if (age > this.maxAge) { + await this.del(key) + return undefined + } + + return item?.value + } + + async set(key: K, value: V): Promise { + await this.run('readwrite', (store) => { + store.put({ value, createdAt: new Date() }, key) + }) + } + + async del(key: K): Promise { + await this.run('readwrite', (store) => { + store.delete(key) + }) + } + + async deleteOutdated() { + const upperBound = new Date(Date.now() - this.maxAge) + const query = IDBKeyRange.upperBound(upperBound) + + await this.run('readwrite', async (store) => { + const index = store.index('createdAt') + const keys = await index.getAllKeys(query) + for (const key of keys) store.delete(key) + }) + } +} diff --git a/packages/oauth/oauth-client-browser/src/indexed-db/README.md b/packages/oauth/oauth-client-browser/src/indexed-db/README.md new file mode 100644 index 00000000000..41f90bed276 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db/README.md @@ -0,0 +1,8 @@ +# IndexedDB utilities + +This is a small wrapper around the IndexedDB API that provides a simple way to +store and retrieve data from an IndexedDB database. + +This _could_ be used as a standalone library, but the Bluesky dev team does not +want to maintain it as such. As it is currently only used by the +`@bluesky/oauth-client-browser` package, it is included here. diff --git a/packages/oauth/oauth-client-browser/src/indexed-db/db-index.ts b/packages/oauth/oauth-client-browser/src/indexed-db/db-index.ts new file mode 100644 index 00000000000..dc041f024b5 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db/db-index.ts @@ -0,0 +1,44 @@ +import { ObjectStoreSchema } from './schema.js' +import { promisify } from './util.js' + +export class DBIndex { + constructor(private idbIndex: IDBIndex) {} + + count(query?: IDBValidKey | IDBKeyRange) { + return promisify(this.idbIndex.count(query)) + } + + get(query: IDBValidKey | IDBKeyRange) { + return promisify(this.idbIndex.get(query)) + } + + getKey(query: IDBValidKey | IDBKeyRange) { + return promisify(this.idbIndex.getKey(query)) + } + + getAll(query?: IDBValidKey | IDBKeyRange | null, count?: number) { + return promisify(this.idbIndex.getAll(query, count)) + } + + getAllKeys(query?: IDBValidKey | IDBKeyRange | null, count?: number) { + return promisify(this.idbIndex.getAllKeys(query, count)) + } + + deleteAll(query?: IDBValidKey | IDBKeyRange | null): Promise { + return new Promise((resolve, reject) => { + const result = this.idbIndex.openCursor(query) + result.onsuccess = function (event) { + const cursor = (event as any).target.result as IDBCursorWithValue + if (cursor) { + cursor.delete() + cursor.continue() + } else { + resolve() + } + } + result.onerror = function (event) { + reject((event.target as any)?.error || new Error('Unexpected error')) + } + }) + } +} diff --git a/packages/oauth/oauth-client-browser/src/indexed-db/db-object-store.ts b/packages/oauth/oauth-client-browser/src/indexed-db/db-object-store.ts new file mode 100644 index 00000000000..9b15fcad6a9 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db/db-object-store.ts @@ -0,0 +1,47 @@ +import { DBIndex } from './db-index.js' +import { ObjectStoreSchema } from './schema.js' +import { promisify } from './util.js' + +export class DBObjectStore { + constructor(private idbObjStore: IDBObjectStore) {} + + get name() { + return this.idbObjStore.name + } + + index(name: string) { + return new DBIndex(this.idbObjStore.index(name)) + } + + get(key: IDBValidKey | IDBKeyRange) { + return promisify(this.idbObjStore.get(key)) + } + + getKey(query: IDBValidKey | IDBKeyRange) { + return promisify(this.idbObjStore.getKey(query)) + } + + getAll(query?: IDBValidKey | IDBKeyRange | null, count?: number) { + return promisify(this.idbObjStore.getAll(query, count)) + } + + getAllKeys(query?: IDBValidKey | IDBKeyRange | null, count?: number) { + return promisify(this.idbObjStore.getAllKeys(query, count)) + } + + add(value: Schema, key?: IDBValidKey) { + return promisify(this.idbObjStore.add(value, key)) + } + + put(value: Schema, key?: IDBValidKey) { + return promisify(this.idbObjStore.put(value, key)) + } + + delete(key: IDBValidKey | IDBKeyRange) { + return promisify(this.idbObjStore.delete(key)) + } + + clear() { + return promisify(this.idbObjStore.clear()) + } +} diff --git a/packages/oauth/oauth-client-browser/src/indexed-db/db-transaction.ts b/packages/oauth/oauth-client-browser/src/indexed-db/db-transaction.ts new file mode 100644 index 00000000000..79905e796e8 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db/db-transaction.ts @@ -0,0 +1,52 @@ +import { DBObjectStore } from './db-object-store.js' +import { DatabaseSchema } from './schema.js' + +export class DBTransaction + implements Disposable +{ + #tx: IDBTransaction | null + + constructor(tx: IDBTransaction) { + this.#tx = tx + + const onAbort = () => { + cleanup() + } + const onComplete = () => { + cleanup() + } + const cleanup = () => { + this.#tx = null + tx.removeEventListener('abort', onAbort) + tx.removeEventListener('complete', onComplete) + } + tx.addEventListener('abort', onAbort) + tx.addEventListener('complete', onComplete) + } + + protected get tx(): IDBTransaction { + if (!this.#tx) throw new Error('Transaction already ended') + return this.#tx + } + + async abort() { + const { tx } = this + this.#tx = null + tx.abort() + } + + async commit() { + const { tx } = this + this.#tx = null + tx.commit?.() + } + + objectStore(name: T) { + const store = this.tx.objectStore(name) + return new DBObjectStore(store) + } + + [Symbol.dispose](): void { + if (this.#tx) this.commit() + } +} diff --git a/packages/oauth/oauth-client-browser/src/indexed-db/db.ts b/packages/oauth/oauth-client-browser/src/indexed-db/db.ts new file mode 100644 index 00000000000..03ffe61d8c2 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db/db.ts @@ -0,0 +1,114 @@ +import { DatabaseSchema } from './schema.js' +import { DBTransaction } from './db-transaction.js' + +export class DB implements Disposable { + static async open( + dbName: string, + migrations: ReadonlyArray<(db: IDBDatabase) => void>, + txOptions?: IDBTransactionOptions, + ) { + const db = await new Promise((resolve, reject) => { + const request = indexedDB.open(dbName, migrations.length) + + request.onerror = () => reject(request.error) + request.onsuccess = () => resolve(request.result) + request.onupgradeneeded = ({ oldVersion, newVersion }) => { + const db = request.result + try { + for ( + let version = oldVersion; + version < (newVersion ?? migrations.length); + ++version + ) { + const migration = migrations[version] + if (migration) migration(db) + else throw new Error(`Missing migration for version ${version}`) + } + } catch (err) { + db.close() + reject(err) + } + } + }) + + return new DB(db, txOptions) + } + + #db: null | IDBDatabase + + constructor( + db: IDBDatabase, + protected readonly txOptions?: IDBTransactionOptions, + ) { + this.#db = db + + const cleanup = () => { + this.#db = null + db.removeEventListener('versionchange', cleanup) + db.removeEventListener('close', cleanup) + db.close() // Can we call close on a "closed" database? + } + + db.addEventListener('versionchange', cleanup) + db.addEventListener('close', cleanup) + } + + protected get db(): IDBDatabase { + if (!this.#db) throw new Error('Database closed') + return this.#db + } + + get name() { + return this.db.name + } + + get objectStoreNames() { + return this.db.objectStoreNames + } + + get version() { + return this.db.version + } + + async transaction( + storeNames: T, + mode: IDBTransactionMode, + run: (tx: DBTransaction>) => R | PromiseLike, + ): Promise { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { + try { + const tx = this.db.transaction(storeNames, mode, this.txOptions) + let result: { done: false } | { done: true; value: R } = { done: false } + + tx.oncomplete = () => { + if (result.done) resolve(result.value) + else reject(new Error('Transaction completed without result')) + } + tx.onerror = () => reject(tx.error) + tx.onabort = () => reject(tx.error || new Error('Transaction aborted')) + + try { + const value = await run(new DBTransaction(tx)) + result = { done: true, value } + tx.commit() + } catch (err) { + tx.abort() + throw err + } + } catch (err) { + reject(err) + } + }) + } + + close() { + const { db } = this + this.#db = null + db.close() + } + + [Symbol.dispose]() { + if (this.#db) return this.close() + } +} diff --git a/packages/oauth/oauth-client-browser/src/indexed-db/index.ts b/packages/oauth/oauth-client-browser/src/indexed-db/index.ts new file mode 100644 index 00000000000..c14ed746899 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db/index.ts @@ -0,0 +1,6 @@ +import '../disposable-polyfill/index.js' + +export * from './db.js' +export * from './db-index.js' +export * from './db-object-store.js' +export * from './db-transaction.js' diff --git a/packages/oauth/oauth-client-browser/src/indexed-db/schema.ts b/packages/oauth/oauth-client-browser/src/indexed-db/schema.ts new file mode 100644 index 00000000000..f8736b2a19d --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db/schema.ts @@ -0,0 +1,2 @@ +export type ObjectStoreSchema = NonNullable +export type DatabaseSchema = Record diff --git a/packages/oauth/oauth-client-browser/src/indexed-db/util.ts b/packages/oauth/oauth-client-browser/src/indexed-db/util.ts new file mode 100644 index 00000000000..6e52b5919c4 --- /dev/null +++ b/packages/oauth/oauth-client-browser/src/indexed-db/util.ts @@ -0,0 +1,20 @@ +export function promisify(request: IDBRequest) { + const promise = new Promise((resolve, reject) => { + const cleanup = () => { + request.removeEventListener('success', success) + request.removeEventListener('error', error) + } + const success = () => { + resolve(request.result) + cleanup() + } + const error = () => { + reject(request.error) + cleanup() + } + request.addEventListener('success', success) + request.addEventListener('error', error) + }) + + return promise +} diff --git a/packages/oauth/oauth-client-browser/tsconfig.build.json b/packages/oauth/oauth-client-browser/tsconfig.build.json new file mode 100644 index 00000000000..2ef4f334355 --- /dev/null +++ b/packages/oauth/oauth-client-browser/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig/isomorphic.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/oauth/oauth-client-browser/tsconfig.json b/packages/oauth/oauth-client-browser/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/oauth/oauth-client-browser/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/oauth/oauth-client-react-native/android/build.gradle b/packages/oauth/oauth-client-react-native/android/build.gradle new file mode 100644 index 00000000000..dcd6fbb4e7b --- /dev/null +++ b/packages/oauth/oauth-client-react-native/android/build.gradle @@ -0,0 +1,94 @@ +buildscript { + // Buildscript is evaluated before everything else so we can't use getExtOrDefault + def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["OauthClientReactNative_kotlinVersion"] + + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:7.2.1" + // noinspection DifferentKotlinGradleVersion + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +apply plugin: "com.android.library" +apply plugin: "kotlin-android" + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} + +def getExtOrDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["OauthClientReactNative_" + name] +} + +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["OauthClientReactNative_" + name]).toInteger() +} + +def supportsNamespace() { + def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') + def major = parsed[0].toInteger() + def minor = parsed[1].toInteger() + + // Namespace support was added in 7.3.0 + return (major == 7 && minor >= 3) || major >= 8 +} + +android { + if (supportsNamespace()) { + namespace "com.oauthclientreactnative" + + sourceSets { + main { + manifest.srcFile "src/main/AndroidManifestNew.xml" + } + } + } + + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + + defaultConfig { + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + + } + + buildTypes { + release { + minifyEnabled false + } + } + + lintOptions { + disable "GradleCompatible" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +repositories { + mavenCentral() + google() +} + +def kotlin_version = getExtOrDefault("kotlinVersion") + +dependencies { + // For < 0.71, this will be from the local maven repo + // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" +} + diff --git a/packages/oauth/oauth-client-react-native/android/gradle.properties b/packages/oauth/oauth-client-react-native/android/gradle.properties new file mode 100644 index 00000000000..f1a05979111 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/android/gradle.properties @@ -0,0 +1,5 @@ +OauthClientReactNative_kotlinVersion=1.7.0 +OauthClientReactNative_minSdkVersion=21 +OauthClientReactNative_targetSdkVersion=31 +OauthClientReactNative_compileSdkVersion=31 +OauthClientReactNative_ndkversion=21.4.7075529 diff --git a/packages/oauth/oauth-client-react-native/android/src/main/AndroidManifest.xml b/packages/oauth/oauth-client-react-native/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..166700ad015 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/oauth/oauth-client-react-native/android/src/main/AndroidManifestNew.xml b/packages/oauth/oauth-client-react-native/android/src/main/AndroidManifestNew.xml new file mode 100644 index 00000000000..a2f47b6057d --- /dev/null +++ b/packages/oauth/oauth-client-react-native/android/src/main/AndroidManifestNew.xml @@ -0,0 +1,2 @@ + + diff --git a/packages/oauth/oauth-client-react-native/android/src/main/java/com/oauthclientreactnative/OauthClientReactNativeModule.kt b/packages/oauth/oauth-client-react-native/android/src/main/java/com/oauthclientreactnative/OauthClientReactNativeModule.kt new file mode 100644 index 00000000000..94320ac715e --- /dev/null +++ b/packages/oauth/oauth-client-react-native/android/src/main/java/com/oauthclientreactnative/OauthClientReactNativeModule.kt @@ -0,0 +1,25 @@ +package com.oauthclientreactnative + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.bridge.Promise + +class OauthClientReactNativeModule(reactContext: ReactApplicationContext) : + ReactContextBaseJavaModule(reactContext) { + + override fun getName(): String { + return NAME + } + + // Example method + // See https://reactnative.dev/docs/native-modules-android + @ReactMethod + fun multiply(a: Double, b: Double, promise: Promise) { + promise.resolve(a * b) + } + + companion object { + const val NAME = "OauthClientReactNative" + } +} diff --git a/packages/oauth/oauth-client-react-native/android/src/main/java/com/oauthclientreactnative/OauthClientReactNativePackage.kt b/packages/oauth/oauth-client-react-native/android/src/main/java/com/oauthclientreactnative/OauthClientReactNativePackage.kt new file mode 100644 index 00000000000..faa30d9036e --- /dev/null +++ b/packages/oauth/oauth-client-react-native/android/src/main/java/com/oauthclientreactnative/OauthClientReactNativePackage.kt @@ -0,0 +1,17 @@ +package com.oauthclientreactnative + +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ViewManager + + +class OauthClientReactNativePackage : ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): List { + return listOf(OauthClientReactNativeModule(reactContext)) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): List> { + return emptyList() + } +} diff --git a/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative-Bridging-Header.h b/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative-Bridging-Header.h new file mode 100644 index 00000000000..dea7ff6bf0c --- /dev/null +++ b/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative-Bridging-Header.h @@ -0,0 +1,2 @@ +#import +#import diff --git a/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative.mm b/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative.mm new file mode 100644 index 00000000000..089360a5219 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative.mm @@ -0,0 +1,14 @@ +#import + +@interface RCT_EXTERN_MODULE(OauthClientReactNative, NSObject) + +RCT_EXTERN_METHOD(multiply:(float)a withB:(float)b + withResolver:(RCTPromiseResolveBlock)resolve + withRejecter:(RCTPromiseRejectBlock)reject) + ++ (BOOL)requiresMainQueueSetup +{ + return NO; +} + +@end diff --git a/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative.swift b/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative.swift new file mode 100644 index 00000000000..1c8fe483e80 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/ios/OauthClientReactNative.swift @@ -0,0 +1,8 @@ +@objc(OauthClientReactNative) +class OauthClientReactNative: NSObject { + + @objc(multiply:withB:withResolver:withRejecter:) + func multiply(a: Float, b: Float, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void { + resolve(a*b) + } +} diff --git a/packages/oauth/oauth-client-react-native/package.json b/packages/oauth/oauth-client-react-native/package.json new file mode 100644 index 00000000000..441b1728d29 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/package.json @@ -0,0 +1,41 @@ +{ + "name": "@atproto/oauth-client-react-native", + "version": "0.0.1", + "license": "MIT", + "description": "Implementation of ATPROTO OAuth client for react-native", + "keywords": [ + "atproto", + "oauth", + "client", + "react-native" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/oauth/oauth-client-react-native" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "dependencies": { + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/handle-resolver": "workspace:*", + "@atproto-labs/identity-resolver": "workspace:*", + "@atproto-labs/simple-store": "workspace:*", + "@atproto/jwk": "workspace:*", + "@atproto/oauth-client": "workspace:*", + "@atproto/oauth-types": "workspace:*", + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/oauth/oauth-client-react-native/src/index.ts b/packages/oauth/oauth-client-react-native/src/index.ts new file mode 100644 index 00000000000..4b33dff2570 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/src/index.ts @@ -0,0 +1 @@ +export * from './react-native-oauth-client-factory.js' diff --git a/packages/oauth/oauth-client-react-native/src/oauth-client-react-native.ts b/packages/oauth/oauth-client-react-native/src/oauth-client-react-native.ts new file mode 100644 index 00000000000..2955c465264 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/src/oauth-client-react-native.ts @@ -0,0 +1,54 @@ +import { Jwk, Jwt } from '@atproto/jwk' +import { NativeModules, Platform } from 'react-native' + +const LINKING_ERROR = + `The package 'oauth-client-react-native' doesn't seem to be linked. Make sure: \n\n` + + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + + '- You rebuilt the app after installing the package\n' + + '- You are not using Expo Go\n' + +type Awaitable = T | Promise + +// This is a stub for the native module. It is used when the module is not +// linked AND to provide types. +export const OauthClientReactNative = + (NativeModules.OauthClientReactNative as null) || { + getRandomValues(_length: number): Awaitable { + throw new Error(LINKING_ERROR) + }, + + /** + * @throws if the algorithm is not supported ("sha256" must be supported) + */ + digest(_bytes: Uint8Array, _algorithm: string): Awaitable { + throw new Error(LINKING_ERROR) + }, + + /** + * Create a private JWK for the given algorithm. The JWK should have a "use" + * an does not need a "kid" property. + * + * @throws if the algorithm is not supported ("ES256" must be supported) + */ + generateJwk(_algo: string): Awaitable { + throw new Error(LINKING_ERROR) + }, + + createJwt( + _header: unknown, + _payload: unknown, + _jwk: unknown, + ): Awaitable { + throw new Error(LINKING_ERROR) + }, + + verifyJwt( + _token: Jwt, + _jwk: Jwk, + ): Awaitable<{ + payload: Record + protectedHeader: Record + }> { + throw new Error(LINKING_ERROR) + }, + } diff --git a/packages/oauth/oauth-client-react-native/src/react-native-crypto-implementation.ts b/packages/oauth/oauth-client-react-native/src/react-native-crypto-implementation.ts new file mode 100644 index 00000000000..863304dd244 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/src/react-native-crypto-implementation.ts @@ -0,0 +1,31 @@ +import { + CryptoImplementation, + DigestAlgorithm, + Key, +} from '@atproto/oauth-client' + +import { OauthClientReactNative } from './oauth-client-react-native.js' +import { ReactNativeKey } from './react-native-key.js' + +export class ReactNativeCryptoImplementation implements CryptoImplementation { + async createKey(algs: string[]): Promise { + const bytes = await this.getRandomValues(12) + const kid = Array.from(bytes, byteToHex).join('') + return ReactNativeKey.generate(kid, algs) + } + + async getRandomValues(length: number): Promise { + return OauthClientReactNative.getRandomValues(length) + } + + async digest( + bytes: Uint8Array, + algorithm: DigestAlgorithm, + ): Promise { + return OauthClientReactNative.digest(bytes, algorithm.name) + } +} + +function byteToHex(b: number): string { + return b.toString(16).padStart(2, '0') +} diff --git a/packages/oauth/oauth-client-react-native/src/react-native-key.ts b/packages/oauth/oauth-client-react-native/src/react-native-key.ts new file mode 100644 index 00000000000..426f63331ae --- /dev/null +++ b/packages/oauth/oauth-client-react-native/src/react-native-key.ts @@ -0,0 +1,113 @@ +import { + Jwt, + JwtHeader, + JwtPayload, + Key, + VerifyOptions, + VerifyPayload, + VerifyResult, + jwkValidator, + jwtHeaderSchema, + jwtPayloadSchema, +} from '@atproto/jwk' + +import { OauthClientReactNative } from './oauth-client-react-native.js' + +export class ReactNativeKey extends Key { + static async generate( + kid: string, + allowedAlgos: string[], + ): Promise { + for (const algo of allowedAlgos) { + try { + // Note: OauthClientReactNative.generatePrivateJwk should throw if it + // doesn't support the algorithm. + const jwk = await OauthClientReactNative.generateJwk(algo) + const use = jwk.use || 'sig' + return new ReactNativeKey(jwkValidator.parse({ ...jwk, use, kid })) + } catch { + // Ignore, try next one + } + } + + throw new Error('No supported algorithms') + } + + async createJwt(header: JwtHeader, payload: JwtPayload): Promise { + return OauthClientReactNative.createJwt(header, payload, this.jwk) + } + + async verifyJwt< + P extends VerifyPayload = JwtPayload, + C extends string = string, + >(token: Jwt, options?: VerifyOptions): Promise> { + const result = await OauthClientReactNative.verifyJwt(token, this.jwk) + + const payload = jwtPayloadSchema.parse(result.payload) + const protectedHeader = jwtHeaderSchema.parse(result.protectedHeader) + + if (options?.audience != null) { + const audience = Array.isArray(options.audience) + ? options.audience + : [options.audience] + if (!audience.includes(payload.aud)) { + throw new Error('Invalid audience') + } + } + + if (options?.issuer != null) { + const issuer = Array.isArray(options.issuer) + ? options.issuer + : [options.issuer] + if (!issuer.includes(payload.iss)) { + throw new Error('Invalid issuer') + } + } + + if (options?.subject != null && payload.sub !== options.subject) { + throw new Error('Invalid subject') + } + + if (options?.typ != null && protectedHeader.typ !== options.typ) { + throw new Error('Invalid type') + } + + if (options?.requiredClaims != null) { + for (const key of options.requiredClaims) { + if ( + !Object.hasOwn(payload, key) || + (payload as Record)[key] === undefined + ) { + throw new Error(`Missing claim: ${key}`) + } + } + } + + if (payload.iat == null) { + throw new Error('Missing issued at') + } + + const now = (options?.currentDate?.getTime() ?? Date.now()) / 1e3 + const clockTolerance = options?.clockTolerance ?? 0 + + if (options?.maxTokenAge != null) { + if (payload.iat < now - options.maxTokenAge + clockTolerance) { + throw new Error('Invalid issued at') + } + } + + if (payload.nbf != null) { + if (payload.nbf > now - clockTolerance) { + throw new Error('Invalid not before') + } + } + + if (payload.exp != null) { + if (payload.exp < now + clockTolerance) { + throw new Error('Invalid expiration') + } + } + + return { payload, protectedHeader } as VerifyResult + } +} diff --git a/packages/oauth/oauth-client-react-native/src/react-native-oauth-client-factory.ts b/packages/oauth/oauth-client-react-native/src/react-native-oauth-client-factory.ts new file mode 100644 index 00000000000..96e57034389 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/src/react-native-oauth-client-factory.ts @@ -0,0 +1,85 @@ +import { Fetch } from '@atproto-labs/fetch' +import { + DidDocument, + ResolvedHandle, + UniversalIdentityResolver, + UniversalIdentityResolverOptions, +} from '@atproto-labs/identity-resolver' +import { + InternalStateData, + OAuthAuthorizeOptions, + OAuthClientFactory, + Session, +} from '@atproto/oauth-client' +import { OAuthClientMetadata, OAuthServerMetadata } from '@atproto/oauth-types' + +import { ReactNativeCryptoImplementation } from './react-native-crypto-implementation.js' +import { ReactNativeStoreWithKey } from './react-native-store-with-key.js' +import { ReactNativeStore } from './react-native-store.js' + +export type ReactNativeOAuthClientFactoryOptions = { + clientMetadata: OAuthClientMetadata + plcDirectoryUrl?: UniversalIdentityResolverOptions['plcDirectoryUrl'] + atprotoLexiconUrl?: UniversalIdentityResolverOptions['atprotoLexiconUrl'] + fetch?: Fetch +} + +export class ReactNativeOAuthClientFactory extends OAuthClientFactory { + constructor({ + clientMetadata, + plcDirectoryUrl, + atprotoLexiconUrl, + }: ReactNativeOAuthClientFactoryOptions) { + super({ + clientMetadata, + responseMode: 'query', + fetch, + cryptoImplementation: new ReactNativeCryptoImplementation(), + sessionStore: new ReactNativeStoreWithKey(({ tokenSet }) => + tokenSet.refresh_token || !tokenSet.expires_at + ? null + : new Date(tokenSet.expires_at), + ), + stateStore: new ReactNativeStoreWithKey( + () => new Date(Date.now() + 600e3), + ), + identityResolver: UniversalIdentityResolver.from({ + fetch, + plcDirectoryUrl, + atprotoLexiconUrl, + didCache: new ReactNativeStore( + () => new Date(Date.now() + 60e3), + ), + handleCache: new ReactNativeStore( + () => new Date(Date.now() + 60e3), + ), + }), + metadataCache: new ReactNativeStore( + () => new Date(Date.now() + 60e3), + ), + dpopNonceCache: new ReactNativeStore( + () => new Date(Date.now() + 600e3), + ), + }) + } + + async signIn( + input: string, + options?: OAuthAuthorizeOptions & { signal?: AbortSignal }, + ) { + const url = await this.authorize(input, options) + const params = await this.openNativeLoginUi(url) + const { client } = await this.callback(params) + return client + } + + async openNativeLoginUi(url: URL): Promise { + // TODO: implement this + return new URLSearchParams({ + error: 'invalid_request', + error_description: 'Not implemented', + state: url.searchParams.get('state') ?? '', + issuer: url.searchParams.get('iss') ?? '', + }) + } +} diff --git a/packages/oauth/oauth-client-react-native/src/react-native-store-with-key.ts b/packages/oauth/oauth-client-react-native/src/react-native-store-with-key.ts new file mode 100644 index 00000000000..d0bd7ee61bf --- /dev/null +++ b/packages/oauth/oauth-client-react-native/src/react-native-store-with-key.ts @@ -0,0 +1,50 @@ +import { SimpleStore, Value } from '@atproto-labs/simple-store' +import { Jwk } from '@atproto/jwk' +import { ReactNativeKey } from './react-native-key.js' +import { ReactNativeStore } from './react-native-store.js' + +type ExposedValue = Value & { dpopKey: ReactNativeKey } +type StoredValue = Omit & { + dpopKey: Jwk +} + +/** + * Uses a {@link ReactNativeStore} to store values that contain a + * {@link ReactNativeKey} as `dpopKey` property. This works by serializing the + * {@link Key} to a JWK before storing it, and deserializing it back to a + * {@link ReactNativeKey} when retrieving the value. + */ +export class ReactNativeStoreWithKey + implements SimpleStore +{ + internalStore: ReactNativeStore> + + constructor( + protected valueExpiresAt: (value: StoredValue) => null | Date, + ) { + this.internalStore = new ReactNativeStore(valueExpiresAt) + } + + async set(key: string, value: V): Promise { + const { dpopKey, ...rest } = value + if (!dpopKey.privateJwk) throw new Error('dpopKey.privateJwk is required') + await this.internalStore.set(key, { + ...rest, + dpopKey: dpopKey.privateJwk, + }) + } + + async get(key: string): Promise { + const value = await this.internalStore.get(key) + if (!value) return undefined + + return { + ...value, + dpopKey: new ReactNativeKey(value.dpopKey), + } as V + } + + async del(key: string): Promise { + await this.internalStore.del(key) + } +} diff --git a/packages/oauth/oauth-client-react-native/src/react-native-store.ts b/packages/oauth/oauth-client-react-native/src/react-native-store.ts new file mode 100644 index 00000000000..9c94ee8b8d1 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/src/react-native-store.ts @@ -0,0 +1,28 @@ +import { SimpleStore, Value } from '@atproto-labs/simple-store' + +// TODO: implement this using the app's safe storage +export class ReactNativeStore + implements SimpleStore +{ + constructor( + /** + * Allows defining, at storage time, when the value should expire. This + * allows the store to automatically delete the values when they expire. + */ + protected valueExpiresAt: (value: V) => null | Date, + ) { + throw new Error('Not implemented') + } + + async get(key: string): Promise { + throw new Error('Not implemented') + } + + async set(key: string, value: V): Promise { + throw new Error('Not implemented') + } + + async del(key: string): Promise { + throw new Error('Not implemented') + } +} diff --git a/packages/oauth/oauth-client-react-native/tsconfig.build.json b/packages/oauth/oauth-client-react-native/tsconfig.build.json new file mode 100644 index 00000000000..511d1b3e64d --- /dev/null +++ b/packages/oauth/oauth-client-react-native/tsconfig.build.json @@ -0,0 +1,36 @@ +{ + "extends": "../../../tsconfig/base.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "target": "esnext", + "module": "commonjs", + "types": ["react-native"], + "lib": [ + "es2019", + "es2020.bigint", + "es2020.date", + "es2020.number", + "es2020.promise", + "es2020.string", + "es2020.symbol.wellknown", + "es2021.promise", + "es2021.string", + "es2021.weakref", + "es2022.array", + "es2022.object", + "es2022.string" + ], + "allowJs": true, + "jsx": "react-native", + "noEmit": true, + "isolatedModules": true, + "strict": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["./src"] +} diff --git a/packages/oauth/oauth-client-react-native/tsconfig.json b/packages/oauth/oauth-client-react-native/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/oauth/oauth-client-react-native/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/oauth/oauth-client/README.md b/packages/oauth/oauth-client/README.md new file mode 100644 index 00000000000..6fb706cc5c5 --- /dev/null +++ b/packages/oauth/oauth-client/README.md @@ -0,0 +1 @@ +# @atproto/oauth-client: atproto flavoured OAuth client diff --git a/packages/oauth/oauth-client/package.json b/packages/oauth/oauth-client/package.json new file mode 100644 index 00000000000..58f63266907 --- /dev/null +++ b/packages/oauth/oauth-client/package.json @@ -0,0 +1,45 @@ +{ + "name": "@atproto/oauth-client", + "version": "0.0.1", + "license": "MIT", + "description": "OAuth client for ATPROTO PDS. This package serves as common base for environment-specific implementations (NodeJS, Browser, React-Native).", + "keywords": [ + "atproto", + "oauth", + "client", + "isomorphic" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/oauth/oauth-client" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/handle-resolver": "workspace:*", + "@atproto-labs/identity-resolver": "workspace:*", + "@atproto-labs/simple-store": "workspace:*", + "@atproto-labs/simple-store-memory": "workspace:*", + "@atproto/did": "workspace:*", + "@atproto/jwk": "workspace:*", + "@atproto/oauth-types": "workspace:*", + "multiformats": "^9.9.0", + "tslib": "^2.6.2" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/oauth/oauth-client/src/constants.ts b/packages/oauth/oauth-client/src/constants.ts new file mode 100644 index 00000000000..2ee644cde0f --- /dev/null +++ b/packages/oauth/oauth-client/src/constants.ts @@ -0,0 +1,4 @@ +/** + * Per ATProto spec (OpenID uses RS256) + */ +export const FALLBACK_ALG = 'ES256' diff --git a/packages/oauth/oauth-client/src/crypto-implementation.ts b/packages/oauth/oauth-client/src/crypto-implementation.ts new file mode 100644 index 00000000000..3812b844bb0 --- /dev/null +++ b/packages/oauth/oauth-client/src/crypto-implementation.ts @@ -0,0 +1,16 @@ +import { Key } from '@atproto/jwk' + +export type DigestAlgorithm = { + name: 'sha256' | 'sha384' | 'sha512' +} + +export type { Key } + +export interface CryptoImplementation { + createKey(algs: string[]): Promise + getRandomValues: (length: number) => Uint8Array | PromiseLike + digest: ( + bytes: Uint8Array, + algorithm: DigestAlgorithm, + ) => Uint8Array | PromiseLike +} diff --git a/packages/oauth/oauth-client/src/crypto-wrapper.ts b/packages/oauth/oauth-client/src/crypto-wrapper.ts new file mode 100644 index 00000000000..72b1bda7c4b --- /dev/null +++ b/packages/oauth/oauth-client/src/crypto-wrapper.ts @@ -0,0 +1,193 @@ +import { base64url } from 'multiformats/bases/base64' +import { JwtHeader, JwtPayload, Key, unsafeDecodeJwt } from '@atproto/jwk' +import { + CryptoImplementation, + DigestAlgorithm, +} from './crypto-implementation.js' + +export class CryptoWrapper { + constructor(protected implementation: CryptoImplementation) {} + + public async generateKey(algs: string[]): Promise { + const algsSorted = Array.from(algs).sort(compareAlgos) + return this.implementation.createKey(algsSorted) + } + + public async sha256(text: string): Promise { + const bytes = new TextEncoder().encode(text) + const digest = await this.implementation.digest(bytes, { name: 'sha256' }) + return base64url.baseEncode(digest) + } + + public async generateNonce(length = 16): Promise { + const bytes = await this.implementation.getRandomValues(length) + return base64url.baseEncode(bytes) + } + + public async validateIdTokenClaims( + token: string, + state: string, + nonce: string, + code?: string, + accessToken?: string, + ): Promise<{ + header: JwtHeader + payload: JwtPayload + }> { + // It's fine to use unsafeDecodeJwt here because the token was received from + // the server's token endpoint. The following checks are to ensure that the + // oauth flow was indeed initiated by the client. + const { header, payload } = unsafeDecodeJwt(token) + if (!payload.nonce || payload.nonce !== nonce) { + throw new TypeError('Nonce mismatch') + } + if (payload.c_hash) { + await this.validateHashClaim(payload.c_hash, code, header) + } + if (payload.s_hash) { + await this.validateHashClaim(payload.s_hash, state, header) + } + if (payload.at_hash) { + await this.validateHashClaim(payload.at_hash, accessToken, header) + } + return { header, payload } + } + + private async validateHashClaim( + claim: unknown, + source: unknown, + header: { alg: string; crv?: string }, + ): Promise { + if (typeof claim !== 'string' || !claim) { + throw new TypeError(`string "_hash" claim expected`) + } + if (typeof source !== 'string' || !source) { + throw new TypeError(`string value expected`) + } + const expected = await this.generateHashClaim(source, header) + if (expected !== claim) { + throw new TypeError(`"_hash" does not match`) + } + } + + protected async generateHashClaim( + source: string, + header: { alg: string; crv?: string }, + ) { + const algo = getHashAlgo(header) + const bytes = new TextEncoder().encode(source) + const digest = await this.implementation.digest(bytes, algo) + if (digest.length % 2 !== 0) throw new TypeError('Invalid digest length') + const digestHalf = digest.slice(0, digest.length / 2) + return base64url.baseEncode(digestHalf) + } + + public async generatePKCE(byteLength?: number) { + const verifier = await this.generateVerifier(byteLength) + return { + verifier, + challenge: await this.sha256(verifier), + method: 'S256', + } + } + + public async calculateJwkThumbprint(jwk) { + const components = extractJktComponents(jwk) + const data = JSON.stringify(components) + return this.sha256(data) + } + + /** + * @see {@link https://datatracker.ietf.org/doc/html/rfc7636#section-4.1} + * @note It is RECOMMENDED that the output of a suitable random number generator + * be used to create a 32-octet sequence. The octet sequence is then + * base64url-encoded to produce a 43-octet URL safe string to use as the code + * verifier. + */ + protected async generateVerifier(byteLength = 32) { + if (byteLength < 32 || byteLength > 96) { + throw new TypeError('Invalid code_verifier length') + } + const bytes = await this.implementation.getRandomValues(byteLength) + return base64url.baseEncode(bytes) + } +} + +function getHashAlgo(header: { alg: string; crv?: string }): DigestAlgorithm { + switch (header.alg) { + case 'HS256': + case 'RS256': + case 'PS256': + case 'ES256': + case 'ES256K': + return { name: 'sha256' } + case 'HS384': + case 'RS384': + case 'PS384': + case 'ES384': + return { name: 'sha384' } + case 'HS512': + case 'RS512': + case 'PS512': + case 'ES512': + return { name: 'sha512' } + case 'EdDSA': + switch (header.crv) { + case 'Ed25519': + return { name: 'sha512' } + default: + throw new TypeError('unrecognized or invalid EdDSA curve provided') + } + default: + throw new TypeError('unrecognized or invalid JWS algorithm provided') + } +} + +function extractJktComponents(jwk) { + const get = (field) => { + const value = jwk[field] + if (typeof value !== 'string' || !value) { + throw new TypeError(`"${field}" Parameter missing or invalid`) + } + return value + } + + switch (jwk.kty) { + case 'EC': + return { crv: get('crv'), kty: get('kty'), x: get('x'), y: get('y') } + case 'OKP': + return { crv: get('crv'), kty: get('kty'), x: get('x') } + case 'RSA': + return { e: get('e'), kty: get('kty'), n: get('n') } + case 'oct': + return { k: get('k'), kty: get('kty') } + default: + throw new TypeError('"kty" (Key Type) Parameter missing or unsupported') + } +} + +/** + * 256K > ES (256 > 384 > 512) > PS (256 > 384 > 512) > RS (256 > 384 > 512) > other (in original order) + */ +function compareAlgos(a: string, b: string): number { + if (a === 'ES256K') return -1 + if (b === 'ES256K') return 1 + + for (const prefix of ['ES', 'PS', 'RS']) { + if (a.startsWith(prefix)) { + if (b.startsWith(prefix)) { + const aLen = parseInt(a.slice(2, 5)) + const bLen = parseInt(b.slice(2, 5)) + + // Prefer shorter key lengths + return aLen - bLen + } + return -1 + } else if (b.startsWith(prefix)) { + return 1 + } + } + + // Don't know how to compare, keep original order + return 0 +} diff --git a/packages/oauth/oauth-client/src/fetch-dpop.ts b/packages/oauth/oauth-client/src/fetch-dpop.ts new file mode 100644 index 00000000000..f9f0b58dc30 --- /dev/null +++ b/packages/oauth/oauth-client/src/fetch-dpop.ts @@ -0,0 +1,254 @@ +import { base64url } from 'multiformats/bases/base64' +import { Fetch, cancelBody, peekJson } from '@atproto-labs/fetch' +import { Key } from '@atproto/jwk' +import { SimpleStore } from '@atproto-labs/simple-store' + +// "undefined" in non https environments or environments without crypto +const subtle = globalThis.crypto?.subtle as SubtleCrypto | undefined + +const ReadableStream = globalThis.ReadableStream as + | typeof globalThis.ReadableStream + | undefined + +export type DpopFetchWrapperOptions = { + key: Key + iss: string + nonces: SimpleStore + supportedAlgs?: string[] + sha256?: (input: string) => Promise + + /** + * Is the intended server an authorization server (true) or a resource server + * (false)? Setting this may allow to avoid parsing the response body to + * determine the dpop-nonce. + * + * @default undefined + */ + isAuthServer?: boolean + fetch?: Fetch +} + +export function dpopFetchWrapper({ + key, + iss, + supportedAlgs, + nonces, + sha256 = typeof subtle !== 'undefined' ? subtleSha256 : undefined, + isAuthServer, + fetch = globalThis.fetch, +}: DpopFetchWrapperOptions) { + if (!sha256) { + throw new TypeError( + `crypto.subtle is not available in this environment. Please provide a sha256 function.`, + ) + } + + const alg = negotiateAlg(key, supportedAlgs) + + return async function ( + this: ThisParameterType, + input: URL | RequestInfo, + init?: RequestInit | undefined, + ) { + return dpopFetch.call( + this, + input, + init, + key, + iss, + nonces, + alg, + sha256, + isAuthServer, + fetch, + ) + } satisfies Fetch +} + +export async function dpopFetch( + this: ThisParameterType, + input: URL | RequestInfo, + init: RequestInit | undefined, + key: Key, + iss: string, + nonces: SimpleStore, + alg: string = negotiateAlg(key, undefined), + sha256: (input: string) => string | PromiseLike = subtleSha256, + isAuthServer?: boolean, + fetch = globalThis.fetch as Fetch, +): Promise { + if (!key.algorithms.includes(alg)) { + throw new TypeError(`Key does not support the algorithm ${alg}`) + } + + const request: Request = + init == null && input instanceof Request ? input : new Request(input, init) + + const authorizationHeader = request.headers.get('Authorization') + const ath = authorizationHeader?.startsWith('DPoP ') + ? await sha256(authorizationHeader.slice(5)) + : undefined + + const { method, url } = request + const { origin } = new URL(url) + + let initNonce: string | undefined + try { + initNonce = await nonces.get(origin) + } catch { + // Ignore get errors, we will just not send a nonce + } + + const initProof = await buildProof(key, alg, iss, method, url, initNonce, ath) + request.headers.set('DPoP', initProof) + + const initResponse = await fetch.call(this, request) + + // Make sure the response body is consumed. Either by the caller (when the + // response is returned), of if an error is thrown (catch block). + + const nextNonce = initResponse.headers.get('DPoP-Nonce') + if (!nextNonce || nextNonce === initNonce) { + // No nonce was returned or it is the same as the one we sent. No need to + // update the nonce store, or retry the request. + return initResponse + } + + // Store the fresh nonce for future requests + try { + await nonces.set(origin, nextNonce) + } catch { + // Ignore set errors + } + + if (!(await isUseDpopNonceError(initResponse, isAuthServer))) { + // Not a "use_dpop_nonce" error, so there is no need to retry + return initResponse + } + + // If the input stream was already consumed, we cannot retry the request. A + // solution would be to clone() the request but that would bufferize the + // entire stream in memory which can lead to memory starvation. Instead, we + // will return the original response and let the calling code handle retries. + + if (input === request) { + // The input request body was consumed. We cannot retry the request. + return initResponse + } + + if (ReadableStream && init?.body instanceof ReadableStream) { + // The init body was consumed. We cannot retry the request. + return initResponse + } + + // We will now retry the request with the fresh nonce. + + // The initial response body must be consumed (see cancelBody's doc). + await cancelBody(initResponse, (err) => { + // TODO: What's the best way to handle this (i.e. what can cause this to + // happen)? + // 0) Not provide this callback and wait for the cancellation synchronously? + // 1) Crash the server? (`throw err`) + // 2) Log the error and continue? + // 3) Silently ignore the error? + // 4) Let the caller decide by providing a callback? + + console.error('Failed to cancel response body', err) + }) + + const nextProof = await buildProof(key, alg, iss, method, url, nextNonce, ath) + const nextRequest = new Request(input, init) + nextRequest.headers.set('DPoP', nextProof) + + return fetch.call(this, nextRequest) +} + +async function buildProof( + key: Key, + alg: string, + iss: string, + htm: string, + htu: string, + nonce?: string, + ath?: string, +) { + if (!key.bareJwk) { + throw new Error('Only asymetric keys can be used as DPoP proofs') + } + + const now = Math.floor(Date.now() / 1e3) + + return key.createJwt( + { + alg, + typ: 'dpop+jwt', + jwk: key.bareJwk, + }, + { + iss, + iat: now, + exp: now + 10, + // Any collision will cause the request to be rejected by the server. no biggie. + jti: Math.random().toString(36).slice(2), + htm, + htu, + nonce, + ath, + }, + ) +} + +async function isUseDpopNonceError( + response: Response, + isAuthServer?: boolean, +): Promise { + // https://datatracker.ietf.org/doc/html/rfc6750#section-3 + // https://datatracker.ietf.org/doc/html/rfc9449#name-resource-server-provided-no + if (isAuthServer === undefined || isAuthServer === false) { + const wwwAuth = response.headers.get('WWW-Authenticate') + if (wwwAuth?.startsWith('DPoP')) { + return wwwAuth.includes('error="use_dpop_nonce"') + } + } + + // https://datatracker.ietf.org/doc/html/rfc9449#name-authorization-server-provid + if (isAuthServer === undefined || isAuthServer === true) { + if (response.status === 400) { + try { + const json = await peekJson(response, 10 * 1024) + return typeof json === 'object' && json?.['error'] === 'use_dpop_nonce' + } catch { + // Response too big (to be "use_dpop_nonce" error) or invalid JSON + return false + } + } + } + + return false +} + +function negotiateAlg(key: Key, supportedAlgs: string[] | undefined): string { + if (supportedAlgs) { + // Use order of supportedAlgs as preference + const alg = supportedAlgs.find((a) => key.algorithms.includes(a)) + if (alg) return alg + } else { + const [alg] = key.algorithms + if (alg) return alg + } + + throw new Error('Key does not match any alg supported by the server') +} + +async function subtleSha256(input: string): Promise { + if (subtle == null) { + throw new Error( + `crypto.subtle is not available in this environment. Please provide a sha256 function.`, + ) + } + + const bytes = new TextEncoder().encode(input) + const digest = await subtle.digest('SHA-256', bytes) + const digestBytes = new Uint8Array(digest) + return base64url.baseEncode(digestBytes) +} diff --git a/packages/oauth/oauth-client/src/index.ts b/packages/oauth/oauth-client/src/index.ts new file mode 100644 index 00000000000..395b483f2b0 --- /dev/null +++ b/packages/oauth/oauth-client/src/index.ts @@ -0,0 +1,11 @@ +export { FetchError } from '@atproto-labs/fetch' +export * from './crypto-implementation.js' +export * from './oauth-callback-error.js' +export * from './oauth-client-factory.js' +export * from './oauth-client.js' +export * from './oauth-response-error.js' +export * from './oauth-server-factory.js' +export * from './oauth-server-metadata-resolver.js' +export * from './oauth-server.js' +export * from './session-getter.js' +export * from './types.js' diff --git a/packages/oauth/oauth-client/src/oauth-callback-error.ts b/packages/oauth/oauth-client/src/oauth-callback-error.ts new file mode 100644 index 00000000000..9c9c26d19da --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-callback-error.ts @@ -0,0 +1,16 @@ +export class OAuthCallbackError extends Error { + static from(err: unknown, params: URLSearchParams, state?: string) { + if (err instanceof OAuthCallbackError) return err + const message = err instanceof Error ? err.message : undefined + return new OAuthCallbackError(params, message, state, err) + } + + constructor( + public readonly params: URLSearchParams, + message = params.get('error_description') || 'OAuth callback error', + public readonly state?: string, + cause?: unknown, + ) { + super(message, { cause }) + } +} diff --git a/packages/oauth/oauth-client/src/oauth-client-factory.ts b/packages/oauth/oauth-client/src/oauth-client-factory.ts new file mode 100644 index 00000000000..a11a2ca79e0 --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-client-factory.ts @@ -0,0 +1,294 @@ +import { Key } from '@atproto/jwk' +import { OAuthResponseMode } from '@atproto/oauth-types' +import { SimpleStore } from '@atproto-labs/simple-store' + +import { FALLBACK_ALG } from './constants.js' +import { OAuthCallbackError } from './oauth-callback-error.js' +import { OAuthClient } from './oauth-client.js' +import { + OAuthServerFactory, + OAuthServerFactoryOptions, +} from './oauth-server-factory.js' +import { OAuthServer } from './oauth-server.js' +import { Session, SessionGetter } from './session-getter.js' +import { OAuthAuthorizeOptions } from './types.js' + +export type InternalStateData = { + iss: string + nonce: string + dpopKey: Key + verifier?: string + + /** + * @note This could be parametrized to be of any type. This wasn't done for + * the sake of simplicity but could be added in a later development. + */ + appState?: string +} + +export type OAuthClientOptions = OAuthServerFactoryOptions & { + stateStore: SimpleStore + sessionStore: SimpleStore + + /** + * "form_post" will typically be used for server-side applications. + */ + responseMode?: OAuthResponseMode +} + +export class OAuthClientFactory { + readonly serverFactory: OAuthServerFactory + + readonly stateStore: SimpleStore + readonly sessionGetter: SessionGetter + + readonly responseMode?: OAuthResponseMode + + constructor(options: OAuthClientOptions) { + this.responseMode = options.responseMode + this.serverFactory = new OAuthServerFactory(options) + this.stateStore = options.stateStore + this.sessionGetter = new SessionGetter( + options.sessionStore, + this.serverFactory, + ) + } + + get clientMetadata() { + return this.serverFactory.clientMetadata + } + + get crypto() { + return this.serverFactory.crypto + } + + get fetch() { + return this.serverFactory.fetch + } + + get resolver() { + return this.serverFactory.resolver + } + + async authorize( + input: string, + options?: OAuthAuthorizeOptions, + ): Promise { + const { did, metadata } = await this.resolver.resolve(input) + + const nonce = await this.crypto.generateNonce() + const pkce = await this.crypto.generatePKCE() + const dpopKey = await this.crypto.generateKey( + metadata.dpop_signing_alg_values_supported || [FALLBACK_ALG], + ) + + const state = await this.crypto.generateNonce() + + await this.stateStore.set(state, { + iss: metadata.issuer, + dpopKey, + nonce, + verifier: pkce?.verifier, + appState: options?.state, + }) + + const parameters = { + client_id: this.clientMetadata.client_id, + redirect_uri: this.clientMetadata.redirect_uris[0], + code_challenge: pkce?.challenge, + code_challenge_method: pkce?.method, + nonce, + state, + login_hint: did || undefined, + response_mode: this.responseMode, + response_type: + // Negotiate by using the order in the client metadata + (this.clientMetadata.response_types || ['code id_token'])?.find((t) => + metadata['response_types_supported']?.includes(t), + ) ?? 'code', + + display: options?.display, + id_token_hint: options?.id_token_hint, + max_age: options?.max_age, // this.clientMetadata.default_max_age + prompt: options?.prompt, + scope: options?.scope + ?.split(' ') + .filter((s) => metadata.scopes_supported?.includes(s)) + .join(' '), + ui_locales: options?.ui_locales, + } + + if (metadata.pushed_authorization_request_endpoint) { + const server = await this.serverFactory.fromMetadata(metadata, dpopKey) + const parResponse = await server.request( + 'pushed_authorization_request', + parameters, + ) + + const authorizationUrl = new URL(metadata.authorization_endpoint) + authorizationUrl.searchParams.set( + 'client_id', + this.clientMetadata.client_id, + ) + authorizationUrl.searchParams.set('request_uri', parResponse.request_uri) + return authorizationUrl + } else if (metadata.require_pushed_authorization_requests) { + throw new Error( + 'Server requires pushed authorization requests (PAR) but no PAR endpoint is available', + ) + } else { + const authorizationUrl = new URL(metadata.authorization_endpoint) + for (const [key, value] of Object.entries(parameters)) { + if (value) authorizationUrl.searchParams.set(key, String(value)) + } + + // Length of the URL that will be sent to the server + const urlLength = + authorizationUrl.pathname.length + authorizationUrl.search.length + if (urlLength < 2048) { + return authorizationUrl + } else if (!metadata.pushed_authorization_request_endpoint) { + throw new Error('Login URL too long') + } + } + + throw new Error( + 'Server does not support pushed authorization requests (PAR)', + ) + } + + async callback(params: URLSearchParams): Promise<{ + client: OAuthClient + state?: string + }> { + const responseJwt = params.get('response') + if (responseJwt != null) { + // https://openid.net/specs/oauth-v2-jarm.html + throw new OAuthCallbackError(params, 'JARM not supported') + } + + const issuerParam = params.get('iss') + const stateParam = params.get('state') + const errorParam = params.get('error') + const codeParam = params.get('code') + + if (!stateParam) { + throw new OAuthCallbackError(params, 'Missing "state" parameter') + } + const stateData = await this.stateStore.get(stateParam) + if (stateData) { + // Prevent any kind of replay + await this.stateStore.del(stateParam) + } else { + throw new OAuthCallbackError(params, 'Invalid state') + } + + try { + if (errorParam != null) { + throw new OAuthCallbackError(params, undefined, stateData.appState) + } + + if (!codeParam) { + throw new OAuthCallbackError( + params, + 'Missing "code" query param', + stateData.appState, + ) + } + + const server = await this.serverFactory.fromIssuer( + stateData.iss, + stateData.dpopKey, + ) + + if (issuerParam != null) { + if (!server.serverMetadata.issuer) { + throw new OAuthCallbackError( + params, + 'Issuer not found in metadata', + stateData.appState, + ) + } + if (server.serverMetadata.issuer !== issuerParam) { + throw new OAuthCallbackError( + params, + 'Issuer mismatch', + stateData.appState, + ) + } + } else if ( + server.serverMetadata.authorization_response_iss_parameter_supported + ) { + throw new OAuthCallbackError( + params, + 'iss missing from the response', + stateData.appState, + ) + } + + const tokenSet = await server.exchangeCode(codeParam, stateData.verifier) + try { + if (tokenSet.id_token) { + await this.crypto.validateIdTokenClaims( + tokenSet.id_token, + stateParam, + stateData.nonce, + codeParam, + tokenSet.access_token, + ) + } + + const sessionId = await this.crypto.generateNonce(4) + + await this.sessionGetter.setStored(sessionId, { + dpopKey: stateData.dpopKey, + tokenSet, + }) + + const client = this.createClient(server, sessionId) + + return { client, state: stateData.appState } + } catch (err) { + await server.revoke(tokenSet.access_token) + + throw err + } + } catch (err) { + // Make sure, whatever the underlying error, that the appState is + // available in the calling code + throw OAuthCallbackError.from(err, params, stateData.appState) + } + } + + /** + * Build a client from a stored session. This will refresh the token only if + * needed (about to expire) by default. + * + * @param refresh See {@link SessionGetter.getSession} + */ + async restore(sessionId: string, refresh?: boolean): Promise { + const { dpopKey, tokenSet } = await this.sessionGetter.getSession( + sessionId, + refresh, + ) + + const server = await this.serverFactory.fromIssuer(tokenSet.iss, dpopKey) + + return this.createClient(server, sessionId) + } + + async revoke(sessionId: string) { + const { dpopKey, tokenSet } = await this.sessionGetter.get(sessionId, { + allowStale: true, + }) + + const server = await this.serverFactory.fromIssuer(tokenSet.iss, dpopKey) + + await server.revoke(tokenSet.access_token) + await this.sessionGetter.delStored(sessionId) + } + + createClient(server: OAuthServer, sessionId: string): OAuthClient { + return new OAuthClient(server, sessionId, this.sessionGetter, this.fetch) + } +} diff --git a/packages/oauth/oauth-client/src/oauth-client.ts b/packages/oauth/oauth-client/src/oauth-client.ts new file mode 100644 index 00000000000..f31be9553a1 --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-client.ts @@ -0,0 +1,151 @@ +import { Fetch, fetchFailureHandler } from '@atproto-labs/fetch' +import { JwtPayload, unsafeDecodeJwt } from '@atproto/jwk' +import { OAuthServerMetadata } from '@atproto/oauth-types' +import { dpopFetchWrapper } from './fetch-dpop.js' +import { OAuthServer, TokenSet } from './oauth-server.js' +import { SessionGetter } from './session-getter.js' + +const ReadableStream = globalThis.ReadableStream as + | typeof globalThis.ReadableStream + | undefined + +export class OAuthClient { + protected dpopFetch: ( + input: string | Request | URL, + init?: RequestInit | undefined, + ) => Promise + + constructor( + private readonly server: OAuthServer, + public readonly sessionId: string, + private readonly sessionGetter: SessionGetter, + fetch = globalThis.fetch as Fetch, + ) { + const dpopFetch = dpopFetchWrapper({ + fetch, + iss: server.clientMetadata.client_id, + key: server.dpopKey, + supportedAlgs: server.serverMetadata.dpop_signing_alg_values_supported, + sha256: async (v) => server.crypto.sha256(v), + nonces: server.dpopNonces, + isAuthServer: false, + }) + + this.dpopFetch = (...args) => dpopFetch(...args).catch(fetchFailureHandler) + } + + get serverMetadata(): OAuthServerMetadata { + return this.server.serverMetadata + } + + /** + * @param refresh See {@link SessionGetter.getSession} + */ + async getTokenSet(refresh?: boolean): Promise { + const { tokenSet } = await this.sessionGetter.getSession( + this.sessionId, + refresh, + ) + return tokenSet + } + + async getUserinfo(): Promise<{ + userinfo?: JwtPayload + expired?: boolean + scope?: string + iss: string + aud: string + sub: string + }> { + const tokenSet = await this.getTokenSet() + + return { + userinfo: tokenSet.id_token + ? unsafeDecodeJwt(tokenSet.id_token).payload + : undefined, + expired: + tokenSet.expires_at == null + ? undefined + : new Date(tokenSet.expires_at).getTime() < Date.now() - 5e3, + scope: tokenSet.scope, + iss: tokenSet.iss, + aud: tokenSet.aud, + sub: tokenSet.sub, + } + } + + async signOut(): Promise { + try { + const { tokenSet } = await this.sessionGetter.getSession( + this.sessionId, + false, + ) + await this.server.revoke(tokenSet.access_token) + } finally { + await this.sessionGetter.delStored(this.sessionId) + } + } + + async request(pathname: string, init?: RequestInit): Promise { + // This will try and refresh the token if it is known to be expired + const tokenSet = await this.getTokenSet(undefined) + + const initialUrl = new URL(pathname, tokenSet.aud) + const initialAuth = `${tokenSet.token_type} ${tokenSet.access_token}` + + const headers = new Headers(init?.headers) + headers.set('Authorization', initialAuth) + const initialResponse = await this.dpopFetch(initialUrl, { + ...init, + headers, + }) + + // If the token is not expired, we don't need to refresh it + if (!isTokenExpiredResponse(initialResponse)) return initialResponse + + // If there is no refresh token, no need to try to refresh the token + if (!tokenSet.refresh_token) return initialResponse + + let tokenSetFresh: TokenSet + try { + tokenSetFresh = await this.getTokenSet(true) + } catch (err) { + return initialResponse + } + + // The stream was already consumed. We cannot retry the request. A solution + // would be to tee() the input stream but that would bufferize the entire + // stream in memory which can lead to memory starvation. Instead, we will + // return the original response and let the calling code handle retries. + if (ReadableStream && init?.body instanceof ReadableStream) { + return initialResponse + } + + const updatedAuth = `${tokenSetFresh.token_type} ${tokenSetFresh.access_token}` + + // No point in retrying the request if the token is still the same + if (updatedAuth === initialAuth) { + return initialResponse + } + + const updatedUrl = new URL(pathname, tokenSetFresh.aud) + + headers.set('Authorization', updatedAuth) + + return this.dpopFetch(updatedUrl, { ...init, headers }) + } +} + +/** + * @see {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3} + * @see {@link https://datatracker.ietf.org/doc/html/rfc9449#name-resource-server-provided-no} + */ +function isTokenExpiredResponse(response: Response) { + if (response.status !== 401) return false + const wwwAuth = response.headers.get('WWW-Authenticate') + return ( + wwwAuth != null && + wwwAuth.startsWith('Bearer ') && + wwwAuth.includes('error="invalid_token"') + ) +} diff --git a/packages/oauth/oauth-client/src/oauth-resolver.ts b/packages/oauth/oauth-client/src/oauth-resolver.ts new file mode 100644 index 00000000000..290af34bf85 --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-resolver.ts @@ -0,0 +1,30 @@ +import { + IdentityResolver, + ResolvedIdentity, +} from '@atproto-labs/identity-resolver' +import { OAuthServerMetadata } from '@atproto/oauth-types' + +import { OAuthServerMetadataResolver } from './oauth-server-metadata-resolver.js' + +export class OAuthResolver { + constructor( + readonly metadataResolver: OAuthServerMetadataResolver, + readonly identityResolver: IdentityResolver, + ) {} + + public async resolve(input: string): Promise< + Partial & { + url: URL + metadata: OAuthServerMetadata + } + > { + const identity = /^https?:\/\//.test(input) + ? // Allow using a PDS url directly as login input (e.g. when the handle does not resolve to a DID) + { url: new URL(input) } + : await this.identityResolver.resolve(input, 'AtprotoPersonalDataServer') + + const metadata = await this.metadataResolver.resolve(identity.url.origin) + + return { ...identity, metadata } + } +} diff --git a/packages/oauth/oauth-client/src/oauth-response-error.ts b/packages/oauth/oauth-client/src/oauth-response-error.ts new file mode 100644 index 00000000000..b49c2f5c1d2 --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-response-error.ts @@ -0,0 +1,31 @@ +import { Json, ifString, ifObject } from '@atproto-labs/fetch' + +export class OAuthResponseError extends Error { + readonly error?: string + readonly errorDescription?: string + + constructor( + public readonly response: Response, + public readonly payload: Json, + ) { + const error = ifString(ifObject(payload)?.['error']) + const errorDescription = ifString(ifObject(payload)?.['error_description']) + + const messageError = error ? `"${error}"` : 'unknown' + const messageDesc = errorDescription ? `: ${errorDescription}` : '' + const message = `OAuth ${messageError} error${messageDesc}` + + super(message) + + this.error = error + this.errorDescription = errorDescription + } + + get status() { + return this.response.status + } + + get headers() { + return this.response.headers + } +} diff --git a/packages/oauth/oauth-client/src/oauth-server-factory.ts b/packages/oauth/oauth-client/src/oauth-server-factory.ts new file mode 100644 index 00000000000..f0676572f93 --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-server-factory.ts @@ -0,0 +1,89 @@ +import { Fetch } from '@atproto-labs/fetch' +import { IdentityResolver } from '@atproto-labs/identity-resolver' +import { Key, Keyset } from '@atproto/jwk' +import { OAuthClientMetadata, OAuthServerMetadata } from '@atproto/oauth-types' +import { SimpleStore } from '@atproto-labs/simple-store' +import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory' + +import { CryptoImplementation } from './crypto-implementation.js' +import { CryptoWrapper } from './crypto-wrapper.js' +import { OAuthResolver } from './oauth-resolver.js' +import { OAuthServerMetadataResolver } from './oauth-server-metadata-resolver.js' +import { OAuthServer } from './oauth-server.js' +import { OAuthClientMetadataId } from './types.js' +import { validateClientMetadata } from './validate-client-metadata.js' + +export type OAuthServerFactoryOptions = { + clientMetadata: OAuthClientMetadata + cryptoImplementation: CryptoImplementation + identityResolver: IdentityResolver + fetch?: Fetch + keyset?: Keyset + metadataCache?: SimpleStore + dpopNonceCache?: SimpleStore +} + +export class OAuthServerFactory { + readonly clientMetadata: OAuthClientMetadataId + readonly metadataResolver: OAuthServerMetadataResolver + readonly crypto: CryptoWrapper + readonly resolver: OAuthResolver + readonly fetch: Fetch + readonly keyset?: Keyset + readonly dpopNonceCache: SimpleStore + + constructor({ + identityResolver, + clientMetadata, + cryptoImplementation, + keyset, + fetch = globalThis.fetch, + metadataCache = new SimpleStoreMemory({ + ttl: 60e3, + max: 100, + }), + dpopNonceCache = new SimpleStoreMemory({ + ttl: 60e3, + max: 100, + }), + }: OAuthServerFactoryOptions) { + validateClientMetadata(clientMetadata, keyset) + + if (!clientMetadata.client_id) { + throw new TypeError('A client_id property must be specified') + } + + const metadataResolver = new OAuthServerMetadataResolver( + metadataCache, + fetch, + ) + + this.clientMetadata = clientMetadata + this.metadataResolver = metadataResolver + this.keyset = keyset + this.fetch = fetch + this.dpopNonceCache = dpopNonceCache + + this.crypto = new CryptoWrapper(cryptoImplementation) + this.resolver = new OAuthResolver(metadataResolver, identityResolver) + } + + async fromIssuer(issuer: string, dpopKey: Key) { + const { origin } = new URL(issuer) + const serverMetadata = await this.metadataResolver.resolve(origin) + return this.fromMetadata(serverMetadata, dpopKey) + } + + async fromMetadata(serverMetadata: OAuthServerMetadata, dpopKey: Key) { + return new OAuthServer( + dpopKey, + serverMetadata, + this.clientMetadata, + this.dpopNonceCache, + this.resolver, + this.crypto, + this.keyset, + this.fetch, + ) + } +} diff --git a/packages/oauth/oauth-client/src/oauth-server-metadata-resolver.ts b/packages/oauth/oauth-client/src/oauth-server-metadata-resolver.ts new file mode 100644 index 00000000000..e2d4f46cfb7 --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-server-metadata-resolver.ts @@ -0,0 +1,107 @@ +import { Fetch, FetchError } from '@atproto-labs/fetch' +import { + OAuthServerMetadata, + oauthServerMetadataValidator, +} from '@atproto/oauth-types' +import { + CachedGetter, + SimpleStore, + GetOptions, +} from '@atproto-labs/simple-store' + +export type ResolveOptions = GetOptions + +export class OAuthServerMetadataResolver { + private readonly getter: CachedGetter + + constructor( + store: SimpleStore, + private readonly fetch: Fetch = globalThis.fetch, + ) { + this.getter = new CachedGetter( + async (origin, options) => + this.fetchServerMetadata(origin, 'oauth-authorization-server', options), + store, + ) + } + + async resolve(origin: string): Promise { + return this.getter.get(origin) + } + + async fetchServerMetadata( + origin: string, + suffix: 'openid-configuration' | 'oauth-authorization-server', + options?: ResolveOptions, + ): Promise { + const originUrl = new URL(origin) + if (originUrl.origin !== origin) { + throw new TypeError( + `OAuth server origin must not contain a path, query, or fragment.`, + ) + } + + if (originUrl.protocol !== 'https:' && originUrl.protocol !== 'http:') { + throw new TypeError(`Issuer origin must use "https" or "http"`) + } + + const oauthServerMetadataEndpoint = new URL( + `/.well-known/${suffix}`, + originUrl, + ) + + const headers = new Headers([['accept', 'application/json']]) + if (options?.noCache) headers.set('cache-control', 'no-cache') + + const request = new Request(oauthServerMetadataEndpoint, { + signal: options?.signal, + headers, + // This is a particularity of the Atproto OAuth implementation. PDS's will + // use a redirect to their AS's metadata endpoint. This might not be spec + // compliant but we *do* check that the issuer is valid w.r.t the origin + // of the last redirect url (see below). + redirect: 'follow', + }) + + const response = await (0, this.fetch)(request) + + if (!response.ok) { + // Fallback to openid-configuration endpoint + if (suffix !== 'openid-configuration') { + return this.fetchServerMetadata(origin, 'openid-configuration', options) + } + + throw new FetchError( + response.status, + `Unable to fetch OAuth server metadata for "${origin}"`, + { request, response }, + ) + } + + const metadata = oauthServerMetadataValidator.parse(await response.json()) + + // Validate the issuer (MIX-UP attacks) + // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#name-mix-up-attacks + const issuerUrl = new URL(metadata.issuer) + if (issuerUrl.pathname !== '/') { + throw new Error(`Invalid issuer ${metadata.issuer}`) + } + const responseUrl = new URL( + response.redirected ? response.url : request.url, + ) + if (issuerUrl.origin !== responseUrl.origin) { + throw new FetchError(502, `Invalid issuer ${metadata.issuer}`, { + request, + response, + }) + } + if (responseUrl.pathname !== `/.well-known/${suffix}`) { + throw new FetchError(502, `Invalid metadata endpoint ${response.url}`, { + request, + response, + }) + } + + return metadata + } +} diff --git a/packages/oauth/oauth-client/src/oauth-server.ts b/packages/oauth/oauth-client/src/oauth-server.ts new file mode 100644 index 00000000000..98f3ec431ca --- /dev/null +++ b/packages/oauth/oauth-client/src/oauth-server.ts @@ -0,0 +1,287 @@ +import { + Fetch, + Json, + fetchFailureHandler, + fetchJsonProcessor, +} from '@atproto-labs/fetch' +import { Jwt, Key, Keyset } from '@atproto/jwk' +import { + CLIENT_ASSERTION_TYPE_JWT_BEARER, + OAuthClientIdentification, + OAuthClientMetadata, + OAuthEndpointName, + OAuthParResponse, + OAuthServerMetadata, + OAuthTokenResponse, + OAuthTokenType, + oauthParResponseSchema, + oauthTokenResponseSchema, +} from '@atproto/oauth-types' +import { SimpleStore } from '@atproto-labs/simple-store' + +import { FALLBACK_ALG } from './constants.js' +import { CryptoWrapper } from './crypto-wrapper.js' +import { dpopFetchWrapper } from './fetch-dpop.js' +import { OAuthResolver } from './oauth-resolver.js' +import { OAuthResponseError } from './oauth-response-error.js' + +export type TokenSet = { + iss: string + sub: string + aud: string + scope?: string + + id_token?: Jwt + refresh_token?: string + access_token: string + token_type: OAuthTokenType + /** ISO Date */ + expires_at?: string +} + +export class OAuthServer { + protected dpopFetch: ( + input: string | Request | URL, + init?: RequestInit | undefined, + ) => Promise + + constructor( + readonly dpopKey: Key, + readonly serverMetadata: OAuthServerMetadata, + readonly clientMetadata: OAuthClientMetadata & { client_id: string }, + readonly dpopNonces: SimpleStore, + readonly resolver: OAuthResolver, + readonly crypto: CryptoWrapper, + readonly keyset?: Keyset, + fetch?: Fetch, + ) { + const dpopFetch = dpopFetchWrapper({ + fetch, + iss: this.clientMetadata.client_id, + key: dpopKey, + supportedAlgs: this.serverMetadata.dpop_signing_alg_values_supported, + sha256: async (v) => crypto.sha256(v), + nonces: dpopNonces, + isAuthServer: true, + }) + + this.dpopFetch = (...args) => dpopFetch(...args).catch(fetchFailureHandler) + } + + async revoke(token: string) { + try { + await this.request('revocation', { token }) + } catch { + // Don't care + } + } + + async exchangeCode(code: string, verifier?: string): Promise { + const tokenResponse = await this.request('token', { + grant_type: 'authorization_code', + redirect_uri: this.clientMetadata.redirect_uris[0]!, + code, + code_verifier: verifier, + }) + + try { + if (!tokenResponse.sub) { + throw new TypeError(`Missing "sub" in token response`) + } + + // VERY IMPORTANT ! + const resolved = await this.checkSubIssuer(tokenResponse.sub) + + return { + sub: tokenResponse.sub, + aud: resolved.url.href, + iss: resolved.metadata.issuer, + + scope: tokenResponse.scope, + id_token: tokenResponse.id_token, + refresh_token: tokenResponse.refresh_token, + access_token: tokenResponse.access_token, + token_type: tokenResponse.token_type ?? 'Bearer', + expires_at: + typeof tokenResponse.expires_in === 'number' + ? new Date( + Date.now() + tokenResponse.expires_in * 1000, + ).toISOString() + : undefined, + } + } catch (err) { + await this.revoke(tokenResponse.access_token) + + throw err + } + } + + async refresh(tokenSet: TokenSet): Promise { + if (!tokenSet.refresh_token) { + throw new Error('No refresh token available') + } + + const tokenResponse = await this.request('token', { + grant_type: 'refresh_token', + refresh_token: tokenSet.refresh_token, + }) + + try { + if (tokenSet.sub !== tokenResponse.sub) { + throw new TypeError(`Unexpected "sub" in token response`) + } + if (tokenSet.iss !== this.serverMetadata.issuer) { + throw new TypeError('Issuer mismatch') + } + + // VERY IMPORTANT ! + const resolved = await this.checkSubIssuer(tokenResponse.sub) + + return { + sub: tokenResponse.sub, + aud: resolved.url.href, + iss: resolved.metadata.issuer, + + id_token: tokenResponse.id_token, + refresh_token: tokenResponse.refresh_token, + access_token: tokenResponse.access_token, + token_type: tokenResponse.token_type ?? 'Bearer', + expires_at: tokenResponse.expires_in + ? new Date(Date.now() + tokenResponse.expires_in * 1000).toISOString() + : undefined, + } + } catch (err) { + await this.revoke(tokenResponse.access_token) + + throw err + } + } + + /** + * Whenever an OAuth token response is received, we **MUST** verify that the + * "sub" is a DID, whose issuer authority is indeed the server we just + * obtained credentials from. This check is a critical step to actually be + * able to use the "sub" (DID) as being the actual user's identifier. + */ + protected async checkSubIssuer(sub: string) { + const resolved = await this.resolver.resolve(sub) + if (resolved.metadata.issuer !== this.serverMetadata.issuer) { + // Maybe the user switched PDS. + throw new TypeError('Issuer mismatch') + } + return resolved + } + + async request( + endpoint: 'token', + payload: Record, + ): Promise + async request( + endpoint: 'pushed_authorization_request', + payload: Record, + ): Promise + async request( + endpoint: OAuthEndpointName, + payload: Record, + ): Promise + + async request(endpoint: OAuthEndpointName, payload: Record) { + const url = this.serverMetadata[`${endpoint}_endpoint`] + if (!url) throw new Error(`No ${endpoint} endpoint available`) + + const auth = await this.buildClientAuth(endpoint) + + const { response, json } = await this.dpopFetch(url, { + method: 'POST', + headers: { ...auth.headers, 'Content-Type': 'application/json' }, + body: JSON.stringify({ ...payload, ...auth.payload }), + }).then(fetchJsonProcessor()) + + if (response.ok) { + switch (endpoint) { + case 'token': + return oauthTokenResponseSchema.parse(json) + case 'pushed_authorization_request': + return oauthParResponseSchema.parse(json) + default: + return json + } + } else { + throw new OAuthResponseError(response, json) + } + } + + async buildClientAuth(endpoint: OAuthEndpointName): Promise<{ + headers?: Record + payload: OAuthClientIdentification + }> { + const methodSupported = + this.serverMetadata[`${endpoint}_endpoint_auth_methods_supported`] || + this.serverMetadata[`token_endpoint_auth_methods_supported`] + + const method = + this.clientMetadata[`${endpoint}_endpoint_auth_method`] || + this.clientMetadata[`token_endpoint_auth_method`] + + if ( + method === 'private_key_jwt' || + (this.keyset && + !method && + (methodSupported?.includes('private_key_jwt') ?? false)) + ) { + if (!this.keyset) throw new Error('No keyset available') + + try { + const alg = + this.serverMetadata[ + `${endpoint}_endpoint_auth_signing_alg_values_supported` + ] ?? + this.serverMetadata[ + `token_endpoint_auth_signing_alg_values_supported` + ] ?? + FALLBACK_ALG + + // If jwks is defined, make sure to only sign using a key that exists in + // the jwks. If jwks_uri is defined, we can't be sure that the key we're + // looking for is in there so we will just assume it is. + const kid = this.clientMetadata.jwks?.keys + .map(({ kid }) => kid) + .filter((v): v is string => !!v) + + return { + payload: { + client_id: this.clientMetadata.client_id, + client_assertion_type: CLIENT_ASSERTION_TYPE_JWT_BEARER, + client_assertion: await this.keyset.createJwt( + { alg, kid }, + { + iss: this.clientMetadata.client_id, + sub: this.clientMetadata.client_id, + aud: this.serverMetadata.issuer, + jti: await this.crypto.generateNonce(), + iat: Math.floor(Date.now() / 1000), + }, + ), + }, + } + } catch (err) { + if (method === 'private_key_jwt') throw err + + // Else try next method + } + } + + if ( + method === 'none' || + (!method && (methodSupported?.includes('none') ?? true)) + ) { + return { + payload: { + client_id: this.clientMetadata.client_id, + }, + } + } + + throw new Error(`Unsupported ${endpoint} authentication method`) + } +} diff --git a/packages/oauth/oauth-client/src/session-getter.ts b/packages/oauth/oauth-client/src/session-getter.ts new file mode 100644 index 00000000000..b173f84ec4e --- /dev/null +++ b/packages/oauth/oauth-client/src/session-getter.ts @@ -0,0 +1,130 @@ +import { CachedGetter, SimpleStore } from '@atproto-labs/simple-store' +import { Key } from '@atproto/jwk' +import { OAuthResponseError } from './oauth-response-error.js' +import { OAuthServerFactory } from './oauth-server-factory.js' +import { TokenSet } from './oauth-server.js' + +export type Session = { + dpopKey: Key + tokenSet: TokenSet +} + +/** + * There are several advantages to wrapping the sessionStore in a (single) + * CachedGetter, the main of which is that the cached getter will ensure that at + * most one fresh call is ever being made. Another advantage, is that it + * contains the logic for reading from the cache which, if the cache is based on + * localStorage/indexedDB, will sync across multiple tabs (for a given + * sessionId). + */ +export class SessionGetter extends CachedGetter { + constructor( + sessionStore: SimpleStore, + serverFactory: OAuthServerFactory, + ) { + super( + async (sessionId, options, storedSession) => { + // There needs to be a previous session to be able to refresh + if (storedSession === undefined) { + throw new Error('The session was revoked') + } + + // Since refresh tokens can only be used once, we might run into + // concurrency issues if multiple tabs/instances are trying to refresh + // the same token. The chances of this happening when multiple instances + // are started simultaneously is reduced by randomizing the expiry time + // (see isStale() bellow). Even so, There still exist chances that + // multiple tabs will try to refresh the token at the same time. The + // best solution would be to use a mutex/lock to ensure that only one + // instance is refreshing the token at a time. A simpler workaround is + // to check if the value stored in the session store is the same as the + // one in memory. If it isn't, then another instance has already + // refreshed the token. + + const { tokenSet, dpopKey } = storedSession + const server = await serverFactory.fromIssuer(tokenSet.iss, dpopKey) + const newTokenSet = await server + .refresh(tokenSet) + .catch(async (err) => { + if (await isRefreshDeniedError(err)) { + // Allow some time for the concurrent request to be stored before + // we try to get it. + await new Promise((r) => setTimeout(r, 500)) + + const stored = await this.getStored(sessionId) + if (stored !== undefined) { + if ( + stored.tokenSet.access_token !== tokenSet.access_token || + stored.tokenSet.refresh_token !== tokenSet.refresh_token + ) { + // A concurrent refresh occurred. Pretend this one succeeded. + return stored.tokenSet + } else { + // The session data will be deleted from the sessionStore by + // the "deleteOnError" callback. + } + } + } + + throw err + }) + return { ...storedSession, tokenSet: newTokenSet } + }, + sessionStore, + { + isStale: (sessionId, { tokenSet }) => { + return ( + tokenSet.expires_at != null && + new Date(tokenSet.expires_at).getTime() < + Date.now() + + // Add some lee way to ensure the token is not expired when it + // reaches the server. + 30e3 + + // Add some randomness to prevent all instances from trying to + // refreshing at the exact same time, when they are started at + // the same time. + 60e3 * Math.random() + ) + }, + onStoreError: async (err, sessionId, { tokenSet, dpopKey }) => { + // If the token data cannot be stored, let's revoke it + const server = await serverFactory.fromIssuer(tokenSet.iss, dpopKey) + await server.revoke(tokenSet.access_token) + throw err + }, + deleteOnError: async (err, sessionId, { tokenSet }) => { + // Not possible to refresh without a refresh token + if (!tokenSet.refresh_token) return true + + // If fetching a refresh token fails because they are no longer valid, + // delete the session from the sessionStore. + if (await isRefreshDeniedError(err)) return true + + // Unknown cause, keep the session in the store + return false + }, + }, + ) + } + + /** + * @param refresh When `true`, the credentials will be refreshed even if they + * are not expired. When `false`, the credentials will not be refreshed even + * if they are expired. When `undefined`, the credentials will be refreshed + * if, and only if, they are (about to be) expired. Defaults to `undefined`. + */ + async getSession(sessionId: string, refresh?: boolean) { + return this.get(sessionId, { + noCache: refresh === true, + allowStale: refresh === false, + }) + } +} + +async function isRefreshDeniedError(err: unknown) { + return ( + err instanceof OAuthResponseError && + err.status === 400 && + err.error === 'invalid_grant' + ) +} diff --git a/packages/oauth/oauth-client/src/types.ts b/packages/oauth/oauth-client/src/types.ts new file mode 100644 index 00000000000..d8e1d775fbd --- /dev/null +++ b/packages/oauth/oauth-client/src/types.ts @@ -0,0 +1,17 @@ +import { OAuthClientMetadata } from '@atproto/oauth-types' + +// TODO: Rename these types without the OAuth prefix. All oauth related types +// are in the oauth-types package. The following types are specific to this +// package, not to oauth in general. + +export type OAuthAuthorizeOptions = { + display?: 'page' | 'popup' | 'touch' | 'wap' + id_token_hint?: string + max_age?: number + prompt?: 'login' | 'none' | 'consent' | 'select_account' + scope?: string + state?: string + ui_locales?: string +} + +export type OAuthClientMetadataId = OAuthClientMetadata & { client_id: string } diff --git a/packages/oauth/oauth-client/src/validate-client-metadata.ts b/packages/oauth/oauth-client/src/validate-client-metadata.ts new file mode 100644 index 00000000000..f50f31a3129 --- /dev/null +++ b/packages/oauth/oauth-client/src/validate-client-metadata.ts @@ -0,0 +1,80 @@ +import { Keyset } from '@atproto/jwk' +import { OAuthClientMetadata } from '@atproto/oauth-types' + +export function validateClientMetadata( + metadata: OAuthClientMetadata, + keyset?: Keyset, +): asserts metadata is OAuthClientMetadata & { client_id: string } { + if (!metadata.client_id) { + throw new TypeError('client_id must be provided') + } + + const url = new URL(metadata.client_id) + if (url.pathname !== '/') { + throw new TypeError('origin must be a URL root') + } + if (url.username || url.password) { + throw new TypeError('client_id URI must not contain a username or password') + } + if (url.search || url.hash) { + throw new TypeError('client_id URI must not contain a query or fragment') + } + if (url.href !== metadata.client_id) { + throw new TypeError('client_id URI must be a normalized URL') + } + + if ( + url.hostname === 'localhost' || + url.hostname === '[::1]' || + url.hostname === '127.0.0.1' + ) { + if (url.protocol !== 'http:' || url.port) { + throw new TypeError('loopback clients must use "http:" and port "80"') + } + } + + if (metadata.client_uri && metadata.client_uri !== metadata.client_id) { + throw new TypeError('client_uri must match client_id') + } + + if (!metadata.redirect_uris.length) { + throw new TypeError('At least one redirect_uri must be provided') + } + for (const u of metadata.redirect_uris) { + const redirectUrl = new URL(u) + // Loopback redirect_uris require special handling + if ( + redirectUrl.hostname === 'localhost' || + redirectUrl.hostname === '[::1]' || + redirectUrl.hostname === '127.0.0.1' + ) { + if (redirectUrl.protocol !== 'http:') { + throw new TypeError('loopback redirect_uris must use "http:"') + } + } else { + // Not a loopback client + if (redirectUrl.origin !== url.origin) { + throw new TypeError('redirect_uris must have the same origin') + } + } + } + + for (const endpoint of [ + 'token', + 'revocation', + 'introspection', + 'pushed_authorization_request', + ] as const) { + const method = metadata[`${endpoint}_endpoint_auth_method`] + if (method && method !== 'none') { + if (!keyset) { + throw new TypeError(`Keyset is required for ${method} method`) + } + if (!metadata[`${endpoint}_endpoint_auth_signing_alg`]) { + throw new TypeError( + `${endpoint}_endpoint_auth_signing_alg must be provided`, + ) + } + } + } +} diff --git a/packages/oauth/oauth-client/tsconfig.build.json b/packages/oauth/oauth-client/tsconfig.build.json new file mode 100644 index 00000000000..d37ddd8d394 --- /dev/null +++ b/packages/oauth/oauth-client/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/isomorphic.json"], + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/oauth/oauth-client/tsconfig.json b/packages/oauth/oauth-client/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/oauth/oauth-client/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/packages/oauth/oauth-provider/.postcssrc.yml b/packages/oauth/oauth-provider/.postcssrc.yml new file mode 100644 index 00000000000..0114fbc9e78 --- /dev/null +++ b/packages/oauth/oauth-provider/.postcssrc.yml @@ -0,0 +1,3 @@ +plugins: + tailwindcss: {} + autoprefixer: {} diff --git a/packages/oauth/oauth-provider/package.json b/packages/oauth/oauth-provider/package.json new file mode 100644 index 00000000000..4b3a6baafcc --- /dev/null +++ b/packages/oauth/oauth-provider/package.json @@ -0,0 +1,83 @@ +{ + "name": "@atproto/oauth-provider", + "version": "0.0.0", + "license": "MIT", + "description": "Generic OAuth2 and OpenID Connect provider for Node.js. Currently only supports features needed for Atproto.", + "keywords": [ + "atproto", + "oauth", + "oauth2", + "open id connect", + "oidc", + "provider", + "oidc provider" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/oauth/oauth-provider" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto-labs/fetch": "workspace:*", + "@atproto-labs/fetch-node": "workspace:*", + "@atproto-labs/pipe": "workspace:*", + "@atproto-labs/simple-store": "workspace:*", + "@atproto-labs/simple-store-memory": "workspace:*", + "@atproto/jwk": "workspace:*", + "@atproto/jwk-jose": "workspace:*", + "@atproto/oauth-types": "workspace:*", + "@hapi/accept": "^6.0.3", + "@hapi/bourne": "^3.0.0", + "cookie": "^0.6.0", + "http-errors": "^2.0.0", + "ioredis": "^5.3.2", + "jose": "^5.2.0", + "keygrip": "^1.1.0", + "oidc-token-hash": "^5.0.3", + "psl": "^1.9.0", + "tslib": "^2.6.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "@atproto-labs/rollup-plugin-bundle-manifest": "workspace:*", + "@rollup/plugin-commonjs": "^25.0.7", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^5.0.5", + "@rollup/plugin-terser": "^0.4.4", + "@rollup/plugin-typescript": "^11.1.6", + "@types/cookie": "^0.6.0", + "@types/keygrip": "^1.0.6", + "@types/react": "^18.2.50", + "@types/react-dom": "^18.2.18", + "@types/send": "^0.17.4", + "@web/rollup-plugin-import-meta-assets": "^2.2.1", + "autoprefixer": "^10.4.17", + "postcss": "^8.4.33", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "rollup": "^4.13.0", + "rollup-plugin-postcss": "^4.0.2", + "tailwindcss": "^3.4.1", + "typescript": "^5.3.3" + }, + "optionalDependencies": { + "ioredis": "^5.3.2", + "keygrip": "^1.1.0" + }, + "scripts": { + "build:frontend": "rollup --config rollup.config.js", + "build:backend": "tsc --build --force tsconfig.backend.json", + "build": "pnpm --parallel --stream '/^build:.+$/'", + "dev": "rollup --config rollup.config.js --watch" + } +} diff --git a/packages/oauth/oauth-provider/rollup.config.js b/packages/oauth/oauth-provider/rollup.config.js new file mode 100644 index 00000000000..54a754a6d6f --- /dev/null +++ b/packages/oauth/oauth-provider/rollup.config.js @@ -0,0 +1,55 @@ +/* eslint-env node */ + +const { defineConfig } = require('rollup') + +const { + default: manifest, +} = require('@atproto-labs/rollup-plugin-bundle-manifest') +const { default: commonjs } = require('@rollup/plugin-commonjs') +const { default: nodeResolve } = require('@rollup/plugin-node-resolve') +const { default: replace } = require('@rollup/plugin-replace') +const { default: terser } = require('@rollup/plugin-terser') +const { default: typescript } = require('@rollup/plugin-typescript') +const postcss = ((m) => m.default || m)(require('rollup-plugin-postcss')) + +module.exports = defineConfig((commandLineArguments) => { + const NODE_ENV = + process.env['NODE_ENV'] ?? + (commandLineArguments.watch ? 'development' : 'production') + + const minify = NODE_ENV !== 'development' + + return { + input: 'src/assets/app/main.tsx', + output: { + manualChunks: undefined, + sourcemap: true, + file: 'dist/assets/app/main.js', + format: 'iife', + }, + plugins: [ + nodeResolve({ preferBuiltins: false, browser: true }), + commonjs(), + postcss({ config: true, extract: true, minimize: minify }), + typescript({ + tsconfig: './tsconfig.frontend.json', + outputToFilesystem: true, + }), + replace({ + preventAssignment: true, + values: { 'process.env.NODE_ENV': JSON.stringify(NODE_ENV) }, + }), + // Change `data` to `true` to include assets data in the manifest, + // allowing for easier bundling of the backend code (eg. using esbuild) as + // bundlers know how to bundle JSON files but not how to bundle assets + // referenced at runtime. + manifest({ data: false }), + minify && terser({}), + ], + onwarn(warning, warn) { + // 'use client' directives are fine + if (warning.code === 'MODULE_LEVEL_DIRECTIVE') return + warn(warning) + }, + } +}) diff --git a/packages/oauth/oauth-provider/src/access-token/access-token-type.ts b/packages/oauth/oauth-provider/src/access-token/access-token-type.ts new file mode 100644 index 00000000000..d75a9711319 --- /dev/null +++ b/packages/oauth/oauth-provider/src/access-token/access-token-type.ts @@ -0,0 +1,5 @@ +export enum AccessTokenType { + auto = 'auto', + jwt = 'jwt', + id = 'id', +} diff --git a/packages/oauth/oauth-provider/src/account/account-hooks.ts b/packages/oauth/oauth-provider/src/account/account-hooks.ts new file mode 100644 index 00000000000..66532b721b4 --- /dev/null +++ b/packages/oauth/oauth-provider/src/account/account-hooks.ts @@ -0,0 +1,26 @@ +import { Client } from '../client/client.js' +import { DeviceId } from '../device/device-id.js' +import { Awaitable } from '../lib/util/type.js' +import { ClientAuth } from '../token/token-store.js' +import { Account } from './account-store.js' +// https://github.com/typescript-eslint/typescript-eslint/issues/8902 +// eslint-disable-next-line +import { AccountStore } from './account-store.js' + +/** + * Allows disabling the call to {@link AccountStore.addAuthorizedClient} based + * on the account, client and clientAuth (not all these info are available to + * the store method). + */ +export type AccountAddAuthorizedClient = ( + client: Client, + data: { + deviceId: DeviceId + account: Account + clientAuth: ClientAuth + }, +) => Awaitable + +export type AccountHooks = { + onAccountAddAuthorizedClient?: AccountAddAuthorizedClient +} diff --git a/packages/oauth/oauth-provider/src/account/account-manager.ts b/packages/oauth/oauth-provider/src/account/account-manager.ts new file mode 100644 index 00000000000..9405a889027 --- /dev/null +++ b/packages/oauth/oauth-provider/src/account/account-manager.ts @@ -0,0 +1,64 @@ +import { Client } from '../client/client.js' +import { DeviceId } from '../device/device-id.js' +import { InvalidRequestError } from '../oauth-errors.js' +import { Sub } from '../oidc/sub.js' +import { ClientAuth } from '../token/token-store.js' +import { constantTime } from '../lib/util/time.js' +import { AccountHooks } from './account-hooks.js' +import { + Account, + AccountInfo, + AccountStore, + LoginCredentials, +} from './account-store.js' + +const TIMING_ATTACK_MITIGATION_DELAY = 400 + +export class AccountManager { + constructor( + protected readonly store: AccountStore, + protected readonly hooks: AccountHooks, + ) {} + + public async signIn( + credentials: LoginCredentials, + deviceId: DeviceId, + ): Promise { + return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => { + const result = await this.store.authenticateAccount(credentials, deviceId) + if (result) return result + + throw new InvalidRequestError('Invalid credentials') + }) + } + + public async get(deviceId: DeviceId, sub: Sub): Promise { + const result = await this.store.getDeviceAccount(deviceId, sub) + if (result) return result + + throw new InvalidRequestError(`Account not found`) + } + + public async addAuthorizedClient( + deviceId: DeviceId, + account: Account, + client: Client, + clientAuth: ClientAuth, + ): Promise { + if (this.hooks.onAccountAddAuthorizedClient) { + const shouldAdd = await this.hooks.onAccountAddAuthorizedClient(client, { + deviceId, + account, + clientAuth, + }) + if (!shouldAdd) return + } + + await this.store.addAuthorizedClient(deviceId, account.sub, client.id) + } + + public async list(deviceId: DeviceId): Promise { + const results = await this.store.listDeviceAccounts(deviceId) + return results.filter((result) => result.info.remembered) + } +} diff --git a/packages/oauth/oauth-provider/src/account/account-store.ts b/packages/oauth/oauth-provider/src/account/account-store.ts new file mode 100644 index 00000000000..e1c1b051c16 --- /dev/null +++ b/packages/oauth/oauth-provider/src/account/account-store.ts @@ -0,0 +1,75 @@ +import { OAuthClientId } from '@atproto/oauth-types' + +import { DeviceId } from '../device/device-id.js' +import { Awaitable } from '../lib/util/type.js' +import { Sub } from '../oidc/sub.js' +import { Account } from './account.js' + +export type LoginCredentials = { + username: string + password: string + + /** + * If false, the account must not be returned from + * {@link AccountStore.listDeviceAccounts}. Note that this only makes sense when + * used with a device ID. + */ + remember?: boolean +} + +export type DeviceAccountInfo = { + remembered: boolean + authenticatedAt: Date + authorizedClients: readonly OAuthClientId[] +} + +// Export all types needed to implement the AccountStore interface +export type { Account, DeviceId, Sub } + +export type AccountInfo = { + account: Account + info: DeviceAccountInfo +} + +export interface AccountStore { + authenticateAccount( + credentials: LoginCredentials, + deviceId: DeviceId, + ): Awaitable + + addAuthorizedClient( + deviceId: DeviceId, + sub: Sub, + clientId: OAuthClientId, + ): Awaitable + + getDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable + removeDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable + + /** + * @note Only the accounts that where logged in with `remember: true` need to + * be returned. The others will be ignored. + */ + listDeviceAccounts(deviceId: DeviceId): Awaitable +} + +export function isAccountStore( + implementation: Record & Partial, +): implementation is Record & AccountStore { + return ( + typeof implementation.authenticateAccount === 'function' && + typeof implementation.getDeviceAccount === 'function' && + typeof implementation.addAuthorizedClient === 'function' && + typeof implementation.listDeviceAccounts === 'function' && + typeof implementation.removeDeviceAccount === 'function' + ) +} + +export function asAccountStore( + implementation?: Record & Partial, +): AccountStore { + if (!implementation || !isAccountStore(implementation)) { + throw new Error('Invalid AccountStore implementation') + } + return implementation +} diff --git a/packages/oauth/oauth-provider/src/account/account.ts b/packages/oauth/oauth-provider/src/account/account.ts new file mode 100644 index 00000000000..0728232375f --- /dev/null +++ b/packages/oauth/oauth-provider/src/account/account.ts @@ -0,0 +1,10 @@ +import { OIDCStandardPayload } from '../oidc/claims.js' +import { Sub } from '../oidc/sub.js' +import { Simplify } from '../lib/util/type.js' + +export type Account = Simplify< + { + sub: Sub // Account id + aud: string | [string, ...string[]] // Resource server URL + } & OIDCStandardPayload +> diff --git a/packages/oauth/oauth-provider/src/assets/app/app.tsx b/packages/oauth/oauth-provider/src/assets/app/app.tsx new file mode 100644 index 00000000000..e925ffae5dd --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/app.tsx @@ -0,0 +1,28 @@ +import type { + AuthorizeData, + CustomizationData, + ErrorData, +} from './backend-data' +import { AuthorizeView } from './views/authorize-view' +import { ErrorView } from './views/error-view' + +export type AppProps = { + authorizeData?: AuthorizeData + customizationData?: CustomizationData + errorData?: ErrorData +} + +export function App({ authorizeData, customizationData, errorData }: AppProps) { + if (authorizeData && !errorData) { + return ( + + ) + } else { + return ( + + ) + } +} diff --git a/packages/oauth/oauth-provider/src/assets/app/backend-data.ts b/packages/oauth/oauth-provider/src/assets/app/backend-data.ts new file mode 100644 index 00000000000..a163bb672f5 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/backend-data.ts @@ -0,0 +1,71 @@ +import type { ClientMetadata, Session } from './types' + +export type FieldDefinition = { + label?: string + placeholder?: string + pattern?: string + title?: string +} + +export type ExtraFieldDefinition = FieldDefinition & { + type: 'text' | 'password' | 'date' | 'captcha' + required?: boolean + [_: string]: unknown +} + +export type LinkDefinition = { + title: string + href: string + rel?: string +} + +export type CustomizationData = { + name?: string + logo?: string + links?: LinkDefinition[] + signIn?: { + fields?: { + username?: FieldDefinition + password?: FieldDefinition + remember?: FieldDefinition + } + } + signUp?: { + fields?: { + username?: FieldDefinition + password?: FieldDefinition + remember?: FieldDefinition + } + extraFields?: Record + } +} + +export type ErrorData = { + error: string + error_description: string +} + +export type AuthorizeData = { + clientId: string + clientMetadata: ClientMetadata + requestUri: string + csrfCookie: string + sessions: Session[] + newSessionsRequireConsent: boolean + loginHint?: string +} + +const readBackendData = (key: string): T | undefined => { + const value = window[key] as T | undefined + delete window[key] // Prevent accidental usage / potential leaks to dependencies + return value +} + +// These values are injected by the backend when it builds the +// page HTML. + +export const customizationData = readBackendData( + '__customizationData', +) +export const errorData = readBackendData('__errorData') +export const authorizeData = readBackendData('__authorizeData') diff --git a/packages/oauth/oauth-provider/src/assets/app/components/accept-form.tsx b/packages/oauth/oauth-provider/src/assets/app/components/accept-form.tsx new file mode 100644 index 00000000000..34b8a82c161 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/accept-form.tsx @@ -0,0 +1,108 @@ +import { type HTMLAttributes } from 'react' +import { clsx } from '../lib/clsx' +import { Account, ClientMetadata } from '../types' +import { ClientIdentifier } from './client-identifier' +import { ClientName } from './client-name' +import { AccountIdentifier } from './account-identifier' + +export type AcceptFormProps = { + account: Account + clientId: string + clientMetadata: ClientMetadata + onAccept: () => void + acceptLabel?: string + + onReject: () => void + rejectLabel?: string + + onBack?: () => void + backLabel?: string +} + +export function AcceptForm({ + account, + clientId, + clientMetadata, + onAccept, + acceptLabel = 'Accept', + onReject, + rejectLabel = 'Deny access', + onBack, + backLabel = 'Back', + + ...attrs +}: AcceptFormProps & HTMLAttributes) { + return ( +
+ {clientMetadata.logo_uri && ( +
+ {clientMetadata.client_name} +
+ )} + + + +

+ {' '} + is asking for permission to access your{' '} + account. +

+ +

+ By clicking {acceptLabel}, you allow this application to access + your information in accordance to its{' '} + + terms of service + + . +

+ +
+ +
+ + + {onBack && ( + + )} + +
+ + +
+
+ ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/account-identifier.tsx b/packages/oauth/oauth-provider/src/assets/app/components/account-identifier.tsx new file mode 100644 index 00000000000..f8981d5ac05 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/account-identifier.tsx @@ -0,0 +1,17 @@ +import { HTMLAttributes } from 'react' +import { Account } from '../types' + +export type AccountIdentifierProps = { + account: Account +} + +export function AccountIdentifier({ + account, + ...attrs +}: AccountIdentifierProps & HTMLAttributes) { + return ( + + {account.preferred_username || account.email || account.sub} + + ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/account-picker.tsx b/packages/oauth/oauth-provider/src/assets/app/components/account-picker.tsx new file mode 100644 index 00000000000..bfc89434613 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/account-picker.tsx @@ -0,0 +1,108 @@ +import type { HTMLAttributes, ReactNode } from 'react' +import { Account } from '../types' +import { clsx } from '../lib/clsx' + +export type AccountPickerProps = { + accounts: readonly Account[] + + onAccount: (account: Account) => void + accountAria?: (account: Account) => string + + onOther?: () => void + otherLabel?: ReactNode + otherAria?: string + + onBack?: () => void + backLabel?: ReactNode + backAria?: string +} + +export function AccountPicker({ + accounts, + + onAccount, + accountAria = (a) => `Sign in as ${a.name}`, + + onOther = undefined, + otherLabel = 'Other account', + otherAria = 'Login to account that is not listed', + + onBack, + backLabel, + backAria, + + className, + ...attrs +}: AccountPickerProps & HTMLAttributes) { + return ( +
+

Sign in as...

+ + {accounts.map((account) => { + const [name, identifier] = [ + account.name, + account.preferred_username, + account.email, + account.sub, + ].filter(Boolean) as [string, string?] + + return ( + + ) + })} + {onOther && ( + + )} + +
+ + {onBack && ( +
+ +
+ )} +
+ ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/client-identifier.tsx b/packages/oauth/oauth-provider/src/assets/app/components/client-identifier.tsx new file mode 100644 index 00000000000..0c983cb8341 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/client-identifier.tsx @@ -0,0 +1,31 @@ +import { HTMLAttributes } from 'react' +import { ClientMetadata } from '../types' +import { UrlViewer } from './url-viewer' + +export type ClientIdentifierProps = { + clientId: string + clientMetadata: ClientMetadata + as?: keyof JSX.IntrinsicElements +} + +export function ClientIdentifier({ + clientId, + clientMetadata, + as: As = 'span', + ...attrs +}: ClientIdentifierProps & HTMLAttributes) { + if (clientMetadata.client_uri) { + return ( + + ) + } + + // Fallback to the client ID + return {clientId} +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/client-name.tsx b/packages/oauth/oauth-provider/src/assets/app/components/client-name.tsx new file mode 100644 index 00000000000..4d141f98b4e --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/client-name.tsx @@ -0,0 +1,29 @@ +import { HTMLAttributes } from 'react' +import { ClientMetadata } from '../types' +import { ClientIdentifier } from './client-identifier' + +export type ClientNameProps = { + clientId: string + clientMetadata: ClientMetadata + as?: keyof JSX.IntrinsicElements +} + +export function ClientName({ + clientId, + clientMetadata, + as: As = 'span', + ...attrs +}: ClientNameProps & HTMLAttributes) { + if (clientMetadata.client_name) { + return {clientMetadata.client_name} + } + + return ( + + ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/error-card.tsx b/packages/oauth/oauth-provider/src/assets/app/components/error-card.tsx new file mode 100644 index 00000000000..3a41112717b --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/error-card.tsx @@ -0,0 +1,41 @@ +import { HtmlHTMLAttributes } from 'react' +import { clsx } from '../lib/clsx' + +export type ErrorCardProps = { + message?: null | string + role?: 'alert' | 'status' +} + +export function ErrorCard({ + message, + + role = 'alert', + className, + ...attrs +}: Partial & + Omit, keyof ErrorCardProps | 'children'>) { + return ( +
+ + + + +
+

+ {typeof message === 'string' ? message : 'An unknown error occurred'} +

+
+
+ ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/help-card.tsx b/packages/oauth/oauth-provider/src/assets/app/components/help-card.tsx new file mode 100644 index 00000000000..23c0434fd06 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/help-card.tsx @@ -0,0 +1,42 @@ +import { HTMLAttributes } from 'react' +import { LinkDefinition } from '../backend-data' +import { clsx } from '../lib/clsx' + +export type HelpCardProps = { + links?: readonly LinkDefinition[] +} + +export function HelpCard({ + links, + + className, + ...attrs +}: HelpCardProps & + Omit< + HTMLAttributes, + keyof HelpCardProps | 'children' + >) { + const helpLink = links?.find((l) => l.rel === 'help') + + if (!helpLink) return null + + return ( +

+ Having trouble?{' '} + + Contact {helpLink.title} + +

+ ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/layout-title-page.tsx b/packages/oauth/oauth-provider/src/assets/app/components/layout-title-page.tsx new file mode 100644 index 00000000000..c4985c22bed --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/layout-title-page.tsx @@ -0,0 +1,43 @@ +import { HTMLAttributes, ReactNode } from 'react' +import { clsx } from '../lib/clsx' + +export type LayoutTitlePageProps = { + title?: ReactNode + subtitle?: ReactNode +} + +export function LayoutTitlePage({ + children, + title, + subtitle, + ...attrs +}: LayoutTitlePageProps & + Omit, keyof LayoutTitlePageProps>) { + return ( +
+
+ {title && ( +

+ {title} +

+ )} + + {subtitle && ( +

+ {subtitle} +

+ )} +
+ +
+ {children} +
+
+ ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/layout-welcome.tsx b/packages/oauth/oauth-provider/src/assets/app/components/layout-welcome.tsx new file mode 100644 index 00000000000..e3c02bba5b2 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/layout-welcome.tsx @@ -0,0 +1,58 @@ +import { PropsWithChildren } from 'react' + +export type LayoutWelcomeProps = { + name?: string + logo?: string + links?: Array<{ + title: string + href: string + rel?: string + }> + logoAlt?: string +} + +export function LayoutWelcome({ + name, + logo, + logoAlt = name || 'Logo', + links, + children, +}: PropsWithChildren) { + return ( +
+
+ {logo && ( + {logoAlt} + )} + + {name && ( +

+ {name} +

+ )} + + {children} +
+ + {links != null && links.length > 0 && ( + + )} +
+ ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/sign-in-form.tsx b/packages/oauth/oauth-provider/src/assets/app/components/sign-in-form.tsx new file mode 100644 index 00000000000..3a0009a3e6c --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/sign-in-form.tsx @@ -0,0 +1,290 @@ +import { + FormHTMLAttributes, + ReactNode, + SyntheticEvent, + useCallback, + useState, +} from 'react' + +import { clsx } from '../lib/clsx' +import { ErrorCard } from './error-card' + +export type SignInFormOutput = { + username: string + password: string + remember?: boolean +} + +export type SignInFormProps = { + title?: ReactNode + + onSubmit: (credentials: SignInFormOutput) => void | PromiseLike + submitLabel?: ReactNode + submitAria?: string + + onCancel?: () => void + cancelLabel?: ReactNode + cancelAria?: string + + usernameDefault?: string + usernameReadonly?: boolean + usernameLabel?: string + usernamePlaceholder?: string + usernameAria?: string + usernamePattern?: string + usernameTitle?: string + + passwordLabel?: string + passwordPlaceholder?: string + passwordWarning?: ReactNode + passwordAria?: string + passwordPattern?: string + passwordTitle?: string + + rememberVisible?: boolean + rememberDefault?: boolean + rememberLabel?: string + rememberAria?: string +} + +export function SignInForm({ + title = 'Sign in', + + onSubmit, + submitAria = 'Next', + submitLabel = submitAria, + + onCancel = undefined, + cancelAria = 'Cancel', + cancelLabel = cancelAria, + + usernameDefault = '', + usernameReadonly = false, + usernameLabel = 'Username', + usernameAria = usernameLabel, + usernamePlaceholder = usernameLabel, + usernamePattern, + usernameTitle = 'Username must not be empty', + + passwordLabel = 'Password', + passwordAria = passwordLabel, + passwordPlaceholder = passwordLabel, + passwordPattern, + passwordTitle = 'Password must not be empty', + passwordWarning = ( + <> +

Warning

+

+ Please verify the domain name of the website before entering your + password. Never enter your password on a domain you do not trust. +

+ + ), + + rememberVisible = true, + rememberDefault = false, + rememberLabel = 'Remember this account on this device', + rememberAria = rememberLabel, + + className, + ...attrs +}: SignInFormProps & + Omit< + FormHTMLAttributes, + keyof SignInFormProps | 'children' + >) { + const [focused, setFocused] = useState(false) + const [loading, setLoading] = useState(false) + const [errorMessage, setErrorMessage] = useState(null) + + const doSubmit = useCallback( + async ( + event: SyntheticEvent< + HTMLFormElement & { + username: HTMLInputElement + password: HTMLInputElement + remember?: HTMLInputElement + }, + SubmitEvent + >, + ) => { + event.preventDefault() + + const credentials = { + username: event.currentTarget.username.value, + password: event.currentTarget.password.value, + remember: event.currentTarget.remember?.checked, + } + + setLoading(true) + setErrorMessage(null) + try { + await onSubmit(credentials) + } catch (err) { + setErrorMessage(parseErrorMessage(err)) + } finally { + setLoading(false) + } + }, + [onSubmit, setErrorMessage, setLoading], + ) + + return ( +
+

{title}

+
+
+ @ + setErrorMessage(null)} + className="relative m-0 block w-[1px] min-w-0 flex-auto px-3 py-[0.25rem] leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100 disabled:text-gray-500" + placeholder={usernamePlaceholder} + aria-label={usernameAria} + autoCapitalize="none" + autoCorrect="off" + autoComplete="username" + spellCheck="false" + dir="auto" + enterKeyHint="next" + required + defaultValue={usernameDefault} + readOnly={usernameReadonly} + disabled={usernameReadonly} + pattern={usernamePattern} + title={usernameTitle} + /> +
+ +
+ +
+ * + setErrorMessage(null)} + onFocus={() => setFocused(true)} + onBlur={() => setTimeout(setFocused, 100, false)} + className="relative m-0 block w-[1px] min-w-0 flex-auto px-3 py-[0.25rem] leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100" + placeholder={passwordPlaceholder} + aria-label={passwordAria} + autoCapitalize="none" + autoCorrect="off" + autoComplete="current-password" + dir="auto" + enterKeyHint="done" + spellCheck="false" + required + pattern={passwordPattern} + title={passwordTitle} + /> +
+ + {passwordWarning && ( + <> +
+
+
+
+ + + +
+
{passwordWarning}
+
+
+ + )} + + {rememberVisible && ( + <> +
+ +
+ + setErrorMessage(null)} + /> + + + +
+ + )} +
+ + {errorMessage && } + +
+ +
+ + + {onCancel && ( + + )} + +
+
+ + ) +} + +function parseErrorMessage(err: unknown): string { + console.error('Sign-in failed:', err) + switch ((err as any)?.message) { + case 'Invalid credentials': + return 'Invalid username or password' + default: + return 'An unknown error occurred' + } +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/sign-up-account-form.tsx b/packages/oauth/oauth-provider/src/assets/app/components/sign-up-account-form.tsx new file mode 100644 index 00000000000..de7bbf410c3 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/sign-up-account-form.tsx @@ -0,0 +1,210 @@ +import { + FormHTMLAttributes, + ReactNode, + SyntheticEvent, + useCallback, + useState, +} from 'react' + +import { clsx } from '../lib/clsx' +import { ErrorCard } from './error-card' + +export type SignUpAccountFormOutput = { + username: string + password: string +} + +export type SignUpAccountFormProps = { + onSubmit: (credentials: SignUpAccountFormOutput) => void | PromiseLike + submitLabel?: ReactNode + submitAria?: string + + onCancel?: () => void + cancelLabel?: ReactNode + cancelAria?: string + + username?: string + usernamePlaceholder?: string + usernameLabel?: string + usernameAria?: string + usernamePattern?: string + usernameTitle?: string + + passwordPlaceholder?: string + passwordLabel?: string + passwordAria?: string + passwordPattern?: string + passwordTitle?: string +} + +export function SignUpAccountForm({ + onSubmit, + submitAria = 'Next', + submitLabel = submitAria, + + onCancel = undefined, + cancelAria = 'Cancel', + cancelLabel = cancelAria, + + username: defaultUsername = '', + usernameLabel = 'Username', + usernameAria = usernameLabel, + usernamePlaceholder = usernameLabel, + usernamePattern, + usernameTitle, + + passwordLabel = 'Password', + passwordAria = passwordLabel, + passwordPlaceholder = passwordLabel, + passwordPattern, + passwordTitle, + + className, + children, + ...attrs +}: SignUpAccountFormProps & + Omit, keyof SignUpAccountFormProps>) { + const [loading, setLoading] = useState(false) + const [errorMessage, setErrorMessage] = useState(null) + + const doSubmit = useCallback( + async ( + event: SyntheticEvent< + HTMLFormElement & { + username: HTMLInputElement + password: HTMLInputElement + }, + SubmitEvent + >, + ) => { + event.preventDefault() + + const credentials = { + username: event.currentTarget.username.value, + password: event.currentTarget.password.value, + } + + setLoading(true) + setErrorMessage(null) + try { + await onSubmit(credentials) + } catch (err) { + setErrorMessage(parseErrorMessage(err)) + } finally { + setLoading(false) + } + }, + [onSubmit, setErrorMessage, setLoading], + ) + + return ( +
+
+ + +
+ @ + setErrorMessage(null)} + className="relative m-1 block w-[1px] min-w-0 flex-auto leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100 disabled:text-gray-500" + placeholder={usernamePlaceholder} + aria-label={usernameAria} + autoCapitalize="none" + autoCorrect="off" + autoComplete="username" + spellCheck="false" + dir="auto" + enterKeyHint="next" + required + defaultValue={defaultUsername} + pattern={usernamePattern} + title={usernameTitle} + /> +
+ + + +
+ + * + + setErrorMessage(null)} + className="relative m-1 block w-[1px] min-w-0 flex-auto leading-[1.6] bg-transparent bg-clip-padding text-base text-inherit outline-none dark:placeholder:text-neutral-100" + placeholder={passwordPlaceholder} + aria-label={passwordAria} + autoCapitalize="none" + autoCorrect="off" + autoComplete="new-password" + dir="auto" + enterKeyHint="done" + spellCheck="false" + required + pattern={passwordPattern} + title={passwordTitle} + /> +
+
+ + {children &&
{children}
} + + {errorMessage && } + +
+ +
+ + + {onCancel && ( + + )} + +
+
+ + ) +} + +function parseErrorMessage(err: unknown): string { + switch ((err as any)?.message) { + case 'Invalid credentials': + return 'Invalid username or password' + default: + console.error(err) + return 'An unknown error occurred' + } +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/sign-up-disclaimer.tsx b/packages/oauth/oauth-provider/src/assets/app/components/sign-up-disclaimer.tsx new file mode 100644 index 00000000000..0acc39f6fa8 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/sign-up-disclaimer.tsx @@ -0,0 +1,44 @@ +import { HTMLAttributes } from 'react' +import { LinkDefinition } from '../backend-data' +import { clsx } from '../lib/clsx' + +export type SignUpDisclaimerProps = { + links?: readonly LinkDefinition[] +} + +export function SignUpDisclaimer({ + links, + + className, + ...attrs +}: SignUpDisclaimerProps & + Omit< + HTMLAttributes, + keyof SignUpDisclaimerProps | 'children' + >) { + const relevantLinks = links?.filter( + (l) => l.rel === 'privacy-policy' || l.rel === 'terms-of-service', + ) + + return ( +

+ By creating an account you agree to the{' '} + {relevantLinks && relevantLinks.length + ? relevantLinks.map((l, i, a) => ( + + {i > 0 && (i < a.length - 1 ? ', ' : ' and ')} + + {l.title} + + + )) + : 'Terms of Service and Privacy Policy'} + . +

+ ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/components/url-viewer.tsx b/packages/oauth/oauth-provider/src/assets/app/components/url-viewer.tsx new file mode 100644 index 00000000000..4879a5d8e23 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/components/url-viewer.tsx @@ -0,0 +1,70 @@ +import { HTMLAttributes, useMemo } from 'react' + +export type UrlPartRenderingOptions = { + faded?: boolean + bold?: boolean +} + +export type UrlRendererProps = { + url: string | URL + proto?: boolean | UrlPartRenderingOptions + host?: boolean | UrlPartRenderingOptions + path?: boolean | UrlPartRenderingOptions + query?: boolean | UrlPartRenderingOptions + hash?: boolean | UrlPartRenderingOptions + as?: keyof JSX.IntrinsicElements +} + +export function UrlViewer({ + url, + proto = false, + host = true, + path = false, + query = false, + hash = false, + as: As = 'span', + ...attrs +}: UrlRendererProps & HTMLAttributes) { + const urlObj = useMemo(() => new URL(url), [url]) + + return ( + + {proto && ( + + )} + {host && ( + + )} + {path && ( + + )} + {query && ( + + )} + {hash && ( + + )} + + ) +} + +function UrlPartViewer({ + value, + faded = true, + bold = false, +}: { value: string } & UrlPartRenderingOptions) { + const Comp = bold ? 'b' : 'span' + return {value} +} diff --git a/packages/oauth/oauth-provider/src/assets/app/cookies.ts b/packages/oauth/oauth-provider/src/assets/app/cookies.ts new file mode 100644 index 00000000000..1f04cf3ae1a --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/cookies.ts @@ -0,0 +1,9 @@ +export const parseCookieString = (cookie: string) => + Object.fromEntries( + cookie + .split(';') + .filter(Boolean) + .map((str) => str.split('=', 2).map((s) => decodeURIComponent(s.trim()))), + ) + +export const cookies = parseCookieString(document.cookie) diff --git a/packages/oauth/oauth-provider/src/assets/app/hooks/use-api.ts b/packages/oauth/oauth-provider/src/assets/app/hooks/use-api.ts new file mode 100644 index 00000000000..a83b1fb54bc --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/hooks/use-api.ts @@ -0,0 +1,105 @@ +import { useCallback, useMemo, useState } from 'react' + +import { AuthorizeData } from '../backend-data' +import { Api } from '../lib/api' +import { upsert } from '../lib/util' +import { Account, Session } from '../types' +import { useCsrfToken } from './use-csrf-token' + +export type SignInCredentials = { + username: string + password: string + remember?: boolean +} + +export type SignUpData = { + username: string + password: string + extra?: Record +} + +export function useApi( + { + clientId, + requestUri, + csrfCookie, + sessions: initialSessions, + newSessionsRequireConsent, + }: AuthorizeData, + { + onRedirected, + }: { + onRedirected?: () => void + } = {}, +) { + const csrfToken = useCsrfToken(csrfCookie) + const [sessions, setSessions] = useState(initialSessions) + + const setSession = useCallback( + (sub: string | null) => { + setSessions((sessions) => + sub === (sessions.find((s) => s.selected)?.account.sub || null) + ? sessions + : sessions.map((s) => ({ ...s, selected: s.account.sub === sub })), + ) + }, + [setSessions], + ) + + const api = useMemo( + () => new Api(requestUri, clientId, csrfToken, newSessionsRequireConsent), + [requestUri, clientId, csrfToken, newSessionsRequireConsent], + ) + + const performRedirect = useCallback( + (url: URL) => { + window.location.href = String(url) + if (onRedirected) setTimeout(onRedirected) + }, + [onRedirected], + ) + + const doSignIn = useCallback( + async (credentials: SignInCredentials): Promise => { + const session = await api.signIn(credentials) + const { sub } = session.account + + setSessions((sessions) => { + return upsert(sessions, session, (s) => s.account.sub === sub).map( + // Make sure to de-select any other selected session + (s) => (s === session || !s.selected ? s : { ...s, selected: false }), + ) + }) + }, + [api, performRedirect, clientId, setSessions], + ) + + const doSignUp = useCallback( + (data: SignUpData) => { + // + console.error('SIGNUPPP', data, api) + }, + [api], + ) + + const doAccept = useCallback( + async (account: Account) => { + performRedirect(await api.accept(account)) + }, + [api, performRedirect], + ) + + const doReject = useCallback(async () => { + performRedirect(await api.reject()) + }, [api, performRedirect]) + + return { + sessions, + setSession, + + doSignIn, + doSignUp, + doAccept, + doReject, + } +} diff --git a/packages/oauth/oauth-provider/src/assets/app/hooks/use-bound-dispatch.ts b/packages/oauth/oauth-provider/src/assets/app/hooks/use-bound-dispatch.ts new file mode 100644 index 00000000000..8945ea41989 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/hooks/use-bound-dispatch.ts @@ -0,0 +1,5 @@ +import { Dispatch, useCallback } from 'react' + +export function useBoundDispatch(dispatch: Dispatch, value: A) { + return useCallback(() => dispatch(value), [dispatch, value]) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/hooks/use-csrf-token.ts b/packages/oauth/oauth-provider/src/assets/app/hooks/use-csrf-token.ts new file mode 100644 index 00000000000..3dc92ef67ae --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/hooks/use-csrf-token.ts @@ -0,0 +1,6 @@ +import { useMemo } from 'react' +import { cookies } from '../cookies' + +export function useCsrfToken(cookieName: string) { + return useMemo(() => cookies[cookieName], [cookieName]) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/lib/api.ts b/packages/oauth/oauth-provider/src/assets/app/lib/api.ts new file mode 100644 index 00000000000..33386c3fe65 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/lib/api.ts @@ -0,0 +1,68 @@ +import { + fetchFailureHandler, + fetchJsonProcessor, + fetchOkProcessor, +} from '@atproto-labs/fetch' + +import { Account, Session } from '../types' + +export class Api { + constructor( + private requestUri: string, + private clientId: string, + private csrfToken: string, + private newSessionsRequireConsent: boolean, + ) {} + + async signIn(credentials: { + username: string + password: string + remember?: boolean + }): Promise { + const { json } = await fetch('/oauth/authorize/sign-in', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + mode: 'same-origin', + body: JSON.stringify({ + csrf_token: this.csrfToken, + request_uri: this.requestUri, + client_id: this.clientId, + credentials, + }), + }) + .then(fetchOkProcessor(), fetchFailureHandler) + .then( + fetchJsonProcessor<{ + account: Account + consentRequired: boolean + }>(), + ) + + return { + account: json.account, + + selected: true, + loginRequired: false, + consentRequired: this.newSessionsRequireConsent || json.consentRequired, + } + } + + async accept(account: Account): Promise { + const url = new URL('/oauth/authorize/accept', window.origin) + url.searchParams.set('request_uri', this.requestUri) + url.searchParams.set('account_sub', account.sub) + url.searchParams.set('client_id', this.clientId) + url.searchParams.set('csrf_token', this.csrfToken) + + return url + } + + async reject(): Promise { + const url = new URL('/oauth/authorize/reject', window.origin) + url.searchParams.set('request_uri', this.requestUri) + url.searchParams.set('client_id', this.clientId) + url.searchParams.set('csrf_token', this.csrfToken) + + return url + } +} diff --git a/packages/oauth/oauth-provider/src/assets/app/lib/clsx.ts b/packages/oauth/oauth-provider/src/assets/app/lib/clsx.ts new file mode 100644 index 00000000000..d60a7e5d722 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/lib/clsx.ts @@ -0,0 +1,4 @@ +export function clsx(a: string | undefined, b?: string) { + if (a && b) return `${a} ${b}` + return a || b +} diff --git a/packages/oauth/oauth-provider/src/assets/app/lib/util.ts b/packages/oauth/oauth-provider/src/assets/app/lib/util.ts new file mode 100644 index 00000000000..b27faabb0d1 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/lib/util.ts @@ -0,0 +1,10 @@ +export function upsert( + arr: readonly T[], + item: T, + predicate: (value: T, index: number, obj: readonly T[]) => boolean, +): T[] { + const idx = arr.findIndex(predicate) + return idx === -1 + ? [...arr, item] + : [...arr.slice(0, idx), item, ...arr.slice(idx + 1)] +} diff --git a/packages/oauth/oauth-provider/src/assets/app/main.css b/packages/oauth/oauth-provider/src/assets/app/main.css new file mode 100644 index 00000000000..74dcc88675c --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/main.css @@ -0,0 +1,11 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Matches colors defined in tailwind.config.js */ +@layer base { + :root { + --color-primary: 255 115 179; + --color-error: 235 65 49; + } +} diff --git a/packages/oauth/oauth-provider/src/assets/app/main.tsx b/packages/oauth/oauth-provider/src/assets/app/main.tsx new file mode 100644 index 00000000000..a9b357f33f5 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/main.tsx @@ -0,0 +1,26 @@ +import './main.css' + +import { createRoot } from 'react-dom/client' + +import { App } from './app' +import * as backendData from './backend-data' +import { authorizeData } from './backend-data' + +if (authorizeData) { + // When the user is logging in, make sure the page URL contains the + // "request_uri" in case the user refreshes the page. + const url = new URL(window.location.href) + if ( + url.pathname === '/oauth/authorize' && + !url.searchParams.has('request_uri') + ) { + url.search = '' + url.searchParams.set('client_id', authorizeData.clientId) + url.searchParams.set('request_uri', authorizeData.requestUri) + window.history.replaceState(history.state, '', url.pathname + url.search) + } +} + +const container = document.getElementById('root')! +const root = createRoot(container) +root.render() diff --git a/packages/oauth/oauth-provider/src/assets/app/types.ts b/packages/oauth/oauth-provider/src/assets/app/types.ts new file mode 100644 index 00000000000..2d9cc36e9e0 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/types.ts @@ -0,0 +1,55 @@ +// ../client is not a shared module (frontend/backend) and cannot be imported in +// the frontend. +export type ClientMetadata = { + client_id?: string + application_type?: 'native' | 'web' + contacts?: string[] + client_name?: string + logo_uri?: string + client_uri?: string + policy_uri?: string + tos_uri?: string + [key: string]: unknown +} + +export type Address = { + formatted?: string + street_address?: string + locality?: string + region?: string + postal_code?: string + country?: string +} + +export type Account = { + sub: string + aud: string + + email?: string + email_verified?: boolean + phone_number?: string + phone_number_verified?: boolean + address?: Address + name?: string + family_name?: string + given_name?: string + middle_name?: string + nickname?: string + preferred_username?: string + gender?: string + picture?: string + profile?: string + website?: string + birthdate?: `${number}-${number}-${number}` + zoneinfo?: string + locale?: `${string}-${string}` | string + updated_at?: number +} + +export type Session = { + account: Account + + selected: boolean + loginRequired: boolean + consentRequired: boolean +} diff --git a/packages/oauth/oauth-provider/src/assets/app/views/accept-view.tsx b/packages/oauth/oauth-provider/src/assets/app/views/accept-view.tsx new file mode 100644 index 00000000000..cc694372445 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/views/accept-view.tsx @@ -0,0 +1,46 @@ +import { AcceptForm } from '../components/accept-form' +import { LayoutTitlePage } from '../components/layout-title-page' +import { ClientMetadata, Session } from '../types' + +export type AcceptViewProps = { + clientId: string + clientMetadata: ClientMetadata + session: Session + + onAccept: () => void + onReject: () => void + onBack?: () => void +} + +export function AcceptView({ + clientId, + clientMetadata, + session, + onAccept, + onReject, + onBack, +}: AcceptViewProps) { + const { account } = session + return ( + + Grant access to your{' '} + {account.preferred_username || account.email || account.sub}{' '} + account. + + } + > + + + ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/views/authorize-view.tsx b/packages/oauth/oauth-provider/src/assets/app/views/authorize-view.tsx new file mode 100644 index 00000000000..6534a656428 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/views/authorize-view.tsx @@ -0,0 +1,103 @@ +import { useEffect, useState } from 'react' + +import type { AuthorizeData, CustomizationData } from '../backend-data' +import { LayoutTitlePage } from '../components/layout-title-page' +import { useApi } from '../hooks/use-api' +import { useBoundDispatch } from '../hooks/use-bound-dispatch' +import { AcceptView } from './accept-view' +import { SignInView } from './sign-in-view' +import { SignUpView } from './sign-up-view' +import { WelcomeView } from './welcome-view' + +export type AuthorizeViewProps = { + authorizeData: AuthorizeData + customizationData?: CustomizationData +} + +export function AuthorizeView({ + authorizeData, + customizationData, +}: AuthorizeViewProps) { + const forceSignIn = authorizeData?.loginHint != null + + const [view, setView] = useState< + 'welcome' | 'sign-in' | 'sign-up' | 'accept' | 'done' + >(forceSignIn ? 'sign-in' : 'welcome') + + const showDone = useBoundDispatch(setView, 'done') + const showSignIn = useBoundDispatch(setView, 'sign-in') + const showSignUp = useBoundDispatch(setView, 'sign-up') + const showAccept = useBoundDispatch(setView, 'accept') + const showWelcome = useBoundDispatch(setView, 'welcome') + + const { sessions, setSession, doAccept, doReject, doSignIn, doSignUp } = + useApi(authorizeData, { onRedirected: showDone }) + + const session = sessions.find((s) => s.selected && !s.loginRequired) + useEffect(() => { + if (session) { + if (session.consentRequired) showAccept() + else doAccept(session.account) + } + }, [session, doAccept, showAccept]) + + if (view === 'welcome') { + return ( + + ) + } + + if (view === 'sign-up') { + return ( + + ) + } + + if (view === 'sign-in') { + return ( + + ) + } + + if (view === 'accept' && session) { + return ( + doAccept(session.account)} + onReject={doReject} + onBack={() => { + setSession(null) + setView(sessions.length ? 'sign-in' : 'welcome') + }} + /> + ) + } + + return ( + + You are being redirected... + + ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/views/error-view.tsx b/packages/oauth/oauth-provider/src/assets/app/views/error-view.tsx new file mode 100644 index 00000000000..90a664d3abd --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/views/error-view.tsx @@ -0,0 +1,27 @@ +import { CustomizationData, ErrorData } from '../backend-data' +import { ErrorCard } from '../components/error-card' +import { LayoutWelcome } from '../components/layout-welcome' + +export type ErrorViewProps = { + customizationData?: CustomizationData + errorData?: ErrorData +} + +export function ErrorView({ errorData, customizationData }: ErrorViewProps) { + return ( + + + + ) +} + +function getUserFriendlyMessage(errorData?: ErrorData) { + const desc = errorData?.error_description + switch (desc) { + case 'Unknown request_uri': // Request was removed from database + case 'This request has expired': + return 'This sign-in session has expired' + default: + return desc || 'An unknown error occurred' + } +} diff --git a/packages/oauth/oauth-provider/src/assets/app/views/sign-in-view.tsx b/packages/oauth/oauth-provider/src/assets/app/views/sign-in-view.tsx new file mode 100644 index 00000000000..522b10fb7c1 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/views/sign-in-view.tsx @@ -0,0 +1,165 @@ +import { useCallback, useEffect, useMemo, useState } from 'react' + +import { FieldDefinition } from '../backend-data' +import { AccountPicker } from '../components/account-picker' +import { LayoutTitlePage } from '../components/layout-title-page' +import { SignInForm, SignInFormOutput } from '../components/sign-in-form' +import { Session } from '../types' + +export type SignInViewProps = { + sessions: readonly Session[] + setSession: (sub: string | null) => void + loginHint?: string + + fields?: { + username?: FieldDefinition + password?: FieldDefinition + remember?: FieldDefinition + } + + onSignIn: (credentials: SignInFormOutput) => void | PromiseLike + onBack?: () => void +} + +export function SignInView({ + loginHint, + sessions, + setSession, + fields, + + onSignIn, + onBack, +}: SignInViewProps) { + const session = useMemo(() => sessions.find((s) => s.selected), [sessions]) + const clearSession = useCallback(() => setSession(null), [setSession]) + const accounts = useMemo(() => sessions.map((s) => s.account), [sessions]) + const [showSignInForm, setShowSignInForm] = useState(sessions.length === 0) + + useEffect(() => { + // Make sure the "back" action shows the account picker instead of the + // sign-in form (since the account was added to the list of current + // sessions). + if (session) setShowSignInForm(false) + }, [session]) + + if (session) { + // All set (parent view will handle the redirect) + if (!session.loginRequired) return null + + return ( + + + + ) + } + + if (loginHint) { + return ( + + + + ) + } + + if (sessions.length === 0) { + return ( + + + + ) + } + + if (showSignInForm) { + return ( + + setShowSignInForm(false)} + cancelLabel="Back" // to account picker + usernameLabel={fields?.username?.label} + usernamePlaceholder={fields?.username?.placeholder} + usernamePattern={fields?.username?.pattern} + usernameTitle={fields?.username?.title} + passwordLabel={fields?.password?.label} + passwordPlaceholder={fields?.password?.placeholder} + passwordPattern={fields?.password?.pattern} + passwordTitle={fields?.password?.title} + rememberLabel={fields?.remember?.label} + /> + + ) + } + + return ( + + setSession(a.sub)} + onOther={() => setShowSignInForm(true)} + onBack={onBack} + backLabel="Back" // to previous view + /> + + ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/views/sign-up-view.tsx b/packages/oauth/oauth-provider/src/assets/app/views/sign-up-view.tsx new file mode 100644 index 00000000000..548cfff1b5e --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/views/sign-up-view.tsx @@ -0,0 +1,141 @@ +import { ReactNode, useCallback, useEffect, useState } from 'react' + +import { + ExtraFieldDefinition, + FieldDefinition, + LinkDefinition, +} from '../backend-data' +import { HelpCard } from '../components/help-card' +import { LayoutTitlePage } from '../components/layout-title-page' +import { + SignUpAccountForm, + SignUpAccountFormOutput, +} from '../components/sign-up-account-form' +import { SignUpDisclaimer } from '../components/sign-up-disclaimer' +import { upsert } from '../lib/util' + +export type SignUpViewProps = { + stepName?: (step: number, total: number) => ReactNode + stepTitle?: (step: number, total: number) => ReactNode + + links?: LinkDefinition[] + fields?: { + username?: FieldDefinition + password?: FieldDefinition + } + extraFields?: Record + onSignUp: (data: { + username: string + password: string + extra?: Record + }) => void | PromiseLike + onBack?: () => void +} + +const defaultStepName: NonNullable = ( + step, + total, +) => `Step ${step} of ${total}` +const defaultStepTitle: NonNullable = ( + step, + total, +) => { + switch (step) { + case 1: + return 'Your account' + default: + return null + } +} + +export function SignUpView({ + stepName = defaultStepName, + stepTitle = defaultStepTitle, + + links, + fields, + extraFields, + + onSignUp, + onBack, +}: SignUpViewProps) { + const [_credentials, setCredentials] = + useState(null) + const [step, setStep] = useState<1 | 2>(1) + + const [extraFieldsEntries, setExtraFieldsEntries] = useState( + extraFields != null ? Object.entries(extraFields) : [], + ) + + const hasExtraFields = extraFieldsEntries.length > 0 + const stepCount = hasExtraFields ? 2 : 1 + + const doSubmitAccount = useCallback( + (credentials: SignUpAccountFormOutput) => { + setCredentials(credentials) + if (hasExtraFields) { + setStep(2) + } else { + onSignUp(credentials) + } + }, + [hasExtraFields, onSignUp, setCredentials, setStep], + ) + + useEffect(() => { + let ef = extraFieldsEntries + for (const entry of extraFields != null + ? Object.entries(extraFields) + : []) { + ef = upsert(ef || [], entry, (a) => a[0] === entry[0]) + } + if (ef !== extraFieldsEntries) setExtraFieldsEntries(ef) + }, [extraFields]) + + return ( + +
+

+ {stepName(step, stepCount)} +

+

+ {stepTitle(step, stepCount)} +

+ + {step === 1 && ( + + + + )} + + {step === 2 && ( + + )} + + +
+
+ ) +} diff --git a/packages/oauth/oauth-provider/src/assets/app/views/welcome-view.tsx b/packages/oauth/oauth-provider/src/assets/app/views/welcome-view.tsx new file mode 100644 index 00000000000..576d3bea8d6 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/app/views/welcome-view.tsx @@ -0,0 +1,61 @@ +import { LayoutWelcome, LayoutWelcomeProps } from '../components/layout-welcome' +import { clsx } from '../lib/clsx' + +export type WelcomeViewParams = LayoutWelcomeProps & { + onSignIn?: () => void + signInLabel?: string + + onSignUp?: () => void + signUpLabel?: string + + onCancel?: () => void + cancelLabel?: string +} + +export function WelcomeView({ + onSignUp, + signUpLabel = 'Create a new account', + onSignIn, + signInLabel = 'Sign in', + onCancel, + cancelLabel = 'Cancel', + + ...props +}: WelcomeViewParams) { + return ( + + {onSignUp && ( + + )} + + {onSignIn && ( + + )} + + {onCancel && ( + + )} + + ) +} diff --git a/packages/oauth/oauth-provider/src/assets/asset.ts b/packages/oauth/oauth-provider/src/assets/asset.ts new file mode 100644 index 00000000000..136e8794572 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/asset.ts @@ -0,0 +1,8 @@ +import type { Readable } from 'node:stream' + +export type Asset = { + url: string + type?: string + sha256: string + createStream: () => Readable +} diff --git a/packages/oauth/oauth-provider/src/assets/assets-middleware.ts b/packages/oauth/oauth-provider/src/assets/assets-middleware.ts new file mode 100644 index 00000000000..2471e4c9075 --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/assets-middleware.ts @@ -0,0 +1,32 @@ +import { writeStream } from '../lib/http/index.js' + +import { ASSETS_URL_PREFIX, getAsset } from './index.js' + +export function authorizeAssetsMiddleware() { + return async function assetsMiddleware(req, res, next): Promise { + if (req.method !== 'GET' && req.method !== 'HEAD') return next() + if (!req.url?.startsWith(ASSETS_URL_PREFIX)) return next() + + const [pathname, query] = req.url.split('?', 2) as [ + string, + string | undefined, + ] + const filename = pathname.slice(ASSETS_URL_PREFIX.length) + if (!filename) return next() + + const asset = await getAsset(filename).catch(() => null) + if (!asset) return next() + + if (req.headers['if-none-match'] === asset.sha256) { + return void res.writeHead(304).end() + } + + res.setHeader('ETag', asset.sha256) + + if (query === asset.sha256) { + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') + } + + await writeStream(res, asset.createStream(), asset.type) + } +} diff --git a/packages/oauth/oauth-provider/src/assets/index.ts b/packages/oauth/oauth-provider/src/assets/index.ts new file mode 100644 index 00000000000..c7cf4d6098d --- /dev/null +++ b/packages/oauth/oauth-provider/src/assets/index.ts @@ -0,0 +1,74 @@ +import type { ManifestItem } from '@atproto-labs/rollup-plugin-bundle-manifest' + +// If this library is used as a regular dependency (e.g. from node_modules), the +// assets will simply be referenced from the node_modules directory. However, if +// this library is bundled (e.g. via rollup), the assets need to be copied to +// the output directory. Most bundlers support this (webpack, rollup, etc.) by +// re-writing new URL('./path', import.meta.url) calls to point to the correct +// output directory. +// +// https://github.com/evanw/esbuild/issues/795 +// https://www.npmjs.com/package/@web/rollup-plugin-import-meta-assets + +// Note that the bundle-manifest, being a JSON file, can be imported directly +// without any special handling. This is because all bundlers support JSON +// imports out of the box. + +import { createReadStream } from 'node:fs' +import { join, posix } from 'node:path' +import { Readable } from 'node:stream' + +// @ts-expect-error: This file is generated at build time +import appBundleManifestJson from './app/bundle-manifest.json' +import { Asset } from './asset' + +const appBundleManifest: Map = new Map( + Object.entries(appBundleManifestJson), +) + +export const ASSETS_URL_PREFIX = '/@atproto/oauth-provider/~assets/' + +export async function getAsset(inputFilename: string): Promise { + const filename = posix.normalize(inputFilename) + + if ( + filename.startsWith('/') || // Prevent absolute paths + filename.startsWith('../') || // Prevent directory traversal attacks + /[<>:"|?*\\]/.test(filename) // Windows disallowed characters + ) { + throw new AssetNotFoundError(filename) + } + + const manifest = appBundleManifest.get(filename) + if (!manifest) throw new AssetNotFoundError(filename) + + // When this package is used as a regular "node_modules" dependency, and gets + // bundled by the consumer, the assets should be copied to the bundle's output + // directory. In case the bundler does not support copying assets from the + // "dist/assets/app" folder, this package's build system can be modified to + // embed the asset data directly into the bundle-manifest.json (see the `data` + // option of "@atproto-labs/rollup-plugin-bundle-manifest" in rollup.config.js). + + const { data } = manifest + + return { + url: posix.join(ASSETS_URL_PREFIX, filename), + type: manifest.mime, + sha256: manifest.sha256, + createStream: data + ? () => Readable.from(Buffer.from(data, 'base64')) + : () => + // ESM version: + // createReadStream(new URL(`./app/${filename}`, import.meta.url)) + // CJS version: + createReadStream(join(__dirname, './app', filename)), + } +} + +class AssetNotFoundError extends Error { + public readonly code = 'ENOENT' + public readonly statusCode = 404 + constructor(filename: string) { + super(`Asset not found: ${filename}`) + } +} diff --git a/packages/oauth/oauth-provider/src/client/client-auth.ts b/packages/oauth/oauth-provider/src/client/client-auth.ts new file mode 100644 index 00000000000..f210ee55dea --- /dev/null +++ b/packages/oauth/oauth-provider/src/client/client-auth.ts @@ -0,0 +1,32 @@ +import { CLIENT_ASSERTION_TYPE_JWT_BEARER } from '@atproto/oauth-types' +import { KeyLike, calculateJwkThumbprint, exportJWK } from 'jose' + +export type ClientAuth = + | { method: 'none' } + | { + method: typeof CLIENT_ASSERTION_TYPE_JWT_BEARER + alg: string + kid: string + jkt: string + } + +export function compareClientAuth(a: ClientAuth, b: ClientAuth): boolean { + if (a.method === 'none') { + if (b.method !== a.method) return false + + return true + } + + if (a.method === CLIENT_ASSERTION_TYPE_JWT_BEARER) { + if (b.method !== a.method) return false + + return true + } + + // Fool-proof + throw new TypeError('Invalid ClientAuth method') +} + +export async function authJwkThumbprint(key: Uint8Array | KeyLike) { + return calculateJwkThumbprint(await exportJWK(key), 'sha512') +} diff --git a/packages/oauth/oauth-provider/src/client/client-data.ts b/packages/oauth/oauth-provider/src/client/client-data.ts new file mode 100644 index 00000000000..47969bd2ce0 --- /dev/null +++ b/packages/oauth/oauth-provider/src/client/client-data.ts @@ -0,0 +1,7 @@ +import { Jwks } from '@atproto/jwk' +import { OAuthClientMetadata } from '@atproto/oauth-types' + +export type ClientData = { + metadata: OAuthClientMetadata + jwks?: Jwks +} diff --git a/packages/oauth/oauth-provider/src/client/client-hooks.ts b/packages/oauth/oauth-provider/src/client/client-hooks.ts new file mode 100644 index 00000000000..6966b33470e --- /dev/null +++ b/packages/oauth/oauth-provider/src/client/client-hooks.ts @@ -0,0 +1,20 @@ +import { Jwks } from '@atproto/jwk' +import { OAuthClientId, OAuthClientMetadata } from '@atproto/oauth-types' + +import { Awaitable } from '../lib/util/type.js' + +/** + * Use this to alter, override or validate the client metadata & jwks returned + * by the client store. + * + * @throws {InvalidClientMetadataError} if the metadata is invalid + * @see {@link InvalidClientMetadataError} + */ +export type ClientDataHook = ( + clientId: OAuthClientId, + data: { metadata: OAuthClientMetadata; jwks?: Jwks }, +) => Awaitable + +export type ClientHooks = { + onClientData?: ClientDataHook +} diff --git a/packages/oauth/oauth-provider/src/client/client-manager.ts b/packages/oauth/oauth-provider/src/client/client-manager.ts new file mode 100644 index 00000000000..d1f1b8536c5 --- /dev/null +++ b/packages/oauth/oauth-provider/src/client/client-manager.ts @@ -0,0 +1,331 @@ +import { Keyset } from '@atproto/jwk' +import { OAuthClientId } from '@atproto/oauth-types' + +import { InvalidClientMetadataError } from '../errors/invalid-client-metadata-error.js' +import { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js' +import { OAuthError } from '../errors/oauth-error.js' +import { ClientData } from './client-data.js' +import { ClientHooks } from './client-hooks.js' +import { ClientStore } from './client-store.js' +import { parseRedirectUri } from './client-utils.js' +import { Client } from './client.js' + +export class ClientManager { + constructor( + protected readonly store: ClientStore, + protected readonly keyset: Keyset, + protected readonly hooks: ClientHooks, + ) {} + + /** + * This method will ensure that the client metadata is valid w.r.t. the OAuth + * and OIDC specifications. It will also ensure that the metadata is + * compatible with this implementation. + */ + protected async findClient(clientId: OAuthClientId): Promise { + try { + const { metadata, jwks } = await this.store.findClient(clientId) + + if (metadata.jwks && metadata.jwks_uri) { + throw new InvalidClientMetadataError( + 'jwks_uri and jwks are mutually exclusive', + ) + } + + const scopes = metadata.scope?.split(' ') + if ( + metadata.grant_types.includes('refresh_token') !== + (scopes?.includes('offline_access') ?? false) + ) { + throw new InvalidClientMetadataError( + 'Grant type "refresh_token" requires scope "offline_access"', + ) + } + + for (const responseType of metadata.response_types) { + const rt = responseType.split(' ') + + if ( + rt.includes('code') && + !metadata.grant_types.includes('authorization_code') + ) { + throw new InvalidClientMetadataError( + `Response type "${responseType}" requires the "authorization_code" grant type`, + ) + } + + if (rt.includes('id_token') && !scopes?.includes('openid')) { + throw new InvalidClientMetadataError( + 'Response type "token" requires scope "openid"', + ) + } + + // Asking for "code token" or "code id_token" is fine (as long as the + // grant_types includes "authorization_code" and the scope includes + // "openid"). Asking for "token" or "id_token" (without "code") requires + // the "implicit" grant type. + if ( + !rt.includes('code') && + (rt.includes('token') || rt.includes('id_token')) && + !metadata.grant_types.includes('implicit') + ) { + throw new InvalidClientMetadataError( + `Response type "${responseType}" requires the "implicit" grant type`, + ) + } + } + + for (const grantType of metadata.grant_types) { + switch (grantType) { + case 'authorization_code': + case 'refresh_token': + case 'implicit': // Required by OIDC (for id_token) + continue + case 'password': + throw new InvalidClientMetadataError( + `Grant type "${grantType}" is not allowed`, + ) + default: + throw new InvalidClientMetadataError( + `Grant type "${grantType}" is not supported`, + ) + } + } + + if (metadata.client_id && metadata.client_id !== clientId) { + throw new InvalidClientMetadataError('client_id does not match') + } + + if (metadata.subject_type && metadata.subject_type !== 'public') { + throw new InvalidClientMetadataError( + 'Only "public" subject_type is supported', + ) + } + + if ( + metadata.userinfo_signed_response_alg && + !this.keyset.signAlgorithms.includes( + metadata.userinfo_signed_response_alg, + ) + ) { + throw new InvalidClientMetadataError( + `Unsupported "userinfo_signed_response_alg" ${metadata.userinfo_signed_response_alg}`, + ) + } + + if ( + metadata.id_token_signed_response_alg && + !this.keyset.signAlgorithms.includes( + metadata.id_token_signed_response_alg, + ) + ) { + throw new InvalidClientMetadataError( + `Unsupported "id_token_signed_response_alg" ${metadata.id_token_signed_response_alg}`, + ) + } + + if (metadata.userinfo_encrypted_response_alg) { + // We only support signature for now. + throw new InvalidClientMetadataError( + 'Encrypted userinfo response is not supported', + ) + } + + for (const endpoint of [ + 'token', + 'introspection', + 'revocation', + ] as const) { + const method = + metadata[`${endpoint}_endpoint_auth_method`] || + metadata[`token_endpoint_auth_method`] + + switch (method || null) { + case 'none': + break + case 'private_key_jwt': + if (!(jwks || metadata.jwks)?.keys.length) { + throw new InvalidClientMetadataError( + `private_key_jwt auth method requires at least one key in jwks`, + ) + } + if (!metadata.token_endpoint_auth_signing_alg) { + throw new InvalidClientMetadataError( + `Missing token_endpoint_auth_signing_alg client metadata`, + ) + } + break + case 'self_signed_tls_client_auth': + case 'tls_client_auth': + // We choose to use the `client_assertion` method instead. + throw new InvalidClientMetadataError( + `${method} is not supported. Use private_key_jwt instead.`, + ) + + case 'client_secret_post': + case 'client_secret_basic': + case 'client_secret_jwt': + // Not supported by the Atproto "lazy client registration" model. + throw new InvalidClientMetadataError(`${method} is not allowed`) + + case null: + throw new InvalidClientMetadataError( + `Missing "${endpoint}_endpoint_auth_method" client metadata`, + ) + default: + throw new InvalidClientMetadataError( + `Unsupported "${endpoint}_endpoint_auth_method" client metadata`, + ) + } + } + + if (metadata.authorization_encrypted_response_enc) { + throw new InvalidClientMetadataError( + 'Encrypted authorization response is not supported', + ) + } + + if ( + metadata.authorization_encrypted_response_enc && + !metadata.authorization_encrypted_response_alg + ) { + throw new InvalidClientMetadataError( + 'authorization_encrypted_response_enc requires authorization_encrypted_response_alg', + ) + } + + // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2 + + // > Web Clients [as defined by "application_type"] using the OAuth + // > Implicit Grant Type MUST only register URLs using the https scheme as + // > redirect_uris; they MUST NOT use localhost as the hostname. Native + // > Clients [as defined by "application_type"] MUST only register + // > redirect_uris using custom URI schemes or loopback URLs using the + // > http scheme; loopback URLs use localhost or the IP loopback literals + // > 127.0.0.1 or [::1] as the hostname. Authorization Servers MAY place + // > additional constraints on Native Clients. Authorization Servers MAY + // > reject Redirection URI values using the http scheme, other than the + // > loopback case for Native Clients. The Authorization Server MUST + // > verify that all the registered redirect_uris conform to these + // > constraints. This prevents sharing a Client ID across different types + // > of Clients. + for (const redirectUri of metadata.redirect_uris) { + const url = parseRedirectUri(redirectUri) + + switch (true) { + // Loopback Interface Redirection + // https://datatracker.ietf.org/doc/html/rfc8252#section-7.3 + case url.hostname === 'localhost': + case url.hostname === '127.0.0.1': + case url.hostname === '[::1]': { + if (metadata.application_type !== 'native') { + throw new InvalidRedirectUriError( + 'Loopback redirect URIs are only allowed for native apps', + ) + } + if (url.port) { + throw new InvalidRedirectUriError( + `Loopback redirect URI ${url} must not contain a port`, + ) + } + if (url.protocol !== 'http:') { + throw new InvalidRedirectUriError( + `Loopback redirect URI ${url} must use HTTP`, + ) + } + continue + } + + // Claimed "https" Scheme URI Redirection + // https://datatracker.ietf.org/doc/html/rfc8252#section-7.2 + case url.protocol === 'https:': + case url.protocol === 'http:': { + continue + } + + // Private-Use URI Scheme (must contain at least one dot) + // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1 + // > When choosing a URI scheme to associate with the app, apps MUST + // > use a URI scheme based on a domain name under their control, + // > expressed in reverse order, as recommended by Section 3.8 of + // > [RFC7595] for private-use URI schemes. + case url.protocol.includes('.'): { + if (metadata.application_type !== 'native') { + throw new InvalidRedirectUriError( + `Private-Use URI Scheme redirect URI are only allowed for native apps`, + ) + } + + if (!metadata.client_uri) { + throw new InvalidRedirectUriError( + `Private-Use URI Scheme redirect URI requires a client_uri metadata field`, + ) + } + + const clientUri = new URL(metadata.client_uri) + + if (clientUri.hostname === 'localhost') { + throw new InvalidRedirectUriError( + `Private-Use URI Scheme are not allowed for loopback clients`, + ) + } + + if (!clientUri.hostname.includes('.')) { + throw new InvalidRedirectUriError( + `Private-Use URI Scheme require a fully qualified domain name (FQDN) client_uri`, + ) + } + + const protocol = `${clientUri.hostname.split('.').reverse().join('.')}:` + if (url.protocol !== protocol) { + throw new InvalidRedirectUriError( + `Private-Use URI Scheme redirect URI must be the client_uri domain name, in reverse order (${protocol})`, + ) + } + + // > Following the requirements of Section 3.2 of [RFC3986], as + // > there is no naming authority for private-use URI scheme + // > redirects, only a single slash ("/") appears after the scheme + // > component. + if ( + url.href.startsWith(`${url.protocol}//`) || + url.username || + url.password || + url.hostname || + url.port + ) { + throw new InvalidRedirectUriError( + `Private-Use URI Scheme must be in the form ${url.protocol}/`, + ) + } + continue + } + + default: + throw new InvalidRedirectUriError( + `Invalid redirect URI scheme "${url.protocol}"`, + ) + } + } + + await this.hooks.onClientData?.(clientId, { metadata, jwks }) + + return { metadata, jwks } + } catch (err) { + if (err instanceof OAuthError) throw err + if ((err as any)?.code === 'DEPTH_ZERO_SELF_SIGNED_CERT') { + throw new InvalidClientMetadataError('Self-signed certificate', err) + } + throw new InvalidClientMetadataError('Unable to obtain metadata', err) + } + } + + /** + * + * @see {@link https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2 OIDC Client Registration} + */ + async getClient(clientId: string) { + const { metadata, jwks } = await this.findClient(clientId) + return new Client(clientId, metadata, jwks) + } +} diff --git a/packages/oauth/oauth-provider/src/client/client-store-uri.ts b/packages/oauth/oauth-provider/src/client/client-store-uri.ts new file mode 100644 index 00000000000..8c3573a5f97 --- /dev/null +++ b/packages/oauth/oauth-provider/src/client/client-store-uri.ts @@ -0,0 +1,416 @@ +import { + Fetch, + fetchFailureHandler, + fetchJsonProcessor, + fetchOkProcessor, +} from '@atproto-labs/fetch' +import { safeFetchWrap } from '@atproto-labs/fetch-node' +import { pipe } from '@atproto-labs/pipe' +import { CachedGetter, SimpleStore } from '@atproto-labs/simple-store' +import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory' +import { Jwks, jwksSchema } from '@atproto/jwk' +import { + Awaitable, + ClientData, + ClientStore, + InvalidClientMetadataError, + InvalidRedirectUriError, + OAuthClientId, + OAuthClientMetadata, + oauthClientMetadataSchema, + parseRedirectUri, +} from '@atproto/oauth-provider' + +import { isInternetHost, isLoopbackHost } from '../lib/util/hostname.js' +import { isSubUrl } from '../lib/util/path.js' +import { buildWellknownUrl } from '../lib/util/well-known.js' + +export type LoopbackMetadataGetter = ( + url: URL, +) => Awaitable> +export type ClientMetadataValidator = ( + clientId: OAuthClientId, + clientUrl: URL, + metadata: OAuthClientMetadata, +) => Awaitable + +export type ClientStoreUriConfig = { + /** + * A custom fetch function that can be used to fetch the client metadata from + * the internet. By default, the fetch function is a safeFetchWrap() function + * that protects against SSRF attacks, large responses & known bad domains. If + * you want to disable all protections, you can provide `globalThis.fetch` as + * fetch function. + */ + safeFetch?: Fetch + + /** + * In order to speed up the client fetching process, you can provide a cache + * to store HTTP responses. + * + * @note the cached entries should automatically expire after a certain time (typically 10 minutes) + */ + clientMetadataCache?: SimpleStore + + /** + * In order to enable loopback clients, you can provide a function that + * returns the client metadata for a given loopback URL. This is useful for + * development and testing purposes. This function is not called for internet + * clients. + * + * @default is as specified by ATPROTO + */ + loopbackMetadata?: null | false | LoopbackMetadataGetter + + /** + * A custom function to validate the client metadata. This is useful for + * enforcing custom rules on the client metadata. This function is called for + * both loopback and internet clients. + * + * @default rules defined by the ATPROTO spec in addition to the OAuth spec + * rules already enforced by {@link ClientStoreUri.validateMetadata} and + * {@link ClientManager}. + */ + validateMetadata?: null | false | ClientMetadataValidator +} + +/** + * This class is responsible for fetching client data based on it's ID. Since + * clients are not pre-registered, we need to fetch their data from the network. + */ +export class ClientStoreUri implements ClientStore { + #jsonFetch: CachedGetter + + protected readonly loopbackMetadata?: LoopbackMetadataGetter + protected readonly validateMetadataCustom?: ClientMetadataValidator + + constructor({ + safeFetch = safeFetchWrap(), + clientMetadataCache = new SimpleStoreMemory({ + maxSize: 50_000_000, + ttl: 600e3, + }), + loopbackMetadata = ({ href }) => ({ + client_name: 'Loopback client', + client_uri: href, + response_types: ['code', 'code id_token'], + grant_types: ['authorization_code'], + scope: 'openid profile', + redirect_uris: ['127.0.0.1', '[::1]'].map( + (ip) => Object.assign(new URL(href), { hostname: ip }).href, + ) as [string, string], + token_endpoint_auth_method: 'none', + application_type: 'native', + dpop_bound_access_tokens: true, + }), + validateMetadata = (clientId, clientUrl, metadata) => { + // ATPROTO spec requires the use of DPoP (OAuth spec defaults to false) + if (metadata.dpop_bound_access_tokens !== true) { + throw new InvalidClientMetadataError( + '"dpop_bound_access_tokens" must be true', + ) + } + + // ATPROTO spec requires the use of PKCE + if ( + metadata.response_types.some((rt) => rt.split(' ').includes('token')) + ) { + throw new InvalidClientMetadataError( + '"token" response type is not compatible with PKCE (use "code" instead)', + ) + } + + for (const redirectUri of metadata.redirect_uris) { + const uri = parseRedirectUri(redirectUri) + + switch (true) { + case uri.protocol === 'http:': + // Only loopback redirect URIs are allowed to use HTTP + switch (uri.hostname) { + // ATPROTO spec requires that the IP is used in case of loopback redirect URIs + case '127.0.0.1': + case '[::1]': + continue + + // ATPROTO spec forbids use of localhost as redirect URI hostname + case 'localhost': + throw new InvalidRedirectUriError( + `Loopback redirect URI ${uri} is not allowed (use explicit IPs instead)`, + ) + } + + // ATPROTO spec forbids http redirects (except for loopback, covered before) + throw new InvalidRedirectUriError( + `Redirect URI ${uri} must use HTTPS`, + ) + + // ATPROTO spec requires that the redirect URI is a sub-url of the client URL + case uri.protocol === 'https:': + if (!isSubUrl(clientUrl, uri)) { + throw new InvalidRedirectUriError( + `Redirect URI ${uri} must be a sub-url of ${clientUrl}`, + ) + } + continue + + // Custom URI schemes are allowed by ATPROTO, following the rules + // defined in the spec & current best practices. These are already + // enforced by ClientManager & ClientStoreUri + default: + continue + } + } + }, + }: ClientStoreUriConfig) { + this.loopbackMetadata = loopbackMetadata || undefined + this.validateMetadataCustom = validateMetadata || undefined + + const fetchSuccessHandler = pipe( + fetchOkProcessor(), + fetchJsonProcessor('application/json', false), + (r: { json: unknown }) => r.json, + ) + + this.#jsonFetch = new CachedGetter(async (url, options) => { + const headers = new Headers([['accept', 'application/json']]) + if (options?.noCache) headers.set('cache-control', 'no-cache') + const request = new Request(url, { + headers, + signal: options?.signal, + redirect: 'error', + }) + return safeFetch(request).then(fetchSuccessHandler, fetchFailureHandler) + }, clientMetadataCache) + } + + public async findClient(clientId: OAuthClientId): Promise { + const clientUrl = await this.buildClientUrl(clientId) + + if (isLoopbackHost(clientUrl.hostname)) { + // It is not possible to fetch the client metadata for loopback URLs + // because they are not accessible from the outside. We support this as a + // special case by generating a client metadata object ourselves. + return this.loopbackClient(clientId, clientUrl) + } else if (isInternetHost(clientUrl.hostname)) { + return this.internetClient(clientId, clientUrl) + } else { + throw new InvalidClientMetadataError( + 'Client ID hostname must be a valid domain', + ) + } + } + + protected async buildClientUrl(clientId: OAuthClientId): Promise { + const url = (() => { + try { + return new URL(clientId) + } catch (err) { + throw new InvalidClientMetadataError('ClientID must be a URI', err) + } + })() + + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + throw new InvalidClientMetadataError( + `ClientID must be an http or https URI`, + ) + } + + if (url.href !== clientId) { + throw new InvalidClientMetadataError(`ClientID must be a normalized URI`) + } + + if (url.username || url.password) { + throw new TypeError('ClientID URI must not contain credentials') + } + + if (url.search || url.hash) { + throw new TypeError('ClientID URI must not contain a query or fragment') + } + + if (url.pathname.includes('//')) { + throw new InvalidClientMetadataError( + `ClientID URI must not contain any double slashes in its path`, + ) + } + + return url + } + + protected async loopbackClient( + clientId: OAuthClientId, + clientUrl: URL, + ): Promise { + if (!this.loopbackMetadata) { + throw new InvalidClientMetadataError('Loopback clients are not allowed') + } + + if (clientUrl.protocol !== 'http:') { + throw new InvalidClientMetadataError( + 'Loopback ClientID URI must use the "http:" protocol', + ) + } + + if (clientUrl.hostname !== 'localhost') { + throw new InvalidClientMetadataError( + 'Loopback ClientID URI must use the "localhost" hostname', + ) + } + + if (clientUrl.port) { + throw new InvalidClientMetadataError( + 'Loopback ClientID URI must not use a custom port number', + ) + } + + const metadata = oauthClientMetadataSchema.parse( + await this.loopbackMetadata(clientUrl), + ) + + await this.validateMetadata(clientId, clientUrl, metadata) + + return { metadata, jwks: undefined } + } + + protected async internetClient( + clientId: OAuthClientId, + clientUrl: URL, + ): Promise { + const metadataEndpoint = await this.getMetadataEndpoint(clientId, clientUrl) + const metadata = await this.fetchMetadata(metadataEndpoint) + await this.validateMetadata(clientId, clientUrl, metadata) + + return { + metadata, + jwks: metadata.jwks_uri + ? await this.fetchJwks(new URL(metadata.jwks_uri)) + : undefined, + } + } + + protected async getMetadataEndpoint( + clientId: OAuthClientId, + clientUrl: URL, + ): Promise { + return buildWellknownUrl(clientUrl, `oauth-client-metadata`) + } + + protected async fetchMetadata( + metadataEndpoint: URL, + ): Promise { + const json = await this.#jsonFetch.get(metadataEndpoint.href) + return oauthClientMetadataSchema.parse(json) + } + + protected async fetchJwks(jwksUri: URL): Promise { + const json = this.#jsonFetch.get(jwksUri.href) + return jwksSchema.parse(json) + } + + /** + * Here we check that the metadata returned by the store is compatible with + * the Atproto OAuth spec. OAuth compliance & validity will be enforced by the + * ClientManager class in the oauth-provider package. + */ + protected async validateMetadata( + clientId: OAuthClientId, + clientUrl: URL, + metadata: OAuthClientMetadata, + ): Promise { + await this.validateMetadataClientId(clientId, clientUrl, metadata) + await this.validateMetadataClientUri(clientId, clientUrl, metadata) + await this.validateMetadataRedirectUris(clientId, clientUrl, metadata) + await this.validateMetadataCustom?.(clientId, clientUrl, metadata) + } + + protected async validateMetadataClientId( + clientId: OAuthClientId, + clientUrl: URL, + metadata: OAuthClientMetadata, + ): Promise { + if (metadata.client_id && metadata.client_id !== clientId) { + throw new InvalidClientMetadataError('client_id must match the client ID') + } + } + + protected async validateMetadataClientUri( + clientId: OAuthClientId, + clientUrl: URL, + metadata: OAuthClientMetadata, + ): Promise { + if (metadata.client_uri && metadata.client_uri !== clientUrl.href) { + throw new InvalidClientMetadataError( + 'client_uri must match the client URI', + ) + } + } + + protected async validateMetadataRedirectUris( + clientId: OAuthClientId, + clientUrl: URL, + metadata: OAuthClientMetadata, + ): Promise { + for (const redirectUri of metadata.redirect_uris) { + const uri = parseRedirectUri(redirectUri) + + switch (true) { + case uri.hostname === 'localhost': + // https://datatracker.ietf.org/doc/html/rfc8252#section-8.3 + // + // > While redirect URIs using localhost (i.e., + // > "http://localhost:{port}/{path}") function similarly to loopback + // > IP redirects described in Section 7.3, the use of localhost is + // > NOT RECOMMENDED. Specifying a redirect URI with the loopback IP + // > literal rather than localhost avoids inadvertently listening on + // > network interfaces other than the loopback interface. It is also + // > less susceptible to client-side firewalls and misconfigured host + // > name resolution on the user's device. + throw new InvalidRedirectUriError( + `Loopback redirect URI ${uri} is not allowed (use explicit IPs instead)`, + ) + + // Loopback redirects + case uri.hostname === '127.0.0.1': + case uri.hostname === '[::1]': + // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2 + // + // > Native Clients MUST only register redirect_uris using custom URI + // > schemes or loopback URLs using the http scheme; loopback URLs use + // > localhost or the IP loopback literals 127.0.0.1 or [::1] as the + // > hostname. + if (metadata.application_type !== 'native') { + throw new InvalidRedirectUriError( + `Loopback redirect URIs are not allowed for non-native clients`, + ) + } + if (uri.protocol !== 'http:') { + throw new InvalidRedirectUriError( + `Loopback redirect URIs must use the "http:" protocol`, + ) + } + continue + + case uri.protocol === 'http:': + case uri.protocol === 'https:': + // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2 + // + // > Native Clients MUST only register redirect_uris using custom URI + // > schemes or loopback URLs using the http scheme; loopback URLs use + // > localhost or the IP loopback literals 127.0.0.1 or [::1] as the + // > hostname. + // + // "http:" case is already handled by the "Loopback redirects" case + // before. + // + if (metadata.application_type === 'native') { + throw new InvalidRedirectUriError( + `Native clients must use loopback redirect URIs or custom URI schemes (got ${uri})`, + ) + } + continue + + default: + continue + } + } + } +} diff --git a/packages/oauth/oauth-provider/src/client/client-store.ts b/packages/oauth/oauth-provider/src/client/client-store.ts new file mode 100644 index 00000000000..64b703fea8d --- /dev/null +++ b/packages/oauth/oauth-provider/src/client/client-store.ts @@ -0,0 +1,36 @@ +import { OAuthClientId, OAuthClientMetadata } from '@atproto/oauth-types' + +import { Awaitable } from '../lib/util/type.js' +import { ClientData } from './client-data.js' + +// Export all types needed to implement the ClientStore interface +export type { Awaitable, ClientData, OAuthClientId, OAuthClientMetadata } + +export interface ClientStore { + findClient(clientId: OAuthClientId): Awaitable +} + +export function isClientStore( + implementation: Record & Partial, +): implementation is Record & ClientStore { + return typeof implementation.findClient === 'function' +} + +export function ifClientStore( + implementation?: Record & Partial, +): ClientStore | undefined { + if (implementation && isClientStore(implementation)) { + return implementation + } + + return undefined +} + +export function asClientStore( + implementation?: Record & Partial, +): ClientStore { + const store = ifClientStore(implementation) + if (store) return store + + throw new Error('Invalid ClientStore implementation') +} diff --git a/packages/oauth/oauth-provider/src/client/client-utils.ts b/packages/oauth/oauth-provider/src/client/client-utils.ts new file mode 100644 index 00000000000..f9650c3b665 --- /dev/null +++ b/packages/oauth/oauth-provider/src/client/client-utils.ts @@ -0,0 +1,9 @@ +import { InvalidRedirectUriError } from '../errors/invalid-redirect-uri-error.js' + +export function parseRedirectUri(redirectUri: string): URL { + try { + return new URL(redirectUri) + } catch (err) { + throw new InvalidRedirectUriError('Invalid redirect URI', err) + } +} diff --git a/packages/oauth/oauth-provider/src/client/client.ts b/packages/oauth/oauth-provider/src/client/client.ts new file mode 100644 index 00000000000..b4a9722b82d --- /dev/null +++ b/packages/oauth/oauth-provider/src/client/client.ts @@ -0,0 +1,221 @@ +import { Jwks } from '@atproto/jwk' +import { + CLIENT_ASSERTION_TYPE_JWT_BEARER, + OAuthClientId, + OAuthClientIdentification, + OAuthClientMetadata, + OAuthEndpointName, +} from '@atproto/oauth-types' +import { + JWTPayload, + JWTVerifyGetKey, + JWTVerifyOptions, + JWTVerifyResult, + KeyLike, + ResolvedKey, + UnsecuredJWT, + UnsecuredResult, + createLocalJWKSet, + createRemoteJWKSet, + jwtVerify, +} from 'jose' +import { JOSEError } from 'jose/errors' + +import { CLIENT_ASSERTION_MAX_AGE, JAR_MAX_AGE } from '../constants.js' +import { InvalidClientError } from '../errors/invalid-client-error.js' +import { InvalidClientMetadataError } from '../errors/invalid-client-metadata-error.js' +import { InvalidRequestError } from '../errors/invalid-request-error.js' +import { ClientAuth, authJwkThumbprint } from './client-auth.js' + +export class Client { + /** + * @see {@link https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#token-endpoint-auth-method} + */ + static readonly AUTH_METHODS_SUPPORTED = ['none', 'private_key_jwt'] as const + + private readonly keyGetter: JWTVerifyGetKey + + constructor( + public readonly id: OAuthClientId, + public readonly metadata: OAuthClientMetadata, + jwks: undefined | Jwks = metadata.jwks, + ) { + // If the remote JWKS content is provided, we don't need to fetch it again. + this.keyGetter = + jwks || !metadata.jwks_uri + ? // @ts-expect-error https://github.com/panva/jose/issues/634 + createLocalJWKSet(jwks || { keys: [] }) + : createRemoteJWKSet(new URL(metadata.jwks_uri), {}) + } + + async decodeRequestObject(jar: string) { + switch (this.metadata.request_object_signing_alg) { + case 'none': + return this.jwtVerifyUnsecured(jar, { + maxTokenAge: JAR_MAX_AGE / 1000, + }) + case undefined: + // https://openid.net/specs/openid-connect-registration-1_0.html#rfc.section.2 + // > The default, if omitted, is that any algorithm supported by the OP + // > and the RP MAY be used. + return this.jwtVerify(jar, { + maxTokenAge: JAR_MAX_AGE / 1000, + }) + default: + return this.jwtVerify(jar, { + maxTokenAge: JAR_MAX_AGE / 1000, + algorithms: [this.metadata.request_object_signing_alg], + }) + } + } + + async jwtVerifyUnsecured( + token: string, + options?: Omit, + ): Promise> { + return UnsecuredJWT.decode(token, { + ...options, + issuer: this.id, + }) + } + + async jwtVerify( + token: string, + options?: Omit, + ): Promise & ResolvedKey> { + return jwtVerify(token, this.keyGetter, { + ...options, + issuer: this.id, + }) + } + + getAuthMethod(endpoint: OAuthEndpointName) { + return ( + this.metadata[`${endpoint}_endpoint_auth_method`] || + this.metadata[`token_endpoint_auth_method`] + ) + } + + /** + * @see {@link https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1} + * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-bearer-11#section-3} + * @see {@link https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#token-endpoint-auth-method} + */ + async verifyCredentials( + input: OAuthClientIdentification, + endpoint: OAuthEndpointName, + checks: { + audience: string + }, + ): Promise<{ + clientAuth: ClientAuth + // for replay protection + nonce?: string + }> { + const method = this.getAuthMethod(endpoint) + + if (method === 'none') { + const clientAuth: ClientAuth = { method: 'none' } + return { clientAuth } + } + + if (method === 'private_key_jwt') { + if (!('client_assertion_type' in input) || !input.client_assertion_type) { + throw new InvalidRequestError( + `client_assertion_type required for "${method}"`, + ) + } else if (!input.client_assertion) { + throw new InvalidRequestError( + `client_assertion required for "${method}"`, + ) + } + + if (input.client_assertion_type === CLIENT_ASSERTION_TYPE_JWT_BEARER) { + const result = await this.jwtVerify<{ + jti: string + }>(input.client_assertion, { + audience: checks.audience, + subject: this.id, + maxTokenAge: CLIENT_ASSERTION_MAX_AGE / 1000, + }).catch((err) => { + if (err instanceof JOSEError) { + const msg = `Validation of "client_assertion" failed: ${err.message}` + throw new InvalidClientError(msg, err) + } + + throw err + }) + + if (!result.protectedHeader.kid) { + throw new InvalidClientError(`"kid" required in client_assertion`) + } + + if (!result.payload.jti) { + throw new InvalidClientError(`"jti" required in client_assertion`) + } + + const clientAuth: ClientAuth = { + method: CLIENT_ASSERTION_TYPE_JWT_BEARER, + jkt: await authJwkThumbprint(result.key), + alg: result.protectedHeader.alg, + kid: result.protectedHeader.kid, + } + + return { clientAuth, nonce: result.payload.jti } + } + + throw new InvalidClientError( + `Unsupported client_assertion_type "${input.client_assertion_type}"`, + ) + } + + // @ts-expect-error Ensure to keep Client.AUTH_METHODS_SUPPORTED in sync + // with the implementation of this function. + if (Client.AUTH_METHODS_SUPPORTED.includes(method)) { + throw new Error( + `verifyCredentials() should implement all of ${[ + Client.AUTH_METHODS_SUPPORTED, + ]}`, + ) + } + + throw new InvalidClientMetadataError( + `Unsupported ${endpoint}_endpoint_auth_method "${method}"`, + ) + } + + /** + * Ensures that a {@link ClientAuth} generated in the past is still valid wrt + * the current client metadata & jwks. This is used to invalidate tokens when + * the client stops advertising the key that it used to authenticate itself + * during the initial token request. + */ + async validateClientAuth(clientAuth: ClientAuth): Promise { + if (clientAuth.method === 'none') { + return this.getAuthMethod('token') === 'none' + } + + if (clientAuth.method === CLIENT_ASSERTION_TYPE_JWT_BEARER) { + if (this.getAuthMethod('token') !== 'private_key_jwt') { + return false + } + try { + const key = await this.keyGetter( + { + kid: clientAuth.kid, + alg: clientAuth.alg, + }, + { payload: '', signature: '' }, + ) + const jtk = await authJwkThumbprint(key) + + return jtk === clientAuth.jkt + } catch (e) { + return false + } + } + + // @ts-expect-error + throw new Error(`Invalid method "${clientAuth.method}"`) + } +} diff --git a/packages/oauth/oauth-provider/src/constants.ts b/packages/oauth/oauth-provider/src/constants.ts new file mode 100644 index 00000000000..767544c9aac --- /dev/null +++ b/packages/oauth/oauth-provider/src/constants.ts @@ -0,0 +1,59 @@ +// The purpose of the prefix is to provide type safety + +export const DEVICE_ID_PREFIX = 'dev-' +export const DEVICE_ID_BYTES_LENGTH = 16 // 128 bits + +export const SESSION_ID_PREFIX = 'ses-' +export const SESSION_ID_BYTES_LENGTH = 16 // 128 bits - only valid if device id is valid + +export const REFRESH_TOKEN_PREFIX = 'ref-' +export const REFRESH_TOKEN_BYTES_LENGTH = 32 // 256 bits + +export const TOKEN_ID_PREFIX = 'tok-' +export const TOKEN_ID_BYTES_LENGTH = 16 // 128 bits - used as `jti` in JWTs (cannot be forged) + +export const REQUEST_ID_PREFIX = 'req-' +export const REQUEST_ID_BYTES_LENGTH = 16 // 128 bits + +export const CODE_PREFIX = 'cod-' +export const CODE_BYTES_LENGTH = 32 + +const SECOND = 1e3 +const MINUTE = 60 * SECOND +const HOUR = 60 * MINUTE +const DAY = 24 * HOUR +const YEAR = 365.25 * DAY +const MONTH = YEAR / 12 + +/** 7 days */ +export const AUTH_MAX_AGE = 7 * DAY + +/** 60 minutes */ +export const TOKEN_MAX_AGE = 60 * MINUTE + +/** 5 minutes */ +export const AUTHORIZATION_INACTIVITY_TIMEOUT = 5 * MINUTE + +/** 1 months */ +export const AUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT = 1 * MONTH + +/** 2 days */ +export const UNAUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT = 2 * DAY + +/** 1 year */ +export const TOTAL_REFRESH_LIFETIME = 1 * YEAR + +/** 5 minutes */ +export const PAR_EXPIRES_IN = 5 * MINUTE + +/** 1 minute */ +export const JAR_MAX_AGE = 1 * MINUTE + +/** 1 minute */ +export const CLIENT_ASSERTION_MAX_AGE = 1 * MINUTE + +/** 3 minutes */ +export const DPOP_NONCE_MAX_AGE = 3 * MINUTE + +/** 5 seconds */ +export const SESSION_FIXATION_MAX_AGE = 5 * SECOND diff --git a/packages/oauth/oauth-provider/src/device/device-data.ts b/packages/oauth/oauth-provider/src/device/device-data.ts new file mode 100644 index 00000000000..6aa3df1ea82 --- /dev/null +++ b/packages/oauth/oauth-provider/src/device/device-data.ts @@ -0,0 +1,29 @@ +import { z } from 'zod' + +import { SESSION_ID_BYTES_LENGTH, SESSION_ID_PREFIX } from '../constants.js' +import { randomHexId } from '../lib/util/crypto.js' +import { deviceDetailsSchema } from './device-details.js' + +export const sessionIdSchema = z + .string() + .length( + SESSION_ID_PREFIX.length + SESSION_ID_BYTES_LENGTH * 2, // hex encoding + ) + .refine( + (v): v is `${typeof SESSION_ID_PREFIX}${string}` => + v.startsWith(SESSION_ID_PREFIX), + { + message: `Invalid session ID format`, + }, + ) +export type SessionId = z.infer +export const generateSessionId = async (): Promise => { + return `${SESSION_ID_PREFIX}${await randomHexId(SESSION_ID_BYTES_LENGTH)}` +} + +export const deviceDataSchema = deviceDetailsSchema.extend({ + sessionId: sessionIdSchema, + lastSeenAt: z.date(), +}) + +export type DeviceData = z.infer diff --git a/packages/oauth/oauth-provider/src/device/device-details.ts b/packages/oauth/oauth-provider/src/device/device-details.ts new file mode 100644 index 00000000000..8673736146b --- /dev/null +++ b/packages/oauth/oauth-provider/src/device/device-details.ts @@ -0,0 +1,43 @@ +import { IncomingMessage } from 'node:http' + +import { z } from 'zod' + +export const deviceDetailsSchema = z.object({ + userAgent: z.string().nullable(), + ipAddress: z.string(), +}) +export type DeviceDetails = z.infer + +export function extractDeviceDetails( + req: IncomingMessage, + trustProxy: boolean, +): DeviceDetails { + const userAgent = req.headers['user-agent'] || null + const ipAddress = extractIpAddress(req, trustProxy) || null + + if (!ipAddress) { + throw new Error('Could not determine IP address') + } + + return { userAgent, ipAddress } +} + +export function extractIpAddress( + req: IncomingMessage, + trustProxy: boolean, +): string | undefined { + // Express app compatibility + if ('ip' in req && typeof req.ip === 'string') { + return req.ip + } + + if (trustProxy) { + const forwardedFor = req.headers['x-forwarded-for'] + if (typeof forwardedFor === 'string') { + const firstForward = forwardedFor.split(',')[0]!.trim() + if (firstForward) return firstForward + } + } + + return req.socket.remoteAddress +} diff --git a/packages/oauth/oauth-provider/src/device/device-id.ts b/packages/oauth/oauth-provider/src/device/device-id.ts new file mode 100644 index 00000000000..1f2f105c641 --- /dev/null +++ b/packages/oauth/oauth-provider/src/device/device-id.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +import { DEVICE_ID_BYTES_LENGTH, DEVICE_ID_PREFIX } from '../constants.js' +import { randomHexId } from '../lib/util/crypto.js' + +export const deviceIdSchema = z + .string() + .length( + DEVICE_ID_PREFIX.length + DEVICE_ID_BYTES_LENGTH * 2, // hex encoding + ) + .refine( + (v): v is `${typeof DEVICE_ID_PREFIX}${string}` => + v.startsWith(DEVICE_ID_PREFIX), + { + message: `Invalid device ID format`, + }, + ) + +export type DeviceId = z.infer +export const generateDeviceId = async (): Promise => { + return `${DEVICE_ID_PREFIX}${await randomHexId(DEVICE_ID_BYTES_LENGTH)}` +} diff --git a/packages/oauth/oauth-provider/src/device/device-manager.ts b/packages/oauth/oauth-provider/src/device/device-manager.ts new file mode 100644 index 00000000000..3f3ec9030fe --- /dev/null +++ b/packages/oauth/oauth-provider/src/device/device-manager.ts @@ -0,0 +1,290 @@ +import { IncomingMessage, ServerResponse } from 'node:http' + +import { serialize as serializeCookie } from 'cookie' +import type Keygrip from 'keygrip' +import { z } from 'zod' + +import { appendHeader, parseHttpCookies } from '../lib/http/index.js' + +import { SESSION_FIXATION_MAX_AGE } from '../constants.js' +import { + DeviceData, + generateSessionId, + sessionIdSchema, +} from './device-data.js' +import { extractDeviceDetails } from './device-details.js' +import { DeviceId, deviceIdSchema, generateDeviceId } from './device-id.js' +import { DeviceStore } from './device-store.js' + +export const DEFAULT_OPTIONS = { + /** + * Controls whether the IP address is read from the `X-Forwarded-For` header + * (if `true`), or from the `req.socket.remoteAddress` property (if `false`). + * + * @default true // (nowadays, most requests are proxied) + */ + trustProxy: true, + + /** + * Amount of time (in ms) after which session IDs will be rotated + * + * @default 300e3 // (5 minutes) + */ + rotationRate: 5 * 60e3, + + /** + * Cookie options + */ + cookie: { + keys: undefined as undefined | Keygrip, + + /** + * Name of the cookie used to identify the device + * + * @default 'session-id' + */ + device: 'device-id', + + /** + * Name of the cookie used to identify the session + * + * @default 'session-id' + */ + session: 'session-id', + + /** + * Url path for the cookie + * + * @default '/oauth/authorize' + */ + path: '/oauth/authorize', + + /** + * Amount of time (in ms) after which the session cookie will expire. + * If set to `null`, the cookie will be a session cookie (deleted when the + * browser is closed). + * + * @default 10 * 365.2 * 24 * 60 * 60e3 // 10 years (in ms) + */ + age: (10 * 365.2 * 24 * 60 * 60e3), + + /** + * Controls whether the cookie is only sent over HTTPS (if `true`), or also + * over HTTP (if `false`). This should **NOT** be set to `false` in + * production. + */ + secure: true, + + /** + * Controls whether the cookie is sent along with cross-site requests. + * + * @default 'lax' + */ + sameSite: 'lax' as 'lax' | 'strict', + }, +} + +export type DeviceDeviceManagerOptions = typeof DEFAULT_OPTIONS + +const cookieValueSchema = z.tuple([deviceIdSchema, sessionIdSchema]) +type CookieValue = z.infer + +/** + * This class provides an abstraction for keeping track of DEVICE sessions. It + * relies on a {@link DeviceStore} to persist session data and a cookie to + * identify the session. + */ +export class DeviceManager { + constructor( + private readonly store: DeviceStore, + private readonly options: DeviceDeviceManagerOptions = DEFAULT_OPTIONS, + ) {} + + public async load( + req: IncomingMessage, + res: ServerResponse, + ): Promise<{ deviceId: DeviceId }> { + const cookie = await this.getCookie(req) + if (cookie) { + return this.refresh(req, res, cookie.value, cookie.mustRotate) + } else { + return this.create(req, res) + } + } + + private async create( + req: IncomingMessage, + res: ServerResponse, + ): Promise<{ deviceId: DeviceId }> { + const { userAgent, ipAddress } = this.getDeviceDetails(req) + + const [deviceId, sessionId] = await Promise.all([ + generateDeviceId(), + generateSessionId(), + ] as const) + + await this.store.createDevice(deviceId, { + sessionId, + lastSeenAt: new Date(), + userAgent, + ipAddress, + }) + + this.setCookie(res, [deviceId, sessionId]) + + return { deviceId } + } + + private async refresh( + req: IncomingMessage, + res: ServerResponse, + [deviceId, sessionId]: CookieValue, + forceRotate = false, + ): Promise<{ deviceId: DeviceId }> { + const data = await this.store.readDevice(deviceId) + if (!data) return this.create(req, res) + + const lastSeenAt = new Date(data.lastSeenAt) + const age = Date.now() - lastSeenAt.getTime() + + if (sessionId !== data.sessionId) { + if (age <= SESSION_FIXATION_MAX_AGE) { + // The cookie was probably rotated by a concurrent request. Let's + // update the cookie with the new sessionId. + forceRotate = true + } else { + // Something's wrong. Let's create a new session. + await this.store.deleteDevice(deviceId) + return this.create(req, res) + } + } + + const details = this.getDeviceDetails(req) + + if ( + forceRotate || + details.ipAddress !== data.ipAddress || + details.userAgent !== data.userAgent || + age > this.options.rotationRate + ) { + await this.rotate(req, res, deviceId, { + ipAddress: details.ipAddress, + userAgent: details.userAgent || data.userAgent, + }) + } + + return { deviceId } + } + + public async rotate( + req: IncomingMessage, + res: ServerResponse, + deviceId: DeviceId, + data?: Partial>, + ): Promise { + const sessionId = await generateSessionId() + + await this.store.updateDevice(deviceId, { + ...data, + sessionId, + lastSeenAt: new Date(), + }) + + this.setCookie(res, [deviceId, sessionId]) + } + + private async getCookie( + req: IncomingMessage, + ): Promise<{ value: CookieValue; mustRotate: boolean } | null> { + const cookies = parseHttpCookies(req) + if (!cookies) return null + + const device = this.parseCookie( + cookies, + this.options.cookie.device, + deviceIdSchema, + ) + const session = this.parseCookie( + cookies, + this.options.cookie.session, + sessionIdSchema, + ) + + // Silently ignore invalid cookies + if (!device || !session) { + // If the device cookie is valid, let's cleanup the DB + if (device) await this.store.deleteDevice(device.value) + + return null + } + + return { + value: [device.value, session.value], + mustRotate: device.mustRotate || session.mustRotate, + } + } + + private parseCookie( + cookies: Record, + name: string, + schema: z.ZodType | z.ZodEffects, + ): null | { value: T; mustRotate: boolean } { + const result = schema.safeParse(cookies[name], { path: ['cookie', name] }) + if (!result.success) return null + + const value = result.data + + if (this.options.cookie.keys) { + const hash = cookies[`${name}:hash`] + if (!hash) return null + + const idx = this.options.cookie.keys.index(value, hash) + if (idx < 0) return null + + return { value, mustRotate: idx !== 0 } + } + + return { value, mustRotate: false } + } + + private setCookie(res: ServerResponse, cookieValue: null | CookieValue) { + this.writeCookie(res, this.options.cookie.device, cookieValue?.[0]) + this.writeCookie(res, this.options.cookie.session, cookieValue?.[1]) + } + + private writeCookie(res: ServerResponse, name: string, value?: string) { + const cookieOptions = { + maxAge: value + ? this.options.cookie.age == null + ? undefined + : this.options.cookie.age / 1000 + : 0, + httpOnly: true, + path: this.options.cookie.path, + secure: this.options.cookie.secure !== false, + sameSite: this.options.cookie.sameSite === 'lax' ? 'lax' : 'strict', + } as const + + appendHeader( + res, + 'Set-Cookie', + serializeCookie(name, value || '', cookieOptions), + ) + + if (this.options.cookie.keys) { + appendHeader( + res, + 'Set-Cookie', + serializeCookie( + `${name}:hash`, + value ? this.options.cookie.keys.sign(value) : '', + cookieOptions, + ), + ) + } + } + + private getDeviceDetails(req: IncomingMessage) { + return extractDeviceDetails(req, this.options.trustProxy) + } +} diff --git a/packages/oauth/oauth-provider/src/device/device-store.ts b/packages/oauth/oauth-provider/src/device/device-store.ts new file mode 100644 index 00000000000..a844c03a1a4 --- /dev/null +++ b/packages/oauth/oauth-provider/src/device/device-store.ts @@ -0,0 +1,33 @@ +import { Awaitable } from '../lib/util/type.js' +import { DeviceData, SessionId } from './device-data.js' +import { DeviceId } from './device-id.js' + +// Export all types needed to implement the DeviceStore interface +export type { DeviceData, DeviceId, SessionId } + +export interface DeviceStore { + createDevice(deviceId: DeviceId, data: DeviceData): Awaitable + readDevice(deviceId: DeviceId): Awaitable + updateDevice(deviceId: DeviceId, data: Partial): Awaitable + deleteDevice(deviceId: DeviceId): Awaitable +} + +export function isDeviceStore( + implementation: Record & Partial, +): implementation is Record & DeviceStore { + return ( + typeof implementation.createDevice === 'function' && + typeof implementation.readDevice === 'function' && + typeof implementation.updateDevice === 'function' && + typeof implementation.deleteDevice === 'function' + ) +} + +export function asDeviceStore( + implementation?: Record & Partial, +): DeviceStore { + if (!implementation || !isDeviceStore(implementation)) { + throw new Error('Invalid DeviceStore implementation') + } + return implementation +} diff --git a/packages/oauth/oauth-provider/src/dpop/dpop-manager.ts b/packages/oauth/oauth-provider/src/dpop/dpop-manager.ts new file mode 100644 index 00000000000..3c50cda6448 --- /dev/null +++ b/packages/oauth/oauth-provider/src/dpop/dpop-manager.ts @@ -0,0 +1,141 @@ +import { createHash } from 'node:crypto' + +import { EmbeddedJWK, calculateJwkThumbprint, jwtVerify } from 'jose' + +import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js' +import { UseDpopNonceError } from '../errors/use-dpop-nonce-error.js' +import { DpopNonce, DpopNonceInput } from './dpop-nonce.js' +import { DPOP_NONCE_MAX_AGE } from '../constants.js' +import { JOSEError } from 'jose/errors' + +export { DpopNonce, type DpopNonceInput } +export type DpopManagerOptions = { + /** + * Set this to `false` to disable the use of nonces in DPoP proofs. Set this + * to a secret Uint8Array or hex encoded string to use a predictable seed for + * all nonces (typically useful when multiple instances are running). Leave + * undefined to generate a random seed at startup. + */ + dpopSecret?: false | DpopNonceInput + dpopStep?: number +} + +export class DpopManager { + protected readonly dpopNonce?: DpopNonce + + constructor({ dpopSecret, dpopStep }: DpopManagerOptions = {}) { + this.dpopNonce = + dpopSecret === false ? undefined : DpopNonce.from(dpopSecret, dpopStep) + } + + nextNonce(): string | undefined { + return this.dpopNonce?.next() + } + + /** + * @see {@link https://datatracker.ietf.org/doc/html/rfc9449#section-4.3} + */ + async checkProof( + proof: unknown, + htm: string, // HTTP Method + htu: string | URL, // HTTP URL + accessToken?: string, // Access Token + ) { + if (Array.isArray(proof) && proof.length === 1) { + proof = proof[0] + } + + if (!proof || typeof proof !== 'string') { + throw new InvalidDpopProofError('DPoP proof required') + } + + const { protectedHeader, payload } = await jwtVerify<{ + iat: number + exp: number + jti: string + }>(proof, EmbeddedJWK, { + typ: 'dpop+jwt', + maxTokenAge: 10, + clockTolerance: DPOP_NONCE_MAX_AGE / 1e3, + requiredClaims: ['iat', 'exp', 'jti'], + }).catch((err) => { + const message = + err instanceof JOSEError + ? `Invalid DPoP proof (${err.message})` + : 'Invalid DPoP proof' + throw new InvalidDpopProofError(message, err) + }) + + if (!payload.jti || typeof payload.jti !== 'string') { + throw new InvalidDpopProofError('Invalid or missing jti property') + } + + if (payload.exp - payload.iat > DPOP_NONCE_MAX_AGE / 3 / 1e3) { + throw new InvalidDpopProofError('DPoP proof validity too long') + } + + // Note rfc9110#section-9.1 states that the method name is case-sensitive + if (!htm || htm !== payload['htm']) { + throw new InvalidDpopProofError('DPoP htm mismatch') + } + + if ( + payload['nonce'] !== undefined && + typeof payload['nonce'] !== 'string' + ) { + throw new InvalidDpopProofError('DPoP nonce must be a string') + } + + if (!payload['nonce'] && this.dpopNonce) { + throw new UseDpopNonceError() + } + + if (payload['nonce'] && !this.dpopNonce?.check(payload['nonce'])) { + throw new UseDpopNonceError() + } + + const htuNorm = normalizeHtu(htu) + if (!htuNorm || htuNorm !== normalizeHtu(payload['htu'])) { + throw new InvalidDpopProofError('DPoP htu mismatch') + } + + if (accessToken) { + const athBuffer = createHash('sha256').update(accessToken).digest() + if (payload['ath'] !== athBuffer.toString('base64url')) { + throw new InvalidDpopProofError('DPoP ath mismatch') + } + } else if (payload['ath']) { + throw new InvalidDpopProofError('DPoP ath not allowed') + } + + return { + protectedHeader, + payload, + jkt: await calculateJwkThumbprint(protectedHeader['jwk']!, 'sha256'), // EmbeddedJWK + } + } +} + +/** + * @note + * > The htu claim matches the HTTP URI value for the HTTP request in which the + * > JWT was received, ignoring any query and fragment parts. + * + * > To reduce the likelihood of false negatives, servers SHOULD employ + * > syntax-based normalization (Section 6.2.2 of [RFC3986]) and scheme-based + * > normalization (Section 6.2.3 of [RFC3986]) before comparing the htu claim. + * @see {@link https://datatracker.ietf.org/doc/html/rfc9449#section-4.3 | RFC9449 section 4.3. Checking DPoP Proofs} + */ +function normalizeHtu(htu: unknown): string | null { + // Optimization + if (!htu) return null + + try { + const url = new URL(String(htu)) + url.hash = '' + url.search = '' + return url.href + } catch { + return null + } +} diff --git a/packages/oauth/oauth-provider/src/dpop/dpop-nonce.ts b/packages/oauth/oauth-provider/src/dpop/dpop-nonce.ts new file mode 100644 index 00000000000..4a74e8af294 --- /dev/null +++ b/packages/oauth/oauth-provider/src/dpop/dpop-nonce.ts @@ -0,0 +1,104 @@ +import { createHmac, randomBytes } from 'node:crypto' + +import { DPOP_NONCE_MAX_AGE } from '../constants.js' + +function numTo64bits(num: number) { + const arr = new Uint8Array(8) + arr[7] = (num = num | 0) & 0xff + arr[6] = (num >>= 8) & 0xff + arr[5] = (num >>= 8) & 0xff + arr[4] = (num >>= 8) & 0xff + arr[3] = (num >>= 8) & 0xff + arr[2] = (num >>= 8) & 0xff + arr[1] = (num >>= 8) & 0xff + arr[0] = (num >>= 8) & 0xff + return arr +} + +export type DpopNonceInput = string | Uint8Array | DpopNonce + +export class DpopNonce { + #secret: Uint8Array + #counter: number + + #prev: string + #now: string + #next: string + + constructor( + protected readonly secret: Uint8Array, + protected readonly step: number, + ) { + if (secret.length !== 32) throw new TypeError('Expected 32 bytes') + if (this.step < 0 || this.step > DPOP_NONCE_MAX_AGE / 3) { + throw new TypeError('Invalid step') + } + + this.#secret = Uint8Array.from(secret) + this.#counter = (Date.now() / step) | 0 + + this.#prev = this.compute(this.#counter - 1) + this.#now = this.compute(this.#counter) + this.#next = this.compute(this.#counter + 1) + } + + protected rotate() { + const counter = (Date.now() / this.step) | 0 + switch (counter - this.#counter) { + case 0: + // counter === this.#counter => nothing to do + return + case 1: + // Optimization: avoid recomputing #prev & #now + this.#prev = this.#now + this.#now = this.#next + this.#next = this.compute(counter + 1) + break + case 2: + // Optimization: avoid recomputing #prev + this.#prev = this.#next + this.#now = this.compute(counter) + this.#next = this.compute(counter + 1) + break + default: + // All nonces are outdated, so we recompute all of them + this.#prev = this.compute(counter - 1) + this.#now = this.compute(counter) + this.#next = this.compute(counter + 1) + break + } + this.#counter = counter + } + + protected compute(counter: number) { + return createHmac('sha256', this.#secret) + .update(numTo64bits(counter)) + .digest() + .toString('base64url') + } + + public next() { + this.rotate() + return this.#next + } + + public check(nonce: string) { + return this.#next === nonce || this.#now === nonce || this.#prev === nonce + } + + static from( + input: DpopNonceInput = randomBytes(32), + step = DPOP_NONCE_MAX_AGE / 3, + ): DpopNonce { + if (input instanceof DpopNonce) { + return input + } + if (input instanceof Uint8Array) { + return new DpopNonce(input, step) + } + if (typeof input === 'string') { + return new DpopNonce(Buffer.from(input, 'hex'), step) + } + return new DpopNonce(input, step) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/access-denied-error.ts b/packages/oauth/oauth-provider/src/errors/access-denied-error.ts new file mode 100644 index 00000000000..19fd3dadac8 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/access-denied-error.ts @@ -0,0 +1,26 @@ +import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types' +import { buildErrorPayload } from '../output/build-error-payload.js' +import { OAuthError } from './oauth-error.js' + +export class AccessDeniedError extends OAuthError { + constructor( + public readonly parameters: OAuthAuthenticationRequestParameters, + error_description: string, + error = 'access_denied', + cause?: unknown, + ) { + super(error, error_description, 400, cause) + } + + static from( + parameters: OAuthAuthenticationRequestParameters, + cause?: unknown, + ) { + if (cause && cause instanceof AccessDeniedError) { + return cause + } + + const { error, error_description } = buildErrorPayload(cause) + return new AccessDeniedError(parameters, error_description, error, cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/account-selection-required-error.ts b/packages/oauth/oauth-provider/src/errors/account-selection-required-error.ts new file mode 100644 index 00000000000..f7bafb1d102 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/account-selection-required-error.ts @@ -0,0 +1,12 @@ +import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types' +import { AccessDeniedError } from './access-denied-error.js' + +export class AccountSelectionRequiredError extends AccessDeniedError { + constructor( + parameters: OAuthAuthenticationRequestParameters, + error_description = 'Account selection required', + cause?: unknown, + ) { + super(parameters, error_description, 'account_selection_required', cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/consent-required-error.ts b/packages/oauth/oauth-provider/src/errors/consent-required-error.ts new file mode 100644 index 00000000000..8883e863b06 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/consent-required-error.ts @@ -0,0 +1,12 @@ +import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types' +import { AccessDeniedError } from './access-denied-error.js' + +export class ConsentRequiredError extends AccessDeniedError { + constructor( + parameters: OAuthAuthenticationRequestParameters, + error_description = 'User consent required', + cause?: unknown, + ) { + super(parameters, error_description, 'consent_required', cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-authorization-details-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-authorization-details-error.ts new file mode 100644 index 00000000000..89d9b0732dc --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-authorization-details-error.ts @@ -0,0 +1,22 @@ +import { OAuthError } from './oauth-error.js' + +/** + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc9396#section-14.6 | RFC 9396 - OAuth Dynamic Client Registration Metadata Registration Error} + * + * The AS MUST refuse to process any unknown authorization details type or + * authorization details not conforming to the respective type definition. The + * AS MUST abort processing and respond with an error + * invalid_authorization_details to the client if any of the following are true + * of the objects in the authorization_details structure: + * - contains an unknown authorization details type value, + * - is an object of known type but containing unknown fields, + * - contains fields of the wrong type for the authorization details type, + * - contains fields with invalid values for the authorization details type, or + * - is missing required fields for the authorization details type. + */ +export class InvalidAuthorizationDetailsError extends OAuthError { + constructor(error_description: string, cause?: unknown) { + super('invalid_authorization_details', error_description, 400, cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-client-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-client-error.ts new file mode 100644 index 00000000000..328d67b2175 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-client-error.ts @@ -0,0 +1,20 @@ +import { OAuthError } from './oauth-error.js' + +/** + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | RFC6749 - Issuing an Access Token } + * + * Client authentication failed (e.g., unknown client, no client authentication + * included, or unsupported authentication method). The authorization server MAY + * return an HTTP 401 (Unauthorized) status code to indicate which HTTP + * authentication schemes are supported. If the client attempted to + * authenticate via the "Authorization" request header field, the authorization + * server MUST respond with an HTTP 401 (Unauthorized) status code and include + * the "WWW-Authenticate" response header field matching the authentication + * scheme used by the client. + */ +export class InvalidClientError extends OAuthError { + constructor(error_description: string, cause?: unknown) { + super('invalid_client', error_description, 400, cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-client-metadata-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-client-metadata-error.ts new file mode 100644 index 00000000000..0d9d49447bf --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-client-metadata-error.ts @@ -0,0 +1,14 @@ +import { OAuthError } from './oauth-error.js' + +/** + * @see {@link https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.2 | RFC7591 - Client Registration Error Response} + * + * The value of one of the client metadata fields is invalid and the server has + * rejected this request. Note that an authorization server MAY choose to + * substitute a valid value for any requested parameter of a client's metadata. + */ +export class InvalidClientMetadataError extends OAuthError { + constructor(error_description: string, cause?: unknown) { + super('invalid_client_metadata', error_description, 400, cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-dpop-key-binding.ts b/packages/oauth/oauth-provider/src/errors/invalid-dpop-key-binding.ts new file mode 100644 index 00000000000..38cd8bac489 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-dpop-key-binding.ts @@ -0,0 +1,21 @@ +import { WWWAuthenticateError } from './www-authenticate-error.js' + +/** + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3.1 | RFC6750 - The WWW-Authenticate Response Header Field} + * + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc9449#name-the-dpop-authentication-sch | RFC9449 - The DPoP Authentication Scheme} + */ +export class InvalidDpopKeyBindingError extends WWWAuthenticateError { + constructor(cause?: unknown) { + const error = 'invalid_token' + const error_description = 'Invalid DPoP key binding' + super( + error, + error_description, + { DPoP: { error, error_description } }, + cause, + ) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-dpop-proof-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-dpop-proof-error.ts new file mode 100644 index 00000000000..d8826fd6a90 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-dpop-proof-error.ts @@ -0,0 +1,13 @@ +import { WWWAuthenticateError } from './www-authenticate-error.js' + +export class InvalidDpopProofError extends WWWAuthenticateError { + constructor(error_description: string, cause?: unknown) { + const error = 'invalid_dpop_proof' + super( + error, + error_description, + { DPoP: { error, error_description } }, + cause, + ) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-grant-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-grant-error.ts new file mode 100644 index 00000000000..a4e3838db38 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-grant-error.ts @@ -0,0 +1,16 @@ +import { OAuthError } from './oauth-error.js' + +/** + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | RFC6749 - Issuing an Access Token } + * + * The provided authorization grant (e.g., authorization code, resource owner + * credentials) or refresh token is invalid, expired, revoked, does not match + * the redirection URI used in the authorization request, or was issued to + * another client. + */ +export class InvalidGrantError extends OAuthError { + constructor(error_description: string, cause?: unknown) { + super('invalid_grant', error_description, 400, cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-parameters-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-parameters-error.ts new file mode 100644 index 00000000000..564e109eb06 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-parameters-error.ts @@ -0,0 +1,12 @@ +import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types' +import { AccessDeniedError } from './access-denied-error.js' + +export class InvalidParametersError extends AccessDeniedError { + constructor( + parameters: OAuthAuthenticationRequestParameters, + error_description: string, + cause?: unknown, + ) { + super(parameters, error_description, 'invalid_request', cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-redirect-uri-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-redirect-uri-error.ts new file mode 100644 index 00000000000..df390f01ccd --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-redirect-uri-error.ts @@ -0,0 +1,12 @@ +import { OAuthError } from './oauth-error.js' + +/** + * @see {@link https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.2 | RFC7591} + * + * The value of one or more redirection URIs is invalid. + */ +export class InvalidRedirectUriError extends OAuthError { + constructor(error_description: string, cause?: unknown) { + super('invalid_redirect_uri', error_description, 400, cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-request-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-request-error.ts new file mode 100644 index 00000000000..fd1dabbbf95 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-request-error.ts @@ -0,0 +1,30 @@ +import { OAuthError } from './oauth-error.js' + +/** + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | RFC6749 - Issuing an Access Token } + * + * The request is missing a required parameter, includes an unsupported + * parameter value (other than grant type), repeats a parameter, includes + * multiple credentials, utilizes more than one mechanism for authenticating the + * client, or is otherwise malformed. + * + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1 | RFC6749 - Authorization Code Grant, Authorization Request} + * + * The request is missing a required parameter, includes an invalid parameter + * value, includes a parameter more than once, or is otherwise malformed. + * + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3.1 | RFC6750 - The WWW-Authenticate Response Header Field } + * + * The request is missing a required parameter, includes an unsupported + * parameter or parameter value, repeats the same parameter, uses more than one + * method for including an access token, or is otherwise malformed. The resource + * server SHOULD respond with the HTTP 400 (Bad Request) status code. + */ +export class InvalidRequestError extends OAuthError { + constructor(error_description: string, cause?: unknown) { + super('invalid_request', error_description, 400, cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/invalid-token-error.ts b/packages/oauth/oauth-provider/src/errors/invalid-token-error.ts new file mode 100644 index 00000000000..ab7d24f3902 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/invalid-token-error.ts @@ -0,0 +1,58 @@ +import { JOSEError } from 'jose/errors' +import { ZodError } from 'zod' + +import { OAuthError } from './oauth-error.js' +import { WWWAuthenticateError } from './www-authenticate-error.js' + +/** + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3.1 | RFC6750 - The WWW-Authenticate Response Header Field } + * + * The access token provided is expired, revoked, malformed, or invalid for + * other reasons. The resource SHOULD respond with the HTTP 401 (Unauthorized) + * status code. The client MAY request a new access token and retry the + * protected resource request. + */ +export class InvalidTokenError extends WWWAuthenticateError { + static from( + err: unknown, + tokenType: string, + fallbackMessage?: string, + ): InvalidTokenError { + if (err instanceof InvalidTokenError) { + return err + } + + if (err instanceof OAuthError) { + return new InvalidTokenError(tokenType, err.error_description, err) + } + + if (err instanceof JOSEError) { + return new InvalidTokenError(tokenType, err.message, err) + } + + if (err instanceof ZodError) { + return new InvalidTokenError(tokenType, err.message, err) + } + + return new InvalidTokenError( + tokenType, + fallbackMessage ?? 'Invalid token', + err, + ) + } + + constructor( + readonly tokenType: string, + error_description: string, + cause?: unknown, + ) { + const error = 'invalid_token' + super( + error, + error_description, + { [tokenType]: { error, error_description } }, + cause, + ) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/login-required-error.ts b/packages/oauth/oauth-provider/src/errors/login-required-error.ts new file mode 100644 index 00000000000..4c2ea382efa --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/login-required-error.ts @@ -0,0 +1,12 @@ +import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types' +import { AccessDeniedError } from './access-denied-error.js' + +export class LoginRequiredError extends AccessDeniedError { + constructor( + parameters: OAuthAuthenticationRequestParameters, + error_description = 'Login is required', + cause?: unknown, + ) { + super(parameters, error_description, 'login_required', cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/oauth-error.ts b/packages/oauth/oauth-provider/src/errors/oauth-error.ts new file mode 100644 index 00000000000..9b14de44a5b --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/oauth-error.ts @@ -0,0 +1,28 @@ +export class OAuthError extends Error { + public expose: boolean + + constructor( + public readonly error: string, + public readonly error_description: string, + public readonly status = 400, + cause?: unknown, + ) { + super(error_description, { cause }) + + Error.captureStackTrace?.(this, this.constructor) + + this.name = this.constructor.name + this.expose = status < 500 + } + + get statusCode() { + return this.status + } + + toJSON() { + return { + error: this.error, + error_description: this.error_description, + } as const + } +} diff --git a/packages/oauth/oauth-provider/src/errors/unauthorized-client-error.ts b/packages/oauth/oauth-provider/src/errors/unauthorized-client-error.ts new file mode 100644 index 00000000000..6f28e5870ec --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/unauthorized-client-error.ts @@ -0,0 +1,20 @@ +import { OAuthError } from './oauth-error.js' + +/** + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | RFC6749 - Issuing an Access Token } + * + * The authenticated client is not authorized to use this authorization grant + * type. + * + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1 | RFC6749 - Authorization Code Grant, Authorization Request} + * + * The client is not authorized to request an authorization code using this + * method. + */ +export class UnauthorizedClientError extends OAuthError { + constructor(error_description: string, cause?: unknown) { + super('unauthorized_client', error_description, 400, cause) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/use-dpop-nonce-error.ts b/packages/oauth/oauth-provider/src/errors/use-dpop-nonce-error.ts new file mode 100644 index 00000000000..9c1d95566a7 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/use-dpop-nonce-error.ts @@ -0,0 +1,32 @@ +import { OAuthError } from './oauth-error.js' +import { WWWAuthenticateError } from './www-authenticate-error.js' + +/** + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc9449#section-8 | RFC9449 - Authorization Server-Provided Nonce} + */ +export class UseDpopNonceError extends OAuthError { + constructor( + error_description = 'Authorization server requires nonce in DPoP proof', + cause?: unknown, + ) { + super('use_dpop_nonce', error_description, 400, cause) + } + + /** + * Convert this error into an error meant to be used as "Resource + * Server-Provided Nonce" error. + * + * @see + * {@link https://datatracker.ietf.org/doc/html/rfc9449#name-resource-server-provided-no | RFC9449 - Resource Server-Provided Nonce} + */ + toWwwAuthenticateError(): WWWAuthenticateError { + const { error, error_description } = this + throw new WWWAuthenticateError( + error, + error_description, + { DPoP: { error, error_description } }, + this, + ) + } +} diff --git a/packages/oauth/oauth-provider/src/errors/www-authenticate-error.ts b/packages/oauth/oauth-provider/src/errors/www-authenticate-error.ts new file mode 100644 index 00000000000..c781d6212e2 --- /dev/null +++ b/packages/oauth/oauth-provider/src/errors/www-authenticate-error.ts @@ -0,0 +1,65 @@ +import { VERIFY_ALGOS } from '../lib/util/crypto.js' + +import { OAuthError } from './oauth-error.js' + +export type WWWAuthenticateParams = Record +export type WWWAuthenticate = Record + +export class WWWAuthenticateError extends OAuthError { + public readonly wwwAuthenticate: WWWAuthenticate + + constructor( + error: string, + error_description: string, + wwwAuthenticate: WWWAuthenticate, + cause?: unknown, + ) { + super(error, error_description, 401, cause) + + this.wwwAuthenticate = + wwwAuthenticate['DPoP'] != null + ? { + ...wwwAuthenticate, + DPoP: { algs: VERIFY_ALGOS.join(' '), ...wwwAuthenticate['DPoP'] }, + } + : wwwAuthenticate + } + + get wwwAuthenticateHeader() { + return formatWWWAuthenticateHeader(this.wwwAuthenticate) + } +} + +function formatWWWAuthenticateHeader(wwwAuthenticate: WWWAuthenticate): string { + return Object.entries(wwwAuthenticate) + .filter(isWWWAuthenticateEntry) + .map(wwwAuthenticateEntryToString) + .join(', ') +} + +type WWWAuthenticateEntry = [type: string, params: WWWAuthenticateParams] +function isWWWAuthenticateEntry( + entry: [string, unknown], +): entry is WWWAuthenticateEntry { + const [, value] = entry + return value != null && typeof value === 'object' +} + +function wwwAuthenticateEntryToString([type, params]: WWWAuthenticateEntry) { + const paramsEnc = Object.entries(params) + .filter(isParamEntry) + .map(paramEntryToString) + + return paramsEnc.length ? `${type} ${paramsEnc.join(', ')}` : type +} + +type ParamEntry = [name: string, value: string] + +function isParamEntry(entry: [string, unknown]): entry is ParamEntry { + const [, value] = entry + return typeof value === 'string' && value !== '' && !value.includes('"') +} + +function paramEntryToString([name, value]: ParamEntry): string { + return `${name}="${value}"` +} diff --git a/packages/oauth/oauth-provider/src/index.ts b/packages/oauth/oauth-provider/src/index.ts new file mode 100644 index 00000000000..4cef6d627a1 --- /dev/null +++ b/packages/oauth/oauth-provider/src/index.ts @@ -0,0 +1,11 @@ +// Avoid having to explicitly depend on `@atproto/jwk-jose` when using this module by re-exporting +export * from '@atproto/jwk-jose' + +export * from './constants.js' +export * from './oauth-client.js' +export * from './oauth-dpop.js' +export * from './oauth-errors.js' +export * from './oauth-hooks.js' +export * from './oauth-provider.js' +export * from './oauth-store.js' +export * from './oauth-verifier.js' diff --git a/packages/oauth/oauth-provider/src/lib/html/README.md b/packages/oauth/oauth-provider/src/lib/html/README.md new file mode 100644 index 00000000000..60c8014734f --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/html/README.md @@ -0,0 +1,9 @@ +# Safe HTML generation and concatenation utility + +This library provides a safe way to generate and concatenate HTML strings. + +This code _could_ be used as a standalone library, but the Bluesky dev team does +not want to maintain it as such. As it is currently only used by the +`@bluesky/oauth-provider` package, it is included here. Future development +should aim to keep this library independent of the rest of the +`@bluesky/oauth-provider` package, so that it can be extracted and published. diff --git a/packages/oauth/oauth-provider/src/lib/html/escapers.ts b/packages/oauth/oauth-provider/src/lib/html/escapers.ts new file mode 100644 index 00000000000..3b0578bfcc2 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/html/escapers.ts @@ -0,0 +1,63 @@ +import { Html } from './html.js' +import { NestedArray, stringReplacer } from './util.js' + +export function* javascriptEscaper(code: string) { + // "" can only appear in javascript strings, so we can safely escape + // the "<" without breaking the javascript. + yield* stringReplacer(code, '', '\\u003c/script>') +} + +export function* jsonEscaper(value: unknown) { + // https://redux.js.org/usage/server-rendering#security-considerations + yield* stringReplacer(JSON.stringify(value), '<', '\\u003c') +} + +export function* cssEscaper(css: string) { + yield* stringReplacer(css, '', '\\u003c/style>') +} + +export type HtmlVariable = Html | string | number | null | undefined +export type HtmlValue = NestedArray + +export function* htmlEscaper( + htmlFragments: TemplateStringsArray, + values: readonly HtmlValue[], +): Generator { + for (let i = 0; i < htmlFragments.length; i++) { + yield htmlFragments[i]! + + const value = values[i] + if (value != null) yield* htmlVariableToFragments(value) + } +} + +function* htmlVariableToFragments( + value: HtmlValue, +): Generator { + if (value == null) { + return + } else if (typeof value === 'number') { + yield String(value) + } else if (typeof value === 'string') { + yield encode(value) + } else if (value instanceof Html) { + yield value + } else if (Array.isArray(value)) { + for (const v of value) { + yield* htmlVariableToFragments(v) + } + } +} + +const specialCharRegExp = /[<>"'&]/g +const specialCharMap = new Map([ + ['<', '<'], + ['>', '>'], + ['"', '"'], + ["'", '''], + ['&', '&'], +]) +const specialCharMapGet = (c: string) => specialCharMap.get(c)! +function encode(value: string): string { + return value.replace(specialCharRegExp, specialCharMapGet) +} diff --git a/packages/oauth/oauth-provider/src/lib/html/html.ts b/packages/oauth/oauth-provider/src/lib/html/html.ts new file mode 100644 index 00000000000..f52da567286 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/html/html.ts @@ -0,0 +1,46 @@ +import { isString } from './util' + +const symbol = Symbol('Html.dangerouslyCreate') + +/** + * This class represents trusted HTML that can be safely embedded in a web page, + * or used as fragments to build a larger HTML document. + */ +export class Html { + #fragments: Iterable + + private constructor(fragments: Iterable, guard: symbol) { + if (guard !== symbol) { + // Force developers to use `Html.dangerouslyCreate` to create an Html + // instance, to make it clear that the content needs to be trusted. + throw new TypeError( + 'Use Html.dangerouslyCreate() to create an Html instance', + ) + } + + this.#fragments = fragments + } + + toString(): string { + // Lazily compute & join the fragments when they are used, to avoid + // unnecessary intermediate strings when concatenating multiple Html as + // fragments. + if ( + !Array.isArray(this.#fragments) || + this.#fragments.length > 1 || + !this.#fragments.every(isString) + ) { + // Will call `toString` recursively, as well as generating iterator + // results. + const fragment = Array.from(this.#fragments, String).join('') + this.#fragments = [fragment] // Cache result for future calls + return fragment + } + + return this.#fragments.join('') + } + + static dangerouslyCreate(fragments: Iterable): Html { + return new Html(fragments, symbol) + } +} diff --git a/packages/oauth/oauth-provider/src/lib/html/index.ts b/packages/oauth/oauth-provider/src/lib/html/index.ts new file mode 100644 index 00000000000..f8e2357ddf7 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/html/index.ts @@ -0,0 +1,34 @@ +import { + HtmlValue, + cssEscaper, + htmlEscaper, + javascriptEscaper, + jsonEscaper, +} from './escapers.js' +import { Html } from './html.js' + +export { Html } + +/** + * Escapes code to use as a JavaScript string inside a `` // hash validity requires no space around the content + : html`` +} + +function styleToHtml(style: Html | AssetRef) { + return style instanceof Html + ? // prettier-ignore + html`` // hash validity requires no space around the content + : html`` +} + +export function sendWebPage( + res: ServerResponse, + { status = 200, ...options }: WebPageOptions & { status?: number }, +): void { + // TODO: make these headers configurable (?) + res.setHeader('Cross-Origin-Embedder-Policy', 'credentialless') + res.setHeader('Cross-Origin-Resource-Policy', 'same-origin') + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin') + res.setHeader('Referrer-Policy', 'same-origin') + res.setHeader('X-Frame-Options', 'DENY') + res.setHeader('X-Content-Type-Options', 'nosniff') + res.setHeader('X-XSS-Protection', '0') + res.setHeader('Strict-Transport-Security', 'max-age=63072000') + // TODO: use CSP rule builder (?) + res.setHeader( + 'Content-Security-Policy', + [ + `default-src 'none'`, + `frame-ancestors 'none'`, + `form-action 'none'`, + `base-uri ${options.base?.origin || `'none'`}`, + `script-src 'self' ${ + options.scripts?.map(assetToHash).map(hashToCspRule).join(' ') ?? '' + }`, + `style-src 'self' ${ + options.styles?.map(assetToHash).map(hashToCspRule).join(' ') ?? '' + }`, + `img-src 'self' data: https:`, + `connect-src 'self'`, + `upgrade-insecure-requests`, + ].join('; '), + ) + + const webPage = buildWebPage(options) + + writeHtml(res, webPage.toString(), status) +} + +function assetToHash(asset: Html | AssetRef): string { + return asset instanceof Html + ? createHash('sha256').update(asset.toString()).digest('base64') + : asset.sha256 +} + +function hashToCspRule(hash: string): string { + return `'sha256-${hash}'` +} diff --git a/packages/oauth/oauth-provider/src/lib/redis.ts b/packages/oauth/oauth-provider/src/lib/redis.ts new file mode 100644 index 00000000000..287ad3ad9d6 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/redis.ts @@ -0,0 +1,23 @@ +import { Redis, type RedisOptions } from 'ioredis' + +export type { Redis, RedisOptions } + +export type CreateRedisOptions = Redis | RedisOptions | string + +export function createRedis(options: CreateRedisOptions): Redis { + if (options instanceof Redis) { + return options + } else if (typeof options === 'string') { + const url = new URL( + options.startsWith('redis://') ? options : `redis://${options}`, + ) + + return new Redis({ + host: url.hostname, + port: parseInt(url.port, 10), + password: url.password, + }) + } else { + return new Redis(options) + } +} diff --git a/packages/oauth/oauth-provider/src/lib/util/authorization-header.ts b/packages/oauth/oauth-provider/src/lib/util/authorization-header.ts new file mode 100644 index 00000000000..f5dbd3c99d6 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/authorization-header.ts @@ -0,0 +1,26 @@ +import { accessTokenSchema, oauthTokenTypeSchema } from '@atproto/oauth-types' +import { z } from 'zod' + +import { InvalidRequestError } from '../../errors/invalid-request-error.js' +import { WWWAuthenticateError } from '../../errors/www-authenticate-error.js' + +export const authorizationHeaderSchema = z.tuple([ + oauthTokenTypeSchema, + accessTokenSchema, +]) + +export const parseAuthorizationHeader = (header?: string) => { + if (header == null) { + throw new WWWAuthenticateError( + 'invalid_request', + 'Authorization header required', + { Bearer: {}, DPoP: {} }, + ) + } + + const parsed = authorizationHeaderSchema.safeParse(header.split(' ', 2)) + if (!parsed.success) { + throw new InvalidRequestError('Invalid authorization header') + } + return parsed.data +} diff --git a/packages/oauth/oauth-provider/src/lib/util/cast.ts b/packages/oauth/oauth-provider/src/lib/util/cast.ts new file mode 100644 index 00000000000..9302f74efe0 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/cast.ts @@ -0,0 +1,4 @@ +export function asArray(value: T | T[]): T[] { + if (value == null) return [] + return Array.isArray(value) ? value : [value] +} diff --git a/packages/oauth/oauth-provider/src/lib/util/crypto.ts b/packages/oauth/oauth-provider/src/lib/util/crypto.ts new file mode 100644 index 00000000000..ef18ab0f6f1 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/crypto.ts @@ -0,0 +1,27 @@ +import { randomBytes } from 'node:crypto' + +export async function randomHexId(bytesLength = 16) { + return new Promise((resolve, reject) => { + randomBytes(bytesLength, (err, buf) => { + if (err) return reject(err) + resolve(buf.toString('hex')) + }) + }) +} + +// Basically all algorithms supported by "jose"'s jwtVerify(). +// TODO: Is there a way to get this list from the runtime instead of hardcoding it? +export const VERIFY_ALGOS = [ + 'RS256', + 'RS384', + 'RS512', + + 'PS256', + 'PS384', + 'PS512', + + 'ES256', + 'ES256K', + 'ES384', + 'ES512', +] as const diff --git a/packages/oauth/oauth-provider/src/lib/util/date.ts b/packages/oauth/oauth-provider/src/lib/util/date.ts new file mode 100644 index 00000000000..829030d2a53 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/date.ts @@ -0,0 +1,7 @@ +export function dateToEpoch(date: Date = new Date()) { + return Math.floor(date.getTime() / 1000) +} + +export function dateToRelativeSeconds(date: Date) { + return Math.floor((date.getTime() - Date.now()) / 1000) +} diff --git a/packages/oauth/oauth-provider/src/lib/util/hostname.ts b/packages/oauth/oauth-provider/src/lib/util/hostname.ts new file mode 100644 index 00000000000..0179641c3d9 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/hostname.ts @@ -0,0 +1,10 @@ +import { parse as pslParse } from 'psl' + +export function isLoopbackHost(host: string): boolean { + return host === 'localhost' || host === '127.0.0.1' || host === '[::1]' +} + +export function isInternetHost(host: string): boolean { + const parsed = pslParse(host) + return 'listed' in parsed && parsed.listed === true +} diff --git a/packages/oauth/oauth-provider/src/lib/util/path.ts b/packages/oauth/oauth-provider/src/lib/util/path.ts new file mode 100644 index 00000000000..50be22a4f21 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/path.ts @@ -0,0 +1,17 @@ +import { isAbsolute, relative } from 'node:path' + +export function isSubUrl(reference: URL, url: URL): boolean { + if (url.origin !== reference.origin) return false + if (url.username !== reference.username) return false + if (url.password !== reference.password) return false + + return ( + reference.pathname === url.pathname || + isSubPath(reference.pathname, url.pathname) + ) +} + +function isSubPath(reference: string, path: string): boolean { + const rel = relative(reference, path) + return !rel.startsWith('..') && !isAbsolute(rel) +} diff --git a/packages/oauth/oauth-provider/src/lib/util/redirect-uri.ts b/packages/oauth/oauth-provider/src/lib/util/redirect-uri.ts new file mode 100644 index 00000000000..4bf2189d733 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/redirect-uri.ts @@ -0,0 +1,42 @@ +import { isLoopbackHost } from './hostname.js' + +/** + * + * @see {@link https://datatracker.ietf.org/doc/html/rfc8252#section-8.4} + */ +export function compareRedirectUri( + allowed_uri: string, + request_uri: string, +): boolean { + // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4 + // + // > Authorization servers MUST require clients to register their complete + // > redirect URI (including the path component) and reject authorization + // > requests that specify a redirect URI that doesn't exactly match the + // > one that was registered; the exception is loopback redirects, where + // > an exact match is required except for the port URI component. + if (allowed_uri === request_uri) return true + + // https://datatracker.ietf.org/doc/html/rfc8252#section-7.3 + const allowedUri = new URL(allowed_uri) + if (isLoopbackHost(allowedUri.hostname)) { + const requestUri = new URL(request_uri) + + // > The authorization server MUST allow any port to be specified at the + // > time of the request for loopback IP redirect URIs, to accommodate + // > clients that obtain an available ephemeral port from the operating + // > system at the time of the request. + return ( + // allowedUri.port === requestUri.port && + allowedUri.hostname === requestUri.hostname && + allowedUri.pathname === requestUri.pathname && + allowedUri.protocol === requestUri.protocol && + allowedUri.search === requestUri.search && + allowedUri.hash === requestUri.hash && + allowedUri.username === requestUri.username && + allowedUri.password === requestUri.password + ) + } + + return false +} diff --git a/packages/oauth/oauth-provider/src/lib/util/time.ts b/packages/oauth/oauth-provider/src/lib/util/time.ts new file mode 100644 index 00000000000..856a8c83748 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/time.ts @@ -0,0 +1,29 @@ +import { Awaitable } from './type.js' + +/** + * Utility function to protect against timing attacks. + */ +export async function constantTime( + delay: number, + fn: () => Awaitable, +): Promise { + if (!(delay > 0) || !Number.isFinite(delay)) { + throw new TypeError('Delay must be greater than 0') + } + + const start = Date.now() + try { + return await fn() + } finally { + const delta = Date.now() - start + if (delta < delay) { + await new Promise((resolve) => setTimeout(resolve, delay - delta)) + } else { + // The delay is too short, let's wait for the next multiple of `delay` + // to avoid leaking information about the execution time. + await new Promise((resolve) => + setTimeout(resolve, delay * Math.ceil(delta / delay)), + ) + } + } +} diff --git a/packages/oauth/oauth-provider/src/lib/util/type.ts b/packages/oauth/oauth-provider/src/lib/util/type.ts new file mode 100644 index 00000000000..44367cbff28 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/type.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/ban-types +export type Simplify = { [K in keyof T]: T[K] } & {} +export type Override = V & Simplify> +export type Awaitable = T | Promise diff --git a/packages/oauth/oauth-provider/src/lib/util/well-known.ts b/packages/oauth/oauth-provider/src/lib/util/well-known.ts new file mode 100644 index 00000000000..e4e8d9f5dc6 --- /dev/null +++ b/packages/oauth/oauth-provider/src/lib/util/well-known.ts @@ -0,0 +1,8 @@ +export function buildWellknownUrl(url: URL, name: string): URL { + const path = + url.pathname === '/' + ? `/.well-known/${name}` + : `${url.pathname.replace(/\/+$/, '')}/${name}` + + return new URL(path, url) +} diff --git a/packages/oauth/oauth-provider/src/metadata/build-metadata.ts b/packages/oauth/oauth-provider/src/metadata/build-metadata.ts new file mode 100644 index 00000000000..2943e67be5a --- /dev/null +++ b/packages/oauth/oauth-provider/src/metadata/build-metadata.ts @@ -0,0 +1,161 @@ +import { Keyset } from '@atproto/jwk' +import { OAuthServerMetadata } from '@atproto/oauth-types' + +import { Client } from '../client/client.js' +import { OIDC_STANDARD_CLAIMS } from '../oidc/claims.js' +import { VERIFY_ALGOS } from '../lib/util/crypto.js' + +export type CustomMetadata = { + claims_supported?: string[] + scopes_supported?: string[] + authorization_details_types_supported?: string[] +} + +/** + * @see {@link https://datatracker.ietf.org/doc/html/rfc8414#section-2} + * @see {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata} + */ +export function buildMetadata( + issuer: string, + keyset: Keyset, + customMetadata?: CustomMetadata, +): OAuthServerMetadata { + return { + issuer: issuer, + + scopes_supported: [ + 'offline_access', + 'openid', + 'email', + 'phone', + 'profile', + + ...(customMetadata?.scopes_supported ?? []), + ], + claims_supported: [ + /* IESG (Always provided) */ + + 'sub', // did + 'iss', // Authorization Server Origin + 'aud', + 'exp', + 'iat', + 'jti', + 'client_id', + + /* OpenID */ + + // 'acr', // "0" + // 'amr', + // 'azp', + 'auth_time', // number - seconds since epoch + 'nonce', // always required in "id_token", why would it not be supported? + + ...(customMetadata?.claims_supported ?? OIDC_STANDARD_CLAIMS), + ], + subject_types_supported: [ + // + 'public', // The same "sub" is returned for all clients + // 'pairwise', // A different "sub" is returned for each client + ], + response_types_supported: [ + // OAuth + 'code', + 'token', + + // OpenID + 'none', + 'code id_token token', + 'code id_token', + 'code token', + 'id_token token', + 'id_token', + ], + response_modes_supported: [ + // https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes + 'query', + 'fragment', + // https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html#FormPostResponseMode + 'form_post', + ], + grant_types_supported: [ + // + 'authorization_code', + 'refresh_token', + ], + code_challenge_methods_supported: [ + // https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml#pkce-code-challenge-method + 'S256', + 'plain', + ], + ui_locales_supported: [ + // + 'en-US', + ], + id_token_signing_alg_values_supported: [...keyset.signAlgorithms], + display_values_supported: [ + // + 'page', + 'popup', + 'touch', + // 'wap', LoL + ], + + // https://datatracker.ietf.org/doc/html/rfc9207 + authorization_response_iss_parameter_supported: true, + + // https://datatracker.ietf.org/doc/html/rfc9101#section-4 + request_object_signing_alg_values_supported: [...VERIFY_ALGOS, 'none'], + request_object_encryption_alg_values_supported: [], // None + request_object_encryption_enc_values_supported: [], // None + + // No claim makes sense to be translated + claims_locales_supported: [], + + claims_parameter_supported: true, + request_parameter_supported: true, + request_uri_parameter_supported: true, + require_request_uri_registration: true, + + jwks_uri: new URL('/oauth/jwks', issuer).href, + + authorization_endpoint: new URL('/oauth/authorize', issuer).href, + + token_endpoint: new URL('/oauth/token', issuer).href, + token_endpoint_auth_methods_supported: [...Client.AUTH_METHODS_SUPPORTED], + token_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS], + + revocation_endpoint: new URL('/oauth/revoke', issuer).href, + revocation_endpoint_auth_methods_supported: [ + ...Client.AUTH_METHODS_SUPPORTED, + ], + revocation_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS], + + introspection_endpoint: new URL('/oauth/introspect', issuer).href, + introspection_endpoint_auth_methods_supported: [ + ...Client.AUTH_METHODS_SUPPORTED, + ], + introspection_endpoint_auth_signing_alg_values_supported: [...VERIFY_ALGOS], + + userinfo_endpoint: new URL('/oauth/userinfo', issuer).href, + // end_session_endpoint: new URL('/oauth/logout', issuer).href, + + // https://datatracker.ietf.org/doc/html/rfc9126#section-5 + pushed_authorization_request_endpoint: new URL('/oauth/par', issuer).href, + pushed_authorization_request_endpoint_auth_methods_supported: [ + ...Client.AUTH_METHODS_SUPPORTED, + ], + pushed_authorization_request_endpoint_auth_signing_alg_values_supported: [ + ...VERIFY_ALGOS, + ], + + require_pushed_authorization_requests: true, + + // https://datatracker.ietf.org/doc/html/rfc9449#section-5.1 + dpop_signing_alg_values_supported: [...VERIFY_ALGOS], + + // https://datatracker.ietf.org/doc/html/rfc9396#section-14.4 + authorization_details_types_supported: + customMetadata?.authorization_details_types_supported, + } +} diff --git a/packages/oauth/oauth-provider/src/oauth-client.ts b/packages/oauth/oauth-provider/src/oauth-client.ts new file mode 100644 index 00000000000..05483606f0a --- /dev/null +++ b/packages/oauth/oauth-provider/src/oauth-client.ts @@ -0,0 +1,3 @@ +export * from '@atproto/oauth-types' +export type * from './client/client.js' +export * from './client/client-utils.js' diff --git a/packages/oauth/oauth-provider/src/oauth-dpop.ts b/packages/oauth/oauth-provider/src/oauth-dpop.ts new file mode 100644 index 00000000000..8ba4e7aad84 --- /dev/null +++ b/packages/oauth/oauth-provider/src/oauth-dpop.ts @@ -0,0 +1,2 @@ +export * from './dpop/dpop-nonce.js' +export * from './dpop/dpop-manager.js' diff --git a/packages/oauth/oauth-provider/src/oauth-errors.ts b/packages/oauth/oauth-provider/src/oauth-errors.ts new file mode 100644 index 00000000000..4b938cd6652 --- /dev/null +++ b/packages/oauth/oauth-provider/src/oauth-errors.ts @@ -0,0 +1,20 @@ +// Root Error class +export { OAuthError } from './errors/oauth-error.js' + +export { AccessDeniedError } from './errors/access-denied-error.js' +export { AccountSelectionRequiredError } from './errors/account-selection-required-error.js' +export { ConsentRequiredError } from './errors/consent-required-error.js' +export { InvalidAuthorizationDetailsError } from './errors/invalid-authorization-details-error.js' +export { InvalidClientError } from './errors/invalid-client-error.js' +export { InvalidClientMetadataError } from './errors/invalid-client-metadata-error.js' +export { InvalidDpopKeyBindingError } from './errors/invalid-dpop-key-binding.js' +export { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js' +export { InvalidGrantError } from './errors/invalid-grant-error.js' +export { InvalidParametersError } from './errors/invalid-parameters-error.js' +export { InvalidRedirectUriError } from './errors/invalid-redirect-uri-error.js' +export { InvalidRequestError } from './errors/invalid-request-error.js' +export { InvalidTokenError } from './errors/invalid-token-error.js' +export { LoginRequiredError } from './errors/login-required-error.js' +export { UnauthorizedClientError } from './errors/unauthorized-client-error.js' +export { UseDpopNonceError } from './errors/use-dpop-nonce-error.js' +export { WWWAuthenticateError } from './errors/www-authenticate-error.js' diff --git a/packages/oauth/oauth-provider/src/oauth-hooks.ts b/packages/oauth/oauth-provider/src/oauth-hooks.ts new file mode 100644 index 00000000000..cad256efec4 --- /dev/null +++ b/packages/oauth/oauth-provider/src/oauth-hooks.ts @@ -0,0 +1,15 @@ +/** + * This file exposes all the hooks that can be used when instantiating the + * OAuthProvider. + */ +import type { AccountHooks } from './account/account-hooks.js' +import type { ClientHooks } from './client/client-hooks.js' +import type { RequestHooks } from './request/request-hooks.js' +import type { TokenHooks } from './token/token-hooks.js' + +export type * from './account/account-hooks.js' +export type * from './client/client-hooks.js' +export type * from './request/request-hooks.js' +export type * from './token/token-hooks.js' + +export type OAuthHooks = AccountHooks & ClientHooks & RequestHooks & TokenHooks diff --git a/packages/oauth/oauth-provider/src/oauth-provider.ts b/packages/oauth/oauth-provider/src/oauth-provider.ts new file mode 100644 index 00000000000..1a380c9a441 --- /dev/null +++ b/packages/oauth/oauth-provider/src/oauth-provider.ts @@ -0,0 +1,1296 @@ +import { Jwks, Jwt, Keyset, jwtSchema } from '@atproto/jwk' +import { + AccessToken, + CLIENT_ASSERTION_TYPE_JWT_BEARER, + OAuthAuthenticationRequestParameters, + OAuthClientId, + OAuthClientIdentification, + OAuthEndpointName, + OAuthServerMetadata, + OAuthTokenType, + oauthAuthenticationRequestParametersSchema, + oauthClientIdSchema, +} from '@atproto/oauth-types' +import { JWTHeaderParameters, ResolvedKey } from 'jose' +import { z } from 'zod' + +import { AccessTokenType } from './access-token/access-token-type.js' +import { AccountManager } from './account/account-manager.js' +import { + AccountInfo, + AccountStore, + DeviceAccountInfo, + LoginCredentials, + asAccountStore, +} from './account/account-store.js' +import { Account } from './account/account.js' +import { authorizeAssetsMiddleware } from './assets/assets-middleware.js' +import { ClientAuth, authJwkThumbprint } from './client/client-auth.js' +import { ClientManager } from './client/client-manager.js' +import { + ClientStoreUri, + ClientStoreUriConfig, +} from './client/client-store-uri.js' +import { ClientStore, ifClientStore } from './client/client-store.js' +import { Client } from './client/client.js' +import { AUTH_MAX_AGE, TOKEN_MAX_AGE } from './constants.js' +import { DeviceId } from './device/device-id.js' +import { DeviceManager } from './device/device-manager.js' +import { DeviceStore, asDeviceStore } from './device/device-store.js' +import { AccessDeniedError } from './errors/access-denied-error.js' +import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js' +import { ConsentRequiredError } from './errors/consent-required-error.js' +import { InvalidClientError } from './errors/invalid-client-error.js' +import { InvalidGrantError } from './errors/invalid-grant-error.js' +import { InvalidParametersError } from './errors/invalid-parameters-error.js' +import { InvalidRequestError } from './errors/invalid-request-error.js' +import { LoginRequiredError } from './errors/login-required-error.js' +import { UnauthorizedClientError } from './errors/unauthorized-client-error.js' +import { WWWAuthenticateError } from './errors/www-authenticate-error.js' +import { + Handler, + IncomingMessage, + Middleware, + Router, + ServerResponse, + acceptMiddleware, + combineMiddlewares, + setupCsrfToken, + staticJsonHandler, + validateCsrfToken, + validateFetchMode, + validateReferer, + validateRequestPayload, + validateSameOrigin, + writeJson, +} from './lib/http/index.js' +import { dateToEpoch, dateToRelativeSeconds } from './lib/util/date.js' +import { Override } from './lib/util/type.js' +import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js' +import { OAuthHooks } from './oauth-hooks.js' +import { OAuthVerifier, OAuthVerifierOptions } from './oauth-verifier.js' +import { Userinfo } from './oidc/userinfo.js' +import { + buildErrorPayload, + buildErrorStatus, +} from './output/build-error-payload.js' +import { Customization } from './output/customization.js' +import { + AuthorizationResultAuthorize, + sendAuthorizePage, +} from './output/send-authorize-page.js' +import { + AuthorizationResultRedirect, + sendAuthorizeRedirect, +} from './output/send-authorize-redirect.js' +import { sendErrorPage } from './output/send-error-page.js' +import { oidcPayload } from './parameters/oidc-payload.js' +import { ReplayStore, ifReplayStore } from './replay/replay-store.js' +import { RequestManager } from './request/request-manager.js' +import { RequestStoreMemory } from './request/request-store-memory.js' +import { RequestStoreRedis } from './request/request-store-redis.js' +import { RequestStore, ifRequestStore } from './request/request-store.js' +import { RequestUri, requestUriSchema } from './request/request-uri.js' +import { + AuthorizationRequestJar, + AuthorizationRequestQuery, + PushedAuthorizationRequest, + authorizationRequestQuerySchema, + pushedAuthorizationRequestSchema, +} from './request/types.js' +import { isTokenId } from './token/token-id.js' +import { TokenManager } from './token/token-manager.js' +import { TokenResponse } from './token/token-response.js' +import { TokenInfo, TokenStore, asTokenStore } from './token/token-store.js' +import { + CodeGrantRequest, + Introspect, + IntrospectionResponse, + RefreshGrantRequest, + Revoke, + TokenRequest, + introspectSchema, + revokeSchema, + tokenRequestSchema, +} from './token/types.js' +import { VerifyTokenClaimsOptions } from './token/verify-token-claims.js' + +export type OAuthProviderStore = Partial< + ClientStore & + AccountStore & + DeviceStore & + TokenStore & + RequestStore & + ReplayStore +> + +export { + Keyset, + type CustomMetadata, + type Customization, + type Handler, + type OAuthServerMetadata, +} +export type OAuthProviderOptions = Override< + OAuthVerifierOptions & OAuthHooks & ClientStoreUriConfig, + { + /** + * Maximum age a device/account session can be before requiring + * re-authentication. This can be overridden on a authorization request basis + * using the `max_age` parameter and on a client basis using the + * `default_max_age` client metadata. + */ + defaultMaxAge?: number + + /** + * Maximum age access & id tokens can be before requiring a refresh. + */ + tokenMaxAge?: number + + /** + * Additional metadata to be included in the discovery document. + */ + metadata?: CustomMetadata + + accountStore?: AccountStore + deviceStore?: DeviceStore + clientStore?: ClientStore + replayStore?: ReplayStore + requestStore?: RequestStore + tokenStore?: TokenStore + + /** + * This will be used as the default store for all the stores. If a store is + * not provided, this store will be used instead. If the `store` does not + * implement a specific store, a runtime error will be thrown. Make sure that + * this store implements all the interfaces not provided in the other + * `Store` options. + */ + store?: OAuthProviderStore + } +> + +export class OAuthProvider extends OAuthVerifier { + public readonly metadata: OAuthServerMetadata + + public readonly defaultMaxAge: number + + public readonly accountManager: AccountManager + public readonly deviceStore: DeviceStore + public readonly clientManager: ClientManager + public readonly requestManager: RequestManager + public readonly tokenManager: TokenManager + + public constructor({ + metadata, + defaultMaxAge = AUTH_MAX_AGE, + tokenMaxAge = TOKEN_MAX_AGE, + + store, // compound store implementation + + accountStore = asAccountStore(store), + deviceStore = asDeviceStore(store), + tokenStore = asTokenStore(store), + + clientStore = ifClientStore(store), + replayStore = ifReplayStore(store), + requestStore = ifRequestStore(store), + + redis, + ...rest + }: OAuthProviderOptions) { + super({ replayStore, redis, ...rest }) + + clientStore ??= new ClientStoreUri(rest) + requestStore ??= redis + ? new RequestStoreRedis({ redis }) + : new RequestStoreMemory() + + this.defaultMaxAge = defaultMaxAge + this.metadata = buildMetadata(this.issuer, this.keyset, metadata) + + this.deviceStore = deviceStore + + this.accountManager = new AccountManager(accountStore, rest) + this.clientManager = new ClientManager(clientStore, this.keyset, rest) + this.requestManager = new RequestManager( + requestStore, + this.signer, + this.metadata, + rest, + ) + this.tokenManager = new TokenManager( + tokenStore, + this.signer, + rest, + this.accessTokenType, + tokenMaxAge, + ) + } + + get jwks(): Jwks { + return this.keyset.publicJwks + } + + protected loginRequired( + client: Client, + parameters: OAuthAuthenticationRequestParameters, + info: DeviceAccountInfo, + ) { + const authAge = Math.max( + 0, // Prevent negative values (fool proof) + (Date.now() - info.authenticatedAt.getTime()) / 1e3, + ) + const maxAge = Math.max( + 0, // Prevent negative values (fool proof) + parameters.max_age ?? + client.metadata.default_max_age ?? + this.defaultMaxAge, + ) + + return Math.floor(authAge) > Math.floor(maxAge) + } + + protected async authenticateClient( + client: Client, + endpoint: OAuthEndpointName, + credentials: OAuthClientIdentification, + ): Promise { + const { clientAuth, nonce } = await client.verifyCredentials( + credentials, + endpoint, + { audience: this.issuer }, + ) + + if (nonce != null) { + const unique = await this.replayManager.uniqueAuth(nonce, client.id) + if (!unique) { + throw new InvalidClientError(`${clientAuth.method} jti reused`) + } + } + + return clientAuth + } + + protected async decodeJAR( + client: Client, + input: AuthorizationRequestJar, + ): Promise< + | { + payload: OAuthAuthenticationRequestParameters + protectedHeader?: undefined + key?: undefined + } + | { + payload: OAuthAuthenticationRequestParameters + protectedHeader: JWTHeaderParameters & { kid: string } + key: ResolvedKey['key'] + } + > { + const result = await client.decodeRequestObject(input.request) + const payload = oauthAuthenticationRequestParametersSchema.parse( + result.payload, + ) + + if (!result.payload.jti) { + throw new InvalidParametersError( + payload, + 'Request object must contain a jti claim', + ) + } + + if (!(await this.replayManager.uniqueJar(result.payload.jti, client.id))) { + throw new InvalidParametersError( + payload, + 'Request object jti is not unique', + ) + } + + if ('protectedHeader' in result) { + if (!result.protectedHeader.kid) { + throw new InvalidParametersError(payload, 'Missing "kid" in header') + } + + return { + key: result.key, + payload, + protectedHeader: result.protectedHeader as JWTHeaderParameters & { + kid: string + }, + } + } + + if ('header' in result) { + return { + payload, + } + } + + // Should never happen + throw new Error('Invalid request object') + } + + /** + * @see {@link https://datatracker.ietf.org/doc/html/rfc9126} + */ + protected async pushedAuthorizationRequest( + input: PushedAuthorizationRequest, + dpopJkt: null | string, + ) { + try { + const client = await this.clientManager.getClient(input.client_id) + const clientAuth = await this.authenticateClient( + client, + 'pushed_authorization_request', + input, + ) + + // TODO (?) should we allow using signed JAR for client authentication? + const { payload: parameters } = + 'request' in input // Handle JAR + ? await this.decodeJAR(client, input) + : { payload: input } + + const { uri, expiresAt } = + await this.requestManager.pushedAuthorizationRequest( + client, + clientAuth, + parameters, + dpopJkt, + ) + + return { + request_uri: uri, + expires_in: dateToRelativeSeconds(expiresAt), + } + } catch (err) { + // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3-1 + // > Since initial processing of the pushed authorization request does not + // > involve resource owner interaction, error codes related to user + // > interaction, such as consent_required defined by [OIDC], are never + // > returned. + if (err instanceof AccessDeniedError) { + throw new InvalidRequestError(err.error_description, err) + } + throw err + } + } + + private async setupAuthorizationRequest( + client: Client, + deviceId: DeviceId, + input: AuthorizationRequestQuery, + ) { + // Load PAR + if ('request_uri' in input) { + return this.requestManager.get(input.request_uri, client.id, deviceId) + } + + // Handle JAR + if ('request' in input) { + const requestObject = await this.decodeJAR(client, input) + + if (requestObject.protectedHeader) { + // Allow using signed JAR during "/authorize" as client authentication. + // This allows clients to skip PAR to initiate trusted sessions. + const clientAuth: ClientAuth = { + method: CLIENT_ASSERTION_TYPE_JWT_BEARER, + kid: requestObject.protectedHeader.kid, + alg: requestObject.protectedHeader.alg, + jkt: await authJwkThumbprint(requestObject.key), + } + + return this.requestManager.authorizationRequest( + client, + clientAuth, + requestObject.payload, + deviceId, + ) + } + + return this.requestManager.authorizationRequest( + client, + { method: 'none' }, + requestObject.payload, + deviceId, + ) + } + + return this.requestManager.authorizationRequest( + client, + { method: 'none' }, + input, + deviceId, + ) + } + + private async deleteRequest( + uri: RequestUri, + parameters: OAuthAuthenticationRequestParameters, + ) { + try { + await this.requestManager.delete(uri) + } catch (err) { + throw AccessDeniedError.from(parameters, err) + } + } + + protected async authorize( + deviceId: DeviceId, + input: AuthorizationRequestQuery, + ): Promise { + const { issuer } = this + const client = await this.clientManager.getClient(input.client_id) + + try { + const { uri, parameters, clientAuth } = + await this.setupAuthorizationRequest(client, deviceId, input) + + try { + const sessions = await this.getSessions( + client, + clientAuth, + deviceId, + parameters, + ) + + if (parameters.prompt === 'none') { + const ssoSessions = sessions.filter((s) => s.matchesHint) + if (ssoSessions.length > 1) { + throw new AccountSelectionRequiredError(parameters) + } + if (ssoSessions.length < 1) { + throw new LoginRequiredError(parameters) + } + + const ssoSession = ssoSessions[0]! + if (ssoSession.loginRequired) { + throw new LoginRequiredError(parameters) + } + if (ssoSession.consentRequired) { + throw new ConsentRequiredError(parameters) + } + + const redirect = await this.requestManager.setAuthorized( + client, + uri, + deviceId, + ssoSession.account, + ssoSession.info, + ) + + return { issuer, client, parameters, redirect } + } + + // Automatic SSO when a did was provided + if (parameters.prompt == null && parameters.login_hint != null) { + const ssoSessions = sessions.filter((s) => s.matchesHint) + if (ssoSessions.length === 1) { + const ssoSession = ssoSessions[0]! + if (!ssoSession.loginRequired && !ssoSession.consentRequired) { + const redirect = await this.requestManager.setAuthorized( + client, + uri, + deviceId, + ssoSession.account, + ssoSession.info, + ) + + return { issuer, client, parameters, redirect } + } + } + } + + return { issuer, client, parameters, authorize: { uri, sessions } } + } catch (err) { + await this.deleteRequest(uri, parameters) + + // Transform into an AccessDeniedError to allow redirecting the user + // to the client with the error details. + throw AccessDeniedError.from(parameters, err) + } + } catch (err) { + if (err instanceof AccessDeniedError) { + return { + issuer, + client, + parameters: err.parameters, + redirect: err.toJSON(), + } + } + + throw err + } + } + + protected async getSessions( + client: Client, + clientAuth: ClientAuth, + deviceId: DeviceId, + parameters: OAuthAuthenticationRequestParameters, + ): Promise< + { + account: Account + info: DeviceAccountInfo + + selected: boolean + loginRequired: boolean + consentRequired: boolean + + matchesHint: boolean + }[] + > { + const accounts = await this.accountManager.list(deviceId) + + return accounts.map(({ account, info }) => ({ + account, + info, + + selected: + parameters.prompt !== 'select_account' && + parameters.login_hint === account.sub, + loginRequired: + parameters.prompt === 'login' || + this.loginRequired(client, parameters, info), + consentRequired: + parameters.prompt === 'login' || + parameters.prompt === 'consent' || + !info.authorizedClients.includes(client.id), + + matchesHint: + parameters.login_hint === account.sub || parameters.login_hint == null, + })) + } + + protected async signIn( + deviceId: DeviceId, + credentials: LoginCredentials, + ): Promise { + return this.accountManager.signIn(credentials, deviceId) + } + + protected async acceptRequest( + deviceId: DeviceId, + uri: RequestUri, + clientId: OAuthClientId, + sub: string, + ): Promise { + const { issuer } = this + const client = await this.clientManager.getClient(clientId) + + try { + const { parameters, clientAuth } = await this.requestManager.get( + uri, + clientId, + deviceId, + ) + + try { + const { account, info } = await this.accountManager.get(deviceId, sub) + + // The user is trying to authorize without a fresh login + if (this.loginRequired(client, parameters, info)) { + throw new LoginRequiredError( + parameters, + 'Account authentication required.', + ) + } + + const redirect = await this.requestManager.setAuthorized( + client, + uri, + deviceId, + account, + info, + ) + + await this.accountManager.addAuthorizedClient( + deviceId, + account, + client, + clientAuth, + ) + + return { issuer, client, parameters, redirect } + } catch (err) { + await this.deleteRequest(uri, parameters) + + throw AccessDeniedError.from(parameters, err) + } + } catch (err) { + if (err instanceof AccessDeniedError) { + const { parameters } = err + return { issuer, client, parameters, redirect: err.toJSON() } + } + + throw err + } + } + + protected async rejectRequest( + deviceId: DeviceId, + uri: RequestUri, + clientId: OAuthClientId, + ): Promise { + try { + const { parameters } = await this.requestManager.get( + uri, + clientId, + deviceId, + ) + + await this.deleteRequest(uri, parameters) + + // Trigger redirect (see catch block) + throw new AccessDeniedError(parameters, 'Access denied') + } catch (err) { + if (err instanceof AccessDeniedError) { + return { + issuer: this.issuer, + client: await this.clientManager.getClient(clientId), + parameters: err.parameters, + redirect: err.toJSON(), + } + } + + throw err + } + } + + protected async token( + input: TokenRequest, + dpopJkt: null | string, + ): Promise { + const client = await this.clientManager.getClient(input.client_id) + const clientAuth = await this.authenticateClient(client, 'token', input) + + if (!client.metadata.grant_types.includes(input.grant_type)) { + throw new InvalidGrantError( + `"${input.grant_type}" grant type is not allowed for this client`, + ) + } + + if (input.grant_type === 'authorization_code') { + return this.codeGrant(client, clientAuth, input, dpopJkt) + } + + if (input.grant_type === 'refresh_token') { + return this.refreshTokenGrant(client, clientAuth, input, dpopJkt) + } + + throw new InvalidGrantError( + // @ts-expect-error: fool proof + `Grant type "${input.grant_type}" not supported`, + ) + } + + protected async codeGrant( + client: Client, + clientAuth: ClientAuth, + input: CodeGrantRequest, + dpopJkt: null | string, + ): Promise { + try { + const { sub, deviceId, parameters } = await this.requestManager.findCode( + client, + clientAuth, + input.code, + ) + + const { account, info } = await this.accountManager.get(deviceId, sub) + + // User revoked consent while client was asking for a token (or store + // failed to persist the consent) + if (!info.authorizedClients.includes(client.id)) { + throw new AccessDeniedError(parameters, 'Client not trusted anymore') + } + + return await this.tokenManager.create( + client, + clientAuth, + account, + { id: deviceId, info }, + parameters, + input, + dpopJkt, + ) + } catch (err) { + // If a token is replayed, requestManager.findCode will throw. In that + // case, we need to revoke any token that was issued for this code. + await this.tokenManager.revoke(input.code) + + throw err + } + } + + async refreshTokenGrant( + client: Client, + clientAuth: ClientAuth, + input: RefreshGrantRequest, + dpopJkt: null | string, + ): Promise { + return this.tokenManager.refresh(client, clientAuth, input, dpopJkt) + } + + /** + * @see {@link https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 rfc7009} + */ + protected async revoke(input: Revoke) { + await this.tokenManager.revoke(input.token) + } + + /** + * @see {@link https://datatracker.ietf.org/doc/html/rfc7662#section-2.1 rfc7662} + */ + protected async introspect( + input: Introspect, + ): Promise { + const client = await this.clientManager.getClient(input.client_id) + const clientAuth = await this.authenticateClient( + client, + 'introspection', + input, + ) + + // RFC7662 states the following: + // + // > To prevent token scanning attacks, the endpoint MUST also require some + // > form of authorization to access this endpoint, such as client + // > authentication as described in OAuth 2.0 [RFC6749] or a separate OAuth + // > 2.0 access token such as the bearer token described in OAuth 2.0 Bearer + // > Token Usage [RFC6750]. The methods of managing and validating these + // > authentication credentials are out of scope of this specification. + if (clientAuth.method === 'none') { + throw new UnauthorizedClientError('Client authentication required') + } + + const start = Date.now() + try { + const tokenInfo = await this.tokenManager.clientTokenInfo( + client, + clientAuth, + input.token, + ) + + return { + active: true, + + scope: tokenInfo.data.parameters.scope, + client_id: tokenInfo.data.clientId, + username: tokenInfo.account.preferred_username, + token_type: tokenInfo.data.parameters.dpop_jkt ? 'DPoP' : 'Bearer', + authorization_details: tokenInfo.data.details ?? undefined, + + aud: tokenInfo.account.aud, + exp: dateToEpoch(tokenInfo.data.expiresAt), + iat: dateToEpoch(tokenInfo.data.updatedAt), + iss: this.signer.issuer, + jti: tokenInfo.id, + sub: tokenInfo.account.sub, + } + } catch (err) { + // Prevent brute force & timing attack (only for inactive tokens) + await new Promise((r) => setTimeout(r, 750 - (Date.now() - start))) + + return { + active: false, + } + } + } + + /** + * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.3.2 Successful UserInfo Response} + */ + protected async userinfo({ data, account }: TokenInfo): Promise { + return { + ...oidcPayload(data.parameters, account), + + sub: account.sub, + + client_id: data.clientId, + username: account.preferred_username, + } + } + + protected async signUserinfo(userinfo: Userinfo): Promise { + const client = await this.clientManager.getClient(userinfo.client_id) + return this.signer.sign( + { + alg: client.metadata.userinfo_signed_response_alg, + typ: 'JWT', + }, + userinfo, + ) + } + + override async authenticateToken( + tokenType: OAuthTokenType, + token: AccessToken, + dpopJkt: string | null, + verifyOptions?: VerifyTokenClaimsOptions, + ) { + if (isTokenId(token)) { + this.assertTokenTypeAllowed(tokenType, AccessTokenType.id) + + return this.tokenManager.authenticateTokenId( + tokenType, + token, + dpopJkt, + verifyOptions, + ) + } + + return super.authenticateToken(tokenType, token, dpopJkt, verifyOptions) + } + + /** + * @returns An http request handler that can be used with node's http server + * or as a middleware with express / connect. + */ + public httpHandler< + T = void, + Req extends IncomingMessage = IncomingMessage, + Res extends ServerResponse = ServerResponse, + >({ + customization, + onError = process.env['NODE_ENV'] === 'development' + ? (req, res, err): void => console.error('OAuthProvider error:', err) + : undefined, + }: { + customization?: Customization + onError?: (req: Req, res: Res, err: unknown) => void + }): Handler { + const deviceManager = new DeviceManager(this.deviceStore) + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const server = this + const issuerUrl = new URL(server.issuer) + const issuerOrigin = issuerUrl.origin + const router = new Router(issuerUrl) + + // Utils + + const csrfCookie = (uri: RequestUri) => `csrf-${uri}` + + /** + * Creates a middleware that will serve static JSON content. + */ + const staticJson = (json: unknown): Middleware => + combineMiddlewares([ + function (req, res, next) { + res.setHeader('Access-Control-Allow-Origin', '*') + res.setHeader('Cache-Control', 'max-age=300') + next() + }, + staticJsonHandler(json), + ]) + + /** + * Wrap an OAuth endpoint in a middleware that will set the appropriate + * response headers and format the response as JSON. + */ + const dynamicJson = ( + buildJson: (this: T, req: TReq, res: TRes) => Json | Promise, + status?: number, + ): Handler => + async function (req, res) { + res.setHeader('Access-Control-Allow-Origin', '*') + + // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1 + res.setHeader('Cache-Control', 'no-store') + res.setHeader('Pragma', 'no-cache') + + // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2 + const dpopNonce = server.nextDpopNonce() + if (dpopNonce) { + const name = 'DPoP-Nonce' + res.setHeader(name, dpopNonce) + res.appendHeader('Access-Control-Expose-Headers', name) + } + + try { + const result = await buildJson.call(this, req, res) + if (result !== undefined) writeJson(res, result, status) + else if (!res.headersSent) res.writeHead(status ?? 204).end() + } catch (err) { + if (!res.headersSent) { + if (err instanceof WWWAuthenticateError) { + const name = 'WWW-Authenticate' + res.setHeader(name, err.wwwAuthenticateHeader) + res.appendHeader('Access-Control-Expose-Headers', name) + } + + writeJson(res, buildErrorPayload(err), buildErrorStatus(err)) + } else { + res.destroy() + } + + await onError?.(req, res, err) + } + } + + //- Public OAuth endpoints + + /* + * Although OpenID compatibility is not required to implement the Atproto + * OAuth2 specification, we do support OIDC discovery in this + * implementation as we believe this may: + * 1) Make the implementation of Atproto clients easier (since lots of + * libraries support OIDC discovery) + * 2) Allow self hosted PDS' to not implement authentication themselves + * but rely on a trusted Atproto actor to act as their OIDC providers. + * By supporting OIDC in the current implementation, Bluesky's + * Authorization Server server can be used as an OIDC provider for + * these users. + */ + router.get('/.well-known/openid-configuration', staticJson(server.metadata)) + + router.get( + '/.well-known/oauth-authorization-server', + staticJson(server.metadata), + ) + + // CORS preflight + router.options<{ + endpoint: 'jwks' | 'par' | 'token' | 'revoke' | 'introspect' | 'userinfo' + }>( + /^\/oauth\/(?jwks|par|token|revoke|introspect|userinfo)$/, + function (req, res, _next) { + res + .writeHead(204, { + 'Access-Control-Allow-Origin': req.headers['origin'] || '*', + 'Access-Control-Allow-Methods': + this.params.endpoint === 'jwks' ? 'GET' : 'POST', + 'Access-Control-Allow-Headers': 'Content-Type,Authorization,DPoP', + 'Access-Control-Max-Age': '86400', // 1 day + }) + .end() + }, + ) + + router.get('/oauth/jwks', staticJson(server.jwks)) + + router.post( + '/oauth/par', + dynamicJson(async function (req, _res) { + const input = await validateRequestPayload( + req, + pushedAuthorizationRequestSchema, + ) + + const dpopJkt = await server.checkDpopProof( + req.headers['dpop'], + req.method!, + this.url, + ) + + return server.pushedAuthorizationRequest(input, dpopJkt) + }, 201), + ) + + // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3 + router.addRoute('*', '/oauth/par', (req, res) => { + res.writeHead(405).end() + }) + + router.post( + '/oauth/token', + dynamicJson(async function (req, _res) { + const input = await validateRequestPayload(req, tokenRequestSchema) + + const dpopJkt = await server.checkDpopProof( + req.headers['dpop'], + req.method!, + this.url, + ) + + return server.token(input, dpopJkt) + }), + ) + + router.post( + '/oauth/revoke', + dynamicJson(async function (req, _res) { + const input = await validateRequestPayload(req, revokeSchema) + await server.revoke(input) + }), + ) + + router.get( + '/oauth/revoke', + dynamicJson(async function (req, res) { + validateFetchMode(req, res, ['navigate']) + validateSameOrigin(req, res, issuerOrigin) + + const query = Object.fromEntries(this.url.searchParams) + const input = await revokeSchema.parseAsync(query, { path: ['query'] }) + await server.revoke(input) + + // Same as POST + redirect to callback URL + // todo: generate JSONP response (if "callback" is provided) + + throw new Error( + 'You are successfully logged out. Redirect not implemented', + ) + }), + ) + + router.post( + '/oauth/introspect', + dynamicJson(async function (req, _res) { + const input = await validateRequestPayload(req, introspectSchema) + return server.introspect(input) + }), + ) + + const userinfoBodySchema = z.object({ + access_token: jwtSchema.optional(), + }) + + router.addRoute( + ['GET', 'POST'], + '/oauth/userinfo', + acceptMiddleware( + async function (req, _res) { + const body = + req.method === 'POST' + ? await validateRequestPayload(req, userinfoBodySchema) + : null + + if (body?.access_token && req.headers['authorization']) { + throw new InvalidRequestError( + 'access token must be provided in either the authorization header or the request body', + ) + } + + const auth = await server.authenticateHttpRequest( + req.method!, + this.url, + body?.access_token // Allow credentials to be parsed from body. + ? { + authorization: `Bearer ${body.access_token}`, + dpop: undefined, // DPoP can only be used with headers + } + : req.headers, + { + // TODO? Add the URL as an audience of the token ? + // audience: [this.url.href], + scope: ['profile'], + }, + ) + + const tokenInfo: TokenInfo = + 'tokenInfo' in auth + ? (auth.tokenInfo as TokenInfo) + : await server.tokenManager.getTokenInfo( + auth.tokenType, + auth.tokenId, + ) + + return server.userinfo(tokenInfo) + }, + { + '': 'application/json', + 'application/json': dynamicJson(async function (_req, _res) { + return this.data + }), + 'application/jwt': dynamicJson(async function (_req, res) { + const jwt = await server.signUserinfo(this.data) + res.writeHead(200, { 'Content-Type': 'application/jwt' }).end(jwt) + return undefined + }), + }, + ), + ) + + //- Private authorization endpoints + + router.use(authorizeAssetsMiddleware()) + + router.get('/oauth/authorize', async function (req, res) { + try { + res.setHeader('Cache-Control', 'no-store') + + validateFetchMode(req, res, ['navigate']) + validateSameOrigin(req, res, issuerOrigin) + + const query = Object.fromEntries(this.url.searchParams) + const input = await authorizationRequestQuerySchema.parseAsync(query, { + path: ['query'], + }) + + const { deviceId } = await deviceManager.load(req, res) + const data = await server.authorize(deviceId, input) + + switch (true) { + case 'redirect' in data: { + return await sendAuthorizeRedirect(req, res, data) + } + case 'authorize' in data: { + await setupCsrfToken(req, res, csrfCookie(data.authorize.uri)) + return await sendAuthorizePage(req, res, data, customization) + } + default: { + // Should never happen + throw new Error('Unexpected authorization result') + } + } + } catch (err) { + await onError?.(req, res, err) + + if (!res.headersSent) { + await sendErrorPage(req, res, err, customization) + } + } + }) + + const signInPayloadSchema = z.object({ + csrf_token: z.string(), + request_uri: requestUriSchema, + client_id: oauthClientIdSchema, + credentials: z.object({ + username: z.string(), + password: z.string(), + remember: z.boolean().optional().default(false), + }), + }) + + router.post('/oauth/authorize/sign-in', async function (req, res) { + validateFetchMode(req, res, ['same-origin']) + validateSameOrigin(req, res, issuerOrigin) + + const input = await validateRequestPayload(req, signInPayloadSchema) + + validateReferer(req, res, { + origin: issuerOrigin, + pathname: '/oauth/authorize', + }) + validateCsrfToken( + req, + res, + input.csrf_token, + csrfCookie(input.request_uri), + ) + + const { deviceId } = await deviceManager.load(req, res) + + const { account, info } = await server.signIn(deviceId, input.credentials) + + // Prevent fixation attacks + await deviceManager.rotate(req, res, deviceId) + + return writeJson(res, { + account, + consentRequired: !info.authorizedClients.includes(input.client_id), + }) + }) + + const acceptQuerySchema = z.object({ + csrf_token: z.string(), + request_uri: requestUriSchema, + client_id: oauthClientIdSchema, + account_sub: z.string(), + }) + + router.get('/oauth/authorize/accept', async function (req, res) { + try { + res.setHeader('Cache-Control', 'no-store') + + validateFetchMode(req, res, ['navigate']) + validateSameOrigin(req, res, issuerOrigin) + + const query = Object.fromEntries(this.url.searchParams) + const input = await acceptQuerySchema.parseAsync(query, { + path: ['query'], + }) + + validateReferer(req, res, { + origin: issuerOrigin, + pathname: '/oauth/authorize', + searchParams: [ + ['request_uri', input.request_uri], + ['client_id', input.client_id], + ], + }) + validateCsrfToken( + req, + res, + input.csrf_token, + csrfCookie(input.request_uri), + true, + ) + + const { deviceId } = await deviceManager.load(req, res) + + const data = await server.acceptRequest( + deviceId, + input.request_uri, + input.client_id, + input.account_sub, + ) + + return await sendAuthorizeRedirect(req, res, data) + } catch (err) { + await onError?.(req, res, err) + + if (!res.headersSent) { + await sendErrorPage(req, res, err, customization) + } + } + }) + + const rejectQuerySchema = z.object({ + csrf_token: z.string(), + request_uri: requestUriSchema, + client_id: oauthClientIdSchema, + }) + + router.get('/oauth/authorize/reject', async function (req, res) { + try { + res.setHeader('Cache-Control', 'no-store') + + validateFetchMode(req, res, ['navigate']) + validateSameOrigin(req, res, issuerOrigin) + + const query = Object.fromEntries(this.url.searchParams) + const input = await rejectQuerySchema.parseAsync(query, { + path: ['query'], + }) + + validateReferer(req, res, { + origin: issuerOrigin, + pathname: '/oauth/authorize', + searchParams: [ + ['request_uri', input.request_uri], + ['client_id', input.client_id], + ], + }) + validateCsrfToken( + req, + res, + input.csrf_token, + csrfCookie(input.request_uri), + true, + ) + + const { deviceId } = await deviceManager.load(req, res) + + const data = await server.rejectRequest( + deviceId, + input.request_uri, + input.client_id, + ) + + return await sendAuthorizeRedirect(req, res, data) + } catch (err) { + await onError?.(req, res, err) + + if (!res.headersSent) { + await sendErrorPage(req, res, err, customization) + } + } + }) + + return router.routes() + } +} diff --git a/packages/oauth/oauth-provider/src/oauth-store.ts b/packages/oauth/oauth-provider/src/oauth-store.ts new file mode 100644 index 00000000000..a397d27e49e --- /dev/null +++ b/packages/oauth/oauth-provider/src/oauth-store.ts @@ -0,0 +1,11 @@ +/** + * Every store file exports all the types needed to implement that store. This + * files re-exports all the types from the x-store files. + */ + +export type * from './account/account-store.js' +export type * from './client/client-store.js' +export type * from './device/device-store.js' +export type * from './replay/replay-store.js' +export type * from './request/request-store.js' +export type * from './token/token-store.js' diff --git a/packages/oauth/oauth-provider/src/oauth-verifier.ts b/packages/oauth/oauth-provider/src/oauth-verifier.ts new file mode 100644 index 00000000000..17ad4fcbf2f --- /dev/null +++ b/packages/oauth/oauth-provider/src/oauth-verifier.ts @@ -0,0 +1,210 @@ +import { Keyset, jwtSchema } from '@atproto/jwk' +import { AccessToken, OAuthTokenType } from '@atproto/oauth-types' +import { Redis, type RedisOptions } from 'ioredis' + +import { AccessTokenType } from './access-token/access-token-type.js' +import { DpopManager, DpopManagerOptions } from './dpop/dpop-manager.js' +import { DpopNonce } from './dpop/dpop-nonce.js' +import { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js' +import { InvalidTokenError } from './errors/invalid-token-error.js' +import { WWWAuthenticateError } from './errors/www-authenticate-error.js' +import { parseAuthorizationHeader } from './lib/util/authorization-header.js' +import { Override } from './lib/util/type.js' +import { ReplayManager } from './replay/replay-manager.js' +import { ReplayStore } from './replay/replay-store.js' +import { Signer } from './signer/signer.js' +import { + VerifyTokenClaimsOptions, + VerifyTokenClaimsResult, + verifyTokenClaims, +} from './token/verify-token-claims.js' +import { ReplayStoreRedis } from './replay/replay-store-redis.js' +import { ReplayStoreMemory } from './replay/replay-store-memory.js' + +export type OAuthVerifierOptions = Override< + DpopManagerOptions, + { + /** + * The "issuer" identifier of the OAuth provider, this is the base URL of the + * OAuth provider. + */ + issuer: URL | string + + /** + * The keyset used to sign tokens. Note that OIDC requires that at least one + * RS256 key is present in the keyset. ATPROTO requires ES256. + */ + keyset: Keyset + + /** + * If set to {@link AccessTokenType.jwt}, the provider will use JWTs for + * access tokens. If set to {@link AccessTokenType.id}, the provider will + * use tokenId as access tokens. If set to {@link AccessTokenType.auto}, + * JWTs will only be used if the audience is different from the issuer. + * Defaults to {@link AccessTokenType.jwt}. + * + * Here is a comparison of the two types: + * + * - pro id: less CPU intensive (no crypto operations) + * - pro id: less bandwidth (shorter tokens than jwt) + * - pro id: token data is in sync with database (e.g. revocation) + * - pro jwt: stateless: no I/O needed (no db lookups through token store) + * - pro jwt: stateless: allows Resource Server to be on a different + * host/server + */ + accessTokenType?: AccessTokenType + + /** + * A redis instance to use for replay protection. If not provided, replay + * protection will use memory storage. + */ + redis?: Redis | RedisOptions | string + + replayStore?: ReplayStore + } +> + +export { + AccessTokenType, + DpopNonce, + Keyset, + type ReplayStore, + type VerifyTokenClaimsOptions, +} + +export class OAuthVerifier { + public readonly issuer: string + public readonly keyset: Keyset + + protected readonly accessTokenType: AccessTokenType + protected readonly dpopManager: DpopManager + protected readonly replayManager: ReplayManager + protected readonly signer: Signer + + constructor({ + redis, + issuer, + keyset, + replayStore = redis != null + ? new ReplayStoreRedis({ redis }) + : new ReplayStoreMemory(), + accessTokenType = AccessTokenType.jwt, + + ...dpopMgrOptions + }: OAuthVerifierOptions) { + const issuerUrl = new URL(String(issuer)) + if (issuerUrl.pathname !== '/' || issuerUrl.search || issuerUrl.hash) { + throw new TypeError( + '"issuer" must be an URL with no path, search or hash', + ) + } + + this.issuer = issuerUrl.href + this.keyset = keyset + + this.accessTokenType = accessTokenType + this.dpopManager = new DpopManager(dpopMgrOptions) + this.replayManager = new ReplayManager(replayStore) + this.signer = new Signer(this.issuer, this.keyset) + } + + public nextDpopNonce() { + return this.dpopManager.nextNonce() + } + + public async checkDpopProof( + proof: unknown, + htm: string, + htu: string | URL, + accessToken?: string, + ): Promise { + if (proof === undefined) return null + + const { payload, jkt } = await this.dpopManager.checkProof( + proof, + htm, + htu, + accessToken, + ) + + const unique = await this.replayManager.uniqueDpop(payload.jti) + if (!unique) throw new InvalidDpopProofError('DPoP proof jti is not unique') + + return jkt + } + + protected assertTokenTypeAllowed( + tokenType: OAuthTokenType, + accessTokenType: AccessTokenType, + ) { + if ( + this.accessTokenType !== AccessTokenType.auto && + this.accessTokenType !== accessTokenType + ) { + throw new InvalidTokenError(tokenType, `Invalid token`) + } + } + + public async authenticateToken( + tokenType: OAuthTokenType, + token: AccessToken, + dpopJkt: string | null, + verifyOptions?: VerifyTokenClaimsOptions, + ): Promise { + const jwt = jwtSchema.safeParse(token) + if (!jwt.success) { + throw new InvalidTokenError(tokenType, `Invalid token`) + } + + this.assertTokenTypeAllowed(tokenType, AccessTokenType.jwt) + + const { payload } = await this.signer + .verifyAccessToken(jwt.data) + .catch((err) => { + throw InvalidTokenError.from(err, tokenType) + }) + + return verifyTokenClaims( + jwt.data, + payload.jti, + tokenType, + dpopJkt, + payload, + verifyOptions, + ) + } + + public async authenticateHttpRequest( + method: string, + url: URL, + headers: { + authorization?: string + dpop?: unknown + }, + verifyOptions?: VerifyTokenClaimsOptions, + ): Promise { + const [tokenType, token] = parseAuthorizationHeader(headers.authorization) + try { + const dpopJkt = await this.checkDpopProof( + headers.dpop, + method, + url, + token, + ) + + if (tokenType === 'DPoP' && !dpopJkt) { + throw new InvalidDpopProofError(`DPoP proof required`) + } + + return await this.authenticateToken( + tokenType, + token, + dpopJkt, + verifyOptions, + ) + } catch (err) { + if (err instanceof WWWAuthenticateError) throw err + throw InvalidTokenError.from(err, tokenType) + } + } +} diff --git a/packages/oauth/oauth-provider/src/oidc/claims.ts b/packages/oauth/oauth-provider/src/oidc/claims.ts new file mode 100644 index 00000000000..cbcbfe2dc8d --- /dev/null +++ b/packages/oauth/oauth-provider/src/oidc/claims.ts @@ -0,0 +1,35 @@ +import { JwtPayload } from '@atproto/jwk' + +/** + * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims | OpenID Connect Core 1.0, 5.4. Requesting Claims using Scope Values} + */ +export const OIDC_SCOPE_CLAIMS = Object.freeze({ + email: Object.freeze(['email', 'email_verified'] as const), + phone: Object.freeze(['phone_number', 'phone_number_verified'] as const), + address: Object.freeze(['address'] as const), + profile: Object.freeze([ + 'name', + 'family_name', + 'given_name', + 'middle_name', + 'nickname', + 'preferred_username', + 'gender', + 'picture', + 'profile', + 'website', + 'birthdate', + 'zoneinfo', + 'locale', + 'updated_at', + ] as const), +}) + +export const OIDC_STANDARD_CLAIMS = Object.freeze( + Object.values(OIDC_SCOPE_CLAIMS).flat(), +) + +export type OIDCStandardClaim = (typeof OIDC_STANDARD_CLAIMS)[number] +export type OIDCStandardPayload = Partial<{ + [K in OIDCStandardClaim]?: JwtPayload[K] +}> diff --git a/packages/oauth/oauth-provider/src/oidc/sub.ts b/packages/oauth/oauth-provider/src/oidc/sub.ts new file mode 100644 index 00000000000..be25110436b --- /dev/null +++ b/packages/oauth/oauth-provider/src/oidc/sub.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const subSchema = z.string().min(1) +export type Sub = z.infer diff --git a/packages/oauth/oauth-provider/src/oidc/userinfo.ts b/packages/oauth/oauth-provider/src/oidc/userinfo.ts new file mode 100644 index 00000000000..3810c6e3748 --- /dev/null +++ b/packages/oauth/oauth-provider/src/oidc/userinfo.ts @@ -0,0 +1,11 @@ +import { OIDCStandardPayload } from './claims.js' + +export type Userinfo = OIDCStandardPayload & { + // "The sub (subject) Claim MUST always be returned in the UserInfo Response." + sub: string + + // client_id is not mandatory per spec, but we require it here for convenience + client_id: string + + username?: string +} diff --git a/packages/oauth/oauth-provider/src/output/build-error-payload.ts b/packages/oauth/oauth-provider/src/output/build-error-payload.ts new file mode 100644 index 00000000000..7ab5acabc24 --- /dev/null +++ b/packages/oauth/oauth-provider/src/output/build-error-payload.ts @@ -0,0 +1,140 @@ +import { errors } from 'jose' +import { ZodError } from 'zod' + +import { OAuthError } from '../errors/oauth-error.js' + +const { JOSEError } = errors + +const INVALID_REQUEST = 'invalid_request' +const SERVER_ERROR = 'server_error' + +export function buildErrorStatus(error: unknown): number { + if (error instanceof OAuthError) { + return error.statusCode + } + + if (error instanceof ZodError) { + return 400 + } + + if (error instanceof JOSEError) { + return 400 + } + + if (error instanceof TypeError) { + return 400 + } + + if (isBoom(error)) { + return error.output.statusCode + } + + if (isXrpcError(error)) { + return error.type + } + + const status = (error as any)?.status + if ( + typeof status === 'number' && + status === (status | 0) && + status >= 400 && + status < 600 + ) { + return status + } + + return 500 +} + +export function buildErrorPayload(error: unknown): { + error: string + error_description: string +} { + if (error instanceof OAuthError) { + return error.toJSON() + } + + if (error instanceof ZodError) { + return { + error: INVALID_REQUEST, + error_description: error.issues[0]?.message || 'Invalid request', + } + } + + if (error instanceof JOSEError) { + return { + error: INVALID_REQUEST, + error_description: error.message, + } + } + + if (error instanceof TypeError) { + return { + error: INVALID_REQUEST, + error_description: error.message, + } + } + + if (isBoom(error)) { + return { + error: error.output.statusCode <= 500 ? INVALID_REQUEST : SERVER_ERROR, + error_description: + error.output.statusCode <= 500 + ? isPayloadLike(error.output?.payload) + ? error.output.payload.message + : error.message + : 'Server error', + } + } + + if (isXrpcError(error)) { + return { + error: error.type <= 500 ? INVALID_REQUEST : SERVER_ERROR, + error_description: error.payload.message, + } + } + + const status = buildErrorStatus(error) + return { + error: status < 500 ? INVALID_REQUEST : SERVER_ERROR, + error_description: + error instanceof Error && (error as any)?.expose === true + ? error.message + : 'Server error', + } +} + +function isBoom(v: unknown): v is Error & { + isBoom: true + output: { statusCode: number; payload: unknown } +} { + return ( + v instanceof Error && + (v as any).isBoom === true && + isHttpErrorCode(v['output']?.['statusCode']) + ) +} + +function isXrpcError(v: unknown): v is Error & { + type: number + payload: { error: string; message: string } +} { + return ( + v instanceof Error && + isHttpErrorCode(v['type']) && + isPayloadLike(v['payload']) + ) +} + +function isHttpErrorCode(v: unknown): v is number { + return typeof v === 'number' && v >= 400 && v < 600 && v === (v | 0) +} + +function isPayloadLike(v: unknown): v is { error: string; message: string } { + return ( + v != null && + typeof v === 'object' && + typeof v['error'] === 'string' && + typeof v['message'] === 'string' + ) +} diff --git a/packages/oauth/oauth-provider/src/output/customization.ts b/packages/oauth/oauth-provider/src/output/customization.ts new file mode 100644 index 00000000000..c69ee81aa1a --- /dev/null +++ b/packages/oauth/oauth-provider/src/output/customization.ts @@ -0,0 +1,118 @@ +// Matches colors defined in tailwind.config.js +const colorNames = ['primary', 'error'] as const +type ColorName = (typeof colorNames)[number] +const isColorName = (name: string): name is ColorName => + (colorNames as readonly string[]).includes(name) + +export type FieldDefinition = { + label?: string + placeholder?: string + pattern?: string + title?: string +} + +export type ExtraFieldDefinition = FieldDefinition & { + type: 'text' | 'password' | 'date' | 'captcha' + required?: boolean + [_: string]: unknown +} + +export type Customization = { + name?: string + logo?: string + colors?: { [_ in ColorName]?: string } + links?: Array<{ + title: string + href: string + rel?: string + }> + + signIn?: { + fields?: { + username?: FieldDefinition + password?: FieldDefinition + remember?: FieldDefinition + } + } + + signUp?: + | false + | { + fields?: { + username?: FieldDefinition + password?: FieldDefinition + } + extraFields?: Record + } +} + +export function buildCustomizationData({ + name, + logo, + links, + signIn, + signUp = false, +}: Customization = {}) { + return { + name, + logo, + links, + signIn, + signUp: signUp || undefined, + } +} + +export function buildCustomizationCss(customization?: Customization) { + if (!customization?.colors) return '' + + const vars = Object.entries(customization.colors) + .filter((e) => isColorName(e[0]) && e[1] != null) + .map(([name, value]) => [name, parseColor(value)] as const) + .filter((e): e is [ColorName, ParsedColor] => e[1] != null) + // alpha not supported by tailwind (it does not work that way) + .map(([name, { r, g, b }]) => `--color-${name}: ${r} ${g} ${b};`) + + return `:root { ${vars.join(' ')} }` +} + +type ParsedColor = { r: number; g: number; b: number; a?: number } +function parseColor(color: string): undefined | ParsedColor { + if (color.startsWith('#')) { + if (color.length === 4 || color.length === 5) { + const [r, g, b, a] = color + .slice(1) + .split('') + .map((c) => parseInt(c + c, 16)) + return { r, g, b, a } + } + + if (color.length === 7 || color.length === 9) { + const r = parseInt(color.slice(1, 3), 16) + const g = parseInt(color.slice(3, 5), 16) + const b = parseInt(color.slice(5, 7), 16) + const a = color.length > 8 ? parseInt(color.slice(7, 9), 16) : undefined + return { r, g, b, a } + } + + return undefined + } + + const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/) + if (rgbMatch) { + const [, r, g, b] = rgbMatch + return { r: parseInt(r, 10), g: parseInt(g, 10), b: parseInt(b, 10) } + } + + const rgbaMatch = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*(\d+)\)/) + if (rgbaMatch) { + const [, r, g, b, a] = rgbaMatch + return { + r: parseInt(r, 10), + g: parseInt(g, 10), + b: parseInt(b, 10), + a: parseInt(a, 10), + } + } + + return undefined +} diff --git a/packages/oauth/oauth-provider/src/output/send-authorize-page.ts b/packages/oauth/oauth-provider/src/output/send-authorize-page.ts new file mode 100644 index 00000000000..420bba32f48 --- /dev/null +++ b/packages/oauth/oauth-provider/src/output/send-authorize-page.ts @@ -0,0 +1,82 @@ +import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types' +import { IncomingMessage, ServerResponse } from 'node:http' + +import { DeviceAccountInfo } from '../account/account-store.js' +import { Account } from '../account/account.js' +import { getAsset } from '../assets/index.js' +import { Client } from '../client/client.js' +import { cssCode, html } from '../lib/html/index.js' +import { declareBrowserGlobalVar, sendWebPage } from '../lib/http/index.js' +import { RequestUri } from '../request/request-uri.js' +import { + Customization, + buildCustomizationCss, + buildCustomizationData, +} from './customization.js' + +export type AuthorizationResultAuthorize = { + issuer: string + client: Client + parameters: OAuthAuthenticationRequestParameters + authorize: { + uri: RequestUri + sessions: readonly { + account: Account + info: DeviceAccountInfo + + selected: boolean + loginRequired: boolean + consentRequired: boolean + }[] + } +} + +function buildAuthorizeData(data: AuthorizationResultAuthorize) { + return { + csrfCookie: `csrf-${data.authorize.uri}`, + requestUri: data.authorize.uri, + clientId: data.client.id, + clientMetadata: data.client.metadata, + loginHint: data.parameters.login_hint, + newSessionsRequireConsent: + data.parameters.prompt === 'login' || + data.parameters.prompt === 'consent', + sessions: data.authorize.sessions.map((session) => ({ + account: session.account, + + selected: session.selected, + loginRequired: session.loginRequired, + consentRequired: session.consentRequired, + })), + } +} + +export async function sendAuthorizePage( + req: IncomingMessage, + res: ServerResponse, + data: AuthorizationResultAuthorize, + customization?: Customization, +): Promise { + res.setHeader('Cache-Control', 'no-store') + res.setHeader('Permissions-Policy', 'otp-credentials=*, document-domain=()') + + return sendWebPage(res, { + scripts: [ + declareBrowserGlobalVar( + '__customizationData', + buildCustomizationData(customization), + ), + declareBrowserGlobalVar('__authorizeData', buildAuthorizeData(data)), + await getAsset('main.js'), + ], + styles: [ + await getAsset('main.css'), + cssCode(buildCustomizationCss(customization)), + ], + head: customization?.links?.map((l) => { + return html`` + }), + title: 'Authorize', + body: html`
`, + }) +} diff --git a/packages/oauth/oauth-provider/src/output/send-authorize-redirect.ts b/packages/oauth/oauth-provider/src/output/send-authorize-redirect.ts new file mode 100644 index 00000000000..52abe3ec599 --- /dev/null +++ b/packages/oauth/oauth-provider/src/output/send-authorize-redirect.ts @@ -0,0 +1,127 @@ +import { + OAuthAuthenticationRequestParameters, + OAuthTokenType, +} from '@atproto/oauth-types' +import { IncomingMessage, ServerResponse } from 'node:http' + +import { Client } from '../client/client.js' +import { html, javascriptCode } from '../lib/html/index.js' +import { sendWebPage } from '../lib/http/index.js' +import { Code } from '../request/code.js' + +export type AuthorizationResponseParameters = { + // Will be added from AuthorizationResultRedirect['issuer'] + // iss: string // rfc9207 + + // Will be added from AuthorizationResultRedirect['parameters'] + // state?: string + + code?: Code + id_token?: string + access_token?: string + token_type?: OAuthTokenType + expires_in?: string + + response?: string // FAPI JARM + session_state?: string // OIDC Session Management + + error?: string + error_description?: string + error_uri?: string +} + +export type AuthorizationResultRedirect = { + issuer: string + client: Client + parameters: OAuthAuthenticationRequestParameters + redirect: AuthorizationResponseParameters +} + +export async function sendAuthorizeRedirect( + req: IncomingMessage, + res: ServerResponse, + result: AuthorizationResultRedirect, +): Promise { + const { issuer, parameters, redirect, client } = result + + const uri = parameters.redirect_uri || client.metadata.redirect_uris[0] + const mode = parameters.response_mode || 'query' // TODO: default depends on response_type + + const entries: [string, string][] = Object.entries({ + iss: issuer, // rfc9207 + state: parameters.state, + + response: redirect.response, // FAPI JARM + session_state: redirect.session_state, // OIDC Session Management + + code: redirect.code, + id_token: redirect.id_token, + access_token: redirect.access_token, + expires_in: redirect.expires_in, + token_type: redirect.token_type, + + error: redirect.error, + error_description: redirect.error_description, + error_uri: redirect.error_uri, + }).filter((entry): entry is [string, string] => entry[1] != null) + + res.setHeader('Cache-Control', 'no-store') + + switch (mode) { + case 'query': + return writeQuery(res, uri, entries) + case 'fragment': + return writeFragment(res, uri, entries) + case 'form_post': + return writeFormPost(res, uri, entries) + } + + // @ts-expect-error fool proof + throw new Error(`Unsupported mode: ${mode}`) +} + +function writeQuery( + res: ServerResponse, + uri: string, + entries: readonly [string, string][], +) { + const url = new URL(uri) + for (const [key, value] of entries) url.searchParams.set(key, value) + res.writeHead(302, { Location: url.href }).end() +} + +function writeFragment( + res: ServerResponse, + uri: string, + entries: readonly [string, string][], +) { + const url = new URL(uri) + const searchParams = new URLSearchParams() + for (const [key, value] of entries) searchParams.set(key, value) + url.hash = searchParams.toString() + res.writeHead(302, { Location: url.href }).end() +} + +async function writeFormPost( + res: ServerResponse, + uri: string, + entries: readonly [string, string][], +) { + // Prevent the Chrome from caching this page + // see: https://latesthackingnews.com/2023/12/12/google-updates-chrome-bfcache-for-faster-page-viewing/ + res.setHeader('Set-Cookie', `bfCacheBypass=foo; max-age=1; SameSite=Lax`) + res.setHeader('Cache-Control', 'no-store') + res.setHeader('Permissions-Policy', 'otp-credentials=*, document-domain=()') + + return sendWebPage(res, { + body: html` +
+ ${entries.map(([key, value]) => [ + html``, + ])} + +
+ `, + scripts: [javascriptCode('document.forms[0].submit();')], + }) +} diff --git a/packages/oauth/oauth-provider/src/output/send-error-page.ts b/packages/oauth/oauth-provider/src/output/send-error-page.ts new file mode 100644 index 00000000000..3c902ae061b --- /dev/null +++ b/packages/oauth/oauth-provider/src/output/send-error-page.ts @@ -0,0 +1,36 @@ +import { IncomingMessage, ServerResponse } from 'node:http' + +import { getAsset } from '../assets/index.js' +import { cssCode, html } from '../lib/html/index.js' +import { declareBrowserGlobalVar, sendWebPage } from '../lib/http/index.js' +import { buildErrorPayload, buildErrorStatus } from './build-error-payload.js' +import { + Customization, + buildCustomizationCss, + buildCustomizationData, +} from './customization.js' + +export async function sendErrorPage( + req: IncomingMessage, + res: ServerResponse, + err: unknown, + customization?: Customization, +): Promise { + return sendWebPage(res, { + status: buildErrorStatus(err), + scripts: [ + declareBrowserGlobalVar( + '__customizationData', + buildCustomizationData(customization), + ), + declareBrowserGlobalVar('__errorData', buildErrorPayload(err)), + await getAsset('main.js'), + ], + styles: [ + await getAsset('main.css'), + cssCode(buildCustomizationCss(customization)), + ], + title: 'Error', + body: html`
`, + }) +} diff --git a/packages/oauth/oauth-provider/src/parameters/claims-requested.ts b/packages/oauth/oauth-provider/src/parameters/claims-requested.ts new file mode 100644 index 00000000000..e6a40e045e9 --- /dev/null +++ b/packages/oauth/oauth-provider/src/parameters/claims-requested.ts @@ -0,0 +1,106 @@ +import { + OAuthAuthenticationRequestParameters, + OidcClaimsParameter, + OidcEntityType, +} from '@atproto/oauth-types' +import { InvalidRequestError } from '../errors/invalid-request-error.js' + +export function claimRequested( + parameters: OAuthAuthenticationRequestParameters, + entityType: OidcEntityType, + claimName: OidcClaimsParameter, + value: unknown, +): boolean { + if (claimAvailable(parameters, entityType, claimName, value)) { + return true + } + + const entityClaims = parameters.claims?.[entityType] + if (entityClaims?.[claimName]?.essential === true) { + // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.5.1 + // + // > By requesting Claims as Essential Claims, the RP indicates to the + // > End-User that releasing these Claims will ensure a smooth + // > authorization for the specific task requested by the End-User. Note + // > that even if the Claims are not available because the End-User did + // > not authorize their release or they are not present, the + // > Authorization Server MUST NOT generate an error when Claims are not + // > returned, whether they are Essential or Voluntary, unless otherwise + // > specified in the description of the specific claim. + switch (claimName) { + case 'acr': + // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.5.1.1 + // + // > If this is an Essential Claim and the requirement cannot be met, + // > then the Authorization Server MUST treat that outcome as a failed + // > authentication attempt. + throw new InvalidRequestError( + `Unable to provide essential claim: ${claimName}`, + ) + } + } + + return false +} + +function claimAvailable( + parameters: OAuthAuthenticationRequestParameters, + entityType: OidcEntityType, + claimName: OidcClaimsParameter, + value: unknown, +): boolean { + if (value === undefined) return false + + if (parameters.claims) { + const entityClaims = parameters.claims[entityType] + if (entityClaims === undefined) return false + + const claimConfig = entityClaims[claimName] + if (claimConfig === undefined) return false + if (claimConfig === null) return true + + if ( + claimConfig.value !== undefined && + !compareClaimValue(claimConfig.value, value) + ) { + return false + } + + if ( + claimConfig?.values !== undefined && + !claimConfig.values.some((v) => compareClaimValue(v, value)) + ) { + return false + } + } + + return true +} + +type DefinedValue = NonNullable | null + +function compareClaimValue( + expectedValue: DefinedValue, + value: DefinedValue, +): boolean { + const expectedType = typeof expectedValue + const valueType = typeof value + + if (expectedType !== valueType) return false + + switch (typeof expectedValue) { + case 'undefined': + case 'string': + case 'number': + case 'boolean': + return expectedValue === value + case 'object': + if (expectedValue === null) return value === null + // TODO (?): allow for object comparison + // falls through + default: + throw new InvalidRequestError( + `Unable to compare claim value of type ${expectedType}`, + ) + } +} diff --git a/packages/oauth/oauth-provider/src/parameters/oidc-payload.ts b/packages/oauth/oauth-provider/src/parameters/oidc-payload.ts new file mode 100644 index 00000000000..5077890e388 --- /dev/null +++ b/packages/oauth/oauth-provider/src/parameters/oidc-payload.ts @@ -0,0 +1,28 @@ +import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types' +import { Account } from '../account/account.js' +import { OIDCStandardPayload, OIDC_SCOPE_CLAIMS } from '../oidc/claims.js' +import { claimRequested } from './claims-requested.js' + +export function oidcPayload( + params: OAuthAuthenticationRequestParameters, + account: Account, +) { + const payload: OIDCStandardPayload = {} + + const scopes = params.scope ? params.scope?.split(' ') : undefined + if (scopes) { + for (const [scope, claims] of Object.entries(OIDC_SCOPE_CLAIMS)) { + const allowed = scopes.includes(scope) + for (const claim of claims) { + const value = allowed ? account[claim] : undefined + // Should not throw as RequestManager should have already checked + // that all the essential claims are available. + if (claimRequested(params, 'id_token', claim, value)) { + payload[claim] = value as any // All good as long as the account props match the claims + } + } + } + } + + return payload +} diff --git a/packages/oauth/oauth-provider/src/replay/replay-manager.ts b/packages/oauth/oauth-provider/src/replay/replay-manager.ts new file mode 100644 index 00000000000..681834ecc9c --- /dev/null +++ b/packages/oauth/oauth-provider/src/replay/replay-manager.ts @@ -0,0 +1,39 @@ +import { OAuthClientId } from '@atproto/oauth-types' + +import { + CLIENT_ASSERTION_MAX_AGE, + DPOP_NONCE_MAX_AGE, + JAR_MAX_AGE, +} from '../constants.js' +import { ReplayStore } from './replay-store.js' + +const SECURITY_RATIO = 1.1 // 10% extra time for security +const asTimeFrame = (timeFrame: number) => Math.ceil(timeFrame * SECURITY_RATIO) + +export class ReplayManager { + constructor(protected readonly replayStore: ReplayStore) {} + + async uniqueAuth(jti: string, clientId: OAuthClientId): Promise { + return this.replayStore.unique( + `Auth@${clientId}`, + jti, + asTimeFrame(CLIENT_ASSERTION_MAX_AGE), + ) + } + + async uniqueJar(jti: string, clientId: OAuthClientId): Promise { + return this.replayStore.unique( + `JAR@${clientId}`, + jti, + asTimeFrame(JAR_MAX_AGE), + ) + } + + async uniqueDpop(jti: string, clientId?: OAuthClientId): Promise { + return this.replayStore.unique( + clientId ? `DPoP@${clientId}` : `DPoP`, + jti, + asTimeFrame(DPOP_NONCE_MAX_AGE), + ) + } +} diff --git a/packages/oauth/oauth-provider/src/replay/replay-store-memory.ts b/packages/oauth/oauth-provider/src/replay/replay-store-memory.ts new file mode 100644 index 00000000000..fea9dc2fdf1 --- /dev/null +++ b/packages/oauth/oauth-provider/src/replay/replay-store-memory.ts @@ -0,0 +1,36 @@ +import type { ReplayStore } from '@atproto/oauth-provider' + +export class ReplayStoreMemory implements ReplayStore { + private lastCleanup = Date.now() + private nonces = new Map() + + /** + * Returns true if the nonce is unique within the given time frame. + */ + async unique( + namespace: string, + nonce: string, + timeFrame: number, + ): Promise { + this.cleanup() + const key = `${namespace}:${nonce}` + + const now = Date.now() + + const exp = this.nonces.get(key) + this.nonces.set(key, now + timeFrame) + + return exp == null || exp < now + } + + private cleanup() { + const now = Date.now() + + if (this.lastCleanup < now - 60_000) { + for (const [key, expires] of this.nonces) { + if (expires < now) this.nonces.delete(key) + } + this.lastCleanup = now + } + } +} diff --git a/packages/oauth/oauth-provider/src/replay/replay-store-redis.ts b/packages/oauth/oauth-provider/src/replay/replay-store-redis.ts new file mode 100644 index 00000000000..ceb9afa0993 --- /dev/null +++ b/packages/oauth/oauth-provider/src/replay/replay-store-redis.ts @@ -0,0 +1,30 @@ +import type { ReplayStore } from '@atproto/oauth-provider' +import { Redis } from 'ioredis' +import { CreateRedisOptions, createRedis } from '../lib/redis.js' + +export type { Redis, CreateRedisOptions } + +export type ReplayStoreRedisOptions = { + redis: CreateRedisOptions +} + +export class ReplayStoreRedis implements ReplayStore { + private readonly redis: Redis + + constructor(options: ReplayStoreRedisOptions) { + this.redis = createRedis(options.redis) + } + + /** + * Returns true if the nonce is unique within the given time frame. + */ + async unique( + namespace: string, + nonce: string, + timeFrame: number, + ): Promise { + const key = `nonces:${namespace}:${nonce}` + const prev = await this.redis.set(key, '1', 'PX', timeFrame, 'GET') + return prev == null + } +} diff --git a/packages/oauth/oauth-provider/src/replay/replay-store.ts b/packages/oauth/oauth-provider/src/replay/replay-store.ts new file mode 100644 index 00000000000..95b83a965d7 --- /dev/null +++ b/packages/oauth/oauth-provider/src/replay/replay-store.ts @@ -0,0 +1,44 @@ +import { Awaitable } from '../lib/util/type.js' + +// Export all types needed to implement the ReplayStore interface +export type { Awaitable } + +export interface ReplayStore { + /** + * Returns true if the nonce is unique within the given time frame. While not + * strictly necessary for security purposes, the namespace should be used to + * mitigate denial of service attacks from one client to the other. + * + * @param timeFrame expressed in milliseconds. Will never exceed 24 hours. + */ + unique( + namespace: string, + nonce: string, + timeFrame: number, + ): Awaitable +} + +export function isReplayStore( + implementation: Record & Partial, +): implementation is Record & ReplayStore { + return typeof implementation.unique === 'function' +} + +export function ifReplayStore( + implementation?: Record & Partial, +): ReplayStore | undefined { + if (implementation && isReplayStore(implementation)) { + return implementation + } + + return undefined +} + +export function asReplayStore( + implementation?: Record & Partial, +): ReplayStore { + const store = ifReplayStore(implementation) + if (store) return store + + throw new Error('Invalid ReplayStore implementation') +} diff --git a/packages/oauth/oauth-provider/src/request/code.ts b/packages/oauth/oauth-provider/src/request/code.ts new file mode 100644 index 00000000000..5d8fae9eef5 --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/code.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +import { CODE_BYTES_LENGTH, CODE_PREFIX } from '../constants.js' +import { randomHexId } from '../lib/util/crypto.js' + +export const codeSchema = z + .string() + .length(CODE_PREFIX.length + CODE_BYTES_LENGTH * 2) // hex encoding + .refine( + (v): v is `${typeof CODE_PREFIX}${string}` => v.startsWith(CODE_PREFIX), + { + message: `Invalid code format`, + }, + ) + +export const isCode = (data: unknown): data is Code => + codeSchema.safeParse(data).success + +export type Code = z.infer +export const generateCode = async (): Promise => { + return `${CODE_PREFIX}${await randomHexId(CODE_BYTES_LENGTH)}` +} diff --git a/packages/oauth/oauth-provider/src/request/request-data.ts b/packages/oauth/oauth-provider/src/request/request-data.ts new file mode 100644 index 00000000000..225c0176548 --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-data.ts @@ -0,0 +1,19 @@ +import { + OAuthAuthenticationRequestParameters, + OAuthClientId, +} from '@atproto/oauth-types' + +import { ClientAuth } from '../client/client-auth.js' +import { DeviceId } from '../device/device-id.js' +import { Sub } from '../oidc/sub.js' +import { Code } from './code.js' + +export type RequestData = { + clientId: OAuthClientId + clientAuth: ClientAuth + parameters: OAuthAuthenticationRequestParameters + expiresAt: Date + deviceId: DeviceId | null + sub: Sub | null + code: Code | null +} diff --git a/packages/oauth/oauth-provider/src/request/request-hooks.ts b/packages/oauth/oauth-provider/src/request/request-hooks.ts new file mode 100644 index 00000000000..5bc5360783f --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-hooks.ts @@ -0,0 +1,19 @@ +import { ClientAuth } from '../client/client-auth.js' +import { Client } from '../client/client.js' +import { Awaitable } from '../lib/util/type.js' + +/** + * Non trusted clients will have reduced scopes, refresh token validity, and + * will require user consent for authorization requests. + */ +export type IsFirstPartyClientHook = ( + this: null, + client: Client, + data: { + clientAuth: ClientAuth + }, +) => Awaitable + +export type RequestHooks = { + onIsFirstPartyClient?: IsFirstPartyClientHook +} diff --git a/packages/oauth/oauth-provider/src/request/request-id.ts b/packages/oauth/oauth-provider/src/request/request-id.ts new file mode 100644 index 00000000000..91267a65c50 --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-id.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +import { REQUEST_ID_BYTES_LENGTH, REQUEST_ID_PREFIX } from '../constants.js' +import { randomHexId } from '../lib/util/crypto.js' + +export const requestIdSchema = z + .string() + .length( + REQUEST_ID_PREFIX.length + REQUEST_ID_BYTES_LENGTH * 2, // hex encoding + ) + .refine( + (v): v is `${typeof REQUEST_ID_PREFIX}${string}` => + v.startsWith(REQUEST_ID_PREFIX), + { + message: `Invalid request ID format`, + }, + ) + +export type RequestId = z.infer +export const generateRequestId = async (): Promise => { + return `${REQUEST_ID_PREFIX}${await randomHexId(REQUEST_ID_BYTES_LENGTH)}` +} diff --git a/packages/oauth/oauth-provider/src/request/request-info.ts b/packages/oauth/oauth-provider/src/request/request-info.ts new file mode 100644 index 00000000000..ea38c37a93b --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-info.ts @@ -0,0 +1,12 @@ +import { OAuthAuthenticationRequestParameters } from '@atproto/oauth-types' +import { ClientAuth } from '../client/client-auth.js' +import { RequestId } from './request-id.js' +import { RequestUri } from './request-uri.js' + +export type RequestInfo = { + id: RequestId + uri: RequestUri + parameters: OAuthAuthenticationRequestParameters + expiresAt: Date + clientAuth: ClientAuth +} diff --git a/packages/oauth/oauth-provider/src/request/request-manager.ts b/packages/oauth/oauth-provider/src/request/request-manager.ts new file mode 100644 index 00000000000..2a273919452 --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-manager.ts @@ -0,0 +1,501 @@ +import { + CLIENT_ASSERTION_TYPE_JWT_BEARER, + OAuthAuthenticationRequestParameters, + OAuthClientId, + OAuthServerMetadata, +} from '@atproto/oauth-types' + +import { DeviceAccountInfo } from '../account/account-store.js' +import { Account } from '../account/account.js' +import { ClientAuth } from '../client/client-auth.js' +import { Client } from '../client/client.js' +import { + AUTHORIZATION_INACTIVITY_TIMEOUT, + PAR_EXPIRES_IN, + TOKEN_MAX_AGE, +} from '../constants.js' +import { DeviceId } from '../device/device-id.js' +import { AccessDeniedError } from '../errors/access-denied-error.js' +import { InvalidGrantError } from '../errors/invalid-grant-error.js' +import { ConsentRequiredError } from '../errors/consent-required-error.js' +import { InvalidParametersError } from '../errors/invalid-parameters-error.js' +import { InvalidRequestError } from '../errors/invalid-request-error.js' +import { compareRedirectUri } from '../lib/util/redirect-uri.js' +import { OIDC_SCOPE_CLAIMS } from '../oidc/claims.js' +import { Sub } from '../oidc/sub.js' +import { Signer } from '../signer/signer.js' +import { Code, generateCode } from './code.js' +import { RequestHooks } from './request-hooks.js' +import { generateRequestId } from './request-id.js' +import { RequestInfo } from './request-info.js' +import { RequestStore, UpdateRequestData } from './request-store.js' +import { + RequestUri, + decodeRequestUri, + encodeRequestUri, +} from './request-uri.js' + +export class RequestManager { + constructor( + protected readonly store: RequestStore, + protected readonly signer: Signer, + protected readonly metadata: OAuthServerMetadata, + protected readonly hooks: RequestHooks, + protected readonly pkceRequired = true, + protected readonly tokenMaxAge = TOKEN_MAX_AGE, + ) {} + + protected createTokenExpiry() { + return new Date(Date.now() + this.tokenMaxAge) + } + + async pushedAuthorizationRequest( + client: Client, + clientAuth: ClientAuth, + input: OAuthAuthenticationRequestParameters, + dpopJkt: null | string, + ): Promise { + const params = await this.validate(client, clientAuth, input, dpopJkt) + return this.create(client, clientAuth, params, null) + } + + async authorizationRequest( + client: Client, + clientAuth: ClientAuth, + input: OAuthAuthenticationRequestParameters, + deviceId: DeviceId, + ): Promise { + const params = await this.validate(client, clientAuth, input, null) + return this.create(client, clientAuth, params, deviceId) + } + + protected async create( + client: Client, + clientAuth: ClientAuth, + parameters: OAuthAuthenticationRequestParameters, + deviceId: null | DeviceId = null, + ): Promise { + const expiresAt = new Date(Date.now() + PAR_EXPIRES_IN) + const id = await generateRequestId() + + await this.store.createRequest(id, { + clientId: client.id, + clientAuth, + parameters, + expiresAt, + deviceId, + sub: null, + code: null, + }) + + const uri = encodeRequestUri(id) + return { id, uri, expiresAt, parameters, clientAuth } + } + + async validate( + client: Client, + clientAuth: ClientAuth, + parameters: Readonly, + dpopJkt: null | string, + pkceRequired = this.pkceRequired, + ): Promise { + // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10#section-1.4.1 + // > The authorization server MAY fully or partially ignore the scope + // > requested by the client, based on the authorization server policy or + // > the resource owner's instructions. If the issued access token scope is + // > different from the one requested by the client, the authorization + // > server MUST include the scope response parameter in the token response + // > (Section 3.2.3) to inform the client of the actual scope granted. + + const cScopes = client.metadata.scope?.split(' ') + const sScopes = this.metadata.scopes_supported + + const scopes = + (parameters.scope || client.metadata.scope) + ?.split(' ') + .filter((scope) => !!scope && (sScopes?.includes(scope) ?? true)) ?? [] + + for (const scope of scopes) { + if (!cScopes?.includes(scope)) { + throw new InvalidParametersError( + parameters, + `Scope "${scope}" is not registered for this client`, + ) + } + } + + for (const [scope, claims] of Object.entries(OIDC_SCOPE_CLAIMS)) { + for (const claim of claims) { + if ( + parameters?.claims?.id_token?.[claim]?.essential === true || + parameters?.claims?.userinfo?.[claim]?.essential === true + ) { + if (!scopes?.includes(scope)) { + throw new InvalidParametersError( + parameters, + `Essential ${claim} claim requires "${scope}" scope`, + ) + } + } + } + } + + parameters = { ...parameters, scope: scopes.join(' ') } + + const responseTypes = parameters.response_type.split(' ') + + if (parameters.authorization_details) { + const cAuthDetailsTypes = client.metadata.authorization_details_types + if (!cAuthDetailsTypes) { + throw new InvalidParametersError( + parameters, + 'Client does not support authorization_details', + ) + } + + for (const detail of parameters.authorization_details) { + if (!cAuthDetailsTypes?.includes(detail.type)) { + throw new InvalidParametersError( + parameters, + `Unsupported authorization_details type "${detail.type}"`, + ) + } + } + } + + const { redirect_uri } = parameters + if ( + redirect_uri && + !client.metadata.redirect_uris.some((uri) => + compareRedirectUri(uri, redirect_uri), + ) + ) { + throw new InvalidParametersError( + parameters, + `Invalid redirect_uri ${redirect_uri} (allowed: ${client.metadata.redirect_uris.join(' ')})`, + ) + } + + // https://datatracker.ietf.org/doc/html/rfc9449#section-10 + if (!parameters.dpop_jkt) { + if (dpopJkt) parameters = { ...parameters, dpop_jkt: dpopJkt } + } else if (parameters.dpop_jkt !== dpopJkt) { + throw new InvalidParametersError( + parameters, + 'DPoP header and dpop_jkt do not match', + ) + } + + if (clientAuth.method === CLIENT_ASSERTION_TYPE_JWT_BEARER) { + if (parameters.dpop_jkt && clientAuth.jkt === parameters.dpop_jkt) { + throw new InvalidParametersError( + parameters, + 'The DPoP proof must be signed with a different key than the client assertion', + ) + } + } + + if (!client.metadata.response_types.includes(parameters.response_type)) { + throw new InvalidParametersError( + parameters, + `Unsupported response_type "${parameters.response_type}"`, + 'unsupported_response_type', + ) + } + + if (pkceRequired && responseTypes.includes('token')) { + throw new InvalidParametersError( + parameters, + `Response type "${parameters.response_type}" is incompatible with PKCE`, + 'unsupported_response_type', + ) + } + + // https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1 + if (pkceRequired && !parameters.code_challenge) { + throw new InvalidParametersError(parameters, 'code_challenge is required') + } + + if ( + parameters.code_challenge && + clientAuth.method === 'none' && + (parameters.code_challenge_method ?? 'plain') === 'plain' + ) { + throw new InvalidParametersError( + parameters, + 'code_challenge_method=plain requires client authentication', + ) + } + + // https://datatracker.ietf.org/doc/html/rfc7636#section-4.3 + if (parameters.code_challenge_method && !parameters.code_challenge) { + throw new InvalidParametersError( + parameters, + 'code_challenge_method requires code_challenge', + ) + } + + // https://openid.net/specs/openid-connect-core-1_0.html#HybridAuthRequest + if (responseTypes.includes('id_token') && !parameters.nonce) { + throw new InvalidParametersError( + parameters, + 'openid scope requires a nonce', + ) + } + + if (responseTypes.includes('id_token') && !scopes?.includes('openid')) { + throw new InvalidParametersError( + parameters, + '"id_token" response_type requires "openid" scope', + ) + } + + // TODO Validate parameters against **all** client metadata (are some checks + // missing?) !!! + + // Make "expensive" checks after the "cheaper" checks + + if (parameters.id_token_hint != null) { + const { payload } = await this.signer.verify(parameters.id_token_hint, { + // these are meant to be outdated when used as a hint + clockTolerance: Infinity, + }) + + if (!payload.sub) { + throw new InvalidParametersError( + parameters, + `Unexpected empty id_token_hint "sub"`, + ) + } else if (parameters.login_hint == null) { + parameters = { ...parameters, login_hint: payload.sub } + } else if (parameters.login_hint !== payload.sub) { + throw new InvalidParametersError( + parameters, + 'login_hint does not match "sub" of id_token_hint', + ) + } + } + + // ATPROTO extension: if the client is not trusted, force users to consent + // to authorization requests. We do this to avoid unauthenticated clients + // from being able to silently re-authenticate users. + if (clientAuth.method === 'none') { + const isFirstParty = await this.hooks.onIsFirstPartyClient?.call( + null, + client, + { clientAuth }, + ) + if (isFirstParty !== true) { + if (parameters.prompt === 'none') { + throw new ConsentRequiredError( + parameters, + 'Public clients are not allowed to use silent-sign-on', + ) + } + + if (parameters.scope?.includes('offline_access')) { + throw new InvalidParametersError( + parameters, + 'Public clients are not allowed to request offline access', + ) + } + + // force "consent" for unauthenticated, third party clients + parameters = { ...parameters, prompt: 'consent' } + } + } + + return parameters + } + + async get( + uri: RequestUri, + clientId: OAuthClientId, + deviceId: DeviceId, + ): Promise { + const id = decodeRequestUri(uri) + + const data = await this.store.readRequest(id) + if (!data) throw new InvalidRequestError(`Unknown request_uri "${uri}"`) + + const updates: UpdateRequestData = {} + + try { + if (data.sub || data.code) { + // If an account was linked to the request, the next step is to exchange + // the code for a token. + throw new AccessDeniedError( + data.parameters, + 'This request was already authorized', + ) + } + + if (data.expiresAt < new Date()) { + throw new AccessDeniedError(data.parameters, 'This request has expired') + } else { + updates.expiresAt = new Date( + Date.now() + AUTHORIZATION_INACTIVITY_TIMEOUT, + ) + } + + if (data.clientId !== clientId) { + throw new AccessDeniedError( + data.parameters, + 'This request was initiated for another client', + ) + } + + if (!data.deviceId) { + updates.deviceId = deviceId + } else if (data.deviceId !== deviceId) { + throw new AccessDeniedError( + data.parameters, + 'This request was initiated from another device', + ) + } + } catch (err) { + await this.store.deleteRequest(id) + throw err + } + + if (Object.keys(updates).length > 0) { + await this.store.updateRequest(id, updates) + } + + return { + id, + uri, + expiresAt: updates.expiresAt || data.expiresAt, + parameters: data.parameters, + clientAuth: data.clientAuth, + } + } + + async setAuthorized( + client: Client, + uri: RequestUri, + deviceId: DeviceId, + account: Account, + info: DeviceAccountInfo, + ): Promise<{ code?: Code; id_token?: string }> { + const id = decodeRequestUri(uri) + + const data = await this.store.readRequest(id) + if (!data) throw new InvalidRequestError(`Unknown request_uri "${uri}"`) + + try { + if (data.expiresAt < new Date()) { + throw new AccessDeniedError(data.parameters, 'This request has expired') + } + if (!data.deviceId) { + throw new AccessDeniedError( + data.parameters, + 'This request was not initiated', + ) + } + if (data.deviceId !== deviceId) { + throw new AccessDeniedError( + data.parameters, + 'This request was initiated from another device', + ) + } + if (data.sub || data.code) { + throw new AccessDeniedError( + data.parameters, + 'This request was already authorized', + ) + } + + const responseType = data.parameters.response_type.split(' ') + + const code = responseType.includes('code') + ? await generateCode() + : undefined + + // Bind the request to the account, preventing it from being used again. + await this.store.updateRequest(id, { + sub: account.sub, + code, + // Allow the client to exchange the code for a token within the next 60 seconds. + expiresAt: new Date(Date.now() + AUTHORIZATION_INACTIVITY_TIMEOUT), + }) + + const id_token = responseType.includes('id_token') + ? await this.signer.idToken(client, data.parameters, account, { + auth_time: info.authenticatedAt, + exp: this.createTokenExpiry(), + code, + }) + : undefined + + return { code, id_token } + } catch (err) { + await this.store.deleteRequest(id) + throw err + } + } + + /** + * @note If this method throws an error, any token previously generated from + * the same `code` **must** me revoked. + */ + public async findCode( + client: Client, + clientAuth: ClientAuth, + code: Code, + ): Promise<{ + sub: Sub + deviceId: DeviceId + parameters: OAuthAuthenticationRequestParameters + }> { + const result = await this.store.findRequestByCode(code) + if (!result) throw new InvalidGrantError('Invalid code') + + try { + const { data } = result + + if (data.sub == null || data.deviceId == null) { + // Maybe the store implementation is faulty ? + throw new Error('Unexpected request state') + } + + if (data.clientId !== client.id) { + throw new InvalidGrantError('This code was issued for another client') + } + + if (data.expiresAt < new Date()) { + throw new InvalidGrantError('This code has expired') + } + + if (data.clientAuth.method === 'none') { + // If the client did not use PAR, it was not authenticated when the + // request was created (see authorize() method above). Since PAR is not + // mandatory, and since the token exchange currently taking place *is* + // authenticated (`clientAuth`), we allow "upgrading" the authentication + // method (the token created will be bound to the current clientAuth). + } else { + if (clientAuth.method !== data.clientAuth.method) { + throw new InvalidGrantError('Invalid client authentication') + } + + if (!(await client.validateClientAuth(data.clientAuth))) { + throw new InvalidGrantError('Invalid client authentication') + } + } + + return { + sub: data.sub, + deviceId: data.deviceId, + parameters: data.parameters, + } + } finally { + // A "code" can only be used once + await this.store.deleteRequest(result.id) + } + } + + async delete(uri: RequestUri): Promise { + const id = decodeRequestUri(uri) + await this.store.deleteRequest(id) + } +} diff --git a/packages/oauth/oauth-provider/src/request/request-store-memory.ts b/packages/oauth/oauth-provider/src/request/request-store-memory.ts new file mode 100644 index 00000000000..243f42f23ed --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-store-memory.ts @@ -0,0 +1,39 @@ +import { Code } from './code.js' +import { RequestId } from './request-id.js' +import { RequestData } from './request-data.js' +import { RequestStore } from './request-store.js' + +export class RequestStoreMemory implements RequestStore { + #requests = new Map() + + async readRequest(id: RequestId): Promise { + return this.#requests.get(id) ?? null + } + + async createRequest(id: RequestId, data: RequestData): Promise { + this.#requests.set(id, data) + } + + async updateRequest( + id: RequestId, + data: Partial, + ): Promise { + const current = this.#requests.get(id) + if (!current) throw new Error('Request not found') + const newData = { ...current, ...data } + this.#requests.set(id, newData) + } + + async deleteRequest(id: RequestId): Promise { + this.#requests.delete(id) + } + + async findRequestByCode( + code: Code, + ): Promise<{ id: RequestId; data: RequestData } | null> { + for (const [id, data] of this.#requests) { + if (data.code === code) return { id, data } + } + return null + } +} diff --git a/packages/oauth/oauth-provider/src/request/request-store-redis.ts b/packages/oauth/oauth-provider/src/request/request-store-redis.ts new file mode 100644 index 00000000000..50a1b21ba20 --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-store-redis.ts @@ -0,0 +1,71 @@ +import { Redis } from 'ioredis' +import { CreateRedisOptions, createRedis } from '../lib/redis.js' +import { Code } from './code.js' +import { RequestData } from './request-data.js' +import { RequestId, requestIdSchema } from './request-id.js' +import { RequestStore } from './request-store.js' + +export type { Redis, CreateRedisOptions } + +export type ReplayStoreRedisOptions = { + redis: CreateRedisOptions +} + +export class RequestStoreRedis implements RequestStore { + private readonly redis: Redis + + constructor(options: ReplayStoreRedisOptions) { + this.redis = createRedis(options.redis) + } + + async readRequest(id: RequestId): Promise { + const data = await this.redis.get(id) + return data ? JSON.parse(data) : null + } + + async createRequest(id: RequestId, data: RequestData): Promise { + const timeFrame = data.expiresAt.getTime() - Date.now() + await this.redis.set(id, JSON.stringify(data), 'PX', timeFrame) + if (data.code) await this.redis.set(data.code, id, 'PX', timeFrame) + } + + async updateRequest( + id: RequestId, + data: Partial, + ): Promise { + const current = await this.readRequest(id) + if (!current) throw new Error('Request not found') + if (current.code) await this.redis.del(current.code) + const newData = { ...current, ...data } + await this.createRequest(id, newData) + } + + async deleteRequest(id: RequestId): Promise { + const data = await this.readRequest(id) + if (!data) return + if (data.code) await this.redis.del(data.code) + await this.redis.del(id) + } + + private async findRequestIdByCode(code: Code): Promise { + const value = await this.redis.get(code) + if (!value) return null + + const parsed = requestIdSchema.safeParse(value) + if (!parsed.success) return null + + return parsed.data + } + + async findRequestByCode( + code: Code, + ): Promise<{ id: RequestId; data: RequestData } | null> { + const id = await this.findRequestIdByCode(code) + if (!id) return null + + const data = await this.readRequest(id) + if (!data) return null + + return { id, data } + } +} diff --git a/packages/oauth/oauth-provider/src/request/request-store.ts b/packages/oauth/oauth-provider/src/request/request-store.ts new file mode 100644 index 00000000000..530f2e19c70 --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-store.ts @@ -0,0 +1,50 @@ +import { Awaitable } from '../lib/util/type.js' +import { Code } from './code.js' +import { RequestData } from './request-data.js' +import { RequestId } from './request-id.js' + +// Export all types needed to implement the RequestStore interface +export type { Awaitable, Code, RequestData, RequestId } +export type UpdateRequestData = Pick< + Partial, + 'sub' | 'code' | 'deviceId' | 'expiresAt' +> + +export type FoundRequestResult = { + id: RequestId + data: RequestData +} + +export interface RequestStore { + createRequest(id: RequestId, data: RequestData): Awaitable + /** + * Note that expired requests **can** be returned to yield a different error + * message than if the request was not found. + */ + readRequest(id: RequestId): Awaitable + updateRequest(id: RequestId, data: UpdateRequestData): Awaitable + deleteRequest(id: RequestId): void | Awaitable + findRequestByCode(code: Code): Awaitable +} + +export function isRequestStore( + implementation: Record & Partial, +): implementation is Record & RequestStore { + return ( + typeof implementation.createRequest === 'function' && + typeof implementation.readRequest === 'function' && + typeof implementation.updateRequest === 'function' && + typeof implementation.deleteRequest === 'function' && + typeof implementation.findRequestByCode === 'function' + ) +} + +export function ifRequestStore( + implementation?: Record & Partial, +): RequestStore | undefined { + if (implementation && isRequestStore(implementation)) { + return implementation + } + + return undefined +} diff --git a/packages/oauth/oauth-provider/src/request/request-uri.ts b/packages/oauth/oauth-provider/src/request/request-uri.ts new file mode 100644 index 00000000000..18f69548038 --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/request-uri.ts @@ -0,0 +1,29 @@ +import { z } from 'zod' + +import { RequestId, requestIdSchema } from './request-id.js' + +export const REQUEST_URI_PREFIX = 'urn:ietf:params:oauth:request_uri:' + +export const requestUriSchema = z + .string() + .url() + .refinement( + (data): data is `${typeof REQUEST_URI_PREFIX}${RequestId}` => + data.startsWith(REQUEST_URI_PREFIX) && + requestIdSchema.safeParse(decodeRequestUri(data as any)).success, + { + code: z.ZodIssueCode.custom, + message: 'Invalid request_uri format', + }, + ) + +export type RequestUri = z.infer + +export function encodeRequestUri(requestId: RequestId): RequestUri { + return `${REQUEST_URI_PREFIX}${encodeURIComponent(requestId) as RequestId}` +} + +export function decodeRequestUri(requestUri: RequestUri): RequestId { + const requestIdEnc = requestUri.slice(REQUEST_URI_PREFIX.length) + return decodeURIComponent(requestIdEnc) as RequestId +} diff --git a/packages/oauth/oauth-provider/src/request/types.ts b/packages/oauth/oauth-provider/src/request/types.ts new file mode 100644 index 00000000000..35a84938c79 --- /dev/null +++ b/packages/oauth/oauth-provider/src/request/types.ts @@ -0,0 +1,49 @@ +import { jwtSchema } from '@atproto/jwk' +import { + oauthAuthenticationRequestParametersSchema, + oauthClientIdentificationSchema, +} from '@atproto/oauth-types' +import { z } from 'zod' + +import { requestUriSchema } from './request-uri.js' + +export const authorizationRequestJarSchema = z.object({ + /** + * AuthorizationRequest inside a JWT: + * - "iat" is required and **MUST** be less than one minute + * + * @see {@link https://datatracker.ietf.org/doc/html/rfc9101} + * @see {AuthorizationServer.getAuthorizationRequest} + */ + request: jwtSchema, +}) + +export type AuthorizationRequestJar = z.infer< + typeof authorizationRequestJarSchema +> + +export const pushedAuthorizationRequestSchema = z.intersection( + oauthClientIdentificationSchema, + z.union([ + oauthAuthenticationRequestParametersSchema, + authorizationRequestJarSchema, + // + ]), +) + +export type PushedAuthorizationRequest = z.infer< + typeof pushedAuthorizationRequestSchema +> + +export const authorizationRequestQuerySchema = z.intersection( + oauthClientIdentificationSchema, + z.union([ + oauthAuthenticationRequestParametersSchema, + authorizationRequestJarSchema, + z.object({ request_uri: requestUriSchema }), + ]), +) + +export type AuthorizationRequestQuery = z.infer< + typeof authorizationRequestQuerySchema +> diff --git a/packages/oauth/oauth-provider/src/signer/signed-token-payload.ts b/packages/oauth/oauth-provider/src/signer/signed-token-payload.ts new file mode 100644 index 00000000000..5ef123dd42c --- /dev/null +++ b/packages/oauth/oauth-provider/src/signer/signed-token-payload.ts @@ -0,0 +1,35 @@ +import { jwtPayloadSchema } from '@atproto/jwk' +import { oauthClientIdSchema } from '@atproto/oauth-types' +import z from 'zod' + +import { subSchema } from '../oidc/sub.js' +import { tokenIdSchema } from '../token/token-id.js' +import { Simplify } from '../lib/util/type.js' + +export const signedTokenPayloadSchema = z.intersection( + jwtPayloadSchema + .pick({ + exp: true, + iat: true, + iss: true, + aud: true, + }) + .required(), + jwtPayloadSchema + .omit({ + exp: true, + iat: true, + iss: true, + aud: true, + }) + .partial() + .extend({ + jti: tokenIdSchema, + sub: subSchema, + client_id: oauthClientIdSchema, + }), +) + +export type SignedTokenPayload = Simplify< + z.infer +> diff --git a/packages/oauth/oauth-provider/src/signer/signer.ts b/packages/oauth/oauth-provider/src/signer/signer.ts new file mode 100644 index 00000000000..dc30a8cab4b --- /dev/null +++ b/packages/oauth/oauth-provider/src/signer/signer.ts @@ -0,0 +1,172 @@ +import { randomBytes } from 'node:crypto' + +import { + Jwt, + JwtPayload, + JwtPayloadGetter, + JwtSignHeader, + Keyset, + VerifyOptions, +} from '@atproto/jwk' +import { + OAuthAuthenticationRequestParameters, + OAuthAuthorizationDetails, +} from '@atproto/oauth-types' +import { generate as hash } from 'oidc-token-hash' + +import { Account } from '../account/account.js' +import { Client } from '../client/client.js' +import { InvalidClientMetadataError } from '../errors/invalid-client-metadata-error.js' +import { InvalidRequestError } from '../errors/invalid-request-error.js' +import { dateToEpoch } from '../lib/util/date.js' +import { claimRequested } from '../parameters/claims-requested.js' +import { oidcPayload } from '../parameters/oidc-payload.js' +import { TokenId } from '../token/token-id.js' +import { + SignedTokenPayload, + signedTokenPayloadSchema, +} from './signed-token-payload.js' + +export type SignPayload = Omit + +export class Signer { + constructor( + public readonly issuer: string, + public readonly keyset: Keyset, + ) {} + + async verify

= JwtPayload>( + token: Jwt, + options?: VerifyOptions, + ) { + return this.keyset.verifyJwt

(token, { + ...options, + issuer: [this.issuer], + }) + } + + public async sign( + signHeader: JwtSignHeader, + payload: SignPayload | JwtPayloadGetter, + ): Promise { + return this.keyset.createJwt(signHeader, async (protectedHeader, key) => ({ + ...(typeof payload === 'function' + ? await payload(protectedHeader, key) + : payload), + iss: this.issuer, + })) + } + + async accessToken( + client: Client, + parameters: OAuthAuthenticationRequestParameters, + account: Account, + extra: { + jti: TokenId + exp: Date + iat?: Date + alg?: string + cnf?: Record + authorization_details?: OAuthAuthorizationDetails + }, + ): Promise { + const header: JwtSignHeader = { + // https://datatracker.ietf.org/doc/html/rfc9068#section-2.1 + alg: extra.alg, + typ: 'at+jwt', + } + + const payload: Omit = { + aud: account.aud, + iat: dateToEpoch(extra?.iat), + exp: dateToEpoch(extra.exp), + sub: account.sub, + jti: extra.jti, + cnf: extra.cnf, + // https://datatracker.ietf.org/doc/html/rfc8693#section-4.3 + client_id: client.id, + scope: parameters.scope || client.metadata.scope, + authorization_details: extra.authorization_details, + } + + return this.sign(header, payload) + } + + async verifyAccessToken(token: Jwt) { + const result = await this.verify(token, { + typ: 'at+jwt', + }) + + // The result is already type casted as an AccessTokenPayload, but we need + // to actually verify this. That should already be covered by the fact that + // we don't sign 'at+jwt' tokens without a valid token ID. Let's double + // check in case another version/implementation was used to generate the + // token. + signedTokenPayloadSchema.parse(result.payload) + + return result + } + + async idToken( + client: Client, + params: OAuthAuthenticationRequestParameters, + account: Account, + extra: { + exp: Date + iat?: Date + auth_time?: Date + code?: string + access_token?: string + }, + ): Promise { + // Should be enforced by the request manager. Let's be sure. + // https://openid.net/specs/openid-connect-core-1_0.html#HybridAuthRequest + if (!params.nonce) { + throw new InvalidRequestError('Missing required "nonce" parameter') + } + + // This can happen when a client is using password_grant. If a client is + // using password_grant, it should not set "require_auth_time" to true. + if (client.metadata.require_auth_time && extra.auth_time == null) { + throw new InvalidClientMetadataError( + '"require_auth_time" metadata is not compatible with "password_grant" flow', + ) + } + + return this.sign( + { + alg: client.metadata.id_token_signed_response_alg, + typ: 'JWT', + }, + async ({ alg }, key) => ({ + ...oidcPayload(params, account), + + aud: client.id, + iat: dateToEpoch(extra.iat), + exp: dateToEpoch(extra.exp), + sub: account.sub, + jti: randomBytes(16).toString('hex'), + scope: params.scope, + nonce: params.nonce, + + s_hash: params.state // + ? await hash(params.state, alg, key.crv) + : undefined, + c_hash: extra.code // + ? await hash(extra.code, alg, key.crv) + : undefined, + at_hash: extra.access_token // + ? await hash(extra.access_token, alg, key.crv) + : undefined, + + // https://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html#rfc.section.5.2 + auth_time: + client.metadata.require_auth_time || + (extra.auth_time != null && params.max_age != null) || + claimRequested(params, 'id_token', 'auth_time', extra.auth_time) + ? dateToEpoch(extra.auth_time!) + : undefined, + }), + ) + } +} diff --git a/packages/oauth/oauth-provider/src/token/refresh-token.ts b/packages/oauth/oauth-provider/src/token/refresh-token.ts new file mode 100644 index 00000000000..7ef3d0b66a7 --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/refresh-token.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +import { + REFRESH_TOKEN_BYTES_LENGTH, + REFRESH_TOKEN_PREFIX, +} from '../constants.js' +import { randomHexId } from '../lib/util/crypto.js' + +export const refreshTokenSchema = z + .string() + .length( + REFRESH_TOKEN_PREFIX.length + REFRESH_TOKEN_BYTES_LENGTH * 2, // hex encoding + ) + .refine( + (v): v is `${typeof REFRESH_TOKEN_PREFIX}${string}` => + v.startsWith(REFRESH_TOKEN_PREFIX), + { + message: `Invalid refresh token format`, + }, + ) + +export const isRefreshToken = (data: unknown): data is RefreshToken => + refreshTokenSchema.safeParse(data).success + +export type RefreshToken = z.infer +export const generateRefreshToken = async (): Promise => { + return `${REFRESH_TOKEN_PREFIX}${await randomHexId( + REFRESH_TOKEN_BYTES_LENGTH, + )}` +} diff --git a/packages/oauth/oauth-provider/src/token/token-claims.ts b/packages/oauth/oauth-provider/src/token/token-claims.ts new file mode 100644 index 00000000000..d6059d2d51b --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/token-claims.ts @@ -0,0 +1,30 @@ +import { jwtPayloadSchema } from '@atproto/jwk' +import { oauthClientIdSchema } from '@atproto/oauth-types' +import z from 'zod' + +import { subSchema } from '../oidc/sub.js' +import { Simplify } from '../lib/util/type.js' + +export const tokenClaimsSchema = z.intersection( + jwtPayloadSchema + .pick({ + exp: true, + aud: true, + }) + .required(), + jwtPayloadSchema + .omit({ + exp: true, + iat: true, + iss: true, + aud: true, + jti: true, + }) + .partial() + .extend({ + sub: subSchema, + client_id: oauthClientIdSchema, + }), +) + +export type TokenClaims = Simplify> diff --git a/packages/oauth/oauth-provider/src/token/token-data.ts b/packages/oauth/oauth-provider/src/token/token-data.ts new file mode 100644 index 00000000000..62ca7ff2a02 --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/token-data.ts @@ -0,0 +1,33 @@ +import { + OAuthAuthenticationRequestParameters, + OAuthAuthorizationDetails, + OAuthClientId, +} from '@atproto/oauth-types' + +import { ClientAuth } from '../client/client-auth.js' +import { DeviceId } from '../device/device-id.js' +import { Sub } from '../oidc/sub.js' +import { Code } from '../request/code.js' + +export type { + ClientAuth, + Code, + DeviceId, + OAuthAuthenticationRequestParameters, + OAuthAuthorizationDetails, + OAuthClientId, + Sub, +} + +export type TokenData = { + createdAt: Date + updatedAt: Date + expiresAt: Date + clientId: OAuthClientId + clientAuth: ClientAuth + deviceId: DeviceId | null + sub: Sub + parameters: OAuthAuthenticationRequestParameters + details: OAuthAuthorizationDetails | null + code: Code | null +} diff --git a/packages/oauth/oauth-provider/src/token/token-hooks.ts b/packages/oauth/oauth-provider/src/token/token-hooks.ts new file mode 100644 index 00000000000..6cfd210ad83 --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/token-hooks.ts @@ -0,0 +1,41 @@ +import { + OAuthAuthenticationRequestParameters, + OAuthAuthorizationDetails, +} from '@atproto/oauth-types' +import { Account } from '../account/account.js' +import { Client } from '../client/client.js' +import { Awaitable } from '../lib/util/type.js' +import { TokenResponse } from './token-response.js' + +export type { TokenResponse } + +export type TokenHookData = { + client: Client + parameters: OAuthAuthenticationRequestParameters + account: Account +} + +/** + * Allows enriching the authorization details with additional information + * before the tokens are issued. + * + * @see {@link https://datatracker.ietf.org/doc/html/rfc9396 | RFC 9396} + */ +export type AuthorizationDetailsHook = ( + this: null, + data: TokenHookData, +) => Awaitable + +/** + * Allows altering the token response before it is sent to the client. + */ +export type TokenResponseHook = ( + this: null, + tokenResponse: TokenResponse, + data: TokenHookData, +) => Awaitable + +export type TokenHooks = { + onAuthorizationDetails?: AuthorizationDetailsHook + onTokenResponse?: TokenResponseHook +} diff --git a/packages/oauth/oauth-provider/src/token/token-id.ts b/packages/oauth/oauth-provider/src/token/token-id.ts new file mode 100644 index 00000000000..ee22bc657d9 --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/token-id.ts @@ -0,0 +1,25 @@ +import { z } from 'zod' + +import { TOKEN_ID_BYTES_LENGTH, TOKEN_ID_PREFIX } from '../constants.js' +import { randomHexId } from '../lib/util/crypto.js' + +export const tokenIdSchema = z + .string() + .length( + TOKEN_ID_PREFIX.length + TOKEN_ID_BYTES_LENGTH * 2, // hex encoding + ) + .refine( + (v): v is `${typeof TOKEN_ID_PREFIX}${string}` => + v.startsWith(TOKEN_ID_PREFIX), + { + message: `Invalid token ID format`, + }, + ) + +export type TokenId = z.infer +export const generateTokenId = async (): Promise => { + return `${TOKEN_ID_PREFIX}${await randomHexId(TOKEN_ID_BYTES_LENGTH)}` +} + +export const isTokenId = (data: unknown): data is TokenId => + tokenIdSchema.safeParse(data).success diff --git a/packages/oauth/oauth-provider/src/token/token-manager.ts b/packages/oauth/oauth-provider/src/token/token-manager.ts new file mode 100644 index 00000000000..6094d6023ea --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/token-manager.ts @@ -0,0 +1,567 @@ +import { isJwt } from '@atproto/jwk' +import { + AccessToken, + CLIENT_ASSERTION_TYPE_JWT_BEARER, + OAuthAuthenticationRequestParameters, + OAuthTokenType, +} from '@atproto/oauth-types' +import { createHash } from 'node:crypto' + +import { AccessTokenType } from '../access-token/access-token-type.js' +import { DeviceAccountInfo } from '../account/account-store.js' +import { Account } from '../account/account.js' +import { ClientAuth } from '../client/client-auth.js' +import { Client } from '../client/client.js' +import { + AUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT, + TOKEN_MAX_AGE, + TOTAL_REFRESH_LIFETIME, + UNAUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT, +} from '../constants.js' +import { DeviceId } from '../device/device-id.js' +import { InvalidDpopKeyBindingError } from '../errors/invalid-dpop-key-binding.js' +import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js' +import { InvalidGrantError } from '../errors/invalid-grant-error.js' +import { InvalidRequestError } from '../errors/invalid-request-error.js' +import { InvalidTokenError } from '../errors/invalid-token-error.js' +import { dateToEpoch, dateToRelativeSeconds } from '../lib/util/date.js' +import { compareRedirectUri } from '../lib/util/redirect-uri.js' +import { isCode } from '../request/code.js' +import { Signer } from '../signer/signer.js' +import { generateRefreshToken, isRefreshToken } from './refresh-token.js' +import { TokenClaims } from './token-claims.js' +import { TokenData } from './token-data.js' +import { TokenHooks } from './token-hooks.js' +import { + TokenId, + generateTokenId, + isTokenId, + tokenIdSchema, +} from './token-id.js' +import { TokenResponse } from './token-response.js' +import { TokenInfo, TokenStore } from './token-store.js' +import { CodeGrantRequest, RefreshGrantRequest } from './types.js' +import { + VerifyTokenClaimsOptions, + VerifyTokenClaimsResult, + verifyTokenClaims, +} from './verify-token-claims.js' + +export type AuthenticateTokenIdResult = VerifyTokenClaimsResult & { + tokenInfo: TokenInfo +} + +export class TokenManager { + constructor( + protected readonly store: TokenStore, + protected readonly signer: Signer, + protected readonly hooks: TokenHooks, + protected readonly accessTokenType: AccessTokenType, + protected readonly tokenMaxAge = TOKEN_MAX_AGE, + ) {} + + protected createTokenExpiry(now = new Date()) { + return new Date(now.getTime() + this.tokenMaxAge) + } + + protected useJwtAccessToken(account: Account) { + if (this.accessTokenType === AccessTokenType.auto) { + return this.signer.issuer !== account.aud + } + + return this.accessTokenType === AccessTokenType.jwt + } + + async create( + client: Client, + clientAuth: ClientAuth, + account: Account, + device: null | { id: DeviceId; info: DeviceAccountInfo }, + parameters: OAuthAuthenticationRequestParameters, + input: CodeGrantRequest, + dpopJkt: null | string, + ): Promise { + if (client.metadata.dpop_bound_access_tokens && !dpopJkt) { + throw new InvalidDpopProofError('DPoP proof required') + } + + if (!parameters.dpop_jkt) { + if (dpopJkt) parameters = { ...parameters, dpop_jkt: dpopJkt } + } else if (!dpopJkt) { + throw new InvalidDpopProofError('DPoP proof required') + } else if (parameters.dpop_jkt !== dpopJkt) { + throw new InvalidDpopKeyBindingError() + } + + if (clientAuth.method === CLIENT_ASSERTION_TYPE_JWT_BEARER) { + // Clients **must not** use their private key to sign DPoP proofs. + if (parameters.dpop_jkt && clientAuth.jkt === parameters.dpop_jkt) { + throw new InvalidRequestError( + 'The DPoP proof must be signed with a different key than the client assertion', + ) + } + } + + if (!client.metadata.grant_types.includes(input.grant_type)) { + throw new InvalidGrantError( + `This client is not allowed to use the "${input.grant_type}" grant type`, + ) + } + + switch (input.grant_type) { + case 'authorization_code': + if (!parameters.code_challenge || !parameters.code_challenge_method) { + throw new InvalidGrantError('PKCE is required') + } + + if (!parameters.redirect_uri) { + const redirect_uri = client.metadata.redirect_uris.find((uri) => + compareRedirectUri(uri, input.redirect_uri), + ) + if (redirect_uri) { + parameters = { ...parameters, redirect_uri } + } else { + throw new InvalidGrantError(`Invalid redirect_uri`) + } + } else if (parameters.redirect_uri !== input.redirect_uri) { + throw new InvalidGrantError( + 'This code was issued for another redirect_uri', + ) + } + + break + + default: + throw new Error(`Unsupported grant type "${input.grant_type}"`) + } + + if (parameters.code_challenge) { + if (!('code_verifier' in input) || !input.code_verifier) { + throw new InvalidGrantError('code_verifier is required') + } + switch (parameters.code_challenge_method) { + case undefined: // Default is "plain" (per spec) + case 'plain': { + if (parameters.code_challenge !== input.code_verifier) { + throw new InvalidGrantError('Invalid code_verifier') + } + break + } + case 'S256': { + // Because the code_challenge is base64url-encoded, we will decode + // it in order to compare based on bytes. + const inputChallenge = Buffer.from( + parameters.code_challenge, + 'base64', + ) + const computedChallenge = createHash('sha256') + .update(input.code_verifier) + .digest() + if (inputChallenge.compare(computedChallenge) !== 0) { + throw new InvalidGrantError('Invalid code_verifier') + } + break + } + default: { + throw new InvalidRequestError( + `Unsupported code_challenge_method ${parameters.code_challenge_method}`, + ) + } + } + } + + const code = 'code' in input ? input.code : undefined + if (code) { + const tokenInfo = await this.store.findTokenByCode(code) + if (tokenInfo) { + await this.store.deleteToken(tokenInfo.id) + throw new InvalidGrantError(`Code replayed`) + } + } + + const tokenId = await generateTokenId() + const scopes = parameters.scope?.split(' ') + const refreshToken = scopes?.includes('offline_access') + ? await generateRefreshToken() + : undefined + + const now = new Date() + const expiresAt = this.createTokenExpiry(now) + + const authorizationDetails = await this.hooks.onAuthorizationDetails?.call( + null, + { client, parameters, account }, + ) + + const tokenData: TokenData = { + createdAt: now, + updatedAt: now, + expiresAt, + clientId: client.id, + clientAuth, + deviceId: device?.id ?? null, + sub: account.sub, + parameters, + details: authorizationDetails ?? null, + code: code ?? null, + } + + await this.store.createToken(tokenId, tokenData, refreshToken) + + const accessToken: AccessToken = !this.useJwtAccessToken(account) + ? tokenId + : await this.signer.accessToken(client, parameters, account, { + // We don't specify the alg here. We suppose the Resource server will be + // able to verify the token using any alg. + alg: undefined, + exp: expiresAt, + iat: now, + jti: tokenId, + cnf: parameters.dpop_jkt ? { jkt: parameters.dpop_jkt } : undefined, + authorization_details: authorizationDetails, + }) + + const responseTypes = parameters.response_type.split(' ') + const idToken = responseTypes.includes('id_token') + ? await this.signer.idToken(client, parameters, account, { + exp: expiresAt, + iat: now, + // If there is no deviceInfo, we are in a "password_grant" context + auth_time: device?.info.authenticatedAt || new Date(), + access_token: accessToken, + code, + }) + : undefined + + const tokenResponse: TokenResponse = { + access_token: accessToken, + token_type: parameters.dpop_jkt ? 'DPoP' : 'Bearer', + refresh_token: refreshToken, + id_token: idToken, + scope: parameters.scope ?? '', + authorization_details: authorizationDetails, + get expires_in() { + return dateToRelativeSeconds(expiresAt) + }, + + // ATPROTO extension: add the sub claim to the token response to allow + // clients to resolve the PDS url (audience) using the did resolution + // mechanism. + sub: account.sub, + } + + await this.hooks.onTokenResponse?.call(null, tokenResponse, { + client, + parameters, + account, + }) + + return tokenResponse + } + + protected async validateAccess( + client: Client, + clientAuth: ClientAuth, + tokenInfo: TokenInfo, + ) { + if (tokenInfo.data.clientId !== client.id) { + throw new InvalidGrantError(`Token was not issued to this client`) + } + + if (tokenInfo.info?.authorizedClients.includes(client.id) === false) { + throw new InvalidGrantError(`Client no longer trusted by user`) + } + + if (tokenInfo.data.clientAuth.method !== clientAuth.method) { + throw new InvalidGrantError(`Client authentication method mismatch`) + } + + if (!(await client.validateClientAuth(tokenInfo.data.clientAuth))) { + throw new InvalidGrantError(`Client authentication mismatch`) + } + } + + async refresh( + client: Client, + clientAuth: ClientAuth, + input: RefreshGrantRequest, + dpopJkt: null | string, + ): Promise { + const tokenInfo = await this.store.findTokenByRefreshToken( + input.refresh_token, + ) + if (!tokenInfo?.currentRefreshToken) { + throw new InvalidGrantError(`Invalid refresh token`) + } + + const { account, info, data } = tokenInfo + const { parameters } = data + + try { + if (tokenInfo.currentRefreshToken !== input.refresh_token) { + throw new InvalidGrantError(`refresh token replayed`) + } + + await this.validateAccess(client, clientAuth, tokenInfo) + + if (parameters.dpop_jkt) { + if (!dpopJkt) { + throw new InvalidDpopProofError('DPoP proof required') + } else if (parameters.dpop_jkt !== dpopJkt) { + throw new InvalidDpopKeyBindingError() + } + } + + const lastActivity = data.updatedAt + const inactivityTimeout = + clientAuth.method === 'none' + ? UNAUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT + : AUTHENTICATED_REFRESH_INACTIVITY_TIMEOUT + if (lastActivity.getTime() + inactivityTimeout < Date.now()) { + throw new InvalidGrantError(`Refresh token exceeded inactivity timeout`) + } + + if (data.createdAt.getTime() + TOTAL_REFRESH_LIFETIME < Date.now()) { + throw new InvalidGrantError(`Refresh token expired`) + } + + const authorization_details = + await this.hooks.onAuthorizationDetails?.call(null, { + client, + parameters, + account, + }) + + const nextTokenId = await generateTokenId() + const nextRefreshToken = await generateRefreshToken() + + const now = new Date() + const expiresAt = this.createTokenExpiry(now) + + await this.store.rotateToken( + tokenInfo.id, + nextTokenId, + nextRefreshToken, + { + updatedAt: now, + expiresAt, + // When clients rotate their public keys, we store the key that was + // used by the client to authenticate itself while requesting new + // tokens. The validateAccess() method will ensure that the client + // still advertises the key that was used to issue the previous + // refresh token. If a client stops advertising a key, all tokens + // bound to that key will no longer be be refreshable. This allows + // clients to proactively invalidate tokens when a key is compromised. + // Note that the original DPoP key cannot be rotated. This protects + // users in case the ownership of the client id changes. In the latter + // case, a malicious actor could still advertises the public keys of + // the previous owner, but the new owner would not be able to present + // a valid DPoP proof. + clientAuth, + }, + ) + + const accessToken: AccessToken = !this.useJwtAccessToken(account) + ? nextTokenId + : await this.signer.accessToken(client, parameters, account, { + // We don't specify the alg here. We suppose the Resource server will be + // able to verify the token using any alg. + alg: undefined, + exp: expiresAt, + iat: now, + jti: nextTokenId, + cnf: parameters.dpop_jkt ? { jkt: parameters.dpop_jkt } : undefined, + authorization_details, + }) + + const responseTypes = parameters.response_type.split(' ') + const idToken = responseTypes.includes('id_token') + ? await this.signer.idToken(client, parameters, account, { + exp: expiresAt, + iat: now, + auth_time: info?.authenticatedAt, + access_token: accessToken, + }) + : undefined + + const tokenResponse: TokenResponse = { + id_token: idToken, + access_token: accessToken, + token_type: parameters.dpop_jkt ? 'DPoP' : 'Bearer', + refresh_token: nextRefreshToken, + scope: parameters.scope ?? '', + authorization_details, + get expires_in() { + return dateToRelativeSeconds(expiresAt) + }, + } + + await this.hooks.onTokenResponse?.call(null, tokenResponse, { + client, + parameters, + account, + }) + + return tokenResponse + } catch (err) { + if (err instanceof InvalidRequestError) { + // Consider the refresh token might be compromised if sanity checks + // failed. + await this.store.deleteToken(tokenInfo.id) + } + throw err + } + } + + /** + * @see {@link https://datatracker.ietf.org/doc/html/rfc7009#section-2.2 | RFC7009 Section 2.2} + */ + async revoke(token: string): Promise { + switch (true) { + case isTokenId(token): { + await this.store.deleteToken(token) + return + } + + case isJwt(token): { + const { payload } = await this.signer.verify(token, { + clockTolerance: Infinity, + }) + const tokenId = tokenIdSchema.parse(payload.jti) + await this.store.deleteToken(tokenId) + return + } + + case isRefreshToken(token): { + const tokenInfo = await this.store.findTokenByRefreshToken(token) + if (tokenInfo) await this.store.deleteToken(tokenInfo.id) + return + } + + case isCode(token): { + const tokenInfo = await this.store.findTokenByCode(token) + if (tokenInfo) await this.store.deleteToken(tokenInfo.id) + return + } + + default: + // No error should be returned if the token is not valid + return + } + } + + /** + * Allows an (authenticated) client to obtain information about a token. + * + * @see {@link https://datatracker.ietf.org/doc/html/rfc7662 RFC7662} + */ + async clientTokenInfo( + client: Client, + clientAuth: ClientAuth, + token: string, + ): Promise { + const tokenInfo = await this.findTokenInfo(token) + if (!tokenInfo) { + throw new InvalidGrantError(`Invalid token`) + } + + try { + await this.validateAccess(client, clientAuth, tokenInfo) + } catch (err) { + await this.store.deleteToken(tokenInfo.id) + throw err + } + + if (tokenInfo.data.expiresAt.getTime() < Date.now()) { + throw new InvalidGrantError(`Token expired`) + } + + return tokenInfo + } + + protected async findTokenInfo(token: string): Promise { + switch (true) { + case isTokenId(token): + return this.store.readToken(token) + + case isJwt(token): { + const { payload } = await this.signer + .verifyAccessToken(token) + .catch((_) => ({ payload: null })) + if (!payload) return null + + const tokenInfo = await this.store.readToken(payload.jti) + if (!tokenInfo) return null + + // Audience changed (e.g. user was moved to another resource server) + if (payload.aud !== tokenInfo.account.aud) { + return null + } + + // Invalid store implementation ? + if (payload.sub !== tokenInfo.account.sub) { + throw new Error( + `Account sub (${tokenInfo.account.sub}) does not match token sub (${payload.sub})`, + ) + } + + return tokenInfo + } + + case isRefreshToken(token): { + const tokenInfo = await this.store.findTokenByRefreshToken(token) + if (!tokenInfo?.currentRefreshToken) return null + if (tokenInfo.currentRefreshToken !== token) return null + return tokenInfo + } + + default: + // Should never happen + return null + } + } + + async getTokenInfo(tokenType: OAuthTokenType, tokenId: TokenId) { + const tokenInfo = await this.store.readToken(tokenId) + + if (!tokenInfo) { + throw new InvalidTokenError(tokenType, `Invalid token`) + } + + if (!(tokenInfo.data.expiresAt.getTime() > Date.now())) { + throw new InvalidTokenError(tokenType, `Token expired`) + } + + return tokenInfo + } + + async authenticateTokenId( + tokenType: OAuthTokenType, + token: TokenId, + dpopJkt: string | null, + verifyOptions?: VerifyTokenClaimsOptions, + ): Promise { + const tokenInfo = await this.getTokenInfo(tokenType, token) + const { parameters } = tokenInfo.data + + const claims: TokenClaims = { + aud: tokenInfo.account.aud, + sub: tokenInfo.account.sub, + exp: dateToEpoch(tokenInfo.data.expiresAt), + scope: tokenInfo.data.parameters.scope, + client_id: tokenInfo.data.clientId, + cnf: parameters.dpop_jkt ? { jkt: parameters.dpop_jkt } : undefined, + } + + const result = verifyTokenClaims( + token, + token, + tokenType, + dpopJkt, + claims, + verifyOptions, + ) + + return { ...result, tokenInfo } + } +} diff --git a/packages/oauth/oauth-provider/src/token/token-response.ts b/packages/oauth/oauth-provider/src/token/token-response.ts new file mode 100644 index 00000000000..3900b8477d9 --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/token-response.ts @@ -0,0 +1,24 @@ +import { + AccessToken, + OAuthAuthorizationDetails, + OAuthTokenType, +} from '@atproto/oauth-types' + +import { RefreshToken } from './refresh-token.js' + +/** + * @see {@link https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1 | RFC 6749 (OAuth2), Section 5.1} + */ +export type TokenResponse = { + id_token?: string + access_token?: AccessToken + token_type?: OAuthTokenType + expires_in?: number + refresh_token?: RefreshToken + scope: string + authorization_details?: OAuthAuthorizationDetails + + // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1 + // > The client MUST ignore unrecognized value names in the response. + [k: string]: unknown +} diff --git a/packages/oauth/oauth-provider/src/token/token-store.ts b/packages/oauth/oauth-provider/src/token/token-store.ts new file mode 100644 index 00000000000..e77e55e0ada --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/token-store.ts @@ -0,0 +1,76 @@ +import { DeviceAccountInfo } from '../account/account-store.js' +import { Account } from '../account/account.js' +import { Awaitable } from '../lib/util/type.js' +import { Code } from '../request/code.js' +import { RefreshToken } from './refresh-token.js' +import { TokenData } from './token-data.js' +import { TokenId } from './token-id.js' + +// Export all types needed to implement the TokenStore interface +export type * from './token-data.js' +export type { Awaitable, RefreshToken, TokenData, TokenId } + +export type TokenInfo = { + id: TokenId + data: TokenData + account: Account + info?: DeviceAccountInfo + currentRefreshToken: null | RefreshToken +} + +export type NewTokenData = Pick< + TokenData, + 'clientAuth' | 'expiresAt' | 'updatedAt' +> + +export interface TokenStore { + createToken( + tokenId: TokenId, + data: TokenData, + refreshToken?: RefreshToken, + ): Awaitable + + readToken(tokenId: TokenId): Awaitable + + deleteToken(tokenId: TokenId): Awaitable + + rotateToken( + tokenId: TokenId, + newTokenId: TokenId, + newRefreshToken: RefreshToken, + newData: NewTokenData, + ): Awaitable + + /** + * Find a token by its refresh token. Note that previous refresh tokens + * should also return the token. The data model is reponsible for storing + * old refresh tokens when a new one is issued. + */ + findTokenByRefreshToken( + refreshToken: RefreshToken, + ): Awaitable + + findTokenByCode(code: Code): Awaitable +} + +export function isTokenStore( + implementation: Record & Partial, +): implementation is Record & TokenStore { + return ( + typeof implementation.createToken === 'function' && + typeof implementation.readToken === 'function' && + typeof implementation.rotateToken === 'function' && + typeof implementation.deleteToken === 'function' && + typeof implementation.findTokenByCode === 'function' && + typeof implementation.findTokenByRefreshToken === 'function' + ) +} + +export function asTokenStore( + implementation?: Record & Partial, +): TokenStore { + if (!implementation || !isTokenStore(implementation)) { + throw new Error('Invalid TokenStore implementation') + } + return implementation +} diff --git a/packages/oauth/oauth-provider/src/token/types.ts b/packages/oauth/oauth-provider/src/token/types.ts new file mode 100644 index 00000000000..4fa4eeafb40 --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/types.ts @@ -0,0 +1,86 @@ +import { + OAuthAuthorizationDetails, + OAuthTokenType, + accessTokenSchema, + oauthClientIdSchema, + oauthClientIdentificationSchema, +} from '@atproto/oauth-types' +import { z } from 'zod' + +import { codeSchema } from '../request/code.js' +import { refreshTokenSchema } from './refresh-token.js' + +export const codeGrantRequestSchema = z.intersection( + oauthClientIdentificationSchema, + z.object({ + grant_type: z.literal('authorization_code'), + code: codeSchema, + /** @see {@link https://datatracker.ietf.org/doc/html/rfc7636#section-4.1} */ + code_verifier: z + .string() + .min(43) + .max(128) + .regex(/^[a-zA-Z0-9-._~]+$/), + redirect_uri: z.string().url(), + // request_uri ??? + }), +) + +export type CodeGrantRequest = z.infer + +export const refreshGrantRequestSchema = z.intersection( + oauthClientIdentificationSchema, + z.object({ + grant_type: z.literal('refresh_token'), + refresh_token: refreshTokenSchema, + client_id: oauthClientIdSchema, + }), +) + +export type RefreshGrantRequest = z.infer + +export const tokenRequestSchema = z.union([ + codeGrantRequestSchema, + refreshGrantRequestSchema, +]) + +export type TokenRequest = z.infer + +export const tokenIdentification = z.object({ + token: z.union([accessTokenSchema, refreshTokenSchema]), + token_type_hint: z.enum(['access_token', 'refresh_token']).optional(), +}) + +export type TokenIdentification = z.infer + +export const revokeSchema = tokenIdentification + +export type Revoke = z.infer + +export const introspectSchema = z.intersection( + oauthClientIdentificationSchema, + tokenIdentification, +) + +export type Introspect = z.infer + +// https://datatracker.ietf.org/doc/html/rfc7662#section-2.2 +export type IntrospectionResponse = + | { active: false } + | { + active: true + + scope?: string + client_id?: string + username?: string + token_type?: OAuthTokenType + authorization_details?: OAuthAuthorizationDetails + + aud?: string | [string, ...string[]] + exp?: number + iat?: number + iss?: string + jti?: string + nbf?: number + sub?: string + } diff --git a/packages/oauth/oauth-provider/src/token/verify-token-claims.ts b/packages/oauth/oauth-provider/src/token/verify-token-claims.ts new file mode 100644 index 00000000000..11f277c88cc --- /dev/null +++ b/packages/oauth/oauth-provider/src/token/verify-token-claims.ts @@ -0,0 +1,60 @@ +import { AccessToken, OAuthTokenType } from '@atproto/oauth-types' + +import { InvalidDpopKeyBindingError } from '../errors/invalid-dpop-key-binding.js' +import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js' +import { asArray } from '../lib/util/cast.js' +import { InvalidTokenError } from '../oauth-errors.js' +import { TokenClaims } from './token-claims.js' +import { TokenId } from './token-id.js' + +export type VerifyTokenClaimsOptions = { + /** One of these audience must be included in the token audience(s) */ + audience?: [string, ...string[]] + /** One of these scope must be included in the token scope(s) */ + scope?: [string, ...string[]] +} + +export type VerifyTokenClaimsResult = { + token: AccessToken + tokenId: TokenId + tokenType: OAuthTokenType + claims: TokenClaims +} + +export function verifyTokenClaims( + token: AccessToken, + tokenId: TokenId, + tokenType: OAuthTokenType, + dpopJkt: string | null, + claims: TokenClaims, + options?: VerifyTokenClaimsOptions, +): VerifyTokenClaimsResult { + const claimsJkt = claims.cnf?.jkt ?? null + + const expectedTokenType: OAuthTokenType = claimsJkt ? 'DPoP' : 'Bearer' + if (expectedTokenType !== tokenType) { + throw new InvalidTokenError(expectedTokenType, `Invalid token type`) + } + if (tokenType === 'DPoP' && !dpopJkt) { + throw new InvalidDpopProofError(`jkt is required for DPoP tokens`) + } + if (claimsJkt !== dpopJkt) { + throw new InvalidDpopKeyBindingError() + } + + if (options?.audience) { + const aud = asArray(claims.aud) + if (!options.audience.some((v) => aud.includes(v))) { + throw new InvalidTokenError(tokenType, `Invalid audience`) + } + } + + if (options?.scope) { + const scopes = claims.scope?.split(' ') + if (!scopes || !options.scope.some((v) => scopes.includes(v))) { + throw new InvalidTokenError(tokenType, `Invalid scope`) + } + } + + return { token, tokenId, tokenType, claims } +} diff --git a/packages/oauth/oauth-provider/tailwind.config.js b/packages/oauth/oauth-provider/tailwind.config.js new file mode 100644 index 00000000000..f2710384390 --- /dev/null +++ b/packages/oauth/oauth-provider/tailwind.config.js @@ -0,0 +1,13 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['src/assets/app/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: { + colors: { + primary: 'rgb(var(--color-primary) / )', + error: 'rgb(var(--color-error) / )', + }, + }, + }, + plugins: [], +} diff --git a/packages/oauth/oauth-provider/tsconfig.backend.json b/packages/oauth/oauth-provider/tsconfig.backend.json new file mode 100644 index 00000000000..c18b22589b9 --- /dev/null +++ b/packages/oauth/oauth-provider/tsconfig.backend.json @@ -0,0 +1,9 @@ +{ + "extends": ["../../../tsconfig/nodenext.json"], + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["src/assets/app"] +} diff --git a/packages/oauth/oauth-provider/tsconfig.frontend.json b/packages/oauth/oauth-provider/tsconfig.frontend.json new file mode 100644 index 00000000000..fd70315541f --- /dev/null +++ b/packages/oauth/oauth-provider/tsconfig.frontend.json @@ -0,0 +1,11 @@ +{ + "extends": [ + "../../../tsconfig/browser.json", + "../../../tsconfig/bundler.json" + ], + "compilerOptions": { + "rootDir": "src/assets/app", + "outDir": "dist/assets/app" + }, + "include": ["src/assets/app/**/*.ts", "src/assets/app/**/*.tsx"] +} diff --git a/packages/oauth/oauth-provider/tsconfig.json b/packages/oauth/oauth-provider/tsconfig.json new file mode 100644 index 00000000000..a6bf0ef639d --- /dev/null +++ b/packages/oauth/oauth-provider/tsconfig.json @@ -0,0 +1,8 @@ +{ + "include": [], + "references": [ + { "path": "./tsconfig.frontend.json" }, + { "path": "./tsconfig.backend.json" }, + { "path": "./tsconfig.tools.json" } + ] +} diff --git a/packages/oauth/oauth-provider/tsconfig.tools.json b/packages/oauth/oauth-provider/tsconfig.tools.json new file mode 100644 index 00000000000..7d601749e11 --- /dev/null +++ b/packages/oauth/oauth-provider/tsconfig.tools.json @@ -0,0 +1,8 @@ +{ + "extends": ["../../../tsconfig/nodenext.json"], + "compilerOptions": { + "rootDir": ".", + "noEmit": true + }, + "include": ["./*.js", "./*.cjs", "./*.mjs", "./*.ts"] +} diff --git a/packages/oauth/oauth-types/README.md b/packages/oauth/oauth-types/README.md new file mode 100644 index 00000000000..d7b94e35448 --- /dev/null +++ b/packages/oauth/oauth-types/README.md @@ -0,0 +1,3 @@ +# @atproto/oauth-types + +This library exposes utilities for typing and validating OAuth related data structures. diff --git a/packages/oauth/oauth-types/package.json b/packages/oauth/oauth-types/package.json new file mode 100644 index 00000000000..9648152187a --- /dev/null +++ b/packages/oauth/oauth-types/package.json @@ -0,0 +1,38 @@ +{ + "name": "@atproto/oauth-types", + "version": "0.0.1", + "license": "MIT", + "description": "OAuth typing & validation library", + "keywords": [ + "atproto", + "oauth", + "types", + "isomorphic" + ], + "homepage": "https://atproto.com", + "repository": { + "type": "git", + "url": "https://github.com/bluesky-social/atproto", + "directory": "packages/oauth/oauth-types" + }, + "type": "commonjs", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "dependencies": { + "@atproto/jwk": "workspace:*", + "tslib": "^2.6.2", + "zod": "^3.22.4" + }, + "devDependencies": { + "typescript": "^5.3.3" + }, + "scripts": { + "build": "tsc --build tsconfig.build.json" + } +} diff --git a/packages/oauth/oauth-types/src/access-token.ts b/packages/oauth/oauth-types/src/access-token.ts new file mode 100644 index 00000000000..6fffa79f3f0 --- /dev/null +++ b/packages/oauth/oauth-types/src/access-token.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const accessTokenSchema = z.string().min(1) +export type AccessToken = z.infer diff --git a/packages/oauth/oauth-types/src/constants.ts b/packages/oauth/oauth-types/src/constants.ts new file mode 100644 index 00000000000..69b1cbc64a7 --- /dev/null +++ b/packages/oauth/oauth-types/src/constants.ts @@ -0,0 +1,2 @@ +export const CLIENT_ASSERTION_TYPE_JWT_BEARER = + 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' diff --git a/packages/oauth/oauth-types/src/index.ts b/packages/oauth/oauth-types/src/index.ts new file mode 100644 index 00000000000..c6aeb079785 --- /dev/null +++ b/packages/oauth/oauth-types/src/index.ts @@ -0,0 +1,20 @@ +export * from './access-token.js' +export * from './constants.js' +export * from './oauth-authentication-request-parameters.js' +export * from './oauth-authorization-details.js' +export * from './oauth-client-credentials.js' +export * from './oauth-client-id.js' +export * from './oauth-client-identification.js' +export * from './oauth-client-metadata.js' +export * from './oauth-endpoint-auth-method.js' +export * from './oauth-endpoint-name.js' +export * from './oauth-grant-type.js' +export * from './oauth-par-response.js' +export * from './oauth-response-mode.js' +export * from './oauth-response-type.js' +export * from './oauth-server-metadata.js' +export * from './oauth-token-response.js' +export * from './oauth-token-type.js' +export * from './oidc-claims-parameter.js' +export * from './oidc-claims-properties.js' +export * from './oidc-entity-type.js' diff --git a/packages/oauth/oauth-types/src/oauth-authentication-request-parameters.ts b/packages/oauth/oauth-types/src/oauth-authentication-request-parameters.ts new file mode 100644 index 00000000000..77e6d142766 --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-authentication-request-parameters.ts @@ -0,0 +1,104 @@ +import { jwtSchema } from '@atproto/jwk' +import { z } from 'zod' + +import { oauthAuthorizationDetailsSchema } from './oauth-authorization-details.js' +import { oauthClientIdSchema } from './oauth-client-id.js' +import { oidcClaimsParameterSchema } from './oidc-claims-parameter.js' +import { oidcClaimsPropertiesSchema } from './oidc-claims-properties.js' +import { oidcEntityTypeSchema } from './oidc-entity-type.js' + +/** + * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest | OIDC} + */ +export const oauthAuthenticationRequestParametersSchema = z.object({ + client_id: oauthClientIdSchema, + + state: z.string().optional(), + nonce: z.string().optional(), + dpop_jkt: z.string().optional(), + + response_type: z.enum([ + // OAuth2 (https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-10#section-4.1.1) + 'code', + 'token', + + // OIDC (https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html) + 'id_token', + 'none', + 'code token', + 'code id_token', + 'id_token token', + 'code id_token token', + ]), + + // Default depend on response_type + response_mode: z.enum(['query', 'fragment', 'form_post']).optional(), + + // PKCE + code_challenge: z.string().optional(), + code_challenge_method: z.enum(['S256', 'plain']).default('S256').optional(), + + redirect_uri: z.string().url().optional(), + + // email profile openid (other?) + scope: z + .string() + .regex(/^[a-zA-Z0-9_]+( [a-zA-Z0-9_]+)*$/) + .optional(), + + // OIDC + + // Specifies the allowable elapsed time in seconds since the last time the + // End-User was actively authenticated by the OP. If the elapsed time is + // greater than this value, the OP MUST attempt to actively re-authenticate + // the End-User. (The max_age request parameter corresponds to the OpenID 2.0 + // PAPE [OpenID.PAPE] max_auth_age request parameter.) When max_age is used, + // the ID Token returned MUST include an auth_time Claim Value. Note that + // max_age=0 is equivalent to prompt=login. + max_age: z.number().int().min(0).optional(), + + claims: z + .record( + oidcEntityTypeSchema, + z.record( + oidcClaimsParameterSchema, + z.union([z.literal(null), oidcClaimsPropertiesSchema]), + ), + ) + .optional(), + + // https://openid.net/specs/openid-connect-core-1_0.html#RegistrationParameter + // Not supported by this library (yet?) + // registration: clientMetadataSchema.optional(), + + login_hint: z.string().min(1).optional(), + + ui_locales: z + .string() + .regex(/^[a-z]{2}(-[A-Z]{2})?( [a-z]{2}(-[A-Z]{2})?)*$/) // fr-CA fr en + .optional(), + + // Previous ID Token, should be provided when prompt=none is used + id_token_hint: jwtSchema.optional(), + + // Type of UI the AS is displayed on + display: z.enum(['page', 'popup', 'touch']).optional(), + + /** + * - "none" will only be allowed if the user already allowed the client on the same device + * - "login" will force the user to login again, unless he very recently logged in + * - "consent" will force the user to consent again + * - "select_account" will force the user to select an account + */ + prompt: z.enum(['none', 'login', 'consent', 'select_account']).optional(), + + // https://datatracker.ietf.org/doc/html/rfc9396 + authorization_details: oauthAuthorizationDetailsSchema.optional(), +}) + +/** + * @see {oauthAuthenticationRequestParametersSchema} + */ +export type OAuthAuthenticationRequestParameters = z.infer< + typeof oauthAuthenticationRequestParametersSchema +> diff --git a/packages/oauth/oauth-types/src/oauth-authorization-details.ts b/packages/oauth/oauth-types/src/oauth-authorization-details.ts new file mode 100644 index 00000000000..71969705d52 --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-authorization-details.ts @@ -0,0 +1,19 @@ +import { z } from 'zod' + +/** + * @see {@link https://datatracker.ietf.org/doc/html/rfc9396#section-2 | RFC 9396, Section 2} + */ +export const oauthAuthorizationDetailsSchema = z.array( + z.object({ + type: z.string(), + locations: z.array(z.string().url()).optional(), + actions: z.array(z.string()).optional(), + datatypes: z.array(z.string()).optional(), + identifier: z.string().optional(), + privileges: z.array(z.string()).optional(), + }), +) + +export type OAuthAuthorizationDetails = z.infer< + typeof oauthAuthorizationDetailsSchema +> diff --git a/packages/oauth/oauth-types/src/oauth-client-credentials.ts b/packages/oauth/oauth-types/src/oauth-client-credentials.ts new file mode 100644 index 00000000000..991611b8e99 --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-client-credentials.ts @@ -0,0 +1,34 @@ +import { z } from 'zod' +import { jwtSchema } from '@atproto/jwk' + +import { oauthClientIdSchema } from './oauth-client-id.js' +import { CLIENT_ASSERTION_TYPE_JWT_BEARER } from './constants.js' + +export const oauthClientCredentialsJwtBearerSchema = z.object({ + client_id: oauthClientIdSchema, + client_assertion_type: z.literal(CLIENT_ASSERTION_TYPE_JWT_BEARER), + /** + * - "sub" the subject MUST be the "client_id" of the OAuth client + * - "iat" is required and MUST be less than one minute + * - "aud" must containing a value that identifies the authorization server + * - The JWT MAY contain a "jti" (JWT ID) claim that provides a unique identifier for the token. + * - Note that the authorization server may reject JWTs with an "exp" claim value that is unreasonably far in the future. + * + * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-jwt-bearer-11#section-3} + */ + client_assertion: jwtSchema, +}) + +export const oauthClientCredentialsSecretPostSchema = z.object({ + client_id: oauthClientIdSchema, + client_secret: z.string(), +}) + +export const oauthClientCredentialsSchema = z.union([ + oauthClientCredentialsJwtBearerSchema, + oauthClientCredentialsSecretPostSchema, +]) + +export type OAuthClientCredentials = z.infer< + typeof oauthClientCredentialsSchema +> diff --git a/packages/oauth/oauth-types/src/oauth-client-id.ts b/packages/oauth/oauth-types/src/oauth-client-id.ts new file mode 100644 index 00000000000..6d5dcb55381 --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-client-id.ts @@ -0,0 +1,4 @@ +import { z } from 'zod' + +export const oauthClientIdSchema = z.string().min(1) +export type OAuthClientId = z.infer diff --git a/packages/oauth/oauth-types/src/oauth-client-identification.ts b/packages/oauth/oauth-types/src/oauth-client-identification.ts new file mode 100644 index 00000000000..7618d2ebdbe --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-client-identification.ts @@ -0,0 +1,14 @@ +import { z } from 'zod' + +import { oauthClientIdSchema } from './oauth-client-id.js' +import { oauthClientCredentialsSchema } from './oauth-client-credentials.js' + +export const oauthClientIdentificationSchema = z.union([ + oauthClientCredentialsSchema, + // Must be last since it is less specific + z.object({ client_id: oauthClientIdSchema }), +]) + +export type OAuthClientIdentification = z.infer< + typeof oauthClientIdentificationSchema +> diff --git a/packages/oauth/oauth-types/src/oauth-client-metadata.ts b/packages/oauth/oauth-types/src/oauth-client-metadata.ts new file mode 100644 index 00000000000..dfc9f61d2e5 --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-client-metadata.ts @@ -0,0 +1,72 @@ +import { jwksPubSchema } from '@atproto/jwk' +import { z } from 'zod' + +import { oauthClientIdSchema } from './oauth-client-id.js' +import { oauthEndpointAuthMethod } from './oauth-endpoint-auth-method.js' +import { oauthGrantTypeSchema } from './oauth-grant-type.js' +import { oauthResponseTypeSchema } from './oauth-response-type.js' + +// https://openid.net/specs/openid-connect-registration-1_0.html +// https://datatracker.ietf.org/doc/html/rfc7591 +export const oauthClientMetadataSchema = z + .object({ + redirect_uris: z.array(z.string().url()).nonempty().readonly(), + response_types: z + .array(oauthResponseTypeSchema) + .nonempty() + // > If omitted, the default is that the client will use only the "code" + // > response type. + .default(['code']) + .readonly(), + grant_types: z + .array(oauthGrantTypeSchema) + .nonempty() + // > If omitted, the default behavior is that the client will use only the + // > "authorization_code" Grant Type. + .default(['authorization_code']) + .readonly(), + scope: z.string().optional(), + token_endpoint_auth_method: oauthEndpointAuthMethod + .default('none') + .optional(), + token_endpoint_auth_signing_alg: z.string().optional(), + introspection_endpoint_auth_method: oauthEndpointAuthMethod.optional(), + introspection_endpoint_auth_signing_alg: z.string().optional(), + revocation_endpoint_auth_method: oauthEndpointAuthMethod.optional(), + revocation_endpoint_auth_signing_alg: z.string().optional(), + pushed_authorization_request_endpoint_auth_method: + oauthEndpointAuthMethod.optional(), + pushed_authorization_request_endpoint_auth_signing_alg: z + .string() + .optional(), + userinfo_signed_response_alg: z.string().optional(), + userinfo_encrypted_response_alg: z.string().optional(), + jwks_uri: z.string().url().optional(), + jwks: jwksPubSchema.optional(), + application_type: z.enum(['web', 'native']).default('web').optional(), // default, per spec, is "web" + subject_type: z.enum(['public', 'pairwise']).default('public').optional(), + request_object_signing_alg: z.string().optional(), + id_token_signed_response_alg: z.string().optional(), + authorization_signed_response_alg: z.string().default('RS256').optional(), + authorization_encrypted_response_enc: z.enum(['A128CBC-HS256']).optional(), + authorization_encrypted_response_alg: z.string().optional(), + client_id: oauthClientIdSchema.optional(), + client_name: z.string().optional(), + client_uri: z.string().url().optional(), + policy_uri: z.string().url().optional(), + tos_uri: z.string().url().optional(), + logo_uri: z.string().url().optional(), + default_max_age: z.number().optional(), + require_auth_time: z.boolean().optional(), + contacts: z.array(z.string().email()).readonly().optional(), + tls_client_certificate_bound_access_tokens: z.boolean().optional(), + + // https://datatracker.ietf.org/doc/html/rfc9449#section-5.2 + dpop_bound_access_tokens: z.boolean().optional(), + + // https://datatracker.ietf.org/doc/html/rfc9396#section-14.5 + authorization_details_types: z.array(z.string()).readonly().optional(), + }) + .readonly() + +export type OAuthClientMetadata = z.infer diff --git a/packages/oauth/oauth-types/src/oauth-endpoint-auth-method.ts b/packages/oauth/oauth-types/src/oauth-endpoint-auth-method.ts new file mode 100644 index 00000000000..d9e6a47f2da --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-endpoint-auth-method.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +export const oauthEndpointAuthMethod = z.enum([ + 'client_secret_basic', + 'client_secret_jwt', + 'client_secret_post', + 'none', + 'private_key_jwt', + 'self_signed_tls_client_auth', + 'tls_client_auth', +]) + +export type OauthEndpointAuthMethod = z.infer diff --git a/packages/oauth/oauth-types/src/oauth-endpoint-name.ts b/packages/oauth/oauth-types/src/oauth-endpoint-name.ts new file mode 100644 index 00000000000..bbb0bbad998 --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-endpoint-name.ts @@ -0,0 +1,5 @@ +export type OAuthEndpointName = + | 'token' + | 'revocation' + | 'introspection' + | 'pushed_authorization_request' diff --git a/packages/oauth/oauth-types/src/oauth-grant-type.ts b/packages/oauth/oauth-types/src/oauth-grant-type.ts new file mode 100644 index 00000000000..db426835b7c --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-grant-type.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +export const oauthGrantTypeSchema = z.enum([ + 'authorization_code', + 'implicit', + 'refresh_token', + 'password', // Not part of OAuth 2.1 + 'client_credentials', + 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'urn:ietf:params:oauth:grant-type:saml2-bearer', +]) + +export type OAuthGrantType = z.infer diff --git a/packages/oauth/oauth-types/src/oauth-par-response.ts b/packages/oauth/oauth-types/src/oauth-par-response.ts new file mode 100644 index 00000000000..e73e7e0438a --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-par-response.ts @@ -0,0 +1,7 @@ +import { z } from 'zod' + +export const oauthParResponseSchema = z.object({ + request_uri: z.string(), +}) + +export type OAuthParResponse = z.infer diff --git a/packages/oauth/oauth-types/src/oauth-response-mode.ts b/packages/oauth/oauth-types/src/oauth-response-mode.ts new file mode 100644 index 00000000000..d14f0dc81cd --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-response-mode.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +export const oauthResponseModeSchema = z.enum([ + 'query', + 'fragment', + 'form_post', +]) + +export type OAuthResponseMode = z.infer diff --git a/packages/oauth/oauth-types/src/oauth-response-type.ts b/packages/oauth/oauth-types/src/oauth-response-type.ts new file mode 100644 index 00000000000..1f65d22380c --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-response-type.ts @@ -0,0 +1,17 @@ +import { z } from 'zod' + +export const oauthResponseTypeSchema = z.enum([ + // OAuth + 'code', // Authorization Code Grant + 'token', // Implicit Grant + + // OpenID + 'none', + 'code id_token token', + 'code id_token', + 'code token', + 'id_token token', + 'id_token', +]) + +export type OAuthResponseType = z.infer diff --git a/packages/oauth/oauth-types/src/oauth-server-metadata.ts b/packages/oauth/oauth-types/src/oauth-server-metadata.ts new file mode 100644 index 00000000000..19b9d17ee40 --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-server-metadata.ts @@ -0,0 +1,131 @@ +import { z } from 'zod' + +export const oauthServerIssuerSchema = z + .string() + .url() + .superRefine((value, ctx) => { + // Validate the issuer (MIX-UP attacks) + const url = new URL(value) + + if (url.protocol !== 'https:' && url.protocol !== 'http:') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Issuer must be an HTTP or HTTPS URL', + }) + return false + } + + if (value !== `${url.origin}/`) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + 'Issuer URL must not contain a path, username, password, query, or fragment', + }) + return false + } + + return true + }) + +/** + * @see {@link https://datatracker.ietf.org/doc/html/rfc8414} + */ +export const oauthServerMetadataSchema = z.object({ + issuer: oauthServerIssuerSchema, + + claims_supported: z.array(z.string()).optional(), + claims_locales_supported: z.array(z.string()).optional(), + claims_parameter_supported: z.boolean().optional(), + request_parameter_supported: z.boolean().optional(), + request_uri_parameter_supported: z.boolean().optional(), + require_request_uri_registration: z.boolean().optional(), + scopes_supported: z.array(z.string()).optional(), + subject_types_supported: z.array(z.string()).optional(), + response_types_supported: z.array(z.string()).optional(), + response_modes_supported: z.array(z.string()).optional(), + grant_types_supported: z.array(z.string()).optional(), + code_challenge_methods_supported: z.array(z.string()).min(1).optional(), + ui_locales_supported: z.array(z.string()).optional(), + id_token_signing_alg_values_supported: z.array(z.string()).optional(), + display_values_supported: z.array(z.string()).optional(), + request_object_signing_alg_values_supported: z.array(z.string()).optional(), + authorization_response_iss_parameter_supported: z.boolean().optional(), + authorization_details_types_supported: z.array(z.string()).optional(), + request_object_encryption_alg_values_supported: z + .array(z.string()) + .optional(), + request_object_encryption_enc_values_supported: z + .array(z.string()) + .optional(), + + jwks_uri: z.string().url().optional(), + + authorization_endpoint: z.string().url(), // .optional(), + + token_endpoint: z.string().url(), // .optional(), + token_endpoint_auth_methods_supported: z.array(z.string()).optional(), + token_endpoint_auth_signing_alg_values_supported: z + .array(z.string()) + .optional(), + + revocation_endpoint: z.string().url().optional(), + revocation_endpoint_auth_methods_supported: z.array(z.string()).optional(), + revocation_endpoint_auth_signing_alg_values_supported: z + .array(z.string()) + .optional(), + + introspection_endpoint: z.string().url().optional(), + introspection_endpoint_auth_methods_supported: z.array(z.string()).optional(), + introspection_endpoint_auth_signing_alg_values_supported: z + .array(z.string()) + .optional(), + + pushed_authorization_request_endpoint: z.string().url().optional(), + pushed_authorization_request_endpoint_auth_methods_supported: z + .array(z.string()) + .optional(), + pushed_authorization_request_endpoint_auth_signing_alg_values_supported: z + .array(z.string()) + .optional(), + + require_pushed_authorization_requests: z.boolean().optional(), + + userinfo_endpoint: z.string().url().optional(), + end_session_endpoint: z.string().url().optional(), + registration_endpoint: z.string().url().optional(), + + // https://datatracker.ietf.org/doc/html/rfc9449#section-5.1 + dpop_signing_alg_values_supported: z.array(z.string()).optional(), +}) + +export type OAuthServerMetadata = z.infer + +export const oauthServerMetadataValidator = oauthServerMetadataSchema + .superRefine((data, ctx) => { + if ( + data.require_pushed_authorization_requests && + !data.pushed_authorization_request_endpoint + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: + '"pushed_authorization_request_endpoint" required when "require_pushed_authorization_requests" is true', + }) + return false + } + + return true + }) + .superRefine((data, ctx) => { + if (data.response_types_supported) { + if (!data.response_types_supported.includes('code')) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Response type "code" is required', + }) + return false + } + } + + return true + }) diff --git a/packages/oauth/oauth-types/src/oauth-token-response.ts b/packages/oauth/oauth-types/src/oauth-token-response.ts new file mode 100644 index 00000000000..86259ed3be0 --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-token-response.ts @@ -0,0 +1,29 @@ +import { jwtSchema } from '@atproto/jwk' +import { z } from 'zod' + +import { oauthAuthorizationDetailsSchema } from './oauth-authorization-details.js' +import { oauthTokenTypeSchema } from './oauth-token-type.js' + +/** + * @see {@link https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1 | RFC 6749 (OAuth2), Section 5.1} + */ +export const oauthTokenResponseSchema = z + .object({ + access_token: z.string(), + token_type: oauthTokenTypeSchema, + issuer: z.string().url().optional(), + sub: z.string().optional(), + scope: z.string().optional(), + id_token: jwtSchema.optional(), + refresh_token: z.string().optional(), + expires_in: z.number().optional(), + authorization_details: oauthAuthorizationDetailsSchema.optional(), + }) + // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1 + // > The client MUST ignore unrecognized value names in the response. + .passthrough() + +/** + * @see {@link oauthTokenResponseSchema} + */ +export type OAuthTokenResponse = z.infer diff --git a/packages/oauth/oauth-types/src/oauth-token-type.ts b/packages/oauth/oauth-types/src/oauth-token-type.ts new file mode 100644 index 00000000000..fb177f1f25a --- /dev/null +++ b/packages/oauth/oauth-types/src/oauth-token-type.ts @@ -0,0 +1,15 @@ +import { z } from 'zod' + +// Case insensitive input, normalized output +export const oauthTokenTypeSchema = z.union([ + z + .string() + .regex(/^DPoP$/i) + .transform(() => 'DPoP' as const), + z + .string() + .regex(/^Bearer$/i) + .transform(() => 'Bearer' as const), +]) + +export type OAuthTokenType = z.infer diff --git a/packages/oauth/oauth-types/src/oidc-claims-parameter.ts b/packages/oauth/oauth-types/src/oidc-claims-parameter.ts new file mode 100644 index 00000000000..ebd3a663ae8 --- /dev/null +++ b/packages/oauth/oauth-types/src/oidc-claims-parameter.ts @@ -0,0 +1,40 @@ +import { z } from 'zod' + +export const oidcClaimsParameterSchema = z.enum([ + // https://openid.net/specs/openid-provider-authentication-policy-extension-1_0.html#rfc.section.5.2 + // if client metadata "require_auth_time" is true, this *must* be provided + 'auth_time', + + // OIDC + 'nonce', + 'acr', + + // OpenID: "profile" scope + 'name', + 'family_name', + 'given_name', + 'middle_name', + 'nickname', + 'preferred_username', + 'gender', + 'picture', + 'profile', + 'website', + 'birthdate', + 'zoneinfo', + 'locale', + 'updated_at', + + // OpenID: "email" scope + 'email', + 'email_verified', + + // OpenID: "phone" scope + 'phone_number', + 'phone_number_verified', + + // OpenID: "address" scope + 'address', +]) + +export type OidcClaimsParameter = z.infer diff --git a/packages/oauth/oauth-types/src/oidc-claims-properties.ts b/packages/oauth/oauth-types/src/oidc-claims-properties.ts new file mode 100644 index 00000000000..f57e00e8058 --- /dev/null +++ b/packages/oauth/oauth-types/src/oidc-claims-properties.ts @@ -0,0 +1,11 @@ +import { z } from 'zod' + +const oidcClaimsValueSchema = z.union([z.string(), z.number(), z.boolean()]) + +export const oidcClaimsPropertiesSchema = z.object({ + essential: z.boolean().optional(), + value: oidcClaimsValueSchema.optional(), + values: z.array(oidcClaimsValueSchema).optional(), +}) + +export type OidcClaimsProperties = z.infer diff --git a/packages/oauth/oauth-types/src/oidc-entity-type.ts b/packages/oauth/oauth-types/src/oidc-entity-type.ts new file mode 100644 index 00000000000..0bb7369c457 --- /dev/null +++ b/packages/oauth/oauth-types/src/oidc-entity-type.ts @@ -0,0 +1,5 @@ +import { z } from 'zod' + +export const oidcEntityTypeSchema = z.enum(['userinfo', 'id_token']) + +export type OidcEntityType = z.infer diff --git a/packages/oauth/oauth-types/tsconfig.build.json b/packages/oauth/oauth-types/tsconfig.build.json new file mode 100644 index 00000000000..2ef4f334355 --- /dev/null +++ b/packages/oauth/oauth-types/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig/isomorphic.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["./src"] +} diff --git a/packages/oauth/oauth-types/tsconfig.json b/packages/oauth/oauth-types/tsconfig.json new file mode 100644 index 00000000000..e84b8178b47 --- /dev/null +++ b/packages/oauth/oauth-types/tsconfig.json @@ -0,0 +1,4 @@ +{ + "include": [], + "references": [{ "path": "./tsconfig.build.json" }] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8a6728069b..29677719104 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -460,6 +460,19 @@ importers: specifier: ^4.17.13 version: 4.17.21 + packages/did: + dependencies: + tslib: + specifier: ^2.6.2 + version: 2.6.2 + zod: + specifier: ^3.22.4 + version: 3.23.4 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + packages/identity: dependencies: '@atproto/common-web': @@ -491,6 +504,196 @@ importers: specifier: ^28.1.2 version: 28.1.2(@types/node@18.19.24)(ts-node@10.8.2) + packages/internal/did-resolver: + dependencies: + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../fetch + '@atproto-labs/pipe': + specifier: workspace:* + version: link:../pipe + '@atproto-labs/simple-store': + specifier: workspace:* + version: link:../simple-store + '@atproto-labs/simple-store-memory': + specifier: workspace:* + version: link:../simple-store-memory + '@atproto/did': + specifier: workspace:* + version: link:../../did + tslib: + specifier: ^2.6.2 + version: 2.6.2 + zod: + specifier: ^3.22.4 + version: 3.23.4 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/fetch: + dependencies: + '@atproto-labs/pipe': + specifier: workspace:* + version: link:../pipe + tslib: + specifier: ^2.6.2 + version: 2.6.2 + zod: + specifier: ^3.22.4 + version: 3.23.4 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/fetch-node: + dependencies: + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../fetch + '@atproto-labs/pipe': + specifier: workspace:* + version: link:../pipe + ipaddr.js: + specifier: ^2.1.0 + version: 2.1.0 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + undici: + specifier: ^6.14.1 + version: 6.16.1 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/handle-resolver: + dependencies: + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../fetch + '@atproto-labs/simple-store': + specifier: workspace:* + version: link:../simple-store + '@atproto-labs/simple-store-memory': + specifier: workspace:* + version: link:../simple-store-memory + '@atproto/did': + specifier: workspace:* + version: link:../../did + tslib: + specifier: ^2.6.2 + version: 2.6.2 + zod: + specifier: ^3.22.4 + version: 3.23.4 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/handle-resolver-node: + dependencies: + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../fetch + '@atproto-labs/fetch-node': + specifier: workspace:* + version: link:../fetch-node + '@atproto-labs/handle-resolver': + specifier: workspace:* + version: link:../handle-resolver + '@atproto/did': + specifier: workspace:* + version: link:../../did + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/identity-resolver: + dependencies: + '@atproto-labs/did-resolver': + specifier: workspace:* + version: link:../did-resolver + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../fetch + '@atproto-labs/handle-resolver': + specifier: workspace:* + version: link:../handle-resolver + '@atproto/did': + specifier: workspace:* + version: link:../../did + '@atproto/syntax': + specifier: workspace:* + version: link:../../syntax + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/pipe: + dependencies: + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/rollup-plugin-bundle-manifest: + dependencies: + mime: + specifier: ^3.0.0 + version: 3.0.0 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + rollup: + specifier: ^4.10.0 + version: 4.17.2 + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/simple-store: + dependencies: + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/internal/simple-store-memory: + dependencies: + '@atproto-labs/simple-store': + specifier: workspace:* + version: link:../simple-store + lru-cache: + specifier: ^10.2.0 + version: 10.2.0 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + packages/lex-cli: dependencies: '@atproto/lexicon': @@ -540,6 +743,360 @@ importers: specifier: ^28.1.2 version: 28.1.2(@types/node@18.19.24)(ts-node@10.8.2) + packages/oauth/jwk: + dependencies: + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + zod: + specifier: ^3.22.4 + version: 3.23.4 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/oauth/jwk-jose: + dependencies: + '@atproto/jwk': + specifier: workspace:* + version: link:../jwk + jose: + specifier: ^5.2.0 + version: 5.3.0 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/oauth/jwk-webcrypto: + dependencies: + '@atproto/jwk': + specifier: workspace:* + version: link:../jwk + '@atproto/jwk-jose': + specifier: workspace:* + version: link:../jwk-jose + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/oauth/oauth-client: + dependencies: + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../../internal/fetch + '@atproto-labs/handle-resolver': + specifier: workspace:* + version: link:../../internal/handle-resolver + '@atproto-labs/identity-resolver': + specifier: workspace:* + version: link:../../internal/identity-resolver + '@atproto-labs/simple-store': + specifier: workspace:* + version: link:../../internal/simple-store + '@atproto-labs/simple-store-memory': + specifier: workspace:* + version: link:../../internal/simple-store-memory + '@atproto/did': + specifier: workspace:* + version: link:../../did + '@atproto/jwk': + specifier: workspace:* + version: link:../jwk + '@atproto/oauth-types': + specifier: workspace:* + version: link:../oauth-types + multiformats: + specifier: ^9.9.0 + version: 9.9.0 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/oauth/oauth-client-browser: + dependencies: + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../../internal/fetch + '@atproto-labs/handle-resolver': + specifier: workspace:* + version: link:../../internal/handle-resolver + '@atproto-labs/identity-resolver': + specifier: workspace:* + version: link:../../internal/identity-resolver + '@atproto-labs/simple-store': + specifier: workspace:* + version: link:../../internal/simple-store + '@atproto/did': + specifier: workspace:* + version: link:../../did + '@atproto/jwk': + specifier: workspace:* + version: link:../jwk + '@atproto/jwk-webcrypto': + specifier: workspace:* + version: link:../jwk-webcrypto + '@atproto/oauth-client': + specifier: workspace:* + version: link:../oauth-client + '@atproto/oauth-types': + specifier: workspace:* + version: link:../oauth-types + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + '@atproto/api': + specifier: workspace:* + version: link:../../api + '@atproto/oauth-client-browser': + specifier: workspace:* + version: 'link:' + '@atproto/xrpc': + specifier: workspace:* + version: link:../../xrpc + '@rollup/plugin-commonjs': + specifier: ^25.0.7 + version: 25.0.7(rollup@4.17.2) + '@rollup/plugin-html': + specifier: ^1.0.3 + version: 1.0.3(rollup@4.17.2) + '@rollup/plugin-node-resolve': + specifier: ^15.2.3 + version: 15.2.3(rollup@4.17.2) + '@rollup/plugin-replace': + specifier: ^5.0.5 + version: 5.0.5(rollup@4.17.2) + '@rollup/plugin-terser': + specifier: ^0.4.4 + version: 0.4.4(rollup@4.17.2) + '@rollup/plugin-typescript': + specifier: ^11.1.6 + version: 11.1.6(rollup@4.17.2)(tslib@2.6.2)(typescript@5.4.4) + '@types/react': + specifier: ^18.2.50 + version: 18.3.2 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.3.0 + autoprefixer: + specifier: ^10.4.17 + version: 10.4.19(postcss@8.4.38) + postcss: + specifier: ^8.4.33 + version: 8.4.38 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + rollup: + specifier: ^4.13.0 + version: 4.17.2 + rollup-plugin-postcss: + specifier: ^4.0.2 + version: 4.0.2(postcss@8.4.38) + rollup-plugin-serve: + specifier: ^1.1.1 + version: 1.1.1 + tailwindcss: + specifier: ^3.4.1 + version: 3.4.3 + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/oauth/oauth-client-react-native: + dependencies: + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../../internal/fetch + '@atproto-labs/handle-resolver': + specifier: workspace:* + version: link:../../internal/handle-resolver + '@atproto-labs/identity-resolver': + specifier: workspace:* + version: link:../../internal/identity-resolver + '@atproto-labs/simple-store': + specifier: workspace:* + version: link:../../internal/simple-store + '@atproto/jwk': + specifier: workspace:* + version: link:../jwk + '@atproto/oauth-client': + specifier: workspace:* + version: link:../oauth-client + '@atproto/oauth-types': + specifier: workspace:* + version: link:../oauth-types + react: + specifier: '*' + version: 18.3.1 + react-native: + specifier: '*' + version: 0.74.1(@babel/core@7.18.6)(@babel/preset-env@7.24.5)(react@18.3.1) + tslib: + specifier: ^2.6.2 + version: 2.6.2 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/oauth/oauth-provider: + dependencies: + '@atproto-labs/fetch': + specifier: workspace:* + version: link:../../internal/fetch + '@atproto-labs/fetch-node': + specifier: workspace:* + version: link:../../internal/fetch-node + '@atproto-labs/pipe': + specifier: workspace:* + version: link:../../internal/pipe + '@atproto-labs/simple-store': + specifier: workspace:* + version: link:../../internal/simple-store + '@atproto-labs/simple-store-memory': + specifier: workspace:* + version: link:../../internal/simple-store-memory + '@atproto/jwk': + specifier: workspace:* + version: link:../jwk + '@atproto/jwk-jose': + specifier: workspace:* + version: link:../jwk-jose + '@atproto/oauth-types': + specifier: workspace:* + version: link:../oauth-types + '@hapi/accept': + specifier: ^6.0.3 + version: 6.0.3 + '@hapi/bourne': + specifier: ^3.0.0 + version: 3.0.0 + cookie: + specifier: ^0.6.0 + version: 0.6.0 + http-errors: + specifier: ^2.0.0 + version: 2.0.0 + jose: + specifier: ^5.2.0 + version: 5.3.0 + oidc-token-hash: + specifier: ^5.0.3 + version: 5.0.3 + psl: + specifier: ^1.9.0 + version: 1.9.0 + tslib: + specifier: ^2.6.2 + version: 2.6.2 + zod: + specifier: ^3.22.4 + version: 3.23.4 + optionalDependencies: + ioredis: + specifier: ^5.3.2 + version: 5.3.2 + keygrip: + specifier: ^1.1.0 + version: 1.1.0 + devDependencies: + '@atproto-labs/rollup-plugin-bundle-manifest': + specifier: workspace:* + version: link:../../internal/rollup-plugin-bundle-manifest + '@rollup/plugin-commonjs': + specifier: ^25.0.7 + version: 25.0.7(rollup@4.17.2) + '@rollup/plugin-node-resolve': + specifier: ^15.2.3 + version: 15.2.3(rollup@4.17.2) + '@rollup/plugin-replace': + specifier: ^5.0.5 + version: 5.0.5(rollup@4.17.2) + '@rollup/plugin-terser': + specifier: ^0.4.4 + version: 0.4.4(rollup@4.17.2) + '@rollup/plugin-typescript': + specifier: ^11.1.6 + version: 11.1.6(rollup@4.17.2)(tslib@2.6.2)(typescript@5.4.4) + '@types/cookie': + specifier: ^0.6.0 + version: 0.6.0 + '@types/keygrip': + specifier: ^1.0.6 + version: 1.0.6 + '@types/react': + specifier: ^18.2.50 + version: 18.3.2 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.3.0 + '@types/send': + specifier: ^0.17.4 + version: 0.17.4 + '@web/rollup-plugin-import-meta-assets': + specifier: ^2.2.1 + version: 2.2.1(rollup@4.17.2) + autoprefixer: + specifier: ^10.4.17 + version: 10.4.19(postcss@8.4.38) + postcss: + specifier: ^8.4.33 + version: 8.4.38 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + rollup: + specifier: ^4.13.0 + version: 4.17.2 + rollup-plugin-postcss: + specifier: ^4.0.2 + version: 4.0.2(postcss@8.4.38) + tailwindcss: + specifier: ^3.4.1 + version: 3.4.3 + typescript: + specifier: ^5.3.3 + version: 5.4.4 + + packages/oauth/oauth-types: + dependencies: + '@atproto/jwk': + specifier: workspace:* + version: link:../jwk + tslib: + specifier: ^2.6.2 + version: 2.6.2 + zod: + specifier: ^3.22.4 + version: 3.23.4 + devDependencies: + typescript: + specifier: ^5.3.3 + version: 5.4.4 + packages/ozone: dependencies: '@atproto/api': @@ -970,6 +1527,10 @@ importers: opentelemetry-plugin-better-sqlite3: specifier: ^1.1.0 version: 1.1.0(better-sqlite3@9.4.5) + devDependencies: + dotenv: + specifier: ^16.4.5 + version: 16.4.5 packages: @@ -978,13 +1539,17 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + dev: true + /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.19 - dev: true /@atproto/common@0.1.0: resolution: {integrity: sha512-OB5tWE2R19jwiMIs2IjQieH5KTUuMb98XGCn9h3xuu6NanwjlmbCYMv08fMYwIp3UQ6jcq//84cDT3Bu6fJD+A==} @@ -3389,12 +3954,23 @@ packages: dependencies: '@babel/highlight': 7.22.10 chalk: 2.4.2 - dev: true + + /@babel/code-frame@7.24.2: + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.24.5 + picocolors: 1.0.0 + dev: false /@babel/compat-data@7.22.9: resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} engines: {node: '>=6.9.0'} - dev: true + + /@babel/compat-data@7.24.4: + resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} + engines: {node: '>=6.9.0'} + dev: false /@babel/core@7.18.6: resolution: {integrity: sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==} @@ -3417,7 +3993,29 @@ packages: semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true + + /@babel/core@7.24.5: + resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.5 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.24.5) + '@babel/helpers': 7.24.5 + '@babel/parser': 7.24.5 + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.5 + '@babel/types': 7.24.5 + convert-source-map: 2.0.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: false /@babel/generator@7.22.10: resolution: {integrity: sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==} @@ -3427,7 +4025,30 @@ packages: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.19 jsesc: 2.5.2 - dev: true + + /@babel/generator@7.24.5: + resolution: {integrity: sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.5 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + dev: false + + /@babel/helper-annotate-as-pure@7.22.5: + resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: false + + /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: + resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.5 + dev: false /@babel/helper-compilation-targets@7.22.10: resolution: {integrity: sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==} @@ -3438,12 +4059,71 @@ packages: browserslist: 4.21.10 lru-cache: 5.1.1 semver: 6.3.1 - dev: true + + /@babel/helper-compilation-targets@7.23.6: + resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.24.4 + '@babel/helper-validator-option': 7.23.5 + browserslist: 4.23.0 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: false + + /@babel/helper-create-class-features-plugin@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-member-expression-to-functions': 7.24.5 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.18.6) + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/helper-split-export-declaration': 7.24.5 + semver: 6.3.1 + dev: false + + /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.18.6): + resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + regexpu-core: 5.3.2 + semver: 6.3.1 + dev: false + + /@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.18.6): + resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.22.10 + '@babel/helper-plugin-utils': 7.24.5 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.4 + transitivePeerDependencies: + - supports-color + dev: false + + /@babel/helper-environment-visitor@7.22.20: + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + dev: false /@babel/helper-environment-visitor@7.22.5: resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-function-name@7.22.5: resolution: {integrity: sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==} @@ -3451,21 +4131,40 @@ packages: dependencies: '@babel/template': 7.22.5 '@babel/types': 7.22.10 - dev: true + + /@babel/helper-function-name@7.23.0: + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.24.0 + '@babel/types': 7.24.5 + dev: false /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.10 - dev: true + + /@babel/helper-member-expression-to-functions@7.24.5: + resolution: {integrity: sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.5 + dev: false /@babel/helper-module-imports@7.22.5: resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.10 - dev: true + + /@babel/helper-module-imports@7.24.3: + resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.5 + dev: false /@babel/helper-module-transforms@7.22.9(@babel/core@7.18.6): resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==} @@ -3479,41 +4178,143 @@ packages: '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.22.5 - dev: true - /@babel/helper-plugin-utils@7.22.5: - resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + /@babel/helper-module-transforms@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==} engines: {node: '>=6.9.0'} - dev: true + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-simple-access': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-validator-identifier': 7.24.5 + dev: false - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + /@babel/helper-module-transforms@7.24.5(@babel/core@7.24.5): + resolution: {integrity: sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-simple-access': 7.24.5 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/helper-validator-identifier': 7.24.5 + dev: false + + /@babel/helper-optimise-call-expression@7.22.5: + resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.10 - dev: true + dev: false + + /@babel/helper-plugin-utils@7.22.5: + resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + engines: {node: '>=6.9.0'} + + /@babel/helper-plugin-utils@7.24.5: + resolution: {integrity: sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.18.6): + resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-wrap-function': 7.24.5 + dev: false + + /@babel/helper-replace-supers@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-member-expression-to-functions': 7.24.5 + '@babel/helper-optimise-call-expression': 7.22.5 + dev: false + + /@babel/helper-simple-access@7.22.5: + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + + /@babel/helper-simple-access@7.24.5: + resolution: {integrity: sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.5 + dev: false + + /@babel/helper-skip-transparent-expression-wrappers@7.22.5: + resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.22.10 + dev: false /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.22.10 - dev: true + + /@babel/helper-split-export-declaration@7.24.5: + resolution: {integrity: sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.5 + dev: false /@babel/helper-string-parser@7.22.5: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} - dev: true + + /@babel/helper-string-parser@7.24.1: + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + dev: false /@babel/helper-validator-identifier@7.22.5: resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} engines: {node: '>=6.9.0'} - dev: true + + /@babel/helper-validator-identifier@7.24.5: + resolution: {integrity: sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==} + engines: {node: '>=6.9.0'} + dev: false /@babel/helper-validator-option@7.22.5: resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==} engines: {node: '>=6.9.0'} - dev: true + + /@babel/helper-validator-option@7.23.5: + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-wrap-function@7.24.5: + resolution: {integrity: sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-function-name': 7.23.0 + '@babel/template': 7.24.0 + '@babel/types': 7.24.5 + dev: false /@babel/helpers@7.22.10: resolution: {integrity: sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==} @@ -3524,7 +4325,17 @@ packages: '@babel/types': 7.22.10 transitivePeerDependencies: - supports-color - dev: true + + /@babel/helpers@7.24.5: + resolution: {integrity: sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.5 + '@babel/types': 7.24.5 + transitivePeerDependencies: + - supports-color + dev: false /@babel/highlight@7.22.10: resolution: {integrity: sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==} @@ -3533,7 +4344,16 @@ packages: '@babel/helper-validator-identifier': 7.22.5 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true + + /@babel/highlight@7.24.5: + resolution: {integrity: sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.24.5 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.0 + dev: false /@babel/parser@7.22.10: resolution: {integrity: sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==} @@ -3541,100 +4361,191 @@ packages: hasBin: true dependencies: '@babel/types': 7.22.10 - dev: true - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.18.6): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + /@babel/parser@7.24.5: + resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.24.5 + dev: false + + /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.24.5 + dev: false + + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false + + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-transform-optional-chaining': 7.24.5(@babel/core@7.18.6) + dev: false + + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.24.5 + dev: false + + /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.18.6): + resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.5 '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.18.6) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) + dev: false - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.18.6): - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) '@babel/helper-plugin-utils': 7.22.5 - dev: true + dev: false - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.18.6): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + /@babel/plugin-proposal-export-default-from@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-+0hrgGGV3xyYIjOrD/bUZk/iUwOIGuoANfRfVg1cPhYBxF+TIXSEcc42DqzBICmWsnAQ+SfKedY0bj8QD+LuMg==} + engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 - '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-export-default-from': 7.24.1(@babel/core@7.18.6) + dev: false - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.18.6): - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.18.6): + resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) + dev: false - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.18.6): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) + dev: false - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.18.6): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) + dev: false - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.18.6): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.18.6): + resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: + '@babel/compat-data': 7.22.9 '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.22.10 '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.18.6) + dev: false - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.18.6): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) + dev: false - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.18.6): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.18.6): + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - dev: true + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + dev: false - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.18.6): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.18.6): + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + dev: false + + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.18.6): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.18.6): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -3642,5868 +4553,9347 @@ packages: '@babel/helper-plugin-utils': 7.22.5 dev: true - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.18.6): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.18.6): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - dev: true - /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.18.6): - resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.18.6): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false + + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.18.6 '@babel/helper-plugin-utils': 7.22.5 - dev: true + dev: false - /@babel/runtime@7.22.10: - resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} + /@babel/plugin-syntax-export-default-from@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-cNXSxv9eTkGUtd0PsNMK8Yx5xeScxfpWOUAxE+ZPAXXEcAMOC3fk7LRdXq5fvpra2pLx2p1YtkAhpUbB2SwaRA==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - regenerator-runtime: 0.14.0 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@babel/template@7.22.5: - resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false + + /@babel/plugin-syntax-flow@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/code-frame': 7.22.10 - '@babel/parser': 7.22.10 - '@babel/types': 7.22.10 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@babel/traverse@7.22.10: - resolution: {integrity: sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==} + /@babel/plugin-syntax-import-assertions@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/code-frame': 7.22.10 - '@babel/generator': 7.22.10 - '@babel/helper-environment-visitor': 7.22.5 - '@babel/helper-function-name': 7.22.5 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.22.10 - '@babel/types': 7.22.10 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@babel/types@7.22.10: - resolution: {integrity: sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==} + /@babel/plugin-syntax-import-attributes@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/helper-string-parser': 7.22.5 - '@babel/helper-validator-identifier': 7.22.5 - to-fast-properties: 2.0.0 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@bcoe/v8-coverage@0.2.3: - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - dev: true + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.18.6): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 - /@bufbuild/buf-darwin-arm64@1.28.1: - resolution: {integrity: sha512-nAyvwKkcd8qQTExCZo5MtSRhXLK7e3vzKFKHjXfkveRakSUST2HFlFZAHfErZimN4wBrPTN0V0hNRU8PPjkMpQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 - /@bufbuild/buf-darwin-x64@1.28.1: - resolution: {integrity: sha512-b0eT3xd3vX5a5lWAbo5h7FPuf9MsOJI4I39qs4TZnrlZ8BOuPfqzwzijiFf9UCwaX2vR1NQXexIoQ80Ci+fCHw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true + /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@bufbuild/buf-linux-aarch64@1.28.1: - resolution: {integrity: sha512-p5h9bZCVLMh8No9/7k7ulXzsFx5P7Lu6DiUMjSJ6aBXPMYo6Xl7r/6L2cQkpsZ53HMtIxCgMYS9a7zoS4K8wIw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.18.6): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 - /@bufbuild/buf-linux-x64@1.28.1: - resolution: {integrity: sha512-fVJ3DiRigIso06jgEl+JNp59Y5t2pxDHd10d3SA4r+14sXbZ2J7Gy/wBqVXPry4x/jW567KKlvmhg7M5ZBgCQQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 - /@bufbuild/buf-win32-arm64@1.28.1: - resolution: {integrity: sha512-KJiRJpugQRK/jXC46Xjlb68UydWhCZj2jHdWLIwNtgXd1WTJ3LngChZV7Y6pPK08pwBAVz0JYeVbD5IlTCD4TQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.18.6): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 - /@bufbuild/buf-win32-x64@1.28.1: - resolution: {integrity: sha512-vMnc+7OVCkmlRWQsgYHgUqiBPRIjD8XeoRyApJ07YZzGs7DkRH4LhvmacJbLd3wORylbn6gLz3pQa8J/M61mzg==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 - /@bufbuild/buf@1.28.1: - resolution: {integrity: sha512-WRDagrf0uBjfV9s5eyrSPJDcdI4A5Q7JMCA4aMrHRR8fo/TTjniDBjJprszhaguqsDkn/LS4QIu92HVFZCrl9A==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@bufbuild/buf-darwin-arm64': 1.28.1 - '@bufbuild/buf-darwin-x64': 1.28.1 - '@bufbuild/buf-linux-aarch64': 1.28.1 - '@bufbuild/buf-linux-x64': 1.28.1 - '@bufbuild/buf-win32-arm64': 1.28.1 - '@bufbuild/buf-win32-x64': 1.28.1 - dev: true + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 - /@bufbuild/protobuf@1.6.0: - resolution: {integrity: sha512-hp19vSFgNw3wBBcVBx5qo5pufCqjaJ0Cfk5H/pfjNOfNWU+4/w0QVOmfAOZNRrNWRrVuaJWxcN8P2vhOkkzbBQ==} + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.18.6): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 - /@bufbuild/protoc-gen-es@1.6.0(@bufbuild/protobuf@1.6.0): - resolution: {integrity: sha512-m0akOPWeD5UBfGdZyudrbnmdjI8l/ZHlP8TyEIcj7qMCR4kh68tMtGvrjRzj5ynIpavrr6G7P06XP9F9f2MDRw==} - engines: {node: '>=14'} - hasBin: true + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.18.6): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@bufbuild/protobuf': 1.6.0 - peerDependenciesMeta: - '@bufbuild/protobuf': - optional: true + '@babel/core': ^7.0.0-0 dependencies: - '@bufbuild/protobuf': 1.6.0 - '@bufbuild/protoplugin': 1.6.0 - transitivePeerDependencies: - - supports-color - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@bufbuild/protoplugin@1.6.0: - resolution: {integrity: sha512-o53ZsvojHQkAPoC9v5sJifY2OfXdRU8DO3QpPoJ+QuvYcfB9Zb3DZkNMQRyfEbF4TVYiaQ0mZzZl1mESDdyCxA==} + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.18.6): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@bufbuild/protobuf': 1.6.0 - '@typescript/vfs': 1.5.0 - typescript: 4.5.2 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 + + /@babel/plugin-syntax-typescript@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.22.5 dev: true - /@cbor-extract/cbor-extract-darwin-arm64@2.2.0: - resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} - cpu: [arm64] - os: [darwin] - requiresBuild: true + /@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 dev: false - optional: true - /@cbor-extract/cbor-extract-darwin-x64@2.2.0: - resolution: {integrity: sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==} - cpu: [x64] - os: [darwin] - requiresBuild: true + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.18.6): + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 dev: false - optional: true - /@cbor-extract/cbor-extract-linux-arm64@2.2.0: - resolution: {integrity: sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true + /@babel/plugin-transform-arrow-functions@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 dev: false - optional: true - /@cbor-extract/cbor-extract-linux-arm@2.2.0: - resolution: {integrity: sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==} - cpu: [arm] - os: [linux] - requiresBuild: true + /@babel/plugin-transform-async-generator-functions@7.24.3(@babel/core@7.18.6): + resolution: {integrity: sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.18.6) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) dev: false - optional: true - /@cbor-extract/cbor-extract-linux-x64@2.2.0: - resolution: {integrity: sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==} - cpu: [x64] - os: [linux] - requiresBuild: true + /@babel/plugin-transform-async-to-generator@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.18.6) dev: false - optional: true - /@cbor-extract/cbor-extract-win32-x64@2.2.0: - resolution: {integrity: sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==} - cpu: [x64] - os: [win32] - requiresBuild: true + /@babel/plugin-transform-block-scoped-functions@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 dev: false - optional: true - /@changesets/apply-release-plan@6.1.4: - resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==} + /@babel/plugin-transform-block-scoping@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/runtime': 7.22.10 - '@changesets/config': 2.3.1 - '@changesets/get-version-range-type': 0.3.2 - '@changesets/git': 2.0.0 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - detect-indent: 6.1.0 - fs-extra: 7.0.1 - lodash.startcase: 4.4.0 - outdent: 0.5.0 - prettier: 2.7.1 - resolve-from: 5.0.0 - semver: 7.5.4 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@changesets/assemble-release-plan@5.2.4: - resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==} + /@babel/plugin-transform-class-properties@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/runtime': 7.22.10 - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.6 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - semver: 7.5.4 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@changesets/changelog-git@0.1.14: - resolution: {integrity: sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==} + /@babel/plugin-transform-class-static-block@7.24.4(@babel/core@7.18.6): + resolution: {integrity: sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 dependencies: - '@changesets/types': 5.2.1 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.18.6) + dev: false - /@changesets/changelog-github@0.4.8: - resolution: {integrity: sha512-jR1DHibkMAb5v/8ym77E4AMNWZKB5NPzw5a5Wtqm1JepAuIF+hrKp2u04NKM14oBZhHglkCfrla9uq8ORnK/dw==} + /@babel/plugin-transform-classes@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@changesets/get-github-info': 0.5.2 - '@changesets/types': 5.2.1 - dotenv: 8.6.0 - transitivePeerDependencies: - - encoding - dev: true + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.18.6) + '@babel/helper-split-export-declaration': 7.24.5 + globals: 11.12.0 + dev: false - /@changesets/cli@2.26.2: - resolution: {integrity: sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==} - hasBin: true + /@babel/plugin-transform-computed-properties@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/runtime': 7.22.10 - '@changesets/apply-release-plan': 6.1.4 - '@changesets/assemble-release-plan': 5.2.4 - '@changesets/changelog-git': 0.1.14 - '@changesets/config': 2.3.1 - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.6 - '@changesets/get-release-plan': 3.0.17 - '@changesets/git': 2.0.0 - '@changesets/logger': 0.0.5 - '@changesets/pre': 1.0.14 - '@changesets/read': 0.5.9 - '@changesets/types': 5.2.1 - '@changesets/write': 0.2.3 - '@manypkg/get-packages': 1.1.3 - '@types/is-ci': 3.0.0 - '@types/semver': 7.5.0 - ansi-colors: 4.1.3 - chalk: 2.4.2 - enquirer: 2.4.1 - external-editor: 3.1.0 - fs-extra: 7.0.1 - human-id: 1.0.2 - is-ci: 3.0.1 - meow: 6.1.1 - outdent: 0.5.0 - p-limit: 2.3.0 - preferred-pm: 3.0.3 - resolve-from: 5.0.0 - semver: 7.5.4 - spawndamnit: 2.0.0 - term-size: 2.2.1 - tty-table: 4.2.1 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/template': 7.24.0 + dev: false - /@changesets/config@2.3.1: - resolution: {integrity: sha512-PQXaJl82CfIXddUOppj4zWu+987GCw2M+eQcOepxN5s+kvnsZOwjEJO3DH9eVy+OP6Pg/KFEWdsECFEYTtbg6w==} + /@babel/plugin-transform-destructuring@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.6 - '@changesets/logger': 0.0.5 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - micromatch: 4.0.5 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@changesets/errors@0.1.4: - resolution: {integrity: sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==} + /@babel/plugin-transform-dotall-regex@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - extendable-error: 0.1.7 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@changesets/get-dependents-graph@1.3.6: - resolution: {integrity: sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==} + /@babel/plugin-transform-duplicate-keys@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - chalk: 2.4.2 - fs-extra: 7.0.1 - semver: 7.5.4 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@changesets/get-github-info@0.5.2: - resolution: {integrity: sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==} + /@babel/plugin-transform-dynamic-import@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - dataloader: 1.4.0 - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.6) + dev: false - /@changesets/get-release-plan@3.0.17: - resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==} + /@babel/plugin-transform-exponentiation-operator@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/runtime': 7.22.10 - '@changesets/assemble-release-plan': 5.2.4 - '@changesets/config': 2.3.1 - '@changesets/pre': 1.0.14 - '@changesets/read': 0.5.9 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - dev: true - - /@changesets/get-version-range-type@0.3.2: - resolution: {integrity: sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==} - dev: true + '@babel/core': 7.18.6 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@changesets/git@2.0.0: - resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==} + /@babel/plugin-transform-export-namespace-from@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/runtime': 7.22.10 - '@changesets/errors': 0.1.4 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - is-subdir: 1.2.0 - micromatch: 4.0.5 - spawndamnit: 2.0.0 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.18.6) + dev: false - /@changesets/logger@0.0.5: - resolution: {integrity: sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==} + /@babel/plugin-transform-flow-strip-types@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - chalk: 2.4.2 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.18.6) + dev: false - /@changesets/parse@0.3.16: - resolution: {integrity: sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==} + /@babel/plugin-transform-for-of@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@changesets/types': 5.2.1 - js-yaml: 3.14.1 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + dev: false - /@changesets/pre@1.0.14: - resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==} + /@babel/plugin-transform-function-name@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/runtime': 7.22.10 - '@changesets/errors': 0.1.4 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - fs-extra: 7.0.1 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@changesets/read@0.5.9: - resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==} + /@babel/plugin-transform-json-strings@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/runtime': 7.22.10 - '@changesets/git': 2.0.0 - '@changesets/logger': 0.0.5 - '@changesets/parse': 0.3.16 - '@changesets/types': 5.2.1 - chalk: 2.4.2 - fs-extra: 7.0.1 - p-filter: 2.1.0 - dev: true - - /@changesets/types@4.1.0: - resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} - dev: true - - /@changesets/types@5.2.1: - resolution: {integrity: sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==} - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) + dev: false - /@changesets/write@0.2.3: - resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==} + /@babel/plugin-transform-literals@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@babel/runtime': 7.22.10 - '@changesets/types': 5.2.1 - fs-extra: 7.0.1 - human-id: 1.0.2 - prettier: 2.7.1 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@connectrpc/connect-express@1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect-node@1.3.0)(@connectrpc/connect@1.3.0): - resolution: {integrity: sha512-6wbaQheD9cb4DnU1PvgVQdB1XPfA0bhlA0V0ZKx6oJJnTgGEYBzPrQztmqs5XW36/r+qJRfMgaVzZfX8MLafgA==} - engines: {node: '>=16.0.0'} + /@babel/plugin-transform-logical-assignment-operators@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==} + engines: {node: '>=6.9.0'} peerDependencies: - '@bufbuild/protobuf': ^1.4.2 - '@connectrpc/connect': 1.3.0 - '@connectrpc/connect-node': 1.3.0 + '@babel/core': ^7.0.0-0 dependencies: - '@bufbuild/protobuf': 1.6.0 - '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.6.0) - '@connectrpc/connect-node': 1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect@1.3.0) - '@types/express': 4.17.21 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) dev: false - /@connectrpc/connect-node@1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect@1.3.0): - resolution: {integrity: sha512-2fV/z/8MUFOkTn2Gbm7T/qvRfkpt/D/w7ykYqACZRH6VNG/faY4lH2wUZiNkwv9tzTrECKOJFyPsaGs3nRYK3w==} - engines: {node: '>=16.0.0'} + /@babel/plugin-transform-member-expression-literals@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==} + engines: {node: '>=6.9.0'} peerDependencies: - '@bufbuild/protobuf': ^1.4.2 - '@connectrpc/connect': 1.3.0 + '@babel/core': ^7.0.0-0 dependencies: - '@bufbuild/protobuf': 1.6.0 - '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.6.0) - undici: 5.28.2 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.6.0): - resolution: {integrity: sha512-kTeWxJnLLtxKc2ZSDN0rIBgwfP8RwcLknthX4AKlIAmN9ZC4gGnCbwp+3BKcP/WH5c8zGBAWqSY3zeqCM+ah7w==} + /@babel/plugin-transform-modules-amd@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==} + engines: {node: '>=6.9.0'} peerDependencies: - '@bufbuild/protobuf': ^1.4.2 + '@babel/core': ^7.0.0-0 dependencies: - '@bufbuild/protobuf': 1.6.0 + '@babel/core': 7.18.6 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@connectrpc/protoc-gen-connect-es@1.3.0(@bufbuild/protoc-gen-es@1.6.0)(@connectrpc/connect@1.3.0): - resolution: {integrity: sha512-UbQN48c0zafo5EFSsh3POIJP6ofYiAgKE1aFOZ2Er4W3flUYihydZdM6TQauPkn7jDj4w9jjLSTTZ9//ecUbPA==} - engines: {node: '>=16.0.0'} - hasBin: true + /@babel/plugin-transform-modules-commonjs@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==} + engines: {node: '>=6.9.0'} peerDependencies: - '@bufbuild/protoc-gen-es': ^1.6.0 - '@connectrpc/connect': 1.3.0 - peerDependenciesMeta: - '@bufbuild/protoc-gen-es': - optional: true - '@connectrpc/connect': - optional: true + '@babel/core': ^7.0.0-0 dependencies: - '@bufbuild/protobuf': 1.6.0 - '@bufbuild/protoc-gen-es': 1.6.0(@bufbuild/protobuf@1.6.0) - '@bufbuild/protoplugin': 1.6.0 - '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.6.0) - transitivePeerDependencies: - - supports-color - dev: true + '@babel/core': 7.18.6 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-simple-access': 7.22.5 + dev: false - /@cspotcode/source-map-support@0.8.1: - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} + /@babel/plugin-transform-modules-systemjs@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@jridgewell/trace-mapping': 0.3.9 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-validator-identifier': 7.24.5 + dev: false - /@datadog/native-appsec@2.0.0: - resolution: {integrity: sha512-XHARZ6MVgbnfOUO6/F3ZoZ7poXHJCNYFlgcyS2Xetuk9ITA5bfcooX2B2F7tReVB+RLJ+j8bsm0t55SyF04KDw==} - engines: {node: '>=12'} - requiresBuild: true + /@babel/plugin-transform-modules-umd@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - node-gyp-build: 3.9.0 + '@babel/core': 7.18.6 + '@babel/helper-module-transforms': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 dev: false - /@datadog/native-appsec@4.0.0: - resolution: {integrity: sha512-myTguXJ3VQHS2E1ylNsSF1avNpDmq5t+K4Q47wdzeakGc3sDIDDyEbvuFTujl9c9wBIkup94O1mZj5DR37ajzA==} - engines: {node: '>=12'} - requiresBuild: true + /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.18.6): + resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: - node-gyp-build: 3.9.0 + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.22.5 dev: false - /@datadog/native-iast-rewriter@1.1.2: - resolution: {integrity: sha512-pigRfRtAjZjMjqIXyXb98S4aDnuHz/EmqpoxAajFZsNjBLM87YonwSY5zoBdCsOyA46ddKOJRoCQd5ZalpOFMQ==} - engines: {node: '>= 10'} + /@babel/plugin-transform-new-target@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - node-gyp-build: 4.6.1 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 dev: false - /@datadog/native-iast-rewriter@2.2.1: - resolution: {integrity: sha512-DyZlE8gNa5AoOFNKGRJU4RYF/Y/tJzv4bIAMuVBbEnMA0xhiIYqpYQG8T3OKkALl3VSEeBMjYwuOR2fCrJ6gzA==} - engines: {node: '>= 10'} + /@babel/plugin-transform-nullish-coalescing-operator@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - lru-cache: 7.18.3 - node-gyp-build: 4.6.1 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) dev: false - /@datadog/native-iast-taint-tracking@1.1.0: - resolution: {integrity: sha512-TOrngpt6Qh52zWFOz1CkFXw0g43rnuUziFBtIMUsOLGzSHr9wdnTnE6HAyuvKy3f3ecAoZESlMfilGRKP93hXQ==} + /@babel/plugin-transform-numeric-separator@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - node-gyp-build: 3.9.0 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) dev: false - /@datadog/native-iast-taint-tracking@1.6.4: - resolution: {integrity: sha512-Owxk7hQ4Dxwv4zJAoMjRga0IvE6lhvxnNc8pJCHsemCWBXchjr/9bqg05Zy5JnMbKUWn4XuZeJD6RFZpRa8bfw==} - requiresBuild: true + /@babel/plugin-transform-object-rest-spread@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - node-gyp-build: 3.9.0 + '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.18.6) dev: false - /@datadog/native-metrics@1.6.0: - resolution: {integrity: sha512-+8jBzd0nlLV+ay3Vb87DLwz8JHAS817hRhSRQ6zxhud9TyvvcNTNN+VA2sb2fe5UK4aMDvj/sGVJjEtgr4RHew==} - engines: {node: '>=12'} - requiresBuild: true + /@babel/plugin-transform-object-super@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - node-gyp-build: 3.9.0 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-replace-supers': 7.24.1(@babel/core@7.18.6) dev: false - /@datadog/native-metrics@2.0.0: - resolution: {integrity: sha512-YklGVwUtmKGYqFf1MNZuOHvTYdKuR4+Af1XkWcMD8BwOAjxmd9Z+97328rCOY8TFUJzlGUPaXzB8j2qgG/BMwA==} - engines: {node: '>=12'} - requiresBuild: true + /@babel/plugin-transform-optional-catch-binding@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - node-addon-api: 6.1.0 - node-gyp-build: 3.9.0 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) dev: false - /@datadog/pprof@1.1.1: - resolution: {integrity: sha512-5lYXUpikQhrJwzODtJ7aFM0oKmPccISnTCecuWhjxIj4/7UJv0DamkLak634bgEW+kiChgkKFDapHSesuXRDXQ==} - engines: {node: '>=12'} - requiresBuild: true + /@babel/plugin-transform-optional-chaining@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - delay: 5.0.0 - findit2: 2.2.3 - node-gyp-build: 3.9.0 - p-limit: 3.1.0 - pify: 5.0.0 - protobufjs: 7.2.5 - source-map: 0.7.4 - split: 1.0.1 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) dev: false - /@datadog/pprof@4.0.1: - resolution: {integrity: sha512-TavqyiyQZOaUM9eQB07r8+K/T1CqKyOdsUGxpN79+BF+eOQBpTj/Cte6KdlhcUSKL3h5hSjC+vlgA7uW2qtVhA==} - engines: {node: '>=14'} - requiresBuild: true + /@babel/plugin-transform-parameters@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - delay: 5.0.0 - node-gyp-build: 3.9.0 - p-limit: 3.1.0 - pprof-format: 2.0.7 - source-map: 0.7.4 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 dev: false - /@datadog/sketches-js@2.1.0: - resolution: {integrity: sha512-smLocSfrt3s53H/XSVP3/1kP42oqvrkjUPtyaFd1F79ux24oE31BKt+q0c6lsa6hOYrFzsIwyc5GXAI5JmfOew==} + /@babel/plugin-transform-private-methods@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 dev: false - /@did-plc/lib@0.0.1: - resolution: {integrity: sha512-RkY5w9DbYMco3SjeepqIiMveqz35exjlVDipCs2gz9AXF4/cp9hvmrp9zUWEw2vny+FjV8vGEN7QpaXWaO6nhg==} + /@babel/plugin-transform-private-property-in-object@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@atproto/common': 0.1.0 - '@atproto/crypto': 0.1.0 - '@ipld/dag-cbor': 7.0.3 - axios: 1.6.2 - multiformats: 9.9.0 - uint8arrays: 3.0.0 - zod: 3.21.4 - transitivePeerDependencies: - - debug + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.18.6) + dev: false - /@did-plc/lib@0.0.4: - resolution: {integrity: sha512-Omeawq3b8G/c/5CtkTtzovSOnWuvIuCI4GTJNrt1AmCskwEQV7zbX5d6km1mjJNbE0gHuQPTVqZxLVqetNbfwA==} + /@babel/plugin-transform-property-literals@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@atproto/common': 0.1.1 - '@atproto/crypto': 0.1.0 - '@ipld/dag-cbor': 7.0.3 - axios: 1.6.7 - multiformats: 9.9.0 - uint8arrays: 3.0.0 - zod: 3.23.4 - transitivePeerDependencies: - - debug + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@did-plc/server@0.0.1: - resolution: {integrity: sha512-GtxxHcOrOQ6fNI1ufq3Zqjc2PtWqPZOdsuzlwtxiH9XibUGwDkb0GmaBHyU5GiOxOKZEW1GspZ8mreBA6XOlTQ==} + /@babel/plugin-transform-react-display-name@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-mvoQg2f9p2qlpDQRBC7M3c3XTr0k7cp/0+kFKKO/7Gtu0LSw16eKB+Fabe2bDT/UpsyasTBBkAnbdsLrkD5XMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@atproto/common': 0.1.0 - '@atproto/crypto': 0.1.0 - '@did-plc/lib': 0.0.4 - axios: 1.4.0 - cors: 2.8.5 - express: 4.18.2 - express-async-errors: 3.1.1(express@4.18.2) - http-terminator: 3.2.0 - kysely: 0.23.5 - multiformats: 9.9.0 - pg: 8.10.0 - pino: 8.15.0 - pino-http: 8.4.0 - transitivePeerDependencies: - - debug - - pg-native - - supports-color + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@babel/plugin-transform-react-jsx-self@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w==} + engines: {node: '>=6.9.0'} peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@babel/core': ^7.0.0-0 dependencies: - eslint: 8.57.0 - eslint-visitor-keys: 3.4.3 - dev: true - - /@eslint-community/regexpp@4.10.0: - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@eslint/eslintrc@2.1.4: - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.6.1 - globals: 13.21.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@eslint/js@8.57.0: - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true + /@babel/plugin-transform-react-jsx@7.23.4(@babel/core@7.18.6): + resolution: {integrity: sha512-5xOpoPguCZCRbo/JeHlloSkTA8Bld1J/E1/kLfD1nsuiW1m8tduTA1ERCgIZokDflX/IBzKcqR3l7VlRgiIfHA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.18.6) + '@babel/types': 7.24.5 + dev: false - /@fastify/busboy@2.1.0: - resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} - engines: {node: '>=14'} - - /@fastify/deepmerge@1.3.0: - resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + /@babel/plugin-transform-regenerator@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + regenerator-transform: 0.15.2 + dev: false - /@gar/promisify@1.1.3: - resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - dev: true + /@babel/plugin-transform-reserved-words@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@humanwhocodes/config-array@0.11.14: - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} + /@babel/plugin-transform-runtime@7.24.3(@babel/core@7.18.6): + resolution: {integrity: sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@humanwhocodes/object-schema': 2.0.2 - debug: 4.3.4 - minimatch: 3.1.2 + '@babel/core': 7.18.6 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-plugin-utils': 7.24.5 + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.18.6) + babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.18.6) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.18.6) + semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true - - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true - - /@humanwhocodes/object-schema@2.0.2: - resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} - dev: true - - /@ioredis/commands@1.2.0: - resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + dev: false - /@ipld/car@3.2.3: - resolution: {integrity: sha512-pXE5mFJlXzJVaBwqAJKGlKqMmxq8H2SLEWBJgkeBDPBIN8ZbscPc3I9itkSQSlS/s6Fgx35Ri3LDTDtodQjCCQ==} + /@babel/plugin-transform-shorthand-properties@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@ipld/dag-cbor': 7.0.3 - multiformats: 9.9.0 - varint: 6.0.0 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 dev: false - /@ipld/dag-cbor@7.0.3: - resolution: {integrity: sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==} + /@babel/plugin-transform-spread@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - cborg: 1.10.2 - multiformats: 9.9.0 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + dev: false - /@isaacs/cliui@8.0.2: - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} + /@babel/plugin-transform-sticky-regex@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 dev: false - /@istanbuljs/load-nyc-config@1.1.0: - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} + /@babel/plugin-transform-template-literals@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.1 - resolve-from: 5.0.0 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@istanbuljs/schema@0.1.3: - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - dev: true + /@babel/plugin-transform-typeof-symbol@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@jest/console@28.1.3: - resolution: {integrity: sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/plugin-transform-typescript@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-E0VWu/hk83BIFUWnsKZ4D81KXjN5L3MobvevOHErASk9IPwKHOkTgvqzvNo1yP/ePJWqqK2SpUR5z+KQbl6NVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@jest/types': 28.1.3 - '@types/node': 18.19.24 - chalk: 4.1.2 - jest-message-util: 28.1.3 - jest-util: 28.1.3 - slash: 3.0.0 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.24.5(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.18.6) + dev: false - /@jest/core@28.1.3(ts-node@10.8.2): - resolution: {integrity: sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/plugin-transform-unicode-escapes@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==} + engines: {node: '>=6.9.0'} peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + '@babel/core': ^7.0.0-0 dependencies: - '@jest/console': 28.1.3 - '@jest/reporters': 28.1.3 - '@jest/test-result': 28.1.3 - '@jest/transform': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.19.24 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.8.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 28.1.3 - jest-config: 28.1.3(@types/node@18.19.24)(ts-node@10.8.2) - jest-haste-map: 28.1.3 - jest-message-util: 28.1.3 - jest-regex-util: 28.0.2 - jest-resolve: 28.1.3 - jest-resolve-dependencies: 28.1.3 - jest-runner: 28.1.3 - jest-runtime: 28.1.3 - jest-snapshot: 28.1.3 - jest-util: 28.1.3 - jest-validate: 28.1.3 - jest-watcher: 28.1.3 - micromatch: 4.0.5 - pretty-format: 28.1.3 - rimraf: 3.0.2 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@jest/create-cache-key-function@27.5.1: - resolution: {integrity: sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + /@babel/plugin-transform-unicode-property-regex@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@jest/types': 27.5.1 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@jest/environment@28.1.3: - resolution: {integrity: sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/plugin-transform-unicode-regex@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@jest/fake-timers': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.19.24 - jest-mock: 28.1.3 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@jest/expect-utils@28.1.3: - resolution: {integrity: sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/plugin-transform-unicode-sets-regex@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: - jest-get-type: 28.0.2 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.6) + '@babel/helper-plugin-utils': 7.24.5 + dev: false - /@jest/expect@28.1.3: - resolution: {integrity: sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/preset-env@7.24.5(@babel/core@7.18.6): + resolution: {integrity: sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - expect: 28.1.3 - jest-snapshot: 28.1.3 + '@babel/compat-data': 7.24.4 + '@babel/core': 7.18.6 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-validator-option': 7.23.5 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.18.6) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.18.6) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.18.6) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-import-assertions': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-syntax-import-attributes': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.18.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.18.6) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-async-generator-functions': 7.24.3(@babel/core@7.18.6) + '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-block-scoped-functions': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-block-scoping': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-class-properties': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-class-static-block': 7.24.4(@babel/core@7.18.6) + '@babel/plugin-transform-classes': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-destructuring': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-dotall-regex': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-duplicate-keys': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-dynamic-import': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-exponentiation-operator': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-export-namespace-from': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-for-of': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-json-strings': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-logical-assignment-operators': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-member-expression-literals': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-modules-amd': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-modules-systemjs': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-modules-umd': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-new-target': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-nullish-coalescing-operator': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-numeric-separator': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-object-rest-spread': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-object-super': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-optional-catch-binding': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-optional-chaining': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-private-property-in-object': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-property-literals': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-regenerator': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-reserved-words': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-template-literals': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-typeof-symbol': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-unicode-escapes': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-unicode-property-regex': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-unicode-sets-regex': 7.24.1(@babel/core@7.18.6) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.18.6) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.18.6) + babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.18.6) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.18.6) + core-js-compat: 3.37.1 + semver: 6.3.1 transitivePeerDependencies: - supports-color - dev: true + dev: false - /@jest/fake-timers@28.1.3: - resolution: {integrity: sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/preset-flow@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@jest/types': 28.1.3 - '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.19.24 - jest-message-util: 28.1.3 - jest-mock: 28.1.3 - jest-util: 28.1.3 - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-validator-option': 7.23.5 + '@babel/plugin-transform-flow-strip-types': 7.24.1(@babel/core@7.18.6) + dev: false - /@jest/globals@28.1.3: - resolution: {integrity: sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.18.6): + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 dependencies: - '@jest/environment': 28.1.3 - '@jest/expect': 28.1.3 - '@jest/types': 28.1.3 - transitivePeerDependencies: - - supports-color - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/types': 7.22.10 + esutils: 2.0.3 + dev: false - /@jest/reporters@28.1.3: - resolution: {integrity: sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/preset-typescript@7.24.1(@babel/core@7.18.6): + resolution: {integrity: sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ==} + engines: {node: '>=6.9.0'} peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true + '@babel/core': ^7.0.0-0 dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 28.1.3 - '@jest/test-result': 28.1.3 - '@jest/transform': 28.1.3 - '@jest/types': 28.1.3 - '@jridgewell/trace-mapping': 0.3.19 - '@types/node': 18.19.24 - chalk: 4.1.2 - collect-v8-coverage: 1.0.2 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.0 - istanbul-lib-instrument: 5.2.1 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.6 - jest-message-util: 28.1.3 - jest-util: 28.1.3 - jest-worker: 28.1.3 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - terminal-link: 2.1.1 - v8-to-istanbul: 9.1.0 - transitivePeerDependencies: - - supports-color - dev: true + '@babel/core': 7.18.6 + '@babel/helper-plugin-utils': 7.24.5 + '@babel/helper-validator-option': 7.23.5 + '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-typescript': 7.24.5(@babel/core@7.18.6) + dev: false - /@jest/schemas@28.1.3: - resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/register@7.23.7(@babel/core@7.18.6): + resolution: {integrity: sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: - '@sinclair/typebox': 0.24.51 - dev: true + '@babel/core': 7.18.6 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.6 + source-map-support: 0.5.21 + dev: false - /@jest/source-map@28.1.2: - resolution: {integrity: sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/regjsgen@0.8.0: + resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} + dev: false + + /@babel/runtime@7.22.10: + resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} + engines: {node: '>=6.9.0'} dependencies: - '@jridgewell/trace-mapping': 0.3.19 - callsites: 3.1.0 - graceful-fs: 4.2.11 - dev: true + regenerator-runtime: 0.14.0 - /@jest/test-result@28.1.3: - resolution: {integrity: sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/template@7.22.5: + resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} + engines: {node: '>=6.9.0'} dependencies: - '@jest/console': 28.1.3 - '@jest/types': 28.1.3 - '@types/istanbul-lib-coverage': 2.0.4 - collect-v8-coverage: 1.0.2 - dev: true + '@babel/code-frame': 7.22.10 + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 - /@jest/test-sequencer@28.1.3: - resolution: {integrity: sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/template@7.24.0: + resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} + engines: {node: '>=6.9.0'} dependencies: - '@jest/test-result': 28.1.3 - graceful-fs: 4.2.11 - jest-haste-map: 28.1.3 - slash: 3.0.0 - dev: true + '@babel/code-frame': 7.24.2 + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 + dev: false - /@jest/transform@28.1.3: - resolution: {integrity: sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/traverse@7.22.10: + resolution: {integrity: sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==} + engines: {node: '>=6.9.0'} dependencies: - '@babel/core': 7.18.6 - '@jest/types': 28.1.3 - '@jridgewell/trace-mapping': 0.3.19 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 1.9.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 28.1.3 - jest-regex-util: 28.0.2 - jest-util: 28.1.3 - micromatch: 4.0.5 - pirates: 4.0.6 - slash: 3.0.0 - write-file-atomic: 4.0.2 + '@babel/code-frame': 7.22.10 + '@babel/generator': 7.22.10 + '@babel/helper-environment-visitor': 7.22.5 + '@babel/helper-function-name': 7.22.5 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + debug: 4.3.4 + globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: true - /@jest/types@27.5.1: - resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + /@babel/traverse@7.24.5: + resolution: {integrity: sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==} + engines: {node: '>=6.9.0'} dependencies: - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 18.19.24 - '@types/yargs': 16.0.5 - chalk: 4.1.2 - dev: true + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.24.5 + '@babel/parser': 7.24.5 + '@babel/types': 7.24.5 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: false - /@jest/types@28.1.3: - resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@babel/types@7.22.10: + resolution: {integrity: sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==} + engines: {node: '>=6.9.0'} dependencies: - '@jest/schemas': 28.1.3 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 18.19.24 - '@types/yargs': 17.0.24 - chalk: 4.1.2 - dev: true + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.5 + to-fast-properties: 2.0.0 - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} + /@babel/types@7.24.5: + resolution: {integrity: sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==} + engines: {node: '>=6.9.0'} dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.19 - dev: true + '@babel/helper-string-parser': 7.24.1 + '@babel/helper-validator-identifier': 7.24.5 + to-fast-properties: 2.0.0 + dev: false - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} - engines: {node: '>=6.0.0'} + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} + /@bufbuild/buf-darwin-arm64@1.28.1: + resolution: {integrity: sha512-nAyvwKkcd8qQTExCZo5MtSRhXLK7e3vzKFKHjXfkveRakSUST2HFlFZAHfErZimN4wBrPTN0V0hNRU8PPjkMpQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true dev: true + optional: true - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + /@bufbuild/buf-darwin-x64@1.28.1: + resolution: {integrity: sha512-b0eT3xd3vX5a5lWAbo5h7FPuf9MsOJI4I39qs4TZnrlZ8BOuPfqzwzijiFf9UCwaX2vR1NQXexIoQ80Ci+fCHw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true dev: true + optional: true - /@jridgewell/trace-mapping@0.3.19: - resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} - dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 + /@bufbuild/buf-linux-aarch64@1.28.1: + resolution: {integrity: sha512-p5h9bZCVLMh8No9/7k7ulXzsFx5P7Lu6DiUMjSJ6aBXPMYo6Xl7r/6L2cQkpsZ53HMtIxCgMYS9a7zoS4K8wIw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true dev: true + optional: true - /@jridgewell/trace-mapping@0.3.9: - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 + /@bufbuild/buf-linux-x64@1.28.1: + resolution: {integrity: sha512-fVJ3DiRigIso06jgEl+JNp59Y5t2pxDHd10d3SA4r+14sXbZ2J7Gy/wBqVXPry4x/jW567KKlvmhg7M5ZBgCQQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true dev: true + optional: true - /@manypkg/find-root@1.1.0: - resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} - dependencies: - '@babel/runtime': 7.22.10 - '@types/node': 12.20.55 - find-up: 4.1.0 - fs-extra: 8.1.0 + /@bufbuild/buf-win32-arm64@1.28.1: + resolution: {integrity: sha512-KJiRJpugQRK/jXC46Xjlb68UydWhCZj2jHdWLIwNtgXd1WTJ3LngChZV7Y6pPK08pwBAVz0JYeVbD5IlTCD4TQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true dev: true + optional: true - /@manypkg/get-packages@1.1.3: - resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - dependencies: - '@babel/runtime': 7.22.10 - '@changesets/types': 4.1.0 - '@manypkg/find-root': 1.1.0 - fs-extra: 8.1.0 - globby: 11.1.0 - read-yaml-file: 1.1.0 + /@bufbuild/buf-win32-x64@1.28.1: + resolution: {integrity: sha512-vMnc+7OVCkmlRWQsgYHgUqiBPRIjD8XeoRyApJ07YZzGs7DkRH4LhvmacJbLd3wORylbn6gLz3pQa8J/M61mzg==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true dev: true + optional: true - /@noble/curves@1.1.0: - resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==} - dependencies: - '@noble/hashes': 1.3.1 - dev: false - - /@noble/hashes@1.3.1: - resolution: {integrity: sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==} - engines: {node: '>= 16'} - dev: false + /@bufbuild/buf@1.28.1: + resolution: {integrity: sha512-WRDagrf0uBjfV9s5eyrSPJDcdI4A5Q7JMCA4aMrHRR8fo/TTjniDBjJprszhaguqsDkn/LS4QIu92HVFZCrl9A==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@bufbuild/buf-darwin-arm64': 1.28.1 + '@bufbuild/buf-darwin-x64': 1.28.1 + '@bufbuild/buf-linux-aarch64': 1.28.1 + '@bufbuild/buf-linux-x64': 1.28.1 + '@bufbuild/buf-win32-arm64': 1.28.1 + '@bufbuild/buf-win32-x64': 1.28.1 + dev: true - /@noble/secp256k1@1.7.1: - resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} + /@bufbuild/protobuf@1.6.0: + resolution: {integrity: sha512-hp19vSFgNw3wBBcVBx5qo5pufCqjaJ0Cfk5H/pfjNOfNWU+4/w0QVOmfAOZNRrNWRrVuaJWxcN8P2vhOkkzbBQ==} - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - - /@npmcli/fs@2.1.2: - resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - dependencies: - '@gar/promisify': 1.1.3 - semver: 7.6.0 - dev: true - - /@npmcli/move-file@2.0.1: - resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This functionality has been moved to @npmcli/fs - dependencies: - mkdirp: 1.0.4 - rimraf: 3.0.2 - dev: true - - /@opentelemetry/api@1.7.0: - resolution: {integrity: sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==} - engines: {node: '>=8.0.0'} - dev: false - - /@opentelemetry/api@1.8.0: - resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==} - engines: {node: '>=8.0.0'} - dev: false - - /@opentelemetry/core@1.18.1(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-kvnUqezHMhsQvdsnhnqTNfAJs3ox/isB0SVrM1dhVFw7SsB7TstuVa6fgWnN2GdPyilIFLUvvbTZoVRmx6eiRg==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.8.0' - dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/semantic-conventions': 1.18.1 - dev: false - - /@opentelemetry/instrumentation@0.44.0(@opentelemetry/api@1.7.0): - resolution: {integrity: sha512-B6OxJTRRCceAhhnPDBshyQO7K07/ltX3quOLu0icEvPK9QZ7r9P1y0RQX8O5DxB4vTv4URRkxkg+aFU/plNtQw==} + /@bufbuild/protoc-gen-es@1.6.0(@bufbuild/protobuf@1.6.0): + resolution: {integrity: sha512-m0akOPWeD5UBfGdZyudrbnmdjI8l/ZHlP8TyEIcj7qMCR4kh68tMtGvrjRzj5ynIpavrr6G7P06XP9F9f2MDRw==} engines: {node: '>=14'} + hasBin: true peerDependencies: - '@opentelemetry/api': ^1.3.0 + '@bufbuild/protobuf': 1.6.0 + peerDependenciesMeta: + '@bufbuild/protobuf': + optional: true dependencies: - '@opentelemetry/api': 1.7.0 - '@types/shimmer': 1.0.5 - import-in-the-middle: 1.4.2 - require-in-the-middle: 7.2.0 - semver: 7.5.4 - shimmer: 1.2.1 + '@bufbuild/protobuf': 1.6.0 + '@bufbuild/protoplugin': 1.6.0 transitivePeerDependencies: - supports-color - dev: false + dev: true - /@opentelemetry/instrumentation@0.45.1(@opentelemetry/api@1.8.0): - resolution: {integrity: sha512-V1Cr0g8hSg35lpW3G/GYVZurrhHrQZJdmP68WyJ83f1FDn3iru+/Vnlto9kiOSm7PHhW+pZGdb9Fbv+mkQ31CA==} - engines: {node: '>=14'} - peerDependencies: - '@opentelemetry/api': ^1.3.0 + /@bufbuild/protoplugin@1.6.0: + resolution: {integrity: sha512-o53ZsvojHQkAPoC9v5sJifY2OfXdRU8DO3QpPoJ+QuvYcfB9Zb3DZkNMQRyfEbF4TVYiaQ0mZzZl1mESDdyCxA==} dependencies: - '@opentelemetry/api': 1.8.0 - '@types/shimmer': 1.0.5 - import-in-the-middle: 1.4.2 - require-in-the-middle: 7.2.0 - semver: 7.5.4 - shimmer: 1.2.1 + '@bufbuild/protobuf': 1.6.0 + '@typescript/vfs': 1.5.0 + typescript: 4.5.2 transitivePeerDependencies: - supports-color - dev: false - - /@opentelemetry/semantic-conventions@1.18.1: - resolution: {integrity: sha512-+NLGHr6VZwcgE/2lw8zDIufOCGnzsA5CbQIMleXZTrgkBd0TanCX+MiDYJ1TOS4KL/Tqk0nFRxawnaYr6pkZkA==} - engines: {node: '>=14'} - dev: false - - /@pkgjs/parseargs@0.11.0: - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - requiresBuild: true - dev: false - optional: true - - /@pkgr/core@0.1.1: - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - dev: true - - /@protobufjs/aspromise@1.1.2: - resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} - dev: false - - /@protobufjs/base64@1.1.2: - resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} - dev: false - - /@protobufjs/codegen@2.0.4: - resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} - dev: false - - /@protobufjs/eventemitter@1.1.0: - resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} - dev: false - - /@protobufjs/fetch@1.1.0: - resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} - dependencies: - '@protobufjs/aspromise': 1.1.2 - '@protobufjs/inquire': 1.1.0 - dev: false - - /@protobufjs/float@1.0.2: - resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} - dev: false - - /@protobufjs/inquire@1.1.0: - resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} - dev: false - - /@protobufjs/path@1.1.2: - resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} - dev: false - - /@protobufjs/pool@1.1.0: - resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} - dev: false - - /@protobufjs/utf8@1.1.0: - resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - dev: false - - /@sinclair/typebox@0.24.51: - resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==} - dev: true - - /@sinonjs/commons@1.8.6: - resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} - dependencies: - type-detect: 4.0.8 - dev: true - - /@sinonjs/fake-timers@9.1.2: - resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==} - dependencies: - '@sinonjs/commons': 1.8.6 dev: true - /@smithy/abort-controller@1.1.0: - resolution: {integrity: sha512-5imgGUlZL4dW4YWdMYAKLmal9ny/tlenM81QZY7xYyb76z9Z/QOg7oM5Ak9HQl8QfFTlGVWwcMXl+54jroRgEQ==} - engines: {node: '>=14.0.0'} - dependencies: - '@smithy/types': 1.2.0 - tslib: 2.6.2 - dev: false - - /@smithy/types@1.2.0: - resolution: {integrity: sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==} - engines: {node: '>=14.0.0'} - dependencies: - tslib: 2.6.2 - dev: false - - /@swc/core-darwin-arm64@1.3.42: - resolution: {integrity: sha512-hM6RrZFyoCM9mX3cj/zM5oXwhAqjUdOCLXJx7KTQps7NIkv/Qjvobgvyf2gAb89j3ARNo9NdIoLjTjJ6oALtiA==} - engines: {node: '>=10'} + /@cbor-extract/cbor-extract-darwin-arm64@2.2.0: + resolution: {integrity: sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==} cpu: [arm64] os: [darwin] requiresBuild: true - dev: true + dev: false optional: true - /@swc/core-darwin-x64@1.3.42: - resolution: {integrity: sha512-bjsWtHMb6wJK1+RGlBs2USvgZ0txlMk11y0qBLKo32gLKTqzUwRw0Fmfzuf6Ue2a/w//7eqMlPFEre4LvJajGw==} - engines: {node: '>=10'} + /@cbor-extract/cbor-extract-darwin-x64@2.2.0: + resolution: {integrity: sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==} cpu: [x64] os: [darwin] requiresBuild: true - dev: true - optional: true - - /@swc/core-linux-arm-gnueabihf@1.3.42: - resolution: {integrity: sha512-Oe0ggMz3MyqXNfeVmY+bBTL0hFSNY3bx8dhcqsh4vXk/ZVGse94QoC4dd92LuPHmKT0x6nsUzB86x2jU9QHW5g==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true + dev: false optional: true - /@swc/core-linux-arm64-gnu@1.3.42: - resolution: {integrity: sha512-ZJsa8NIW1RLmmHGTJCbM7OPSbBZ9rOMrLqDtUOGrT0uoJXZnnQqolflamB5wviW0X6h3Z3/PSTNGNDCJ3u3Lqg==} - engines: {node: '>=10'} + /@cbor-extract/cbor-extract-linux-arm64@2.2.0: + resolution: {integrity: sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==} cpu: [arm64] os: [linux] requiresBuild: true - dev: true + dev: false optional: true - /@swc/core-linux-arm64-musl@1.3.42: - resolution: {integrity: sha512-YpZwlFAfOp5vkm/uVUJX1O7N3yJDO1fDQRWqsOPPNyIJkI2ydlRQtgN6ZylC159Qv+TimfXnGTlNr7o3iBAqjg==} - engines: {node: '>=10'} - cpu: [arm64] + /@cbor-extract/cbor-extract-linux-arm@2.2.0: + resolution: {integrity: sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==} + cpu: [arm] os: [linux] requiresBuild: true - dev: true + dev: false optional: true - /@swc/core-linux-x64-gnu@1.3.42: - resolution: {integrity: sha512-0ccpKnsZbyHBzaQFdP8U9i29nvOfKitm6oJfdJzlqsY/jCqwvD8kv2CAKSK8WhJz//ExI2LqNrDI0yazx5j7+A==} - engines: {node: '>=10'} + /@cbor-extract/cbor-extract-linux-x64@2.2.0: + resolution: {integrity: sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==} cpu: [x64] os: [linux] requiresBuild: true - dev: true + dev: false optional: true - /@swc/core-linux-x64-musl@1.3.42: - resolution: {integrity: sha512-7eckRRuTZ6+3K21uyfXXgc2ZCg0mSWRRNwNT3wap2bYkKPeqTgb8pm8xYSZNEiMuDonHEat6XCCV36lFY6kOdQ==} - engines: {node: '>=10'} + /@cbor-extract/cbor-extract-win32-x64@2.2.0: + resolution: {integrity: sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==} cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@swc/core-win32-arm64-msvc@1.3.42: - resolution: {integrity: sha512-t27dJkdw0GWANdN4TV0lY/V5vTYSx5SRjyzzZolep358ueCGuN1XFf1R0JcCbd1ojosnkQg2L7A7991UjXingg==} - engines: {node: '>=10'} - cpu: [arm64] os: [win32] requiresBuild: true - dev: true - optional: true - - /@swc/core-win32-ia32-msvc@1.3.42: - resolution: {integrity: sha512-xfpc/Zt/aMILX4IX0e3loZaFyrae37u3MJCv1gJxgqrpeLi7efIQr3AmERkTK3mxTO6R5urSliWw2W3FyZ7D3Q==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true + dev: false optional: true - /@swc/core-win32-x64-msvc@1.3.42: - resolution: {integrity: sha512-ra2K4Tu++EJLPhzZ6L8hWUsk94TdK/2UKhL9dzCBhtzKUixsGCEqhtqH1zISXNvW8qaVLFIMUP37ULe80/IJaA==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - requiresBuild: true + /@changesets/apply-release-plan@6.1.4: + resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/config': 2.3.1 + '@changesets/get-version-range-type': 0.3.2 + '@changesets/git': 2.0.0 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.7.1 + resolve-from: 5.0.0 + semver: 7.5.4 dev: true - optional: true - /@swc/core@1.3.42: - resolution: {integrity: sha512-nVFUd5+7tGniM2cT3LXaqnu3735Cu4az8A9gAKK+8sdpASI52SWuqfDBmjFCK9xG90MiVDVp2PTZr0BWqCIzpw==} - engines: {node: '>=10'} - requiresBuild: true - optionalDependencies: - '@swc/core-darwin-arm64': 1.3.42 - '@swc/core-darwin-x64': 1.3.42 - '@swc/core-linux-arm-gnueabihf': 1.3.42 - '@swc/core-linux-arm64-gnu': 1.3.42 - '@swc/core-linux-arm64-musl': 1.3.42 - '@swc/core-linux-x64-gnu': 1.3.42 - '@swc/core-linux-x64-musl': 1.3.42 - '@swc/core-win32-arm64-msvc': 1.3.42 - '@swc/core-win32-ia32-msvc': 1.3.42 - '@swc/core-win32-x64-msvc': 1.3.42 + /@changesets/assemble-release-plan@5.2.4: + resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==} + dependencies: + '@babel/runtime': 7.22.10 + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.6 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + semver: 7.5.4 dev: true - /@swc/jest@0.2.24(@swc/core@1.3.42): - resolution: {integrity: sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q==} - engines: {npm: '>= 7.0.0'} - peerDependencies: - '@swc/core': '*' + /@changesets/changelog-git@0.1.14: + resolution: {integrity: sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==} dependencies: - '@jest/create-cache-key-function': 27.5.1 - '@swc/core': 1.3.42 - jsonc-parser: 3.2.0 + '@changesets/types': 5.2.1 dev: true - /@tokenizer/token@0.3.0: - resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} - - /@tootallnate/once@2.0.0: - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} + /@changesets/changelog-github@0.4.8: + resolution: {integrity: sha512-jR1DHibkMAb5v/8ym77E4AMNWZKB5NPzw5a5Wtqm1JepAuIF+hrKp2u04NKM14oBZhHglkCfrla9uq8ORnK/dw==} + dependencies: + '@changesets/get-github-info': 0.5.2 + '@changesets/types': 5.2.1 + dotenv: 8.6.0 + transitivePeerDependencies: + - encoding dev: true - /@ts-morph/common@0.17.0: - resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==} + /@changesets/cli@2.26.2: + resolution: {integrity: sha512-dnWrJTmRR8bCHikJHl9b9HW3gXACCehz4OasrXpMp7sx97ECuBGGNjJhjPhdZNCvMy9mn4BWdplI323IbqsRig==} + hasBin: true dependencies: - fast-glob: 3.3.1 - minimatch: 5.1.6 - mkdirp: 1.0.4 - path-browserify: 1.0.1 - dev: false - - /@tsconfig/node10@1.0.9: - resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + '@babel/runtime': 7.22.10 + '@changesets/apply-release-plan': 6.1.4 + '@changesets/assemble-release-plan': 5.2.4 + '@changesets/changelog-git': 0.1.14 + '@changesets/config': 2.3.1 + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.6 + '@changesets/get-release-plan': 3.0.17 + '@changesets/git': 2.0.0 + '@changesets/logger': 0.0.5 + '@changesets/pre': 1.0.14 + '@changesets/read': 0.5.9 + '@changesets/types': 5.2.1 + '@changesets/write': 0.2.3 + '@manypkg/get-packages': 1.1.3 + '@types/is-ci': 3.0.0 + '@types/semver': 7.5.0 + ansi-colors: 4.1.3 + chalk: 2.4.2 + enquirer: 2.4.1 + external-editor: 3.1.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + is-ci: 3.0.1 + meow: 6.1.1 + outdent: 0.5.0 + p-limit: 2.3.0 + preferred-pm: 3.0.3 + resolve-from: 5.0.0 + semver: 7.5.4 + spawndamnit: 2.0.0 + term-size: 2.2.1 + tty-table: 4.2.1 dev: true - /@tsconfig/node12@1.0.11: - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + /@changesets/config@2.3.1: + resolution: {integrity: sha512-PQXaJl82CfIXddUOppj4zWu+987GCw2M+eQcOepxN5s+kvnsZOwjEJO3DH9eVy+OP6Pg/KFEWdsECFEYTtbg6w==} + dependencies: + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.6 + '@changesets/logger': 0.0.5 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.5 dev: true - /@tsconfig/node14@1.0.3: - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + /@changesets/errors@0.1.4: + resolution: {integrity: sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==} + dependencies: + extendable-error: 0.1.7 dev: true - /@tsconfig/node16@1.0.4: - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + /@changesets/get-dependents-graph@1.3.6: + resolution: {integrity: sha512-Q/sLgBANmkvUm09GgRsAvEtY3p1/5OCzgBE5vX3vgb5CvW0j7CEljocx5oPXeQSNph6FXulJlXV3Re/v3K3P3Q==} + dependencies: + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + chalk: 2.4.2 + fs-extra: 7.0.1 + semver: 7.5.4 dev: true - /@types/babel__core@7.20.1: - resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} + /@changesets/get-github-info@0.5.2: + resolution: {integrity: sha512-JppheLu7S114aEs157fOZDjFqUDpm7eHdq5E8SSR0gUBTEK0cNSHsrSR5a66xs0z3RWuo46QvA3vawp8BxDHvg==} dependencies: - '@babel/parser': 7.22.10 - '@babel/types': 7.22.10 - '@types/babel__generator': 7.6.4 - '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.20.1 + dataloader: 1.4.0 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding dev: true - /@types/babel__generator@7.6.4: - resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} + /@changesets/get-release-plan@3.0.17: + resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==} dependencies: - '@babel/types': 7.22.10 + '@babel/runtime': 7.22.10 + '@changesets/assemble-release-plan': 5.2.4 + '@changesets/config': 2.3.1 + '@changesets/pre': 1.0.14 + '@changesets/read': 0.5.9 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 dev: true - /@types/babel__template@7.4.1: - resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} + /@changesets/get-version-range-type@0.3.2: + resolution: {integrity: sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==} + dev: true + + /@changesets/git@2.0.0: + resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==} dependencies: - '@babel/parser': 7.22.10 - '@babel/types': 7.22.10 + '@babel/runtime': 7.22.10 + '@changesets/errors': 0.1.4 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.5 + spawndamnit: 2.0.0 dev: true - /@types/babel__traverse@7.20.1: - resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==} + /@changesets/logger@0.0.5: + resolution: {integrity: sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==} dependencies: - '@babel/types': 7.22.10 + chalk: 2.4.2 dev: true - /@types/bn.js@5.1.1: - resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==} + /@changesets/parse@0.3.16: + resolution: {integrity: sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==} dependencies: - '@types/node': 18.19.24 + '@changesets/types': 5.2.1 + js-yaml: 3.14.1 + dev: true - /@types/body-parser@1.19.2: - resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + /@changesets/pre@1.0.14: + resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==} dependencies: - '@types/connect': 3.4.35 - '@types/node': 18.19.24 + '@babel/runtime': 7.22.10 + '@changesets/errors': 0.1.4 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + dev: true - /@types/connect@3.4.35: - resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + /@changesets/read@0.5.9: + resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==} dependencies: - '@types/node': 18.19.24 + '@babel/runtime': 7.22.10 + '@changesets/git': 2.0.0 + '@changesets/logger': 0.0.5 + '@changesets/parse': 0.3.16 + '@changesets/types': 5.2.1 + chalk: 2.4.2 + fs-extra: 7.0.1 + p-filter: 2.1.0 + dev: true - /@types/cors@2.8.12: - resolution: {integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==} + /@changesets/types@4.1.0: + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} dev: true - /@types/disposable-email@0.2.0: - resolution: {integrity: sha512-i4fPC8+a5j7RlKVe9cOHz6cYVxkSFYuJ78GB3EJPMfBJURWmEsD4gb/gD48j7KAWe0M8ZvdJR6a6GaDohTYttw==} + /@changesets/types@5.2.1: + resolution: {integrity: sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==} dev: true - /@types/elliptic@6.4.14: - resolution: {integrity: sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ==} + /@changesets/write@0.2.3: + resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==} dependencies: - '@types/bn.js': 5.1.1 + '@babel/runtime': 7.22.10 + '@changesets/types': 5.2.1 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.7.1 + dev: true - /@types/express-serve-static-core@4.17.36: - resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==} + /@connectrpc/connect-express@1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect-node@1.3.0)(@connectrpc/connect@1.3.0): + resolution: {integrity: sha512-6wbaQheD9cb4DnU1PvgVQdB1XPfA0bhlA0V0ZKx6oJJnTgGEYBzPrQztmqs5XW36/r+qJRfMgaVzZfX8MLafgA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 + '@connectrpc/connect': 1.3.0 + '@connectrpc/connect-node': 1.3.0 dependencies: - '@types/node': 18.17.8 - '@types/qs': 6.9.7 - '@types/range-parser': 1.2.4 - '@types/send': 0.17.1 - - /@types/express@4.17.13: - resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==} - dependencies: - '@types/body-parser': 1.19.2 - '@types/express-serve-static-core': 4.17.36 - '@types/qs': 6.9.7 - '@types/serve-static': 1.15.2 - dev: true + '@bufbuild/protobuf': 1.6.0 + '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.6.0) + '@connectrpc/connect-node': 1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect@1.3.0) + '@types/express': 4.17.21 + dev: false - /@types/express@4.17.21: - resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + /@connectrpc/connect-node@1.3.0(@bufbuild/protobuf@1.6.0)(@connectrpc/connect@1.3.0): + resolution: {integrity: sha512-2fV/z/8MUFOkTn2Gbm7T/qvRfkpt/D/w7ykYqACZRH6VNG/faY4lH2wUZiNkwv9tzTrECKOJFyPsaGs3nRYK3w==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 + '@connectrpc/connect': 1.3.0 dependencies: - '@types/body-parser': 1.19.2 - '@types/express-serve-static-core': 4.17.36 - '@types/qs': 6.9.7 - '@types/serve-static': 1.15.2 + '@bufbuild/protobuf': 1.6.0 + '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.6.0) + undici: 5.28.2 - /@types/graceful-fs@4.1.6: - resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} + /@connectrpc/connect@1.3.0(@bufbuild/protobuf@1.6.0): + resolution: {integrity: sha512-kTeWxJnLLtxKc2ZSDN0rIBgwfP8RwcLknthX4AKlIAmN9ZC4gGnCbwp+3BKcP/WH5c8zGBAWqSY3zeqCM+ah7w==} + peerDependencies: + '@bufbuild/protobuf': ^1.4.2 dependencies: - '@types/node': 18.19.24 - dev: true - - /@types/http-errors@2.0.1: - resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==} + '@bufbuild/protobuf': 1.6.0 - /@types/is-ci@3.0.0: - resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} + /@connectrpc/protoc-gen-connect-es@1.3.0(@bufbuild/protoc-gen-es@1.6.0)(@connectrpc/connect@1.3.0): + resolution: {integrity: sha512-UbQN48c0zafo5EFSsh3POIJP6ofYiAgKE1aFOZ2Er4W3flUYihydZdM6TQauPkn7jDj4w9jjLSTTZ9//ecUbPA==} + engines: {node: '>=16.0.0'} + hasBin: true + peerDependencies: + '@bufbuild/protoc-gen-es': ^1.6.0 + '@connectrpc/connect': 1.3.0 + peerDependenciesMeta: + '@bufbuild/protoc-gen-es': + optional: true + '@connectrpc/connect': + optional: true dependencies: - ci-info: 3.8.0 - dev: true - - /@types/istanbul-lib-coverage@2.0.4: - resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + '@bufbuild/protobuf': 1.6.0 + '@bufbuild/protoc-gen-es': 1.6.0(@bufbuild/protobuf@1.6.0) + '@bufbuild/protoplugin': 1.6.0 + '@connectrpc/connect': 1.3.0(@bufbuild/protobuf@1.6.0) + transitivePeerDependencies: + - supports-color dev: true - /@types/istanbul-lib-report@3.0.0: - resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} dependencies: - '@types/istanbul-lib-coverage': 2.0.4 + '@jridgewell/trace-mapping': 0.3.9 dev: true - /@types/istanbul-reports@3.0.1: - resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + /@datadog/native-appsec@2.0.0: + resolution: {integrity: sha512-XHARZ6MVgbnfOUO6/F3ZoZ7poXHJCNYFlgcyS2Xetuk9ITA5bfcooX2B2F7tReVB+RLJ+j8bsm0t55SyF04KDw==} + engines: {node: '>=12'} + requiresBuild: true dependencies: - '@types/istanbul-lib-report': 3.0.0 - dev: true + node-gyp-build: 3.9.0 + dev: false - /@types/jest@28.1.4: - resolution: {integrity: sha512-telv6G5N7zRJiLcI3Rs3o+ipZ28EnE+7EvF0pSrt2pZOMnAVI/f+6/LucDxOvcBcTeTL3JMF744BbVQAVBUQRA==} + /@datadog/native-appsec@4.0.0: + resolution: {integrity: sha512-myTguXJ3VQHS2E1ylNsSF1avNpDmq5t+K4Q47wdzeakGc3sDIDDyEbvuFTujl9c9wBIkup94O1mZj5DR37ajzA==} + engines: {node: '>=12'} + requiresBuild: true dependencies: - jest-matcher-utils: 28.1.3 - pretty-format: 28.1.3 - dev: true - - /@types/json-schema@7.0.12: - resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} - dev: true - - /@types/mime@1.3.2: - resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} - - /@types/mime@3.0.1: - resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} - - /@types/minimist@1.2.2: - resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} - dev: true - - /@types/node@12.20.55: - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - dev: true - - /@types/node@18.17.8: - resolution: {integrity: sha512-Av/7MqX/iNKwT9Tr60V85NqMnsmh8ilfJoBlIVibkXfitk9Q22D9Y5mSpm+FvG5DET7EbVfB40bOiLzKgYFgPw==} + node-gyp-build: 3.9.0 + dev: false - /@types/node@18.19.24: - resolution: {integrity: sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==} + /@datadog/native-iast-rewriter@1.1.2: + resolution: {integrity: sha512-pigRfRtAjZjMjqIXyXb98S4aDnuHz/EmqpoxAajFZsNjBLM87YonwSY5zoBdCsOyA46ddKOJRoCQd5ZalpOFMQ==} + engines: {node: '>= 10'} dependencies: - undici-types: 5.26.5 + node-gyp-build: 4.6.1 + dev: false - /@types/nodemailer@6.4.6: - resolution: {integrity: sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==} + /@datadog/native-iast-rewriter@2.2.1: + resolution: {integrity: sha512-DyZlE8gNa5AoOFNKGRJU4RYF/Y/tJzv4bIAMuVBbEnMA0xhiIYqpYQG8T3OKkALl3VSEeBMjYwuOR2fCrJ6gzA==} + engines: {node: '>= 10'} dependencies: - '@types/node': 18.17.8 - dev: true - - /@types/normalize-package-data@2.4.1: - resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} - dev: true + lru-cache: 7.18.3 + node-gyp-build: 4.6.1 + dev: false - /@types/pg@8.6.6: - resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} + /@datadog/native-iast-taint-tracking@1.1.0: + resolution: {integrity: sha512-TOrngpt6Qh52zWFOz1CkFXw0g43rnuUziFBtIMUsOLGzSHr9wdnTnE6HAyuvKy3f3ecAoZESlMfilGRKP93hXQ==} dependencies: - '@types/node': 18.17.8 - pg-protocol: 1.6.0 - pg-types: 2.2.0 - dev: true - - /@types/prettier@2.7.3: - resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} - dev: true - - /@types/qs@6.9.7: - resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} - - /@types/range-parser@1.2.4: - resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - - /@types/semver@7.5.0: - resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} - dev: true + node-gyp-build: 3.9.0 + dev: false - /@types/send@0.17.1: - resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} + /@datadog/native-iast-taint-tracking@1.6.4: + resolution: {integrity: sha512-Owxk7hQ4Dxwv4zJAoMjRga0IvE6lhvxnNc8pJCHsemCWBXchjr/9bqg05Zy5JnMbKUWn4XuZeJD6RFZpRa8bfw==} + requiresBuild: true dependencies: - '@types/mime': 1.3.2 - '@types/node': 18.19.24 + node-gyp-build: 3.9.0 + dev: false - /@types/serve-static@1.15.2: - resolution: {integrity: sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==} + /@datadog/native-metrics@1.6.0: + resolution: {integrity: sha512-+8jBzd0nlLV+ay3Vb87DLwz8JHAS817hRhSRQ6zxhud9TyvvcNTNN+VA2sb2fe5UK4aMDvj/sGVJjEtgr4RHew==} + engines: {node: '>=12'} + requiresBuild: true dependencies: - '@types/http-errors': 2.0.1 - '@types/mime': 3.0.1 - '@types/node': 18.19.24 + node-gyp-build: 3.9.0 + dev: false - /@types/shimmer@1.0.5: - resolution: {integrity: sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==} + /@datadog/native-metrics@2.0.0: + resolution: {integrity: sha512-YklGVwUtmKGYqFf1MNZuOHvTYdKuR4+Af1XkWcMD8BwOAjxmd9Z+97328rCOY8TFUJzlGUPaXzB8j2qgG/BMwA==} + engines: {node: '>=12'} + requiresBuild: true + dependencies: + node-addon-api: 6.1.0 + node-gyp-build: 3.9.0 dev: false - /@types/stack-utils@2.0.1: - resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} - dev: true + /@datadog/pprof@1.1.1: + resolution: {integrity: sha512-5lYXUpikQhrJwzODtJ7aFM0oKmPccISnTCecuWhjxIj4/7UJv0DamkLak634bgEW+kiChgkKFDapHSesuXRDXQ==} + engines: {node: '>=12'} + requiresBuild: true + dependencies: + delay: 5.0.0 + findit2: 2.2.3 + node-gyp-build: 3.9.0 + p-limit: 3.1.0 + pify: 5.0.0 + protobufjs: 7.2.5 + source-map: 0.7.4 + split: 1.0.1 + dev: false - /@types/ws@8.5.4: - resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} + /@datadog/pprof@4.0.1: + resolution: {integrity: sha512-TavqyiyQZOaUM9eQB07r8+K/T1CqKyOdsUGxpN79+BF+eOQBpTj/Cte6KdlhcUSKL3h5hSjC+vlgA7uW2qtVhA==} + engines: {node: '>=14'} + requiresBuild: true dependencies: - '@types/node': 18.17.8 - dev: true + delay: 5.0.0 + node-gyp-build: 3.9.0 + p-limit: 3.1.0 + pprof-format: 2.0.7 + source-map: 0.7.4 + dev: false - /@types/yargs-parser@21.0.0: - resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} - dev: true + /@datadog/sketches-js@2.1.0: + resolution: {integrity: sha512-smLocSfrt3s53H/XSVP3/1kP42oqvrkjUPtyaFd1F79ux24oE31BKt+q0c6lsa6hOYrFzsIwyc5GXAI5JmfOew==} + dev: false - /@types/yargs@16.0.5: - resolution: {integrity: sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==} + /@did-plc/lib@0.0.1: + resolution: {integrity: sha512-RkY5w9DbYMco3SjeepqIiMveqz35exjlVDipCs2gz9AXF4/cp9hvmrp9zUWEw2vny+FjV8vGEN7QpaXWaO6nhg==} dependencies: - '@types/yargs-parser': 21.0.0 - dev: true + '@atproto/common': 0.1.0 + '@atproto/crypto': 0.1.0 + '@ipld/dag-cbor': 7.0.3 + axios: 1.6.2 + multiformats: 9.9.0 + uint8arrays: 3.0.0 + zod: 3.21.4 + transitivePeerDependencies: + - debug - /@types/yargs@17.0.24: - resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} + /@did-plc/lib@0.0.4: + resolution: {integrity: sha512-Omeawq3b8G/c/5CtkTtzovSOnWuvIuCI4GTJNrt1AmCskwEQV7zbX5d6km1mjJNbE0gHuQPTVqZxLVqetNbfwA==} dependencies: - '@types/yargs-parser': 21.0.0 - dev: true + '@atproto/common': 0.1.1 + '@atproto/crypto': 0.1.0 + '@ipld/dag-cbor': 7.0.3 + axios: 1.6.7 + multiformats: 9.9.0 + uint8arrays: 3.0.0 + zod: 3.23.4 + transitivePeerDependencies: + - debug - /@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.4): - resolution: {integrity: sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + /@did-plc/server@0.0.1: + resolution: {integrity: sha512-GtxxHcOrOQ6fNI1ufq3Zqjc2PtWqPZOdsuzlwtxiH9XibUGwDkb0GmaBHyU5GiOxOKZEW1GspZ8mreBA6XOlTQ==} dependencies: - '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/scope-manager': 7.4.0 - '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.4) - '@typescript-eslint/visitor-keys': 7.4.0 - debug: 4.3.4 - eslint: 8.57.0 - graphemer: 1.4.0 - ignore: 5.2.4 - natural-compare: 1.4.0 - semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@5.4.4) - typescript: 5.4.4 + '@atproto/common': 0.1.0 + '@atproto/crypto': 0.1.0 + '@did-plc/lib': 0.0.4 + axios: 1.4.0 + cors: 2.8.5 + express: 4.18.2 + express-async-errors: 3.1.1(express@4.18.2) + http-terminator: 3.2.0 + kysely: 0.23.5 + multiformats: 9.9.0 + pg: 8.10.0 + pino: 8.15.0 + pino-http: 8.4.0 transitivePeerDependencies: + - debug + - pg-native - supports-color - dev: true - /@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.4.4): - resolution: {integrity: sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==} - engines: {node: ^18.18.0 || >=20.0.0} + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - '@typescript-eslint/scope-manager': 7.4.0 - '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.4) - '@typescript-eslint/visitor-keys': 7.4.0 - debug: 4.3.4 eslint: 8.57.0 - typescript: 5.4.4 - transitivePeerDependencies: - - supports-color + eslint-visitor-keys: 3.4.3 dev: true - /@typescript-eslint/scope-manager@7.4.0: - resolution: {integrity: sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==} - engines: {node: ^18.18.0 || >=20.0.0} - dependencies: - '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/visitor-keys': 7.4.0 + /@eslint-community/regexpp@4.10.0: + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.4.4): - resolution: {integrity: sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + /@eslint/eslintrc@2.1.4: + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.4) - '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.4) + ajv: 6.12.6 debug: 4.3.4 - eslint: 8.57.0 - ts-api-utils: 1.0.3(typescript@5.4.4) - typescript: 5.4.4 + espree: 9.6.1 + globals: 13.21.0 + ignore: 5.2.4 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@7.4.0: - resolution: {integrity: sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==} - engines: {node: ^18.18.0 || >=20.0.0} + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree@7.4.0(typescript@5.4.4): - resolution: {integrity: sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/visitor-keys': 7.4.0 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@5.4.4) - typescript: 5.4.4 - transitivePeerDependencies: - - supports-color + /@fastify/busboy@2.1.0: + resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} + engines: {node: '>=14'} + + /@fastify/deepmerge@1.3.0: + resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==} + + /@gar/promisify@1.1.3: + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} dev: true - /@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.4.4): - resolution: {integrity: sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 + /@hapi/accept@6.0.3: + resolution: {integrity: sha512-p72f9k56EuF0n3MwlBNThyVE5PXX40g+aQh+C/xbKrfzahM2Oispv3AXmOIU51t3j77zay1qrX7IIziZXspMlw==} dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@types/json-schema': 7.0.12 - '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 7.4.0 - '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.4) - eslint: 8.57.0 - semver: 7.5.4 - transitivePeerDependencies: - - supports-color - - typescript - dev: true + '@hapi/boom': 10.0.1 + '@hapi/hoek': 11.0.4 + dev: false - /@typescript-eslint/visitor-keys@7.4.0: - resolution: {integrity: sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==} - engines: {node: ^18.18.0 || >=20.0.0} + /@hapi/boom@10.0.1: + resolution: {integrity: sha512-ERcCZaEjdH3OgSJlyjVk8pHIFeus91CjKP3v+MpgBNp5IvGzP2l/bRiD78nqYcKPaZdbKkK5vDBVPd2ohHBlsA==} dependencies: - '@typescript-eslint/types': 7.4.0 - eslint-visitor-keys: 3.4.3 - dev: true + '@hapi/hoek': 11.0.4 + dev: false - /@typescript/vfs@1.5.0: - resolution: {integrity: sha512-AJS307bPgbsZZ9ggCT3wwpg3VbTKMFNHfaY/uF0ahSkYYrPF2dSSKDNIDIQAHm9qJqbLvCsSJH7yN4Vs/CsMMg==} + /@hapi/bourne@3.0.0: + resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==} + dev: false + + /@hapi/hoek@11.0.4: + resolution: {integrity: sha512-PnsP5d4q7289pS2T2EgGz147BFJ2Jpb4yrEdkpz2IhgEUzos1S7HTl7ezWh1yfYzYlj89KzLdCRkqsP6SIryeQ==} + dev: false + + /@hapi/hoek@9.3.0: + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + dev: false + + /@hapi/topo@5.1.0: + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: false + + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} dependencies: + '@humanwhocodes/object-schema': 2.0.2 debug: 4.3.4 + minimatch: 3.1.2 transitivePeerDependencies: - supports-color dev: true - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} dev: true - /abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + /@humanwhocodes/object-schema@2.0.2: + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} dev: true - /abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} + /@ioredis/commands@1.2.0: + resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + + /@ipld/car@3.2.3: + resolution: {integrity: sha512-pXE5mFJlXzJVaBwqAJKGlKqMmxq8H2SLEWBJgkeBDPBIN8ZbscPc3I9itkSQSlS/s6Fgx35Ri3LDTDtodQjCCQ==} dependencies: - event-target-shim: 5.0.1 + '@ipld/dag-cbor': 7.0.3 + multiformats: 9.9.0 + varint: 6.0.0 + dev: false - /accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} + /@ipld/dag-cbor@7.0.3: + resolution: {integrity: sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==} dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 + cborg: 1.10.2 + multiformats: 9.9.0 - /acorn-import-assertions@1.9.0(acorn@8.10.0): - resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} - peerDependencies: - acorn: ^8 + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} dependencies: - acorn: 8.10.0 + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + + /@isaacs/ttlcache@1.4.1: + resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} + engines: {node: '>=12'} dev: false - /acorn-jsx@5.3.2(acorn@8.10.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + /@istanbuljs/load-nyc-config@1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} dependencies: - acorn: 8.10.0 + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 dev: true - /acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} dev: true - /acorn@8.10.0: - resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} - engines: {node: '>=0.4.0'} - hasBin: true - - /agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} + /@jest/console@28.1.3: + resolution: {integrity: sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - - /agentkeepalive@4.5.0: - resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} - engines: {node: '>= 8.0.0'} - dependencies: - humanize-ms: 1.2.1 - dev: true - - /aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + chalk: 4.1.2 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + slash: 3.0.0 dev: true - /ajv-formats@2.1.1(ajv@8.12.0): - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + /@jest/core@28.1.3(ts-node@10.8.2): + resolution: {integrity: sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} peerDependencies: - ajv: ^8.0.0 + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 peerDependenciesMeta: - ajv: + node-notifier: optional: true dependencies: - ajv: 8.12.0 - - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 + '@jest/console': 28.1.3 + '@jest/reporters': 28.1.3 + '@jest/test-result': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.8.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 28.1.3 + jest-config: 28.1.3(@types/node@18.19.24)(ts-node@10.8.2) + jest-haste-map: 28.1.3 + jest-message-util: 28.1.3 + jest-regex-util: 28.0.2 + jest-resolve: 28.1.3 + jest-resolve-dependencies: 28.1.3 + jest-runner: 28.1.3 + jest-runtime: 28.1.3 + jest-snapshot: 28.1.3 + jest-util: 28.1.3 + jest-validate: 28.1.3 + jest-watcher: 28.1.3 + micromatch: 4.0.5 + pretty-format: 28.1.3 + rimraf: 3.0.2 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - supports-color + - ts-node dev: true - /ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + /@jest/create-cache-key-function@27.5.1: + resolution: {integrity: sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - - /ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} + '@jest/types': 27.5.1 dev: true - /ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + /@jest/create-cache-key-function@29.7.0: + resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - type-fest: 0.21.3 - dev: true - - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - /ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} + '@jest/types': 29.6.3 dev: false - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} + /@jest/environment@28.1.3: + resolution: {integrity: sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - color-convert: 1.9.3 + '@jest/fake-timers': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + jest-mock: 28.1.3 dev: true - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - color-convert: 2.0.1 - - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - dev: true - - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.19.24 + jest-mock: 29.7.0 dev: false - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} + /@jest/expect-utils@28.1.3: + resolution: {integrity: sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 + jest-get-type: 28.0.2 dev: true - /aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + /@jest/expect@28.1.3: + resolution: {integrity: sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + expect: 28.1.3 + jest-snapshot: 28.1.3 + transitivePeerDependencies: + - supports-color dev: true - /are-we-there-yet@3.0.1: - resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + /@jest/fake-timers@28.1.3: + resolution: {integrity: sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 + '@jest/types': 28.1.3 + '@sinonjs/fake-timers': 9.1.2 + '@types/node': 18.19.24 + jest-message-util: 28.1.3 + jest-mock: 28.1.3 + jest-util: 28.1.3 dev: true - /arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true + /@jest/fake-timers@29.7.0: + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 18.19.24 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: false - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + /@jest/globals@28.1.3: + resolution: {integrity: sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - sprintf-js: 1.0.3 + '@jest/environment': 28.1.3 + '@jest/expect': 28.1.3 + '@jest/types': 28.1.3 + transitivePeerDependencies: + - supports-color dev: true - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + /@jest/reporters@28.1.3: + resolution: {integrity: sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 28.1.3 + '@jest/test-result': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + '@jridgewell/trace-mapping': 0.3.19 + '@types/node': 18.19.24 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.0 + istanbul-lib-instrument: 5.2.1 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.6 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + jest-worker: 28.1.3 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + terminal-link: 2.1.1 + v8-to-istanbul: 9.1.0 + transitivePeerDependencies: + - supports-color dev: true - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + /@jest/schemas@28.1.3: + resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - call-bind: 1.0.2 - is-array-buffer: 3.0.2 + '@sinclair/typebox': 0.24.51 dev: true - /array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: false - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} + /@jest/source-map@28.1.2: + resolution: {integrity: sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jridgewell/trace-mapping': 0.3.19 + callsites: 3.1.0 + graceful-fs: 4.2.11 dev: true - /array.prototype.flat@1.3.1: - resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} - engines: {node: '>= 0.4'} + /@jest/test-result@28.1.3: + resolution: {integrity: sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - es-shim-unscopables: 1.0.0 + '@jest/console': 28.1.3 + '@jest/types': 28.1.3 + '@types/istanbul-lib-coverage': 2.0.4 + collect-v8-coverage: 1.0.2 dev: true - /arraybuffer.prototype.slice@1.0.1: - resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} - engines: {node: '>= 0.4'} + /@jest/test-sequencer@28.1.3: + resolution: {integrity: sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - array-buffer-byte-length: 1.0.0 - call-bind: 1.0.2 - define-properties: 1.2.0 - get-intrinsic: 1.2.1 - is-array-buffer: 3.0.2 - is-shared-array-buffer: 1.0.2 - dev: true - - /arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - dev: true - - /asn1.js@5.4.1: - resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} - dependencies: - bn.js: 4.12.0 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - safer-buffer: 2.1.2 - - /asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - /atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} + '@jest/test-result': 28.1.3 + graceful-fs: 4.2.11 + jest-haste-map: 28.1.3 + slash: 3.0.0 dev: true - /axios@0.27.2: - resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} - dependencies: - follow-redirects: 1.15.2 - form-data: 4.0.0 - transitivePeerDependencies: - - debug - - /axios@1.4.0: - resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} - dependencies: - follow-redirects: 1.15.2 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - /axios@1.6.2: - resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==} - dependencies: - follow-redirects: 1.15.3 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - /axios@1.6.7: - resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} - dependencies: - follow-redirects: 1.15.5 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - /b4a@1.6.4: - resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} - - /babel-jest@28.1.3(@babel/core@7.18.6): - resolution: {integrity: sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==} + /@jest/transform@28.1.3: + resolution: {integrity: sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - peerDependencies: - '@babel/core': ^7.8.0 dependencies: '@babel/core': 7.18.6 - '@jest/transform': 28.1.3 - '@types/babel__core': 7.20.1 + '@jest/types': 28.1.3 + '@jridgewell/trace-mapping': 0.3.19 babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 28.1.3(@babel/core@7.18.6) chalk: 4.1.2 + convert-source-map: 1.9.0 + fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.11 + jest-haste-map: 28.1.3 + jest-regex-util: 28.0.2 + jest-util: 28.1.3 + micromatch: 4.0.5 + pirates: 4.0.6 slash: 3.0.0 + write-file-atomic: 4.0.2 transitivePeerDependencies: - supports-color dev: true - /babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - dependencies: - '@babel/helper-plugin-utils': 7.22.5 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - dev: true - - /babel-plugin-jest-hoist@28.1.3: - resolution: {integrity: sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /@jest/types@26.6.2: + resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==} + engines: {node: '>= 10.14.2'} dependencies: - '@babel/template': 7.22.5 - '@babel/types': 7.22.10 - '@types/babel__core': 7.20.1 - '@types/babel__traverse': 7.20.1 - dev: true + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.19.24 + '@types/yargs': 15.0.19 + chalk: 4.1.2 + dev: false - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.18.6): - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} - peerDependencies: - '@babel/core': ^7.0.0 + /@jest/types@27.5.1: + resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@babel/core': 7.18.6 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.18.6) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.18.6) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.18.6) + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.19.24 + '@types/yargs': 16.0.5 + chalk: 4.1.2 dev: true - /babel-preset-jest@28.1.3(@babel/core@7.18.6): - resolution: {integrity: sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==} + /@jest/types@28.1.3: + resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - peerDependencies: - '@babel/core': ^7.0.0 dependencies: - '@babel/core': 7.18.6 - babel-plugin-jest-hoist: 28.1.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.18.6) + '@jest/schemas': 28.1.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.19.24 + '@types/yargs': 17.0.24 + chalk: 4.1.2 dev: true - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - /better-path-resolve@1.0.0: - resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} - engines: {node: '>=4'} + /@jest/types@29.6.3: + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - is-windows: 1.0.2 - dev: true + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.4 + '@types/istanbul-reports': 3.0.1 + '@types/node': 18.19.24 + '@types/yargs': 17.0.24 + chalk: 4.1.2 + dev: false - /better-sqlite3@9.4.0: - resolution: {integrity: sha512-5kynxekMxSjCMiFyUBLHggFcJkCmiZi6fUkiGz/B5GZOvdRWQJD0klqCx5/Y+bm2AKP7I/DHbSFx26AxjruWNg==} - requiresBuild: true + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.1 - dev: false + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 - /better-sqlite3@9.4.5: - resolution: {integrity: sha512-uFVyoyZR9BNcjSca+cp3MWCv6upAv+tbMC4SWM51NIMhoQOm4tjIkyxFO/ZsYdGAF61WJBgdzyJcz4OokJi0gQ==} - requiresBuild: true + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} dependencies: - bindings: 1.5.0 - prebuild-install: 7.1.2 + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 - /big-integer@1.6.51: - resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} - engines: {node: '>=0.6'} + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} - /bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - dependencies: - file-uri-to-path: 1.0.0 + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} - /bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + /@jridgewell/source-map@0.3.6: + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 - /bn.js@4.12.0: - resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - /body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + /@jridgewell/trace-mapping@0.3.19: + resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.11.0 - raw-body: 2.5.1 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - /boolean@3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 - /bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - dev: false + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - dependencies: - balanced-match: 1.0.2 - - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - - /breakword@1.0.6: - resolution: {integrity: sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==} + /@manypkg/find-root@1.1.0: + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: - wcwidth: 1.0.1 + '@babel/runtime': 7.22.10 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 dev: true - /brorand@1.1.0: - resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - - /browserslist@4.21.10: - resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true + /@manypkg/get-packages@1.1.3: + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} dependencies: - caniuse-lite: 1.0.30001522 - electron-to-chromium: 1.4.499 - node-releases: 2.0.13 - update-browserslist-db: 1.0.11(browserslist@4.21.10) + '@babel/runtime': 7.22.10 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 dev: true - /bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + /@noble/curves@1.1.0: + resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==} dependencies: - node-int64: 0.4.0 - dev: true - - /buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - dev: true + '@noble/hashes': 1.3.1 + dev: false - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true + /@noble/hashes@1.3.1: + resolution: {integrity: sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==} + engines: {node: '>= 16'} + dev: false - /buffer-writer@2.0.0: - resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} - engines: {node: '>=4'} + /@noble/secp256k1@1.7.1: + resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} - /buffer@5.6.0: - resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: false + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - /buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - /bytes@3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} - engines: {node: '>= 0.8'} + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 - /bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} + /@npmcli/fs@2.1.2: + resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.6.0 + dev: true - /cacache@16.1.3: - resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + /@npmcli/move-file@2.0.1: + resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs dependencies: - '@npmcli/fs': 2.1.2 - '@npmcli/move-file': 2.0.1 - chownr: 2.0.0 - fs-minipass: 2.1.0 - glob: 8.1.0 - infer-owner: 1.0.4 - lru-cache: 7.18.3 - minipass: 3.3.6 - minipass-collect: 1.0.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 mkdirp: 1.0.4 - p-map: 4.0.0 - promise-inflight: 1.0.1 rimraf: 3.0.2 - ssri: 9.0.1 - tar: 6.1.15 - unique-filename: 2.0.1 - transitivePeerDependencies: - - bluebird dev: true - /call-bind@1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + /@opentelemetry/api@1.7.0: + resolution: {integrity: sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw==} + engines: {node: '>=8.0.0'} + dev: false + + /@opentelemetry/api@1.8.0: + resolution: {integrity: sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w==} + engines: {node: '>=8.0.0'} + dev: false + + /@opentelemetry/core@1.18.1(@opentelemetry/api@1.7.0): + resolution: {integrity: sha512-kvnUqezHMhsQvdsnhnqTNfAJs3ox/isB0SVrM1dhVFw7SsB7TstuVa6fgWnN2GdPyilIFLUvvbTZoVRmx6eiRg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.8.0' dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.2.1 + '@opentelemetry/api': 1.7.0 + '@opentelemetry/semantic-conventions': 1.18.1 + dev: false - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true + /@opentelemetry/instrumentation@0.44.0(@opentelemetry/api@1.7.0): + resolution: {integrity: sha512-B6OxJTRRCceAhhnPDBshyQO7K07/ltX3quOLu0icEvPK9QZ7r9P1y0RQX8O5DxB4vTv4URRkxkg+aFU/plNtQw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.7.0 + '@types/shimmer': 1.0.5 + import-in-the-middle: 1.4.2 + require-in-the-middle: 7.2.0 + semver: 7.5.4 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + dev: false - /camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} + /@opentelemetry/instrumentation@0.45.1(@opentelemetry/api@1.8.0): + resolution: {integrity: sha512-V1Cr0g8hSg35lpW3G/GYVZurrhHrQZJdmP68WyJ83f1FDn3iru+/Vnlto9kiOSm7PHhW+pZGdb9Fbv+mkQ31CA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - dev: true + '@opentelemetry/api': 1.8.0 + '@types/shimmer': 1.0.5 + import-in-the-middle: 1.4.2 + require-in-the-middle: 7.2.0 + semver: 7.5.4 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + dev: false - /camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - dev: true + /@opentelemetry/semantic-conventions@1.18.1: + resolution: {integrity: sha512-+NLGHr6VZwcgE/2lw8zDIufOCGnzsA5CbQIMleXZTrgkBd0TanCX+MiDYJ1TOS4KL/Tqk0nFRxawnaYr6pkZkA==} + engines: {node: '>=14'} + dev: false - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - dev: true + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + optional: true - /caniuse-lite@1.0.30001522: - resolution: {integrity: sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==} + /@pkgr/core@0.1.1: + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dev: true - /cbor-extract@2.2.0: - resolution: {integrity: sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==} - hasBin: true - requiresBuild: true - dependencies: - node-gyp-build-optional-packages: 5.1.1 - optionalDependencies: - '@cbor-extract/cbor-extract-darwin-arm64': 2.2.0 - '@cbor-extract/cbor-extract-darwin-x64': 2.2.0 - '@cbor-extract/cbor-extract-linux-arm': 2.2.0 - '@cbor-extract/cbor-extract-linux-arm64': 2.2.0 - '@cbor-extract/cbor-extract-linux-x64': 2.2.0 - '@cbor-extract/cbor-extract-win32-x64': 2.2.0 + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} dev: false - optional: true - /cbor-x@1.5.1: - resolution: {integrity: sha512-/vAkC4tiKCQCm5en4sA+mpKmjwY6Xxp1LO+BgZCNhp+Zow3pomyUHeBOK5EDp0mDaE36jw39l5eLHsoF3M1Lmg==} - optionalDependencies: - cbor-extract: 2.2.0 + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} dev: false - /cborg@1.10.2: - resolution: {integrity: sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==} - hasBin: true + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - dev: true + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - /char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - dev: true - - /chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - dev: true + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false - /chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false - /chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - dev: true + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false - /ci-info@3.8.0: - resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} - engines: {node: '>=8'} - dev: true + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false - /cjs-module-lexer@1.2.3: - resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false - /clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - dev: true + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false - /cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + /@react-native-community/cli-clean@13.6.6: + resolution: {integrity: sha512-cBwJTwl0NyeA4nyMxbhkWZhxtILYkbU3TW3k8AXLg+iGphe0zikYMGB3T+haTvTc6alTyEFwPbimk9bGIqkjAQ==} dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - dev: true + '@react-native-community/cli-tools': 13.6.6 + chalk: 4.1.2 + execa: 5.1.1 + fast-glob: 3.3.2 + transitivePeerDependencies: + - encoding + dev: false - /cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + /@react-native-community/cli-config@13.6.6: + resolution: {integrity: sha512-mbG425zCKr8JZhv/j11382arezwS/70juWMsn8j2lmrGTrP1cUdW0MF15CCIFtJsqyK3Qs+FTmqttRpq81QfSg==} dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - dev: true - - /clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - dev: true + '@react-native-community/cli-tools': 13.6.6 + chalk: 4.1.2 + cosmiconfig: 5.2.1 + deepmerge: 4.3.1 + fast-glob: 3.3.2 + joi: 17.13.1 + transitivePeerDependencies: + - encoding + dev: false - /cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} + /@react-native-community/cli-debugger-ui@13.6.6: + resolution: {integrity: sha512-Vv9u6eS4vKSDAvdhA0OiQHoA7y39fiPIgJ6biT32tN4avHDtxlc6TWZGiqv7g98SBvDWvoVAmdPLcRf3kU+c8g==} + dependencies: + serve-static: 1.15.0 + transitivePeerDependencies: + - supports-color + dev: false - /co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - dev: true + /@react-native-community/cli-doctor@13.6.6: + resolution: {integrity: sha512-TWZb5g6EmQe2Ua2TEWNmyaEayvlWH4GmdD9ZC+p8EpKFpB1NpDGMK6sXbpb42TDvwZg5s4TDRplK0PBEA/SVDg==} + dependencies: + '@react-native-community/cli-config': 13.6.6 + '@react-native-community/cli-platform-android': 13.6.6 + '@react-native-community/cli-platform-apple': 13.6.6 + '@react-native-community/cli-platform-ios': 13.6.6 + '@react-native-community/cli-tools': 13.6.6 + chalk: 4.1.2 + command-exists: 1.2.9 + deepmerge: 4.3.1 + envinfo: 7.13.0 + execa: 5.1.1 + hermes-profile-transformer: 0.0.6 + node-stream-zip: 1.15.0 + ora: 5.4.1 + semver: 7.6.0 + strip-ansi: 5.2.0 + wcwidth: 1.0.1 + yaml: 2.4.2 + transitivePeerDependencies: + - encoding + dev: false - /code-block-writer@11.0.3: - resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} + /@react-native-community/cli-hermes@13.6.6: + resolution: {integrity: sha512-La5Ie+NGaRl3klei6WxKoOxmCUSGGxpOk6vU5pEGf0/O7ky+Ay0io+zXYUZqlNMi/cGpO7ZUijakBYOB/uyuFg==} + dependencies: + '@react-native-community/cli-platform-android': 13.6.6 + '@react-native-community/cli-tools': 13.6.6 + chalk: 4.1.2 + hermes-profile-transformer: 0.0.6 + transitivePeerDependencies: + - encoding dev: false - /collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - dev: true + /@react-native-community/cli-platform-android@13.6.6: + resolution: {integrity: sha512-/tMwkBeNxh84syiSwNlYtmUz/Ppc+HfKtdopL/5RB+fd3SV1/5/NPNjMlyLNgFKnpxvKCInQ7dnl6jGHJjeHjg==} + dependencies: + '@react-native-community/cli-tools': 13.6.6 + chalk: 4.1.2 + execa: 5.1.1 + fast-glob: 3.3.2 + fast-xml-parser: 4.3.6 + logkitty: 0.7.1 + transitivePeerDependencies: + - encoding + dev: false - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + /@react-native-community/cli-platform-apple@13.6.6: + resolution: {integrity: sha512-bOmSSwoqNNT3AmCRZXEMYKz1Jf1l2F86Nhs7qBcXdY/sGiJ+Flng564LOqvdAlVLTbkgz47KjNKCS2pP4Jg0Mg==} dependencies: - color-name: 1.1.3 - dev: true + '@react-native-community/cli-tools': 13.6.6 + chalk: 4.1.2 + execa: 5.1.1 + fast-glob: 3.3.2 + fast-xml-parser: 4.3.6 + ora: 5.4.1 + transitivePeerDependencies: + - encoding + dev: false - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} + /@react-native-community/cli-platform-ios@13.6.6: + resolution: {integrity: sha512-vjDnRwhlSN5ryqKTas6/DPkxuouuyFBAqAROH4FR1cspTbn6v78JTZKDmtQy9JMMo7N5vZj1kASU5vbFep9IOQ==} dependencies: - color-name: 1.1.4 + '@react-native-community/cli-platform-apple': 13.6.6 + transitivePeerDependencies: + - encoding + dev: false - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true + /@react-native-community/cli-server-api@13.6.6: + resolution: {integrity: sha512-ZtCXxoFlM7oDv3iZ3wsrT3SamhtUJuIkX2WePLPlN5bcbq7zimbPm2lHyicNJtpcGQ5ymsgpUWPCNZsWQhXBqQ==} + dependencies: + '@react-native-community/cli-debugger-ui': 13.6.6 + '@react-native-community/cli-tools': 13.6.6 + compression: 1.7.4 + connect: 3.7.0 + errorhandler: 1.5.1 + nocache: 3.0.4 + pretty-format: 26.6.2 + serve-static: 1.15.0 + ws: 6.2.2 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: false - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /@react-native-community/cli-tools@13.6.6: + resolution: {integrity: sha512-ptOnn4AJczY5njvbdK91k4hcYazDnGtEPrqIwEI+k/CTBHNdb27Rsm2OZ7ye6f7otLBqF8gj/hK6QzJs8CEMgw==} + dependencies: + appdirsjs: 1.2.7 + chalk: 4.1.2 + execa: 5.1.1 + find-up: 5.0.0 + mime: 2.6.0 + node-fetch: 2.7.0 + open: 6.4.0 + ora: 5.4.1 + semver: 7.6.0 + shell-quote: 1.8.1 + sudo-prompt: 9.2.1 + transitivePeerDependencies: + - encoding + dev: false - /color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + /@react-native-community/cli-types@13.6.6: + resolution: {integrity: sha512-733iaYzlmvNK7XYbnWlMjdE+2k0hlTBJW071af/xb6Bs+hbJqBP9c03FZuYH2hFFwDDntwj05bkri/P7VgSxug==} dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 + joi: 17.13.1 + dev: false - /color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + /@react-native-community/cli@13.6.6: + resolution: {integrity: sha512-IqclB7VQ84ye8Fcs89HOpOscY4284VZg2pojHNl8H0Lzd4DadXJWQoxC7zWm8v2f8eyeX2kdhxp2ETD5tceIgA==} + engines: {node: '>=18'} hasBin: true - dev: true - - /color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 + '@react-native-community/cli-clean': 13.6.6 + '@react-native-community/cli-config': 13.6.6 + '@react-native-community/cli-debugger-ui': 13.6.6 + '@react-native-community/cli-doctor': 13.6.6 + '@react-native-community/cli-hermes': 13.6.6 + '@react-native-community/cli-server-api': 13.6.6 + '@react-native-community/cli-tools': 13.6.6 + '@react-native-community/cli-types': 13.6.6 + chalk: 4.1.2 + commander: 9.5.0 + deepmerge: 4.3.1 + execa: 5.1.1 + find-up: 4.1.0 + fs-extra: 8.1.0 + graceful-fs: 4.2.11 + prompts: 2.4.2 + semver: 7.6.0 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: false - /colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - dev: true + /@react-native/assets-registry@0.74.83: + resolution: {integrity: sha512-2vkLMVnp+YTZYTNSDIBZojSsjz8sl5PscP3j4GcV6idD8V978SZfwFlk8K0ti0BzRs11mzL0Pj17km597S/eTQ==} + engines: {node: '>=18'} + dev: false - /combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} + /@react-native/babel-plugin-codegen@0.74.83(@babel/preset-env@7.24.5): + resolution: {integrity: sha512-+S0st3t4Ro00bi9gjT1jnK8qTFOU+CwmziA7U9odKyWrCoRJrgmrvogq/Dr1YXlpFxexiGIupGut1VHxr+fxJA==} + engines: {node: '>=18'} dependencies: - delayed-stream: 1.0.0 - - /commander@9.4.0: - resolution: {integrity: sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==} - engines: {node: ^12.20.0 || >=14} + '@react-native/codegen': 0.74.83(@babel/preset-env@7.24.5) + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color dev: false - /compressible@2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} - engines: {node: '>= 0.6'} + /@react-native/babel-preset@0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5): + resolution: {integrity: sha512-KJuu3XyVh3qgyUer+rEqh9a/JoUxsDOzkJNfRpDyXiAyjDRoVch60X/Xa/NcEQ93iCVHAWs0yQ+XGNGIBCYE6g==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' dependencies: - mime-db: 1.52.0 + '@babel/core': 7.18.6 + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.18.6) + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-export-default-from': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.18.6) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.18.6) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.18.6) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-export-default-from': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-transform-arrow-functions': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-async-to-generator': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-block-scoping': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-classes': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-computed-properties': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-destructuring': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-flow-strip-types': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-function-name': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-literals': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.18.6) + '@babel/plugin-transform-parameters': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-private-methods': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-private-property-in-object': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-react-display-name': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-react-jsx': 7.23.4(@babel/core@7.18.6) + '@babel/plugin-transform-react-jsx-self': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-runtime': 7.24.3(@babel/core@7.18.6) + '@babel/plugin-transform-shorthand-properties': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-spread': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-sticky-regex': 7.24.1(@babel/core@7.18.6) + '@babel/plugin-transform-typescript': 7.24.5(@babel/core@7.18.6) + '@babel/plugin-transform-unicode-regex': 7.24.1(@babel/core@7.18.6) + '@babel/template': 7.22.5 + '@react-native/babel-plugin-codegen': 0.74.83(@babel/preset-env@7.24.5) + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.18.6) + react-refresh: 0.14.2 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + dev: false - /compression@1.7.4: - resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} - engines: {node: '>= 0.8.0'} + /@react-native/codegen@0.74.83(@babel/preset-env@7.24.5): + resolution: {integrity: sha512-GgvgHS3Aa2J8/mp1uC/zU8HuTh8ZT5jz7a4mVMWPw7+rGyv70Ba8uOVBq6UH2Q08o617IATYc+0HfyzAfm4n0w==} + engines: {node: '>=18'} + peerDependencies: + '@babel/preset-env': ^7.1.6 dependencies: - accepts: 1.3.8 - bytes: 3.0.0 - compressible: 2.0.18 - debug: 2.6.9 - on-headers: 1.0.2 - safe-buffer: 5.1.2 - vary: 1.1.2 + '@babel/parser': 7.22.10 + '@babel/preset-env': 7.24.5(@babel/core@7.18.6) + glob: 7.2.3 + hermes-parser: 0.19.1 + invariant: 2.2.4 + jscodeshift: 0.14.0(@babel/preset-env@7.24.5) + mkdirp: 0.5.6 + nullthrows: 1.1.1 transitivePeerDependencies: - supports-color + dev: false - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true + /@react-native/community-cli-plugin@0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5): + resolution: {integrity: sha512-7GAFjFOg1mFSj8bnFNQS4u8u7+QtrEeflUIDVZGEfBZQ3wMNI5ycBzbBGycsZYiq00Xvoc6eKFC7kvIaqeJpUQ==} + engines: {node: '>=18'} + dependencies: + '@react-native-community/cli-server-api': 13.6.6 + '@react-native-community/cli-tools': 13.6.6 + '@react-native/dev-middleware': 0.74.83 + '@react-native/metro-babel-transformer': 0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5) + chalk: 4.1.2 + execa: 5.1.1 + metro: 0.80.9 + metro-config: 0.80.9 + metro-core: 0.80.9 + node-fetch: 2.7.0 + querystring: 0.2.1 + readline: 1.3.0 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: false - /console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - dev: true + /@react-native/debugger-frontend@0.74.83: + resolution: {integrity: sha512-RGQlVUegBRxAUF9c1ss1ssaHZh6CO+7awgtI9sDeU0PzDZY/40ImoPD5m0o0SI6nXoVzbPtcMGzU+VO590pRfA==} + engines: {node: '>=18'} + dev: false - /content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} + /@react-native/dev-middleware@0.74.83: + resolution: {integrity: sha512-UH8iriqnf7N4Hpi20D7M2FdvSANwTVStwFCSD7VMU9agJX88Yk0D1T6Meh2RMhUu4kY2bv8sTkNRm7LmxvZqgA==} + engines: {node: '>=18'} dependencies: - safe-buffer: 5.2.1 + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.74.83 + '@rnx-kit/chromium-edge-launcher': 1.0.0 + chrome-launcher: 0.15.2 + connect: 3.7.0 + debug: 2.6.9 + node-fetch: 2.7.0 + nullthrows: 1.1.1 + open: 7.4.2 + selfsigned: 2.4.1 + serve-static: 1.15.0 + temp-dir: 2.0.0 + ws: 6.2.2 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: false - /content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} + /@react-native/gradle-plugin@0.74.83: + resolution: {integrity: sha512-Pw2BWVyOHoBuJVKxGVYF6/GSZRf6+v1Ygc+ULGz5t20N8qzRWPa2fRZWqoxsN7TkNLPsECYY8gooOl7okOcPAQ==} + engines: {node: '>=18'} + dev: false - /convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - dev: true + /@react-native/js-polyfills@0.74.83: + resolution: {integrity: sha512-/t74n8r6wFhw4JEoOj3bN71N1NDLqaawB75uKAsSjeCwIR9AfCxlzZG0etsXtOexkY9KMeZIQ7YwRPqUdNXuqw==} + engines: {node: '>=18'} + dev: false - /cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + /@react-native/metro-babel-transformer@0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5): + resolution: {integrity: sha512-hGdx5N8diu8y+GW/ED39vTZa9Jx1di2ZZ0aapbhH4egN1agIAusj5jXTccfNBwwWF93aJ5oVbRzfteZgjbutKg==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + dependencies: + '@babel/core': 7.18.6 + '@react-native/babel-preset': 0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5) + hermes-parser: 0.19.1 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@babel/preset-env' + - supports-color + dev: false - /cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} + /@react-native/normalize-colors@0.74.83: + resolution: {integrity: sha512-jhCY95gRDE44qYawWVvhTjTplW1g+JtKTKM3f8xYT1dJtJ8QWv+gqEtKcfmOHfDkSDaMKG0AGBaDTSK8GXLH8Q==} + dev: false - /cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} + /@react-native/virtualized-lists@0.74.83(react-native@0.74.1)(react@18.3.1): + resolution: {integrity: sha512-rmaLeE34rj7py4FxTod7iMTC7BAsm+HrGA8WxYmEJeyTV7WSaxAkosKoYBz8038mOiwnG9VwA/7FrB6bEQvn1A==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': ^18.2.6 + react: '*' + react-native: '*' + peerDependenciesMeta: + '@types/react': + optional: true dependencies: - object-assign: 4.1.1 - vary: 1.1.2 + invariant: 2.2.4 + nullthrows: 1.1.1 + react: 18.3.1 + react-native: 0.74.1(@babel/core@7.18.6)(@babel/preset-env@7.24.5)(react@18.3.1) + dev: false - /create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + /@rnx-kit/chromium-edge-launcher@1.0.0: + resolution: {integrity: sha512-lzD84av1ZQhYUS+jsGqJiCMaJO2dn9u+RTT9n9q6D3SaKVwWqv+7AoRKqBu19bkwyE+iFRl1ymr40QS90jVFYg==} + engines: {node: '>=14.15'} + dependencies: + '@types/node': 18.19.24 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + mkdirp: 1.0.4 + rimraf: 3.0.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@rollup/plugin-commonjs@25.0.7(rollup@4.17.2): + resolution: {integrity: sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.17.2) + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 8.1.0 + is-reference: 1.2.1 + magic-string: 0.30.10 + rollup: 4.17.2 dev: true - /cross-spawn@5.1.0: - resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + /@rollup/plugin-dynamic-import-vars@2.1.2(rollup@4.17.2): + resolution: {integrity: sha512-4lr2oXxs9hcxtGGaK8s0i9evfjzDrAs7ngw28TqruWKTEm0+U4Eljb+F6HXGYdFv8xRojQlrQwV7M/yxeh3yzQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: - lru-cache: 4.1.5 - shebang-command: 1.2.0 - which: 1.3.1 + '@rollup/pluginutils': 5.1.0(rollup@4.17.2) + astring: 1.8.6 + estree-walker: 2.0.2 + fast-glob: 3.3.1 + magic-string: 0.30.10 + rollup: 4.17.2 dev: true - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} + /@rollup/plugin-html@1.0.3(rollup@4.17.2): + resolution: {integrity: sha512-bbjQciNXitHX+Bgk0xsW3/0wFWih/356/r7/kvmdz4wzWhAU/a0zYBWTczihrlzz/6Qpw/kZ0yXqOJwsETgg7A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + rollup: 4.17.2 + dev: true - /crypto-randomuuid@1.0.0: - resolution: {integrity: sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA==} - dev: false + /@rollup/plugin-node-resolve@15.2.3(rollup@4.17.2): + resolution: {integrity: sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.17.2) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-builtin-module: 3.2.1 + is-module: 1.0.0 + resolve: 1.22.4 + rollup: 4.17.2 + dev: true - /csv-generate@3.4.3: - resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} + /@rollup/plugin-replace@5.0.5(rollup@4.17.2): + resolution: {integrity: sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.17.2) + magic-string: 0.30.10 + rollup: 4.17.2 dev: true - /csv-parse@4.16.3: - resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} + /@rollup/plugin-terser@0.4.4(rollup@4.17.2): + resolution: {integrity: sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + dependencies: + rollup: 4.17.2 + serialize-javascript: 6.0.2 + smob: 1.5.0 + terser: 5.31.0 dev: true - /csv-stringify@5.6.5: - resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} + /@rollup/plugin-typescript@11.1.6(rollup@4.17.2)(tslib@2.6.2)(typescript@5.4.4): + resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.14.0||^3.0.0||^4.0.0 + tslib: '*' + typescript: '>=3.7.0' + peerDependenciesMeta: + rollup: + optional: true + tslib: + optional: true + dependencies: + '@rollup/pluginutils': 5.1.0(rollup@4.17.2) + resolve: 1.22.4 + rollup: 4.17.2 + tslib: 2.6.2 + typescript: 5.4.4 dev: true - /csv@5.5.3: - resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} - engines: {node: '>= 0.1.90'} + /@rollup/pluginutils@5.1.0(rollup@4.17.2): + resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: - csv-generate: 3.4.3 - csv-parse: 4.16.3 - csv-stringify: 5.6.5 - stream-transform: 2.1.3 + '@types/estree': 1.0.5 + estree-walker: 2.0.2 + picomatch: 2.3.1 + rollup: 4.17.2 dev: true - /dataloader@1.4.0: - resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + /@rollup/rollup-android-arm-eabi@4.17.2: + resolution: {integrity: sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==} + cpu: [arm] + os: [android] + requiresBuild: true dev: true + optional: true - /dateformat@4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + /@rollup/rollup-android-arm64@4.17.2: + resolution: {integrity: sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==} + cpu: [arm64] + os: [android] + requiresBuild: true dev: true + optional: true - /dayjs@1.11.10: - resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + /@rollup/rollup-darwin-arm64@4.17.2: + resolution: {integrity: sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==} + cpu: [arm64] + os: [darwin] + requiresBuild: true dev: true + optional: true - /dc-polyfill@0.1.3: - resolution: {integrity: sha512-Wyk5n/5KUj3GfVKV2jtDbtChC/Ff9fjKsBcg4ZtYW1yQe3DXNHcGURvmoxhqQdfOQ9TwyMjnfyv1lyYcOkFkFA==} - engines: {node: '>=12.17'} - dev: false + /@rollup/rollup-darwin-x64@4.17.2: + resolution: {integrity: sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true - /dd-trace@3.13.2: - resolution: {integrity: sha512-POO9nEcAufe5pgp2xV1X3PfWip6wh+6TpEcRSlSgZJCIIMvWVCkcIVL/J2a6KAZq6V3Yjbkl8Ktfe+MOzQf5kw==} - engines: {node: '>=14'} + /@rollup/rollup-linux-arm-gnueabihf@4.17.2: + resolution: {integrity: sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==} + cpu: [arm] + os: [linux] requiresBuild: true - dependencies: - '@datadog/native-appsec': 2.0.0 - '@datadog/native-iast-rewriter': 1.1.2 - '@datadog/native-iast-taint-tracking': 1.1.0 - '@datadog/native-metrics': 1.6.0 - '@datadog/pprof': 1.1.1 - '@datadog/sketches-js': 2.1.0 - crypto-randomuuid: 1.0.0 - diagnostics_channel: 1.1.0 - ignore: 5.2.4 - import-in-the-middle: 1.4.2 - ipaddr.js: 2.1.0 - istanbul-lib-coverage: 3.2.0 - koalas: 1.0.2 - limiter: 1.1.5 - lodash.kebabcase: 4.1.1 - lodash.pick: 4.4.0 - lodash.sortby: 4.7.0 - lodash.uniq: 4.5.0 - lru-cache: 7.18.3 - methods: 1.1.2 - module-details-from-path: 1.0.3 - node-abort-controller: 3.1.1 - opentracing: 0.14.7 - path-to-regexp: 0.1.7 - protobufjs: 7.2.5 - retry: 0.10.1 - semver: 5.7.2 - dev: false + dev: true + optional: true - /dd-trace@4.20.0: - resolution: {integrity: sha512-y7IDLSSt6nww6zMdw/I8oLdfgOQADIOkERCNdfSzlBrXHz5CHimEOFfsHN87ag0mn6vusr06aoi+CQRZSNJz2g==} - engines: {node: '>=16'} + /@rollup/rollup-linux-arm-musleabihf@4.17.2: + resolution: {integrity: sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==} + cpu: [arm] + os: [linux] requiresBuild: true - dependencies: - '@datadog/native-appsec': 4.0.0 - '@datadog/native-iast-rewriter': 2.2.1 - '@datadog/native-iast-taint-tracking': 1.6.4 - '@datadog/native-metrics': 2.0.0 - '@datadog/pprof': 4.0.1 - '@datadog/sketches-js': 2.1.0 - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.18.1(@opentelemetry/api@1.7.0) - crypto-randomuuid: 1.0.0 - dc-polyfill: 0.1.3 - ignore: 5.2.4 - import-in-the-middle: 1.4.2 - int64-buffer: 0.1.10 - ipaddr.js: 2.1.0 - istanbul-lib-coverage: 3.2.0 - jest-docblock: 29.7.0 - koalas: 1.0.2 - limiter: 1.1.5 - lodash.kebabcase: 4.1.1 - lodash.pick: 4.4.0 - lodash.sortby: 4.7.0 - lodash.uniq: 4.5.0 - lru-cache: 7.18.3 - methods: 1.1.2 - module-details-from-path: 1.0.3 - msgpack-lite: 0.1.26 - node-abort-controller: 3.1.1 - opentracing: 0.14.7 - path-to-regexp: 0.1.7 - pprof-format: 2.0.7 - protobufjs: 7.2.5 - retry: 0.13.1 - semver: 7.5.4 - dev: false + dev: true + optional: true - /debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.0.0 + /@rollup/rollup-linux-arm64-gnu@4.17.2: + resolution: {integrity: sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true + /@rollup/rollup-linux-arm64-musl@4.17.2: + resolution: {integrity: sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-powerpc64le-gnu@4.17.2: + resolution: {integrity: sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.17.2: + resolution: {integrity: sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-s390x-gnu@4.17.2: + resolution: {integrity: sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.17.2: + resolution: {integrity: sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.17.2: + resolution: {integrity: sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.17.2: + resolution: {integrity: sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.17.2: + resolution: {integrity: sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.17.2: + resolution: {integrity: sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sideway/address@4.1.5: + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + dependencies: + '@hapi/hoek': 9.3.0 + dev: false + + /@sideway/formula@3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + dev: false + + /@sideway/pinpoint@2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + dev: false + + /@sinclair/typebox@0.24.51: + resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==} + dev: true + + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: false + + /@sinonjs/commons@1.8.6: + resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==} + dependencies: + type-detect: 4.0.8 + dev: true + + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + dependencies: + type-detect: 4.0.8 + dev: false + + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + dependencies: + '@sinonjs/commons': 3.0.1 + dev: false + + /@sinonjs/fake-timers@9.1.2: + resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==} + dependencies: + '@sinonjs/commons': 1.8.6 + dev: true + + /@smithy/abort-controller@1.1.0: + resolution: {integrity: sha512-5imgGUlZL4dW4YWdMYAKLmal9ny/tlenM81QZY7xYyb76z9Z/QOg7oM5Ak9HQl8QfFTlGVWwcMXl+54jroRgEQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 1.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/types@1.2.0: + resolution: {integrity: sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@swc/core-darwin-arm64@1.3.42: + resolution: {integrity: sha512-hM6RrZFyoCM9mX3cj/zM5oXwhAqjUdOCLXJx7KTQps7NIkv/Qjvobgvyf2gAb89j3ARNo9NdIoLjTjJ6oALtiA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-darwin-x64@1.3.42: + resolution: {integrity: sha512-bjsWtHMb6wJK1+RGlBs2USvgZ0txlMk11y0qBLKo32gLKTqzUwRw0Fmfzuf6Ue2a/w//7eqMlPFEre4LvJajGw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm-gnueabihf@1.3.42: + resolution: {integrity: sha512-Oe0ggMz3MyqXNfeVmY+bBTL0hFSNY3bx8dhcqsh4vXk/ZVGse94QoC4dd92LuPHmKT0x6nsUzB86x2jU9QHW5g==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-gnu@1.3.42: + resolution: {integrity: sha512-ZJsa8NIW1RLmmHGTJCbM7OPSbBZ9rOMrLqDtUOGrT0uoJXZnnQqolflamB5wviW0X6h3Z3/PSTNGNDCJ3u3Lqg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-musl@1.3.42: + resolution: {integrity: sha512-YpZwlFAfOp5vkm/uVUJX1O7N3yJDO1fDQRWqsOPPNyIJkI2ydlRQtgN6ZylC159Qv+TimfXnGTlNr7o3iBAqjg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-gnu@1.3.42: + resolution: {integrity: sha512-0ccpKnsZbyHBzaQFdP8U9i29nvOfKitm6oJfdJzlqsY/jCqwvD8kv2CAKSK8WhJz//ExI2LqNrDI0yazx5j7+A==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-musl@1.3.42: + resolution: {integrity: sha512-7eckRRuTZ6+3K21uyfXXgc2ZCg0mSWRRNwNT3wap2bYkKPeqTgb8pm8xYSZNEiMuDonHEat6XCCV36lFY6kOdQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-arm64-msvc@1.3.42: + resolution: {integrity: sha512-t27dJkdw0GWANdN4TV0lY/V5vTYSx5SRjyzzZolep358ueCGuN1XFf1R0JcCbd1ojosnkQg2L7A7991UjXingg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-ia32-msvc@1.3.42: + resolution: {integrity: sha512-xfpc/Zt/aMILX4IX0e3loZaFyrae37u3MJCv1gJxgqrpeLi7efIQr3AmERkTK3mxTO6R5urSliWw2W3FyZ7D3Q==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-x64-msvc@1.3.42: + resolution: {integrity: sha512-ra2K4Tu++EJLPhzZ6L8hWUsk94TdK/2UKhL9dzCBhtzKUixsGCEqhtqH1zISXNvW8qaVLFIMUP37ULe80/IJaA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core@1.3.42: + resolution: {integrity: sha512-nVFUd5+7tGniM2cT3LXaqnu3735Cu4az8A9gAKK+8sdpASI52SWuqfDBmjFCK9xG90MiVDVp2PTZr0BWqCIzpw==} + engines: {node: '>=10'} + requiresBuild: true + optionalDependencies: + '@swc/core-darwin-arm64': 1.3.42 + '@swc/core-darwin-x64': 1.3.42 + '@swc/core-linux-arm-gnueabihf': 1.3.42 + '@swc/core-linux-arm64-gnu': 1.3.42 + '@swc/core-linux-arm64-musl': 1.3.42 + '@swc/core-linux-x64-gnu': 1.3.42 + '@swc/core-linux-x64-musl': 1.3.42 + '@swc/core-win32-arm64-msvc': 1.3.42 + '@swc/core-win32-ia32-msvc': 1.3.42 + '@swc/core-win32-x64-msvc': 1.3.42 + dev: true + + /@swc/jest@0.2.24(@swc/core@1.3.42): + resolution: {integrity: sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q==} + engines: {npm: '>= 7.0.0'} + peerDependencies: + '@swc/core': '*' + dependencies: + '@jest/create-cache-key-function': 27.5.1 + '@swc/core': 1.3.42 + jsonc-parser: 3.2.0 + dev: true + + /@tokenizer/token@0.3.0: + resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: true + + /@trysound/sax@0.2.0: + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + dev: true + + /@ts-morph/common@0.17.0: + resolution: {integrity: sha512-RMSSvSfs9kb0VzkvQ2NWobwnj7TxCA9vI/IjR9bDHqgAyVbu2T0DN4wiKVqomyDWqO7dPr/tErSfq7urQ1Q37g==} + dependencies: + fast-glob: 3.3.1 + minimatch: 5.1.6 + mkdirp: 1.0.4 + path-browserify: 1.0.1 + dev: false + + /@tsconfig/node10@1.0.9: + resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} + dev: true + + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + dev: true + + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + dev: true + + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + dev: true + + /@types/babel__core@7.20.1: + resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} + dependencies: + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + '@types/babel__generator': 7.6.4 + '@types/babel__template': 7.4.1 + '@types/babel__traverse': 7.20.1 + dev: true + + /@types/babel__generator@7.6.4: + resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@types/babel__template@7.4.1: + resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} + dependencies: + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + dev: true + + /@types/babel__traverse@7.20.1: + resolution: {integrity: sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==} + dependencies: + '@babel/types': 7.22.10 + dev: true + + /@types/bn.js@5.1.1: + resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==} + dependencies: + '@types/node': 18.19.24 + + /@types/body-parser@1.19.2: + resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + dependencies: + '@types/connect': 3.4.35 + '@types/node': 18.19.24 + + /@types/connect@3.4.35: + resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + dependencies: + '@types/node': 18.19.24 + + /@types/cookie@0.6.0: + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + dev: true + + /@types/cors@2.8.12: + resolution: {integrity: sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==} + dev: true + + /@types/disposable-email@0.2.0: + resolution: {integrity: sha512-i4fPC8+a5j7RlKVe9cOHz6cYVxkSFYuJ78GB3EJPMfBJURWmEsD4gb/gD48j7KAWe0M8ZvdJR6a6GaDohTYttw==} + dev: true + + /@types/elliptic@6.4.14: + resolution: {integrity: sha512-z4OBcDAU0GVwDTuwJzQCiL6188QvZMkvoERgcVjq0/mPM8jCfdwZ3x5zQEVoL9WCAru3aG5wl3Z5Ww5wBWn7ZQ==} + dependencies: + '@types/bn.js': 5.1.1 + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/express-serve-static-core@4.17.36: + resolution: {integrity: sha512-zbivROJ0ZqLAtMzgzIUC4oNqDG9iF0lSsAqpOD9kbs5xcIM3dTiyuHvBc7R8MtWBp3AAWGaovJa+wzWPjLYW7Q==} + dependencies: + '@types/node': 18.17.8 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + '@types/send': 0.17.1 + + /@types/express@4.17.13: + resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==} + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.36 + '@types/qs': 6.9.7 + '@types/serve-static': 1.15.2 + dev: true + + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.36 + '@types/qs': 6.9.7 + '@types/serve-static': 1.15.2 + + /@types/graceful-fs@4.1.6: + resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} + dependencies: + '@types/node': 18.19.24 + dev: true + + /@types/http-errors@2.0.1: + resolution: {integrity: sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==} + + /@types/is-ci@3.0.0: + resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} + dependencies: + ci-info: 3.8.0 + dev: true + + /@types/istanbul-lib-coverage@2.0.4: + resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} + + /@types/istanbul-lib-report@3.0.0: + resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} + dependencies: + '@types/istanbul-lib-coverage': 2.0.4 + + /@types/istanbul-reports@3.0.1: + resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} + dependencies: + '@types/istanbul-lib-report': 3.0.0 + + /@types/jest@28.1.4: + resolution: {integrity: sha512-telv6G5N7zRJiLcI3Rs3o+ipZ28EnE+7EvF0pSrt2pZOMnAVI/f+6/LucDxOvcBcTeTL3JMF744BbVQAVBUQRA==} + dependencies: + jest-matcher-utils: 28.1.3 + pretty-format: 28.1.3 + dev: true + + /@types/json-schema@7.0.12: + resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==} + dev: true + + /@types/keygrip@1.0.6: + resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + dev: true + + /@types/mime@1.3.2: + resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + + /@types/mime@3.0.1: + resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==} + + /@types/minimist@1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + + /@types/node-forge@1.3.11: + resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} + dependencies: + '@types/node': 18.19.24 + dev: false + + /@types/node@12.20.55: + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + dev: true + + /@types/node@18.17.8: + resolution: {integrity: sha512-Av/7MqX/iNKwT9Tr60V85NqMnsmh8ilfJoBlIVibkXfitk9Q22D9Y5mSpm+FvG5DET7EbVfB40bOiLzKgYFgPw==} + + /@types/node@18.19.24: + resolution: {integrity: sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==} + dependencies: + undici-types: 5.26.5 + + /@types/nodemailer@6.4.6: + resolution: {integrity: sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==} + dependencies: + '@types/node': 18.17.8 + dev: true + + /@types/normalize-package-data@2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true + + /@types/pg@8.6.6: + resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==} + dependencies: + '@types/node': 18.17.8 + pg-protocol: 1.6.0 + pg-types: 2.2.0 + dev: true + + /@types/prettier@2.7.3: + resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} + dev: true + + /@types/prop-types@15.7.12: + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + dev: true + + /@types/qs@6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + + /@types/range-parser@1.2.4: + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + + /@types/react-dom@18.3.0: + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + dependencies: + '@types/react': 18.3.2 + dev: true + + /@types/react@18.3.2: + resolution: {integrity: sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==} + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + dev: true + + /@types/resolve@1.20.2: + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + dev: true + + /@types/semver@7.5.0: + resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} + dev: true + + /@types/send@0.17.1: + resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} + dependencies: + '@types/mime': 1.3.2 + '@types/node': 18.19.24 + + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + dependencies: + '@types/mime': 1.3.2 + '@types/node': 18.19.24 + dev: true + + /@types/serve-static@1.15.2: + resolution: {integrity: sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==} + dependencies: + '@types/http-errors': 2.0.1 + '@types/mime': 3.0.1 + '@types/node': 18.19.24 + + /@types/shimmer@1.0.5: + resolution: {integrity: sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==} + dev: false + + /@types/stack-utils@2.0.1: + resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} + + /@types/ws@8.5.4: + resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==} + dependencies: + '@types/node': 18.17.8 + dev: true + + /@types/yargs-parser@21.0.0: + resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} + + /@types/yargs@15.0.19: + resolution: {integrity: sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: false + + /@types/yargs@16.0.5: + resolution: {integrity: sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==} + dependencies: + '@types/yargs-parser': 21.0.0 + dev: true + + /@types/yargs@17.0.24: + resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} + dependencies: + '@types/yargs-parser': 21.0.0 + + /@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.4): + resolution: {integrity: sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/scope-manager': 7.4.0 + '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.4) + '@typescript-eslint/visitor-keys': 7.4.0 + debug: 4.3.4 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.2.4 + natural-compare: 1.4.0 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.4.4) + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.4.4): + resolution: {integrity: sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/scope-manager': 7.4.0 + '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.4) + '@typescript-eslint/visitor-keys': 7.4.0 + debug: 4.3.4 + eslint: 8.57.0 + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/scope-manager@7.4.0: + resolution: {integrity: sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/visitor-keys': 7.4.0 + dev: true + + /@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.4.4): + resolution: {integrity: sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.4) + '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.4) + debug: 4.3.4 + eslint: 8.57.0 + ts-api-utils: 1.0.3(typescript@5.4.4) + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@7.4.0: + resolution: {integrity: sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==} + engines: {node: ^18.18.0 || >=20.0.0} + dev: true + + /@typescript-eslint/typescript-estree@7.4.0(typescript@5.4.4): + resolution: {integrity: sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/visitor-keys': 7.4.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.5.4 + ts-api-utils: 1.0.3(typescript@5.4.4) + typescript: 5.4.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.4.4): + resolution: {integrity: sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.12 + '@types/semver': 7.5.0 + '@typescript-eslint/scope-manager': 7.4.0 + '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.4) + eslint: 8.57.0 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@7.4.0: + resolution: {integrity: sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.4.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@typescript/vfs@1.5.0: + resolution: {integrity: sha512-AJS307bPgbsZZ9ggCT3wwpg3VbTKMFNHfaY/uF0ahSkYYrPF2dSSKDNIDIQAHm9qJqbLvCsSJH7yN4Vs/CsMMg==} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + + /@web/rollup-plugin-import-meta-assets@2.2.1(rollup@4.17.2): + resolution: {integrity: sha512-nG7nUQqSJWdl63pBTmnIElJuFi2V1x9eVje19BJuFvfz266jSmZtX3m30ncb7fOJxQt3/ge+FVL8tuNI9+63dQ==} + engines: {node: '>=18.0.0'} + dependencies: + '@rollup/plugin-dynamic-import-vars': 2.1.2(rollup@4.17.2) + '@rollup/pluginutils': 5.1.0(rollup@4.17.2) + estree-walker: 2.0.2 + globby: 13.2.2 + magic-string: 0.30.10 + transitivePeerDependencies: + - rollup + dev: true + + /abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + dev: true + + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + /acorn-import-assertions@1.9.0(acorn@8.10.0): + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.10.0 + dev: false + + /acorn-jsx@5.3.2(acorn@8.10.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.10.0 + dev: true + + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn@8.10.0: + resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} + engines: {node: '>=0.4.0'} + hasBin: true + + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: true + + /aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + + /ajv-formats@2.1.1(ajv@8.12.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: + ajv: 8.12.0 + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + /anser@1.4.10: + resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + dev: false + + /ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true + + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.21.3 + dev: true + + /ansi-fragments@0.2.1: + resolution: {integrity: sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==} + dependencies: + colorette: 1.4.0 + slice-ansi: 2.1.0 + strip-ansi: 5.2.0 + dev: false + + /ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + dev: false + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + /appdirsjs@1.2.7: + resolution: {integrity: sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==} + dev: false + + /aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + dev: true + + /are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + dev: true + + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true + + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + dependencies: + sprintf-js: 1.0.3 + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + dependencies: + call-bind: 1.0.2 + is-array-buffer: 3.0.2 + dev: true + + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true + + /array.prototype.flat@1.3.1: + resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + es-shim-unscopables: 1.0.0 + dev: true + + /arraybuffer.prototype.slice@1.0.1: + resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.2 + define-properties: 1.2.0 + get-intrinsic: 1.2.1 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + dev: true + + /arrify@1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + + /asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: false + + /asn1.js@5.4.1: + resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + dependencies: + bn.js: 4.12.0 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + safer-buffer: 2.1.2 + + /ast-types@0.15.2: + resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==} + engines: {node: '>=4'} + dependencies: + tslib: 2.6.2 + dev: false + + /astral-regex@1.0.0: + resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} + engines: {node: '>=4'} + dev: false + + /astring@1.8.6: + resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} + hasBin: true + dev: true + + /async-limiter@1.0.1: + resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + dev: false + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + /atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + /autoprefixer@10.4.19(postcss@8.4.38): + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.23.0 + caniuse-lite: 1.0.30001618 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + + /available-typed-arrays@1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /axios@0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + + /axios@1.4.0: + resolution: {integrity: sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==} + dependencies: + follow-redirects: 1.15.2 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + /axios@1.6.2: + resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==} + dependencies: + follow-redirects: 1.15.3 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + /axios@1.6.7: + resolution: {integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==} + dependencies: + follow-redirects: 1.15.5 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + /b4a@1.6.4: + resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} + + /babel-core@7.0.0-bridge.0(@babel/core@7.18.6): + resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.18.6 + dev: false + + /babel-jest@28.1.3(@babel/core@7.18.6): + resolution: {integrity: sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + dependencies: + '@babel/core': 7.18.6 + '@jest/transform': 28.1.3 + '@types/babel__core': 7.20.1 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 28.1.3(@babel/core@7.18.6) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + dependencies: + '@babel/helper-plugin-utils': 7.22.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + dev: true + + /babel-plugin-jest-hoist@28.1.3: + resolution: {integrity: sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@babel/template': 7.22.5 + '@babel/types': 7.22.10 + '@types/babel__core': 7.20.1 + '@types/babel__traverse': 7.20.1 + dev: true + + /babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.18.6): + resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/compat-data': 7.22.9 + '@babel/core': 7.18.6 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.18.6) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: false + + /babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.18.6): + resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.18.6) + core-js-compat: 3.37.1 + transitivePeerDependencies: + - supports-color + dev: false + + /babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.18.6): + resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.18.6) + transitivePeerDependencies: + - supports-color + dev: false + + /babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.18.6): + resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} + dependencies: + '@babel/plugin-syntax-flow': 7.24.1(@babel/core@7.18.6) + transitivePeerDependencies: + - '@babel/core' + dev: false + + /babel-preset-current-node-syntax@1.0.1(@babel/core@7.18.6): + resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.6) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.18.6) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.18.6) + dev: true + + /babel-preset-jest@28.1.3(@babel/core@7.18.6): + resolution: {integrity: sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.18.6 + babel-plugin-jest-hoist: 28.1.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.18.6) + dev: true + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + /better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + dependencies: + is-windows: 1.0.2 + dev: true + + /better-sqlite3@9.4.0: + resolution: {integrity: sha512-5kynxekMxSjCMiFyUBLHggFcJkCmiZi6fUkiGz/B5GZOvdRWQJD0klqCx5/Y+bm2AKP7I/DHbSFx26AxjruWNg==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.1 + dev: false + + /better-sqlite3@9.4.5: + resolution: {integrity: sha512-uFVyoyZR9BNcjSca+cp3MWCv6upAv+tbMC4SWM51NIMhoQOm4tjIkyxFO/ZsYdGAF61WJBgdzyJcz4OokJi0gQ==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.2 + + /big-integer@1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + dev: true + + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + /bn.js@4.12.0: + resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} + + /body-parser@1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + + /boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + + /bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + dev: false + + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + + /braces@3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + + /breakword@1.0.6: + resolution: {integrity: sha512-yjxDAYyK/pBvws9H4xKYpLDpYKEH6CzrBPAuXq3x18I+c/2MkVtT3qAr7Oloi6Dss9qNhPVueAAVU1CSeNDIXw==} + dependencies: + wcwidth: 1.0.1 + dev: true + + /brorand@1.1.0: + resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} + + /browserslist@4.21.10: + resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001522 + electron-to-chromium: 1.4.499 + node-releases: 2.0.13 + update-browserslist-db: 1.0.11(browserslist@4.21.10) + + /browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001618 + electron-to-chromium: 1.4.769 + node-releases: 2.0.14 + update-browserslist-db: 1.0.16(browserslist@4.23.0) + + /bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + dependencies: + node-int64: 0.4.0 + + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: true + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + /buffer-writer@2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + + /buffer@5.6.0: + resolution: {integrity: sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + /builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + dev: true + + /bytes@3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + /cacache@16.1.3: + resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + '@npmcli/fs': 2.1.2 + '@npmcli/move-file': 2.0.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 8.1.0 + infer-owner: 1.0.4 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 9.0.1 + tar: 6.1.15 + unique-filename: 2.0.1 + transitivePeerDependencies: + - bluebird + dev: true + + /call-bind@1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.2.1 + + /caller-callsite@2.0.0: + resolution: {integrity: sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==} + engines: {node: '>=4'} + dependencies: + callsites: 2.0.0 + dev: false + + /caller-path@2.0.0: + resolution: {integrity: sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==} + engines: {node: '>=4'} + dependencies: + caller-callsite: 2.0.0 + dev: false + + /callsites@2.0.0: + resolution: {integrity: sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==} + engines: {node: '>=4'} + dev: false + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + dev: true + + /camelcase-keys@6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + /caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + dependencies: + browserslist: 4.21.10 + caniuse-lite: 1.0.30001522 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + dev: true + + /caniuse-lite@1.0.30001522: + resolution: {integrity: sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==} + + /caniuse-lite@1.0.30001618: + resolution: {integrity: sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==} + + /cbor-extract@2.2.0: + resolution: {integrity: sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==} + hasBin: true + requiresBuild: true + dependencies: + node-gyp-build-optional-packages: 5.1.1 + optionalDependencies: + '@cbor-extract/cbor-extract-darwin-arm64': 2.2.0 + '@cbor-extract/cbor-extract-darwin-x64': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm': 2.2.0 + '@cbor-extract/cbor-extract-linux-arm64': 2.2.0 + '@cbor-extract/cbor-extract-linux-x64': 2.2.0 + '@cbor-extract/cbor-extract-win32-x64': 2.2.0 + dev: false + optional: true + + /cbor-x@1.5.1: + resolution: {integrity: sha512-/vAkC4tiKCQCm5en4sA+mpKmjwY6Xxp1LO+BgZCNhp+Zow3pomyUHeBOK5EDp0mDaE36jw39l5eLHsoF3M1Lmg==} + optionalDependencies: + cbor-extract: 2.2.0 + dev: false + + /cborg@1.10.2: + resolution: {integrity: sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug==} + hasBin: true + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + /char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + dev: true + + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + /chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + dev: true + + /chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + dependencies: + '@types/node': 18.19.24 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + dev: false + + /ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + dev: false + + /ci-info@3.8.0: + resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} + engines: {node: '>=8'} + + /cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + + /clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: false + + /cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + dev: false + + /cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + /clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + dev: false + + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + /cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + /co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true + + /code-block-writer@11.0.3: + resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==} + dev: false + + /collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + dev: true + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + /color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + dev: true + + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + /colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + dev: true + + /colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + dev: false + + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: true + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + + /command-exists@1.2.9: + resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} + dev: false + + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: true + + /commander@9.4.0: + resolution: {integrity: sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==} + engines: {node: ^12.20.0 || >=14} + dev: false + + /commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + dev: false + + /commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + /compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + + /compression@1.7.4: + resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + engines: {node: '>= 0.8.0'} + dependencies: + accepts: 1.3.8 + bytes: 3.0.0 + compressible: 2.0.18 + debug: 2.6.9 + on-headers: 1.0.2 + safe-buffer: 5.1.2 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + /concat-with-sourcemaps@1.1.0: + resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} + dependencies: + source-map: 0.6.1 + dev: true + + /connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + dev: true + + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: false + + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + + /cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + dev: false + + /core-js-compat@3.37.1: + resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} + dependencies: + browserslist: 4.23.0 + dev: false + + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: false + + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + /cosmiconfig@5.2.1: + resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} + engines: {node: '>=4'} + dependencies: + import-fresh: 2.0.0 + is-directory: 0.3.1 + js-yaml: 3.14.1 + parse-json: 4.0.0 + dev: false + + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + dev: true + + /cross-spawn@7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + /crypto-randomuuid@1.0.0: + resolution: {integrity: sha512-/RC5F4l1SCqD/jazwUF6+t34Cd8zTSAGZ7rvvZu1whZUhD2a5MOGKjSGowoGcpj/cbVZk1ZODIooJEQQq3nNAA==} + dev: false + + /css-declaration-sorter@6.4.1(postcss@8.4.38): + resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} + engines: {node: ^10 || ^12 || >=14} + peerDependencies: + postcss: ^8.0.9 + dependencies: + postcss: 8.4.38 + dev: true + + /css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + dev: true + + /css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + dev: true + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /cssnano-preset-default@5.2.14(postcss@8.4.38): + resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + css-declaration-sorter: 6.4.1(postcss@8.4.38) + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-calc: 8.2.4(postcss@8.4.38) + postcss-colormin: 5.3.1(postcss@8.4.38) + postcss-convert-values: 5.1.3(postcss@8.4.38) + postcss-discard-comments: 5.1.2(postcss@8.4.38) + postcss-discard-duplicates: 5.1.0(postcss@8.4.38) + postcss-discard-empty: 5.1.1(postcss@8.4.38) + postcss-discard-overridden: 5.1.0(postcss@8.4.38) + postcss-merge-longhand: 5.1.7(postcss@8.4.38) + postcss-merge-rules: 5.1.4(postcss@8.4.38) + postcss-minify-font-values: 5.1.0(postcss@8.4.38) + postcss-minify-gradients: 5.1.1(postcss@8.4.38) + postcss-minify-params: 5.1.4(postcss@8.4.38) + postcss-minify-selectors: 5.2.1(postcss@8.4.38) + postcss-normalize-charset: 5.1.0(postcss@8.4.38) + postcss-normalize-display-values: 5.1.0(postcss@8.4.38) + postcss-normalize-positions: 5.1.1(postcss@8.4.38) + postcss-normalize-repeat-style: 5.1.1(postcss@8.4.38) + postcss-normalize-string: 5.1.0(postcss@8.4.38) + postcss-normalize-timing-functions: 5.1.0(postcss@8.4.38) + postcss-normalize-unicode: 5.1.1(postcss@8.4.38) + postcss-normalize-url: 5.1.0(postcss@8.4.38) + postcss-normalize-whitespace: 5.1.1(postcss@8.4.38) + postcss-ordered-values: 5.1.3(postcss@8.4.38) + postcss-reduce-initial: 5.1.2(postcss@8.4.38) + postcss-reduce-transforms: 5.1.0(postcss@8.4.38) + postcss-svgo: 5.1.0(postcss@8.4.38) + postcss-unique-selectors: 5.1.1(postcss@8.4.38) + dev: true + + /cssnano-utils@3.1.0(postcss@8.4.38): + resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + dev: true + + /cssnano@5.1.15(postcss@8.4.38): + resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + cssnano-preset-default: 5.2.14(postcss@8.4.38) + lilconfig: 2.1.0 + postcss: 8.4.38 + yaml: 1.10.2 + dev: true + + /csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + dependencies: + css-tree: 1.1.3 + dev: true + + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + dev: true + + /csv-generate@3.4.3: + resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} + dev: true + + /csv-parse@4.16.3: + resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} + dev: true + + /csv-stringify@5.6.5: + resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} + dev: true + + /csv@5.5.3: + resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} + engines: {node: '>= 0.1.90'} + dependencies: + csv-generate: 3.4.3 + csv-parse: 4.16.3 + csv-stringify: 5.6.5 + stream-transform: 2.1.3 + dev: true + + /dataloader@1.4.0: + resolution: {integrity: sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==} + dev: true + + /dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + dev: true + + /dayjs@1.11.10: + resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + + /dc-polyfill@0.1.3: + resolution: {integrity: sha512-Wyk5n/5KUj3GfVKV2jtDbtChC/Ff9fjKsBcg4ZtYW1yQe3DXNHcGURvmoxhqQdfOQ9TwyMjnfyv1lyYcOkFkFA==} + engines: {node: '>=12.17'} + dev: false + + /dd-trace@3.13.2: + resolution: {integrity: sha512-POO9nEcAufe5pgp2xV1X3PfWip6wh+6TpEcRSlSgZJCIIMvWVCkcIVL/J2a6KAZq6V3Yjbkl8Ktfe+MOzQf5kw==} + engines: {node: '>=14'} + requiresBuild: true + dependencies: + '@datadog/native-appsec': 2.0.0 + '@datadog/native-iast-rewriter': 1.1.2 + '@datadog/native-iast-taint-tracking': 1.1.0 + '@datadog/native-metrics': 1.6.0 + '@datadog/pprof': 1.1.1 + '@datadog/sketches-js': 2.1.0 + crypto-randomuuid: 1.0.0 + diagnostics_channel: 1.1.0 + ignore: 5.2.4 + import-in-the-middle: 1.4.2 + ipaddr.js: 2.1.0 + istanbul-lib-coverage: 3.2.0 + koalas: 1.0.2 + limiter: 1.1.5 + lodash.kebabcase: 4.1.1 + lodash.pick: 4.4.0 + lodash.sortby: 4.7.0 + lodash.uniq: 4.5.0 + lru-cache: 7.18.3 + methods: 1.1.2 + module-details-from-path: 1.0.3 + node-abort-controller: 3.1.1 + opentracing: 0.14.7 + path-to-regexp: 0.1.7 + protobufjs: 7.2.5 + retry: 0.10.1 + semver: 5.7.2 + dev: false + + /dd-trace@4.20.0: + resolution: {integrity: sha512-y7IDLSSt6nww6zMdw/I8oLdfgOQADIOkERCNdfSzlBrXHz5CHimEOFfsHN87ag0mn6vusr06aoi+CQRZSNJz2g==} + engines: {node: '>=16'} + requiresBuild: true + dependencies: + '@datadog/native-appsec': 4.0.0 + '@datadog/native-iast-rewriter': 2.2.1 + '@datadog/native-iast-taint-tracking': 1.6.4 + '@datadog/native-metrics': 2.0.0 + '@datadog/pprof': 4.0.1 + '@datadog/sketches-js': 2.1.0 + '@opentelemetry/api': 1.7.0 + '@opentelemetry/core': 1.18.1(@opentelemetry/api@1.7.0) + crypto-randomuuid: 1.0.0 + dc-polyfill: 0.1.3 + ignore: 5.2.4 + import-in-the-middle: 1.4.2 + int64-buffer: 0.1.10 + ipaddr.js: 2.1.0 + istanbul-lib-coverage: 3.2.0 + jest-docblock: 29.7.0 + koalas: 1.0.2 + limiter: 1.1.5 + lodash.kebabcase: 4.1.1 + lodash.pick: 4.4.0 + lodash.sortby: 4.7.0 + lodash.uniq: 4.5.0 + lru-cache: 7.18.3 + methods: 1.1.2 + module-details-from-path: 1.0.3 + msgpack-lite: 0.1.26 + node-abort-controller: 3.1.1 + opentracing: 0.14.7 + path-to-regexp: 0.1.7 + pprof-format: 2.0.7 + protobufjs: 7.2.5 + retry: 0.13.1 + semver: 7.5.4 + dev: false + + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + + /debug@4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + + /decamelize-keys@1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + + /dedent@0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + dev: true + + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + dependencies: + clone: 1.0.4 + + /define-properties@1.2.0: + resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + + /delay@5.0.0: + resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} + engines: {node: '>=10'} + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + /delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dev: true + + /denodeify@1.2.1: + resolution: {integrity: sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==} + dev: false + + /denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + /detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + + /detect-libc@2.0.2: + resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} + engines: {node: '>=8'} + + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + + /detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + /diagnostics_channel@1.1.0: + resolution: {integrity: sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw==} + engines: {node: '>=4'} + dev: false + + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: true + + /diff-sequences@28.1.1: + resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dev: true + + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true + + /disposable-email@0.2.3: + resolution: {integrity: sha512-gkBQQ5Res431ZXqLlAafrXHizG7/1FWmi8U2RTtriD78Vc10HhBUvdJun3R4eSF0KRIQQJs+wHlxjkED/Hr1EQ==} + + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: true + + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + /domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + + /domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + /dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: true + + /dotenv@8.6.0: + resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} + engines: {node: '>=10'} + dev: true + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + /electron-to-chromium@1.4.499: + resolution: {integrity: sha512-0NmjlYBLKVHva4GABWAaHuPJolnDuL0AhV3h1hES6rcLCWEIbRL6/8TghfsVwkx6TEroQVdliX7+aLysUpKvjw==} + + /electron-to-chromium@1.4.769: + resolution: {integrity: sha512-bZu7p623NEA2rHTc9K1vykl57ektSPQYFFqQir8BOYf6EKOB+yIsbFB9Kpm7Cgt6tsLr9sRkqfqSZUw7LP1XxQ==} + + /elliptic@6.5.4: + resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} + dependencies: + bn.js: 4.12.0 + brorand: 1.1.0 + hash.js: 1.1.7 + hmac-drbg: 1.0.1 + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 + + /emittery@0.10.2: + resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} + engines: {node: '>=12'} + dev: true + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + /encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + requiresBuild: true + dependencies: + iconv-lite: 0.6.3 + dev: true + optional: true + + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + + /enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + dev: true + + /entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: true + + /envinfo@7.13.0: + resolution: {integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==} + engines: {node: '>=4'} + hasBin: true + dev: false + + /err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + dev: true + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + + /error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + dependencies: + stackframe: 1.3.4 + dev: false + + /errorhandler@1.5.1: + resolution: {integrity: sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==} + engines: {node: '>= 0.8'} + dependencies: + accepts: 1.3.8 + escape-html: 1.0.3 + dev: false + + /es-abstract@1.22.1: + resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} + engines: {node: '>= 0.4'} + dependencies: + array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.1 + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-set-tostringtag: 2.0.1 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.1 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.0.1 + has: 1.0.3 + has-property-descriptors: 1.0.0 + has-proto: 1.0.1 + has-symbols: 1.0.3 + internal-slot: 1.0.5 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.12 + is-weakref: 1.0.2 + object-inspect: 1.12.3 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.0 + safe-array-concat: 1.0.1 + safe-regex-test: 1.0.0 + string.prototype.trim: 1.2.7 + string.prototype.trimend: 1.0.6 + string.prototype.trimstart: 1.0.7 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.11 + dev: true + + /es-set-tostringtag@2.0.1: + resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.1 + has: 1.0.3 + has-tostringtag: 1.0.0 + dev: true + + /es-shim-unscopables@1.0.0: + resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} + dependencies: + has: 1.0.3 + dev: true + + /es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /esbuild-android-64@0.14.48: + resolution: {integrity: sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-android-arm64@0.14.48: + resolution: {integrity: sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-64@0.14.48: + resolution: {integrity: sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-darwin-arm64@0.14.48: + resolution: {integrity: sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-64@0.14.48: + resolution: {integrity: sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-freebsd-arm64@0.14.48: + resolution: {integrity: sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-32@0.14.48: + resolution: {integrity: sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-64@0.14.48: + resolution: {integrity: sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm64@0.14.48: + resolution: {integrity: sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-arm@0.14.48: + resolution: {integrity: sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-mips64le@0.14.48: + resolution: {integrity: sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-ppc64le@0.14.48: + resolution: {integrity: sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-riscv64@0.14.48: + resolution: {integrity: sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-linux-s390x@0.14.48: + resolution: {integrity: sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /esbuild-netbsd-64@0.14.48: + resolution: {integrity: sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-openbsd-64@0.14.48: + resolution: {integrity: sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /esbuild-plugin-handlebars@1.0.3: + resolution: {integrity: sha512-vOqurrqU7s4f9xgwDiUG0I+9auJPf21GFf6xYxObRrQdi088N81P4ztCs0xVg0uSmWkIPcZuCLIwP+ttzKpezQ==} + dependencies: + handlebars: 4.7.8 + dev: true + + /esbuild-sunos-64@0.14.48: + resolution: {integrity: sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-32@0.14.48: + resolution: {integrity: sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-64@0.14.48: + resolution: {integrity: sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild-windows-arm64@0.14.48: + resolution: {integrity: sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /esbuild@0.14.48: + resolution: {integrity: sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + esbuild-android-64: 0.14.48 + esbuild-android-arm64: 0.14.48 + esbuild-darwin-64: 0.14.48 + esbuild-darwin-arm64: 0.14.48 + esbuild-freebsd-64: 0.14.48 + esbuild-freebsd-arm64: 0.14.48 + esbuild-linux-32: 0.14.48 + esbuild-linux-64: 0.14.48 + esbuild-linux-arm: 0.14.48 + esbuild-linux-arm64: 0.14.48 + esbuild-linux-mips64le: 0.14.48 + esbuild-linux-ppc64le: 0.14.48 + esbuild-linux-riscv64: 0.14.48 + esbuild-linux-s390x: 0.14.48 + esbuild-netbsd-64: 0.14.48 + esbuild-openbsd-64: 0.14.48 + esbuild-sunos-64: 0.14.48 + esbuild-windows-32: 0.14.48 + esbuild-windows-64: 0.14.48 + esbuild-windows-arm64: 0.14.48 + dev: true + + /escalade@3.1.1: + resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} + engines: {node: '>=6'} + + /escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + /escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + /eslint-config-prettier@9.1.0(eslint@8.57.0): + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.57.0 + dev: true + + /eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5): + resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.57.0 + eslint-config-prettier: 9.1.0(eslint@8.57.0) + prettier: 3.2.5 + prettier-linter-helpers: 1.0.0 + synckit: 0.8.8 + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true dependencies: - ms: 2.1.2 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.10.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.21.0 + graphemer: 1.4.0 + ignore: 5.2.4 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true - /decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 + acorn: 8.10.0 + acorn-jsx: 5.3.2(acorn@8.10.0) + eslint-visitor-keys: 3.4.3 dev: true - /decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 dev: true - /decompress-response@6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} dependencies: - mimic-response: 3.1.0 + estraverse: 5.3.0 + dev: true - /dedent@0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} dev: true - /deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} + /estree-walker@0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + dev: true - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - /defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + /event-lite@0.1.3: + resolution: {integrity: sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==} + dev: false + + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + /eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} dependencies: - clone: 1.0.4 + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + /exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} dev: true - /define-properties@1.2.0: - resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} - engines: {node: '>= 0.4'} + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + /expect@28.1.3: + resolution: {integrity: sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 + '@jest/expect-utils': 28.1.3 + jest-get-type: 28.0.2 + jest-matcher-utils: 28.1.3 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + dev: true - /delay@5.0.0: - resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} - engines: {node: '>=10'} + /express-async-errors@3.1.1(express@4.18.2): + resolution: {integrity: sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==} + peerDependencies: + express: ^4.16.2 + dependencies: + express: 4.18.2 - /delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} + /express@4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color - /delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + /extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} dev: true - /denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true - /depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} + /fast-copy@2.1.7: + resolution: {integrity: sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==} + dev: true - /destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - /detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} + /fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: true - /detect-libc@2.0.2: - resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} - engines: {node: '>=8'} - - /detect-libc@2.0.3: - resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} - engines: {node: '>=8'} + /fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - /detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 - /diagnostics_channel@1.1.0: - resolution: {integrity: sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw==} - engines: {node: '>=4'} + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 dev: false - /diff-sequences@28.1.1: - resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} + /fast-json-stringify@5.8.0: + resolution: {integrity: sha512-VVwK8CFMSALIvt14U8AvrSzQAwN/0vaVRiFFUVlpnXSnDGrSkOAO5MtzyN8oQNjLd5AqTW5OZRgyjoNuAuR3jQ==} + dependencies: + '@fastify/deepmerge': 1.3.0 + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + fast-deep-equal: 3.1.3 + fast-uri: 2.2.0 + rfdc: 1.3.0 + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + /fast-printf@1.6.9: + resolution: {integrity: sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==} + engines: {node: '>=10.0'} dependencies: - path-type: 4.0.0 - dev: true + boolean: 3.2.0 - /disposable-email@0.2.3: - resolution: {integrity: sha512-gkBQQ5Res431ZXqLlAafrXHizG7/1FWmi8U2RTtriD78Vc10HhBUvdJun3R4eSF0KRIQQJs+wHlxjkED/Hr1EQ==} + /fast-redact@3.3.0: + resolution: {integrity: sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==} + engines: {node: '>=6'} - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 + /fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} dev: true - /dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - - /domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + /fast-uri@2.2.0: + resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} - /domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} + /fast-url-parser@1.1.3: + resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} dependencies: - domelementtype: 2.3.0 + punycode: 1.4.1 + dev: false - /domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + /fast-xml-parser@4.0.11: + resolution: {integrity: sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==} + hasBin: true dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - - /dotenv@16.0.3: - resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} - engines: {node: '>=12'} - - /dotenv@8.6.0: - resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} - engines: {node: '>=10'} - dev: true + strnum: 1.0.5 + dev: false - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + /fast-xml-parser@4.3.6: + resolution: {integrity: sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==} + hasBin: true + dependencies: + strnum: 1.0.5 dev: false - /ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + /fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: - safe-buffer: 5.2.1 - dev: true + reusify: 1.0.4 - /ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + /fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + dependencies: + bser: 2.1.1 - /electron-to-chromium@1.4.499: - resolution: {integrity: sha512-0NmjlYBLKVHva4GABWAaHuPJolnDuL0AhV3h1hES6rcLCWEIbRL6/8TghfsVwkx6TEroQVdliX7+aLysUpKvjw==} + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 dev: true - /elliptic@6.5.4: - resolution: {integrity: sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==} + /file-type@16.5.4: + resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} + engines: {node: '>=10'} dependencies: - bn.js: 4.12.0 - brorand: 1.1.0 - hash.js: 1.1.7 - hmac-drbg: 1.0.1 - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 - - /emittery@0.10.2: - resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} - engines: {node: '>=12'} - dev: true + readable-web-to-node-stream: 3.0.2 + strtok3: 6.3.0 + token-types: 4.2.1 - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: false + /fill-range@7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 - /encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + /finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} - - /encoding@0.1.13: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - requiresBuild: true dependencies: - iconv-lite: 0.6.3 - dev: true - optional: true + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false - /end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} dependencies: - once: 1.4.0 + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color - /enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} + /find-cache-dir@2.1.0: + resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} + engines: {node: '>=6'} dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - dev: true - - /entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + dev: false - /env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + /find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} - dev: true + dependencies: + locate-path: 3.0.0 + dev: false - /err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - dev: true + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} dependencies: - is-arrayish: 0.2.1 - dev: true + locate-path: 6.0.0 + path-exists: 4.0.0 - /es-abstract@1.22.1: - resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==} - engines: {node: '>= 0.4'} + /find-yarn-workspace-root2@1.2.16: + resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} dependencies: - array-buffer-byte-length: 1.0.0 - arraybuffer.prototype.slice: 1.0.1 - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - es-set-tostringtag: 2.0.1 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.1 - get-symbol-description: 1.0.0 - globalthis: 1.0.3 - gopd: 1.0.1 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-proto: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - is-array-buffer: 3.0.2 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-typed-array: 1.1.12 - is-weakref: 1.0.2 - object-inspect: 1.12.3 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 - safe-array-concat: 1.0.1 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.7 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.7 - typed-array-buffer: 1.0.0 - typed-array-byte-length: 1.0.0 - typed-array-byte-offset: 1.0.0 - typed-array-length: 1.0.4 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.11 + micromatch: 4.0.5 + pkg-dir: 4.2.0 dev: true - /es-set-tostringtag@2.0.1: - resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} - engines: {node: '>= 0.4'} + /findit2@2.2.3: + resolution: {integrity: sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==} + engines: {node: '>=0.8.22'} + dev: false + + /flat-cache@3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: - get-intrinsic: 1.2.1 - has: 1.0.3 - has-tostringtag: 1.0.0 + flatted: 3.2.7 + rimraf: 3.0.2 dev: true - /es-shim-unscopables@1.0.0: - resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} - dependencies: - has: 1.0.3 + /flatted@3.2.7: + resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} + /flow-enums-runtime@0.0.6: + resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + dev: false + + /flow-parser@0.236.0: + resolution: {integrity: sha512-0OEk9Gr+Yj7wjDW2KgaNYUypKau71jAfFyeLQF5iVtxqc6uJHag/MT7pmaEApf4qM7u86DkBcd4ualddYMfbLw==} + engines: {node: '>=0.4.0'} + dev: false + + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + /follow-redirects@1.15.3: + resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + /follow-redirects@1.15.5: + resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + /for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 dev: true - /esbuild-android-64@0.14.48: - resolution: {integrity: sha512-3aMjboap/kqwCUpGWIjsk20TtxVoKck8/4Tu19rubh7t5Ra0Yrpg30Mt1QXXlipOazrEceGeWurXKeFJgkPOUg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 - /esbuild-android-arm64@0.14.48: - resolution: {integrity: sha512-vptI3K0wGALiDq+EvRuZotZrJqkYkN5282iAfcffjI5lmGG9G1ta/CIVauhY42MBXwEgDJkweiDcDMRLzBZC4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 - /esbuild-darwin-64@0.14.48: - resolution: {integrity: sha512-gGQZa4+hab2Va/Zww94YbshLuWteyKGD3+EsVon8EWTWhnHFRm5N9NbALNbwi/7hQ/hM1Zm4FuHg+k6BLsl5UA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} - /esbuild-darwin-arm64@0.14.48: - resolution: {integrity: sha512-bFjnNEXjhZT+IZ8RvRGNJthLWNHV5JkCtuOFOnjvo5pC0sk2/QVk0Qc06g2PV3J0TcU6kaPC3RN9yy9w2PSLEA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true + /fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: true - optional: true - /esbuild-freebsd-64@0.14.48: - resolution: {integrity: sha512-1NOlwRxmOsnPcWOGTB10JKAkYSb2nue0oM1AfHWunW/mv3wERfJmnYlGzL3UAOIUXZqW8GeA2mv+QGwq7DToqA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} - /esbuild-freebsd-arm64@0.14.48: - resolution: {integrity: sha512-gXqKdO8wabVcYtluAbikDH2jhXp+Klq5oCD5qbVyUG6tFiGhrC9oczKq3vIrrtwcxDQqK6+HDYK8Zrd4bCA9Gw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - /esbuild-linux-32@0.14.48: - resolution: {integrity: sha512-ghGyDfS289z/LReZQUuuKq9KlTiTspxL8SITBFQFAFRA/IkIvDpnZnCAKTCjGXAmUqroMQfKJXMxyjJA69c/nQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true + /fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 dev: true - optional: true - /esbuild-linux-64@0.14.48: - resolution: {integrity: sha512-vni3p/gppLMVZLghI7oMqbOZdGmLbbKR23XFARKnszCIBpEMEDxOMNIKPmMItQrmH/iJrL1z8Jt2nynY0bE1ug==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 - /esbuild-linux-arm64@0.14.48: - resolution: {integrity: sha512-3CFsOlpoxlKPRevEHq8aAntgYGYkE1N9yRYAcPyng/p4Wyx0tPR5SBYsxLKcgPB9mR8chHEhtWYz6EZ+H199Zw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true + /fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 dev: true - optional: true - /esbuild-linux-arm@0.14.48: - resolution: {integrity: sha512-+VfSV7Akh1XUiDNXgqgY1cUP1i2vjI+BmlyXRfVz5AfV3jbpde8JTs5Q9sYgaoq5cWfuKfoZB/QkGOI+QcL1Tw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /esbuild-linux-mips64le@0.14.48: - resolution: {integrity: sha512-cs0uOiRlPp6ymknDnjajCgvDMSsLw5mST2UXh+ZIrXTj2Ifyf2aAP3Iw4DiqgnyYLV2O/v/yWBJx+WfmKEpNLA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] requiresBuild: true - dev: true optional: true - /esbuild-linux-ppc64le@0.14.48: - resolution: {integrity: sha512-+2F0vJMkuI0Wie/wcSPDCqXvSFEELH7Jubxb7mpWrA/4NpT+/byjxDz0gG6R1WJoeDefcrMfpBx4GFNN1JQorQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - /esbuild-linux-riscv64@0.14.48: - resolution: {integrity: sha512-BmaK/GfEE+5F2/QDrIXteFGKnVHGxlnK9MjdVKMTfvtmudjY3k2t8NtlY4qemKSizc+QwyombGWTBDc76rxePA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true + /function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + functions-have-names: 1.2.3 dev: true - optional: true - /esbuild-linux-s390x@0.14.48: - resolution: {integrity: sha512-tndw/0B9jiCL+KWKo0TSMaUm5UWBLsfCKVdbfMlb3d5LeV9WbijZ8Ordia8SAYv38VSJWOEt6eDCdOx8LqkC4g==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true + /functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} dev: true - optional: true - /esbuild-netbsd-64@0.14.48: - resolution: {integrity: sha512-V9hgXfwf/T901Lr1wkOfoevtyNkrxmMcRHyticybBUHookznipMOHoF41Al68QBsqBxnITCEpjjd4yAos7z9Tw==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true + /gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 dev: true - optional: true - /esbuild-openbsd-64@0.14.48: - resolution: {integrity: sha512-+IHf4JcbnnBl4T52egorXMatil/za0awqzg2Vy6FBgPcBpisDWT2sVz/tNdrK9kAqj+GZG/jZdrOkj7wsrNTKA==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true + /generic-names@4.0.0: + resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} + dependencies: + loader-utils: 3.2.1 dev: true - optional: true - /esbuild-plugin-handlebars@1.0.3: - resolution: {integrity: sha512-vOqurrqU7s4f9xgwDiUG0I+9auJPf21GFf6xYxObRrQdi088N81P4ztCs0xVg0uSmWkIPcZuCLIwP+ttzKpezQ==} + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + /get-intrinsic@1.2.1: + resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: - handlebars: 4.7.8 - dev: true + function-bind: 1.1.1 + has: 1.0.3 + has-proto: 1.0.1 + has-symbols: 1.0.3 - /esbuild-sunos-64@0.14.48: - resolution: {integrity: sha512-77m8bsr5wOpOWbGi9KSqDphcq6dFeJyun8TA+12JW/GAjyfTwVtOnN8DOt6DSPUfEV+ltVMNqtXUeTeMAxl5KA==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} dev: true - optional: true - /esbuild-windows-32@0.14.48: - resolution: {integrity: sha512-EPgRuTPP8vK9maxpTGDe5lSoIBHGKO/AuxDncg5O3NkrPeLNdvvK8oywB0zGaAZXxYWfNNSHskvvDgmfVTguhg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true + /get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + dev: false - /esbuild-windows-64@0.14.48: - resolution: {integrity: sha512-YmpXjdT1q0b8ictSdGwH3M8VCoqPpK1/UArze3X199w6u8hUx3V8BhAi1WjbsfDYRBanVVtduAhh2sirImtAvA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true + /get-port@6.1.2: + resolution: {integrity: sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true - optional: true - /esbuild-windows-arm64@0.14.48: - resolution: {integrity: sha512-HHaOMCsCXp0rz5BT2crTka6MPWVno121NKApsGs/OIW5QC0ggC69YMGs1aJct9/9FSUF4A1xNE/cLvgB5svR4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} - /esbuild@0.14.48: - resolution: {integrity: sha512-w6N1Yn5MtqK2U1/WZTX9ZqUVb8IOLZkZ5AdHkT6x3cHDMVsYWC7WPdiLmx19w3i4Rwzy5LqsEMtVihG3e4rFzA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - esbuild-android-64: 0.14.48 - esbuild-android-arm64: 0.14.48 - esbuild-darwin-64: 0.14.48 - esbuild-darwin-arm64: 0.14.48 - esbuild-freebsd-64: 0.14.48 - esbuild-freebsd-arm64: 0.14.48 - esbuild-linux-32: 0.14.48 - esbuild-linux-64: 0.14.48 - esbuild-linux-arm: 0.14.48 - esbuild-linux-arm64: 0.14.48 - esbuild-linux-mips64le: 0.14.48 - esbuild-linux-ppc64le: 0.14.48 - esbuild-linux-riscv64: 0.14.48 - esbuild-linux-s390x: 0.14.48 - esbuild-netbsd-64: 0.14.48 - esbuild-openbsd-64: 0.14.48 - esbuild-sunos-64: 0.14.48 - esbuild-windows-32: 0.14.48 - esbuild-windows-64: 0.14.48 - esbuild-windows-arm64: 0.14.48 + /get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.2.1 dev: true - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 dev: true - /escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 5.0.0 + path-scurry: 1.10.1 - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 dev: true - /escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + /globals@13.21.0: + resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 dev: true - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true + /globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + dependencies: + define-properties: 1.2.0 - /eslint-config-prettier@9.1.0(eslint@8.57.0): - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} dependencies: - eslint: 8.57.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 3.0.0 dev: true - /eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5): - resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '*' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: - eslint: 8.57.0 - eslint-config-prettier: 9.1.0(eslint@8.57.0) - prettier: 3.2.5 - prettier-linter-helpers: 1.0.0 - synckit: 0.8.8 + dir-glob: 3.0.1 + fast-glob: 3.3.1 + ignore: 5.2.4 + merge2: 1.4.1 + slash: 4.0.0 dev: true - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 + get-intrinsic: 1.2.1 dev: true - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + /grapheme-splitter@1.0.4: + resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + /handlebars@4.7.7: + resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} + engines: {node: '>=0.4.7'} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.10.0 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.21.0 - graphemer: 1.4.0 - ignore: 5.2.4 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.3 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + dev: false - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + /handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true dependencies: - acorn: 8.10.0 - acorn-jsx: 5.3.2(acorn@8.10.0) - eslint-visitor-keys: 3.4.3 + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 dev: true - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true + /hard-rejection@2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} dev: true - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 + /has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + /has-property-descriptors@1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: - estraverse: 5.3.0 + get-intrinsic: 1.2.1 + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + /has-tostringtag@1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 dev: true - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} + /has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} dev: true - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hash.js@1.1.7: + resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + dependencies: + inherits: 2.0.4 + minimalistic-assert: 1.0.1 + + /he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + /help-me@4.2.0: + resolution: {integrity: sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==} + dependencies: + glob: 8.1.0 + readable-stream: 3.6.2 dev: true - /etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} + /hermes-estree@0.19.1: + resolution: {integrity: sha512-daLGV3Q2MKk8w4evNMKwS8zBE/rcpA800nu1Q5kM08IKijoSnPe9Uo1iIxzPKRkn95IxxsgBMPeYHt3VG4ej2g==} + dev: false - /event-lite@0.1.3: - resolution: {integrity: sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw==} + /hermes-estree@0.20.1: + resolution: {integrity: sha512-SQpZK4BzR48kuOg0v4pb3EAGNclzIlqMj3Opu/mu7bbAoFw6oig6cEt/RAi0zTFW/iW6Iz9X9ggGuZTAZ/yZHg==} dev: false - /event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} + /hermes-parser@0.19.1: + resolution: {integrity: sha512-Vp+bXzxYJWrpEuJ/vXxUsLnt0+y4q9zyi4zUlkLqD8FKv4LjIfOvP69R/9Lty3dCyKh0E2BU7Eypqr63/rKT/A==} + dependencies: + hermes-estree: 0.19.1 + dev: false - /eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + /hermes-parser@0.20.1: + resolution: {integrity: sha512-BL5P83cwCogI8D7rrDCgsFY0tdYUtmFP9XaXtl2IQjC+2Xo+4okjfXintlTxcIwl4qeGddEl28Z11kbVIw0aNA==} + dependencies: + hermes-estree: 0.20.1 + dev: false - /events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} + /hermes-profile-transformer@0.0.6: + resolution: {integrity: sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==} + engines: {node: '>=8'} + dependencies: + source-map: 0.7.4 + dev: false - /execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} + /hmac-drbg@1.0.1: + resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - dev: true + hash.js: 1.1.7 + minimalistic-assert: 1.0.1 + minimalistic-crypto-utils: 1.0.1 - /exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} + /hosted-git-info@2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} dev: true - /expand-template@2.0.3: - resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} - engines: {node: '>=6'} + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true - /expect@28.1.3: - resolution: {integrity: sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /html-to-text@7.1.1: + resolution: {integrity: sha512-c9QWysrfnRZevVpS8MlE7PyOdSuIOjg8Bt8ZE10jMU/BEngA6j3llj4GRfAmtQzcd1FjKE0sWu5IHXRUH9YxIQ==} + engines: {node: '>=10.23.2'} + hasBin: true dependencies: - '@jest/expect-utils': 28.1.3 - jest-get-type: 28.0.2 - jest-matcher-utils: 28.1.3 - jest-message-util: 28.1.3 - jest-util: 28.1.3 - dev: true + deepmerge: 4.3.1 + he: 1.2.0 + htmlparser2: 6.1.0 + minimist: 1.2.8 - /express-async-errors@3.1.1(express@4.18.2): - resolution: {integrity: sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==} - peerDependencies: - express: ^4.16.2 + /htmlparser2@6.1.0: + resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} dependencies: - express: 4.18.2 + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 2.2.0 - /express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} - engines: {node: '>= 0.10.0'} + /http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + dev: true + + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.1 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.5.0 - cookie-signature: 1.0.6 - debug: 2.6.9 depd: 2.0.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.2.0 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.1 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.7 - proxy-addr: 2.0.7 - qs: 6.11.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + inherits: 2.0.4 setprototypeof: 1.2.0 statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 + toidentifier: 1.0.1 + + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.4 transitivePeerDependencies: - supports-color + dev: true - /extendable-error@0.1.7: - resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + /http-terminator@3.2.0: + resolution: {integrity: sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g==} + engines: {node: '>=14'} + dependencies: + delay: 5.0.0 + p-wait-for: 3.2.0 + roarr: 7.15.1 + type-fest: 2.19.0 + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /human-id@1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + dev: true + + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 dev: true - /external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - dev: true + safer-buffer: 2.1.2 - /fast-copy@2.1.7: - resolution: {integrity: sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==} + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + requiresBuild: true + dependencies: + safer-buffer: 2.1.2 dev: true + optional: true - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - /fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + /icss-replace-symbols@1.1.0: + resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} dev: true - /fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - - /fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} + /icss-utils@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + postcss: 8.4.38 dev: true - /fast-json-stringify@5.8.0: - resolution: {integrity: sha512-VVwK8CFMSALIvt14U8AvrSzQAwN/0vaVRiFFUVlpnXSnDGrSkOAO5MtzyN8oQNjLd5AqTW5OZRgyjoNuAuR3jQ==} + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + /ignore@5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + + /image-size@1.1.1: + resolution: {integrity: sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==} + engines: {node: '>=16.x'} + hasBin: true dependencies: - '@fastify/deepmerge': 1.3.0 - ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) - fast-deep-equal: 3.1.3 - fast-uri: 2.2.0 - rfdc: 1.3.0 + queue: 6.0.2 + dev: false - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + /import-cwd@3.0.0: + resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} + engines: {node: '>=8'} + dependencies: + import-from: 3.0.0 dev: true - /fast-printf@1.6.9: - resolution: {integrity: sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==} - engines: {node: '>=10.0'} + /import-fresh@2.0.0: + resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} + engines: {node: '>=4'} dependencies: - boolean: 3.2.0 + caller-path: 2.0.0 + resolve-from: 3.0.0 + dev: false - /fast-redact@3.3.0: - resolution: {integrity: sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==} + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - - /fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 dev: true - /fast-uri@2.2.0: - resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==} + /import-from@3.0.0: + resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + dev: true - /fast-url-parser@1.1.3: - resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} + /import-in-the-middle@1.4.2: + resolution: {integrity: sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==} dependencies: - punycode: 1.4.1 + acorn: 8.10.0 + acorn-import-assertions: 1.9.0(acorn@8.10.0) + cjs-module-lexer: 1.2.3 + module-details-from-path: 1.0.3 dev: false - /fast-xml-parser@4.0.11: - resolution: {integrity: sha512-4aUg3aNRR/WjQAcpceODG1C3x3lFANXRo8+1biqfieHmg9pyMt7qB4lQV/Ta6sJCTbA5vfD8fnA8S54JATiFUA==} + /import-local@3.1.0: + resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + engines: {node: '>=8'} hasBin: true dependencies: - strnum: 1.0.5 - dev: false + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + dev: true - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} - /fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - dependencies: - bser: 2.1.1 + /indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} dev: true - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.0.4 + /infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} dev: true - /file-type@16.5.4: - resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} - engines: {node: '>=10'} + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: - readable-web-to-node-stream: 3.0.2 - strtok3: 6.3.0 - token-types: 4.2.1 + once: 1.4.0 + wrappy: 1.0.2 - /file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - /finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} - engines: {node: '>= 0.8'} - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color + /int64-buffer@0.1.10: + resolution: {integrity: sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==} + dev: false - /find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + /internal-slot@1.0.5: + resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} + engines: {node: '>= 0.4'} dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 + get-intrinsic: 1.2.1 + has: 1.0.3 + side-channel: 1.0.4 dev: true - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + /invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true + loose-envify: 1.4.0 + dev: false - /find-yarn-workspace-root2@1.2.16: - resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} + /ioredis@5.3.2: + resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} + engines: {node: '>=12.22.0'} dependencies: - micromatch: 4.0.5 - pkg-dir: 4.2.0 + '@ioredis/commands': 1.2.0 + cluster-key-slot: 1.1.2 + debug: 4.3.4 + denque: 2.1.0 + lodash.defaults: 4.2.0 + lodash.isarguments: 3.1.0 + redis-errors: 1.2.0 + redis-parser: 3.0.0 + standard-as-callback: 2.1.0 + transitivePeerDependencies: + - supports-color + + /ip@2.0.0: + resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} dev: true - /findit2@2.2.3: - resolution: {integrity: sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==} - engines: {node: '>=0.8.22'} + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + /ipaddr.js@2.1.0: + resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==} + engines: {node: '>= 10'} dev: false - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} + /is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: - flatted: 3.2.7 - rimraf: 3.0.2 + call-bind: 1.0.2 + get-intrinsic: 1.2.1 + is-typed-array: 1.1.12 dev: true - /flatted@3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + /is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.2 dev: true - /follow-redirects@1.15.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.3.0 + dev: true - /follow-redirects@1.15.3: - resolution: {integrity: sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true + /is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true - /follow-redirects@1.15.5: - resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true + /is-builtin-module@3.2.1: + resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} + engines: {node: '>=6'} + dependencies: + builtin-modules: 3.3.0 + dev: true - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + /is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + dev: true + + /is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true dependencies: - is-callable: 1.2.7 + ci-info: 3.8.0 dev: true - /foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - dev: false + has: 1.0.3 - /form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} + /is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 + has-tostringtag: 1.0.0 + dev: true - /forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} + /is-directory@0.3.1: + resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} + engines: {node: '>=0.10.0'} + dev: false - /fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} + /is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + dev: false - /fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} - /fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 + /is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + dev: false + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + /is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} dev: true - /fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 + is-extglob: 2.1.1 + + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: false + + /is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} dev: true - /fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} + /is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + dev: true + + /is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} dependencies: - minipass: 3.3.6 + has-tostringtag: 1.0.0 dev: true - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + /is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} dev: true - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true + /is-plain-obj@1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} dev: true - optional: true - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + /is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + dependencies: + isobject: 3.0.1 + dev: false - /function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + /is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + dependencies: + '@types/estree': 1.0.5 + dev: true + + /is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.22.1 - functions-have-names: 1.2.3 + has-tostringtag: 1.0.0 dev: true - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + /is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 dev: true - /gauge@4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + /is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 + has-tostringtag: 1.0.0 dev: true - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} + /is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + dependencies: + better-path-resolve: 1.0.0 dev: true - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - /get-intrinsic@1.2.1: - resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} + /is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-proto: 1.0.1 has-symbols: 1.0.3 - - /get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} dev: true - /get-port@5.1.1: - resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} - engines: {node: '>=8'} - dev: false - - /get-port@6.1.2: - resolution: {integrity: sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + /is-typed-array@1.1.12: + resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} + engines: {node: '>= 0.4'} + dependencies: + which-typed-array: 1.1.11 dev: true - /get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - dev: true + dev: false - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} + /is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} dependencies: call-bind: 1.0.2 - get-intrinsic: 1.2.1 dev: true - /github-from-package@0.0.0: - resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + /is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 + /is-wsl@1.1.0: + resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} + engines: {node: '>=4'} + dev: false - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} + /is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} dependencies: - is-glob: 4.0.3 + is-docker: 2.2.1 + dev: false + + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + dev: false + + /isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} dev: true - /glob@10.3.10: - resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.3 - minipass: 5.0.0 - path-scurry: 1.10.1 + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /iso-datestring-validator@2.2.2: + resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} + dev: false + + /isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} dev: false - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + /istanbul-lib-coverage@3.2.0: + resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} + engines: {node: '>=8'} + + /istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 + '@babel/core': 7.18.6 + '@babel/parser': 7.22.10 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color dev: true - /glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.6 - once: 1.4.0 + istanbul-lib-coverage: 3.2.0 + make-dir: 4.0.0 + supports-color: 7.2.0 dev: true - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + dependencies: + debug: 4.3.4 + istanbul-lib-coverage: 3.2.0 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color dev: true - /globals@13.21.0: - resolution: {integrity: sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==} + /istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} engines: {node: '>=8'} dependencies: - type-fest: 0.20.2 + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 dev: true - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} dependencies: - define-properties: 1.2.0 + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} + /jest-changed-files@28.1.3: + resolution: {integrity: sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.1 - ignore: 5.2.4 - merge2: 1.4.1 - slash: 3.0.0 + execa: 5.1.1 + p-limit: 3.1.0 dev: true - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + /jest-circus@28.1.3: + resolution: {integrity: sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - get-intrinsic: 1.2.1 + '@jest/environment': 28.1.3 + '@jest/expect': 28.1.3 + '@jest/test-result': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + chalk: 4.1.2 + co: 4.6.0 + dedent: 0.7.0 + is-generator-fn: 2.1.0 + jest-each: 28.1.3 + jest-matcher-utils: 28.1.3 + jest-message-util: 28.1.3 + jest-runtime: 28.1.3 + jest-snapshot: 28.1.3 + jest-util: 28.1.3 + p-limit: 3.1.0 + pretty-format: 28.1.3 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - supports-color dev: true - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + /jest-cli@28.1.3(@types/node@18.19.24)(ts-node@10.8.2): + resolution: {integrity: sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + dependencies: + '@jest/core': 28.1.3(ts-node@10.8.2) + '@jest/test-result': 28.1.3 + '@jest/types': 28.1.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + import-local: 3.1.0 + jest-config: 28.1.3(@types/node@18.19.24)(ts-node@10.8.2) + jest-util: 28.1.3 + jest-validate: 28.1.3 + prompts: 2.4.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node dev: true - /grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} + /jest-config@28.1.3(@types/node@18.19.24)(ts-node@10.8.2): + resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + dependencies: + '@babel/core': 7.18.6 + '@jest/test-sequencer': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + babel-jest: 28.1.3(@babel/core@7.18.6) + chalk: 4.1.2 + ci-info: 3.8.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 28.1.3 + jest-environment-node: 28.1.3 + jest-get-type: 28.0.2 + jest-regex-util: 28.0.2 + jest-resolve: 28.1.3 + jest-runner: 28.1.3 + jest-util: 28.1.3 + jest-validate: 28.1.3 + micromatch: 4.0.5 + parse-json: 5.2.0 + pretty-format: 28.1.3 + slash: 3.0.0 + strip-json-comments: 3.1.1 + ts-node: 10.8.2(@swc/core@1.3.42)(@types/node@18.19.24)(typescript@5.4.4) + transitivePeerDependencies: + - supports-color dev: true - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - /handlebars@4.7.7: - resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==} - engines: {node: '>=0.4.7'} - hasBin: true + /jest-diff@28.1.3: + resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.17.4 - dev: false + chalk: 4.1.2 + diff-sequences: 28.1.1 + jest-get-type: 28.0.2 + pretty-format: 28.1.3 + dev: true - /handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true + /jest-docblock@28.1.1: + resolution: {integrity: sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.17.4 + detect-newline: 3.1.0 dev: true - /hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - dev: true + /jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + detect-newline: 3.1.0 + dev: false - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + /jest-each@28.1.3: + resolution: {integrity: sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + chalk: 4.1.2 + jest-get-type: 28.0.2 + jest-util: 28.1.3 + pretty-format: 28.1.3 dev: true - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + /jest-environment-node@28.1.3: + resolution: {integrity: sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/environment': 28.1.3 + '@jest/fake-timers': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + jest-mock: 28.1.3 + jest-util: 28.1.3 dev: true - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - /has-property-descriptors@1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + /jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - get-intrinsic: 1.2.1 + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 18.19.24 + jest-mock: 29.7.0 + jest-util: 29.7.0 + dev: false - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} + /jest-get-type@28.0.2: + resolution: {integrity: sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dev: true - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} + /jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: false - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} + /jest-haste-map@28.1.3: + resolution: {integrity: sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - has-symbols: 1.0.3 + '@jest/types': 28.1.3 + '@types/graceful-fs': 4.1.6 + '@types/node': 18.19.24 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 28.0.2 + jest-util: 28.1.3 + jest-worker: 28.1.3 + micromatch: 4.0.5 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 dev: true - /has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + /jest-leak-detector@28.1.3: + resolution: {integrity: sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + jest-get-type: 28.0.2 + pretty-format: 28.1.3 dev: true - /has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} + /jest-matcher-utils@28.1.3: + resolution: {integrity: sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - function-bind: 1.1.1 + chalk: 4.1.2 + jest-diff: 28.1.3 + jest-get-type: 28.0.2 + pretty-format: 28.1.3 + dev: true - /hash.js@1.1.7: - resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} + /jest-message-util@28.1.3: + resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 + '@babel/code-frame': 7.22.10 + '@jest/types': 28.1.3 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + pretty-format: 28.1.3 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: true - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true + /jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@babel/code-frame': 7.22.10 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + dev: false - /help-me@4.2.0: - resolution: {integrity: sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==} + /jest-mock@28.1.3: + resolution: {integrity: sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - glob: 8.1.0 - readable-stream: 3.6.2 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 dev: true - /hmac-drbg@1.0.1: - resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} + /jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - hash.js: 1.1.7 - minimalistic-assert: 1.0.1 - minimalistic-crypto-utils: 1.0.1 + '@jest/types': 29.6.3 + '@types/node': 18.19.24 + jest-util: 29.7.0 + dev: false - /hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + /jest-pnp-resolver@1.2.3(jest-resolve@28.1.3): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: + jest-resolve: 28.1.3 dev: true - /html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + /jest-regex-util@28.0.2: + resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dev: true - /html-to-text@7.1.1: - resolution: {integrity: sha512-c9QWysrfnRZevVpS8MlE7PyOdSuIOjg8Bt8ZE10jMU/BEngA6j3llj4GRfAmtQzcd1FjKE0sWu5IHXRUH9YxIQ==} - engines: {node: '>=10.23.2'} - hasBin: true - dependencies: - deepmerge: 4.3.1 - he: 1.2.0 - htmlparser2: 6.1.0 - minimist: 1.2.8 - - /htmlparser2@6.1.0: - resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + /jest-resolve-dependencies@28.1.3: + resolution: {integrity: sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - domutils: 2.8.0 - entities: 2.2.0 - - /http-cache-semantics@4.1.1: - resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + jest-regex-util: 28.0.2 + jest-snapshot: 28.1.3 + transitivePeerDependencies: + - supports-color dev: true - /http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} + /jest-resolve@28.1.3: + resolution: {integrity: sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 28.1.3 + jest-pnp-resolver: 1.2.3(jest-resolve@28.1.3) + jest-util: 28.1.3 + jest-validate: 28.1.3 + resolve: 1.22.4 + resolve.exports: 1.1.1 + slash: 3.0.0 + dev: true - /http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} + /jest-runner@28.1.3: + resolution: {integrity: sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.3.4 + '@jest/console': 28.1.3 + '@jest/environment': 28.1.3 + '@jest/test-result': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + chalk: 4.1.2 + emittery: 0.10.2 + graceful-fs: 4.2.11 + jest-docblock: 28.1.1 + jest-environment-node: 28.1.3 + jest-haste-map: 28.1.3 + jest-leak-detector: 28.1.3 + jest-message-util: 28.1.3 + jest-resolve: 28.1.3 + jest-runtime: 28.1.3 + jest-util: 28.1.3 + jest-watcher: 28.1.3 + jest-worker: 28.1.3 + p-limit: 3.1.0 + source-map-support: 0.5.13 transitivePeerDependencies: - supports-color dev: true - /http-terminator@3.2.0: - resolution: {integrity: sha512-JLjck1EzPaWjsmIf8bziM3p9fgR1Y3JoUKAkyYEbZmFrIvJM6I8vVJfBGWlEtV9IWOvzNnaTtjuwZeBY2kwB4g==} - engines: {node: '>=14'} + /jest-runtime@28.1.3: + resolution: {integrity: sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - delay: 5.0.0 - p-wait-for: 3.2.0 - roarr: 7.15.1 - type-fest: 2.19.0 + '@jest/environment': 28.1.3 + '@jest/fake-timers': 28.1.3 + '@jest/globals': 28.1.3 + '@jest/source-map': 28.1.2 + '@jest/test-result': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + chalk: 4.1.2 + cjs-module-lexer: 1.2.3 + collect-v8-coverage: 1.0.2 + execa: 5.1.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 28.1.3 + jest-message-util: 28.1.3 + jest-mock: 28.1.3 + jest-regex-util: 28.0.2 + jest-resolve: 28.1.3 + jest-snapshot: 28.1.3 + jest-util: 28.1.3 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: true - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} + /jest-snapshot@28.1.3: + resolution: {integrity: sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - agent-base: 6.0.2 - debug: 4.3.4 + '@babel/core': 7.18.6 + '@babel/generator': 7.22.10 + '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.18.6) + '@babel/traverse': 7.22.10 + '@babel/types': 7.22.10 + '@jest/expect-utils': 28.1.3 + '@jest/transform': 28.1.3 + '@jest/types': 28.1.3 + '@types/babel__traverse': 7.20.1 + '@types/prettier': 2.7.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.18.6) + chalk: 4.1.2 + expect: 28.1.3 + graceful-fs: 4.2.11 + jest-diff: 28.1.3 + jest-get-type: 28.0.2 + jest-haste-map: 28.1.3 + jest-matcher-utils: 28.1.3 + jest-message-util: 28.1.3 + jest-util: 28.1.3 + natural-compare: 1.4.0 + pretty-format: 28.1.3 + semver: 7.5.4 transitivePeerDependencies: - supports-color dev: true - /human-id@1.0.2: - resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + /jest-util@28.1.3: + resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + dependencies: + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + chalk: 4.1.2 + ci-info: 3.8.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 dev: true - /human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - dev: true + /jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/types': 29.6.3 + '@types/node': 18.19.24 + chalk: 4.1.2 + ci-info: 3.8.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + dev: false - /humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + /jest-validate@28.1.3: + resolution: {integrity: sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - ms: 2.1.3 + '@jest/types': 28.1.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 28.0.2 + leven: 3.1.0 + pretty-format: 28.1.3 dev: true - /iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} + /jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - safer-buffer: 2.1.2 + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + dev: false - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - requiresBuild: true + /jest-watcher@28.1.3: + resolution: {integrity: sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - safer-buffer: 2.1.2 + '@jest/test-result': 28.1.3 + '@jest/types': 28.1.3 + '@types/node': 18.19.24 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.10.2 + jest-util: 28.1.3 + string-length: 4.0.2 dev: true - optional: true - - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - /ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} + /jest-worker@28.1.3: + resolution: {integrity: sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 + '@types/node': 18.19.24 + merge-stream: 2.0.0 + supports-color: 8.1.1 dev: true - /import-in-the-middle@1.4.2: - resolution: {integrity: sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==} + /jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - acorn: 8.10.0 - acorn-import-assertions: 1.9.0(acorn@8.10.0) - cjs-module-lexer: 1.2.3 - module-details-from-path: 1.0.3 + '@types/node': 18.19.24 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 dev: false - /import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} - engines: {node: '>=8'} + /jest@28.1.2(@types/node@18.19.24)(ts-node@10.8.2): + resolution: {integrity: sha512-Tuf05DwLeCh2cfWCQbcz9UxldoDyiR1E9Igaei5khjonKncYdc6LDfynKCEWozK0oLE3GD+xKAo2u8x/0s6GOg==} + engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 + '@jest/core': 28.1.3(ts-node@10.8.2) + '@jest/types': 28.1.3 + import-local: 3.1.0 + jest-cli: 28.1.3(@types/node@18.19.24)(ts-node@10.8.2) + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node dev: true - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} + /jiti@1.21.0: + resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} + hasBin: true dev: true - /indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - dev: true + /joi@17.13.1: + resolution: {integrity: sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==} + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + dev: false - /infer-owner@1.0.4: - resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + /jose@4.15.4: + resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==} dev: true - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true + /jose@5.1.3: + resolution: {integrity: sha512-GPExOkcMsCLBTi1YetY2LmkoY559fss0+0KVa6kOfb2YFe84nAM7Nm/XzuZozah4iHgmBGrCOHL5/cy670SBRw==} + dev: false - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /jose@5.3.0: + resolution: {integrity: sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==} + dev: false - /ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + /joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + dev: true - /int64-buffer@0.1.10: - resolution: {integrity: sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA==} - dev: false + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - /internal-slot@1.0.5: - resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} - engines: {node: '>= 0.4'} + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true dependencies: - get-intrinsic: 1.2.1 - has: 1.0.3 - side-channel: 1.0.4 + argparse: 1.0.10 + esprima: 4.0.1 + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 dev: true - /ioredis@5.3.2: - resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} - engines: {node: '>=12.22.0'} + /jsc-android@250231.0.0: + resolution: {integrity: sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==} + dev: false + + /jsc-safe-url@0.2.4: + resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} + dev: false + + /jscodeshift@0.14.0(@babel/preset-env@7.24.5): + resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==} + hasBin: true + peerDependencies: + '@babel/preset-env': ^7.1.6 dependencies: - '@ioredis/commands': 1.2.0 - cluster-key-slot: 1.1.2 - debug: 4.3.4 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 + '@babel/core': 7.18.6 + '@babel/parser': 7.22.10 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.18.6) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.18.6) + '@babel/plugin-transform-modules-commonjs': 7.24.1(@babel/core@7.18.6) + '@babel/preset-env': 7.24.5(@babel/core@7.18.6) + '@babel/preset-flow': 7.24.1(@babel/core@7.18.6) + '@babel/preset-typescript': 7.24.1(@babel/core@7.18.6) + '@babel/register': 7.23.7(@babel/core@7.18.6) + babel-core: 7.0.0-bridge.0(@babel/core@7.18.6) + chalk: 4.1.2 + flow-parser: 0.236.0 + graceful-fs: 4.2.11 + micromatch: 4.0.5 + neo-async: 2.6.2 + node-dir: 0.1.17 + recast: 0.21.5 + temp: 0.8.4 + write-file-atomic: 2.4.3 transitivePeerDependencies: - supports-color + dev: false - /ip@2.0.0: - resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} - dev: true + /jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + dev: false - /ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true - /ipaddr.js@2.1.0: - resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==} - engines: {node: '>= 10'} + /json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} dev: false - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-typed-array: 1.1.12 + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true - /is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + /json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true + + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.11 + + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} dependencies: - has-bigints: 1.0.2 + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.0 dev: true - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: true - - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 dev: true - /is-ci@3.0.1: - resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} - hasBin: true + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} dependencies: - ci-info: 3.8.0 + jwa: 1.4.1 + safe-buffer: 5.2.1 dev: true - /is-core-module@2.13.0: - resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} + /key-encoder@2.0.3: + resolution: {integrity: sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==} dependencies: - has: 1.0.3 + '@types/elliptic': 6.4.14 + asn1.js: 5.4.1 + bn.js: 4.12.0 + elliptic: 6.5.4 - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} + /keygrip@1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + requiresBuild: true dependencies: - has-tostringtag: 1.0.0 - dev: true + tsscmp: 1.0.6 + dev: false + optional: true - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + /kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + /kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} - /is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + /kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} dev: true - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + /koalas@1.0.2: + resolution: {integrity: sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA==} engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 + dev: false - /is-lambda@1.0.1: - resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} - dev: true + /kysely@0.22.0: + resolution: {integrity: sha512-ZE3qWtnqLOalodzfK5QUEcm7AEulhxsPNuKaGFsC3XiqO92vMLm+mAHk/NnbSIOtC4RmGm0nsv700i8KDp1gfQ==} + engines: {node: '>=14.0.0'} - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - dev: true + /kysely@0.23.5: + resolution: {integrity: sha512-TH+b56pVXQq0tsyooYLeNfV11j6ih7D50dyN8tkM0e7ndiUH28Nziojiog3qRFlmEj9XePYdZUrNJ2079Qjdow==} + engines: {node: '>=14.0.0'} - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} + /lande@1.0.10: + resolution: {integrity: sha512-yT52DQh+UV2pEp08jOYrA4drDv0DbjpiRyZYgl25ak9G2cVR2AimzrqkYQWrD9a7Ud+qkAcaiDDoNH9DXfHPmw==} dependencies: - has-tostringtag: 1.0.0 + toygrad: 2.6.0 + dev: false + + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 dev: true - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} + /lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + dependencies: + debug: 2.6.9 + marky: 1.2.5 + transitivePeerDependencies: + - supports-color + dev: false - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} dev: true - /is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} + /lilconfig@3.1.1: + resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} + engines: {node: '>=14'} dev: true - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 + /limiter@1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + dev: false + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + /load-yaml-file@0.2.0: + resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} + engines: {node: '>=6'} dependencies: - call-bind: 1.0.2 + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 dev: true - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} + /loader-utils@3.2.1: + resolution: {integrity: sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==} + engines: {node: '>= 12.13.0'} dev: true - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} + /locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} dependencies: - has-tostringtag: 1.0.0 - dev: true + p-locate: 3.0.0 + path-exists: 3.0.0 + dev: false - /is-subdir@1.2.0: - resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} - engines: {node: '>=4'} + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} dependencies: - better-path-resolve: 1.0.0 - dev: true + p-locate: 4.1.0 - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} dependencies: - has-symbols: 1.0.3 + p-locate: 5.0.0 + + /lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: true - /is-typed-array@1.1.12: - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} - engines: {node: '>= 0.4'} - dependencies: - which-typed-array: 1.1.11 + /lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + dev: false + + /lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} dev: true - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - dependencies: - call-bind: 1.0.2 + /lodash.isarguments@3.1.0: + resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} dev: true - /is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} dev: true - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: false + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: true - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} dev: true - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: true - /iso-datestring-validator@2.2.2: - resolution: {integrity: sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==} + /lodash.kebabcase@4.1.1: + resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} dev: false - /istanbul-lib-coverage@3.2.0: - resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} - engines: {node: '>=8'} - - /istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - dependencies: - '@babel/core': 7.18.6 - '@babel/parser': 7.22.10 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} dev: true - /istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - dependencies: - istanbul-lib-coverage: 3.2.0 - make-dir: 4.0.0 - supports-color: 7.2.0 + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true - /istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - dependencies: - debug: 4.3.4 - istanbul-lib-coverage: 3.2.0 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} dev: true - /istanbul-reports@3.1.6: - resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} - engines: {node: '>=8'} - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 + /lodash.pick@4.4.0: + resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} + dev: false + + /lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + dev: false + + /lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} dev: true - /jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 + /lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} dev: false - /jest-changed-files@28.1.3: - resolution: {integrity: sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - execa: 5.1.1 - p-limit: 3.1.0 - dev: true + /lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - /jest-circus@28.1.3: - resolution: {integrity: sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} dependencies: - '@jest/environment': 28.1.3 - '@jest/expect': 28.1.3 - '@jest/test-result': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.19.24 chalk: 4.1.2 - co: 4.6.0 - dedent: 0.7.0 - is-generator-fn: 2.1.0 - jest-each: 28.1.3 - jest-matcher-utils: 28.1.3 - jest-message-util: 28.1.3 - jest-runtime: 28.1.3 - jest-snapshot: 28.1.3 - jest-util: 28.1.3 - p-limit: 3.1.0 - pretty-format: 28.1.3 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - supports-color - dev: true + is-unicode-supported: 0.1.0 + dev: false - /jest-cli@28.1.3(@types/node@18.19.24)(ts-node@10.8.2): - resolution: {integrity: sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /logkitty@0.7.1: + resolution: {integrity: sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==} hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true dependencies: - '@jest/core': 28.1.3(ts-node@10.8.2) - '@jest/test-result': 28.1.3 - '@jest/types': 28.1.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - import-local: 3.1.0 - jest-config: 28.1.3(@types/node@18.19.24)(ts-node@10.8.2) - jest-util: 28.1.3 - jest-validate: 28.1.3 - prompts: 2.4.2 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - supports-color - - ts-node - dev: true + ansi-fragments: 0.2.1 + dayjs: 1.11.10 + yargs: 15.4.1 + dev: false - /jest-config@28.1.3(@types/node@18.19.24)(ts-node@10.8.2): - resolution: {integrity: sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.18.6 - '@jest/test-sequencer': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.19.24 - babel-jest: 28.1.3(@babel/core@7.18.6) - chalk: 4.1.2 - ci-info: 3.8.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 28.1.3 - jest-environment-node: 28.1.3 - jest-get-type: 28.0.2 - jest-regex-util: 28.0.2 - jest-resolve: 28.1.3 - jest-runner: 28.1.3 - jest-util: 28.1.3 - jest-validate: 28.1.3 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 28.1.3 - slash: 3.0.0 - strip-json-comments: 3.1.1 - ts-node: 10.8.2(@swc/core@1.3.42)(@types/node@18.19.24)(typescript@5.4.4) - transitivePeerDependencies: - - supports-color - dev: true + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false - /jest-diff@28.1.3: - resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true dependencies: - chalk: 4.1.2 - diff-sequences: 28.1.1 - jest-get-type: 28.0.2 - pretty-format: 28.1.3 - dev: true + js-tokens: 4.0.0 - /jest-docblock@28.1.1: - resolution: {integrity: sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} + engines: {node: 14 || >=16.14} + + /lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: - detect-newline: 3.1.0 + pseudomap: 1.0.2 + yallist: 2.1.2 dev: true - /jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: - detect-newline: 3.1.0 - dev: false + yallist: 3.1.1 - /jest-each@28.1.3: - resolution: {integrity: sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} dependencies: - '@jest/types': 28.1.3 - chalk: 4.1.2 - jest-get-type: 28.0.2 - jest-util: 28.1.3 - pretty-format: 28.1.3 - dev: true + yallist: 4.0.0 - /jest-environment-node@28.1.3: - resolution: {integrity: sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + /magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} dependencies: - '@jest/environment': 28.1.3 - '@jest/fake-timers': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.19.24 - jest-mock: 28.1.3 - jest-util: 28.1.3 + '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /jest-get-type@28.0.2: - resolution: {integrity: sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dev: true + /make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + dependencies: + pify: 4.0.1 + semver: 5.7.2 + dev: false - /jest-haste-map@28.1.3: - resolution: {integrity: sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} dependencies: - '@jest/types': 28.1.3 - '@types/graceful-fs': 4.1.6 - '@types/node': 18.19.24 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 28.0.2 - jest-util: 28.1.3 - jest-worker: 28.1.3 - micromatch: 4.0.5 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 + semver: 7.6.0 dev: true - /jest-leak-detector@28.1.3: - resolution: {integrity: sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - jest-get-type: 28.0.2 - pretty-format: 28.1.3 + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true - /jest-matcher-utils@28.1.3: - resolution: {integrity: sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /make-fetch-happen@10.2.1: + resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: - chalk: 4.1.2 - jest-diff: 28.1.3 - jest-get-type: 28.0.2 - pretty-format: 28.1.3 + agentkeepalive: 4.5.0 + cacache: 16.1.3 + http-cache-semantics: 4.1.1 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 2.1.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 9.0.1 + transitivePeerDependencies: + - bluebird + - supports-color dev: true - /jest-message-util@28.1.3: - resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} dependencies: - '@babel/code-frame': 7.22.10 - '@jest/types': 28.1.3 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.5 - pretty-format: 28.1.3 - slash: 3.0.0 - stack-utils: 2.0.6 + tmpl: 1.0.5 + + /map-obj@1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} dev: true - /jest-mock@28.1.3: - resolution: {integrity: sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@jest/types': 28.1.3 - '@types/node': 18.19.24 + /map-obj@4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} dev: true - /jest-pnp-resolver@1.2.3(jest-resolve@28.1.3): - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - dependencies: - jest-resolve: 28.1.3 + /marky@1.2.5: + resolution: {integrity: sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==} + dev: false + + /mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} dev: true - /jest-regex-util@28.0.2: - resolution: {integrity: sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + /memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + dev: false + + /meow@6.1.1: + resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} + engines: {node: '>=8'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 2.5.0 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.13.1 + yargs-parser: 18.1.3 dev: true - /jest-resolve-dependencies@28.1.3: - resolution: {integrity: sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + /metro-babel-transformer@0.80.9: + resolution: {integrity: sha512-d76BSm64KZam1nifRZlNJmtwIgAeZhZG3fi3K+EmPOlrR8rDtBxQHDSN3fSGeNB9CirdTyabTMQCkCup6BXFSQ==} + engines: {node: '>=18'} dependencies: - jest-regex-util: 28.0.2 - jest-snapshot: 28.1.3 + '@babel/core': 7.24.5 + hermes-parser: 0.20.1 + nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - dev: true + dev: false - /jest-resolve@28.1.3: - resolution: {integrity: sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /metro-cache-key@0.80.9: + resolution: {integrity: sha512-hRcYGhEiWIdM87hU0fBlcGr+tHDEAT+7LYNCW89p5JhErFt/QaAkVx4fb5bW3YtXGv5BTV7AspWPERoIb99CXg==} + engines: {node: '>=18'} + dev: false + + /metro-cache@0.80.9: + resolution: {integrity: sha512-ujEdSI43QwI+Dj2xuNax8LMo8UgKuXJEdxJkzGPU6iIx42nYa1byQ+aADv/iPh5sh5a//h5FopraW5voXSgm2w==} + engines: {node: '>=18'} dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 28.1.3 - jest-pnp-resolver: 1.2.3(jest-resolve@28.1.3) - jest-util: 28.1.3 - jest-validate: 28.1.3 - resolve: 1.22.4 - resolve.exports: 1.1.1 - slash: 3.0.0 - dev: true + metro-core: 0.80.9 + rimraf: 3.0.2 + dev: false - /jest-runner@28.1.3: - resolution: {integrity: sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /metro-config@0.80.9: + resolution: {integrity: sha512-28wW7CqS3eJrunRGnsibWldqgwRP9ywBEf7kg+uzUHkSFJNKPM1K3UNSngHmH0EZjomizqQA2Zi6/y6VdZMolg==} + engines: {node: '>=18'} dependencies: - '@jest/console': 28.1.3 - '@jest/environment': 28.1.3 - '@jest/test-result': 28.1.3 - '@jest/transform': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.19.24 - chalk: 4.1.2 - emittery: 0.10.2 - graceful-fs: 4.2.11 - jest-docblock: 28.1.1 - jest-environment-node: 28.1.3 - jest-haste-map: 28.1.3 - jest-leak-detector: 28.1.3 - jest-message-util: 28.1.3 - jest-resolve: 28.1.3 - jest-runtime: 28.1.3 - jest-util: 28.1.3 - jest-watcher: 28.1.3 - jest-worker: 28.1.3 - p-limit: 3.1.0 - source-map-support: 0.5.13 + connect: 3.7.0 + cosmiconfig: 5.2.1 + jest-validate: 29.7.0 + metro: 0.80.9 + metro-cache: 0.80.9 + metro-core: 0.80.9 + metro-runtime: 0.80.9 transitivePeerDependencies: + - bufferutil + - encoding - supports-color - dev: true + - utf-8-validate + dev: false - /jest-runtime@28.1.3: - resolution: {integrity: sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /metro-core@0.80.9: + resolution: {integrity: sha512-tbltWQn+XTdULkGdzHIxlxk4SdnKxttvQQV3wpqqFbHDteR4gwCyTR2RyYJvxgU7HELfHtrVbqgqAdlPByUSbg==} + engines: {node: '>=18'} dependencies: - '@jest/environment': 28.1.3 - '@jest/fake-timers': 28.1.3 - '@jest/globals': 28.1.3 - '@jest/source-map': 28.1.2 - '@jest/test-result': 28.1.3 - '@jest/transform': 28.1.3 - '@jest/types': 28.1.3 - chalk: 4.1.2 - cjs-module-lexer: 1.2.3 - collect-v8-coverage: 1.0.2 - execa: 5.1.1 - glob: 7.2.3 + lodash.throttle: 4.1.1 + metro-resolver: 0.80.9 + dev: false + + /metro-file-map@0.80.9: + resolution: {integrity: sha512-sBUjVtQMHagItJH/wGU9sn3k2u0nrCl0CdR4SFMO1tksXLKbkigyQx4cbpcyPVOAmGTVuy3jyvBlELaGCAhplQ==} + engines: {node: '>=18'} + dependencies: + anymatch: 3.1.3 + debug: 2.6.9 + fb-watchman: 2.0.2 graceful-fs: 4.2.11 - jest-haste-map: 28.1.3 - jest-message-util: 28.1.3 - jest-mock: 28.1.3 - jest-regex-util: 28.0.2 - jest-resolve: 28.1.3 - jest-snapshot: 28.1.3 - jest-util: 28.1.3 - slash: 3.0.0 - strip-bom: 4.0.0 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.5 + node-abort-controller: 3.1.1 + nullthrows: 1.1.1 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 transitivePeerDependencies: - supports-color - dev: true + dev: false - /jest-snapshot@28.1.3: - resolution: {integrity: sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /metro-minify-terser@0.80.9: + resolution: {integrity: sha512-FEeCeFbkvvPuhjixZ1FYrXtO0araTpV6UbcnGgDUpH7s7eR5FG/PiJz3TsuuPP/HwCK19cZtQydcA2QrCw446A==} + engines: {node: '>=18'} + dependencies: + terser: 5.31.0 + dev: false + + /metro-resolver@0.80.9: + resolution: {integrity: sha512-wAPIjkN59BQN6gocVsAvvpZ1+LQkkqUaswlT++cJafE/e54GoVkMNCmrR4BsgQHr9DknZ5Um/nKueeN7kaEz9w==} + engines: {node: '>=18'} + dev: false + + /metro-runtime@0.80.9: + resolution: {integrity: sha512-8PTVIgrVcyU+X/rVCy/9yxNlvXsBCk5JwwkbAm/Dm+Abo6NBGtNjWF0M1Xo/NWCb4phamNWcD7cHdR91HhbJvg==} + engines: {node: '>=18'} + dependencies: + '@babel/runtime': 7.22.10 + dev: false + + /metro-source-map@0.80.9: + resolution: {integrity: sha512-RMn+XS4VTJIwMPOUSj61xlxgBvPeY4G6s5uIn6kt6HB6A/k9ekhr65UkkDD7WzHYs3a9o869qU8tvOZvqeQzgw==} + engines: {node: '>=18'} dependencies: - '@babel/core': 7.18.6 - '@babel/generator': 7.22.10 - '@babel/plugin-syntax-typescript': 7.22.5(@babel/core@7.18.6) '@babel/traverse': 7.22.10 '@babel/types': 7.22.10 - '@jest/expect-utils': 28.1.3 - '@jest/transform': 28.1.3 - '@jest/types': 28.1.3 - '@types/babel__traverse': 7.20.1 - '@types/prettier': 2.7.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.18.6) - chalk: 4.1.2 - expect: 28.1.3 - graceful-fs: 4.2.11 - jest-diff: 28.1.3 - jest-get-type: 28.0.2 - jest-haste-map: 28.1.3 - jest-matcher-utils: 28.1.3 - jest-message-util: 28.1.3 - jest-util: 28.1.3 - natural-compare: 1.4.0 - pretty-format: 28.1.3 - semver: 7.5.4 + invariant: 2.2.4 + metro-symbolicate: 0.80.9 + nullthrows: 1.1.1 + ob1: 0.80.9 + source-map: 0.5.7 + vlq: 1.0.1 transitivePeerDependencies: - supports-color - dev: true - - /jest-util@28.1.3: - resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} - dependencies: - '@jest/types': 28.1.3 - '@types/node': 18.19.24 - chalk: 4.1.2 - ci-info: 3.8.0 - graceful-fs: 4.2.11 - picomatch: 2.3.1 - dev: true + dev: false - /jest-validate@28.1.3: - resolution: {integrity: sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /metro-symbolicate@0.80.9: + resolution: {integrity: sha512-Ykae12rdqSs98hg41RKEToojuIW85wNdmSe/eHUgMkzbvCFNVgcC0w3dKZEhSsqQOXapXRlLtHkaHLil0UD/EA==} + engines: {node: '>=18'} + hasBin: true dependencies: - '@jest/types': 28.1.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 28.0.2 - leven: 3.1.0 - pretty-format: 28.1.3 - dev: true + invariant: 2.2.4 + metro-source-map: 0.80.9 + nullthrows: 1.1.1 + source-map: 0.5.7 + through2: 2.0.5 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + dev: false - /jest-watcher@28.1.3: - resolution: {integrity: sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /metro-transform-plugins@0.80.9: + resolution: {integrity: sha512-UlDk/uc8UdfLNJhPbF3tvwajyuuygBcyp+yBuS/q0z3QSuN/EbLllY3rK8OTD9n4h00qZ/qgxGv/lMFJkwP4vg==} + engines: {node: '>=18'} dependencies: - '@jest/test-result': 28.1.3 - '@jest/types': 28.1.3 - '@types/node': 18.19.24 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.10.2 - jest-util: 28.1.3 - string-length: 4.0.2 - dev: true + '@babel/core': 7.24.5 + '@babel/generator': 7.22.10 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.10 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + dev: false - /jest-worker@28.1.3: - resolution: {integrity: sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /metro-transform-worker@0.80.9: + resolution: {integrity: sha512-c/IrzMUVnI0hSVVit4TXzt3A1GiUltGVlzCmLJWxNrBGHGrJhvgePj38+GXl1Xf4Fd4vx6qLUkKMQ3ux73bFLQ==} + engines: {node: '>=18'} dependencies: - '@types/node': 18.19.24 - merge-stream: 2.0.0 - supports-color: 8.1.1 - dev: true + '@babel/core': 7.24.5 + '@babel/generator': 7.22.10 + '@babel/parser': 7.22.10 + '@babel/types': 7.22.10 + metro: 0.80.9 + metro-babel-transformer: 0.80.9 + metro-cache: 0.80.9 + metro-cache-key: 0.80.9 + metro-minify-terser: 0.80.9 + metro-source-map: 0.80.9 + metro-transform-plugins: 0.80.9 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: false - /jest@28.1.2(@types/node@18.19.24)(ts-node@10.8.2): - resolution: {integrity: sha512-Tuf05DwLeCh2cfWCQbcz9UxldoDyiR1E9Igaei5khjonKncYdc6LDfynKCEWozK0oLE3GD+xKAo2u8x/0s6GOg==} - engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} + /metro@0.80.9: + resolution: {integrity: sha512-Bc57Xf3GO2Xe4UWQsBj/oW6YfLPABEu8jfDVDiNmJvoQW4CO34oDPuYKe4KlXzXhcuNsqOtSxpbjCRRVjhhREg==} + engines: {node: '>=18'} hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true dependencies: - '@jest/core': 28.1.3(ts-node@10.8.2) - '@jest/types': 28.1.3 - import-local: 3.1.0 - jest-cli: 28.1.3(@types/node@18.19.24)(ts-node@10.8.2) + '@babel/code-frame': 7.22.10 + '@babel/core': 7.24.5 + '@babel/generator': 7.22.10 + '@babel/parser': 7.22.10 + '@babel/template': 7.22.5 + '@babel/traverse': 7.22.10 + '@babel/types': 7.22.10 + accepts: 1.3.8 + chalk: 4.1.2 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 2.6.9 + denodeify: 1.2.1 + error-stack-parser: 2.1.4 + graceful-fs: 4.2.11 + hermes-parser: 0.20.1 + image-size: 1.1.1 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.80.9 + metro-cache: 0.80.9 + metro-cache-key: 0.80.9 + metro-config: 0.80.9 + metro-core: 0.80.9 + metro-file-map: 0.80.9 + metro-resolver: 0.80.9 + metro-runtime: 0.80.9 + metro-source-map: 0.80.9 + metro-symbolicate: 0.80.9 + metro-transform-plugins: 0.80.9 + metro-transform-worker: 0.80.9 + mime-types: 2.1.35 + node-fetch: 2.7.0 + nullthrows: 1.1.1 + rimraf: 3.0.2 + serialize-error: 2.1.0 + source-map: 0.5.7 + strip-ansi: 6.0.1 + throat: 5.0.0 + ws: 7.5.9 + yargs: 17.7.2 transitivePeerDependencies: - - '@types/node' + - bufferutil + - encoding - supports-color - - ts-node - dev: true + - utf-8-validate + dev: false - /jose@4.15.4: - resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==} - dev: true + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 - /jose@5.1.3: - resolution: {integrity: sha512-GPExOkcMsCLBTi1YetY2LmkoY559fss0+0KVa6kOfb2YFe84nAM7Nm/XzuZozah4iHgmBGrCOHL5/cy670SBRw==} - dev: false + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} - /joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - dev: true + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + /mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - dev: true - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + /mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} hasBin: true - dependencies: - argparse: 2.0.1 - dev: true + dev: false - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + /min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - hasBin: true dev: true - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: true + /minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true + /minimalistic-crypto-utils@1.0.1: + resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - /json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + + /minimist-options@4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 dev: true - /jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + /minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 dev: true - /jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + /minipass-fetch@2.1.2: + resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 optionalDependencies: - graceful-fs: 4.2.11 + encoding: 0.1.13 dev: true - /jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} + /minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} dependencies: - jws: 3.2.2 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 7.6.0 + minipass: 3.3.6 dev: true - /jwa@1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + /minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 + minipass: 3.3.6 dev: true - /jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + /minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} dependencies: - jwa: 1.4.1 - safe-buffer: 5.2.1 + minipass: 3.3.6 dev: true - /key-encoder@2.0.3: - resolution: {integrity: sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==} + /minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} dependencies: - '@types/elliptic': 6.4.14 - asn1.js: 5.4.1 - bn.js: 4.12.0 - elliptic: 6.5.4 - - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} + yallist: 4.0.0 dev: true - /kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} + /minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + /minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 dev: true - /kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} + /mixme@0.5.9: + resolution: {integrity: sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==} + engines: {node: '>= 8.0.0'} dev: true - /koalas@1.0.2: - resolution: {integrity: sha512-RYhBbYaTTTHId3l6fnMZc3eGQNW6FVCqMG6AMwA5I1Mafr6AflaXeoi6x3xQuATRotGYRLk6+1ELZH4dstFNOA==} - engines: {node: '>=0.10.0'} - dev: false + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - /kysely@0.22.0: - resolution: {integrity: sha512-ZE3qWtnqLOalodzfK5QUEcm7AEulhxsPNuKaGFsC3XiqO92vMLm+mAHk/NnbSIOtC4RmGm0nsv700i8KDp1gfQ==} - engines: {node: '>=14.0.0'} + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.8 + dev: false - /kysely@0.23.5: - resolution: {integrity: sha512-TH+b56pVXQq0tsyooYLeNfV11j6ih7D50dyN8tkM0e7ndiUH28Nziojiog3qRFlmEj9XePYdZUrNJ2079Qjdow==} - engines: {node: '>=14.0.0'} + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true - /lande@1.0.10: - resolution: {integrity: sha512-yT52DQh+UV2pEp08jOYrA4drDv0DbjpiRyZYgl25ak9G2cVR2AimzrqkYQWrD9a7Ud+qkAcaiDDoNH9DXfHPmw==} - dependencies: - toygrad: 2.6.0 + /module-details-from-path@1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} dev: false - /leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - dev: true + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true + /ms@2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /limiter@1.1.5: - resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + /msgpack-lite@0.1.26: + resolution: {integrity: sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==} + hasBin: true + dependencies: + event-lite: 0.1.3 + ieee754: 1.2.1 + int64-buffer: 0.1.10 + isarray: 1.0.0 dev: false - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: true - - /load-yaml-file@0.2.0: - resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} - engines: {node: '>=6'} - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.1 - pify: 4.0.1 - strip-bom: 3.0.0 - dev: true + /multiformats@9.9.0: + resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} - /locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: - p-locate: 4.1.0 + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 dev: true - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true dev: true - /lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} - /lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true - /lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} - /lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - dev: true + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - /lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - dev: true + /nocache@3.0.4: + resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} + engines: {node: '>=12.0.0'} + dev: false - /lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - dev: true + /node-abi@3.47.0: + resolution: {integrity: sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 - /lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - dev: true + /node-abi@3.57.0: + resolution: {integrity: sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==} + engines: {node: '>=10'} + dependencies: + semver: 7.6.0 - /lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - dev: true + /node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + dev: false - /lodash.kebabcase@4.1.1: - resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} + /node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + + /node-dir@0.1.17: + resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} + engines: {node: '>= 0.10.5'} + dependencies: + minimatch: 3.1.2 dev: false - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 - /lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - dev: true + /node-forge@1.3.1: + resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + engines: {node: '>= 6.13.0'} + dev: false - /lodash.pick@4.4.0: - resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} + /node-gyp-build-optional-packages@5.1.1: + resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} + hasBin: true + requiresBuild: true + dependencies: + detect-libc: 2.0.3 dev: false + optional: true - /lodash.sortby@4.7.0: - resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + /node-gyp-build@3.9.0: + resolution: {integrity: sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==} + hasBin: true dev: false - /lodash.startcase@4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + /node-gyp-build@4.6.1: + resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} + hasBin: true + dev: false + + /node-gyp@9.3.1: + resolution: {integrity: sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + dependencies: + env-paths: 2.2.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.1.15 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color dev: true - /lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - dev: false + /node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - /long@5.2.3: - resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} - dev: false + /node-releases@2.0.13: + resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} - /lru-cache@10.2.0: - resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} - engines: {node: 14 || >=16.14} + /node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + + /node-stream-zip@1.15.0: + resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} + engines: {node: '>=0.12.0'} dev: false - /lru-cache@4.1.5: - resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + /nodemailer-html-to-text@3.2.0: + resolution: {integrity: sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg==} + engines: {node: '>= 10.23.0'} dependencies: - pseudomap: 1.0.2 - yallist: 2.1.2 - dev: true + html-to-text: 7.1.1 - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + /nodemailer@6.8.0: + resolution: {integrity: sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==} + engines: {node: '>=6.0.0'} + + /nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true dependencies: - yallist: 3.1.1 + abbrev: 1.1.1 dev: true - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} + /normalize-package-data@2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: - yallist: 4.0.0 + hosted-git-info: 2.8.9 + resolve: 1.22.4 + semver: 5.7.2 + validate-npm-package-license: 3.0.4 + dev: true - /lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} - /make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - dependencies: - semver: 7.6.0 + /normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} dev: true - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + /normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} dev: true - /make-fetch-happen@10.2.1: - resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + dependencies: + path-key: 3.1.1 + + /npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: - agentkeepalive: 4.5.0 - cacache: 16.1.3 - http-cache-semantics: 4.1.1 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - is-lambda: 1.0.1 - lru-cache: 7.18.3 - minipass: 3.3.6 - minipass-collect: 1.0.2 - minipass-fetch: 2.1.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - negotiator: 0.6.3 - promise-retry: 2.0.1 - socks-proxy-agent: 7.0.0 - ssri: 9.0.1 - transitivePeerDependencies: - - bluebird - - supports-color + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 dev: true - /makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} dependencies: - tmpl: 1.0.5 + boolbase: 1.0.0 dev: true - /map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + /nullthrows@1.1.1: + resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + dev: false + + /ob1@0.80.9: + resolution: {integrity: sha512-v9yOxowkZbxWhKOaaTyLjIm1aLy4ebMNcSn4NYJKOAI/Qv+SkfEfszpLr2GIxsccmb2Y2HA9qtsqiIJ80ucpVA==} + engines: {node: '>=18'} + dev: false + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + dev: true + + /object-inspect@1.12.3: + resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} + + /object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + /object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + has-symbols: 1.0.3 + object-keys: 1.1.1 dev: true - /map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - dev: true + /oidc-token-hash@5.0.3: + resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} + engines: {node: ^10.13.0 || >=12.0.0} + dev: false - /media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} + /on-exit-leak-free@2.1.0: + resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} - /meow@6.1.1: - resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} - engines: {node: '>=8'} + /on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 6.2.2 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 2.5.0 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.13.1 - yargs-parser: 18.1.3 - dev: true + ee-first: 1.1.1 + dev: false - /merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: true + /on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + dependencies: + wrappy: 1.0.2 - /methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} + /one-webcrypto@1.0.3: + resolution: {integrity: sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==} - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} dependencies: - braces: 3.0.2 - picomatch: 2.3.1 + mimic-fn: 2.1.0 - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} + /open@6.4.0: + resolution: {integrity: sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==} + engines: {node: '>=8'} + dependencies: + is-wsl: 1.1.0 + dev: false - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} + /open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} dependencies: - mime-db: 1.52.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + dev: false - /mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} + /opener@1.5.2: + resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true + dev: true - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} + /opentelemetry-plugin-better-sqlite3@1.1.0(better-sqlite3@9.4.5): + resolution: {integrity: sha512-yd+mgaB5W5JxzcQt9TvX1VIrusqtbbeuxSoZ6KQe4Ra0J/Kqkp6kz7dg0VQUU5+cenOWkza6xtvsT0KGXI03HA==} + peerDependencies: + better-sqlite3: ^7.1.1 || ^8.0.0 || ^9.0.0 + dependencies: + '@opentelemetry/api': 1.7.0 + '@opentelemetry/core': 1.18.1(@opentelemetry/api@1.7.0) + '@opentelemetry/instrumentation': 0.44.0(@opentelemetry/api@1.7.0) + '@opentelemetry/semantic-conventions': 1.18.1 + better-sqlite3: 9.4.5 + transitivePeerDependencies: + - supports-color + dev: false + + /opentracing@0.14.7: + resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} + engines: {node: '>=0.10'} + dev: false + + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 dev: true - /mimic-response@3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + dev: false - /min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} dev: true - /minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - /minimalistic-crypto-utils@1.0.1: - resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + /outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + dev: true - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + /p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} dependencies: - brace-expansion: 1.1.11 + p-map: 2.1.0 dev: true - /minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - dependencies: - brace-expansion: 2.0.1 + /p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} - /minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} dependencies: - brace-expansion: 2.0.1 + p-try: 2.2.0 - /minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - dev: true + yocto-queue: 0.1.0 - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + /p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + dependencies: + p-limit: 2.3.0 + dev: false - /minipass-collect@1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} dependencies: - minipass: 3.3.6 - dev: true + p-limit: 2.3.0 - /minipass-fetch@2.1.2: - resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} dependencies: - minipass: 3.3.6 - minipass-sized: 1.0.3 - minizlib: 2.1.2 - optionalDependencies: - encoding: 0.1.13 + p-limit: 3.1.0 + + /p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} dev: true - /minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} + /p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} dependencies: - minipass: 3.3.6 + aggregate-error: 3.1.0 dev: true - /minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + /p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} engines: {node: '>=8'} dependencies: - minipass: 3.3.6 - dev: true + eventemitter3: 4.0.7 + p-timeout: 3.2.0 - /minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + /p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} engines: {node: '>=8'} dependencies: - minipass: 3.3.6 - dev: true + p-finally: 1.0.0 - /minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + /p-wait-for@3.2.0: + resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} engines: {node: '>=8'} dependencies: - yallist: 4.0.0 - dev: true + p-timeout: 3.2.0 - /minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} + /packet-reader@1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} - /minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} dependencies: - minipass: 3.3.6 - yallist: 4.0.0 + callsites: 3.1.0 dev: true - /mixme@0.5.9: - resolution: {integrity: sha512-VC5fg6ySUscaWUpI4gxCBTQMH2RdUpNrk+MsbpCYtIvf9SBJdiUey4qE7BXviJsJR4nDQxCZ+3yaYNW3guz/Pw==} - engines: {node: '>= 8.0.0'} + /parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + dev: false + + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.22.10 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 dev: true - /mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} - /mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true + /path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: false - /module-details-from-path@1.0.3: - resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + /path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} dev: false - /ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} - /msgpack-lite@0.1.26: - resolution: {integrity: sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw==} - hasBin: true - dependencies: - event-lite: 0.1.3 - ieee754: 1.2.1 - int64-buffer: 0.1.10 - isarray: 1.0.0 - dev: false + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - /multiformats@9.9.0: - resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.2.0 + minipass: 5.0.0 - /napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} dev: true - /negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} + /peek-readable@4.1.0: + resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} + engines: {node: '>=8'} - /neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + /pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} - /node-abi@3.47.0: - resolution: {integrity: sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==} - engines: {node: '>=10'} - dependencies: - semver: 7.5.4 + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} - /node-abi@3.57.0: - resolution: {integrity: sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==} - engines: {node: '>=10'} + /pg-pool@3.6.1(pg@8.10.0): + resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} + peerDependencies: + pg: '>=8.0' dependencies: - semver: 7.6.0 + pg: 8.10.0 - /node-abort-controller@3.1.1: - resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - dev: false + /pg-protocol@1.6.0: + resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} - /node-addon-api@6.1.0: - resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 - /node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} + /pg@8.10.0: + resolution: {integrity: sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==} + engines: {node: '>= 8.0.0'} peerDependencies: - encoding: ^0.1.0 + pg-native: '>=3.0.1' peerDependenciesMeta: - encoding: + pg-native: optional: true dependencies: - whatwg-url: 5.0.0 - dev: true + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.6.2 + pg-pool: 3.6.1(pg@8.10.0) + pg-protocol: 1.6.0 + pg-types: 2.2.0 + pgpass: 1.0.5 - /node-gyp-build-optional-packages@5.1.1: - resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} - hasBin: true - requiresBuild: true + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} dependencies: - detect-libc: 2.0.3 - dev: false - optional: true + split2: 4.2.0 - /node-gyp-build@3.9.0: - resolution: {integrity: sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==} - hasBin: true - dev: false + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - /node-gyp-build@4.6.1: - resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} - hasBin: true - dev: false + /picocolors@1.0.1: + resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} - /node-gyp@9.3.1: - resolution: {integrity: sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==} - engines: {node: ^12.13 || ^14.13 || >=16} - hasBin: true - dependencies: - env-paths: 2.2.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - make-fetch-happen: 10.2.1 - nopt: 6.0.0 - npmlog: 6.0.2 - rimraf: 3.0.2 - semver: 7.5.4 - tar: 6.1.15 - which: 2.0.2 - transitivePeerDependencies: - - bluebird - - supports-color - dev: true + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} - /node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} dev: true - /node-releases@2.0.13: - resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} - dev: true + /pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} - /nodemailer-html-to-text@3.2.0: - resolution: {integrity: sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg==} - engines: {node: '>= 10.23.0'} + /pify@5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + + /pino-abstract-transport@1.0.0: + resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} dependencies: - html-to-text: 7.1.1 + readable-stream: 4.4.2 + split2: 4.2.0 - /nodemailer@6.8.0: - resolution: {integrity: sha512-EjYvSmHzekz6VNkNd12aUqAco+bOkRe3Of5jVhltqKhEsjw/y0PYPJfp83+s9Wzh1dspYAkUW/YNQ350NATbSQ==} - engines: {node: '>=6.0.0'} + /pino-http@8.2.1: + resolution: {integrity: sha512-bdWAE4HYfFjDhKw2/N7BLNSIFAs+WDLZnetsGRpBdNEKq7/RoZUgblLS5OlMY257RPQml6J5QiiLkwxbstzWbA==} + dependencies: + fast-url-parser: 1.1.3 + get-caller-file: 2.0.5 + pino: 8.15.0 + pino-std-serializers: 6.2.2 + process-warning: 2.2.0 + dev: false - /nopt@6.0.0: - resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + /pino-http@8.4.0: + resolution: {integrity: sha512-9I1eRLxsujQJwLQTrHBU0wDlwnry2HzV2TlDwAsmZ9nT3Y2NQBLrz+DYp73L4i11vl/eudnFT8Eg0Kp62tMwEw==} + dependencies: + get-caller-file: 2.0.5 + pino: 8.15.0 + pino-std-serializers: 6.2.2 + process-warning: 2.2.0 + + /pino-pretty@9.1.0: + resolution: {integrity: sha512-IM6NY9LLo/dVgY7/prJhCh4rAJukafdt0ibxeNOWc2fxKMyTk90SOB9Ao2HfbtShT9QPeP0ePpJktksMhSQMYA==} hasBin: true dependencies: - abbrev: 1.1.1 + colorette: 2.0.20 + dateformat: 4.6.3 + fast-copy: 2.1.7 + fast-safe-stringify: 2.1.1 + help-me: 4.2.0 + joycon: 3.1.1 + minimist: 1.2.8 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pump: 3.0.0 + readable-stream: 4.4.2 + secure-json-parse: 2.7.0 + sonic-boom: 3.3.0 + strip-json-comments: 3.1.1 dev: true - /normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + /pino-std-serializers@6.2.2: + resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + + /pino@8.15.0: + resolution: {integrity: sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ==} + hasBin: true + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.3.0 + on-exit-leak-free: 2.1.0 + pino-abstract-transport: 1.0.0 + pino-std-serializers: 6.2.2 + process-warning: 2.2.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.4.3 + sonic-boom: 3.3.0 + thread-stream: 2.4.0 + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + /pkg-dir@3.0.0: + resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} + engines: {node: '>=6'} dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.4 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - dev: true - - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - dev: true + find-up: 3.0.0 + dev: false - /npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} dependencies: - path-key: 3.1.1 + find-up: 4.1.0 dev: true - /npmlog@6.0.2: - resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + /postcss-calc@8.2.4(postcss@8.4.38): + resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} + peerDependencies: + postcss: ^8.2.2 dependencies: - are-we-there-yet: 3.0.1 - console-control-strings: 1.1.0 - gauge: 4.0.4 - set-blocking: 2.0.0 + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + postcss-value-parser: 4.2.0 dev: true - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - /object-inspect@1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - /object.assign@4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} - engines: {node: '>= 0.4'} + /postcss-colormin@5.3.1(postcss@8.4.38): + resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - has-symbols: 1.0.3 - object-keys: 1.1.1 + browserslist: 4.21.10 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 dev: true - /on-exit-leak-free@2.1.0: - resolution: {integrity: sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==} - - /on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - dependencies: - ee-first: 1.1.1 - - /on-headers@1.0.2: - resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} - engines: {node: '>= 0.8'} - - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - - /one-webcrypto@1.0.3: - resolution: {integrity: sha512-fu9ywBVBPx0gS9K0etIROTiCkvI5S1TDjFsYFb3rC1ewFxeOqsbzq7aIMBHsYfrTHBcGXJaONXXjTl8B01cW1Q==} - - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} + /postcss-convert-values@5.1.3(postcss@8.4.38): + resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - mimic-fn: 2.1.0 + browserslist: 4.21.10 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 dev: true - /opentelemetry-plugin-better-sqlite3@1.1.0(better-sqlite3@9.4.5): - resolution: {integrity: sha512-yd+mgaB5W5JxzcQt9TvX1VIrusqtbbeuxSoZ6KQe4Ra0J/Kqkp6kz7dg0VQUU5+cenOWkza6xtvsT0KGXI03HA==} + /postcss-discard-comments@5.1.2(postcss@8.4.38): + resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: - better-sqlite3: ^7.1.1 || ^8.0.0 || ^9.0.0 + postcss: ^8.2.15 dependencies: - '@opentelemetry/api': 1.7.0 - '@opentelemetry/core': 1.18.1(@opentelemetry/api@1.7.0) - '@opentelemetry/instrumentation': 0.44.0(@opentelemetry/api@1.7.0) - '@opentelemetry/semantic-conventions': 1.18.1 - better-sqlite3: 9.4.5 - transitivePeerDependencies: - - supports-color - dev: false - - /opentracing@0.14.7: - resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} - engines: {node: '>=0.10'} - dev: false + postcss: 8.4.38 + dev: true - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} + /postcss-discard-duplicates@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 + postcss: 8.4.38 dev: true - /os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} + /postcss-discard-empty@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 dev: true - /outdent@0.5.0: - resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + /postcss-discard-overridden@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 dev: true - /p-filter@2.1.0: - resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} - engines: {node: '>=8'} + /postcss-import@15.1.0(postcss@8.4.38): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 dependencies: - p-map: 2.1.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.4 dev: true - /p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - - /p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} + /postcss-js@4.0.1(postcss@8.4.38): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 dependencies: - p-try: 2.2.0 + camelcase-css: 2.0.1 + postcss: 8.4.38 dev: true - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} + /postcss-load-config@3.1.4(postcss@8.4.38): + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true dependencies: - yocto-queue: 0.1.0 + lilconfig: 2.1.0 + postcss: 8.4.38 + yaml: 1.10.2 + dev: true - /p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} + /postcss-load-config@4.0.2(postcss@8.4.38): + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true dependencies: - p-limit: 2.3.0 + lilconfig: 3.1.1 + postcss: 8.4.38 + yaml: 2.4.2 dev: true - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} + /postcss-merge-longhand@5.1.7(postcss@8.4.38): + resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - p-limit: 3.1.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.1(postcss@8.4.38) dev: true - /p-map@2.1.0: - resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} - engines: {node: '>=6'} + /postcss-merge-rules@5.1.4(postcss@8.4.38): + resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 dev: true - /p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} + /postcss-minify-font-values@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - aggregate-error: 3.1.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 dev: true - /p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} + /postcss-minify-gradients@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 + colord: 2.9.3 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} + /postcss-minify-params@5.1.4(postcss@8.4.38): + resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - p-finally: 1.0.0 - - /p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} + browserslist: 4.21.10 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 dev: true - /p-wait-for@3.2.0: - resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} - engines: {node: '>=8'} + /postcss-minify-selectors@5.2.1(postcss@8.4.38): + resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - p-timeout: 3.2.0 - - /packet-reader@1.0.0: - resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + dev: true - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} + /postcss-modules-extract-imports@3.1.0(postcss@8.4.38): + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 dependencies: - callsites: 3.1.0 + postcss: 8.4.38 dev: true - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} + /postcss-modules-local-by-default@4.0.5(postcss@8.4.38): + resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 dependencies: - '@babel/code-frame': 7.22.10 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + postcss-value-parser: 4.2.0 dev: true - /parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - /path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - dev: false - - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} + /postcss-modules-scope@3.2.0(postcss@8.4.38): + resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 dev: true - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} + /postcss-modules-values@4.0.0(postcss@8.4.38): + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + dependencies: + icss-utils: 5.1.0(postcss@8.4.38) + postcss: 8.4.38 dev: true - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - /path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} + /postcss-modules@4.3.1(postcss@8.4.38): + resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} + peerDependencies: + postcss: ^8.0.0 dependencies: - lru-cache: 10.2.0 - minipass: 5.0.0 - dev: false - - /path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} + generic-names: 4.0.0 + icss-replace-symbols: 1.1.0 + lodash.camelcase: 4.3.0 + postcss: 8.4.38 + postcss-modules-extract-imports: 3.1.0(postcss@8.4.38) + postcss-modules-local-by-default: 4.0.5(postcss@8.4.38) + postcss-modules-scope: 3.2.0(postcss@8.4.38) + postcss-modules-values: 4.0.0(postcss@8.4.38) + string-hash: 1.1.3 dev: true - /peek-readable@4.1.0: - resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} - engines: {node: '>=8'} - - /pg-connection-string@2.6.2: - resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} - - /pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} + /postcss-nested@6.0.1(postcss@8.4.38): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + dev: true - /pg-pool@3.6.1(pg@8.10.0): - resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} + /postcss-normalize-charset@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: - pg: '>=8.0' + postcss: ^8.2.15 dependencies: - pg: 8.10.0 + postcss: 8.4.38 + dev: true - /pg-protocol@1.6.0: - resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} + /postcss-normalize-display-values@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} + /postcss-normalize-positions@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - pg-int8: 1.0.1 - postgres-array: 2.0.0 - postgres-bytea: 1.0.0 - postgres-date: 1.0.7 - postgres-interval: 1.2.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /pg@8.10.0: - resolution: {integrity: sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==} - engines: {node: '>= 8.0.0'} + /postcss-normalize-repeat-style@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} + engines: {node: ^10 || ^12 || >=14.0} peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true + postcss: ^8.2.15 dependencies: - buffer-writer: 2.0.0 - packet-reader: 1.0.0 - pg-connection-string: 2.6.2 - pg-pool: 3.6.1(pg@8.10.0) - pg-protocol: 1.6.0 - pg-types: 2.2.0 - pgpass: 1.0.5 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + /postcss-normalize-string@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - split2: 4.2.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + /postcss-normalize-timing-functions@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 dev: true - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + /postcss-normalize-unicode@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} + /postcss-normalize-url@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + normalize-url: 6.1.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 dev: true - /pify@5.0.0: - resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} - engines: {node: '>=10'} - dev: false + /postcss-normalize-whitespace@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /pino-abstract-transport@1.0.0: - resolution: {integrity: sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==} + /postcss-ordered-values@5.1.3(postcss@8.4.38): + resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - readable-stream: 4.4.2 - split2: 4.2.0 + cssnano-utils: 3.1.0(postcss@8.4.38) + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /pino-http@8.2.1: - resolution: {integrity: sha512-bdWAE4HYfFjDhKw2/N7BLNSIFAs+WDLZnetsGRpBdNEKq7/RoZUgblLS5OlMY257RPQml6J5QiiLkwxbstzWbA==} + /postcss-reduce-initial@5.1.2(postcss@8.4.38): + resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - fast-url-parser: 1.1.3 - get-caller-file: 2.0.5 - pino: 8.15.0 - pino-std-serializers: 6.2.2 - process-warning: 2.2.0 - dev: false + browserslist: 4.21.10 + caniuse-api: 3.0.0 + postcss: 8.4.38 + dev: true - /pino-http@8.4.0: - resolution: {integrity: sha512-9I1eRLxsujQJwLQTrHBU0wDlwnry2HzV2TlDwAsmZ9nT3Y2NQBLrz+DYp73L4i11vl/eudnFT8Eg0Kp62tMwEw==} + /postcss-reduce-transforms@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - get-caller-file: 2.0.5 - pino: 8.15.0 - pino-std-serializers: 6.2.2 - process-warning: 2.2.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true - /pino-pretty@9.1.0: - resolution: {integrity: sha512-IM6NY9LLo/dVgY7/prJhCh4rAJukafdt0ibxeNOWc2fxKMyTk90SOB9Ao2HfbtShT9QPeP0ePpJktksMhSQMYA==} - hasBin: true + /postcss-selector-parser@6.0.16: + resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} + engines: {node: '>=4'} dependencies: - colorette: 2.0.20 - dateformat: 4.6.3 - fast-copy: 2.1.7 - fast-safe-stringify: 2.1.1 - help-me: 4.2.0 - joycon: 3.1.1 - minimist: 1.2.8 - on-exit-leak-free: 2.1.0 - pino-abstract-transport: 1.0.0 - pump: 3.0.0 - readable-stream: 4.4.2 - secure-json-parse: 2.7.0 - sonic-boom: 3.3.0 - strip-json-comments: 3.1.1 + cssesc: 3.0.0 + util-deprecate: 1.0.2 dev: true - /pino-std-serializers@6.2.2: - resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} + /postcss-svgo@5.1.0(postcss@8.4.38): + resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + svgo: 2.8.0 + dev: true - /pino@8.15.0: - resolution: {integrity: sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ==} - hasBin: true + /postcss-unique-selectors@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 dependencies: - atomic-sleep: 1.0.0 - fast-redact: 3.3.0 - on-exit-leak-free: 2.1.0 - pino-abstract-transport: 1.0.0 - pino-std-serializers: 6.2.2 - process-warning: 2.2.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.4.3 - sonic-boom: 3.3.0 - thread-stream: 2.4.0 + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + dev: true - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true - /pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} dependencies: - find-up: 4.1.0 + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.2.0 dev: true /postgres-array@2.0.0: @@ -9605,6 +13995,16 @@ packages: engines: {node: '>=14'} hasBin: true + /pretty-format@26.6.2: + resolution: {integrity: sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==} + engines: {node: '>= 10'} + dependencies: + '@jest/types': 26.6.2 + ansi-regex: 5.0.1 + ansi-styles: 4.3.0 + react-is: 17.0.2 + dev: false + /pretty-format@28.1.3: resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} @@ -9615,6 +14015,19 @@ packages: react-is: 18.2.0 dev: true + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: false + + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: false + /process-warning@2.2.0: resolution: {integrity: sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==} @@ -9639,13 +14052,23 @@ packages: retry: 0.12.0 dev: true + /promise.series@0.2.0: + resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==} + engines: {node: '>=0.12'} + dev: true + + /promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + dependencies: + asap: 2.0.6 + dev: false + /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} dependencies: kleur: 3.0.3 sisteransi: 1.0.5 - dev: true /protobufjs@7.2.5: resolution: {integrity: sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==} @@ -9680,6 +14103,10 @@ packages: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} dev: true + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: false + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -9700,6 +14127,12 @@ packages: dependencies: side-channel: 1.0.4 + /querystring@0.2.1: + resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==} + engines: {node: '>=0.4.x'} + deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. + dev: false + /querystringify@2.2.0: resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} dev: true @@ -9710,6 +14143,12 @@ packages: /queue-tick@1.0.1: resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + /queue@6.0.2: + resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + dependencies: + inherits: 2.0.4 + dev: false + /quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -9718,6 +14157,12 @@ packages: engines: {node: '>=8'} dev: true + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -9743,8 +14188,116 @@ packages: minimist: 1.2.8 strip-json-comments: 2.0.1 + /react-devtools-core@5.2.0: + resolution: {integrity: sha512-vZK+/gvxxsieAoAyYaiRIVFxlajb7KXhgBDV7OsoMzaAE+IqGpoxusBjIgq5ibqA2IloKu0p9n7tE68z1xs18A==} + dependencies: + shell-quote: 1.8.1 + ws: 7.5.9 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /react-dom@18.3.1(react@18.3.1): + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + dev: true + + /react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: false + /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + + /react-native@0.74.1(@babel/core@7.18.6)(@babel/preset-env@7.24.5)(react@18.3.1): + resolution: {integrity: sha512-0H2XpmghwOtfPpM2LKqHIN7gxy+7G/r1hwJHKLV6uoyXGC/gCojRtoo5NqyKrWpFC8cqyT6wTYCLuG7CxEKilg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@types/react': ^18.2.6 + react: 18.2.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native-community/cli': 13.6.6 + '@react-native-community/cli-platform-android': 13.6.6 + '@react-native-community/cli-platform-ios': 13.6.6 + '@react-native/assets-registry': 0.74.83 + '@react-native/codegen': 0.74.83(@babel/preset-env@7.24.5) + '@react-native/community-cli-plugin': 0.74.83(@babel/core@7.18.6)(@babel/preset-env@7.24.5) + '@react-native/gradle-plugin': 0.74.83 + '@react-native/js-polyfills': 0.74.83 + '@react-native/normalize-colors': 0.74.83 + '@react-native/virtualized-lists': 0.74.83(react-native@0.74.1)(react@18.3.1) + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + base64-js: 1.5.1 + chalk: 4.1.2 + event-target-shim: 5.0.1 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + jest-environment-node: 29.7.0 + jsc-android: 250231.0.0 + memoize-one: 5.2.1 + metro-runtime: 0.80.9 + metro-source-map: 0.80.9 + mkdirp: 0.5.6 + nullthrows: 1.1.1 + pretty-format: 26.6.2 + promise: 8.3.0 + react: 18.3.1 + react-devtools-core: 5.2.0 + react-refresh: 0.14.2 + react-shallow-renderer: 16.15.0(react@18.3.1) + regenerator-runtime: 0.13.11 + scheduler: 0.24.0-canary-efb381bbf-20230505 + stacktrace-parser: 0.1.10 + whatwg-fetch: 3.6.20 + ws: 6.2.2 + yargs: 17.7.2 + transitivePeerDependencies: + - '@babel/core' + - '@babel/preset-env' + - bufferutil + - encoding + - supports-color + - utf-8-validate + dev: false + + /react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + dev: false + + /react-shallow-renderer@16.15.0(react@18.3.1): + resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + object-assign: 4.1.1 + react: 18.3.1 + react-is: 18.2.0 + dev: false + + /react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 dev: true /read-pkg-up@7.0.1: @@ -9776,6 +14329,18 @@ packages: strip-bom: 3.0.0 dev: true + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: false + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -9800,10 +14365,31 @@ packages: dependencies: readable-stream: 3.6.2 + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /readline@1.3.0: + resolution: {integrity: sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==} + dev: false + /real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + /recast@0.21.5: + resolution: {integrity: sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==} + engines: {node: '>= 4'} + dependencies: + ast-types: 0.15.2 + esprima: 4.0.1 + source-map: 0.6.1 + tslib: 2.6.2 + dev: false + /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -9822,9 +14408,29 @@ packages: dependencies: redis-errors: 1.2.0 + /regenerate-unicode-properties@10.1.1: + resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + dev: false + + /regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + dev: false + + /regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: false + /regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} - dev: true + + /regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + dependencies: + '@babel/runtime': 7.22.10 + dev: false /regexp.prototype.flags@1.5.0: resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} @@ -9835,10 +14441,28 @@ packages: functions-have-names: 1.2.3 dev: true + /regexpu-core@5.3.2: + resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} + engines: {node: '>=4'} + dependencies: + '@babel/regjsgen': 0.8.0 + regenerate: 1.4.2 + regenerate-unicode-properties: 10.1.1 + regjsparser: 0.9.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + dev: false + + /regjsparser@0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: false + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - dev: true /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} @@ -9857,7 +14481,6 @@ packages: /require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: true /requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -9870,6 +14493,11 @@ packages: resolve-from: 5.0.0 dev: true + /resolve-from@3.0.0: + resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} + engines: {node: '>=4'} + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -9893,6 +14521,14 @@ packages: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: false + /retry@0.10.1: resolution: {integrity: sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==} dev: false @@ -9914,12 +14550,18 @@ packages: /rfdc@1.3.0: resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + /rimraf@2.6.3: + resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + hasBin: true + dependencies: + glob: 7.2.3 + dev: false + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true dependencies: glob: 7.2.3 - dev: true /roarr@7.15.1: resolution: {integrity: sha512-0ExL9rjOXeQPvQvQo8IcV8SR2GTXmDr1FQFlY2HiAV+gdVQjaVZNOx9d4FI2RqFFsd0sNsiw2TRS/8RU9g0ZfA==} @@ -9932,6 +14574,69 @@ packages: safe-stable-stringify: 2.4.3 semver-compare: 1.0.0 + /rollup-plugin-postcss@4.0.2(postcss@8.4.38): + resolution: {integrity: sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==} + engines: {node: '>=10'} + peerDependencies: + postcss: 8.x + dependencies: + chalk: 4.1.2 + concat-with-sourcemaps: 1.1.0 + cssnano: 5.1.15(postcss@8.4.38) + import-cwd: 3.0.0 + p-queue: 6.6.2 + pify: 5.0.0 + postcss: 8.4.38 + postcss-load-config: 3.1.4(postcss@8.4.38) + postcss-modules: 4.3.1(postcss@8.4.38) + promise.series: 0.2.0 + resolve: 1.22.4 + rollup-pluginutils: 2.8.2 + safe-identifier: 0.4.2 + style-inject: 0.3.0 + transitivePeerDependencies: + - ts-node + dev: true + + /rollup-plugin-serve@1.1.1: + resolution: {integrity: sha512-H0VarZRtFR0lfiiC9/P8jzCDvtFf1liOX4oSdIeeYqUCKrmFA7vNiQ0rg2D+TuoP7leaa/LBR8XBts5viF6lnw==} + dependencies: + mime: 2.6.0 + opener: 1.5.2 + dev: true + + /rollup-pluginutils@2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + dependencies: + estree-walker: 0.6.1 + dev: true + + /rollup@4.17.2: + resolution: {integrity: sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + dependencies: + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.17.2 + '@rollup/rollup-android-arm64': 4.17.2 + '@rollup/rollup-darwin-arm64': 4.17.2 + '@rollup/rollup-darwin-x64': 4.17.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.17.2 + '@rollup/rollup-linux-arm-musleabihf': 4.17.2 + '@rollup/rollup-linux-arm64-gnu': 4.17.2 + '@rollup/rollup-linux-arm64-musl': 4.17.2 + '@rollup/rollup-linux-powerpc64le-gnu': 4.17.2 + '@rollup/rollup-linux-riscv64-gnu': 4.17.2 + '@rollup/rollup-linux-s390x-gnu': 4.17.2 + '@rollup/rollup-linux-x64-gnu': 4.17.2 + '@rollup/rollup-linux-x64-musl': 4.17.2 + '@rollup/rollup-win32-arm64-msvc': 4.17.2 + '@rollup/rollup-win32-ia32-msvc': 4.17.2 + '@rollup/rollup-win32-x64-msvc': 4.17.2 + fsevents: 2.3.3 + dev: true + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -9960,6 +14665,10 @@ packages: /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + /safe-identifier@0.4.2: + resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} + dev: true + /safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} dependencies: @@ -9976,6 +14685,18 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} requiresBuild: true + /scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + dependencies: + loose-envify: 1.4.0 + dev: true + + /scheduler@0.24.0-canary-efb381bbf-20230505: + resolution: {integrity: sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==} + dependencies: + loose-envify: 1.4.0 + dev: false + /scmp@2.1.0: resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==} dev: true @@ -9984,6 +14705,14 @@ packages: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} dev: true + /selfsigned@2.4.1: + resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} + engines: {node: '>=10'} + dependencies: + '@types/node-forge': 1.3.11 + node-forge: 1.3.1 + dev: false + /semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} @@ -9994,7 +14723,6 @@ packages: /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - dev: true /semver@7.5.4: resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} @@ -10030,6 +14758,17 @@ packages: transitivePeerDependencies: - supports-color + /serialize-error@2.1.0: + resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} + engines: {node: '>=0.10.0'} + dev: false + + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + dependencies: + randombytes: 2.1.0 + dev: true + /serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} @@ -10043,11 +14782,17 @@ packages: /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + /shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + dependencies: + kind-of: 6.0.3 + dev: false + /sharp@0.32.6: resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} engines: {node: '>=14.15.0'} @@ -10084,6 +14829,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + /shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + dev: false + /shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} dev: false @@ -10097,12 +14846,10 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - dev: true /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: false /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -10121,13 +14868,25 @@ packages: /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - dev: true /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} dev: true + /slice-ansi@2.1.0: + resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==} + engines: {node: '>=6'} + dependencies: + ansi-styles: 3.2.1 + astral-regex: 1.0.0 + is-fullwidth-code-point: 2.0.0 + dev: false + /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -10146,6 +14905,10 @@ packages: yargs: 15.4.1 dev: true + /smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + dev: true + /socks-proxy-agent@7.0.0: resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} engines: {node: '>= 10'} @@ -10170,6 +14933,11 @@ packages: dependencies: atomic-sleep: 1.0.0 + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + dev: true + /source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: @@ -10177,6 +14945,17 @@ packages: source-map: 0.6.1 dev: true + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + /source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + dev: false + /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -10227,7 +15006,6 @@ packages: /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - dev: true /ssri@9.0.1: resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} @@ -10236,16 +15014,36 @@ packages: minipass: 3.3.6 dev: true + /stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + dev: true + /stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 - dev: true + + /stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + dev: false + + /stacktrace-parser@0.1.10: + resolution: {integrity: sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==} + engines: {node: '>=6'} + dependencies: + type-fest: 0.7.1 + dev: false /standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + /statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + dev: false + /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -10269,6 +15067,10 @@ packages: fast-fifo: 1.3.2 queue-tick: 1.0.1 + /string-hash@1.1.3: + resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} + dev: true + /string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -10292,7 +15094,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: false /string.prototype.trim@1.2.7: resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} @@ -10319,11 +15120,24 @@ packages: es-abstract: 1.22.1 dev: true + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: false + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 + /strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + dependencies: + ansi-regex: 4.1.1 + dev: false + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -10335,7 +15149,6 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: false /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} @@ -10350,7 +15163,6 @@ packages: /strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} - dev: true /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} @@ -10384,12 +15196,44 @@ packages: engines: {node: '>= 14', npm: '>=6'} dev: false + /style-inject@0.3.0: + resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} + dev: true + + /stylehacks@5.1.1(postcss@8.4.38): + resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + dependencies: + browserslist: 4.21.10 + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + dev: true + + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + commander: 4.1.1 + glob: 10.3.10 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + dev: true + + /sudo-prompt@9.2.1: + resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} + dev: false + /supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -10402,7 +15246,6 @@ packages: engines: {node: '>=10'} dependencies: has-flag: 4.0.0 - dev: true /supports-hyperlinks@2.3.0: resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} @@ -10416,6 +15259,20 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.0.0 + stable: 0.1.8 + dev: true + /synckit@0.8.8: resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -10424,6 +15281,37 @@ packages: tslib: 2.6.2 dev: true + /tailwindcss@3.4.3: + resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.1 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.0 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.38 + postcss-import: 15.1.0(postcss@8.4.38) + postcss-js: 4.0.1(postcss@8.4.38) + postcss-load-config: 4.0.2(postcss@8.4.38) + postcss-nested: 6.0.1(postcss@8.4.38) + postcss-selector-parser: 6.0.16 + resolve: 1.22.4 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + dev: true + /tar-fs@2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} dependencies: @@ -10468,6 +15356,18 @@ packages: yallist: 4.0.0 dev: true + /temp-dir@2.0.0: + resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} + engines: {node: '>=8'} + dev: false + + /temp@0.8.4: + resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==} + engines: {node: '>=6.0.0'} + dependencies: + rimraf: 2.6.3 + dev: false + /term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -10481,6 +15381,16 @@ packages: supports-hyperlinks: 2.3.0 dev: true + /terser@5.31.0: + resolution: {integrity: sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.10.0 + commander: 2.20.3 + source-map-support: 0.5.21 + /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -10494,11 +15404,35 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + /thread-stream@2.4.0: resolution: {integrity: sha512-xZYtOtmnA63zj04Q+F9bdEay5r47bvpo1CaNqsKi7TpoJHcotUez8Fkfo2RJWpW91lnnaApdpRbVwCWsy+ifcw==} dependencies: real-require: 0.2.0 + /throat@5.0.0: + resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + dev: false + + /through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + dev: false + /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: false @@ -10517,12 +15451,10 @@ packages: /tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - dev: true /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - dev: true /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -10547,7 +15479,6 @@ packages: /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: true /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} @@ -10563,6 +15494,10 @@ packages: typescript: 5.4.4 dev: true + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true + /ts-morph@16.0.0: resolution: {integrity: sha512-jGNF0GVpFj0orFw55LTsQxVYEUOCWBAbR5Ls7fTYE5pQsbW18ssTb/6UXx/GYAEjS+DQTp8VoTw0vqYMiaaQuw==} dependencies: @@ -10610,6 +15545,13 @@ packages: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} requiresBuild: true + /tsscmp@1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + requiresBuild: true + dev: false + optional: true + /tty-table@4.2.1: resolution: {integrity: sha512-xz0uKo+KakCQ+Dxj1D/tKn2FSyreSYWzdkL/BYhgN6oMW808g8QRMuh1atAV9fjTPbWBjfbkKQpI/5rEcnAc7g==} engines: {node: '>=8.0.0'} @@ -10656,7 +15598,6 @@ packages: /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} - dev: true /type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} @@ -10678,6 +15619,11 @@ packages: engines: {node: '>=8'} dev: true + /type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + dev: false + /type-fest@0.8.1: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} @@ -10785,6 +15731,34 @@ packages: dependencies: '@fastify/busboy': 2.1.0 + /undici@6.16.1: + resolution: {integrity: sha512-NeNiTT7ixpeiL1qOIU/xTVpHpVP0svmI6PwoCKaMGaI5AsHOaRdwqU/f7Fi9eyU4u03nd5U/BC8wmRMnS9nqoA==} + engines: {node: '>=18.17'} + dev: false + + /unicode-canonical-property-names-ecmascript@2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + dev: false + + /unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + dev: false + + /unicode-match-property-value-ecmascript@2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} + dev: false + + /unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + dev: false + /unique-filename@2.0.1: resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -10802,7 +15776,6 @@ packages: /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} - dev: true /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} @@ -10817,7 +15790,16 @@ packages: browserslist: 4.21.10 escalade: 3.1.1 picocolors: 1.0.0 - dev: true + + /update-browserslist-db@1.0.16(browserslist@4.23.0): + resolution: {integrity: sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.23.0 + escalade: 3.1.2 + picocolors: 1.0.1 /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -10871,28 +15853,32 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + /vlq@1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + dev: false + /walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: makeerror: 1.0.12 - dev: true /wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: defaults: 1.0.4 - dev: true /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: true + + /whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + dev: false /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - dev: true /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -10906,7 +15892,6 @@ packages: /which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - dev: true /which-pm@2.0.0: resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} @@ -10957,7 +15942,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} @@ -10974,11 +15958,18 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: false /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + /write-file-atomic@2.4.3: + resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} + dependencies: + graceful-fs: 4.2.11 + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + dev: false + /write-file-atomic@4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -10987,6 +15978,33 @@ packages: signal-exit: 3.0.7 dev: true + /ws@6.2.2: + resolution: {integrity: sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + async-limiter: 1.0.1 + dev: false + + /ws@7.5.9: + resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /ws@8.12.0: resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} engines: {node: '>=10.0.0'} @@ -11010,12 +16028,10 @@ packages: /y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - dev: true /yallist@2.1.2: resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} @@ -11023,23 +16039,30 @@ packages: /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: true /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + + /yaml@2.4.2: + resolution: {integrity: sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==} + engines: {node: '>= 14'} + hasBin: true + /yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} dependencies: camelcase: 5.3.1 decamelize: 1.2.0 - dev: true /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - dev: true /yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} @@ -11056,7 +16079,6 @@ packages: which-module: 2.0.1 y18n: 4.0.3 yargs-parser: 18.1.3 - dev: true /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} @@ -11069,7 +16091,6 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: true /yesno@0.4.0: resolution: {integrity: sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ceb38b3a8f3..4079b020696 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,5 @@ packages: - 'services/*' - 'packages/*' + - 'packages/oauth/*' + - 'packages/internal/*' diff --git a/tsconfig.json b/tsconfig.json index 73a52e54516..9534c66ac7e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,9 +9,28 @@ { "path": "./packages/common-web" }, { "path": "./packages/crypto" }, { "path": "./packages/dev-env" }, + { "path": "./packages/did" }, { "path": "./packages/identity" }, + { "path": "./packages/internal/did-resolver" }, + { "path": "./packages/internal/fetch" }, + { "path": "./packages/internal/fetch-node" }, + { "path": "./packages/internal/handle-resolver" }, + { "path": "./packages/internal/handle-resolver-node" }, + { "path": "./packages/internal/identity-resolver" }, + { "path": "./packages/internal/pipe" }, + { "path": "./packages/internal/rollup-plugin-bundle-manifest" }, + { "path": "./packages/internal/simple-store" }, + { "path": "./packages/internal/simple-store-memory" }, { "path": "./packages/lex-cli" }, { "path": "./packages/lexicon" }, + { "path": "./packages/oauth/jwk" }, + { "path": "./packages/oauth/jwk-jose" }, + { "path": "./packages/oauth/jwk-webcrypto" }, + { "path": "./packages/oauth/oauth-client" }, + { "path": "./packages/oauth/oauth-client-browser" }, + { "path": "./packages/oauth/oauth-client-react-native" }, + { "path": "./packages/oauth/oauth-provider" }, + { "path": "./packages/oauth/oauth-types" }, { "path": "./packages/ozone" }, { "path": "./packages/pds" }, { "path": "./packages/repo" }, diff --git a/tsconfig/browser.json b/tsconfig/browser.json new file mode 100644 index 00000000000..e60c4970688 --- /dev/null +++ b/tsconfig/browser.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "lib": ["ES2022", "DOM", "DOM.Iterable", "ESNext.Disposable"], + "jsx": "react-jsx" + } +} diff --git a/tsconfig/bundler.json b/tsconfig/bundler.json new file mode 100644 index 00000000000..6aea841facb --- /dev/null +++ b/tsconfig/bundler.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "module": "ESNext", + "moduleResolution": "Bundler", + "declaration": false, + "declarationMap": false, + "noEmit": true + } +} diff --git a/tsconfig/nodenext.json b/tsconfig/nodenext.json new file mode 100644 index 00000000000..5072557d696 --- /dev/null +++ b/tsconfig/nodenext.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./base.json", + "compilerOptions": { + "lib": ["ES2022", "ScriptHost"], + "types": ["node"], + "module": "Node16", + "target": "ES2022", + "moduleResolution": "Node16" + } +}