Projektstart

This commit is contained in:
2026-01-22 15:49:12 +01:00
parent 7212eb6f7a
commit 57e5f652f8
10637 changed files with 2598792 additions and 64 deletions

327
backend/node_modules/fast-jwt/src/crypto.js generated vendored Normal file
View File

@@ -0,0 +1,327 @@
'use strict'
const asn = require('asn1.js')
const {
createHmac,
createVerify,
createSign,
timingSafeEqual,
createPublicKey,
constants: {
RSA_PKCS1_PSS_PADDING,
RSA_PSS_SALTLEN_DIGEST,
RSA_PKCS1_PADDING,
RSA_PSS_SALTLEN_MAX_SIGN,
RSA_PSS_SALTLEN_AUTO
}
} = require('node:crypto')
const { sign: directSign, verify: directVerify } = require('node:crypto')
const { joseToDer, derToJose } = require('ecdsa-sig-formatter')
const Cache = require('mnemonist/lru-cache')
const { TokenError } = require('./error')
const base64UrlMatcher = /[=+/]/g
const encoderMap = { '=': '', '+': '-', '/': '_' }
const privateKeyPemMatcher = /^-----BEGIN(?: (RSA|EC|ENCRYPTED))? PRIVATE KEY-----/
const publicKeyPemMatcher = /^-----BEGIN(?: (RSA))? PUBLIC KEY-----/
const publicKeyX509CertMatcher = '-----BEGIN CERTIFICATE-----'
const privateKeysCache = new Cache(1000)
const publicKeysCache = new Cache(1000)
const hsAlgorithms = ['HS256', 'HS384', 'HS512']
const esAlgorithms = ['ES256', 'ES384', 'ES512']
const rsaAlgorithms = ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512']
const edAlgorithms = ['EdDSA']
const ecCurves = {
'1.2.840.10045.3.1.7': { bits: '256', names: ['P-256', 'prime256v1'] },
'1.3.132.0.10': { bits: '256', names: ['secp256k1'] },
'1.3.132.0.34': { bits: '384', names: ['P-384', 'secp384r1'] },
'1.3.132.0.35': { bits: '512', names: ['P-521', 'secp521r1'] }
}
const PrivateKey = asn.define('PrivateKey', function () {
this.seq().obj(
this.key('version').int(),
this.key('algorithm').seq().obj(this.key('algorithm').objid(), this.key('parameters').optional().objid())
)
})
const PublicKey = asn.define('PublicKey', function () {
this.seq().obj(
this.key('algorithm').seq().obj(this.key('algorithm').objid(), this.key('parameters').optional().objid())
)
})
const ECPrivateKey = asn.define('ECPrivateKey', function () {
this.seq().obj(
this.key('version').int(),
this.key('privateKey').octstr(),
this.key('parameters').explicit(0).optional().choice({ namedCurve: this.objid() })
)
})
function base64UrlReplacer(c) {
return encoderMap[c]
}
function cacheSet(cache, key, value, error) {
cache.set(key, [value, error])
return value || error
}
function performDetectPrivateKeyAlgorithm(key) {
if (key.match(publicKeyPemMatcher) || key.includes(publicKeyX509CertMatcher)) {
throw new TokenError(TokenError.codes.invalidKey, 'Public keys are not supported for signing.')
}
const pemData = key.trim().match(privateKeyPemMatcher)
if (!pemData) {
return 'HS256'
}
let keyData
let oid
let curveId
switch (pemData[1]) {
case 'RSA': // pkcs1 format - Can only be RSA key
return 'RS256'
case 'EC': // sec1 format - Can only be a EC key
keyData = ECPrivateKey.decode(key, 'pem', { label: 'EC PRIVATE KEY' })
curveId = keyData.parameters.value.join('.')
break
case 'ENCRYPTED': // Can be either RSA or EC key - we'll used the supplied algorithm
return 'ENCRYPTED'
default:
// pkcs8
keyData = PrivateKey.decode(key, 'pem', { label: 'PRIVATE KEY' })
oid = keyData.algorithm.algorithm.join('.')
switch (oid) {
case '1.2.840.113549.1.1.1': // RSA
return 'RS256'
case '1.2.840.10045.2.1': // EC
curveId = keyData.algorithm.parameters.join('.')
break
case '1.3.101.112': // Ed25519
case '1.3.101.113': // Ed448
return 'EdDSA'
default:
throw new TokenError(TokenError.codes.invalidKey, `Unsupported PEM PCKS8 private key with OID ${oid}.`)
}
}
const curve = ecCurves[curveId]
if (!curve) {
throw new TokenError(TokenError.codes.invalidKey, `Unsupported EC private key with curve ${curveId}.`)
}
return `ES${curve.bits}`
}
function performDetectPublicKeyAlgorithms(key) {
const publicKeyPemMatch = key.match(publicKeyPemMatcher)
if (key.match(privateKeyPemMatcher)) {
throw new TokenError(TokenError.codes.invalidKey, 'Private keys are not supported for verifying.')
} else if (publicKeyPemMatch && publicKeyPemMatch[1] === 'RSA') {
// pkcs1 format - Can only be RSA key
return rsaAlgorithms
} else if (!publicKeyPemMatch && !key.includes(publicKeyX509CertMatcher)) {
// Not a PEM, assume a plain secret
return hsAlgorithms
}
// if the key is a X509 cert we need to convert it
if (key.includes(publicKeyX509CertMatcher)) {
key = createPublicKey(key).export({ type: 'spki', format: 'pem' })
}
const keyData = PublicKey.decode(key, 'pem', { label: 'PUBLIC KEY' })
const oid = keyData.algorithm.algorithm.join('.')
let curveId
switch (oid) {
case '1.2.840.113549.1.1.1': // RSA
return rsaAlgorithms
case '1.2.840.10045.2.1': // EC
curveId = keyData.algorithm.parameters.join('.')
break
case '1.3.101.112': // Ed25519
case '1.3.101.113': // Ed448
return ['EdDSA']
default:
throw new TokenError(TokenError.codes.invalidKey, `Unsupported PEM PCKS8 public key with OID ${oid}.`)
}
const curve = ecCurves[curveId]
if (!curve) {
throw new TokenError(TokenError.codes.invalidKey, `Unsupported EC public key with curve ${curveId}.`)
}
return [`ES${curve.bits}`]
}
function detectPrivateKeyAlgorithm(key, providedAlgorithm) {
if (key instanceof Buffer) {
key = key.toString('utf-8')
} else if (typeof key !== 'string') {
throw new TokenError(TokenError.codes.invalidKey, 'The private key must be a string or a buffer.')
}
// Check cache first
const [cached, error] = privateKeysCache.get(key) || []
if (cached) {
return cached
} else if (error) {
throw error
}
// Try detecting
try {
const detectedAlgorithm = performDetectPrivateKeyAlgorithm(key)
if (detectedAlgorithm === 'ENCRYPTED') {
return cacheSet(privateKeysCache, key, providedAlgorithm)
}
return cacheSet(privateKeysCache, key, detectedAlgorithm)
} catch (e) {
throw cacheSet(
privateKeysCache,
key,
null,
TokenError.wrap(e, TokenError.codes.invalidKey, 'Unsupported PEM private key.')
)
}
}
function detectPublicKeyAlgorithms(key) {
if (!key) {
return 'none'
}
// Check cache first
const [cached, error] = publicKeysCache.get(key) || []
if (cached) {
return cached
} else if (error) {
throw error
}
// Try detecting
try {
if (key instanceof Buffer) {
key = key.toString('utf-8')
} else if (typeof key !== 'string') {
throw new TokenError(TokenError.codes.invalidKey, 'The public key must be a string or a buffer.')
}
return cacheSet(publicKeysCache, key, performDetectPublicKeyAlgorithms(key))
} catch (e) {
throw cacheSet(
publicKeysCache,
key,
null,
TokenError.wrap(e, TokenError.codes.invalidKey, 'Unsupported PEM public key.')
)
}
}
function createSignature(algorithm, key, input) {
try {
const type = algorithm.slice(0, 2)
const alg = `sha${algorithm.slice(2)}`
let raw
let options
switch (type) {
case 'HS':
raw = createHmac(alg, key).update(input).digest('base64')
break
case 'ES':
raw = derToJose(directSign(alg, Buffer.from(input, 'utf-8'), key), algorithm).toString('base64')
break
case 'RS':
case 'PS':
options = {
key,
padding: RSA_PKCS1_PADDING,
saltLength: RSA_PSS_SALTLEN_MAX_SIGN
}
if (type === 'PS') {
options.padding = RSA_PKCS1_PSS_PADDING
options.saltLength = RSA_PSS_SALTLEN_DIGEST
}
raw = createSign(alg).update(input).sign(options).toString('base64')
break
case 'Ed':
raw = directSign(undefined, Buffer.from(input, 'utf-8'), key).toString('base64')
}
return raw.replace(base64UrlMatcher, base64UrlReplacer)
} catch (e) {
/* istanbul ignore next */
throw new TokenError(TokenError.codes.signError, 'Cannot create the signature.', { originalError: e })
}
}
function verifySignature(algorithm, key, input, signature) {
try {
const type = algorithm.slice(0, 2)
const alg = `SHA${algorithm.slice(2)}`
signature = Buffer.from(signature, 'base64')
if (type === 'HS') {
try {
return timingSafeEqual(createHmac(alg, key).update(input).digest(), signature)
} catch {
return false
}
} else if (type === 'Ed') {
// Check if supported on Node 10
/* istanbul ignore next */
if (typeof directVerify === 'function') {
return directVerify(undefined, Buffer.from(input, 'utf-8'), key, signature)
} else {
throw new TokenError(TokenError.codes.signError, 'EdDSA algorithms are not supported by your Node.js version.')
}
}
const options = { key, padding: RSA_PKCS1_PADDING, saltLength: RSA_PSS_SALTLEN_AUTO }
if (type === 'PS') {
options.padding = RSA_PKCS1_PSS_PADDING
options.saltLength = RSA_PSS_SALTLEN_DIGEST
} else if (type === 'ES') {
signature = joseToDer(signature, algorithm)
}
return createVerify('RSA-' + alg)
.update(input)
.verify(options, signature)
} catch (e) {
/* istanbul ignore next */
throw new TokenError(TokenError.codes.verifyError, 'Cannot verify the signature.', { originalError: e })
}
}
module.exports = {
base64UrlMatcher,
base64UrlReplacer,
hsAlgorithms,
rsaAlgorithms,
esAlgorithms,
edAlgorithms,
detectPrivateKeyAlgorithm,
detectPublicKeyAlgorithms,
createSignature,
verifySignature
}

