@nexartis/sentinel-sdk 1.9.2 → 1.10.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.
- package/README.md +32 -8
- package/dist/core/auth-utils.d.ts.map +1 -1
- package/dist/core/auth-utils.js +23 -3
- package/dist/core/auth-utils.js.map +1 -1
- package/dist/core/client.d.ts +4 -0
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +6 -0
- package/dist/core/client.js.map +1 -1
- package/dist/core/consumer-version-floor.d.ts +56 -0
- package/dist/core/consumer-version-floor.d.ts.map +1 -0
- package/dist/core/consumer-version-floor.js +233 -0
- package/dist/core/consumer-version-floor.js.map +1 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/jwks.d.ts +8 -1
- package/dist/core/jwks.d.ts.map +1 -1
- package/dist/core/jwks.js +28 -4
- package/dist/core/jwks.js.map +1 -1
- package/dist/core/types.d.ts +35 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/sveltekit/cookies.d.ts +10 -0
- package/dist/sveltekit/cookies.d.ts.map +1 -1
- package/dist/sveltekit/cookies.js +13 -0
- package/dist/sveltekit/cookies.js.map +1 -1
- package/dist/sveltekit/handle.js +8 -2
- package/dist/sveltekit/handle.js.map +1 -1
- package/dist/sveltekit/handlers/index.d.ts.map +1 -1
- package/dist/sveltekit/handlers/index.js +30 -5
- package/dist/sveltekit/handlers/index.js.map +1 -1
- package/dist/sveltekit/pages/index.d.ts.map +1 -1
- package/dist/sveltekit/pages/index.js +21 -10
- package/dist/sveltekit/pages/index.js.map +1 -1
- package/package.json +8 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumer-version-floor.js","sourceRoot":"","sources":["../../src/core/consumer-version-floor.ts"],"names":[],"mappings":"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","sourcesContent":["// src/core/consumer-version-floor.ts\n//\n// Fleet consumer version-floor gate for @nexartis/sentinel-sdk.\n//\n// 1.8.0 is the first line that verifies every access-token signature against\n// the Sentinel JWKS on each request (the admin-gate audit fix). A consumer\n// pinned below that floor trusts unverified token payloads, so the fleet must\n// fail closed on any in-scope repo that resolves below it.\n//\n// This module is pure and dependency-free so the same logic backs the unit\n// tests, the `scripts/check-consumer-version-floor.mjs` CLI gate, and CI.\n\nexport const SENTINEL_SDK_PACKAGE = '@nexartis/sentinel-sdk';\n\n/** First sentinel-sdk line that verifies signatures against the JWKS. */\nexport const SENTINEL_SDK_VERSION_FLOOR = '1.8.0';\n\nexport type PinKind = 'exact' | 'range' | 'tarball' | 'alias' | 'unresolved';\n\nexport interface ParsedPin {\n\tkind: PinKind;\n\t/** Lower-bound version the specifier guarantees, or null when unknown. */\n\tversion: string | null;\n\tspec: string;\n\treason: string;\n}\n\nexport type FloorStatus = 'ok' | 'below-floor' | 'unresolved' | 'absent';\n\nexport interface FloorVerdict {\n\tstatus: FloorStatus;\n\t/** Version the specifier is guaranteed to resolve at or above, when known. */\n\tpinnedVersion: string | null;\n\tspec: string | null;\n\treason: string;\n}\n\nexport interface ConsumerEntry {\n\trepo: string;\n\t/** The declared @nexartis/sentinel-sdk specifier, or null when not declared. */\n\tdependencySpec?: string | null;\n}\n\nexport interface ConsumerVerdict extends FloorVerdict {\n\trepo: string;\n}\n\nexport interface FloorReport {\n\tfloor: string;\n\tconsumers: ConsumerVerdict[];\n\t/** Repos that resolve below the floor — the hard gate failures. */\n\tviolations: string[];\n\t/** Repos whose specifier cannot prove a floor (strict mode fails these). */\n\tunresolved: string[];\n\t/** True when no consumer resolves below the floor. */\n\tok: boolean;\n\t/** True when no consumer is below the floor AND none is unresolved. */\n\tstrictOk: boolean;\n}\n\ninterface Semver {\n\tmajor: number;\n\tminor: number;\n\tpatch: number;\n\tprerelease: string | null;\n}\n\nconst SEMVER_RE = /^v?(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:-([0-9A-Za-z.-]+))?(?:\\+[0-9A-Za-z.-]+)?$/;\n\nfunction toSemver(token: string): Semver | null {\n\tconst match = SEMVER_RE.exec(token.trim());\n\tif (!match) return null;\n\treturn {\n\t\tmajor: Number(match[1]),\n\t\tminor: Number(match[2] ?? 0),\n\t\tpatch: Number(match[3] ?? 0),\n\t\tprerelease: match[4] ?? null\n\t};\n}\n\n/** Normalise a loose version token (`v1.9`, `1.9.2+build`) to `major.minor.patch[-pre]`. */\nfunction normalizeVersion(token: string): string | null {\n\tconst parsed = toSemver(token);\n\tif (!parsed) return null;\n\tconst base = `${parsed.major}.${parsed.minor}.${parsed.patch}`;\n\treturn parsed.prerelease ? `${base}-${parsed.prerelease}` : base;\n}\n\nfunction comparePrerelease(a: string, b: string): number {\n\tconst left = a.split('.');\n\tconst right = b.split('.');\n\tfor (let i = 0; i < Math.max(left.length, right.length); i += 1) {\n\t\tconst x = left[i];\n\t\tconst y = right[i];\n\t\tif (x === undefined) return -1;\n\t\tif (y === undefined) return 1;\n\t\tconst xn = /^\\d+$/.test(x);\n\t\tconst yn = /^\\d+$/.test(y);\n\t\tif (xn && yn) {\n\t\t\tconst diff = Number(x) - Number(y);\n\t\t\tif (diff !== 0) return diff < 0 ? -1 : 1;\n\t\t} else if (xn !== yn) {\n\t\t\t// Numeric identifiers always have lower precedence than alphanumeric ones.\n\t\t\treturn xn ? -1 : 1;\n\t\t} else if (x !== y) {\n\t\t\treturn x < y ? -1 : 1;\n\t\t}\n\t}\n\treturn 0;\n}\n\n/** Compare two semver strings. Returns -1, 0, or 1. Unparseable input throws. */\nexport function compareVersions(a: string, b: string): number {\n\tconst left = toSemver(a);\n\tconst right = toSemver(b);\n\tif (!left || !right) {\n\t\tthrow new Error(`compareVersions: not a version: \"${!left ? a : b}\"`);\n\t}\n\tfor (const key of ['major', 'minor', 'patch'] as const) {\n\t\tif (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1;\n\t}\n\tif (!left.prerelease && !right.prerelease) return 0;\n\t// A release outranks its own prereleases (1.8.0 > 1.8.0-beta.1).\n\tif (!left.prerelease) return 1;\n\tif (!right.prerelease) return -1;\n\treturn comparePrerelease(left.prerelease, right.prerelease);\n}\n\nconst TARBALL_SUFFIX = '.tgz';\nconst TARBALL_MARKER = 'sentinel-sdk-';\n\nfunction isVersionTokenChar(ch: string): boolean {\n\treturn (\n\t\t(ch >= '0' && ch <= '9') ||\n\t\t(ch >= 'A' && ch <= 'Z') ||\n\t\t(ch >= 'a' && ch <= 'z') ||\n\t\tch === '.' ||\n\t\tch === '+' ||\n\t\tch === '-'\n\t);\n}\n\n/**\n * Extract the `<version>` from a `sentinel-sdk-<version>.tgz` tarball name by\n * index slicing (strictly linear, no regex). The previous backtracking pattern\n * `/sentinel-sdk-([0-9][0-9A-Za-z.+-]*)\\.tgz/` let the version class and the\n * literal `.tgz` both consume `.`, a polynomial-regex (ReDoS) shape on\n * uncontrolled specifier input. Returns null when the specifier is not a\n * sentinel-sdk tarball name.\n */\nfunction extractTarballVersion(raw: string): string | null {\n\tif (!raw.endsWith(TARBALL_SUFFIX)) return null;\n\tconst stem = raw.slice(0, raw.length - TARBALL_SUFFIX.length);\n\tconst markerAt = stem.lastIndexOf(TARBALL_MARKER);\n\tif (markerAt < 0) return null;\n\tconst candidate = stem.slice(markerAt + TARBALL_MARKER.length);\n\tif (candidate.length === 0) return null;\n\tif (candidate[0] < '0' || candidate[0] > '9') return null;\n\tfor (const ch of candidate) {\n\t\tif (!isVersionTokenChar(ch)) return null;\n\t}\n\treturn candidate;\n}\n\n/**\n * Parse a declared dependency specifier into the lower-bound version it\n * guarantees. Anything that cannot prove a published floor (workspace/file/\n * git sources, floating tags, upper-bounded ranges) is reported `unresolved`\n * rather than guessed — the gate fails closed on those in strict mode.\n */\nexport function parsePinnedVersion(spec: string): ParsedPin {\n\tconst raw = (spec ?? '').trim();\n\tconst unresolved = (reason: string): ParsedPin => ({\n\t\tkind: 'unresolved',\n\t\tversion: null,\n\t\tspec: raw,\n\t\treason\n\t});\n\n\tif (!raw) return unresolved('empty specifier');\n\n\tif (/^(workspace|file|link|portal):/i.test(raw)) {\n\t\treturn unresolved(`local source (${raw.split(':')[0]}:) — cannot prove a published floor`);\n\t}\n\tif (/^(git|github)(\\+|:)/i.test(raw) || /\\.git(#|$)/.test(raw)) {\n\t\treturn unresolved('git source — cannot prove a published floor');\n\t}\n\n\t// npm alias, e.g. `npm:@nexartis/sentinel-sdk@1.9.0`.\n\tif (raw.startsWith('npm:')) {\n\t\tconst rest = raw.slice('npm:'.length);\n\t\tconst at = rest.lastIndexOf('@');\n\t\tconst range = at > 0 ? rest.slice(at + 1) : '';\n\t\tconst inner = parsePinnedVersion(range);\n\t\treturn {\n\t\t\t...inner,\n\t\t\tkind: inner.kind === 'unresolved' ? 'unresolved' : 'alias',\n\t\t\tspec: raw\n\t\t};\n\t}\n\n\t// Tarball pin, e.g. `.../@nexartis/sentinel-sdk/-/sentinel-sdk-1.9.2.tgz`.\n\tconst tarballVersion = extractTarballVersion(raw);\n\tif (tarballVersion !== null) {\n\t\tconst version = normalizeVersion(tarballVersion);\n\t\tif (!version) return unresolved(`unparseable tarball version in \"${raw}\"`);\n\t\treturn { kind: 'tarball', version, spec: raw, reason: `tarball pin ${version}` };\n\t}\n\tif (/^(https?:|file:)/i.test(raw)) {\n\t\treturn unresolved('tarball without a sentinel-sdk-<version>.tgz name');\n\t}\n\n\tif (raw === '*' || /^(latest|next|x)$/i.test(raw)) {\n\t\treturn unresolved(`floating specifier \"${raw}\" — cannot prove a floor`);\n\t}\n\n\tif (raw.includes('||')) {\n\t\treturn unresolved(`composite range \"${raw}\" — cannot prove a single floor`);\n\t}\n\n\tconst operator = /^(\\^|~|>=|<=|>|<|=)/.exec(raw)?.[1] ?? '';\n\tconst versionToken = /(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:-([0-9A-Za-z.-]+))?/.exec(raw);\n\tif (!versionToken) return unresolved(`no semver version in \"${raw}\"`);\n\tconst version = normalizeVersion(versionToken[0]);\n\tif (!version) return unresolved(`unparseable version in \"${raw}\"`);\n\n\t// `<`/`<=` cannot prove a lower bound at or above the floor.\n\tif (operator === '<' || operator === '<=') {\n\t\treturn unresolved(`upper-bounded range \"${raw}\" — cannot prove a floor`);\n\t}\n\n\tconst kind: PinKind = operator === '' || operator === '=' ? 'exact' : 'range';\n\treturn { kind, version, spec: raw, reason: `${kind} ${operator || '='} ${version}` };\n}\n\n/**\n * Evaluate one declared specifier against the floor. A missing specifier is\n * `absent` (not a consumer); an unprovable specifier is `unresolved`.\n */\nexport function evaluatePin(\n\tspec: string | null | undefined,\n\tfloor: string = SENTINEL_SDK_VERSION_FLOOR\n): FloorVerdict {\n\tif (spec == null || !String(spec).trim()) {\n\t\treturn {\n\t\t\tstatus: 'absent',\n\t\t\tpinnedVersion: null,\n\t\t\tspec: spec ?? null,\n\t\t\treason: `no ${SENTINEL_SDK_PACKAGE} dependency declared`\n\t\t};\n\t}\n\tconst declared = String(spec);\n\tconst parsed = parsePinnedVersion(declared);\n\tif (parsed.kind === 'unresolved' || !parsed.version) {\n\t\treturn { status: 'unresolved', pinnedVersion: null, spec: declared, reason: parsed.reason };\n\t}\n\tif (compareVersions(parsed.version, floor) < 0) {\n\t\treturn {\n\t\t\tstatus: 'below-floor',\n\t\t\tpinnedVersion: parsed.version,\n\t\t\tspec: declared,\n\t\t\treason: `${parsed.version} resolves below the ${floor} floor`\n\t\t};\n\t}\n\treturn {\n\t\tstatus: 'ok',\n\t\tpinnedVersion: parsed.version,\n\t\tspec: declared,\n\t\treason: `${parsed.version} is at or above the ${floor} floor`\n\t};\n}\n\n/** Evaluate a set of consumers and summarise the fleet verdict. */\nexport function evaluateConsumers(\n\tentries: readonly ConsumerEntry[],\n\tfloor: string = SENTINEL_SDK_VERSION_FLOOR\n): FloorReport {\n\tconst consumers: ConsumerVerdict[] = entries.map((entry) => ({\n\t\trepo: entry.repo,\n\t\t...evaluatePin(entry.dependencySpec, floor)\n\t}));\n\tconst violations = consumers.filter((c) => c.status === 'below-floor').map((c) => c.repo);\n\tconst unresolved = consumers.filter((c) => c.status === 'unresolved').map((c) => c.repo);\n\treturn {\n\t\tfloor,\n\t\tconsumers,\n\t\tviolations,\n\t\tunresolved,\n\t\tok: violations.length === 0,\n\t\tstrictOk: violations.length === 0 && unresolved.length === 0\n\t};\n}\n"]}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type { SentinelCodeValue } from './errors.js';
|
|
|
4
4
|
export { validateToken, isTokenExpired, decodeTokenLocally } from './auth-utils.js';
|
|
5
5
|
export { createJwksVerifier, __resetJwksCacheForTests } from './jwks.js';
|
|
6
6
|
export type { JwksVerifier, JwksVerifierOptions, TokenVerifyOutcome } from './jwks.js';
|
|
7
|
+
export { SENTINEL_SDK_PACKAGE, SENTINEL_SDK_VERSION_FLOOR, parsePinnedVersion, compareVersions, evaluatePin, evaluateConsumers } from './consumer-version-floor.js';
|
|
8
|
+
export type { PinKind, ParsedPin, FloorStatus, FloorVerdict, ConsumerEntry, ConsumerVerdict, FloorReport } from './consumer-version-floor.js';
|
|
7
9
|
export { createLogger, noopLogger } from './logger.js';
|
|
8
10
|
export type { SentinelLogger, LogLevel, LogEntry } from './logger.js';
|
|
9
11
|
export type { SentinelConfig, SentinelResponse, SentinelResponseBase, StartLoginResponse, PollResponse, ClaimResponse, RefreshResponse, ValidateResponse, RegisterResponse, VerifyResponse, CompletePasswordlessResponse, DecodedToken, UserLocals, SentinelHandleOptions, AuthHandlerOptions, BeforeLoginContext, BeforeLoginReject, AfterLoginContext, AfterClaimContext, ErrorContext, ResolveRedirectContext, LogoutRedirectContext } from './types.js';
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAClJ,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACzE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtE,YAAY,EACX,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,4BAA4B,EAC5B,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAClJ,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AACzE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACvF,OAAO,EACN,oBAAoB,EACpB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACX,OAAO,EACP,SAAS,EACT,WAAW,EACX,YAAY,EACZ,aAAa,EACb,eAAe,EACf,WAAW,EACX,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACtE,YAAY,EACX,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,kBAAkB,EAClB,YAAY,EACZ,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,4BAA4B,EAC5B,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,MAAM,YAAY,CAAC"}
|
package/dist/core/index.js
CHANGED
|
@@ -3,5 +3,6 @@ export { SentinelClient } from './client.js';
|
|
|
3
3
|
export { SentinelError, SentinelApiError, SentinelTimeoutError, ErrorCode, SentinelCode, isRetryableError, isRetryableStatus } from './errors.js';
|
|
4
4
|
export { validateToken, isTokenExpired, decodeTokenLocally } from './auth-utils.js';
|
|
5
5
|
export { createJwksVerifier, __resetJwksCacheForTests } from './jwks.js';
|
|
6
|
+
export { SENTINEL_SDK_PACKAGE, SENTINEL_SDK_VERSION_FLOOR, parsePinnedVersion, compareVersions, evaluatePin, evaluateConsumers } from './consumer-version-floor.js';
|
|
6
7
|
export { createLogger, noopLogger } from './logger.js';
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAElJ,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAEzE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC","sourcesContent":["// src/core/index.ts\nexport { SentinelClient } from './client.js';\nexport { SentinelError, SentinelApiError, SentinelTimeoutError, ErrorCode, SentinelCode, isRetryableError, isRetryableStatus } from './errors.js';\nexport type { SentinelCodeValue } from './errors.js';\nexport { validateToken, isTokenExpired, decodeTokenLocally } from './auth-utils.js';\nexport { createJwksVerifier, __resetJwksCacheForTests } from './jwks.js';\nexport type { JwksVerifier, JwksVerifierOptions, TokenVerifyOutcome } from './jwks.js';\nexport { createLogger, noopLogger } from './logger.js';\nexport type { SentinelLogger, LogLevel, LogEntry } from './logger.js';\nexport type {\n\tSentinelConfig,\n\tSentinelResponse,\n\tSentinelResponseBase,\n\tStartLoginResponse,\n\tPollResponse,\n\tClaimResponse,\n\tRefreshResponse,\n\tValidateResponse,\n\tRegisterResponse,\n\tVerifyResponse,\n\tCompletePasswordlessResponse,\n\tDecodedToken,\n\tUserLocals,\n\tSentinelHandleOptions,\n\tAuthHandlerOptions,\n\tBeforeLoginContext,\n\tBeforeLoginReject,\n\tAfterLoginContext,\n\tAfterClaimContext,\n\tErrorContext,\n\tResolveRedirectContext,\n\tLogoutRedirectContext\n} from './types.js';\n\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAElJ,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACpF,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAEzE,OAAO,EACN,oBAAoB,EACpB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,MAAM,6BAA6B,CAAC;AAUrC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC","sourcesContent":["// src/core/index.ts\nexport { SentinelClient } from './client.js';\nexport { SentinelError, SentinelApiError, SentinelTimeoutError, ErrorCode, SentinelCode, isRetryableError, isRetryableStatus } from './errors.js';\nexport type { SentinelCodeValue } from './errors.js';\nexport { validateToken, isTokenExpired, decodeTokenLocally } from './auth-utils.js';\nexport { createJwksVerifier, __resetJwksCacheForTests } from './jwks.js';\nexport type { JwksVerifier, JwksVerifierOptions, TokenVerifyOutcome } from './jwks.js';\nexport {\n\tSENTINEL_SDK_PACKAGE,\n\tSENTINEL_SDK_VERSION_FLOOR,\n\tparsePinnedVersion,\n\tcompareVersions,\n\tevaluatePin,\n\tevaluateConsumers\n} from './consumer-version-floor.js';\nexport type {\n\tPinKind,\n\tParsedPin,\n\tFloorStatus,\n\tFloorVerdict,\n\tConsumerEntry,\n\tConsumerVerdict,\n\tFloorReport\n} from './consumer-version-floor.js';\nexport { createLogger, noopLogger } from './logger.js';\nexport type { SentinelLogger, LogLevel, LogEntry } from './logger.js';\nexport type {\n\tSentinelConfig,\n\tSentinelResponse,\n\tSentinelResponseBase,\n\tStartLoginResponse,\n\tPollResponse,\n\tClaimResponse,\n\tRefreshResponse,\n\tValidateResponse,\n\tRegisterResponse,\n\tVerifyResponse,\n\tCompletePasswordlessResponse,\n\tDecodedToken,\n\tUserLocals,\n\tSentinelHandleOptions,\n\tAuthHandlerOptions,\n\tBeforeLoginContext,\n\tBeforeLoginReject,\n\tAfterLoginContext,\n\tAfterClaimContext,\n\tErrorContext,\n\tResolveRedirectContext,\n\tLogoutRedirectContext\n} from './types.js';\n\n"]}
|
package/dist/core/jwks.d.ts
CHANGED
|
@@ -38,7 +38,14 @@ export interface JwksVerifierOptions {
|
|
|
38
38
|
/** Optional structured logger. */
|
|
39
39
|
logger?: SentinelLogger;
|
|
40
40
|
}
|
|
41
|
-
/**
|
|
41
|
+
/**
|
|
42
|
+
* Signature-verified current Sentinel access claims, not revocation or
|
|
43
|
+
* membership evidence.
|
|
44
|
+
*
|
|
45
|
+
* Accepts both TRUSTIP-147 token shapes during the migration window: legacy
|
|
46
|
+
* tokens carrying `roles` (optional, `[]` when absent) and post-cutover tokens
|
|
47
|
+
* carrying the identity-only `platform_admin` boolean.
|
|
48
|
+
*/
|
|
42
49
|
export interface VerifiedAccessToken extends DecodedToken {
|
|
43
50
|
iss: 'nexartis';
|
|
44
51
|
aud: 'nexartis';
|
package/dist/core/jwks.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/core/jwks.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,mBAAmB;IACnC,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAClC,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;CACxB;AAED
|
|
1
|
+
{"version":3,"file":"jwks.d.ts","sourceRoot":"","sources":["../../src/core/jwks.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,WAAW,mBAAmB;IACnC,yFAAyF;IACzF,OAAO,EAAE,MAAM,CAAC;IAChB,kGAAkG;IAClG,OAAO,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAClC,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,MAAM,CAAC,EAAE,cAAc,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAoB,SAAQ,YAAY;IACxD,GAAG,EAAE,UAAU,CAAC;IAChB,GAAG,EAAE,UAAU,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,kBAAkB;AAC7B,mEAAmE;AACjE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAE;AACnD;;;;GAIG;GACD;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AACvC;;;GAGG;GACD;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC5B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACnD,iEAAiE;IACjE,mBAAmB,IAAI,IAAI,CAAC;CAC5B;AA4BD,6CAA6C;AAC7C,wBAAgB,wBAAwB,IAAI,IAAI,CAI/C;AAsLD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,mBAAmB,GAAG,YAAY,CAmFjF"}
|
package/dist/core/jwks.js
CHANGED
|
@@ -152,13 +152,37 @@ function parseAndValidatePayload(payloadPart) {
|
|
|
152
152
|
return null;
|
|
153
153
|
if ('nbf' in payload && !isSafeNumericDate(payload.nbf))
|
|
154
154
|
return null;
|
|
155
|
-
|
|
155
|
+
// TRUSTIP-147 migration window: the session token is moving from a global
|
|
156
|
+
// `roles` array to an identity-only `platform_admin` boolean. `roles` is now
|
|
157
|
+
// OPTIONAL — a token that omits it is still valid. A `roles` claim that IS
|
|
158
|
+
// present must still be a clean string array (fail closed on malformed input).
|
|
159
|
+
if ('roles' in payload && payload.roles !== undefined) {
|
|
160
|
+
if (!Array.isArray(payload.roles) || !payload.roles.every((role) => typeof role === 'string'))
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
// `platform_admin`, when present, must be a real boolean — never coerce.
|
|
164
|
+
if ('platform_admin' in payload && payload.platform_admin !== undefined &&
|
|
165
|
+
typeof payload.platform_admin !== 'boolean')
|
|
166
|
+
return null;
|
|
167
|
+
// A verified token must carry at least one authorization claim: the legacy
|
|
168
|
+
// `roles` array or the post-cutover `platform_admin` boolean. A token with
|
|
169
|
+
// neither is neither documented shape; admitting it would authenticate a
|
|
170
|
+
// principal with no authorization signal, so fail closed.
|
|
171
|
+
const hasRoles = 'roles' in payload && payload.roles !== undefined;
|
|
172
|
+
const hasPlatformAdmin = 'platform_admin' in payload && payload.platform_admin !== undefined;
|
|
173
|
+
if (!hasRoles && !hasPlatformAdmin)
|
|
156
174
|
return null;
|
|
157
|
-
// The signature covers these original claims.
|
|
175
|
+
// The signature covers these original claims. When `roles` is absent we
|
|
176
|
+
// surface a defined empty array so existing consumers keep an array; we
|
|
177
|
+
// never invent privileged roles. `platform_admin` is passed through only
|
|
178
|
+
// when the token actually carried it (absence means false to consumers).
|
|
158
179
|
return {
|
|
159
180
|
iss: payload.iss, aud: payload.aud, sub: payload.sub, email: payload.email,
|
|
160
|
-
jti: payload.jti,
|
|
161
|
-
|
|
181
|
+
jti: payload.jti,
|
|
182
|
+
roles: Array.isArray(payload.roles) ? payload.roles : [],
|
|
183
|
+
iat: payload.iat, exp: payload.exp,
|
|
184
|
+
...('nbf' in payload ? { nbf: payload.nbf } : {}),
|
|
185
|
+
...('platform_admin' in payload ? { platform_admin: payload.platform_admin } : {})
|
|
162
186
|
};
|
|
163
187
|
}
|
|
164
188
|
catch {
|
package/dist/core/jwks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwks.js","sourceRoot":"","sources":["../../src/core/jwks.ts"],"names":[],"mappings":"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","sourcesContent":["// src/core/jwks.ts\n\n/**\n * JWKS-based access-token signature verification (Ed25519 / EdDSA).\n *\n * Security model: the Sentinel backend signs access tokens with EdDSA and\n * publishes the public key at `/.well-known/jwks.json` (RFC 7517). This\n * module fetches and caches that key set, then verifies each access token's\n * signature with WebCrypto BEFORE the payload is trusted. Tokens that fail\n * verification are treated as unauthenticated — verification never throws.\n *\n * Cache behaviour:\n * - JWKS documents are cached per isolate, keyed by URL, with a TTL\n * (default 10 minutes).\n * - A token whose `kid` is not in the cache triggers ONE forced refresh\n * (key rotation support), throttled to at most once per\n * `refreshMinIntervalMs` (default 10 seconds) so unknown-kid floods cannot\n * be used to hammer the auth server.\n * - JWKS fetch failure classifies as `unavailable` (treat as\n * unauthenticated); it never surfaces as a 500.\n *\n * Only OKP/Ed25519 keys with `alg: EdDSA` semantics are accepted. Tokens\n * with any other algorithm (including legacy HS256) are rejected as\n * `invalid` — a symmetric HS256 secret can never be verified from a public\n * JWKS, and accepting it here would reintroduce the unverified-payload hole.\n */\n\nimport type { DecodedToken } from './types.js';\nimport type { SentinelLogger } from './logger.js';\nimport { createLogger } from './logger.js';\n\nexport interface JwksVerifierOptions {\n\t/** Full URL of the JWKS document, e.g. https://auth.example.com/.well-known/jwks.json */\n\tjwksUrl: string;\n\t/** Fetch implementation. Defaults to globalThis.fetch (pass event.fetch in SvelteKit handles). */\n\tfetchFn?: typeof globalThis.fetch;\n\t/** Cache TTL in milliseconds. Default: 600_000 (10 minutes). */\n\tcacheTtlMs?: number;\n\t/** Minimum interval between forced refreshes for unknown kids, in ms. Default: 10_000. */\n\trefreshMinIntervalMs?: number;\n\t/** Timeout for each JWKS fetch, in ms. Default: 5_000. */\n\ttimeoutMs?: number;\n\t/** Optional structured logger. */\n\tlogger?: SentinelLogger;\n}\n\n/** Signature-verified current Sentinel access claims, not revocation or membership evidence. */\nexport interface VerifiedAccessToken extends DecodedToken {\n\tiss: 'nexartis';\n\taud: 'nexartis';\n\tjti: string;\n\tnbf?: number;\n}\n\nexport type TokenVerifyOutcome =\n\t/** Signature valid, payload structurally sound and not expired. */\n\t| { status: 'valid'; payload: VerifiedAccessToken }\n\t/**\n\t * Token rejected: bad signature, malformed token, unsupported alg,\n\t * unknown kid (after refresh), expired, or structurally invalid payload.\n\t * Treat the request as unauthenticated.\n\t */\n\t| { status: 'invalid'; reason: string }\n\t/**\n\t * Verification could not run because the JWKS document is unreachable or\n\t * unusable. Treat the request as unauthenticated (fail closed) — never a 500.\n\t */\n\t| { status: 'unavailable'; reason: string };\n\nexport interface JwksVerifier {\n\tverify(token: string): Promise<TokenVerifyOutcome>;\n\t/** Test-only: drop cached JWKS state for this verifier's URL. */\n\t_resetCacheForTests(): void;\n}\n\ninterface JwksCacheEntry {\n\tkeys: Map<string, CryptoKey>;\n\tfetchedAt: number;\n}\n\ninterface JwksKeyShape {\n\tkty?: unknown;\n\tcrv?: unknown;\n\tx?: unknown;\n\tkid?: unknown;\n\talg?: unknown;\n\tuse?: unknown;\n\tkey_ops?: unknown;\n}\n\ninterface JwksDocumentShape {\n\tkeys?: unknown;\n}\n\n// Per-isolate cache shared across verifiers with the same JWKS URL.\nconst jwksCache = new Map<string, JwksCacheEntry>();\n// Per-URL in-flight fetch dedup (avoids thundering herd on cold cache).\nconst inflightFetches = new Map<string, Promise<JwksCacheEntry | null>>();\n// Per-URL throttle markers for forced refreshes on unknown kid.\nconst lastForcedRefreshAt = new Map<string, number>();\n\n/** Test-only: drop all cached JWKS state. */\nexport function __resetJwksCacheForTests(): void {\n\tjwksCache.clear();\n\tinflightFetches.clear();\n\tlastForcedRefreshAt.clear();\n}\n\nfunction base64UrlDecodeToBytes(input: string): Uint8Array<ArrayBuffer> {\n\tlet base64 = input.replace(/-/g, '+').replace(/_/g, '/');\n\tconst pad = base64.length % 4;\n\tif (pad) base64 += '='.repeat(4 - pad);\n\tconst binary = atob(base64);\n\tconst bytes = new Uint8Array(new ArrayBuffer(binary.length));\n\tfor (let i = 0; i < binary.length; i += 1) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n\nfunction base64UrlDecodeToString(input: string): string {\n\t// Decode via Uint8Array + TextDecoder for proper UTF-8 handling.\n\treturn new TextDecoder('utf-8').decode(base64UrlDecodeToBytes(input));\n}\n\nasync function importPublicEd25519Key(jwk: JwksKeyShape): Promise<CryptoKey | null> {\n\t// Optional JWK metadata must not contradict the key's advertised verification purpose.\n\tif ('alg' in jwk && jwk.alg !== 'EdDSA') return null;\n\tif ('use' in jwk && jwk.use !== 'sig') return null;\n\tif ('key_ops' in jwk && (!Array.isArray(jwk.key_ops) ||\n\t\t!jwk.key_ops.every((operation: unknown) => typeof operation === 'string') ||\n\t\t!jwk.key_ops.includes('verify'))) return null;\n\tif (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519' || typeof jwk.x !== 'string' || jwk.x.length === 0) {\n\t\treturn null;\n\t}\n\ttry {\n\t\treturn await crypto.subtle.importKey(\n\t\t\t'jwk',\n\t\t\t{ kty: 'OKP', crv: 'Ed25519', x: jwk.x },\n\t\t\t{ name: 'Ed25519' },\n\t\t\tfalse,\n\t\t\t['verify']\n\t\t);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function fetchJwksDocument(\n\tjwksUrl: string,\n\tfetchFn: typeof globalThis.fetch,\n\ttimeoutMs: number,\n\tlog: SentinelLogger\n): Promise<JwksCacheEntry | null> {\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\ttry {\n\t\tconst response = await fetchFn(jwksUrl, {\n\t\t\tsignal: controller.signal,\n\t\t\theaders: { accept: 'application/json' }\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tlog.warn('JWKS fetch returned non-OK status', { jwksUrl, status: response.status });\n\t\t\treturn null;\n\t\t}\n\t\tconst document = (await response.json()) as JwksDocumentShape;\n\t\tif (!Array.isArray(document.keys)) {\n\t\t\tlog.warn('JWKS document has no keys array', { jwksUrl });\n\t\t\treturn null;\n\t\t}\n\t\tconst keys = new Map<string, CryptoKey>();\n\t\tfor (const rawKey of document.keys as JwksKeyShape[]) {\n\t\t\tif (typeof rawKey?.kid !== 'string' || rawKey.kid.length === 0) continue;\n\t\t\tconst imported = await importPublicEd25519Key(rawKey);\n\t\t\tif (imported) keys.set(rawKey.kid, imported);\n\t\t}\n\t\tif (keys.size === 0) {\n\t\t\tlog.warn('JWKS document contained no usable Ed25519 keys', { jwksUrl });\n\t\t\treturn null;\n\t\t}\n\t\treturn { keys, fetchedAt: Date.now() };\n\t} catch (error) {\n\t\tlog.warn('JWKS fetch failed', { jwksUrl, error: String(error) });\n\t\treturn null;\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\nasync function getJwksEntry(\n\toptions: Required<Pick<JwksVerifierOptions, 'jwksUrl' | 'cacheTtlMs' | 'refreshMinIntervalMs' | 'timeoutMs'>> & JwksVerifierOptions,\n\tforce: boolean,\n\tlog: SentinelLogger\n): Promise<JwksCacheEntry | null> {\n\tconst { jwksUrl, cacheTtlMs, refreshMinIntervalMs, timeoutMs } = options;\n\tconst fetchFn = options.fetchFn ?? globalThis.fetch;\n\tconst now = Date.now();\n\n\tconst cached = jwksCache.get(jwksUrl);\n\tif (!force && cached && now - cached.fetchedAt < cacheTtlMs) {\n\t\treturn cached;\n\t}\n\n\tif (force) {\n\t\tconst lastForced = lastForcedRefreshAt.get(jwksUrl) ?? 0;\n\t\tif (now - lastForced < refreshMinIntervalMs) {\n\t\t\t// Throttled: report whatever we have (possibly nothing).\n\t\t\treturn cached ?? null;\n\t\t}\n\t\tlastForcedRefreshAt.set(jwksUrl, now);\n\t}\n\n\tconst inflight = inflightFetches.get(jwksUrl);\n\tif (inflight) return inflight;\n\n\tconst pending = fetchJwksDocument(jwksUrl, fetchFn, timeoutMs, log)\n\t\t.then((entry) => {\n\t\t\tif (entry) jwksCache.set(jwksUrl, entry);\n\t\t\treturn entry;\n\t\t})\n\t\t.finally(() => {\n\t\t\tinflightFetches.delete(jwksUrl);\n\t\t});\n\tinflightFetches.set(jwksUrl, pending);\n\treturn pending;\n}\n\nfunction parseTokenHeader(token: string): { alg?: unknown; kid?: unknown } | null {\n\ttry {\n\t\tconst parts = token.split('.');\n\t\tif (parts.length !== 3) return null;\n\t\tconst header = JSON.parse(base64UrlDecodeToString(parts[0]));\n\t\treturn typeof header === 'object' && header !== null ? (header as { alg?: unknown; kid?: unknown }) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isSafeNumericDate(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value) && value >= 0 &&\n\t\tvalue * 1000 <= Number.MAX_SAFE_INTEGER;\n}\n\nfunction parseAndValidatePayload(payloadPart: string): VerifiedAccessToken | null {\n\ttry {\n\t\tconst payload = JSON.parse(base64UrlDecodeToString(payloadPart));\n\t\tif (typeof payload !== 'object' || payload === null || Array.isArray(payload)) return null;\n\t\t// Current backend contract is a fixed service tuple, not a client/tenant audience.\n\t\tif (payload.iss !== 'nexartis' || payload.aud !== 'nexartis') return null;\n\t\tif (typeof payload.sub !== 'string' || payload.sub.length === 0) return null;\n\t\tif (typeof payload.email !== 'string' || payload.email.length === 0) return null;\n\t\tif (typeof payload.jti !== 'string' || payload.jti.length === 0) return null;\n\t\tif (!isSafeNumericDate(payload.exp) || !isSafeNumericDate(payload.iat)) return null;\n\t\tif ('nbf' in payload && !isSafeNumericDate(payload.nbf)) return null;\n\t\tif (!Array.isArray(payload.roles) || !payload.roles.every((role: unknown) => typeof role === 'string')) return null;\n\t\t// The signature covers these original claims. Do not normalize or silently drop roles.\n\t\treturn {\n\t\t\tiss: payload.iss, aud: payload.aud, sub: payload.sub, email: payload.email,\n\t\t\tjti: payload.jti, roles: payload.roles, iat: payload.iat, exp: payload.exp,\n\t\t\t...('nbf' in payload ? { nbf: payload.nbf } : {})\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Create a JWKS verifier bound to one JWKS URL. The underlying key cache is\n * shared per isolate across verifiers with the same URL.\n */\nexport function createJwksVerifier(userOptions: JwksVerifierOptions): JwksVerifier {\n\tconst options = {\n\t\tjwksUrl: userOptions.jwksUrl,\n\t\tfetchFn: userOptions.fetchFn,\n\t\tcacheTtlMs: userOptions.cacheTtlMs ?? 600_000,\n\t\trefreshMinIntervalMs: userOptions.refreshMinIntervalMs ?? 10_000,\n\t\ttimeoutMs: userOptions.timeoutMs ?? 5_000,\n\t\tlogger: userOptions.logger\n\t};\n\tconst log = options.logger ?? createLogger('sentinel-jwks');\n\n\tasync function resolveKey(kid: string): Promise<{ key?: CryptoKey; entry: JwksCacheEntry | null }> {\n\t\tlet entry = await getJwksEntry(options, false, log);\n\t\tif (entry?.keys.has(kid)) return { key: entry.keys.get(kid), entry };\n\n\t\t// Unknown kid (or no cache): one throttled forced refresh for key rotation.\n\t\tentry = await getJwksEntry(options, true, log);\n\t\tif (entry?.keys.has(kid)) return { key: entry.keys.get(kid), entry };\n\t\treturn { entry };\n\t}\n\n\treturn {\n\t\tasync verify(token: string): Promise<TokenVerifyOutcome> {\n\t\t\ttry {\n\t\t\t\tconst parts = token.split('.');\n\t\t\t\tif (parts.length !== 3) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'malformed token structure' };\n\t\t\t\t}\n\t\t\t\tconst [headerPart, payloadPart, signaturePart] = parts;\n\n\t\t\t\tconst header = parseTokenHeader(token);\n\t\t\t\tif (!header) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'unparseable token header' };\n\t\t\t\t}\n\t\t\t\tif (header.alg !== 'EdDSA') {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'invalid',\n\t\t\t\t\t\treason: `unsupported token alg \"${String(header.alg)}\" — only EdDSA tokens can be JWKS-verified (backend must be migrated from HS256)`\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (typeof header.kid !== 'string' || header.kid.length === 0) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token header missing kid' };\n\t\t\t\t}\n\n\t\t\t\tconst { key, entry } = await resolveKey(header.kid);\n\t\t\t\tif (!entry) {\n\t\t\t\t\treturn { status: 'unavailable', reason: 'JWKS document unreachable' };\n\t\t\t\t}\n\t\t\t\tif (!key) {\n\t\t\t\t\treturn { status: 'invalid', reason: `unknown kid \"${header.kid}\" (after JWKS refresh)` };\n\t\t\t\t}\n\n\t\t\t\tconst signature = base64UrlDecodeToBytes(signaturePart);\n\t\t\t\tconst data = new TextEncoder().encode(`${headerPart}.${payloadPart}`);\n\t\t\t\tconst signatureValid = await crypto.subtle.verify('Ed25519', key, signature, data);\n\t\t\t\tif (!signatureValid) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'signature verification failed' };\n\t\t\t\t}\n\n\t\t\t\tconst payload = parseAndValidatePayload(payloadPart);\n\t\t\t\tif (!payload) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'payload missing required claims' };\n\t\t\t\t}\n\t\t\t\tif (Date.now() >= payload.exp * 1000) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token expired' };\n\t\t\t\t}\n\t\t\t\tif (payload.nbf !== undefined && Date.now() < payload.nbf * 1000) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token not yet valid' };\n\t\t\t\t}\n\t\t\t\treturn { status: 'valid', payload };\n\t\t\t} catch (error) {\n\t\t\t\t// Never throw out of verification — classify and fail closed.\n\t\t\t\tlog.error('Unexpected JWKS verification error', { error: String(error) });\n\t\t\t\treturn { status: 'invalid', reason: 'verification error' };\n\t\t\t}\n\t\t},\n\n\t\t_resetCacheForTests(): void {\n\t\t\tjwksCache.delete(options.jwksUrl);\n\t\t\tlastForcedRefreshAt.delete(options.jwksUrl);\n\t\t\tinflightFetches.delete(options.jwksUrl);\n\t\t}\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"jwks.js","sourceRoot":"","sources":["../../src/core/jwks.ts"],"names":[],"mappings":"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","sourcesContent":["// src/core/jwks.ts\n\n/**\n * JWKS-based access-token signature verification (Ed25519 / EdDSA).\n *\n * Security model: the Sentinel backend signs access tokens with EdDSA and\n * publishes the public key at `/.well-known/jwks.json` (RFC 7517). This\n * module fetches and caches that key set, then verifies each access token's\n * signature with WebCrypto BEFORE the payload is trusted. Tokens that fail\n * verification are treated as unauthenticated — verification never throws.\n *\n * Cache behaviour:\n * - JWKS documents are cached per isolate, keyed by URL, with a TTL\n * (default 10 minutes).\n * - A token whose `kid` is not in the cache triggers ONE forced refresh\n * (key rotation support), throttled to at most once per\n * `refreshMinIntervalMs` (default 10 seconds) so unknown-kid floods cannot\n * be used to hammer the auth server.\n * - JWKS fetch failure classifies as `unavailable` (treat as\n * unauthenticated); it never surfaces as a 500.\n *\n * Only OKP/Ed25519 keys with `alg: EdDSA` semantics are accepted. Tokens\n * with any other algorithm (including legacy HS256) are rejected as\n * `invalid` — a symmetric HS256 secret can never be verified from a public\n * JWKS, and accepting it here would reintroduce the unverified-payload hole.\n */\n\nimport type { DecodedToken } from './types.js';\nimport type { SentinelLogger } from './logger.js';\nimport { createLogger } from './logger.js';\n\nexport interface JwksVerifierOptions {\n\t/** Full URL of the JWKS document, e.g. https://auth.example.com/.well-known/jwks.json */\n\tjwksUrl: string;\n\t/** Fetch implementation. Defaults to globalThis.fetch (pass event.fetch in SvelteKit handles). */\n\tfetchFn?: typeof globalThis.fetch;\n\t/** Cache TTL in milliseconds. Default: 600_000 (10 minutes). */\n\tcacheTtlMs?: number;\n\t/** Minimum interval between forced refreshes for unknown kids, in ms. Default: 10_000. */\n\trefreshMinIntervalMs?: number;\n\t/** Timeout for each JWKS fetch, in ms. Default: 5_000. */\n\ttimeoutMs?: number;\n\t/** Optional structured logger. */\n\tlogger?: SentinelLogger;\n}\n\n/**\n * Signature-verified current Sentinel access claims, not revocation or\n * membership evidence.\n *\n * Accepts both TRUSTIP-147 token shapes during the migration window: legacy\n * tokens carrying `roles` (optional, `[]` when absent) and post-cutover tokens\n * carrying the identity-only `platform_admin` boolean.\n */\nexport interface VerifiedAccessToken extends DecodedToken {\n\tiss: 'nexartis';\n\taud: 'nexartis';\n\tjti: string;\n\tnbf?: number;\n}\n\nexport type TokenVerifyOutcome =\n\t/** Signature valid, payload structurally sound and not expired. */\n\t| { status: 'valid'; payload: VerifiedAccessToken }\n\t/**\n\t * Token rejected: bad signature, malformed token, unsupported alg,\n\t * unknown kid (after refresh), expired, or structurally invalid payload.\n\t * Treat the request as unauthenticated.\n\t */\n\t| { status: 'invalid'; reason: string }\n\t/**\n\t * Verification could not run because the JWKS document is unreachable or\n\t * unusable. Treat the request as unauthenticated (fail closed) — never a 500.\n\t */\n\t| { status: 'unavailable'; reason: string };\n\nexport interface JwksVerifier {\n\tverify(token: string): Promise<TokenVerifyOutcome>;\n\t/** Test-only: drop cached JWKS state for this verifier's URL. */\n\t_resetCacheForTests(): void;\n}\n\ninterface JwksCacheEntry {\n\tkeys: Map<string, CryptoKey>;\n\tfetchedAt: number;\n}\n\ninterface JwksKeyShape {\n\tkty?: unknown;\n\tcrv?: unknown;\n\tx?: unknown;\n\tkid?: unknown;\n\talg?: unknown;\n\tuse?: unknown;\n\tkey_ops?: unknown;\n}\n\ninterface JwksDocumentShape {\n\tkeys?: unknown;\n}\n\n// Per-isolate cache shared across verifiers with the same JWKS URL.\nconst jwksCache = new Map<string, JwksCacheEntry>();\n// Per-URL in-flight fetch dedup (avoids thundering herd on cold cache).\nconst inflightFetches = new Map<string, Promise<JwksCacheEntry | null>>();\n// Per-URL throttle markers for forced refreshes on unknown kid.\nconst lastForcedRefreshAt = new Map<string, number>();\n\n/** Test-only: drop all cached JWKS state. */\nexport function __resetJwksCacheForTests(): void {\n\tjwksCache.clear();\n\tinflightFetches.clear();\n\tlastForcedRefreshAt.clear();\n}\n\nfunction base64UrlDecodeToBytes(input: string): Uint8Array<ArrayBuffer> {\n\tlet base64 = input.replace(/-/g, '+').replace(/_/g, '/');\n\tconst pad = base64.length % 4;\n\tif (pad) base64 += '='.repeat(4 - pad);\n\tconst binary = atob(base64);\n\tconst bytes = new Uint8Array(new ArrayBuffer(binary.length));\n\tfor (let i = 0; i < binary.length; i += 1) {\n\t\tbytes[i] = binary.charCodeAt(i);\n\t}\n\treturn bytes;\n}\n\nfunction base64UrlDecodeToString(input: string): string {\n\t// Decode via Uint8Array + TextDecoder for proper UTF-8 handling.\n\treturn new TextDecoder('utf-8').decode(base64UrlDecodeToBytes(input));\n}\n\nasync function importPublicEd25519Key(jwk: JwksKeyShape): Promise<CryptoKey | null> {\n\t// Optional JWK metadata must not contradict the key's advertised verification purpose.\n\tif ('alg' in jwk && jwk.alg !== 'EdDSA') return null;\n\tif ('use' in jwk && jwk.use !== 'sig') return null;\n\tif ('key_ops' in jwk && (!Array.isArray(jwk.key_ops) ||\n\t\t!jwk.key_ops.every((operation: unknown) => typeof operation === 'string') ||\n\t\t!jwk.key_ops.includes('verify'))) return null;\n\tif (jwk.kty !== 'OKP' || jwk.crv !== 'Ed25519' || typeof jwk.x !== 'string' || jwk.x.length === 0) {\n\t\treturn null;\n\t}\n\ttry {\n\t\treturn await crypto.subtle.importKey(\n\t\t\t'jwk',\n\t\t\t{ kty: 'OKP', crv: 'Ed25519', x: jwk.x },\n\t\t\t{ name: 'Ed25519' },\n\t\t\tfalse,\n\t\t\t['verify']\n\t\t);\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nasync function fetchJwksDocument(\n\tjwksUrl: string,\n\tfetchFn: typeof globalThis.fetch,\n\ttimeoutMs: number,\n\tlog: SentinelLogger\n): Promise<JwksCacheEntry | null> {\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), timeoutMs);\n\ttry {\n\t\tconst response = await fetchFn(jwksUrl, {\n\t\t\tsignal: controller.signal,\n\t\t\theaders: { accept: 'application/json' }\n\t\t});\n\t\tif (!response.ok) {\n\t\t\tlog.warn('JWKS fetch returned non-OK status', { jwksUrl, status: response.status });\n\t\t\treturn null;\n\t\t}\n\t\tconst document = (await response.json()) as JwksDocumentShape;\n\t\tif (!Array.isArray(document.keys)) {\n\t\t\tlog.warn('JWKS document has no keys array', { jwksUrl });\n\t\t\treturn null;\n\t\t}\n\t\tconst keys = new Map<string, CryptoKey>();\n\t\tfor (const rawKey of document.keys as JwksKeyShape[]) {\n\t\t\tif (typeof rawKey?.kid !== 'string' || rawKey.kid.length === 0) continue;\n\t\t\tconst imported = await importPublicEd25519Key(rawKey);\n\t\t\tif (imported) keys.set(rawKey.kid, imported);\n\t\t}\n\t\tif (keys.size === 0) {\n\t\t\tlog.warn('JWKS document contained no usable Ed25519 keys', { jwksUrl });\n\t\t\treturn null;\n\t\t}\n\t\treturn { keys, fetchedAt: Date.now() };\n\t} catch (error) {\n\t\tlog.warn('JWKS fetch failed', { jwksUrl, error: String(error) });\n\t\treturn null;\n\t} finally {\n\t\tclearTimeout(timer);\n\t}\n}\n\nasync function getJwksEntry(\n\toptions: Required<Pick<JwksVerifierOptions, 'jwksUrl' | 'cacheTtlMs' | 'refreshMinIntervalMs' | 'timeoutMs'>> & JwksVerifierOptions,\n\tforce: boolean,\n\tlog: SentinelLogger\n): Promise<JwksCacheEntry | null> {\n\tconst { jwksUrl, cacheTtlMs, refreshMinIntervalMs, timeoutMs } = options;\n\tconst fetchFn = options.fetchFn ?? globalThis.fetch;\n\tconst now = Date.now();\n\n\tconst cached = jwksCache.get(jwksUrl);\n\tif (!force && cached && now - cached.fetchedAt < cacheTtlMs) {\n\t\treturn cached;\n\t}\n\n\tif (force) {\n\t\tconst lastForced = lastForcedRefreshAt.get(jwksUrl) ?? 0;\n\t\tif (now - lastForced < refreshMinIntervalMs) {\n\t\t\t// Throttled: report whatever we have (possibly nothing).\n\t\t\treturn cached ?? null;\n\t\t}\n\t\tlastForcedRefreshAt.set(jwksUrl, now);\n\t}\n\n\tconst inflight = inflightFetches.get(jwksUrl);\n\tif (inflight) return inflight;\n\n\tconst pending = fetchJwksDocument(jwksUrl, fetchFn, timeoutMs, log)\n\t\t.then((entry) => {\n\t\t\tif (entry) jwksCache.set(jwksUrl, entry);\n\t\t\treturn entry;\n\t\t})\n\t\t.finally(() => {\n\t\t\tinflightFetches.delete(jwksUrl);\n\t\t});\n\tinflightFetches.set(jwksUrl, pending);\n\treturn pending;\n}\n\nfunction parseTokenHeader(token: string): { alg?: unknown; kid?: unknown } | null {\n\ttry {\n\t\tconst parts = token.split('.');\n\t\tif (parts.length !== 3) return null;\n\t\tconst header = JSON.parse(base64UrlDecodeToString(parts[0]));\n\t\treturn typeof header === 'object' && header !== null ? (header as { alg?: unknown; kid?: unknown }) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isSafeNumericDate(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value) && value >= 0 &&\n\t\tvalue * 1000 <= Number.MAX_SAFE_INTEGER;\n}\n\nfunction parseAndValidatePayload(payloadPart: string): VerifiedAccessToken | null {\n\ttry {\n\t\tconst payload = JSON.parse(base64UrlDecodeToString(payloadPart));\n\t\tif (typeof payload !== 'object' || payload === null || Array.isArray(payload)) return null;\n\t\t// Current backend contract is a fixed service tuple, not a client/tenant audience.\n\t\tif (payload.iss !== 'nexartis' || payload.aud !== 'nexartis') return null;\n\t\tif (typeof payload.sub !== 'string' || payload.sub.length === 0) return null;\n\t\tif (typeof payload.email !== 'string' || payload.email.length === 0) return null;\n\t\tif (typeof payload.jti !== 'string' || payload.jti.length === 0) return null;\n\t\tif (!isSafeNumericDate(payload.exp) || !isSafeNumericDate(payload.iat)) return null;\n\t\tif ('nbf' in payload && !isSafeNumericDate(payload.nbf)) return null;\n\t\t// TRUSTIP-147 migration window: the session token is moving from a global\n\t\t// `roles` array to an identity-only `platform_admin` boolean. `roles` is now\n\t\t// OPTIONAL — a token that omits it is still valid. A `roles` claim that IS\n\t\t// present must still be a clean string array (fail closed on malformed input).\n\t\tif ('roles' in payload && payload.roles !== undefined) {\n\t\t\tif (!Array.isArray(payload.roles) || !payload.roles.every((role: unknown) => typeof role === 'string')) return null;\n\t\t}\n\t\t// `platform_admin`, when present, must be a real boolean — never coerce.\n\t\tif ('platform_admin' in payload && payload.platform_admin !== undefined &&\n\t\t\ttypeof payload.platform_admin !== 'boolean') return null;\n\t\t// A verified token must carry at least one authorization claim: the legacy\n\t\t// `roles` array or the post-cutover `platform_admin` boolean. A token with\n\t\t// neither is neither documented shape; admitting it would authenticate a\n\t\t// principal with no authorization signal, so fail closed.\n\t\tconst hasRoles = 'roles' in payload && payload.roles !== undefined;\n\t\tconst hasPlatformAdmin = 'platform_admin' in payload && payload.platform_admin !== undefined;\n\t\tif (!hasRoles && !hasPlatformAdmin) return null;\n\t\t// The signature covers these original claims. When `roles` is absent we\n\t\t// surface a defined empty array so existing consumers keep an array; we\n\t\t// never invent privileged roles. `platform_admin` is passed through only\n\t\t// when the token actually carried it (absence means false to consumers).\n\t\treturn {\n\t\t\tiss: payload.iss, aud: payload.aud, sub: payload.sub, email: payload.email,\n\t\t\tjti: payload.jti,\n\t\t\troles: Array.isArray(payload.roles) ? payload.roles : [],\n\t\t\tiat: payload.iat, exp: payload.exp,\n\t\t\t...('nbf' in payload ? { nbf: payload.nbf } : {}),\n\t\t\t...('platform_admin' in payload ? { platform_admin: payload.platform_admin } : {})\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Create a JWKS verifier bound to one JWKS URL. The underlying key cache is\n * shared per isolate across verifiers with the same URL.\n */\nexport function createJwksVerifier(userOptions: JwksVerifierOptions): JwksVerifier {\n\tconst options = {\n\t\tjwksUrl: userOptions.jwksUrl,\n\t\tfetchFn: userOptions.fetchFn,\n\t\tcacheTtlMs: userOptions.cacheTtlMs ?? 600_000,\n\t\trefreshMinIntervalMs: userOptions.refreshMinIntervalMs ?? 10_000,\n\t\ttimeoutMs: userOptions.timeoutMs ?? 5_000,\n\t\tlogger: userOptions.logger\n\t};\n\tconst log = options.logger ?? createLogger('sentinel-jwks');\n\n\tasync function resolveKey(kid: string): Promise<{ key?: CryptoKey; entry: JwksCacheEntry | null }> {\n\t\tlet entry = await getJwksEntry(options, false, log);\n\t\tif (entry?.keys.has(kid)) return { key: entry.keys.get(kid), entry };\n\n\t\t// Unknown kid (or no cache): one throttled forced refresh for key rotation.\n\t\tentry = await getJwksEntry(options, true, log);\n\t\tif (entry?.keys.has(kid)) return { key: entry.keys.get(kid), entry };\n\t\treturn { entry };\n\t}\n\n\treturn {\n\t\tasync verify(token: string): Promise<TokenVerifyOutcome> {\n\t\t\ttry {\n\t\t\t\tconst parts = token.split('.');\n\t\t\t\tif (parts.length !== 3) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'malformed token structure' };\n\t\t\t\t}\n\t\t\t\tconst [headerPart, payloadPart, signaturePart] = parts;\n\n\t\t\t\tconst header = parseTokenHeader(token);\n\t\t\t\tif (!header) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'unparseable token header' };\n\t\t\t\t}\n\t\t\t\tif (header.alg !== 'EdDSA') {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tstatus: 'invalid',\n\t\t\t\t\t\treason: `unsupported token alg \"${String(header.alg)}\" — only EdDSA tokens can be JWKS-verified (backend must be migrated from HS256)`\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t\tif (typeof header.kid !== 'string' || header.kid.length === 0) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token header missing kid' };\n\t\t\t\t}\n\n\t\t\t\tconst { key, entry } = await resolveKey(header.kid);\n\t\t\t\tif (!entry) {\n\t\t\t\t\treturn { status: 'unavailable', reason: 'JWKS document unreachable' };\n\t\t\t\t}\n\t\t\t\tif (!key) {\n\t\t\t\t\treturn { status: 'invalid', reason: `unknown kid \"${header.kid}\" (after JWKS refresh)` };\n\t\t\t\t}\n\n\t\t\t\tconst signature = base64UrlDecodeToBytes(signaturePart);\n\t\t\t\tconst data = new TextEncoder().encode(`${headerPart}.${payloadPart}`);\n\t\t\t\tconst signatureValid = await crypto.subtle.verify('Ed25519', key, signature, data);\n\t\t\t\tif (!signatureValid) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'signature verification failed' };\n\t\t\t\t}\n\n\t\t\t\tconst payload = parseAndValidatePayload(payloadPart);\n\t\t\t\tif (!payload) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'payload missing required claims' };\n\t\t\t\t}\n\t\t\t\tif (Date.now() >= payload.exp * 1000) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token expired' };\n\t\t\t\t}\n\t\t\t\tif (payload.nbf !== undefined && Date.now() < payload.nbf * 1000) {\n\t\t\t\t\treturn { status: 'invalid', reason: 'token not yet valid' };\n\t\t\t\t}\n\t\t\t\treturn { status: 'valid', payload };\n\t\t\t} catch (error) {\n\t\t\t\t// Never throw out of verification — classify and fail closed.\n\t\t\t\tlog.error('Unexpected JWKS verification error', { error: String(error) });\n\t\t\t\treturn { status: 'invalid', reason: 'verification error' };\n\t\t\t}\n\t\t},\n\n\t\t_resetCacheForTests(): void {\n\t\t\tjwksCache.delete(options.jwksUrl);\n\t\t\tlastForcedRefreshAt.delete(options.jwksUrl);\n\t\t\tinflightFetches.delete(options.jwksUrl);\n\t\t}\n\t};\n}\n"]}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -48,11 +48,18 @@ export interface PollResponse extends SentinelResponseBase {
|
|
|
48
48
|
export interface ClaimResponse extends SentinelResponseBase {
|
|
49
49
|
access_token?: string;
|
|
50
50
|
refresh_token?: string;
|
|
51
|
+
/** FanCastle / TRUSTIP-143 community access token (short-lived, scoped). */
|
|
52
|
+
community_access_token?: string;
|
|
53
|
+
community_id?: string;
|
|
54
|
+
community_scope?: string;
|
|
51
55
|
}
|
|
52
56
|
/** Response from POST /auth/refresh */
|
|
53
57
|
export interface RefreshResponse extends SentinelResponseBase {
|
|
54
58
|
access_token?: string;
|
|
55
59
|
refresh_token?: string;
|
|
60
|
+
community_access_token?: string;
|
|
61
|
+
community_id?: string;
|
|
62
|
+
community_scope?: string;
|
|
56
63
|
}
|
|
57
64
|
/** Response from POST /auth/validate */
|
|
58
65
|
export interface ValidateResponse extends SentinelResponseBase {
|
|
@@ -115,11 +122,29 @@ export interface SentinelResponse {
|
|
|
115
122
|
error?: string;
|
|
116
123
|
[key: string]: unknown;
|
|
117
124
|
}
|
|
118
|
-
/**
|
|
125
|
+
/**
|
|
126
|
+
* Decoded/API payload shape only: this type does not prove signature or claim verification.
|
|
127
|
+
*
|
|
128
|
+
* TRUSTIP-147 migration window: the Sentinel session token is moving from a
|
|
129
|
+
* global `roles: string[]` claim to an identity-only `platform_admin: boolean`
|
|
130
|
+
* claim. During the window a token MAY carry `roles`, `platform_admin`, or
|
|
131
|
+
* both — so `roles` is optional and both claims are read when present.
|
|
132
|
+
*/
|
|
119
133
|
export interface DecodedToken {
|
|
120
134
|
sub: string;
|
|
121
135
|
email: string;
|
|
122
|
-
|
|
136
|
+
/**
|
|
137
|
+
* Global business roles carried by legacy tokens. OPTIONAL during the
|
|
138
|
+
* TRUSTIP-147 migration window — a token that omits it is still valid, and
|
|
139
|
+
* consumers must treat absence as an empty array (`[]`).
|
|
140
|
+
*/
|
|
141
|
+
roles?: string[];
|
|
142
|
+
/**
|
|
143
|
+
* Platform super-admin flag (identity-scoped, not community-scoped). Present
|
|
144
|
+
* on tokens minted after the TRUSTIP-147 cutover; absent on legacy
|
|
145
|
+
* role-bearing tokens. Surfaced as `UserLocals.platformAdmin` (default false).
|
|
146
|
+
*/
|
|
147
|
+
platform_admin?: boolean;
|
|
123
148
|
exp: number;
|
|
124
149
|
iat: number;
|
|
125
150
|
}
|
|
@@ -127,7 +152,10 @@ export interface UserLocals {
|
|
|
127
152
|
isAuthenticated: boolean;
|
|
128
153
|
id: string;
|
|
129
154
|
email: string;
|
|
155
|
+
/** Always a defined array — `[]` when the token carried no `roles` claim. */
|
|
130
156
|
roles: string[];
|
|
157
|
+
/** Platform super-admin flag (TRUSTIP-147). Defaults to false when absent. */
|
|
158
|
+
platformAdmin: boolean;
|
|
131
159
|
}
|
|
132
160
|
export interface SentinelHandleOptions {
|
|
133
161
|
protectedRoutes?: string[];
|
|
@@ -157,6 +185,8 @@ export interface BeforeLoginContext {
|
|
|
157
185
|
email: string;
|
|
158
186
|
rememberMe: boolean;
|
|
159
187
|
redirectTo?: string;
|
|
188
|
+
audience?: 'site_owner' | 'fan' | 'admin';
|
|
189
|
+
communityId?: string;
|
|
160
190
|
}
|
|
161
191
|
/**
|
|
162
192
|
* Return this from onBeforeLogin to reject the request.
|
|
@@ -201,6 +231,9 @@ export interface AfterClaimContext {
|
|
|
201
231
|
accessToken: string;
|
|
202
232
|
refreshToken: string;
|
|
203
233
|
redirectTo?: string;
|
|
234
|
+
communityAccessToken?: string;
|
|
235
|
+
communityId?: string;
|
|
236
|
+
communityScope?: string;
|
|
204
237
|
}
|
|
205
238
|
/** Data passed to the onError hook. */
|
|
206
239
|
export interface ErrorContext {
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMlD,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,MAAM,CAAC,EAAE,cAAc,CAAC;CACxB;AAMD,wDAAwD;AACxD,MAAM,WAAW,oBAAoB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED,kDAAkD;AAClD,MAAM,WAAW,kBAAmB,SAAQ,oBAAoB;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,oCAAoC;AACpC,MAAM,WAAW,YAAa,SAAQ,oBAAoB;IACzD,MAAM,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;IAC7C;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,qCAAqC;AACrC,MAAM,WAAW,aAAc,SAAQ,oBAAoB;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"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"}
|
package/dist/core/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["// src/core/types.ts\nimport type { SentinelLogger } from './logger.js';\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface SentinelConfig {\n\tapiUrl: string;\n\tbaseUrl: string;\n\t/**\n\t * When true, validateToken() performs a second GET /users/me request after\n\t * POST /auth/validate to refresh DB-backed profile fields such as roles.\n\t * Default: false. Keep disabled for high-volume per-request validation paths.\n\t */\n\thydrateUserProfile?: boolean;\n\t/**\n\t * Timeout in milliseconds for each individual fetch call to the Sentinel API.\n\t * Default: 8000 (8 seconds).\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Maximum number of retries for retryable errors (network timeout, HTTP 502/503/504).\n\t * Non-retryable errors (400, 401, 403) are never retried.\n\t * Default: 1.\n\t */\n\tmaxRetries?: number;\n\t/** Optional structured logger. Falls back to console if not provided. */\n\tlogger?: SentinelLogger;\n}\n\n// ---------------------------------------------------------------------------\n// Typed API Responses (replaces the old untyped SentinelResponse bag)\n// ---------------------------------------------------------------------------\n\n/** Base fields shared by all Sentinel API responses. */\nexport interface SentinelResponseBase {\n\tsuccess?: boolean;\n\tcode?: number;\n\tmessage?: string;\n\terror?: string;\n}\n\n/** Response from POST /auth/login-passwordless */\nexport interface StartLoginResponse extends SentinelResponseBase {\n\ttransactionId?: string;\n\tclientToken?: string;\n}\n\n/** Response from POST /auth/poll */\nexport interface PollResponse extends SentinelResponseBase {\n\tstatus?: 'pending' | 'completed' | 'expired';\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back on the claimed poll\n\t * response. Present only when the originating register/startLogin call\n\t * supplied a `redirectContext`. Null or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/** Response from POST /auth/claim */\nexport interface ClaimResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n}\n\n/** Response from POST /auth/refresh */\nexport interface RefreshResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n}\n\n/** Response from POST /auth/validate */\nexport interface ValidateResponse extends SentinelResponseBase {\n\tvalid?: boolean;\n\tpayload?: DecodedToken;\n\tuser?: {\n\t\tsub: string;\n\t\temail: string;\n\t\troles: string[];\n\t};\n}\n\n/** Response from GET /users/me. */\nexport interface CurrentUserResponse extends SentinelResponseBase {\n\tid?: string;\n\temail?: string;\n\trole?: string;\n\troles?: string[];\n\tstatus?: string;\n\tlegalName?: string;\n\tocmeMemberNumber?: string;\n\twalletAddress?: string;\n\taliasName?: string;\n\ttwitterAccount?: string;\n\tyoutubeAccount?: string;\n\tcubicubeAddress?: string;\n}\n\n/** Response from POST /auth/register */\nexport interface RegisterResponse extends SentinelResponseBase {}\n\n/** Response from POST /auth/verify */\nexport interface VerifyResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back from the originating\n\t * register call. Present only when a `redirectContext` was supplied. Null\n\t * or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/** Response from POST /auth/complete-passwordless */\nexport interface CompletePasswordlessResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back from the originating\n\t * startLogin call. Present only when a `redirectContext` was supplied. Null\n\t * or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/**\n * @deprecated Use the specific typed response interfaces instead.\n * Kept for backward compatibility — will be removed in v2.0.0.\n */\nexport interface SentinelResponse {\n\tsuccess?: boolean;\n\tcode?: number;\n\tmessage?: string;\n\terror?: string;\n\t[key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// JWT / Auth\n// ---------------------------------------------------------------------------\n\n/** Decoded/API payload shape only: this type does not prove signature or claim verification. */\nexport interface DecodedToken {\n\tsub: string;\n\temail: string;\n\troles: string[];\n\texp: number;\n\tiat: number;\n}\n\nexport interface UserLocals {\n\tisAuthenticated: boolean;\n\tid: string;\n\temail: string;\n\troles: string[];\n}\n\n// ---------------------------------------------------------------------------\n// SvelteKit Handle Options\n// ---------------------------------------------------------------------------\n\nexport interface SentinelHandleOptions {\n\tprotectedRoutes?: string[];\n\tloginRedirect?: string;\n\tapiUrl?: string;\n\tbaseUrl?: string;\n\t/** Optional structured logger for auth middleware. */\n\tlogger?: SentinelLogger;\n\t/**\n\t * Full JWKS URL used to verify access-token signatures.\n\t * Default: derived from `apiUrl` (origin + `/.well-known/jwks.json`).\n\t * Also overridable via the `SENTINEL_JWKS_URL` environment variable.\n\t */\n\tjwksUrl?: string;\n\t/** JWKS cache TTL in milliseconds. Default: 600_000 (10 minutes). */\n\tjwksCacheTtlMs?: number;\n\t/**\n\t * DEVELOPMENT ONLY: disable JWKS signature verification and fall back to\n\t * decoding the (unverified) token payload locally. Equivalent to setting\n\t * the environment variable `SENTINEL_SKIP_JWKS_VERIFY=1`. Never enable\n\t * this in production — the admin gate would again trust unverified claims.\n\t */\n\tskipJwksVerify?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Auth Handler Options & Lifecycle Hooks\n// ---------------------------------------------------------------------------\n\n/** Data passed to the onBeforeLogin hook. */\nexport interface BeforeLoginContext {\n\temail: string;\n\trememberMe: boolean;\n\tredirectTo?: string;\n}\n\n/**\n * Return this from onBeforeLogin to reject the request.\n *\n * When `redirect` is set and is a safe relative path (starts with `/`, no\n * protocol, no `//` / `\\\\` prefix), the SDK issues a `303 See Other` to that\n * path instead of returning the JSON error body. Unsafe redirects are ignored\n * and the JSON-reject path is used as a fallback.\n *\n * @example\n * ```ts\n * onBeforeLogin: async (_event, { email }) => {\n * const gate = await isLoginAllowed(email);\n * if (!gate.ok) {\n * return {\n * error: gate.reason,\n * status: 403,\n * redirect: gate.reason === 'waitlisted' ? '/waitlist' : '/unauthorized'\n * };\n * }\n * }\n * ```\n */\nexport interface BeforeLoginReject {\n\terror: string;\n\tstatus?: number;\n\t/**\n\t * Optional branded destination the SDK will 303-redirect to instead of\n\t * returning a JSON error. Must be a safe relative path (starts with `/`,\n\t * no protocol, no `//` / `\\\\` prefix). Unsafe values fall back to the\n\t * JSON-reject behaviour.\n\t */\n\tredirect?: string;\n}\n\n/** Data passed to the onAfterLogin hook. */\nexport interface AfterLoginContext {\n\temail: string;\n\ttransactionId: string;\n}\n\n/** Data passed to the onAfterClaim hook. */\nexport interface AfterClaimContext {\n\taccessToken: string;\n\trefreshToken: string;\n\tredirectTo?: string;\n}\n\n/** Data passed to the onError hook. */\nexport interface ErrorContext {\n\thandler: 'start-login' | 'poll' | 'claim' | 'logout' | 'refresh' | 'register' | 'verify';\n\terror: unknown;\n}\n\n/**\n * Context passed to the `resolveRedirect` hook after a successful token claim.\n * Hosts use this to pick a post-login destination based on the user's roles,\n * a requested redirect, or fall back to the configured default.\n */\nexport interface ResolveRedirectContext {\n\t/** Newly authenticated user, decoded from the access token. */\n\tuser: UserLocals;\n\t/** Access token just issued for this user. */\n\taccessToken: string;\n\t/** Whether the user opted into a longer session at login time. */\n\trememberMe: boolean;\n\t/**\n\t * The redirect the client originally requested (from the `login_redirect_to`\n\t * cookie, `?redirect=` on `/auth`, or the `redirectTo` body field).\n\t * Already sanitised to a safe relative path by the SDK.\n\t */\n\trequestedRedirect?: string;\n\t/** The redirect that would be used if the resolver is absent. Always safe. */\n\tdefaultRedirect: string;\n}\n\n/**\n * Context passed to the `logoutRedirect` function form, if the host wants to\n * decide the post-logout landing at request time (e.g. send authenticated\n * users to `/` and anonymous users to `/auth`).\n */\nexport interface LogoutRedirectContext {\n\t/** User present when logout was invoked, if any (may be null for stale sessions). */\n\tuser: UserLocals | null;\n}\n\nexport interface AuthHandlerOptions {\n\t/**\n\t * Login cookie TTL in seconds. Controls how long the login transaction\n\t * cookies (transaction ID, client token, remember-me) persist.\n\t * Default: 600 (10 minutes).\n\t */\n\tloginCookieTtlSeconds?: number;\n\n\t/**\n\t * Path to redirect unauthenticated users (logout, claim errors).\n\t * Default: '/auth'.\n\t */\n\tloginRedirect?: string;\n\n\t/**\n\t * Store a `login_redirect_to` cookie from the request body's `redirectTo`\n\t * field, then use it on claim to redirect the user back to their original page.\n\t * Default: true.\n\t */\n\tredirectCookie?: boolean;\n\n\t/** Optional structured logger for auth handlers. */\n\tlogger?: SentinelLogger;\n\n\t/**\n\t * Runs before the startLogin API call.\n\t * Return `{ error, status? }` to reject the request.\n\t */\n\tonBeforeLogin?: (event: import('@sveltejs/kit').RequestEvent, ctx: BeforeLoginContext) => Promise<BeforeLoginReject | void> | BeforeLoginReject | void;\n\n\t/** Runs after a successful startLogin (transaction cookies set). */\n\tonAfterLogin?: (event: import('@sveltejs/kit').RequestEvent, ctx: AfterLoginContext) => Promise<void> | void;\n\n\t/** Runs after successful token claim (session cookies set). */\n\tonAfterClaim?: (event: import('@sveltejs/kit').RequestEvent, ctx: AfterClaimContext) => Promise<void> | void;\n\n\t/** Runs on any handler error. */\n\tonError?: (event: import('@sveltejs/kit').RequestEvent, ctx: ErrorContext) => Promise<void> | void;\n\n\t/**\n\t * Called after a successful token claim to decide the post-login destination.\n\t * Return a safe relative path (starts with `/`, no protocol, no `//` prefix).\n\t * If the resolver throws, returns a non-string, an empty string, or an unsafe\n\t * path, the SDK falls back to `ctx.defaultRedirect`.\n\t *\n\t * @example\n\t * ```ts\n\t * resolveRedirect: async ({ user, requestedRedirect, defaultRedirect }) => {\n\t * if (user.roles.includes('admin')) return '/admin';\n\t * return requestedRedirect || defaultRedirect;\n\t * }\n\t * ```\n\t */\n\tresolveRedirect?: (\n\t\tevent: import('@sveltejs/kit').RequestEvent,\n\t\tctx: ResolveRedirectContext\n\t) => Promise<string> | string;\n\n\t/**\n\t * Where `POST /logout` and `GET /logout` redirect after clearing cookies.\n\t *\n\t * - Omitted: falls back to `loginRedirect` (current behaviour, `/auth` by default).\n\t * - String: treated as a literal safe relative path. Unsafe values fall back to `loginRedirect`.\n\t * - Function: called with the pre-logout user context; return a safe relative\n\t * path (async permitted). Throws / empty / unsafe → falls back to `loginRedirect`.\n\t *\n\t * @example\n\t * ```ts\n\t * logoutRedirect: '/'\n\t * // or\n\t * logoutRedirect: ({ user }) => user ? '/' : '/auth'\n\t * ```\n\t */\n\tlogoutRedirect?:\n\t\t| string\n\t\t| ((\n\t\t\t\tevent: import('@sveltejs/kit').RequestEvent,\n\t\t\t\tctx: LogoutRedirectContext\n\t\t ) => Promise<string> | string);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"","sourcesContent":["// src/core/types.ts\nimport type { SentinelLogger } from './logger.js';\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport interface SentinelConfig {\n\tapiUrl: string;\n\tbaseUrl: string;\n\t/**\n\t * When true, validateToken() performs a second GET /users/me request after\n\t * POST /auth/validate to refresh DB-backed profile fields such as roles.\n\t * Default: false. Keep disabled for high-volume per-request validation paths.\n\t */\n\thydrateUserProfile?: boolean;\n\t/**\n\t * Timeout in milliseconds for each individual fetch call to the Sentinel API.\n\t * Default: 8000 (8 seconds).\n\t */\n\ttimeoutMs?: number;\n\t/**\n\t * Maximum number of retries for retryable errors (network timeout, HTTP 502/503/504).\n\t * Non-retryable errors (400, 401, 403) are never retried.\n\t * Default: 1.\n\t */\n\tmaxRetries?: number;\n\t/** Optional structured logger. Falls back to console if not provided. */\n\tlogger?: SentinelLogger;\n}\n\n// ---------------------------------------------------------------------------\n// Typed API Responses (replaces the old untyped SentinelResponse bag)\n// ---------------------------------------------------------------------------\n\n/** Base fields shared by all Sentinel API responses. */\nexport interface SentinelResponseBase {\n\tsuccess?: boolean;\n\tcode?: number;\n\tmessage?: string;\n\terror?: string;\n}\n\n/** Response from POST /auth/login-passwordless */\nexport interface StartLoginResponse extends SentinelResponseBase {\n\ttransactionId?: string;\n\tclientToken?: string;\n}\n\n/** Response from POST /auth/poll */\nexport interface PollResponse extends SentinelResponseBase {\n\tstatus?: 'pending' | 'completed' | 'expired';\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back on the claimed poll\n\t * response. Present only when the originating register/startLogin call\n\t * supplied a `redirectContext`. Null or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/** Response from POST /auth/claim */\nexport interface ClaimResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n\t/** FanCastle / TRUSTIP-143 community access token (short-lived, scoped). */\n\tcommunity_access_token?: string;\n\tcommunity_id?: string;\n\tcommunity_scope?: string;\n}\n\n/** Response from POST /auth/refresh */\nexport interface RefreshResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n\tcommunity_access_token?: string;\n\tcommunity_id?: string;\n\tcommunity_scope?: string;\n}\n\n/** Response from POST /auth/validate */\nexport interface ValidateResponse extends SentinelResponseBase {\n\tvalid?: boolean;\n\tpayload?: DecodedToken;\n\tuser?: {\n\t\tsub: string;\n\t\temail: string;\n\t\troles: string[];\n\t};\n}\n\n/** Response from GET /users/me. */\nexport interface CurrentUserResponse extends SentinelResponseBase {\n\tid?: string;\n\temail?: string;\n\trole?: string;\n\troles?: string[];\n\tstatus?: string;\n\tlegalName?: string;\n\tocmeMemberNumber?: string;\n\twalletAddress?: string;\n\taliasName?: string;\n\ttwitterAccount?: string;\n\tyoutubeAccount?: string;\n\tcubicubeAddress?: string;\n}\n\n/** Response from POST /auth/register */\nexport interface RegisterResponse extends SentinelResponseBase {}\n\n/** Response from POST /auth/verify */\nexport interface VerifyResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back from the originating\n\t * register call. Present only when a `redirectContext` was supplied. Null\n\t * or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/** Response from POST /auth/complete-passwordless */\nexport interface CompletePasswordlessResponse extends SentinelResponseBase {\n\taccess_token?: string;\n\trefresh_token?: string;\n\t/**\n\t * Opaque transaction-bound redirect hint echoed back from the originating\n\t * startLogin call. Present only when a `redirectContext` was supplied. Null\n\t * or absent otherwise.\n\t */\n\tredirect_context?: string | null;\n}\n\n/**\n * @deprecated Use the specific typed response interfaces instead.\n * Kept for backward compatibility — will be removed in v2.0.0.\n */\nexport interface SentinelResponse {\n\tsuccess?: boolean;\n\tcode?: number;\n\tmessage?: string;\n\terror?: string;\n\t[key: string]: unknown;\n}\n\n// ---------------------------------------------------------------------------\n// JWT / Auth\n// ---------------------------------------------------------------------------\n\n/**\n * Decoded/API payload shape only: this type does not prove signature or claim verification.\n *\n * TRUSTIP-147 migration window: the Sentinel session token is moving from a\n * global `roles: string[]` claim to an identity-only `platform_admin: boolean`\n * claim. During the window a token MAY carry `roles`, `platform_admin`, or\n * both — so `roles` is optional and both claims are read when present.\n */\nexport interface DecodedToken {\n\tsub: string;\n\temail: string;\n\t/**\n\t * Global business roles carried by legacy tokens. OPTIONAL during the\n\t * TRUSTIP-147 migration window — a token that omits it is still valid, and\n\t * consumers must treat absence as an empty array (`[]`).\n\t */\n\troles?: string[];\n\t/**\n\t * Platform super-admin flag (identity-scoped, not community-scoped). Present\n\t * on tokens minted after the TRUSTIP-147 cutover; absent on legacy\n\t * role-bearing tokens. Surfaced as `UserLocals.platformAdmin` (default false).\n\t */\n\tplatform_admin?: boolean;\n\texp: number;\n\tiat: number;\n}\n\nexport interface UserLocals {\n\tisAuthenticated: boolean;\n\tid: string;\n\temail: string;\n\t/** Always a defined array — `[]` when the token carried no `roles` claim. */\n\troles: string[];\n\t/** Platform super-admin flag (TRUSTIP-147). Defaults to false when absent. */\n\tplatformAdmin: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// SvelteKit Handle Options\n// ---------------------------------------------------------------------------\n\nexport interface SentinelHandleOptions {\n\tprotectedRoutes?: string[];\n\tloginRedirect?: string;\n\tapiUrl?: string;\n\tbaseUrl?: string;\n\t/** Optional structured logger for auth middleware. */\n\tlogger?: SentinelLogger;\n\t/**\n\t * Full JWKS URL used to verify access-token signatures.\n\t * Default: derived from `apiUrl` (origin + `/.well-known/jwks.json`).\n\t * Also overridable via the `SENTINEL_JWKS_URL` environment variable.\n\t */\n\tjwksUrl?: string;\n\t/** JWKS cache TTL in milliseconds. Default: 600_000 (10 minutes). */\n\tjwksCacheTtlMs?: number;\n\t/**\n\t * DEVELOPMENT ONLY: disable JWKS signature verification and fall back to\n\t * decoding the (unverified) token payload locally. Equivalent to setting\n\t * the environment variable `SENTINEL_SKIP_JWKS_VERIFY=1`. Never enable\n\t * this in production — the admin gate would again trust unverified claims.\n\t */\n\tskipJwksVerify?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Auth Handler Options & Lifecycle Hooks\n// ---------------------------------------------------------------------------\n\n/** Data passed to the onBeforeLogin hook. */\nexport interface BeforeLoginContext {\n\temail: string;\n\trememberMe: boolean;\n\tredirectTo?: string;\n\taudience?: 'site_owner' | 'fan' | 'admin';\n\tcommunityId?: string;\n}\n\n/**\n * Return this from onBeforeLogin to reject the request.\n *\n * When `redirect` is set and is a safe relative path (starts with `/`, no\n * protocol, no `//` / `\\\\` prefix), the SDK issues a `303 See Other` to that\n * path instead of returning the JSON error body. Unsafe redirects are ignored\n * and the JSON-reject path is used as a fallback.\n *\n * @example\n * ```ts\n * onBeforeLogin: async (_event, { email }) => {\n * const gate = await isLoginAllowed(email);\n * if (!gate.ok) {\n * return {\n * error: gate.reason,\n * status: 403,\n * redirect: gate.reason === 'waitlisted' ? '/waitlist' : '/unauthorized'\n * };\n * }\n * }\n * ```\n */\nexport interface BeforeLoginReject {\n\terror: string;\n\tstatus?: number;\n\t/**\n\t * Optional branded destination the SDK will 303-redirect to instead of\n\t * returning a JSON error. Must be a safe relative path (starts with `/`,\n\t * no protocol, no `//` / `\\\\` prefix). Unsafe values fall back to the\n\t * JSON-reject behaviour.\n\t */\n\tredirect?: string;\n}\n\n/** Data passed to the onAfterLogin hook. */\nexport interface AfterLoginContext {\n\temail: string;\n\ttransactionId: string;\n}\n\n/** Data passed to the onAfterClaim hook. */\nexport interface AfterClaimContext {\n\taccessToken: string;\n\trefreshToken: string;\n\tredirectTo?: string;\n\tcommunityAccessToken?: string;\n\tcommunityId?: string;\n\tcommunityScope?: string;\n}\n\n/** Data passed to the onError hook. */\nexport interface ErrorContext {\n\thandler: 'start-login' | 'poll' | 'claim' | 'logout' | 'refresh' | 'register' | 'verify';\n\terror: unknown;\n}\n\n/**\n * Context passed to the `resolveRedirect` hook after a successful token claim.\n * Hosts use this to pick a post-login destination based on the user's roles,\n * a requested redirect, or fall back to the configured default.\n */\nexport interface ResolveRedirectContext {\n\t/** Newly authenticated user, decoded from the access token. */\n\tuser: UserLocals;\n\t/** Access token just issued for this user. */\n\taccessToken: string;\n\t/** Whether the user opted into a longer session at login time. */\n\trememberMe: boolean;\n\t/**\n\t * The redirect the client originally requested (from the `login_redirect_to`\n\t * cookie, `?redirect=` on `/auth`, or the `redirectTo` body field).\n\t * Already sanitised to a safe relative path by the SDK.\n\t */\n\trequestedRedirect?: string;\n\t/** The redirect that would be used if the resolver is absent. Always safe. */\n\tdefaultRedirect: string;\n}\n\n/**\n * Context passed to the `logoutRedirect` function form, if the host wants to\n * decide the post-logout landing at request time (e.g. send authenticated\n * users to `/` and anonymous users to `/auth`).\n */\nexport interface LogoutRedirectContext {\n\t/** User present when logout was invoked, if any (may be null for stale sessions). */\n\tuser: UserLocals | null;\n}\n\nexport interface AuthHandlerOptions {\n\t/**\n\t * Login cookie TTL in seconds. Controls how long the login transaction\n\t * cookies (transaction ID, client token, remember-me) persist.\n\t * Default: 600 (10 minutes).\n\t */\n\tloginCookieTtlSeconds?: number;\n\n\t/**\n\t * Path to redirect unauthenticated users (logout, claim errors).\n\t * Default: '/auth'.\n\t */\n\tloginRedirect?: string;\n\n\t/**\n\t * Store a `login_redirect_to` cookie from the request body's `redirectTo`\n\t * field, then use it on claim to redirect the user back to their original page.\n\t * Default: true.\n\t */\n\tredirectCookie?: boolean;\n\n\t/** Optional structured logger for auth handlers. */\n\tlogger?: SentinelLogger;\n\n\t/**\n\t * Runs before the startLogin API call.\n\t * Return `{ error, status? }` to reject the request.\n\t */\n\tonBeforeLogin?: (event: import('@sveltejs/kit').RequestEvent, ctx: BeforeLoginContext) => Promise<BeforeLoginReject | void> | BeforeLoginReject | void;\n\n\t/** Runs after a successful startLogin (transaction cookies set). */\n\tonAfterLogin?: (event: import('@sveltejs/kit').RequestEvent, ctx: AfterLoginContext) => Promise<void> | void;\n\n\t/** Runs after successful token claim (session cookies set). */\n\tonAfterClaim?: (event: import('@sveltejs/kit').RequestEvent, ctx: AfterClaimContext) => Promise<void> | void;\n\n\t/** Runs on any handler error. */\n\tonError?: (event: import('@sveltejs/kit').RequestEvent, ctx: ErrorContext) => Promise<void> | void;\n\n\t/**\n\t * Called after a successful token claim to decide the post-login destination.\n\t * Return a safe relative path (starts with `/`, no protocol, no `//` prefix).\n\t * If the resolver throws, returns a non-string, an empty string, or an unsafe\n\t * path, the SDK falls back to `ctx.defaultRedirect`.\n\t *\n\t * @example\n\t * ```ts\n\t * resolveRedirect: async ({ user, requestedRedirect, defaultRedirect }) => {\n\t * if (user.roles.includes('admin')) return '/admin';\n\t * return requestedRedirect || defaultRedirect;\n\t * }\n\t * ```\n\t */\n\tresolveRedirect?: (\n\t\tevent: import('@sveltejs/kit').RequestEvent,\n\t\tctx: ResolveRedirectContext\n\t) => Promise<string> | string;\n\n\t/**\n\t * Where `POST /logout` and `GET /logout` redirect after clearing cookies.\n\t *\n\t * - Omitted: falls back to `loginRedirect` (current behaviour, `/auth` by default).\n\t * - String: treated as a literal safe relative path. Unsafe values fall back to `loginRedirect`.\n\t * - Function: called with the pre-logout user context; return a safe relative\n\t * path (async permitted). Throws / empty / unsafe → falls back to `loginRedirect`.\n\t *\n\t * @example\n\t * ```ts\n\t * logoutRedirect: '/'\n\t * // or\n\t * logoutRedirect: ({ user }) => user ? '/' : '/auth'\n\t * ```\n\t */\n\tlogoutRedirect?:\n\t\t| string\n\t\t| ((\n\t\t\t\tevent: import('@sveltejs/kit').RequestEvent,\n\t\t\t\tctx: LogoutRedirectContext\n\t\t ) => Promise<string> | string);\n}\n"]}
|
|
@@ -2,6 +2,8 @@ import type { Cookies } from '@sveltejs/kit';
|
|
|
2
2
|
export declare const COOKIE_NAMES: {
|
|
3
3
|
readonly ACCESS_TOKEN: "access_token";
|
|
4
4
|
readonly REFRESH_TOKEN: "refresh_token";
|
|
5
|
+
/** TRUSTIP-143 per-community access token (FanCastle audience scope). */
|
|
6
|
+
readonly COMMUNITY_ACCESS_TOKEN: "fc_community_access_token";
|
|
5
7
|
readonly VERIFY_ACCESS_TOKEN: "verify_access_token";
|
|
6
8
|
readonly VERIFY_REFRESH_TOKEN: "verify_refresh_token";
|
|
7
9
|
};
|
|
@@ -34,6 +36,13 @@ export declare const COOKIE_OPTIONS: {
|
|
|
34
36
|
readonly sameSite: "lax";
|
|
35
37
|
readonly maxAge: 60;
|
|
36
38
|
};
|
|
39
|
+
readonly COMMUNITY_ACCESS_TOKEN: {
|
|
40
|
+
readonly path: "/";
|
|
41
|
+
readonly httpOnly: true;
|
|
42
|
+
readonly secure: true;
|
|
43
|
+
readonly sameSite: "lax";
|
|
44
|
+
readonly maxAge: number;
|
|
45
|
+
};
|
|
37
46
|
};
|
|
38
47
|
/**
|
|
39
48
|
* Set session cookies (access + refresh tokens)
|
|
@@ -50,5 +59,6 @@ export declare function getSessionCookies(cookies: Cookies): {
|
|
|
50
59
|
/**
|
|
51
60
|
* Clear all authentication cookies
|
|
52
61
|
*/
|
|
62
|
+
export declare function setCommunityAccessCookie(cookies: Cookies, communityAccessToken: string): void;
|
|
53
63
|
export declare function clearAuthCookies(cookies: Cookies): void;
|
|
54
64
|
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/sveltekit/cookies.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/sveltekit/cookies.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,eAAO,MAAM,YAAY;;;IAGxB,yEAAyE;;;;CAIhE,CAAC;AAEX,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCjB,CAAC;AAEX;;;GAGG;AACH,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,UAAU,GAAE,OAAe,GACzB,IAAI,CAQN;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG;IACpD,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CACjC,CAKA;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAM7F;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAIvD"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export const COOKIE_NAMES = {
|
|
2
2
|
ACCESS_TOKEN: 'access_token',
|
|
3
3
|
REFRESH_TOKEN: 'refresh_token',
|
|
4
|
+
/** TRUSTIP-143 per-community access token (FanCastle audience scope). */
|
|
5
|
+
COMMUNITY_ACCESS_TOKEN: 'fc_community_access_token',
|
|
4
6
|
VERIFY_ACCESS_TOKEN: 'verify_access_token',
|
|
5
7
|
VERIFY_REFRESH_TOKEN: 'verify_refresh_token'
|
|
6
8
|
};
|
|
@@ -32,6 +34,13 @@ export const COOKIE_OPTIONS = {
|
|
|
32
34
|
secure: true,
|
|
33
35
|
sameSite: 'lax',
|
|
34
36
|
maxAge: 60 // 1 minute — single-use, consumed immediately by /auth/verified
|
|
37
|
+
},
|
|
38
|
+
COMMUNITY_ACCESS_TOKEN: {
|
|
39
|
+
path: '/',
|
|
40
|
+
httpOnly: true,
|
|
41
|
+
secure: true,
|
|
42
|
+
sameSite: 'lax',
|
|
43
|
+
maxAge: 60 * 15 // matches Sentinel community access window (5–15 min)
|
|
35
44
|
}
|
|
36
45
|
};
|
|
37
46
|
/**
|
|
@@ -57,8 +66,12 @@ export function getSessionCookies(cookies) {
|
|
|
57
66
|
/**
|
|
58
67
|
* Clear all authentication cookies
|
|
59
68
|
*/
|
|
69
|
+
export function setCommunityAccessCookie(cookies, communityAccessToken) {
|
|
70
|
+
cookies.set(COOKIE_NAMES.COMMUNITY_ACCESS_TOKEN, communityAccessToken, COOKIE_OPTIONS.COMMUNITY_ACCESS_TOKEN);
|
|
71
|
+
}
|
|
60
72
|
export function clearAuthCookies(cookies) {
|
|
61
73
|
cookies.delete(COOKIE_NAMES.ACCESS_TOKEN, { path: '/' });
|
|
62
74
|
cookies.delete(COOKIE_NAMES.REFRESH_TOKEN, { path: '/' });
|
|
75
|
+
cookies.delete(COOKIE_NAMES.COMMUNITY_ACCESS_TOKEN, { path: '/' });
|
|
63
76
|
}
|
|
64
77
|
//# sourceMappingURL=cookies.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/sveltekit/cookies.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,YAAY,EAAE,cAAc;IAC5B,aAAa,EAAE,eAAe;IAC9B,mBAAmB,EAAE,qBAAqB;IAC1C,oBAAoB,EAAE,sBAAsB;CACnC,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,YAAY,EAAE;QACb,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,aAAa;KAC7B;IACD,kBAAkB,EAAE;QACnB,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,yCAAyC;KAClE;IACD,mBAAmB,EAAE;QACpB,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,8CAA8C;KAClE;IACD,YAAY,EAAE;QACb,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,CAAC,gEAAgE;KAC3E;CACQ,CAAC;AAEX;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAChC,OAAgB,EAChB,WAAmB,EACnB,YAAoB,EACpB,aAAsB,KAAK;IAE3B,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,WAAW,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;IAEjF,MAAM,mBAAmB,GAAG,UAAU;QACrC,CAAC,CAAC,cAAc,CAAC,kBAAkB;QACnC,CAAC,CAAC,cAAc,CAAC,mBAAmB,CAAC;IAEtC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,aAAa,EAAE,YAAY,EAAE,mBAAmB,CAAC,CAAC;AAC5E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAIjD,OAAO;QACN,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,YAAY,CAAC;QACnD,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,aAAa,CAAC;KACrD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAChD,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/sveltekit/cookies.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,YAAY,EAAE,cAAc;IAC5B,aAAa,EAAE,eAAe;IAC9B,yEAAyE;IACzE,sBAAsB,EAAE,2BAA2B;IACnD,mBAAmB,EAAE,qBAAqB;IAC1C,oBAAoB,EAAE,sBAAsB;CACnC,CAAC;AAEX,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,YAAY,EAAE;QACb,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,aAAa;KAC7B;IACD,kBAAkB,EAAE;QACnB,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,yCAAyC;KAClE;IACD,mBAAmB,EAAE;QACpB,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,8CAA8C;KAClE;IACD,YAAY,EAAE;QACb,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,CAAC,gEAAgE;KAC3E;IACD,sBAAsB,EAAE;QACvB,IAAI,EAAE,GAAG;QACT,QAAQ,EAAE,IAAI;QACd,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,sDAAsD;KACtE;CACQ,CAAC;AAEX;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAChC,OAAgB,EAChB,WAAmB,EACnB,YAAoB,EACpB,aAAsB,KAAK;IAE3B,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,WAAW,EAAE,cAAc,CAAC,YAAY,CAAC,CAAC;IAEjF,MAAM,mBAAmB,GAAG,UAAU;QACrC,CAAC,CAAC,cAAc,CAAC,kBAAkB;QACnC,CAAC,CAAC,cAAc,CAAC,mBAAmB,CAAC;IAEtC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,aAAa,EAAE,YAAY,EAAE,mBAAmB,CAAC,CAAC;AAC5E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAgB;IAIjD,OAAO;QACN,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,YAAY,CAAC;QACnD,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,aAAa,CAAC;KACrD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAgB,EAAE,oBAA4B;IACtF,OAAO,CAAC,GAAG,CACV,YAAY,CAAC,sBAAsB,EACnC,oBAAoB,EACpB,cAAc,CAAC,sBAAsB,CACrC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAChD,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,aAAa,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1D,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,sBAAsB,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;AACpE,CAAC","sourcesContent":["// src/sveltekit/cookies.ts\nimport type { Cookies } from '@sveltejs/kit';\n\nexport const COOKIE_NAMES = {\n\tACCESS_TOKEN: 'access_token',\n\tREFRESH_TOKEN: 'refresh_token',\n\t/** TRUSTIP-143 per-community access token (FanCastle audience scope). */\n\tCOMMUNITY_ACCESS_TOKEN: 'fc_community_access_token',\n\tVERIFY_ACCESS_TOKEN: 'verify_access_token',\n\tVERIFY_REFRESH_TOKEN: 'verify_refresh_token'\n} as const;\n\nexport const COOKIE_OPTIONS = {\n\tACCESS_TOKEN: {\n\t\tpath: '/',\n\t\thttpOnly: true,\n\t\tsecure: true,\n\t\tsameSite: 'lax' as const,\n\t\tmaxAge: 60 * 15 // 15 minutes\n\t},\n\tREFRESH_TOKEN_LONG: {\n\t\tpath: '/',\n\t\thttpOnly: true,\n\t\tsecure: true,\n\t\tsameSite: 'lax' as const,\n\t\tmaxAge: 60 * 60 * 24 * 7 // 7 days (when \"Remember Me\" is checked)\n\t},\n\tREFRESH_TOKEN_SHORT: {\n\t\tpath: '/',\n\t\thttpOnly: true,\n\t\tsecure: true,\n\t\tsameSite: 'lax' as const,\n\t\tmaxAge: 60 * 60 * 2 // 2 hours (when \"Remember Me\" is NOT checked)\n\t},\n\tVERIFY_TOKEN: {\n\t\tpath: '/auth',\n\t\thttpOnly: true,\n\t\tsecure: true,\n\t\tsameSite: 'lax' as const,\n\t\tmaxAge: 60 // 1 minute — single-use, consumed immediately by /auth/verified\n\t},\n\tCOMMUNITY_ACCESS_TOKEN: {\n\t\tpath: '/',\n\t\thttpOnly: true,\n\t\tsecure: true,\n\t\tsameSite: 'lax' as const,\n\t\tmaxAge: 60 * 15 // matches Sentinel community access window (5–15 min)\n\t}\n} as const;\n\n/**\n * Set session cookies (access + refresh tokens)\n * @param rememberMe - If true, refresh token lasts 7 days. If false, 2 hours.\n */\nexport function setSessionCookies(\n\tcookies: Cookies,\n\taccessToken: string,\n\trefreshToken: string,\n\trememberMe: boolean = false\n): void {\n\tcookies.set(COOKIE_NAMES.ACCESS_TOKEN, accessToken, COOKIE_OPTIONS.ACCESS_TOKEN);\n\n\tconst refreshTokenOptions = rememberMe\n\t\t? COOKIE_OPTIONS.REFRESH_TOKEN_LONG\n\t\t: COOKIE_OPTIONS.REFRESH_TOKEN_SHORT;\n\n\tcookies.set(COOKIE_NAMES.REFRESH_TOKEN, refreshToken, refreshTokenOptions);\n}\n\n/**\n * Get session cookies (access + refresh tokens)\n */\nexport function getSessionCookies(cookies: Cookies): {\n\taccessToken: string | undefined;\n\trefreshToken: string | undefined;\n} {\n\treturn {\n\t\taccessToken: cookies.get(COOKIE_NAMES.ACCESS_TOKEN),\n\t\trefreshToken: cookies.get(COOKIE_NAMES.REFRESH_TOKEN)\n\t};\n}\n\n/**\n * Clear all authentication cookies\n */\nexport function setCommunityAccessCookie(cookies: Cookies, communityAccessToken: string): void {\n\tcookies.set(\n\t\tCOOKIE_NAMES.COMMUNITY_ACCESS_TOKEN,\n\t\tcommunityAccessToken,\n\t\tCOOKIE_OPTIONS.COMMUNITY_ACCESS_TOKEN\n\t);\n}\n\nexport function clearAuthCookies(cookies: Cookies): void {\n\tcookies.delete(COOKIE_NAMES.ACCESS_TOKEN, { path: '/' });\n\tcookies.delete(COOKIE_NAMES.REFRESH_TOKEN, { path: '/' });\n\tcookies.delete(COOKIE_NAMES.COMMUNITY_ACCESS_TOKEN, { path: '/' });\n}\n\n"]}
|
package/dist/sveltekit/handle.js
CHANGED
|
@@ -2,7 +2,7 @@ import { SentinelClient } from '../core/client.js';
|
|
|
2
2
|
import { isTokenExpired, decodeTokenLocally } from '../core/auth-utils.js';
|
|
3
3
|
import { createJwksVerifier } from '../core/jwks.js';
|
|
4
4
|
import { createLogger } from '../core/logger.js';
|
|
5
|
-
import { COOKIE_NAMES, COOKIE_OPTIONS } from './cookies.js';
|
|
5
|
+
import { COOKIE_NAMES, COOKIE_OPTIONS, setCommunityAccessCookie } from './cookies.js';
|
|
6
6
|
/**
|
|
7
7
|
* DEVELOPMENT-ONLY escape hatch. When set to "1" (or "true") the handle skips
|
|
8
8
|
* JWKS signature verification and trusts the decoded token payload, which is
|
|
@@ -220,7 +220,10 @@ function createUserLocals(decoded) {
|
|
|
220
220
|
isAuthenticated: true,
|
|
221
221
|
id: decoded.sub,
|
|
222
222
|
email: decoded.email,
|
|
223
|
-
|
|
223
|
+
// TRUSTIP-147: roles may be absent on post-cutover tokens — keep a
|
|
224
|
+
// defined array for existing consumers.
|
|
225
|
+
roles: decoded.roles || [],
|
|
226
|
+
platformAdmin: decoded.platform_admin === true
|
|
224
227
|
};
|
|
225
228
|
}
|
|
226
229
|
async function tryRefreshToken(event, config, refreshToken, log, verifier) {
|
|
@@ -236,6 +239,9 @@ async function tryRefreshToken(event, config, refreshToken, log, verifier) {
|
|
|
236
239
|
if (result.refresh_token) {
|
|
237
240
|
event.cookies.set(COOKIE_NAMES.REFRESH_TOKEN, result.refresh_token, COOKIE_OPTIONS.REFRESH_TOKEN_LONG);
|
|
238
241
|
}
|
|
242
|
+
if (result.community_access_token) {
|
|
243
|
+
setCommunityAccessCookie(event.cookies, result.community_access_token);
|
|
244
|
+
}
|
|
239
245
|
// Verify the freshly-issued token's signature before trusting its
|
|
240
246
|
// payload (same JWKS path as the cookie flow). The cookie is kept —
|
|
241
247
|
// it was just minted by the backend over TLS — but locals.user is
|