@formancy/data-server 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets-DhKHG-Y9.mjs","names":[],"sources":["../src/ed25519.ts","../src/identity-keys.ts","../src/identity.ts","../src/identity-file.ts","../src/secrets.ts"],"sourcesContent":["/**\n * What an Ed25519 public key's 32 bytes are, as far as a verifier has to ask\n * (0059): a point on the curve, and whether it is one of small order.\n *\n * createPublicKey imports every point of small order, and under such a key a\n * signature nobody made verifies: R the identity and S zero verified 64\n * messages of 64 under the identity, and 34 of 64 under the point of order 2\n * (Node 22.22.1, 2026-10-13). A key A is of small order exactly when [8]A,\n * its multiple by the curve's cofactor, is the identity, which is what this\n * computes from the curve itself: no list of points is transcribed.\n */\n\nconst P = 2n ** 255n - 19n\n\nfunction mod(value: bigint): bigint {\n const rest = value % P\n return rest < 0n ? rest + P : rest\n}\n\nfunction power(base: bigint, exponent: bigint): bigint {\n let result = 1n\n let square = mod(base)\n for (let rest = exponent; rest > 0n; rest >>= 1n) {\n if ((rest & 1n) === 1n) result = (result * square) % P\n square = (square * square) % P\n }\n return result\n}\n\n/** d = -121665/121666 (RFC 8032 section 5.1). */\nconst D = mod(-121665n * power(121666n, P - 2n))\n/** A square root of -1 modulo p (RFC 8032 section 5.1.3). */\nconst ROOT_OF_MINUS_ONE = power(2n, (P - 1n) / 4n)\n\n/**\n * The point an encoding names, or undefined when it names none (RFC 8032\n * section 5.1.3). y is read modulo p, as OpenSSL reads it, where the RFC\n * refuses y at or above p: such a key is what the server would verify with.\n */\nfunction decode(bytes: Uint8Array): { x: bigint; y: bigint } | undefined {\n let y = 0n\n for (let index = 31; index >= 0; index--) y = (y << 8n) | BigInt(index === 31 ? (bytes[index] as number) & 0x7f : (bytes[index] as number))\n y = mod(y)\n const sign = BigInt((bytes[31] as number) >> 7)\n const u = mod(y * y - 1n)\n const v = mod(D * y * y + 1n)\n let x = mod(u * power(v, 3n) * power(u * power(v, 7n), (P - 5n) / 8n))\n const check = mod(v * x * x)\n if (check === mod(-u)) x = mod(x * ROOT_OF_MINUS_ONE)\n else if (check !== u) return undefined\n if (x === 0n && sign === 1n) return undefined\n if ((x & 1n) !== sign) x = P - x\n return { x, y }\n}\n\n/** [8]A in projective coordinates, by doubling three times (a = -1; dbl-2008-hwcd), and whether it is the identity. */\nfunction eightTimesIsIdentity({ x, y }: { x: bigint; y: bigint }): boolean {\n let [X, Y, Z] = [x, y, 1n]\n for (let doubling = 0; doubling < 3; doubling++) {\n const A = mod(X * X)\n const B = mod(Y * Y)\n const C = mod(2n * Z * Z)\n const E = mod((X + Y) * (X + Y) - A - B)\n const G = mod(B - A)\n const F = mod(G - C)\n const H = mod(-A - B)\n ;[X, Y, Z] = [mod(E * F), mod(G * H), mod(F * G)]\n }\n return X === 0n && Y === Z\n}\n\nexport type Ed25519Point = 'no point' | 'small order' | 'a point'\n\n/** What the 32 bytes of an Ed25519 public key are: a JWK's `x`, once createPublicKey has imported it, which refuses any other length. */\nexport function ed25519Point(bytes: Uint8Array): Ed25519Point {\n const point = decode(bytes)\n if (point === undefined) return 'no point'\n return eightTimesIsIdentity(point) ? 'small order' : 'a point'\n}\n","import { createPublicKey, createSecretKey, KeyObject } from 'node:crypto'\nimport type { JsonWebKey } from 'node:crypto'\nimport { errors, exportJWK, importSPKI, jwtVerify } from 'jose'\nimport type { CryptoKey, JWTHeaderParameters, JWTVerifyOptions, JWTVerifyResult } from 'jose'\nimport { ed25519Point } from './ed25519.js'\n\n/**\n * The keys a host identity token is verified with (0014, 0059): one shared\n * secret, one public key, or a JSON Web Key Set read from a file, each of its\n * keys selected by the kid a token names.\n *\n * A `KeySet` is opaque. Its constructor refuses every caller but the\n * builders below, which check what they are given, so a set the verifier\n * meets is always one that `readKeySet`, `KeySet.fromSecret` or\n * `KeySet.fromPem` made: one algorithm, public keys or long secrets, every kid\n * distinct.\n *\n * Nothing a sentence here says comes from the file. JSON.parse's own message\n * quotes the text around the error, and the text may be a secret, so a file\n * that does not parse gets one fixed sentence; every other sentence names a\n * key by its kid, when that is in the alphabet below and could not be a\n * secret, or by its place in the file, and names a member, never its value.\n *\n * The class, its prototype and every set are frozen, and a set is verified\n * with only through `verifyWithKeySet`, which index.ts does not export: code\n * that reaches the class can replace none of its methods, and no caller can\n * verify under a set with looser options than identity.ts passes.\n */\n\nexport const KEY_SET_ALGORITHMS = ['RS256', 'ES256', 'EdDSA', 'HS256'] as const\nexport type KeySetAlgorithm = (typeof KEY_SET_ALGORITHMS)[number]\nexport type PublicKeyAlgorithm = Exclude<KeySetAlgorithm, 'HS256'>\n\nexport function isKeySetAlgorithm(value: unknown): value is KeySetAlgorithm {\n return KEY_SET_ALGORITHMS.some((algorithm) => algorithm === value)\n}\n\n/** 256 bits, the strength HS256 offers (0014). A shorter secret is a weaker key than the algorithm claims. */\nexport const MIN_SECRET_BYTES = 32\n\n/** jose's floor for RS256, which it checks only when a token arrives; a set is refused for it when it is read. */\nconst MIN_RSA_BITS = 2048\n\n/**\n * The least RSA public exponent a key in force may have: FIPS 186's floor for\n * an RSA key pair is an odd e above 2^16. createPublicKey imports e = 1, under\n * which a signature is its own message's padding and anyone signs any token.\n */\nconst MIN_RSA_EXPONENT = 65537n\n\n/** The member that left a key out of force. Never its value. */\nexport type LeftOutBecause = 'kty' | 'crv' | 'use' | 'key_ops' | 'alg'\n\n/** A key the file holds that cannot verify the pinned algorithm: by its kid, or by its place in the file (`#3`). */\nexport interface LeftOutKey {\n readonly key: string\n readonly because: LeftOutBecause\n}\n\n/** What changed between two sets, by kid; `replaced` is a kid whose key is not the same key. */\nexport interface KeySetChange {\n readonly added: readonly string[]\n readonly removed: readonly string[]\n readonly replaced: readonly string[]\n}\n\n/** A sentence saying why a set was refused. It holds no text of the file, so it may be logged. */\nexport class KeySetRefusal extends Error {\n override readonly name = 'KeySetRefusal'\n}\n\n/**\n * The alphabet of a kid that may be named in a sentence: base64 and base64url,\n * and the punctuation providers use. Anchored, one class, so nothing in it\n * backtracks.\n */\nconst KID = /^[A-Za-z0-9._~+/=-]+$/\n\n/** The key types of a public key. A kid beside one is a name, often a 43-character thumbprint, as Keycloak's are. */\nconst PUBLIC_KEY_TYPES: readonly unknown[] = ['RSA', 'EC', 'OKP']\n\n/** As many base64 characters as a secret HS256 takes: 32 bytes are 43 of them. */\nconst SECRET_CHARACTERS = Math.ceil((MIN_SECRET_BYTES * 8) / 6)\nconst BASE64_CHARACTER = /[A-Za-z0-9_+/-]/g\n\n/** Private key members (RFC 7518 section 6, and AKP's `priv`). Their presence in a verifier's file refuses the whole set. */\nconst PRIVATE_MEMBERS = ['d', 'p', 'q', 'dp', 'dq', 'qi', 'oth', 'priv'] as const\n\n/** What a key must be to verify each algorithm. */\nconst VERIFIES: Record<KeySetAlgorithm, { kty: string; crv?: string; kind: string }> = {\n RS256: { kty: 'RSA', kind: 'an RSA' },\n ES256: { kty: 'EC', crv: 'P-256', kind: 'an EC' },\n EdDSA: { kty: 'OKP', crv: 'Ed25519', kind: 'an Ed25519' },\n HS256: { kty: 'oct', kind: 'a secret' },\n}\n\n/** Only the builders hold this, so only they can call the constructor. */\nconst BUILD = Symbol('KeySet builder')\n\ninterface InForce {\n readonly kid: string\n readonly key: KeyObject\n}\n\ntype Entry = Record<string, unknown>\n\nfunction isEntry(value: unknown): value is Entry {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Whether a kid could be key material: one beside a key that is not a public\n * key, that is its own `k` or holds as many base64 characters as a secret.\n * Every kid fits the alphabet a secret is written in, and a kid is logged --\n * in a sentence, and on the planes and SIGHUP lines -- so a secret pasted\n * where the kid belongs would reach the log through it.\n */\nfunction kidCouldBeSecret(entry: Entry): boolean {\n const kid = entry['kid']\n if (typeof kid !== 'string' || PUBLIC_KEY_TYPES.includes(entry['kty'])) return false\n return kid === entry['k'] || (kid.match(BASE64_CHARACTER)?.length ?? 0) >= SECRET_CHARACTERS\n}\n\n/** A key as a sentence names it: `\"kid\"` when its kid is in the alphabet and could not be a secret, otherwise `#n`, its place in the file from 1. */\nfunction nameOf(entry: Entry, index: number): string {\n return typeof entry['kid'] === 'string' && KID.test(entry['kid']) && !kidCouldBeSecret(entry) ? entry['kid'] : `#${String(index + 1)}`\n}\n\nfunction quoted(name: string): string {\n return name.startsWith('#') ? name : `\"${name}\"`\n}\n\n/**\n * Why a key cannot verify `algorithm`, or why its publisher says it is not for\n * that, as the member that says so; undefined when it is in force. `use` is\n * asked before `alg`, because what a key is for is what its publisher says\n * first: Keycloak's encryption key says both.\n */\nfunction leftOutBecause(entry: Entry, algorithm: KeySetAlgorithm): LeftOutBecause | undefined {\n const wanted = VERIFIES[algorithm]\n if (entry['kty'] !== wanted.kty) return 'kty'\n if (wanted.crv !== undefined && entry['crv'] !== wanted.crv) return 'crv'\n if (entry['use'] !== undefined && entry['use'] !== 'sig') return 'use'\n const ops = entry['key_ops']\n if (ops !== undefined && !(Array.isArray(ops) && ops.includes('verify'))) return 'key_ops'\n if (entry['alg'] !== undefined && entry['alg'] !== algorithm) return 'alg'\n return undefined\n}\n\n/**\n * The key, imported now and synchronously, so a set that cannot verify is\n * refused when it is read rather than at its first token: an off-curve point\n * is refused by createPublicKey, where jose would refuse it only then, and an\n * RSA modulus is measured from the imported key, because createPublicKey\n * imports one of no bits at all (M4). A key under which anyone can sign --\n * an RSA exponent of 1, an Ed25519 point of small order -- imports too, and is\n * refused here.\n */\nfunction importKey(entry: Entry, algorithm: KeySetAlgorithm, kid: string): KeyObject {\n const name = quoted(kid)\n if (algorithm === 'HS256') {\n const k = entry['k']\n if (typeof k !== 'string') throw new KeySetRefusal(`key ${name} has no \"k\"`)\n // Node's decoder skips what is not base64url; a value that does not\n // encode back to itself is not the key its writer meant.\n const bytes = Buffer.from(k, 'base64url')\n if (bytes.toString('base64url') !== k) throw new KeySetRefusal(`key ${name}'s \"k\" is not base64url without padding`)\n if (bytes.length < MIN_SECRET_BYTES) throw new KeySetRefusal(`key ${name} is shorter than ${String(MIN_SECRET_BYTES)} bytes, which HS256 needs (0014)`)\n return createSecretKey(bytes)\n }\n let key: KeyObject\n try {\n key = createPublicKey({ key: entry as JsonWebKey, format: 'jwk' })\n } catch {\n throw new KeySetRefusal(`key ${name} does not import as ${VERIFIES[algorithm].kind} public key`)\n }\n if (algorithm === 'RS256') {\n const details = key.asymmetricKeyDetails\n if ((details?.modulusLength ?? 0) < MIN_RSA_BITS) throw new KeySetRefusal(`key ${name} is an RSA key of fewer than ${String(MIN_RSA_BITS)} bits, which RS256 refuses`)\n const exponent = details?.publicExponent ?? 0n\n if (exponent < MIN_RSA_EXPONENT || exponent % 2n === 0n) {\n throw new KeySetRefusal(`key ${name}'s RSA public exponent is not an odd number of at least ${String(MIN_RSA_EXPONENT)}: under an exponent of 1 anyone signs any token`)\n }\n }\n if (algorithm === 'EdDSA') {\n const point = ed25519Point(Buffer.from(String(entry['x']), 'base64url'))\n if (point === 'no point') throw new KeySetRefusal(`key ${name} does not import as ${VERIFIES[algorithm].kind} public key`)\n if (point === 'small order') throw new KeySetRefusal(`key ${name} is an Ed25519 point of small order, under which a signature nobody made verifies`)\n }\n return key\n}\n\nfunction signatureFailed(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED'\n}\n\n/** jwtVerify's options but the algorithm, which the set pins. */\nexport type KeySetVerifyOptions = Omit<JWTVerifyOptions, 'algorithms'>\n\n// Set by KeySet's static block, the only code that can read a set's private\n// fields, and reached through the functions below, which nothing outside\n// this module can replace.\nlet branded!: (value: unknown) => value is KeySet\nlet verifyUnder!: (set: KeySet, token: string, options: KeySetVerifyOptions) => Promise<JWTVerifyResult>\n\nexport class KeySet {\n readonly #algorithm: KeySetAlgorithm\n /** A set read from a file: its keys in force, in file order, each selected by its kid. */\n readonly #keys: readonly InForce[]\n readonly #leftOut: readonly LeftOutKey[]\n /** A one-key setting's key, which ignores a token's kid as it always has. */\n readonly #single: KeyObject | CryptoKey | undefined\n\n private constructor(build: symbol, algorithm: KeySetAlgorithm, keys: readonly InForce[], leftOut: readonly LeftOutKey[], single: KeyObject | CryptoKey | undefined) {\n if (build !== BUILD) throw new TypeError('a KeySet is made by readKeySet, KeySet.fromSecret or KeySet.fromPem')\n this.#algorithm = algorithm\n this.#keys = keys\n this.#leftOut = leftOut\n this.#single = single\n Object.freeze(this)\n }\n\n static {\n branded = (value): value is KeySet => typeof value === 'object' && value !== null && #keys in value\n verifyUnder = (set, token, options) => set.#verify(token, options)\n }\n\n /**\n * A JSON Web Key Set, `{\"keys\":[…]}`, read for one algorithm. Throws a\n * `KeySetRefusal` for an algorithm not one of the four, a file that does\n * not parse, an entry that is not an object, any key holding a private\n * member, a key in force without a kid in the alphabet, with another's kid\n * or with one that could be a secret, one that does not import, an RSA key\n * under 2048 bits or with an exponent under 65537 or even, an Ed25519 point\n * of small order, a secret under 32 bytes, and a file with no key in force.\n * A key that cannot verify the algorithm is left out and named.\n */\n static read(text: string, algorithm: KeySetAlgorithm): KeySet {\n // An embedder's algorithm, which nothing else has checked: VERIFIES has\n // no entry for \"hs256\", and one for \"toString\".\n if (!isKeySetAlgorithm(algorithm)) throw new KeySetRefusal('the algorithm of a key set is RS256, ES256, EdDSA or HS256')\n let document: unknown\n try {\n document = JSON.parse(text)\n } catch {\n throw new KeySetRefusal('the identity key set file is not JSON')\n }\n const entries = isEntry(document) ? document['keys'] : undefined\n if (!Array.isArray(entries) || entries.length === 0) throw new KeySetRefusal('the identity key set is not an object with a non-empty \"keys\" array')\n\n // Every key, in force or not, before any is classified: a verifier's file\n // holding private material is a key in the wrong place, whatever it is for.\n const checked: Entry[] = entries.map((entry: unknown, index) => {\n if (!isEntry(entry)) throw new KeySetRefusal(`key #${String(index + 1)} of the identity key set is not an object`)\n const member = PRIVATE_MEMBERS.find((candidate) => entry[candidate] !== undefined)\n if (member !== undefined) throw new KeySetRefusal(`key ${quoted(nameOf(entry, index))} holds the private member \"${member}\": a verifier's file holds public keys only`)\n return entry\n })\n\n const inForce: InForce[] = []\n const leftOut: LeftOutKey[] = []\n for (const [index, entry] of checked.entries()) {\n const because = leftOutBecause(entry, algorithm)\n if (because !== undefined) {\n leftOut.push({ key: nameOf(entry, index), because })\n continue\n }\n const kid = entry['kid']\n if (kid === undefined) throw new KeySetRefusal(`key #${String(index + 1)} has no \"kid\": a key in force is selected by it`)\n if (typeof kid !== 'string' || !KID.test(kid)) throw new KeySetRefusal(`key #${String(index + 1)}'s \"kid\" is not a non-empty string of A-Z, a-z, 0-9 and . _ ~ + / = -`)\n if (kidCouldBeSecret(entry)) {\n throw new KeySetRefusal(`key #${String(index + 1)}'s \"kid\" could be a secret -- it is its \"k\", or ${String(SECRET_CHARACTERS)} base64 characters or more -- and a kid is logged, so it is a name, never key material`)\n }\n if (inForce.some((other) => other.kid === kid)) throw new KeySetRefusal(`two keys in force are both \"${kid}\": a kid selects one key`)\n inForce.push({ kid, key: importKey(entry, algorithm, kid) })\n }\n if (inForce.length === 0) {\n const named = leftOut.map((key) => `${key.key} (${key.because})`).join(', ')\n throw new KeySetRefusal(`no key in the file can verify ${algorithm} (left out: ${named})`)\n }\n return new KeySet(BUILD, algorithm, inForce, leftOut, undefined)\n }\n\n /** `FORMANCY_DATA_IDENTITY_SECRET`'s key: HS256 over the secret's UTF-8 bytes, at least 32 of them. */\n static fromSecret(secret: string): KeySet {\n if (Buffer.byteLength(secret, 'utf8') < MIN_SECRET_BYTES) throw new KeySetRefusal(`the identity secret must be at least ${String(MIN_SECRET_BYTES)} bytes`)\n return new KeySet(BUILD, 'HS256', [], [], createSecretKey(Buffer.from(secret, 'utf8')))\n }\n\n /** `FORMANCY_DATA_IDENTITY_PUBLIC_KEY_FILE`'s key: an SPKI PEM, imported by jose as it always was, for RS256, ES256 or EdDSA and never HS256. */\n static async fromPem(pem: string, algorithm: string): Promise<KeySet> {\n if (algorithm !== 'RS256' && algorithm !== 'ES256' && algorithm !== 'EdDSA') {\n throw new KeySetRefusal('a public key verifies RS256, ES256 or EdDSA: HS256 is a shared secret, never a public key')\n }\n let key: CryptoKey\n try {\n key = await importSPKI(pem, algorithm)\n } catch {\n // jose's message is about its argument (\"spki\" must be...), not the setting.\n throw new KeySetRefusal(`the identity public key is not an SPKI PEM public key for ${algorithm}`)\n }\n return new KeySet(BUILD, algorithm, [], [], key)\n }\n\n /** Whether `value` is a set the builders made. A brand check, so neither a look-alike nor an object made from the prototype passes. */\n static is(value: unknown): value is KeySet {\n return branded(value)\n }\n\n get algorithm(): KeySetAlgorithm {\n return this.#algorithm\n }\n\n /** The kids in force, in file order; none for a one-key setting, which ignores kids. */\n get kids(): readonly string[] {\n return this.#keys.map((entry) => entry.kid)\n }\n\n get leftOut(): readonly LeftOutKey[] {\n return this.#leftOut\n }\n\n /** The type of each key in force, `public` or `secret`: what a test can ask of a key without being handed it. */\n get keyTypes(): readonly string[] {\n return this.#single === undefined ? this.#keys.map((entry) => entry.key.type) : [this.#single.type]\n }\n\n /** What `next` adds, removes and replaces of this set's kids. A key is replaced when its kid stays and its key does not. */\n compare(next: KeySet): KeySetChange {\n const before = new Map(this.#keys.map((entry) => [entry.kid, entry.key]))\n const after = new Map(next.#keys.map((entry) => [entry.kid, entry.key]))\n return {\n added: [...after.keys()].filter((kid) => !before.has(kid)),\n removed: [...before.keys()].filter((kid) => !after.has(kid)),\n replaced: [...after].filter(([kid, key]) => before.has(kid) && !(before.get(kid) as KeyObject).equals(key)).map(([kid]) => kid),\n }\n }\n\n /**\n * A one-key setting's key as a key set file holding it under `kid`: what\n * `identity-keys-cli.mjs convert` writes. The secret's key is the base64url\n * of the bytes this set verifies with, not the secret's text, which is also\n * valid base64url and another key (M2). Read back before it is returned.\n * This is key material; nothing logs it.\n */\n async document(kid: string): Promise<string> {\n const single = this.#single\n if (single === undefined) throw new KeySetRefusal('a set read from a file is a key set file already')\n if (!KID.test(kid)) throw new KeySetRefusal('a kid is a non-empty string of A-Z, a-z, 0-9 and . _ ~ + / = -')\n const { kty, ...material } = single instanceof KeyObject && single.type === 'secret' ? { kty: 'oct', k: single.export().toString('base64url') } : await exportJWK(single)\n const text = `${JSON.stringify({ keys: [{ kty, kid, alg: this.#algorithm, ...material }] }, null, 2)}\\n`\n KeySet.read(text, this.#algorithm)\n return text\n }\n\n /**\n * jwtVerify under this set, its algorithm pinned. A one-key setting's key is\n * the key, as it always was. A set's key is chosen inside jwtVerify by its\n * key resolver, which jose calls only after it has refused a malformed\n * token or another algorithm, so those keep their codes (M5):\n *\n * - a kid that is a string selects that key and no other, and one the set\n * does not hold is `ERR_JWKS_NO_MATCHING_KEY`, even when another key\n * would verify;\n * - no kid: each key in force in file order, moving on only past a\n * signature that does not verify, as PostgREST does; any other refusal\n * (expired, another audience, a missing claim) is the answer.\n *\n * No member of the token's header but its kid chooses a key: one that\n * carries a `jwk`, or names a `jku` or `x5u`, is verified against the set\n * alone (identity.test.ts).\n */\n async #verify(token: string, options: KeySetVerifyOptions): Promise<JWTVerifyResult> {\n const pinned: JWTVerifyOptions = { ...options, algorithms: [this.#algorithm] }\n if (this.#single !== undefined) return jwtVerify(token, this.#single, pinned)\n const keys = this.#keys\n let kidless = false\n const select = (header: JWTHeaderParameters): KeyObject => {\n const { kid } = header\n if (kid === undefined) {\n kidless = true\n return (keys[0] as InForce).key\n }\n if (typeof kid !== 'string') throw new errors.JWSInvalid('the token\\'s \"kid\" is not a string')\n const selected = keys.find((entry) => entry.kid === kid)\n if (selected === undefined) throw new errors.JWKSNoMatchingKey()\n return selected.key\n }\n try {\n return await jwtVerify(token, select, pinned)\n } catch (error) {\n if (!kidless || !signatureFailed(error)) throw error\n }\n for (const { key } of keys.slice(1)) {\n try {\n return await jwtVerify(token, key, pinned)\n } catch (error) {\n if (!signatureFailed(error)) throw error\n }\n }\n throw new errors.JWSSignatureVerificationFailed()\n }\n}\n\nObject.freeze(KeySet.prototype)\nObject.freeze(KeySet)\n\n/** The one function that checks a key set file's text; see `KeySet.read`. */\nexport function readKeySet(text: string, algorithm: KeySetAlgorithm): KeySet {\n return KeySet.read(text, algorithm)\n}\n\n/** Whether `value` is a set the builders made: identity.ts's check, which nothing outside this module can replace. */\nexport function isKeySet(value: unknown): value is KeySet {\n return branded(value)\n}\n\n/**\n * jwtVerify under a set the builders made, its algorithm pinned (see\n * `KeySet#verify`). identity.ts's one way to verify; index.ts does not export\n * it, so an embedder cannot call it with looser options than identity.ts's.\n */\nexport function verifyWithKeySet(set: KeySet, token: string, options: KeySetVerifyOptions): Promise<JWTVerifyResult> {\n return verifyUnder(set, token, options)\n}\n","import type { JWTPayload } from 'jose'\nimport { isKeySet, KeySet, verifyWithKeySet } from './identity-keys.js'\n\n/**\n * Who is asking, as the host application vouched for it.\n *\n * Built only from a token whose signature verified. Nothing in a request body,\n * query or header other than that token contributes to it — a tenant named in a\n * request is a claim by the browser, and the browser is the party policy exists\n * to constrain.\n */\nexport interface HostIdentity {\n actor: { id: string; roles: string[] }\n attributes: Record<string, string>\n}\n\nexport type IdentityOutcome = { ok: true; identity: HostIdentity } | { ok: false; reason: string }\n\nexport type IdentityVerifier = (token: string) => Promise<IdentityOutcome>\n\n/** A setting that names one key: a shared secret, or a public key's PEM. Its tokens' kid is ignored. */\nexport type OneKey = { kind: 'secret'; secret: string } | { kind: 'public-key'; pem: string; algorithm: 'RS256' | 'ES256' | 'EdDSA' }\n\nexport interface IdentityOptions {\n /**\n * How tokens are signed. A shared secret (HS256), a public key the host\n * signs against with its private one, or a key set read from a file and\n * read again on SIGHUP, whose `keys` is asked on every token. Never a URL,\n * and never a key or a URL a token names: an outbound request from inside\n * a customer's network, and a key the token chooses (0014, 0059).\n */\n key: OneKey | { kind: 'key-set'; keys: () => KeySet }\n issuer: string\n audience: string\n /** Attribute name to claim name, e.g. `{ tenant: 'tid' }`. Only these claims are read. */\n attributes: Readonly<Record<string, string>>\n /** The claim holding the actor's roles. Default `roles`. */\n rolesClaim?: string\n /** Seconds of clock skew tolerated on `exp` and `nbf`. Default 30. */\n clockToleranceSeconds?: number\n}\n\n/** Why a token is refused when the verifier is handed something that is not a set the builders made. */\nconst NOT_A_KEY_SET = 'the identity key set is not one readKeySet made'\n\n/**\n * The set a one-key setting verifies with: a secret's UTF-8 bytes, at least\n * 32 of them, or the PEM as jose imports it. What `identity-keys-cli.mjs\n * convert` writes is this set's key, so the two never differ.\n */\nexport async function oneKeySet(key: OneKey): Promise<KeySet> {\n return key.kind === 'secret' ? KeySet.fromSecret(key.secret) : KeySet.fromPem(key.pem, key.algorithm)\n}\n\n/**\n * A verifier for the host application's tokens, built once at startup.\n *\n * Fails at construction on a configuration that would accept forgeries — a\n * short secret, a key that does not parse, a key set no builder checked —\n * rather than on the first request. The algorithm is pinned per key kind, and\n * for a whole key set, so a token cannot choose `none` or swap an asymmetric\n * key for a shared one.\n *\n * A key set's `keys` is asked once per token, so a set read again replaces\n * the one in force between tokens, and a verification that has begun\n * finishes on the set it began with.\n */\nexport async function createIdentityVerifier(options: IdentityOptions): Promise<IdentityVerifier> {\n let keys: () => unknown\n if (options.key.kind === 'key-set') {\n keys = options.key.keys\n if (!isKeySet(keys())) throw new Error(NOT_A_KEY_SET)\n } else {\n const set = await oneKeySet(options.key)\n keys = () => set\n }\n\n const rolesClaim = options.rolesClaim ?? 'roles'\n const tolerance = options.clockToleranceSeconds ?? 30\n\n return async (token) => {\n let set: unknown\n try {\n set = keys()\n } catch {\n set = undefined\n }\n if (!isKeySet(set)) return { ok: false, reason: NOT_A_KEY_SET }\n\n let payload: JWTPayload\n try {\n // Never a method looked up on the set: its class is frozen, and its\n // verification is reached only through this module function (0059).\n ;({ payload } = await verifyWithKeySet(set, token, {\n issuer: options.issuer,\n audience: options.audience,\n clockTolerance: tolerance,\n requiredClaims: ['sub', 'exp'],\n }))\n } catch (error) {\n // The reason is jose's code, never the token: a token in a log is a\n // credential in a log.\n const code = typeof error === 'object' && error !== null && 'code' in error ? String(error.code) : 'ERR_JWT_INVALID'\n return { ok: false, reason: code }\n }\n\n const id = payload.sub\n if (typeof id !== 'string' || id === '') return { ok: false, reason: 'the token has no subject' }\n\n const roles = payload[rolesClaim] ?? []\n if (!Array.isArray(roles) || !roles.every((role) => typeof role === 'string')) {\n return { ok: false, reason: `the ${rolesClaim} claim is not a list of strings` }\n }\n\n const attributes: Record<string, string> = {}\n for (const [attribute, claim] of Object.entries(options.attributes)) {\n const value = payload[claim]\n if (value === undefined) continue\n // A tenant id is often a number in a token. A whole number is the same\n // tenant as its decimal string; anything else is not an identifier.\n if (typeof value === 'string') attributes[attribute] = value\n else if (typeof value === 'number' && Number.isSafeInteger(value)) attributes[attribute] = String(value)\n else return { ok: false, reason: `the ${claim} claim is not a string or a whole number` }\n }\n\n return { ok: true, identity: { actor: { id, roles: [...roles] }, attributes } }\n }\n}\n","import { constants } from 'node:fs'\nimport { open } from 'node:fs/promises'\nimport { KeySetRefusal, readKeySet } from './identity-keys.js'\nimport type { KeySet, KeySetAlgorithm, LeftOutKey } from './identity-keys.js'\n\n/**\n * What one read again of a key set file did, which the log line is built from\n * (identity-settings.ts). `problem` is readKeySet's sentence, so it holds no\n * text of the file.\n */\nexport type KeySetReadAgain =\n | { read: true; changed: true; kids: readonly string[]; added: readonly string[]; removed: readonly string[]; replaced: readonly string[]; leftOut: readonly LeftOutKey[] }\n | { read: true; changed: false; kids: readonly string[]; leftOut: readonly LeftOutKey[] }\n | { read: false; kids: readonly string[]; problem: string }\n\nexport interface KeySetFile {\n /** The set in force. The verifier asks for it on every token. */\n readonly current: () => KeySet\n /**\n * Reads the file again. A set readKeySet accepts replaces the one in force\n * at once; anything else leaves it as it was. Reads run one at a time, in\n * the order they were asked for, and none ever rejects.\n */\n readonly readAgain: () => Promise<KeySetReadAgain>\n}\n\n/**\n * A key set file's text, read the way a read always finishes: opened without\n * waiting for a writer, and refused unless it is a regular file. readFile on\n * a FIFO waited for a writer, and a read again that never finished held every\n * later one behind it, SIGTERM's shutdown too. A file on a network mount that\n * stalls still stalls the read: nothing here can tell a slow server from a\n * lost one.\n */\nexport async function readKeySetText(path: string): Promise<string> {\n const handle = await open(path, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0))\n try {\n if (!(await handle.stat()).isFile()) throw new KeySetRefusal(`the identity key set file ${path} is not a regular file`)\n return await handle.readFile('utf8')\n } finally {\n await handle.close()\n }\n}\n\nasync function readSet(path: string, algorithm: KeySetAlgorithm, read: (path: string) => Promise<string>): Promise<KeySet> {\n let text: string\n try {\n text = await read(path)\n } catch (error) {\n if (error instanceof KeySetRefusal) throw error\n // The path is configuration and is named, as resolveSecret names a secret file's; the error's own text is not passed on.\n throw new KeySetRefusal(`the identity key set file ${path} cannot be read`)\n }\n return readKeySet(text, algorithm)\n}\n\n/**\n * The key set in `path`, read now -- a refused set throws, which stops the\n * server at start -- and read again whenever `readAgain` is called: on SIGHUP\n * in the running server (main.ts), and by an embedder wherever it chooses.\n *\n * A read that fails keeps the set in force, a key removed from the file\n * included, because a typo or a half-written file must not refuse every\n * token; the outcome says so, and the same file then stops the next start\n * until it is fixed. `read` is injected for the tests; the server and the\n * tool read through readKeySetText.\n */\nexport async function openKeySetFile(path: string, algorithm: KeySetAlgorithm, read: (path: string) => Promise<string> = readKeySetText): Promise<KeySetFile> {\n let current = await readSet(path, algorithm, read)\n\n const readOnce = async (): Promise<KeySetReadAgain> => {\n let next: KeySet\n try {\n next = await readSet(path, algorithm, read)\n } catch (error) {\n return { read: false, kids: current.kids, problem: error instanceof KeySetRefusal ? error.message : 'the identity key set file could not be read as a key set' }\n }\n const { added, removed, replaced } = current.compare(next)\n current = next\n if (added.length === 0 && removed.length === 0 && replaced.length === 0) return { read: true, changed: false, kids: next.kids, leftOut: next.leftOut }\n return { read: true, changed: true, kids: next.kids, added, removed, replaced, leftOut: next.leftOut }\n }\n\n let queue: Promise<unknown> = Promise.resolve()\n return {\n current: () => current,\n readAgain: () => {\n const outcome = queue.then(readOnce)\n queue = outcome\n return outcome\n },\n }\n}\n","import { readFile } from 'node:fs/promises'\nimport { isAbsolute } from 'node:path'\n\n/**\n * Where a secret comes from: injected, so a test can supply an environment and\n * a file system without touching the process's own.\n */\nexport interface SecretSource {\n env: Readonly<Record<string, string | undefined>>\n readFile(path: string): Promise<string>\n}\n\nconst DEFAULT_SOURCE: SecretSource = {\n env: process.env,\n readFile: (path) => readFile(path, 'utf8'),\n}\n\nconst ENV_NAME = /^[A-Z_][A-Z0-9_]*$/\n\n/**\n * The value behind a secret reference.\n *\n * Configuration names a secret, never contains one (plan section 3: \"a\n * server-side secret reference\"). Two forms: `env:NAME`, read from the\n * environment, and `file:/absolute/path`, read from a file — which is how\n * Docker and Kubernetes hand secrets to a container.\n *\n * **No error message ever contains the reference's value**, only its form. The\n * commonest mistake is pasting the secret itself where its reference belongs,\n * and an error that echoed the input would print the secret into a log.\n */\nexport async function resolveSecret(reference: string, source: SecretSource = DEFAULT_SOURCE): Promise<string> {\n if (reference.startsWith('env:')) {\n const name = reference.slice(4)\n if (!ENV_NAME.test(name)) throw new Error('an env: secret reference names a variable in upper case, digits and underscores')\n const value = source.env[name]\n if (value === undefined || value === '') throw new Error(`the environment variable ${name} is unset or empty`)\n return value\n }\n\n if (reference.startsWith('file:')) {\n const path = reference.slice(5)\n if (!isAbsolute(path)) throw new Error('a file: secret reference needs an absolute path')\n let text: string\n try {\n text = await source.readFile(path)\n } catch {\n throw new Error(`the secret file ${path} cannot be read`)\n }\n // One trailing newline is how most tools write a file; a secret that\n // really ends in one is not something anybody types.\n const value = text.endsWith('\\r\\n') ? text.slice(0, -2) : text.endsWith('\\n') ? text.slice(0, -1) : text\n if (value === '') throw new Error(`the secret file ${path} is empty`)\n return value\n }\n\n throw new Error('a secret reference is env:NAME or file:/absolute/path. If you pasted the secret itself here, move it into one of those instead.')\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@formancy/data-server",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "The Formancy Data server: the configuration store, host identity and the HTTP surface the adapters sit behind. Fastify, like formancy's own server.",
5
+ "license": "SEE LICENSE IN LICENSE.md",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.mts",
10
+ "default": "./dist/index.mjs"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "LICENSE.md",
16
+ "NOTICE"
17
+ ],
18
+ "dependencies": {
19
+ "@fastify/rate-limit": "^11.2.0",
20
+ "@formancy/data-core": "0.1.0",
21
+ "@formancy/data-postgres": "0.1.0",
22
+ "@formancy/data-sqlserver": "0.1.0",
23
+ "@formancy/spec": "0.5.0",
24
+ "fastify": "^5.12.5",
25
+ "jose": "^6.2.12"
26
+ },
27
+ "devDependencies": {
28
+ "@arethetypeswrong/cli": "^0.18.5",
29
+ "@formancy/data-fixtures": "0.1.0",
30
+ "@types/node": "^22.10.0",
31
+ "@vitest/coverage-v8": "^5.0.1",
32
+ "publint": "^0.3.24",
33
+ "tsdown": "^0.23.0",
34
+ "typescript": "~6.0.3",
35
+ "unrun": "^0.3.1",
36
+ "vitest": "^5.0.1"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/sharkysan/formancy.ai-data.git",
44
+ "directory": "packages/data-server"
45
+ },
46
+ "homepage": "https://github.com/sharkysan/formancy.ai-data",
47
+ "bugs": "https://github.com/sharkysan/formancy.ai-data/issues",
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "test:coverage": "vitest run --coverage",
51
+ "test": "vitest run",
52
+ "typecheck": "tsc --noEmit",
53
+ "check:pkg": "publint && attw --pack . --profile esm-only"
54
+ }
6
55
  }