60
backend/node_modules/fast-jwt/src/decoder.js generated vendored Normal file
View File

@@ -0,0 +1,60 @@
'use strict'
const { TokenError } = require('./error')
function decode({ complete, checkTyp }, token) {
// Make sure the token is a string or a Buffer - Other cases make no sense to even try to validate
if (token instanceof Buffer) {
token = token.toString('utf-8')
} else if (typeof token !== 'string') {
throw new TokenError(TokenError.codes.invalidType, 'The token must be a string or a buffer.')
}
// Validate the format
const firstSeparator = token.indexOf('.')
const lastSeparator = token.lastIndexOf('.')
if (firstSeparator === -1 || firstSeparator >= lastSeparator) {
throw new TokenError(TokenError.codes.malformed, 'The token is malformed.')
}
// Parse header
let validHeader = false
try {
const header = JSON.parse(Buffer.from(token.slice(0, firstSeparator), 'base64').toString('utf-8'))
if (checkTyp && header.typ !== checkTyp) {
throw new TokenError(TokenError.codes.invalidType, `The type must be "${checkTyp}".`, { header })
}
validHeader = true
// Parse payload
let payload = Buffer.from(token.slice(firstSeparator + 1, lastSeparator), 'base64').toString('utf-8')
payload = JSON.parse(payload)
// https://tools.ietf.org/html/rfc7519#section-7.2
//
// 10. Verify that the resulting octet sequence is a UTF-8-encoded
// representation of a completely valid JSON object conforming to
// RFC 7159 [RFC7159]; let the JWT Claims Set be this JSON object.
if (!payload || typeof payload !== 'object') {
throw new TokenError(TokenError.codes.invalidPayload, 'The payload must be an object', { payload })
}
// Return whatever was requested
return complete
? { header, payload, signature: token.slice(lastSeparator + 1), input: token.slice(0, lastSeparator) }
: payload
} catch (e) {
throw TokenError.wrap(
e,
TokenError.codes.malformed,
`The token ${validHeader ? 'payload' : 'header'} is not a valid base64url serialized JSON.`
)
}
}
module.exports = function createDecoder(options = {}) {
const complete = options.complete || false
const checkTyp = options.checkTyp
return decode.bind(null, { complete, checkTyp })
}

