diff --git a/.changeset/metal-oranges-sing.md b/.changeset/metal-oranges-sing.md new file mode 100644 index 000000000..8a69fcb40 --- /dev/null +++ b/.changeset/metal-oranges-sing.md @@ -0,0 +1,5 @@ +--- +"@atproto/oauth-provider": minor +--- + +Improve validation of DPoP proofs diff --git a/.changeset/nasty-knives-kick.md b/.changeset/nasty-knives-kick.md new file mode 100644 index 000000000..136669c36 --- /dev/null +++ b/.changeset/nasty-knives-kick.md @@ -0,0 +1,5 @@ +--- +"@atproto/jwk": minor +--- + +Properly validate JWK `htu` claim by enforcing URL without query or fragment diff --git a/.changeset/sweet-ways-allow.md b/.changeset/sweet-ways-allow.md new file mode 100644 index 000000000..69f1b0ce5 --- /dev/null +++ b/.changeset/sweet-ways-allow.md @@ -0,0 +1,5 @@ +--- +"@atproto/pds": patch +--- + +Log clients using invalid "htu" claim in DPoP proof diff --git a/.changeset/weak-cycles-speak.md b/.changeset/weak-cycles-speak.md new file mode 100644 index 000000000..9d74a5663 --- /dev/null +++ b/.changeset/weak-cycles-speak.md @@ -0,0 +1,5 @@ +--- +"@atproto/oauth-provider": patch +--- + +Return DPoP validation result from `authenticateRequest` diff --git a/package.json b/package.json index 691894874..dccd0c8f5 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "prettier": "^3.2.5", "prettier-config-standard": "^7.0.0", "prettier-plugin-tailwindcss": "^0.6.11", - "typescript": "^5.8.2" + "typescript": "^5.8.3" }, "workspaces": { "packages": [ diff --git a/packages/oauth/jwk/src/jwt.ts b/packages/oauth/jwk/src/jwt.ts index 264857fd3..2c2c76f52 100644 --- a/packages/oauth/jwk/src/jwt.ts +++ b/packages/oauth/jwk/src/jwt.ts @@ -61,6 +61,50 @@ export const jwtHeaderSchema = z export type JwtHeader = z.infer +/** + * @see {@link https://www.rfc-editor.org/rfc/rfc9449.html#section-4.2-4.6} + * @see {@link https://www.rfc-editor.org/rfc/rfc9110#section-7.1} + */ +export const htuSchema = z.string().superRefine((value, ctx) => { + try { + const url = new URL(value) + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Only http: and https: protocols are allowed', + }) + } + + if (url.username || url.password) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Credentials not allowed', + }) + } + + if (url.search) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Query string not allowed', + }) + } + + if (url.hash) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Fragment not allowed', + }) + } + } catch (err) { + ctx.addIssue({ + code: z.ZodIssueCode.invalid_string, + validation: 'url', + }) + } + + return value +}) + // https://www.iana.org/assignments/jwt/jwt.xhtml export const jwtPayloadSchema = z .object({ @@ -72,7 +116,7 @@ export const jwtPayloadSchema = z iat: z.number().int().optional(), jti: z.string().optional(), htm: z.string().optional(), - htu: z.string().optional(), + htu: htuSchema.optional(), ath: z.string().optional(), acr: z.string().optional(), azp: z.string().optional(), diff --git a/packages/oauth/oauth-provider/src/dpop/dpop-manager.ts b/packages/oauth/oauth-provider/src/dpop/dpop-manager.ts index e5c05b74f..a25562f1b 100644 --- a/packages/oauth/oauth-provider/src/dpop/dpop-manager.ts +++ b/packages/oauth/oauth-provider/src/dpop/dpop-manager.ts @@ -1,15 +1,18 @@ import { createHash } from 'node:crypto' import { EmbeddedJWK, calculateJwkThumbprint, errors, jwtVerify } from 'jose' import { z } from 'zod' +import { ValidationError } from '@atproto/jwk' import { DPOP_NONCE_MAX_AGE } from '../constants.js' import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js' import { UseDpopNonceError } from '../errors/use-dpop-nonce-error.js' +import { ifURL } from '../lib/util/cast.js' import { DpopNonce, DpopSecret, dpopSecretSchema, rotationIntervalSchema, } from './dpop-nonce.js' +import { DpopProof } from './dpop-proof.js' const { JOSEError } = errors @@ -47,111 +50,163 @@ export class DpopManager { * @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] + httpMethod: string, + httpUrl: Readonly, + httpHeaders: Record, + accessToken?: string, + ): Promise { + // Fool proofing against use of empty string + if (!httpMethod) { + throw new TypeError('HTTP method is required') } - if (!proof || typeof proof !== 'string') { - throw new InvalidDpopProofError('DPoP proof required') - } + const proof = extractProof(httpHeaders) + if (!proof) return null - const { protectedHeader, payload } = await jwtVerify<{ - iat: number - jti: string - }>(proof, EmbeddedJWK, { + const { protectedHeader, payload } = await jwtVerify(proof, EmbeddedJWK, { typ: 'dpop+jwt', - maxTokenAge: 10, + maxTokenAge: 10, // Will ensure presence & validity of "iat" claim clockTolerance: DPOP_NONCE_MAX_AGE / 1e3, - requiredClaims: ['iat', 'jti'], }).catch((err) => { - const message = - err instanceof JOSEError - ? `Invalid DPoP proof (${err.message})` - : 'Invalid DPoP proof' - throw new InvalidDpopProofError(message, err) + throw newInvalidDpopProofError('Failed to verify DPoP proof', err) }) - if (!payload.jti || typeof payload.jti !== 'string') { - throw new InvalidDpopProofError('Invalid or missing jti property') + // @NOTE For legacy & backwards compatibility reason, we cannot use + // `jwtPayloadSchema` here as it will reject DPoP proofs containing a query + // or fragment component in the "htu" claim. + + // const { ath, htm, htu, jti, nonce } = await jwtPayloadSchema + // .parseAsync(payload) + // .catch((err) => { + // throw buildInvalidDpopProofError('Invalid DPoP proof', err) + // }) + + // @TODO Uncomment previous lines (and remove redundant checks bellow) once + // we decide to drop legacy support. + const { ath, htm, htu, jti, nonce } = payload + + if (nonce !== undefined && typeof nonce !== 'string') { + throw newInvalidDpopProofError('Invalid DPoP "nonce" type') + } + + if (!jti || typeof jti !== 'string') { + throw newInvalidDpopProofError('DPoP "jti" missing') } // 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 (!htm || htm !== httpMethod) { + throw newInvalidDpopProofError('DPoP "htm" mismatch') } - if ( - payload['nonce'] !== undefined && - typeof payload['nonce'] !== 'string' - ) { - throw new InvalidDpopProofError('DPoP nonce must be a string') + if (!htu || typeof htu !== 'string') { + throw newInvalidDpopProofError('Invalid DPoP "htu" type') } - if (!payload['nonce'] && this.dpopNonce) { + // > 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. + // + // RFC9449 section 4.3. Checking DPoP Proofs - https://datatracker.ietf.org/doc/html/rfc9449#section-4.3 + if (!htu || parseHtu(htu) !== normalizeHtuUrl(httpUrl)) { + throw newInvalidDpopProofError('DPoP "htu" mismatch') + } + + if (!nonce && this.dpopNonce) { throw new UseDpopNonceError() } - if (payload['nonce'] && !this.dpopNonce?.check(payload['nonce'])) { - throw new UseDpopNonceError('DPoP nonce mismatch') - } - - const htuNorm = normalizeHtu(htu) - if (!htuNorm) { - throw new TypeError('Invalid "htu" argument') - } - - if (htuNorm !== normalizeHtu(payload['htu'])) { - throw new InvalidDpopProofError('DPoP htu mismatch') + if (nonce && !this.dpopNonce?.check(nonce)) { + throw new UseDpopNonceError('DPoP "nonce" mismatch') } if (accessToken) { - const athBuffer = createHash('sha256').update(accessToken).digest() - if (payload['ath'] !== athBuffer.toString('base64url')) { - throw new InvalidDpopProofError('DPoP ath mismatch') + const accessTokenHash = createHash('sha256').update(accessToken).digest() + if (ath !== accessTokenHash.toString('base64url')) { + throw newInvalidDpopProofError('DPoP "ath" mismatch') } - } else if (payload['ath']) { - throw new InvalidDpopProofError('DPoP ath not allowed') + } else if (ath !== undefined) { + throw newInvalidDpopProofError('DPoP "ath" claim not allowed') } - try { - return { - protectedHeader, - payload, - jkt: await calculateJwkThumbprint(protectedHeader['jwk']!, 'sha256'), // EmbeddedJWK - } - } catch (err) { - const message = - err instanceof JOSEError ? err.message : 'Failed to calculate jkt' - throw new InvalidDpopProofError(message, err) - } + // @NOTE we can assert there is a jwk because the jwtVerify used the + // EmbeddedJWK key getter mechanism. + const jwk = protectedHeader.jwk! + const jkt = await calculateJwkThumbprint(jwk, 'sha256').catch((err) => { + throw newInvalidDpopProofError('Failed to calculate jkt', err) + }) + + return { jti, jkt, htm, htu } + } +} + +function extractProof( + httpHeaders: Record, +): string | null { + const dpopHeader = httpHeaders['dpop'] + switch (typeof dpopHeader) { + case 'string': + if (dpopHeader) return dpopHeader + throw newInvalidDpopProofError('DPoP header cannot be empty') + case 'object': + // @NOTE the "0" case should never happen a node.js HTTP server will only + // return an array if the header is set multiple times. + if (dpopHeader.length === 1 && dpopHeader[0]) return dpopHeader[0]! + throw newInvalidDpopProofError('DPoP header must contain a single proof') + default: + return null } } /** - * @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. + * Constructs the HTTP URI (htu) claim as defined in RFC9449. * - * > 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} + * The htu claim is the normalized URL of the HTTP request, excluding the query + * string and fragment. This function ensures that the URL is normalized by + * removing the search and hash components, as well as by using an URL object to + * simplify the pathname (e.g. removing dot segments). + * + * @returns The normalized URL as a string. + * @see {@link https://datatracker.ietf.org/doc/html/rfc9449#section-4.3} */ -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 - } +function normalizeHtuUrl(url: Readonly): string { + // NodeJS's `URL` normalizes the pathname, so we can just use that. + return url.origin + url.pathname +} + +function parseHtu(htu: string): string { + const url = ifURL(htu) + if (!url) { + throw newInvalidDpopProofError('DPoP "htu" is not a valid URL') + } + + // @NOTE the checks bellow can be removed once once jwtPayloadSchema is used + // to validate the DPoP proof payload as it already performs these checks + // (though the htuSchema). + + if (url.password || url.username) { + throw newInvalidDpopProofError('DPoP "htu" must not contain credentials') + } + + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + throw newInvalidDpopProofError('DPoP "htu" must be http or https') + } + + // @NOTE For legacy & backwards compatibility reason, we allow a query and + // fragment in the DPoP proof's htu. This is not a standard behavior as the + // htu is not supposed to contain query or fragment. + + // NodeJS's `URL` normalizes the pathname. + return normalizeHtuUrl(url) +} + +function newInvalidDpopProofError( + title: string, + err?: unknown, +): InvalidDpopProofError { + const msg = + err instanceof JOSEError || err instanceof ValidationError + ? `${title}: ${err.message}` + : title + return new InvalidDpopProofError(msg, err) } diff --git a/packages/oauth/oauth-provider/src/dpop/dpop-proof.ts b/packages/oauth/oauth-provider/src/dpop/dpop-proof.ts new file mode 100644 index 000000000..804ca76d3 --- /dev/null +++ b/packages/oauth/oauth-provider/src/dpop/dpop-proof.ts @@ -0,0 +1,6 @@ +export type DpopProof = { + jti: string + jkt: string + htm: string + htu: string +} diff --git a/packages/oauth/oauth-provider/src/lib/util/authorization-header.ts b/packages/oauth/oauth-provider/src/lib/util/authorization-header.ts index 3376be364..6b1982964 100644 --- a/packages/oauth/oauth-provider/src/lib/util/authorization-header.ts +++ b/packages/oauth/oauth-provider/src/lib/util/authorization-header.ts @@ -11,8 +11,8 @@ export const authorizationHeaderSchema = z.tuple([ oauthAccessTokenSchema, ]) -export const parseAuthorizationHeader = (header?: string) => { - if (header == null) { +export const parseAuthorizationHeader = (header: unknown) => { + if (typeof header !== 'string') { throw new WWWAuthenticateError( 'invalid_request', 'Authorization header required', diff --git a/packages/oauth/oauth-provider/src/lib/util/cast.ts b/packages/oauth/oauth-provider/src/lib/util/cast.ts index 9302f74ef..86138ab93 100644 --- a/packages/oauth/oauth-provider/src/lib/util/cast.ts +++ b/packages/oauth/oauth-provider/src/lib/util/cast.ts @@ -2,3 +2,17 @@ export function asArray(value: T | T[]): T[] { if (value == null) return [] return Array.isArray(value) ? value : [value] } + +export function asURL(value: string | { toString: () => string }): URL { + return new URL(value) +} + +export function ifURL( + value: string | { toString: () => string }, +): URL | undefined { + try { + return asURL(value) + } catch { + return undefined + } +} diff --git a/packages/oauth/oauth-provider/src/oauth-provider.ts b/packages/oauth/oauth-provider/src/oauth-provider.ts index e645be73f..7c520045c 100644 --- a/packages/oauth/oauth-provider/src/oauth-provider.ts +++ b/packages/oauth/oauth-provider/src/oauth-provider.ts @@ -69,7 +69,11 @@ import { LocalizedString, MultiLangString } from './lib/util/locale.js' import { extractZodErrorMessage } from './lib/util/zod-error.js' import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js' import { OAuthHooks } from './oauth-hooks.js' -import { OAuthVerifier, OAuthVerifierOptions } from './oauth-verifier.js' +import { + DpopProof, + OAuthVerifier, + OAuthVerifierOptions, +} from './oauth-verifier.js' import { ReplayStore, ifReplayStore } from './replay/replay-store.js' import { codeSchema } from './request/code.js' import { RequestInfo } from './request/request-info.js' @@ -458,7 +462,7 @@ export class OAuthProvider extends OAuthVerifier { public async pushedAuthorizationRequest( credentials: OAuthClientCredentials, authorizationRequest: OAuthAuthorizationRequestPar, - dpopJkt: null | string, + dpopProof: null | DpopProof, ): Promise { try { const [client, clientAuth] = await this.authenticateClient(credentials) @@ -474,7 +478,7 @@ export class OAuthProvider extends OAuthVerifier { clientAuth, parameters, null, - dpopJkt, + dpopProof, ) return { @@ -717,7 +721,7 @@ export class OAuthProvider extends OAuthVerifier { clientCredentials: OAuthClientCredentials, clientMetadata: RequestMetadata, request: OAuthTokenRequest, - dpopJkt: null | string, + dpopProof: null | DpopProof, ): Promise { const [client, clientAuth] = await this.authenticateClient(clientCredentials) @@ -740,7 +744,7 @@ export class OAuthProvider extends OAuthVerifier { clientAuth, clientMetadata, request, - dpopJkt, + dpopProof, ) } @@ -750,7 +754,7 @@ export class OAuthProvider extends OAuthVerifier { clientAuth, clientMetadata, request, - dpopJkt, + dpopProof, ) } @@ -764,7 +768,7 @@ export class OAuthProvider extends OAuthVerifier { clientAuth: ClientAuth, clientMetadata: RequestMetadata, input: OAuthAuthorizationCodeGrantTokenRequest, - dpopJkt: null | string, + dpopProof: null | DpopProof, ): Promise { const code = codeSchema.parse(input.code) try { @@ -807,7 +811,7 @@ export class OAuthProvider extends OAuthVerifier { deviceId, parameters, input, - dpopJkt, + dpopProof, ) } catch (err) { // If a token is replayed, requestManager.findCode will throw. In that @@ -835,14 +839,14 @@ export class OAuthProvider extends OAuthVerifier { clientAuth: ClientAuth, clientMetadata: RequestMetadata, input: OAuthRefreshTokenGrantTokenRequest, - dpopJkt: null | string, + dpopProof: null | DpopProof, ): Promise { return this.tokenManager.refresh( client, clientAuth, clientMetadata, input, - dpopJkt, + dpopProof, ) } @@ -874,24 +878,24 @@ export class OAuthProvider extends OAuthVerifier { protected override async verifyToken( tokenType: OAuthTokenType, token: OAuthAccessToken, - dpopJkt: string | null, + dpopProof: null | DpopProof, verifyOptions?: VerifyTokenClaimsOptions, ): Promise { if (this.accessTokenMode === AccessTokenMode.stateless) { - return super.verifyToken(tokenType, token, dpopJkt, verifyOptions) + return super.verifyToken(tokenType, token, dpopProof, verifyOptions) } if (this.accessTokenMode === AccessTokenMode.light) { - const { claims } = await super.verifyToken( + const { tokenClaims } = await super.verifyToken( tokenType, token, - dpopJkt, + dpopProof, // Do not verify the scope and audience in case of "light" tokens. // these will be checked through the tokenManager hereafter. undefined, ) - const tokenId = claims.jti + const tokenId = tokenClaims.jti // In addition to verifying the signature (through the verifier above), we // also verify the tokenId is still valid using a database to fetch @@ -900,7 +904,7 @@ export class OAuthProvider extends OAuthVerifier { token, tokenType, tokenId, - dpopJkt, + dpopProof, verifyOptions, ) } diff --git a/packages/oauth/oauth-provider/src/oauth-verifier.ts b/packages/oauth/oauth-provider/src/oauth-verifier.ts index eb0442817..613c115db 100644 --- a/packages/oauth/oauth-provider/src/oauth-verifier.ts +++ b/packages/oauth/oauth-provider/src/oauth-verifier.ts @@ -8,6 +8,7 @@ import { } from '@atproto/oauth-types' import { DpopManager, DpopManagerOptions } from './dpop/dpop-manager.js' import { DpopNonce } from './dpop/dpop-nonce.js' +import { DpopProof } from './dpop/dpop-proof.js' import { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js' import { InvalidTokenError } from './errors/invalid-token-error.js' import { UseDpopNonceError } from './errors/use-dpop-nonce-error.js' @@ -50,7 +51,7 @@ export type OAuthVerifierOptions = Override< > export { DpopNonce, Key, Keyset } -export type { RedisOptions, ReplayStore, VerifyTokenClaimsOptions } +export type { DpopProof, RedisOptions, ReplayStore, VerifyTokenClaimsOptions } export class OAuthVerifier { public readonly issuer: OAuthIssuerIdentifier @@ -95,30 +96,30 @@ export class OAuthVerifier { } public async checkDpopProof( - proof: unknown, - htm: string, - htu: string | URL, + httpMethod: string, + httpUrl: Readonly, + httpHeaders: Record, accessToken?: string, - ): Promise { - if (proof === undefined) return null - - const { payload, jkt } = await this.dpopManager.checkProof( - proof, - htm, - htu, + ): Promise { + const dpopProof = await this.dpopManager.checkProof( + httpMethod, + httpUrl, + httpHeaders, accessToken, ) - const unique = await this.replayManager.uniqueDpop(payload.jti) - if (!unique) throw new InvalidDpopProofError('DPoP proof jti is not unique') + if (dpopProof) { + const unique = await this.replayManager.uniqueDpop(dpopProof.jti) + if (!unique) throw new InvalidDpopProofError('DPoP proof replayed') + } - return jkt + return dpopProof } protected async verifyToken( tokenType: OAuthTokenType, token: OAuthAccessToken, - dpopJkt: string | null, + dpopProof: null | DpopProof, verifyOptions?: VerifyTokenClaimsOptions, ): Promise { if (!isSignedJwt(token)) { @@ -135,35 +136,37 @@ export class OAuthVerifier { token, payload.jti, tokenType, - dpopJkt, payload, + dpopProof, verifyOptions, ) } public async authenticateRequest( - method: string, - url: URL, - headers: { - authorization?: string - dpop?: unknown - }, + httpMethod: string, + httpUrl: Readonly, + httpHeaders: Record, verifyOptions?: VerifyTokenClaimsOptions, - ) { - const [tokenType, token] = parseAuthorizationHeader(headers.authorization) + ): Promise { + const [tokenType, token] = parseAuthorizationHeader( + httpHeaders['authorization'], + ) try { - const dpopJkt = await this.checkDpopProof( - headers.dpop, - method, - url, + const dpopProof = await this.checkDpopProof( + httpMethod, + httpUrl, + httpHeaders, token, ) - if (tokenType === 'DPoP' && !dpopJkt) { - throw new InvalidDpopProofError(`DPoP proof required`) - } + const tokenResult = await this.verifyToken( + tokenType, + token, + dpopProof, + verifyOptions, + ) - return await this.verifyToken(tokenType, token, dpopJkt, verifyOptions) + return tokenResult } catch (err) { if (err instanceof UseDpopNonceError) throw err.toWwwAuthenticateError() if (err instanceof WWWAuthenticateError) throw err diff --git a/packages/oauth/oauth-provider/src/request/request-manager.ts b/packages/oauth/oauth-provider/src/request/request-manager.ts index 70cb5151e..a7fc67e5b 100644 --- a/packages/oauth/oauth-provider/src/request/request-manager.ts +++ b/packages/oauth/oauth-provider/src/request/request-manager.ts @@ -16,6 +16,8 @@ import { DeviceId } from '../device/device-id.js' import { AccessDeniedError } from '../errors/access-denied-error.js' import { ConsentRequiredError } from '../errors/consent-required-error.js' import { InvalidAuthorizationDetailsError } from '../errors/invalid-authorization-details-error.js' +import { InvalidDpopKeyBindingError } from '../errors/invalid-dpop-key-binding-error.js' +import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-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' @@ -23,6 +25,7 @@ import { InvalidScopeError } from '../errors/invalid-scope-error.js' import { RequestMetadata } from '../lib/http/request.js' import { callAsync } from '../lib/util/function.js' import { OAuthHooks } from '../oauth-hooks.js' +import { DpopProof } from '../oauth-verifier.js' import { Signer } from '../signer/signer.js' import { Code, generateCode } from './code.js' import { @@ -56,9 +59,9 @@ export class RequestManager { clientAuth: ClientAuth, input: Readonly, deviceId: null | DeviceId, - dpopJkt: null | string, + dpopProof: null | DpopProof, ): Promise { - const parameters = await this.validate(client, clientAuth, input, dpopJkt) + const parameters = await this.validate(client, clientAuth, input, dpopProof) return this.create(client, clientAuth, parameters, deviceId) } @@ -89,7 +92,7 @@ export class RequestManager { client: Client, clientAuth: ClientAuth, parameters: Readonly, - dpop_jkt: null | string, + dpopProof: null | DpopProof, ): Promise> { // ------------------------------- // Validate unsupported parameters @@ -196,12 +199,11 @@ export class RequestManager { // https://datatracker.ietf.org/doc/html/rfc9449#section-10 if (!parameters.dpop_jkt) { - if (dpop_jkt) parameters = { ...parameters, dpop_jkt } - } else if (parameters.dpop_jkt !== dpop_jkt) { - throw new InvalidParametersError( - parameters, - '"dpop_jkt" parameters does not match the DPoP proof', - ) + if (dpopProof) parameters = { ...parameters, dpop_jkt: dpopProof.jkt } + } else if (!dpopProof) { + throw new InvalidDpopProofError('DPoP proof required') + } else if (parameters.dpop_jkt !== dpopProof.jkt) { + throw new InvalidDpopKeyBindingError() } if (clientAuth.method === CLIENT_ASSERTION_TYPE_JWT_BEARER) { diff --git a/packages/oauth/oauth-provider/src/router/create-oauth-middleware.ts b/packages/oauth/oauth-provider/src/router/create-oauth-middleware.ts index e09282de0..1670a005b 100644 --- a/packages/oauth/oauth-provider/src/router/create-oauth-middleware.ts +++ b/packages/oauth/oauth-provider/src/router/create-oauth-middleware.ts @@ -101,16 +101,16 @@ export function createOAuthMiddleware< .parseAsync(payload, { path: ['body'] }) .catch(throwInvalidRequest) - const dpopJkt = await server.checkDpopProof( - req.headers['dpop'], + const dpopProof = await server.checkDpopProof( req.method!, this.url, + req.headers, ) return server.pushedAuthorizationRequest( credentials, authorizationRequest, - dpopJkt, + dpopProof, ) }, 201), ) @@ -138,17 +138,17 @@ export function createOAuthMiddleware< .parseAsync(payload, { path: ['body'] }) .catch(throwInvalidGrant) - const dpopJkt = await server.checkDpopProof( - req.headers['dpop'], + const dpopProof = await server.checkDpopProof( req.method!, this.url, + req.headers, ) return server.token( clientCredentials, clientMetadata, tokenRequest, - dpopJkt, + dpopProof, ) }), ) diff --git a/packages/oauth/oauth-provider/src/token/token-manager.ts b/packages/oauth/oauth-provider/src/token/token-manager.ts index bfb5df81d..683498004 100644 --- a/packages/oauth/oauth-provider/src/token/token-manager.ts +++ b/packages/oauth/oauth-provider/src/token/token-manager.ts @@ -32,6 +32,7 @@ import { RequestMetadata } from '../lib/http/request.js' import { dateToEpoch, dateToRelativeSeconds } from '../lib/util/date.js' import { callAsync } from '../lib/util/function.js' import { OAuthHooks } from '../oauth-hooks.js' +import { DpopProof } from '../oauth-verifier.js' import { Sub } from '../oidc/sub.js' import { Code, isCode } from '../request/code.js' import { SignedTokenPayload } from '../signer/signed-token-payload.js' @@ -104,12 +105,12 @@ export class TokenManager { | OAuthAuthorizationCodeGrantTokenRequest | OAuthClientCredentialsGrantTokenRequest | OAuthPasswordGrantTokenRequest, - dpopJkt: null | string, + dpopProof: null | DpopProof, ): Promise { // @NOTE the atproto specific DPoP requirement is enforced though the // "dpop_bound_access_tokens" metadata, which is enforced by the // ClientManager class. - if (client.metadata.dpop_bound_access_tokens && !dpopJkt) { + if (client.metadata.dpop_bound_access_tokens && !dpopProof) { throw new InvalidDpopProofError('DPoP proof required') } @@ -117,8 +118,10 @@ export class TokenManager { // Allow clients to bind their access tokens to a DPoP key during // token request if they didn't provide a "dpop_jkt" during the // authorization request. - if (dpopJkt) parameters = { ...parameters, dpop_jkt: dpopJkt } - } else if (parameters.dpop_jkt !== dpopJkt) { + if (dpopProof) parameters = { ...parameters, dpop_jkt: dpopProof.jkt } + } else if (!dpopProof) { + throw new InvalidDpopProofError('DPoP proof required') + } else if (parameters.dpop_jkt !== dpopProof.jkt) { throw new InvalidDpopKeyBindingError() } @@ -347,7 +350,7 @@ export class TokenManager { clientAuth: ClientAuth, clientMetadata: RequestMetadata, input: OAuthRefreshTokenGrantTokenRequest, - dpopJkt: null | string, + dpopProof: null | DpopProof, ): Promise { const refreshTokenParsed = refreshTokenSchema.safeParse(input.refresh_token) if (!refreshTokenParsed.success) { @@ -381,9 +384,9 @@ export class TokenManager { } if (parameters.dpop_jkt) { - if (!dpopJkt) { + if (!dpopProof) { throw new InvalidDpopProofError('DPoP proof required') - } else if (parameters.dpop_jkt !== dpopJkt) { + } else if (parameters.dpop_jkt !== dpopProof.jkt) { throw new InvalidDpopKeyBindingError() } } @@ -531,7 +534,7 @@ export class TokenManager { token: OAuthAccessToken, tokenType: OAuthTokenType, tokenId: TokenId, - dpopJkt: string | null, + dpopProof: null | DpopProof, verifyOptions?: VerifyTokenClaimsOptions, ): Promise { const tokenInfo = await this.getTokenInfo(tokenId).catch((err) => { @@ -547,7 +550,7 @@ export class TokenManager { const { parameters } = data // Construct a list of claim, as if the token was a JWT. - const claims: SignedTokenPayload = { + const tokenClaims: SignedTokenPayload = { iss: this.signer.issuer, jti: tokenId, sub: account.sub, @@ -566,8 +569,8 @@ export class TokenManager { token, tokenId, tokenType, - dpopJkt, - claims, + tokenClaims, + dpopProof, verifyOptions, ) } diff --git a/packages/oauth/oauth-provider/src/token/verify-token-claims.ts b/packages/oauth/oauth-provider/src/token/verify-token-claims.ts index 9b5e936ff..51486bb9a 100644 --- a/packages/oauth/oauth-provider/src/token/verify-token-claims.ts +++ b/packages/oauth/oauth-provider/src/token/verify-token-claims.ts @@ -3,9 +3,13 @@ import { InvalidDpopKeyBindingError } from '../errors/invalid-dpop-key-binding-e import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js' import { asArray } from '../lib/util/cast.js' import { InvalidTokenError } from '../oauth-errors.js' +import { DpopProof } from '../oauth-verifier.js' import { SignedTokenPayload } from '../signer/signed-token-payload.js' import { TokenId } from './token-id.js' +const BEARER = 'Bearer' satisfies OAuthTokenType +const DPOP = 'DPoP' satisfies OAuthTokenType + export type VerifyTokenClaimsOptions = { /** One of these audience must be included in the token audience(s) */ audience?: [string, ...string[]] @@ -17,48 +21,73 @@ export type VerifyTokenClaimsResult = { token: OAuthAccessToken tokenId: TokenId tokenType: OAuthTokenType - claims: SignedTokenPayload + tokenClaims: SignedTokenPayload + dpopProof: null | DpopProof } export function verifyTokenClaims( token: OAuthAccessToken, tokenId: TokenId, tokenType: OAuthTokenType, - dpopJkt: string | null, - claims: SignedTokenPayload, + tokenClaims: SignedTokenPayload, + dpopProof: null | DpopProof, options?: VerifyTokenClaimsOptions, ): VerifyTokenClaimsResult { const dateReference = Date.now() - 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 (tokenClaims.cnf?.jkt) { + // An access token with a cnf.jkt claim must be a DPoP token + if (tokenType !== DPOP) { + throw new InvalidTokenError( + DPOP, + `Access token is bound to a DPoP proof, but token type is ${tokenType}`, + ) + } + + // DPoP token type must be used with a DPoP proof + if (!dpopProof) { + throw new InvalidDpopProofError(`DPoP proof required`) + } + + // DPoP proof must be signed with the key that matches the "cnf" claim + if (tokenClaims.cnf.jkt !== dpopProof.jkt) { + throw new InvalidDpopKeyBindingError() + } + } else { + // An access token without a cnf.jkt claim must be a Bearer token + if (tokenType !== BEARER) { + throw new InvalidTokenError( + BEARER, + `Bearer token type must be used without a DPoP proof`, + ) + } + + // Unexpected DPoP proof received for a Bearer token + if (dpopProof) { + throw new InvalidTokenError( + BEARER, + `DPoP proof not expected for Bearer token type`, + ) + } } if (options?.audience) { - const aud = asArray(claims.aud) + const aud = asArray(tokenClaims.aud) if (!options.audience.some((v) => aud.includes(v))) { throw new InvalidTokenError(tokenType, `Invalid audience`) } } if (options?.scope) { - const scopes = claims.scope?.split(' ') + const scopes = tokenClaims.scope?.split(' ') if (!scopes || !options.scope.some((v) => scopes.includes(v))) { throw new InvalidTokenError(tokenType, `Invalid scope`) } } - if (claims.exp != null && claims.exp * 1000 <= dateReference) { + if (tokenClaims.exp != null && tokenClaims.exp * 1000 <= dateReference) { throw new InvalidTokenError(tokenType, `Token expired`) } - return { token, tokenId, tokenType, claims } + return { token, tokenId, tokenType, tokenClaims, dpopProof } } diff --git a/packages/pds/src/auth-verifier.ts b/packages/pds/src/auth-verifier.ts index fc0f6dfb1..6e032d995 100644 --- a/packages/pds/src/auth-verifier.ts +++ b/packages/pds/src/auth-verifier.ts @@ -490,19 +490,19 @@ export class AuthVerifier { const originalUrl = ('originalUrl' in req && req.originalUrl) || req.url || '/' const url = new URL(originalUrl, this._publicUrl) - const result = await this.oauthVerifier.authenticateRequest( + const { tokenClaims } = await this.oauthVerifier.authenticateRequest( req.method || 'GET', url, req.headers, { audience: [this.dids.pds] }, ) - const { sub } = result.claims + const { sub } = tokenClaims if (typeof sub !== 'string' || !sub.startsWith('did:')) { throw new InvalidRequestError('Malformed token', 'InvalidToken') } - const oauthScopes = new Set(result.claims.scope?.split(' ')) + const oauthScopes = new Set(tokenClaims.scope?.split(' ')) if (!oauthScopes.has('transition:generic')) { throw new AuthRequiredError( @@ -535,7 +535,7 @@ export class AuthVerifier { return { credentials: { type: 'oauth', - did: result.claims.sub, + did: tokenClaims.sub, scope: scopeEquivalent, oauthScopes, isPrivileged: scopeEquivalent === AuthScope.AppPassPrivileged, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 09604f515..7293ce29d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,10 +31,10 @@ importers: version: 18.19.67 '@typescript-eslint/eslint-plugin': specifier: ^7.4.0 - version: 7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.8.2) + version: 7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.8.3) '@typescript-eslint/parser': specifier: ^7.4.0 - version: 7.4.0(eslint@8.57.0)(typescript@5.8.2) + version: 7.4.0(eslint@8.57.0)(typescript@5.8.3) dotenv: specifier: ^16.0.3 version: 16.0.3 @@ -58,7 +58,7 @@ importers: version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5) jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) node-gyp: specifier: ^9.3.1 version: 9.3.1 @@ -75,8 +75,8 @@ importers: specifier: ^0.6.11 version: 0.6.11(prettier@3.2.5) typescript: - specifier: ^5.8.2 - version: 5.8.2 + specifier: ^5.8.3 + version: 5.8.3 packages/api: dependencies: @@ -110,7 +110,7 @@ importers: version: link:../lex-cli jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) prettier: specifier: ^3.2.5 version: 3.2.5 @@ -319,10 +319,10 @@ importers: version: 6.9.7 jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@22.15.27)(ts-node@10.8.2) ts-node: specifier: ^10.8.2 - version: 10.8.2(@types/node@18.19.67)(typescript@5.8.2) + version: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -377,10 +377,10 @@ importers: version: 5.1.1 jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@22.15.27)(ts-node@10.8.2) ts-node: specifier: ^10.8.2 - version: 10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2) + version: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -408,7 +408,7 @@ importers: devDependencies: jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -433,7 +433,7 @@ importers: devDependencies: jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -455,7 +455,7 @@ importers: version: link:../common jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -538,10 +538,10 @@ importers: devDependencies: '@swc/jest': specifier: ^0.2.24 - version: 0.2.24(@swc/core@1.11.21) + version: 0.2.24(@swc/core@1.11.29) jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -572,7 +572,7 @@ importers: version: 6.1.2 jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -784,7 +784,7 @@ importers: devDependencies: jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -939,7 +939,7 @@ importers: version: 15.2.3(rollup@4.34.9) '@rollup/plugin-swc': specifier: ^0.4.0 - version: 0.4.0(@swc/core@1.11.21)(rollup@4.34.9) + version: 0.4.0(@swc/core@1.11.29)(rollup@4.34.9) '@swc/helpers': specifier: ^0.5.15 version: 0.5.15 @@ -1141,7 +1141,7 @@ importers: version: link:../../syntax '@lingui/cli': specifier: ^5.2.0 - version: 5.2.0(typescript@5.8.2) + version: 5.2.0(typescript@5.8.3) '@lingui/core': specifier: ^5.2.0 version: 5.2.0(@lingui/babel-plugin-lingui-macro@5.2.0)(babel-plugin-macros@3.1.0) @@ -1153,7 +1153,7 @@ importers: version: 5.4.0(@lingui/core@5.2.0)(@swc/core@1.11.18) '@lingui/vite-plugin': specifier: ^5.2.0 - version: 5.2.0(typescript@5.8.2)(vite@6.2.0) + version: 5.2.0(typescript@5.8.3)(vite@6.2.0) '@radix-ui/react-dialog': specifier: ^1.1.6 version: 1.1.6(@types/react-dom@19.0.4)(@types/react@19.0.10)(react-dom@19.0.0)(react@19.0.0) @@ -1171,7 +1171,7 @@ importers: version: 4.1.3(vite@6.2.0) '@tanstack/react-form': specifier: ^1.3.0 - version: 1.3.0(react-dom@19.0.0)(react@19.0.0)(typescript@5.8.2) + version: 1.3.0(react-dom@19.0.0)(react@19.0.0)(typescript@5.8.3) '@tanstack/react-query': specifier: ^5.71.10 version: 5.71.10(react@19.0.0) @@ -1180,7 +1180,7 @@ importers: version: 1.115.0(react-dom@19.0.0)(react@19.0.0) '@tanstack/react-router-devtools': specifier: ^1.115.0 - version: 1.115.0(@tanstack/react-router@1.115.0)(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.0.0)(react@19.0.0)(tiny-invariant@1.3.3) + version: 1.115.0(@tanstack/react-router@1.115.0)(@tanstack/router-core@1.120.10)(csstype@3.1.3)(react-dom@19.0.0)(react@19.0.0)(tiny-invariant@1.3.3) '@tanstack/router-plugin': specifier: ^1.115.0 version: 1.115.0(@tanstack/react-router@1.115.0)(vite@6.2.0) @@ -1399,10 +1399,10 @@ importers: version: 6.9.7 jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@22.15.27)(ts-node@10.8.2) ts-node: specifier: ^10.8.2 - version: 10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2) + version: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -1577,13 +1577,13 @@ importers: version: 6.1.2 jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@22.15.27)(ts-node@10.8.2) puppeteer: specifier: ^23.5.2 version: 23.5.3(typescript@5.8.2) ts-node: specifier: ^10.8.2 - version: 10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2) + version: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -1623,7 +1623,7 @@ importers: devDependencies: jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -1663,7 +1663,7 @@ importers: version: 8.5.4 jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -1672,7 +1672,7 @@ importers: devDependencies: jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) typescript: specifier: ^5.6.3 version: 5.8.2 @@ -1746,7 +1746,7 @@ importers: version: 6.1.2 jest: specifier: ^28.1.2 - version: 28.1.2(@types/node@18.19.67)(ts-node@10.8.2) + version: 28.1.2(@types/node@18.19.67) jose: specifier: ^4.15.4 version: 4.15.4 @@ -5269,8 +5269,8 @@ packages: - pg-native - supports-color - /@emnapi/runtime@1.4.1: - resolution: {integrity: sha512-LMshMVP0ZhACNjQNYXiU1iZJ6QCcv0lUdPDPugqGvCGXt5xtRVBPdtA0qU12pEXZzpWAhWlZYptfdAFq10DOVQ==} + /@emnapi/runtime@1.4.3: + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} requiresBuild: true dependencies: tslib: 2.8.1 @@ -6047,7 +6047,7 @@ packages: cpu: [wasm32] requiresBuild: true dependencies: - '@emnapi/runtime': 1.4.1 + '@emnapi/runtime': 1.4.3 dev: false optional: true @@ -6444,6 +6444,27 @@ packages: - typescript dev: true + /@lingui/babel-plugin-lingui-macro@5.2.0(babel-plugin-macros@3.1.0)(typescript@5.8.3): + resolution: {integrity: sha512-IEpEfKW2WoGiK30dbovwXaPj69dKUP+GEAk00/6KUMB0sonaBWO4NO3Bj9G6NSdA6fB1lm9BtvuPqJQ2DvjF5g==} + engines: {node: '>=20.0.0'} + peerDependencies: + babel-plugin-macros: 2 || 3 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dependencies: + '@babel/core': 7.26.9 + '@babel/runtime': 7.22.10 + '@babel/types': 7.26.9 + '@lingui/conf': 5.2.0(typescript@5.8.3) + '@lingui/core': 5.2.0(@lingui/babel-plugin-lingui-macro@5.2.0)(babel-plugin-macros@3.1.0) + '@lingui/message-utils': 5.2.0 + babel-plugin-macros: 3.1.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@lingui/cli@5.2.0(typescript@5.8.2): resolution: {integrity: sha512-SLMPi9VMNAmhKRGt3HCGIZVHHmxfAcb7zNK9qwrEhlvcwxNmtsPtLb4iJvKy/VpdCQYm7C6D34tFjyVjUZ4ROg==} engines: {node: '>=20.0.0'} @@ -6483,6 +6504,45 @@ packages: - typescript dev: true + /@lingui/cli@5.2.0(typescript@5.8.3): + resolution: {integrity: sha512-SLMPi9VMNAmhKRGt3HCGIZVHHmxfAcb7zNK9qwrEhlvcwxNmtsPtLb4iJvKy/VpdCQYm7C6D34tFjyVjUZ4ROg==} + engines: {node: '>=20.0.0'} + hasBin: true + dependencies: + '@babel/core': 7.26.9 + '@babel/generator': 7.26.9 + '@babel/parser': 7.26.9 + '@babel/runtime': 7.22.10 + '@babel/types': 7.26.9 + '@lingui/babel-plugin-extract-messages': 5.2.0 + '@lingui/babel-plugin-lingui-macro': 5.2.0(babel-plugin-macros@3.1.0)(typescript@5.8.3) + '@lingui/conf': 5.2.0(typescript@5.8.3) + '@lingui/core': 5.2.0(@lingui/babel-plugin-lingui-macro@5.2.0)(babel-plugin-macros@3.1.0) + '@lingui/format-po': 5.2.0(typescript@5.8.3) + '@lingui/message-utils': 5.2.0 + babel-plugin-macros: 3.1.0 + chalk: 4.1.2 + chokidar: 3.5.1 + cli-table: 0.3.11 + commander: 10.0.1 + convert-source-map: 2.0.0 + date-fns: 3.6.0 + esbuild: 0.21.5 + glob: 11.0.1 + inquirer: 7.3.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + ora: 5.4.1 + pathe: 1.1.2 + pkg-up: 3.1.0 + pofile: 1.1.4 + pseudolocale: 2.1.0 + source-map: 0.8.0-beta.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@lingui/conf@5.2.0(typescript@5.8.2): resolution: {integrity: sha512-3biQJxGntCP+EnOe9jjlquGCBfk6ogq+I8ZduHwmBceY5aQ0OR7V23ItDrMz0NBy8dFNk5YoeHun3CYKYOS/Jg==} engines: {node: '>=20.0.0'} @@ -6497,6 +6557,20 @@ packages: - typescript dev: true + /@lingui/conf@5.2.0(typescript@5.8.3): + resolution: {integrity: sha512-3biQJxGntCP+EnOe9jjlquGCBfk6ogq+I8ZduHwmBceY5aQ0OR7V23ItDrMz0NBy8dFNk5YoeHun3CYKYOS/Jg==} + engines: {node: '>=20.0.0'} + dependencies: + '@babel/runtime': 7.22.10 + chalk: 4.1.2 + cosmiconfig: 8.3.6(typescript@5.8.3) + jest-validate: 29.7.0 + jiti: 1.21.0 + lodash.get: 4.4.2 + transitivePeerDependencies: + - typescript + dev: true + /@lingui/core@5.2.0(@lingui/babel-plugin-lingui-macro@5.2.0)(babel-plugin-macros@3.1.0): resolution: {integrity: sha512-cz35uKDxIGb/CPvgwn7BM/QYpxtARmQm7n+mHUoNJdNKSrg9R7vKkLRG7k9dukZwix2Mdjh+2dPIJnAkor2CiA==} engines: {node: '>=20.0.0'} @@ -6510,7 +6584,7 @@ packages: optional: true dependencies: '@babel/runtime': 7.22.10 - '@lingui/babel-plugin-lingui-macro': 5.2.0(babel-plugin-macros@3.1.0)(typescript@5.8.2) + '@lingui/babel-plugin-lingui-macro': 5.2.0(babel-plugin-macros@3.1.0)(typescript@5.8.3) '@lingui/message-utils': 5.2.0 babel-plugin-macros: 3.1.0 unraw: 3.0.0 @@ -6528,6 +6602,18 @@ packages: - typescript dev: true + /@lingui/format-po@5.2.0(typescript@5.8.3): + resolution: {integrity: sha512-viUQaoRa8UxSghayTY7xjtwXbfXIVdlM8C4HsxmozQnl5TXnPVEwlaPYds3sdJ8PmQGcYCm35r8EsmuKBoWYDQ==} + engines: {node: '>=20.0.0'} + dependencies: + '@lingui/conf': 5.2.0(typescript@5.8.3) + '@lingui/message-utils': 5.2.0 + date-fns: 3.6.0 + pofile: 1.1.4 + transitivePeerDependencies: + - typescript + dev: true + /@lingui/message-utils@5.2.0: resolution: {integrity: sha512-qJFKNc1b7SRX6y5ywtA1x+2/gaY22e09hjC6fiDvDpAFdEguI4qAJGmBmqlAZG/kcokR0tmMpo9zYUF8jjcHEA==} engines: {node: '>=20.0.0'} @@ -6586,6 +6672,20 @@ packages: - typescript dev: true + /@lingui/vite-plugin@5.2.0(typescript@5.8.3)(vite@6.2.0): + resolution: {integrity: sha512-jMpf6JJY1s3t4eFRBseTyuQNxy6ERRwg+uLi8EZ/qcaQgQW+GK6qWX/Qg5xQ8k1mJpaP6ihanMQMrkS6d5oR/A==} + engines: {node: '>=20.0.0'} + peerDependencies: + vite: ^3 || ^4 || ^5.0.9 || ^6 + dependencies: + '@lingui/cli': 5.2.0(typescript@5.8.3) + '@lingui/conf': 5.2.0(typescript@5.8.3) + vite: 6.2.0(@types/node@18.19.67) + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@manypkg/find-root@1.1.0: resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} dependencies: @@ -7261,7 +7361,7 @@ packages: resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} dev: true - /@remix-run/node@2.16.4(typescript@5.8.2): + /@remix-run/node@2.16.4(typescript@5.8.3): resolution: {integrity: sha512-scZwpc78cJS8dx6LMLemVHqnaAVbPWasx36TxYWUASA63hxPx5XbGbb6pe4cSpT8dWqhBtsPpHZoFTrM1aqx7A==} engines: {node: '>=18.0.0'} peerDependencies: @@ -7270,13 +7370,13 @@ packages: typescript: optional: true dependencies: - '@remix-run/server-runtime': 2.16.4(typescript@5.8.2) + '@remix-run/server-runtime': 2.16.4(typescript@5.8.3) '@remix-run/web-fetch': 4.4.2 '@web3-storage/multipart-parser': 1.0.0 cookie-signature: 1.2.2 source-map-support: 0.5.21 stream-slice: 0.1.2 - typescript: 5.8.2 + typescript: 5.8.3 undici: 6.19.8 dev: true @@ -7285,7 +7385,7 @@ packages: engines: {node: '>=14.0.0'} dev: true - /@remix-run/server-runtime@2.16.4(typescript@5.8.2): + /@remix-run/server-runtime@2.16.4(typescript@5.8.3): resolution: {integrity: sha512-6M1Lq2mrH5zfGN++ay+a2KzdPqOh2TB7n6wYPPXA0rxQan8c5NZCvDFF635KS65LSzZDB+2VfFTgoPBERdYkYg==} engines: {node: '>=18.0.0'} peerDependencies: @@ -7301,7 +7401,7 @@ packages: set-cookie-parser: 2.7.1 source-map: 0.7.4 turbo-stream: 2.4.0 - typescript: 5.8.2 + typescript: 5.8.3 dev: true /@remix-run/web-blob@3.1.0: @@ -7404,7 +7504,7 @@ packages: rollup: 4.34.9 dev: true - /@rollup/plugin-swc@0.4.0(@swc/core@1.11.21)(rollup@4.34.9): + /@rollup/plugin-swc@0.4.0(@swc/core@1.11.29)(rollup@4.34.9): resolution: {integrity: sha512-oAtqXa8rOl7BOK1Rz3rRxI+LIL53S9SqO2KSq2UUUzWgOgXg6492Jh5mL2mv/f9cpit8zFWdwILuVeozZ0C8mg==} engines: {node: '>=14.0.0'} peerDependencies: @@ -7415,7 +7515,7 @@ packages: optional: true dependencies: '@rollup/pluginutils': 5.1.0(rollup@4.34.9) - '@swc/core': 1.11.21(@swc/helpers@0.5.15) + '@swc/core': 1.11.29(@swc/helpers@0.5.15) rollup: 4.34.9 smob: 1.5.0 dev: true @@ -7635,8 +7735,8 @@ packages: dev: true optional: true - /@swc/core-darwin-arm64@1.11.21: - resolution: {integrity: sha512-v6gjw9YFWvKulCw3ZA1dY+LGMafYzJksm1mD4UZFZ9b36CyHFowYVYug1ajYRIRqEvvfIhHUNV660zTLoVFR8g==} + /@swc/core-darwin-arm64@1.11.29: + resolution: {integrity: sha512-whsCX7URzbuS5aET58c75Dloby3Gtj/ITk2vc4WW6pSDQKSPDuONsIcZ7B2ng8oz0K6ttbi4p3H/PNPQLJ4maQ==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] @@ -7653,8 +7753,8 @@ packages: dev: true optional: true - /@swc/core-darwin-x64@1.11.21: - resolution: {integrity: sha512-CUiTiqKlzskwswrx9Ve5NhNoab30L1/ScOfQwr1duvNlFvarC8fvQSgdtpw2Zh3MfnfNPpyLZnYg7ah4kbT9JQ==} + /@swc/core-darwin-x64@1.11.29: + resolution: {integrity: sha512-S3eTo/KYFk+76cWJRgX30hylN5XkSmjYtCBnM4jPLYn7L6zWYEPajsFLmruQEiTEDUg0gBEWLMNyUeghtswouw==} engines: {node: '>=10'} cpu: [x64] os: [darwin] @@ -7671,8 +7771,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm-gnueabihf@1.11.21: - resolution: {integrity: sha512-YyBTAFM/QPqt1PscD8hDmCLnqPGKmUZpqeE25HXY8OLjl2MUs8+O4KjwPZZ+OGxpdTbwuWFyMoxjcLy80JODvg==} + /@swc/core-linux-arm-gnueabihf@1.11.29: + resolution: {integrity: sha512-o9gdshbzkUMG6azldHdmKklcfrcMx+a23d/2qHQHPDLUPAN+Trd+sDQUYArK5Fcm7TlpG4sczz95ghN0DMkM7g==} engines: {node: '>=10'} cpu: [arm] os: [linux] @@ -7689,8 +7789,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm64-gnu@1.11.21: - resolution: {integrity: sha512-DQD+ooJmwpNsh4acrftdkuwl5LNxxg8U4+C/RJNDd7m5FP9Wo4c0URi5U0a9Vk/6sQNh9aSGcYChDpqCDWEcBw==} + /@swc/core-linux-arm64-gnu@1.11.29: + resolution: {integrity: sha512-sLoaciOgUKQF1KX9T6hPGzvhOQaJn+3DHy4LOHeXhQqvBgr+7QcZ+hl4uixPKTzxk6hy6Hb0QOvQEdBAAR1gXw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] @@ -7707,8 +7807,8 @@ packages: dev: true optional: true - /@swc/core-linux-arm64-musl@1.11.21: - resolution: {integrity: sha512-y1L49+snt1a1gLTYPY641slqy55QotPdtRK9Y6jMi4JBQyZwxC8swWYlQWb+MyILwxA614fi62SCNZNznB3XSA==} + /@swc/core-linux-arm64-musl@1.11.29: + resolution: {integrity: sha512-PwjB10BC0N+Ce7RU/L23eYch6lXFHz7r3NFavIcwDNa/AAqywfxyxh13OeRy+P0cg7NDpWEETWspXeI4Ek8otw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] @@ -7725,8 +7825,8 @@ packages: dev: true optional: true - /@swc/core-linux-x64-gnu@1.11.21: - resolution: {integrity: sha512-NesdBXv4CvVEaFUlqKj+GA4jJMNUzK2NtKOrUNEtTbXaVyNiXjFCSaDajMTedEB0jTAd9ybB0aBvwhgkJUWkWA==} + /@swc/core-linux-x64-gnu@1.11.29: + resolution: {integrity: sha512-i62vBVoPaVe9A3mc6gJG07n0/e7FVeAvdD9uzZTtGLiuIfVfIBta8EMquzvf+POLycSk79Z6lRhGPZPJPYiQaA==} engines: {node: '>=10'} cpu: [x64] os: [linux] @@ -7743,8 +7843,8 @@ packages: dev: true optional: true - /@swc/core-linux-x64-musl@1.11.21: - resolution: {integrity: sha512-qFV60pwpKVOdmX67wqQzgtSrUGWX9Cibnp1CXyqZ9Mmt8UyYGvmGu7p6PMbTyX7vdpVUvWVRf8DzrW2//wmVHg==} + /@swc/core-linux-x64-musl@1.11.29: + resolution: {integrity: sha512-YER0XU1xqFdK0hKkfSVX1YIyCvMDI7K07GIpefPvcfyNGs38AXKhb2byySDjbVxkdl4dycaxxhRyhQ2gKSlsFQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] @@ -7761,8 +7861,8 @@ packages: dev: true optional: true - /@swc/core-win32-arm64-msvc@1.11.21: - resolution: {integrity: sha512-DJJe9k6gXR/15ZZVLv1SKhXkFst8lYCeZRNHH99SlBodvu4slhh/MKQ6YCixINRhCwliHrpXPym8/5fOq8b7Ig==} + /@swc/core-win32-arm64-msvc@1.11.29: + resolution: {integrity: sha512-po+WHw+k9g6FAg5IJ+sMwtA/fIUL3zPQ4m/uJgONBATCVnDDkyW6dBA49uHNVtSEvjvhuD8DVWdFP847YTcITw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] @@ -7779,8 +7879,8 @@ packages: dev: true optional: true - /@swc/core-win32-ia32-msvc@1.11.21: - resolution: {integrity: sha512-TqEXuy6wedId7bMwLIr9byds+mKsaXVHctTN88R1UIBPwJA92Pdk0uxDgip0pEFzHB/ugU27g6d8cwUH3h2eIw==} + /@swc/core-win32-ia32-msvc@1.11.29: + resolution: {integrity: sha512-h+NjOrbqdRBYr5ItmStmQt6x3tnhqgwbj9YxdGPepbTDamFv7vFnhZR0YfB3jz3UKJ8H3uGJ65Zw1VsC+xpFkg==} engines: {node: '>=10'} cpu: [ia32] os: [win32] @@ -7797,8 +7897,8 @@ packages: dev: true optional: true - /@swc/core-win32-x64-msvc@1.11.21: - resolution: {integrity: sha512-BT9BNNbMxdpUM1PPAkYtviaV0A8QcXttjs2MDtOeSqqvSJaPtyM+Fof2/+xSwQDmDEFzbGCcn75M5+xy3lGqpA==} + /@swc/core-win32-x64-msvc@1.11.29: + resolution: {integrity: sha512-Q8cs2BDV9wqDvqobkXOYdC+pLUSEpX/KvI0Dgfun1F+LzuLotRFuDhrvkU9ETJA6OnD2+Fn/ieHgloiKA/Mn/g==} engines: {node: '>=10'} cpu: [x64] os: [win32] @@ -7831,8 +7931,8 @@ packages: '@swc/core-win32-x64-msvc': 1.11.18 dev: true - /@swc/core@1.11.21(@swc/helpers@0.5.15): - resolution: {integrity: sha512-/Y3BJLcwd40pExmdar8MH2UGGvCBrqNN7hauOMckrEX2Ivcbv3IMhrbGX4od1dnF880Ed8y/E9aStZCIQi0EGw==} + /@swc/core@1.11.29(@swc/helpers@0.5.15): + resolution: {integrity: sha512-g4mThMIpWbNhV8G2rWp5a5/Igv8/2UFRJx2yImrLGMgrDDYZIopqZ/z0jZxDgqNA1QDx93rpwNF7jGsxVWcMlA==} engines: {node: '>=10'} requiresBuild: true peerDependencies: @@ -7845,16 +7945,16 @@ packages: '@swc/helpers': 0.5.15 '@swc/types': 0.1.21 optionalDependencies: - '@swc/core-darwin-arm64': 1.11.21 - '@swc/core-darwin-x64': 1.11.21 - '@swc/core-linux-arm-gnueabihf': 1.11.21 - '@swc/core-linux-arm64-gnu': 1.11.21 - '@swc/core-linux-arm64-musl': 1.11.21 - '@swc/core-linux-x64-gnu': 1.11.21 - '@swc/core-linux-x64-musl': 1.11.21 - '@swc/core-win32-arm64-msvc': 1.11.21 - '@swc/core-win32-ia32-msvc': 1.11.21 - '@swc/core-win32-x64-msvc': 1.11.21 + '@swc/core-darwin-arm64': 1.11.29 + '@swc/core-darwin-x64': 1.11.29 + '@swc/core-linux-arm-gnueabihf': 1.11.29 + '@swc/core-linux-arm64-gnu': 1.11.29 + '@swc/core-linux-arm64-musl': 1.11.29 + '@swc/core-linux-x64-gnu': 1.11.29 + '@swc/core-linux-x64-musl': 1.11.29 + '@swc/core-win32-arm64-msvc': 1.11.29 + '@swc/core-win32-ia32-msvc': 1.11.29 + '@swc/core-win32-x64-msvc': 1.11.29 dev: true /@swc/counter@0.1.3: @@ -7878,14 +7978,14 @@ packages: jsonc-parser: 3.2.0 dev: true - /@swc/jest@0.2.24(@swc/core@1.11.21): + /@swc/jest@0.2.24(@swc/core@1.11.29): 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.11.21(@swc/helpers@0.5.15) + '@swc/core': 1.11.29(@swc/helpers@0.5.15) jsonc-parser: 3.2.0 dev: true @@ -8046,7 +8146,7 @@ packages: resolution: {integrity: sha512-/fKEY8fO1nbszfrBatzmhJa1nEwIKn0c6Tv2A1ocSA5OiD2GukOIV8nnBbvJRgZb/VIoBy9/N4PVbABI8YQLow==} dev: true - /@tanstack/react-form@1.3.0(react-dom@19.0.0)(react@19.0.0)(typescript@5.8.2): + /@tanstack/react-form@1.3.0(react-dom@19.0.0)(react@19.0.0)(typescript@5.8.3): resolution: {integrity: sha512-W5btaBUpSdP64ZNt3wdLcg3EZL05IWwX/ROOOdyAbV4AdNlCQ5f7fOhEtY4Y9gJSt5HdapVcGJlZFsBy+8Dd4Q==} peerDependencies: '@tanstack/react-start': ^1.112.0 @@ -8058,7 +8158,7 @@ packages: vinxi: optional: true dependencies: - '@remix-run/node': 2.16.4(typescript@5.8.2) + '@remix-run/node': 2.16.4(typescript@5.8.3) '@tanstack/form-core': 1.3.0 '@tanstack/react-store': 0.7.0(react-dom@19.0.0)(react@19.0.0) decode-formdata: 0.9.0 @@ -8077,7 +8177,7 @@ packages: react: 19.0.0 dev: true - /@tanstack/react-router-devtools@1.115.0(@tanstack/react-router@1.115.0)(@tanstack/router-core@1.115.3)(csstype@3.1.3)(react-dom@19.0.0)(react@19.0.0)(tiny-invariant@1.3.3): + /@tanstack/react-router-devtools@1.115.0(@tanstack/react-router@1.115.0)(@tanstack/router-core@1.120.10)(csstype@3.1.3)(react-dom@19.0.0)(react@19.0.0)(tiny-invariant@1.3.3): resolution: {integrity: sha512-lvKe0ecJOBTij5TO/O2yLL/UknHLA0aIm4uJYXydM5qW69WktXfOzC7aUaQHEMTQiU0bP1RditQXZqZhQuH+4w==} engines: {node: '>=12'} peerDependencies: @@ -8086,7 +8186,7 @@ packages: react-dom: '>=18.0.0 || >=19.0.0' dependencies: '@tanstack/react-router': 1.115.0(react-dom@19.0.0)(react@19.0.0) - '@tanstack/router-devtools-core': 1.115.0(@tanstack/router-core@1.115.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) + '@tanstack/router-devtools-core': 1.115.0(@tanstack/router-core@1.120.10)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3) react: 19.0.0 react-dom: 19.0.0(react@19.0.0) solid-js: 1.9.5 @@ -8134,16 +8234,16 @@ packages: tiny-invariant: 1.3.3 dev: true - /@tanstack/router-core@1.115.3: - resolution: {integrity: sha512-gynHs72LHVg05fuJTwZZYhDL4VNEAK0sXz7IqiBv7a3qsYeEmIZsGaFr9sVjTkuF1kbrFBdJd5JYutzBh9Uuhw==} + /@tanstack/router-core@1.120.10: + resolution: {integrity: sha512-AmEJAYt+6w/790zTnfddVhnK1QJCnd96H4xg1aD65Oohc8+OTQBxgWky/wzqwhHRdkdsBgRT7iWac9x5Y8UrQA==} engines: {node: '>=12'} dependencies: '@tanstack/history': 1.115.0 - '@tanstack/store': 0.7.0 + '@tanstack/store': 0.7.1 tiny-invariant: 1.3.3 dev: true - /@tanstack/router-devtools-core@1.115.0(@tanstack/router-core@1.115.3)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3): + /@tanstack/router-devtools-core@1.115.0(@tanstack/router-core@1.120.10)(csstype@3.1.3)(solid-js@1.9.5)(tiny-invariant@1.3.3): resolution: {integrity: sha512-s46V8bWxp4fWWjjDm7aGIYw8uPDXu8l1HkwGJwxkf1OQn1MdE7KRIVhGs/GM3Hp2KptQe4Gjomr7r1xrajuMhA==} engines: {node: '>=12'} peerDependencies: @@ -8155,7 +8255,7 @@ packages: csstype: optional: true dependencies: - '@tanstack/router-core': 1.115.3 + '@tanstack/router-core': 1.120.10 clsx: 2.1.1 csstype: 3.1.3 goober: 2.1.16(csstype@3.1.3) @@ -8237,6 +8337,10 @@ packages: resolution: {integrity: sha512-CNIhdoUsmD2NolYuaIs8VfWM467RK6oIBAW4nPEKZhg1smZ+/CwtCdpURgp7nxSqOaV9oKkzdWD80+bC66F/Jg==} dev: true + /@tanstack/store@0.7.1: + resolution: {integrity: sha512-PjUQKXEXhLYj2X5/6c1Xn/0/qKY0IVFxTJweopRfF26xfjVyb14yALydJrHupDh3/d+1WKmfEgZPBVCmDkzzwg==} + dev: true + /@tanstack/virtual-file-routes@1.115.0: resolution: {integrity: sha512-XLUh1Py3AftcERrxkxC5Y5m5mfllRH3YR6YVlyjFgI2Tc2Ssy2NKmQFQIafoxfW459UJ8Dn81nWKETEIJifE4g==} engines: {node: '>=12'} @@ -8480,6 +8584,12 @@ packages: dependencies: undici-types: 5.26.5 + /@types/node@22.15.27: + resolution: {integrity: sha512-5fF+eu5mwihV2BeVtX5vijhdaZOfkQTATrePEaXTcKqI16LhJ7gi2/Vhd9OZM0UojcdmiOCVg5rrax+i1MdoQQ==} + dependencies: + undici-types: 6.21.0 + dev: true + /@types/nodemailer@6.4.6: resolution: {integrity: sha512-pD6fL5GQtUKvD2WnPmg5bC2e8kWCAPDwMPmHe/ohQbW+Dy0EcHgZ2oCSuPlWNqk74LS5BVMig1SymQbFMPPK3w==} dependencies: @@ -8592,7 +8702,7 @@ packages: dev: true optional: true - /@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.8.2): + /@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.8.3): resolution: {integrity: sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -8604,10 +8714,10 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.8.2) + '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.8.3) '@typescript-eslint/scope-manager': 7.4.0 - '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.8.2) - '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.8.2) + '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.8.3) + '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.8.3) '@typescript-eslint/visitor-keys': 7.4.0 debug: 4.3.4 eslint: 8.57.0 @@ -8615,13 +8725,13 @@ packages: ignore: 5.2.4 natural-compare: 1.4.0 semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@5.8.2) - typescript: 5.8.2 + ts-api-utils: 1.0.3(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.8.2): + /@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.8.3): resolution: {integrity: sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -8633,11 +8743,11 @@ packages: dependencies: '@typescript-eslint/scope-manager': 7.4.0 '@typescript-eslint/types': 7.4.0 - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.8.2) + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 7.4.0 debug: 4.3.4 eslint: 8.57.0 - typescript: 5.8.2 + typescript: 5.8.3 transitivePeerDependencies: - supports-color dev: true @@ -8650,7 +8760,7 @@ packages: '@typescript-eslint/visitor-keys': 7.4.0 dev: true - /@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.8.2): + /@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.8.3): resolution: {integrity: sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -8660,12 +8770,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.8.2) - '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.8.2) + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.8.3) + '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.8.3) debug: 4.3.4 eslint: 8.57.0 - ts-api-utils: 1.0.3(typescript@5.8.2) - typescript: 5.8.2 + ts-api-utils: 1.0.3(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color dev: true @@ -8675,7 +8785,7 @@ packages: engines: {node: ^18.18.0 || >=20.0.0} dev: true - /@typescript-eslint/typescript-estree@7.4.0(typescript@5.8.2): + /@typescript-eslint/typescript-estree@7.4.0(typescript@5.8.3): resolution: {integrity: sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -8691,13 +8801,13 @@ packages: is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.5.4 - ts-api-utils: 1.0.3(typescript@5.8.2) - typescript: 5.8.2 + ts-api-utils: 1.0.3(typescript@5.8.3) + typescript: 5.8.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.8.2): + /@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.8.3): resolution: {integrity: sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: @@ -8708,7 +8818,7 @@ packages: '@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.8.2) + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.8.3) eslint: 8.57.0 semver: 7.5.4 transitivePeerDependencies: @@ -9977,6 +10087,22 @@ packages: typescript: 5.8.2 dev: true + /cosmiconfig@8.3.6(typescript@5.8.3): + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + typescript: 5.8.3 + dev: true + /cosmiconfig@9.0.0(typescript@5.8.2): resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} engines: {node: '>=14'} @@ -11226,7 +11352,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.8.2) + '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.8.3) debug: 3.2.7 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 @@ -11258,7 +11384,7 @@ packages: optional: true dependencies: '@rtsao/scc': 1.1.0 - '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.8.2) + '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.8.3) array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 @@ -13035,7 +13161,7 @@ packages: - supports-color dev: true - /jest-cli@28.1.3(@types/node@18.19.67)(ts-node@10.8.2): + /jest-cli@28.1.3(@types/node@18.19.67): resolution: {integrity: sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} hasBin: true @@ -13063,6 +13189,34 @@ packages: - ts-node dev: true + /jest-cli@28.1.3(@types/node@22.15.27)(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@22.15.27)(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 + /jest-config@28.1.3(@types/node@18.19.67)(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} @@ -13098,7 +13252,47 @@ packages: pretty-format: 28.1.3 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.8.2(@types/node@18.19.67)(typescript@5.8.2) + ts-node: 10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2) + transitivePeerDependencies: + - supports-color + dev: true + + /jest-config@28.1.3(@types/node@22.15.27)(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': 22.15.27 + 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.11.18)(@types/node@22.15.27)(typescript@5.8.2) transitivePeerDependencies: - supports-color dev: true @@ -13411,7 +13605,7 @@ packages: supports-color: 8.1.1 dev: true - /jest@28.1.2(@types/node@18.19.67)(ts-node@10.8.2): + /jest@28.1.2(@types/node@18.19.67): resolution: {integrity: sha512-Tuf05DwLeCh2cfWCQbcz9UxldoDyiR1E9Igaei5khjonKncYdc6LDfynKCEWozK0oLE3GD+xKAo2u8x/0s6GOg==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} hasBin: true @@ -13424,7 +13618,27 @@ packages: '@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.67)(ts-node@10.8.2) + jest-cli: 28.1.3(@types/node@18.19.67) + transitivePeerDependencies: + - '@types/node' + - supports-color + - ts-node + dev: true + + /jest@28.1.2(@types/node@22.15.27)(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: + '@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@22.15.27)(ts-node@10.8.2) transitivePeerDependencies: - '@types/node' - supports-color @@ -17195,13 +17409,13 @@ packages: engines: {node: '>=8'} dev: true - /ts-api-utils@1.0.3(typescript@5.8.2): + /ts-api-utils@1.0.3(typescript@5.8.3): resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} engines: {node: '>=16.13.0'} peerDependencies: typescript: '>=4.2.0' dependencies: - typescript: 5.8.2 + typescript: 5.8.3 dev: true /ts-interface-checker@0.1.13: @@ -17215,7 +17429,7 @@ packages: code-block-writer: 13.0.3 dev: false - /ts-node@10.8.2(@swc/core@1.11.18)(@types/node@18.19.67)(typescript@5.8.2): + /ts-node@10.8.2(@swc/core@1.11.18)(@types/node@22.15.27)(typescript@5.8.2): resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} hasBin: true peerDependencies: @@ -17235,38 +17449,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 18.19.67 - acorn: 8.10.0 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.8.2 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - - /ts-node@10.8.2(@types/node@18.19.67)(typescript@5.8.2): - resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 18.19.67 + '@types/node': 22.15.27 acorn: 8.10.0 acorn-walk: 8.2.0 arg: 4.1.3 @@ -17506,6 +17689,12 @@ packages: hasBin: true dev: true + /typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + dev: true + /ua-is-frozen@0.1.2: resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==} dev: true @@ -17557,6 +17746,10 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + /undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + dev: true + /undici@5.28.4: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'}