51
backend/node_modules/fast-jwt/src/error.js generated vendored Normal file
View File

@@ -0,0 +1,51 @@
'use strict'
const TOKEN_ERROR_CODES = {
invalidType: 'FAST_JWT_INVALID_TYPE', // Invalid token type
invalidOption: 'FAST_JWT_INVALID_OPTION', // The option object is not valid
invalidAlgorithm: 'FAST_JWT_INVALID_ALGORITHM', // The token algorithm is invalid
invalidClaimType: 'FAST_JWT_INVALID_CLAIM_TYPE', // The claim type is not supported
invalidClaimValue: 'FAST_JWT_INVALID_CLAIM_VALUE', // The claim type is not a positive integer or an number array
invalidKey: 'FAST_JWT_INVALID_KEY', // The key is not a string or a buffer or is unsupported
invalidSignature: 'FAST_JWT_INVALID_SIGNATURE', // The token signature is invalid
invalidPayload: 'FAST_JWT_INVALID_PAYLOAD', // The payload to be decoded must be an object
malformed: 'FAST_JWT_MALFORMED', // The token is malformed
inactive: 'FAST_JWT_INACTIVE', // The token is not valid yet
expired: 'FAST_JWT_EXPIRED', // The token is expired
missingKey: 'FAST_JWT_MISSING_KEY', // The key option is missing
keyFetchingError: 'FAST_JWT_KEY_FETCHING_ERROR', // Could not retrieve the key
signError: 'FAST_JWT_SIGN_ERROR', // Cannot create the signature
verifyError: 'FAST_JWT_VERIFY_ERROR', // Cannot verify the signature
missingRequiredClaim: 'FAST_JWT_MISSING_REQUIRED_CLAIM', // A required claim is missing
missingSignature: 'FAST_JWT_MISSING_SIGNATURE' // The token signature is missing
}
class TokenError extends Error {
constructor(code, message, additional) {
super(message)
Error.captureStackTrace(this, this.constructor)
this.code = code
if (additional) {
for (const k in additional) {
this[k] = additional[k]
}
}
}
}
TokenError.codes = TOKEN_ERROR_CODES
TokenError.wrap = function (originalError, code, message) {
if (originalError instanceof TokenError) {
return originalError
}
return new TokenError(code, message, { originalError })
}
module.exports = {
TokenError,
TOKEN_ERROR_CODES
}

157
backend/node_modules/fast-jwt/src/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,157 @@
export type Algorithm =
| 'none'
| 'HS256'
| 'HS384'
| 'HS512'
| 'ES256'
| 'ES384'
| 'ES512'
| 'RS256'
| 'RS384'
| 'RS512'
| 'PS256'
| 'PS384'
| 'PS512'
| 'EdDSA'
export type TokenValidationErrorCode =
| 'FAST_JWT_INVALID_TYPE'
| 'FAST_JWT_INVALID_OPTION'
| 'FAST_JWT_INVALID_ALGORITHM'
| 'FAST_JWT_INVALID_CLAIM_TYPE'
| 'FAST_JWT_INVALID_CLAIM_VALUE'
| 'FAST_JWT_INVALID_KEY'
| 'FAST_JWT_INVALID_SIGNATURE'
| 'FAST_JWT_INVALID_PAYLOAD'
| 'FAST_JWT_MALFORMED'
| 'FAST_JWT_INACTIVE'
| 'FAST_JWT_EXPIRED'
| 'FAST_JWT_MISSING_KEY'
| 'FAST_JWT_KEY_FETCHING_ERROR'
| 'FAST_JWT_SIGN_ERROR'
| 'FAST_JWT_VERIFY_ERROR'
| 'FAST_JWT_MISSING_REQUIRED_CLAIM'
| 'FAST_JWT_MISSING_SIGNATURE'
declare class TokenError extends Error {
static wrap(originalError: Error, code: TokenValidationErrorCode, message: string): TokenError
static codes: {
invalidType: 'FAST_JWT_INVALID_TYPE'
invalidOption: 'FAST_JWT_INVALID_OPTION'
invalidAlgorithm: 'FAST_JWT_INVALID_ALGORITHM'
invalidClaimType: 'FAST_JWT_INVALID_CLAIM_TYPE'
invalidClaimValue: 'FAST_JWT_INVALID_CLAIM_VALUE'
invalidKey: 'FAST_JWT_INVALID_KEY'
invalidSignature: 'FAST_JWT_INVALID_SIGNATURE'
invalidPayload: 'FAST_JWT_INVALID_PAYLOAD'
malformed: 'FAST_JWT_MALFORMED'
inactive: 'FAST_JWT_INACTIVE'
expired: 'FAST_JWT_EXPIRED'
missingKey: 'FAST_JWT_MISSING_KEY'
keyFetchingError: 'FAST_JWT_KEY_FETCHING_ERROR'
signError: 'FAST_JWT_SIGN_ERROR'
verifyError: 'FAST_JWT_VERIFY_ERROR'
missingRequiredClaim: 'FAST_JWT_MISSING_REQUIRED_CLAIM'
missingSignature: 'FAST_JWT_MISSING_SIGNATURE'
}
code: TokenValidationErrorCode;
[key: string]: any
}
type SignerCallback = (e: Error | TokenError | null, token: string) => void
type VerifierCallback = (e: Error | TokenError | null, payload: any) => void
type DecodedJwt = {
header: Record<string, any>
payload: any
signature: string
input: string
}
type Bufferable = string | Buffer
type KeyFetcher =
| ((DecodedJwt: DecodedJwt) => Promise<Bufferable>)
| ((DecodedJwt: DecodedJwt, cb: (err: Error | TokenError | null, key: Bufferable) => void) => void)
type SignerPayload = Bufferable | Record<string, any>
declare function SignerSync<T = SignerPayload>(payload: T): string
declare function SignerAsync<T = SignerPayload>(payload: T): Promise<string>
declare function SignerAsync<T = SignerPayload>(payload: T, cb: SignerCallback): void
declare function VerifierSync<T = Bufferable>(token: T): any
declare function VerifierAsync<T = Bufferable>(token: T): Promise<any>
declare function VerifierAsync<T = Bufferable>(token: T, cb: VerifierCallback): void
export interface JwtHeader extends Record<string, any> {
alg: string | Algorithm
typ?: string
cty?: string
crit?: Array<string | Exclude<keyof JwtHeader, 'crit'>>
kid?: string
jku?: string
x5u?: string | string[]
'x5t#S256'?: string
x5t?: string
x5c?: string | string[]
}
export interface SignerOptions {
algorithm?: Algorithm
mutatePayload?: boolean
expiresIn?: number | string
notBefore?: number
jti?: string
aud?: string | string[]
iss?: string
sub?: string
nonce?: string
kid?: string
header?: JwtHeader
noTimestamp?: boolean
clockTimestamp?: number
}
export interface DecoderOptions {
complete?: boolean
checkTyp?: string
}
type VerifierAllowedBase = string | RegExp
type VerifierAllowed = VerifierAllowedBase | Array<VerifierAllowedBase>
export interface VerifierOptions {
algorithms?: Algorithm[]
complete?: boolean
cache?: boolean | number
cacheTTL?: number
errorCacheTTL?: number | ((tokenError: TokenError) => number)
allowedJti?: VerifierAllowed
allowedAud?: VerifierAllowed
allowedIss?: VerifierAllowed
allowedSub?: VerifierAllowed
allowedNonce?: VerifierAllowed
ignoreExpiration?: boolean
ignoreNotBefore?: boolean
maxAge?: number
clockTimestamp?: number
clockTolerance?: number
requiredClaims?: Array<string>
checkTyp?: string
cacheKeyBuilder?: (token: string) => string
}
export interface PrivateKey {
key: Bufferable
passphrase: string | undefined
}
export function createSigner<T = SignerPayload>(
options?: Partial<SignerOptions & { key: Bufferable | PrivateKey }>
): typeof SignerSync<T>
export function createSigner<T = SignerPayload>(options?: Partial<SignerOptions & { key: KeyFetcher }>): typeof SignerAsync<T>
export function createDecoder(options?: Partial<DecoderOptions>): (token: Bufferable) => any
export function createVerifier<T = Bufferable>(options?: Partial<VerifierOptions & { key: Bufferable }>): typeof VerifierSync<T>
export function createVerifier<T = Bufferable>(options?: Partial<VerifierOptions & { key: KeyFetcher }>): typeof VerifierAsync<T>

14
backend/node_modules/fast-jwt/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,14 @@
'use strict'
const { TokenError, TOKEN_ERROR_CODES } = require('./error')
const createDecoder = require('./decoder')
const createVerifier = require('./verifier')
const createSigner = require('./signer')
module.exports = {
TokenError,
TOKEN_ERROR_CODES,
createDecoder,
createVerifier,
createSigner
}

313
backend/node_modules/fast-jwt/src/signer.js generated vendored Normal file
View File

@@ -0,0 +1,313 @@
'use strict'
const {
base64UrlMatcher,
base64UrlReplacer,
hsAlgorithms,
esAlgorithms,
rsaAlgorithms,
edAlgorithms,
detectPrivateKeyAlgorithm,
createSignature
} = require('./crypto')
const { TokenError } = require('./error')
const { getAsyncKey, ensurePromiseCallback } = require('./utils')
const { createPrivateKey, createSecretKey } = require('node:crypto')
const { parse: parseMs } = require('@lukeed/ms')
const supportedAlgorithms = new Set([...hsAlgorithms, ...esAlgorithms, ...rsaAlgorithms, ...edAlgorithms, 'none'])
const supportedAlgorithmsList = Array.from(supportedAlgorithms).join(', ')
function checkIsCompatibleAlgorithm(expected, actual) {
const expectedType = expected.slice(0, 2)
const actualType = actual.slice(0, 2)
let valid = true // We accept everything for HS
// If the key is passphrase encrypted (actual === "ENCRYPTED") only RS and ES algos are supported
if (expectedType === 'RS' || expectedType === 'PS') {
// RS and PS use same keys
valid = actualType === 'RS' || (expectedType === 'RS' && actual === 'ENCRYPTED')
} else if (expectedType === 'ES' || expectedType === 'Ed') {
// ES and Ed must match
valid = expectedType === actualType || (expectedType === 'ES' && actual === 'ENCRYPTED')
}
if (!valid) {
throw new TokenError(TokenError.codes.invalidKey, `Invalid private key provided for algorithm ${expected}.`)
}
}
function prepareKeyOrSecret(key, algorithm) {
if (typeof key === 'string') {
key = Buffer.from(key, 'utf-8')
}
return algorithm[0] === 'H' ? createSecretKey(key) : createPrivateKey(key)
}
function sign(
{
key,
algorithm,
noTimestamp,
mutatePayload,
clockTimestamp,
expiresIn,
notBefore,
kid,
typ,
isAsync,
additionalHeader,
fixedPayload
},
payload,
cb
) {
const [callback, promise] = isAsync ? ensurePromiseCallback(cb) : []
// Validate payload
if (typeof payload !== 'object') {
throw new TokenError(TokenError.codes.invalidType, 'The payload must be an object.')
}
if (payload.exp && (!Number.isInteger(payload.exp) || payload.exp < 0)) {
throw new TokenError(TokenError.codes.invalidClaimValue, 'The exp claim must be a positive integer.')
}
// Prepare the header
const header = {
alg: algorithm,
typ: typ || 'JWT',
kid,
...additionalHeader
}
// Prepare the payload
let encodedPayload = ''
// Add claims
const iat = payload.iat * 1000 || clockTimestamp || Date.now()
const finalPayload = {
...payload,
...fixedPayload,
iat: noTimestamp ? undefined : Math.floor(iat / 1000),
exp: payload.exp ? payload.exp : expiresIn ? Math.floor((iat + expiresIn) / 1000) : undefined,
nbf: payload.nbf ? payload.nbf : notBefore ? Math.floor((iat + notBefore) / 1000) : undefined
}
if (mutatePayload) {
Object.assign(payload, finalPayload)
}
encodedPayload = Buffer.from(JSON.stringify(finalPayload), 'utf-8')
.toString('base64')
.replace(base64UrlMatcher, base64UrlReplacer)
// We have the key
if (!callback) {
const encodedHeader = Buffer.from(JSON.stringify(header), 'utf-8')
.toString('base64')
.replace(base64UrlMatcher, base64UrlReplacer)
const input = encodedHeader + '.' + encodedPayload
const signature = algorithm === 'none' ? '' : createSignature(algorithm, key, input)
return input + '.' + signature
}
// Get the key asynchronously
getAsyncKey(key, { header, payload }, (err, currentKey) => {
if (err) {
const error = TokenError.wrap(err, TokenError.codes.keyFetchingError, 'Cannot fetch key.')
return callback(error)
}
if (typeof currentKey === 'string') {
currentKey = Buffer.from(currentKey, 'utf-8')
} else if (!(currentKey instanceof Buffer)) {
return callback(
new TokenError(
TokenError.codes.keyFetchingError,
'The key returned from the callback must be a string or a buffer containing a secret or a private key.'
)
)
}
let token
try {
// Detect the private key - If the algorithm was known, just verify they match, otherwise assign it
const availableAlgorithm = detectPrivateKeyAlgorithm(currentKey, algorithm)
if (algorithm) {
checkIsCompatibleAlgorithm(algorithm, availableAlgorithm)
} else {
header.alg = algorithm = availableAlgorithm
}
currentKey = prepareKeyOrSecret(currentKey, algorithm)
const encodedHeader = Buffer.from(JSON.stringify(header), 'utf-8')
.toString('base64')
.replace(base64UrlMatcher, base64UrlReplacer)
const input = encodedHeader + '.' + encodedPayload
token = input + '.' + createSignature(algorithm, currentKey, input)
} catch (e) {
return callback(e)
}
callback(null, token)
})
return promise
}
module.exports = function createSigner(options) {
let {
key,
algorithm,
noTimestamp,
mutatePayload,
clockTimestamp,
expiresIn,
notBefore,
jti,
aud,
iss,
sub,
nonce,
kid,
typ,
header: additionalHeader
} = { clockTimestamp: 0, ...options }
// Validate options
if (algorithm && !supportedAlgorithms.has(algorithm)) {
throw new TokenError(
TokenError.codes.invalidOption,
`The algorithm option must be one of the following values: ${supportedAlgorithmsList}.`
)
}
const keyType = typeof key
const isKeyPasswordProtected = keyType === 'object' && key && key.key && key.passphrase
if (algorithm === 'none') {
if (key) {
throw new TokenError(
TokenError.codes.invalidOption,
'The key option must not be provided when the algorithm option is "none".'
)
}
} else if (
!key ||
(keyType !== 'string' && !(key instanceof Buffer) && keyType !== 'function' && !isKeyPasswordProtected)
) {
throw new TokenError(
TokenError.codes.invalidOption,
'The key option must be a string, a buffer, an object containing key/passphrase properties or a function returning the algorithm secret or private key.'
)
} else if (isKeyPasswordProtected && !algorithm) {
throw new TokenError(
TokenError.codes.invalidAlgorithm,
'When using password protected key you must provide the algorithm option.'
)
}
// Convert the key to a string when not a function, in order to be able to detect
if (key && keyType !== 'function') {
// Detect the private key - If the algorithm was known, just verify they match, otherwise assign it
const availableAlgorithm = detectPrivateKeyAlgorithm(isKeyPasswordProtected ? key.key : key, algorithm)
if (algorithm) {
checkIsCompatibleAlgorithm(algorithm, availableAlgorithm)
} else {
algorithm = availableAlgorithm
}
key = prepareKeyOrSecret(key, algorithm)
}
if (expiresIn) {
if (typeof expiresIn === 'string') {
expiresIn = parseMs(expiresIn)
}
if (typeof expiresIn !== 'number' || expiresIn < 0) {
throw new TokenError(
TokenError.codes.invalidOption,
'The expiresIn option must be a positive number or a valid string.'
)
}
}
if (notBefore) {
if (typeof notBefore === 'string') {
notBefore = parseMs(notBefore)
}
if (typeof notBefore !== 'number' || notBefore < 0) {
throw new TokenError(
TokenError.codes.invalidOption,
'The notBefore option must be a positive number or a valid string.'
)
}
}
if (clockTimestamp && (typeof clockTimestamp !== 'number' || clockTimestamp < 0)) {
throw new TokenError(TokenError.codes.invalidOption, 'The clockTimestamp option must be a positive number.')
}
if (jti && typeof jti !== 'string') {
throw new TokenError(TokenError.codes.invalidOption, 'The jti option must be a string.')
}
if (aud && typeof aud !== 'string' && !Array.isArray(aud)) {
throw new TokenError(TokenError.codes.invalidOption, 'The aud option must be a string or an array of strings.')
}
if (iss && typeof iss !== 'string') {
throw new TokenError(TokenError.codes.invalidOption, 'The iss option must be a string.')
}
if (sub && typeof sub !== 'string') {
throw new TokenError(TokenError.codes.invalidOption, 'The sub option must be a string.')
}
if (nonce && typeof nonce !== 'string') {
throw new TokenError(TokenError.codes.invalidOption, 'The nonce option must be a string.')
}
if (kid && typeof kid !== 'string') {
throw new TokenError(TokenError.codes.invalidOption, 'The kid option must be a string.')
}
if (additionalHeader && typeof additionalHeader !== 'object') {
throw new TokenError(TokenError.codes.invalidOption, 'The header option must be a object.')
}
const fpo = { jti, aud, iss, sub, nonce }
const fixedPayload = Object.entries(fpo).reduce((obj, [key, value]) => {
if (value !== undefined) {
obj[key] = value
}
return obj
}, {})
// Return the signer
return sign.bind(null, {
key,
algorithm,
noTimestamp,
mutatePayload,
clockTimestamp,
expiresIn,
notBefore,
kid,
typ,
isAsync: keyType === 'function',
additionalHeader,
fixedPayload
})
}

65
backend/node_modules/fast-jwt/src/utils.js generated vendored Normal file
View File

@@ -0,0 +1,65 @@
'use strict'
const { createHash } = require('node:crypto')
const algorithmMatcher = /"alg"\s*:\s*"[HERP]S(256|384)"/m
const edAlgorithmMatcher = /"alg"\s*:\s*"EdDSA"/m
const ed448CurveMatcher = /"crv"\s*:\s*"Ed448"/m
function getAsyncKey(handler, decoded, callback) {
const result = handler(decoded, callback)
if (result && typeof result.then === 'function') {
result
.then(key => {
// This avoids the callback to be thrown twice if callback throws
process.nextTick(() => callback(null, key))
})
.catch(callback)
}
}
function ensurePromiseCallback(callback) {
if (typeof callback === 'function') {
return [callback]
}
let promiseResolve, promiseReject
const promise = new Promise((resolve, reject) => {
promiseResolve = resolve
promiseReject = reject
})
return [
function (err, token) {
if (err) {
return promiseReject(err)
}
return promiseResolve(token)
},
promise
]
}
function hashToken(token) {
const rawHeader = token.split('.', 1)[0]
const header = Buffer.from(rawHeader, 'base64').toString('utf-8')
let hasher = null
/* istanbul ignore next */
if (header.match(edAlgorithmMatcher) && header.match(ed448CurveMatcher)) {
hasher = createHash('shake256', { outputLength: 114 })
} else {
const mo = header.match(algorithmMatcher)
hasher = createHash(`sha${mo ? mo[1] : '512'}`)
}
return hasher.update(token).digest('hex')
}
module.exports = {
getAsyncKey,
ensurePromiseCallback,
hashToken
}

524
backend/node_modules/fast-jwt/src/verifier.js generated vendored Normal file
View File

@@ -0,0 +1,524 @@
'use strict'
const { createPublicKey, createSecretKey } = require('node:crypto')
const Cache = require('mnemonist/lru-cache')
const { hsAlgorithms, verifySignature, detectPublicKeyAlgorithms } = require('./crypto')
const createDecoder = require('./decoder')
const { TokenError } = require('./error')
const { getAsyncKey, ensurePromiseCallback, hashToken } = require('./utils')
const defaultCacheSize = 1000
function exactStringClaimMatcher(allowed, actual) {
return allowed === actual
}
function checkAreCompatibleAlgorithms(expected, actual) {
for (const expectedAlg of expected) {
// if at least one of the expected algorithms is compatible we're done
if (actual.includes(expectedAlg)) {
return
}
}
throw new TokenError(
TokenError.codes.invalidKey,
`Invalid public key provided for algorithms ${expected.join(', ')}.`
)
}
function prepareKeyOrSecret(key, isSecret) {
if (typeof key === 'string') {
key = Buffer.from(key, 'utf-8')
}
return isSecret ? createSecretKey(key) : createPublicKey(key)
}
function ensureStringClaimMatcher(raw) {
if (!Array.isArray(raw)) {
raw = [raw]
}
return raw
.filter(r => r)
.map(r => {
if (r && typeof r.test === 'function') {
return r
}
return { test: exactStringClaimMatcher.bind(null, r) }
})
}
function createCache(rawSize) {
const size = parseInt(rawSize === true ? defaultCacheSize : rawSize, 10)
return size > 0 ? new Cache(size) : null
}
function cacheSet(
{
cache,
token,
cacheTTL,
payload,
ignoreExpiration,
ignoreNotBefore,
maxAge,
clockTimestamp = Date.now(),
clockTolerance,
errorCacheTTL,
cacheKeyBuilder
},
value
) {
if (!cache) {
return value
}
const cacheValue = [value, 0, 0]
if (value instanceof TokenError) {
const ttl = typeof errorCacheTTL === 'function' ? errorCacheTTL(value) : errorCacheTTL
cacheValue[2] = clockTimestamp + clockTolerance + ttl
cache.set(cacheKeyBuilder(token), cacheValue)
return value
}
const hasIat = payload && typeof payload.iat === 'number'
// Add time range of the token
if (hasIat) {
cacheValue[1] = !ignoreNotBefore && typeof payload.nbf === 'number' ? payload.nbf * 1000 - clockTolerance : 0
if (!ignoreExpiration) {
if (typeof payload.exp === 'number') {
cacheValue[2] = payload.exp * 1000 + clockTolerance
} else if (maxAge) {
cacheValue[2] = payload.iat * 1000 + maxAge + clockTolerance
}
}
}
// The maximum TTL for the token cannot exceed the configured cacheTTL
const maxTTL = clockTimestamp + clockTolerance + cacheTTL
cacheValue[2] = cacheValue[2] === 0 ? maxTTL : Math.min(cacheValue[2], maxTTL)
cache.set(cacheKeyBuilder(token), cacheValue)
return value
}
function handleCachedResult(cached, callback, promise) {
if (cached instanceof TokenError) {
if (!callback) {
throw cached
}
callback(cached)
} else {
if (!callback) {
return cached
}
callback(null, cached)
}
return promise
}
function validateAlgorithmAndSignature(input, header, signature, key, allowedAlgorithms) {
// According to the signature and key, check with algorithms are supported
// Verify the token is allowed
if (!allowedAlgorithms.includes(header.alg)) {
throw new TokenError(TokenError.codes.invalidAlgorithm, 'The token algorithm is invalid.')
}
// Verify the signature, if present
if (signature && !verifySignature(header.alg, key, input, signature)) {
throw new TokenError(TokenError.codes.invalidSignature, 'The token signature is invalid.')
}
}
function validateClaimType(values, claim, array, type) {
const typeFailureMessage = array
? `The ${claim} claim must be a ${type} or an array of ${type}s.`
: `The ${claim} claim must be a ${type}.`
if (values.map(v => typeof v).some(t => t !== type)) {
throw new TokenError(TokenError.codes.invalidClaimType, typeFailureMessage)
}
}
function validateClaimValues(values, claim, allowed, arrayValue) {
const failureMessage = arrayValue
? `Not all of the ${claim} claim values are allowed.`
: `The ${claim} claim value is not allowed.`
if (!values.every(v => allowed.some(a => a.test(v)))) {
throw new TokenError(TokenError.codes.invalidClaimValue, failureMessage)
}
}
function validateClaimArrayValues(values, claim, allowed, arrayValue) {
const failureMessage = arrayValue
? `None of ${claim} claim values are allowed.`
: `The ${claim} claim value is not allowed.`
if (!values.some(v => allowed.some(a => a.test(v)))) {
throw new TokenError(TokenError.codes.invalidClaimValue, failureMessage)
}
}
function validateClaimDateValue(value, modifier, now, greater, errorCode, errorVerb) {
const adjusted = value * 1000 + (modifier || 0)
const valid = greater ? now >= adjusted : now <= adjusted
if (!valid) {
throw new TokenError(TokenError.codes[errorCode], `The token ${errorVerb} at ${new Date(adjusted).toISOString()}.`)
}
}
function verifyToken(
key,
{ input, header, payload, signature },
{ validators, allowedAlgorithms, checkTyp, clockTimestamp, requiredClaims }
) {
// Verify the key
/* istanbul ignore next */
const hasKey = key instanceof Buffer ? key.length : !!key
if (hasKey && !signature) {
throw new TokenError(TokenError.codes.missingSignature, 'The token signature is missing.')
} else if (!hasKey && signature) {
throw new TokenError(TokenError.codes.missingKey, 'The key option is missing.')
}
validateAlgorithmAndSignature(input, header, signature, key, allowedAlgorithms)
// Verify typ
if (
checkTyp &&
(typeof header.typ !== 'string' || checkTyp !== header.typ.toLowerCase().replace(/^application\//, ''))
) {
throw new TokenError(TokenError.codes.invalidType, 'Invalid typ.')
}
if (requiredClaims) {
for (const claim of requiredClaims) {
if (!(claim in payload)) {
throw new TokenError(TokenError.codes.missingRequiredClaim, `The ${claim} claim is required.`)
}
}
}
// Verify the payload
const now = clockTimestamp || Date.now()
for (const { type, claim, allowed, array, modifier, greater, errorCode, errorVerb } of validators) {
const value = payload[claim]
const arrayValue = Array.isArray(value)
const values = arrayValue ? value : [value]
// We have already checked above that all required claims are present
// Therefore we can skip this validator if the claim is not present
if (!(claim in payload)) {
continue
}
// Validate type
validateClaimType(values, claim, array, type === 'date' ? 'number' : 'string')
if (type === 'date') {
validateClaimDateValue(value, modifier, now, greater, errorCode, errorVerb)
} else if (array) {
validateClaimArrayValues(values, claim, allowed, arrayValue)
} else {
validateClaimValues(values, claim, allowed, arrayValue)
}
}
}
function verify(
{
key,
allowedAlgorithms,
complete,
cacheTTL,
checkTyp,
clockTimestamp,
clockTolerance,
ignoreExpiration,
ignoreNotBefore,
maxAge,
isAsync,
validators,
decode,
cache,
requiredClaims,
errorCacheTTL,
cacheKeyBuilder
},
token,
cb
) {
const [callback, promise] = isAsync ? ensurePromiseCallback(cb) : []
// Check the cache
if (cache) {
const [value, min, max] = cache.get(cacheKeyBuilder(token)) || [undefined, 0, 0]
const now = clockTimestamp || Date.now()
// Validate time range
if (
/* istanbul ignore next */
typeof value !== 'undefined' &&
(min === 0 ||
(now < min && value.code === 'FAST_JWT_INACTIVE') ||
(now >= min && value.code !== 'FAST_JWT_INACTIVE')) &&
(max === 0 || now <= max)
) {
// Cache hit
return handleCachedResult(value, callback, promise)
}
}
/*
As very first thing, decode the token - If invalid, everything else is useless.
We don't involve cache here since it's much slower.
*/
let decoded
try {
decoded = decode(token)
} catch (e) {
if (callback) {
callback(e)
return promise
}
throw e
}
const { header, payload, signature, input } = decoded
const cacheContext = {
cache,
token,
cacheTTL,
errorCacheTTL,
ignoreExpiration,
ignoreNotBefore,
maxAge,
clockTimestamp,
clockTolerance,
payload,
cacheKeyBuilder
}
const validationContext = { validators, allowedAlgorithms, checkTyp, clockTimestamp, clockTolerance, requiredClaims }
// We have the key
if (!callback) {
try {
verifyToken(key, decoded, validationContext)
return cacheSet(cacheContext, complete ? { header, payload, signature, input } : payload)
} catch (e) {
throw cacheSet(cacheContext, e)
}
}
// Get the key asynchronously
getAsyncKey(key, { header, payload, signature }, (err, currentKey) => {
if (err) {
return callback(
cacheSet(cacheContext, TokenError.wrap(err, TokenError.codes.keyFetchingError, 'Cannot fetch key.'))
)
}
if (typeof currentKey === 'string') {
currentKey = Buffer.from(currentKey, 'utf-8')
} else if (!(currentKey instanceof Buffer)) {
return callback(
cacheSet(
cacheContext,
new TokenError(
TokenError.codes.keyFetchingError,
'The key returned from the callback must be a string or a buffer containing a secret or a public key.'
)
)
)
}
try {
// Detect the private key - If the algorithms were known, just verify they match, otherwise assign them
const availableAlgorithms = detectPublicKeyAlgorithms(currentKey)
if (validationContext.allowedAlgorithms.length) {
checkAreCompatibleAlgorithms(allowedAlgorithms, availableAlgorithms)
} else {
validationContext.allowedAlgorithms = availableAlgorithms
}
currentKey = prepareKeyOrSecret(currentKey, availableAlgorithms[0] === hsAlgorithms[0])
verifyToken(currentKey, decoded, validationContext)
} catch (e) {
return callback(cacheSet(cacheContext, e))
}
callback(null, cacheSet(cacheContext, complete ? { header, payload, signature, input: token } : payload))
})
return promise
}
module.exports = function createVerifier(options) {
let {
key,
algorithms: allowedAlgorithms,
complete,
cache: cacheSize,
cacheTTL,
errorCacheTTL,
checkTyp,
clockTimestamp,
clockTolerance,
ignoreExpiration,
ignoreNotBefore,
maxAge,
allowedJti,
allowedAud,
allowedIss,
allowedSub,
allowedNonce,
requiredClaims,
cacheKeyBuilder
} = { cacheTTL: 600_000, clockTolerance: 0, errorCacheTTL: -1, cacheKeyBuilder: hashToken, ...options }
// Validate options
if (!Array.isArray(allowedAlgorithms)) {
allowedAlgorithms = []
}
const keyType = typeof key
if (keyType !== 'string' && keyType !== 'object' && keyType !== 'function') {
throw new TokenError(
TokenError.codes.INVALID_OPTION,
'The key option must be a string, a buffer or a function returning the algorithm secret or public key.'
)
}
if (key && keyType !== 'function') {
// Detect the private key - If the algorithms were known, just verify they match, otherwise assign them
const availableAlgorithms = detectPublicKeyAlgorithms(key)
if (allowedAlgorithms.length) {
checkAreCompatibleAlgorithms(allowedAlgorithms, availableAlgorithms)
} else {
allowedAlgorithms = availableAlgorithms
}
key = prepareKeyOrSecret(key, availableAlgorithms[0] === hsAlgorithms[0])
}
if (clockTimestamp && (typeof clockTimestamp !== 'number' || clockTimestamp < 0)) {
throw new TokenError(TokenError.codes.invalidOption, 'The clockTimestamp option must be a positive number.')
}
if (clockTolerance && (typeof clockTolerance !== 'number' || clockTolerance < 0)) {
throw new TokenError(TokenError.codes.invalidOption, 'The clockTolerance option must be a positive number.')
}
if (cacheTTL && (typeof cacheTTL !== 'number' || cacheTTL < 0)) {
throw new TokenError(TokenError.codes.invalidOption, 'The cacheTTL option must be a positive number.')
}
if (
(errorCacheTTL && typeof errorCacheTTL !== 'function' && typeof errorCacheTTL !== 'number') ||
errorCacheTTL < -1
) {
throw new TokenError(
TokenError.codes.invalidOption,
'The errorCacheTTL option must be a number greater than -1 or a function.'
)
}
if (requiredClaims && !Array.isArray(requiredClaims)) {
throw new TokenError(TokenError.codes.invalidOption, 'The requiredClaims option must be an array.')
}
// Add validators
const validators = []
if (!ignoreNotBefore) {
validators.push({
type: 'date',
claim: 'nbf',
errorCode: 'inactive',
errorVerb: 'will be active',
greater: true,
modifier: -clockTolerance
})
}
if (!ignoreExpiration) {
validators.push({
type: 'date',
claim: 'exp',
errorCode: 'expired',
errorVerb: 'has expired',
modifier: +clockTolerance
})
}
if (typeof maxAge === 'number') {
validators.push({ type: 'date', claim: 'iat', errorCode: 'expired', errorVerb: 'has expired', modifier: maxAge })
}
if (allowedJti) {
validators.push({ type: 'string', claim: 'jti', allowed: ensureStringClaimMatcher(allowedJti) })
}
if (allowedAud) {
validators.push({ type: 'string', claim: 'aud', allowed: ensureStringClaimMatcher(allowedAud), array: true })
}
if (allowedIss) {
validators.push({ type: 'string', claim: 'iss', allowed: ensureStringClaimMatcher(allowedIss) })
}
if (allowedSub) {
validators.push({ type: 'string', claim: 'sub', allowed: ensureStringClaimMatcher(allowedSub) })
}
if (allowedNonce) {
validators.push({ type: 'string', claim: 'nonce', allowed: ensureStringClaimMatcher(allowedNonce) })
}
const normalizedTyp = checkTyp ? checkTyp.toLowerCase().replace(/^application\//, '') : null
const context = {
key,
allowedAlgorithms,
complete,
cacheTTL,
errorCacheTTL,
checkTyp: normalizedTyp,
clockTimestamp,
clockTolerance,
ignoreExpiration,
ignoreNotBefore,
maxAge,
isAsync: keyType === 'function',
validators,
decode: createDecoder({ complete: true }),
cache: createCache(cacheSize),
requiredClaims,
cacheKeyBuilder
}
// Return the verifier
const verifier = verify.bind(null, context)
verifier.cache = context.cache
return verifier
}