@naylence/runtime 0.3.5-test.911 → 0.3.5-test.913

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.
Files changed (31) hide show
  1. package/dist/browser/index.cjs +72 -164
  2. package/dist/browser/index.mjs +72 -164
  3. package/dist/cjs/naylence/fame/config/extended-fame-config.js +52 -0
  4. package/dist/cjs/naylence/fame/http/jwks-api-router.js +16 -18
  5. package/dist/cjs/naylence/fame/http/oauth2-server.js +28 -31
  6. package/dist/cjs/naylence/fame/http/oauth2-token-router.js +153 -8
  7. package/dist/cjs/naylence/fame/http/openid-configuration-router.js +30 -32
  8. package/dist/cjs/naylence/fame/node/admission/admission-profile-factory.js +18 -0
  9. package/dist/cjs/naylence/fame/security/crypto/providers/default-crypto-provider.js +0 -162
  10. package/dist/cjs/version.js +2 -2
  11. package/dist/esm/naylence/fame/config/extended-fame-config.js +52 -0
  12. package/dist/esm/naylence/fame/http/jwks-api-router.js +16 -17
  13. package/dist/esm/naylence/fame/http/oauth2-server.js +28 -31
  14. package/dist/esm/naylence/fame/http/oauth2-token-router.js +153 -8
  15. package/dist/esm/naylence/fame/http/openid-configuration-router.js +30 -31
  16. package/dist/esm/naylence/fame/node/admission/admission-profile-factory.js +18 -0
  17. package/dist/esm/naylence/fame/security/crypto/providers/default-crypto-provider.js +0 -162
  18. package/dist/esm/version.js +2 -2
  19. package/dist/node/index.cjs +72 -164
  20. package/dist/node/index.mjs +72 -164
  21. package/dist/node/node.cjs +299 -249
  22. package/dist/node/node.mjs +299 -249
  23. package/dist/types/naylence/fame/http/jwks-api-router.d.ts +8 -8
  24. package/dist/types/naylence/fame/http/oauth2-server.d.ts +3 -3
  25. package/dist/types/naylence/fame/http/oauth2-token-router.d.ts +5 -5
  26. package/dist/types/naylence/fame/http/openid-configuration-router.d.ts +8 -8
  27. package/dist/types/naylence/fame/security/crypto/providers/default-crypto-provider.d.ts +0 -1
  28. package/dist/types/version.d.ts +1 -1
  29. package/package.json +4 -6
  30. package/dist/esm/naylence/fame/fastapi/oauth2-server.js +0 -205
  31. package/dist/types/naylence/fame/fastapi/oauth2-server.d.ts +0 -22
@@ -11,12 +11,9 @@ import { x25519 } from '@noble/curves/ed25519.js';
11
11
  import { hkdf } from '@noble/hashes/hkdf.js';
12
12
  import { sha256, sha512 } from '@noble/hashes/sha2.js';
13
13
  import { utf8ToBytes, bytesToHex, randomBytes, concatBytes } from '@noble/hashes/utils.js';
14
- import { AsnConvert, OctetString } from '@peculiar/asn1-schema';
15
- import { SubjectPublicKeyInfo, SubjectAlternativeName, GeneralName, Extensions, Extension, id_ce_subjectAltName, Attribute, AlgorithmIdentifier, AttributeTypeAndValue, AttributeValue, Name, RelativeDistinguishedName } from '@peculiar/asn1-x509';
16
- import { Attributes, CertificationRequestInfo, CertificationRequest } from '@peculiar/asn1-csr';
17
14
  import fastify from 'fastify';
18
15
  import websocketPlugin from '@fastify/websocket';
19
- import express from 'express';
16
+ import formbody from '@fastify/formbody';
20
17
  import { createHash, timingSafeEqual, randomBytes as randomBytes$1 } from 'node:crypto';
21
18
  import { hashes, sign, verify } from '@noble/ed25519';
22
19
 
@@ -5366,12 +5363,12 @@ for (const [name, config] of Object.entries(SQLITE_PROFILES)) {
5366
5363
  }
5367
5364
 
5368
5365
  // This file is auto-generated during build - do not edit manually
5369
- // Generated from package.json version: 0.3.5-test.911
5366
+ // Generated from package.json version: 0.3.5-test.913
5370
5367
  /**
5371
5368
  * The package version, injected at build time.
5372
5369
  * @internal
5373
5370
  */
5374
- const VERSION = '0.3.5-test.911';
5371
+ const VERSION = '0.3.5-test.913';
5375
5372
 
5376
5373
  /**
5377
5374
  * Fame errors module - Fame protocol specific error classes
@@ -14700,6 +14697,57 @@ const CONFIG_SEARCH_PATHS = [
14700
14697
  ];
14701
14698
  const fsModuleSpecifier = String.fromCharCode(102) + String.fromCharCode(115);
14702
14699
  let cachedFsModule = null;
14700
+ // Capture this module's URL without triggering TypeScript's import.meta restriction on CJS builds
14701
+ const currentModuleUrl = (() => {
14702
+ try {
14703
+ return (0, eval)('import.meta.url');
14704
+ }
14705
+ catch {
14706
+ return undefined;
14707
+ }
14708
+ })();
14709
+ // Shared flag that allows synchronous waiting for the Node-specific require shim
14710
+ const requireReadyFlag = isNode && typeof SharedArrayBuffer !== 'undefined'
14711
+ ? new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT))
14712
+ : null;
14713
+ if (requireReadyFlag) {
14714
+ // 0 means initializing, 1 means ready (success or failure)
14715
+ Atomics.store(requireReadyFlag, 0, 0);
14716
+ // Prepare a CommonJS-style require when running in pure ESM contexts
14717
+ void (async () => {
14718
+ try {
14719
+ if (typeof require !== 'function') {
14720
+ const moduleNamespace = (await import('node:module'));
14721
+ const createRequire = moduleNamespace.createRequire;
14722
+ if (typeof createRequire === 'function') {
14723
+ const fallbackPath = `${process.cwd()}/.__naylence_require_shim__.mjs`;
14724
+ const nodeRequire = createRequire(currentModuleUrl ?? fallbackPath);
14725
+ globalThis.require = nodeRequire;
14726
+ }
14727
+ }
14728
+ }
14729
+ catch {
14730
+ // Ignore failures – getFsModule will surface a helpful error when needed
14731
+ }
14732
+ })()
14733
+ .catch(() => {
14734
+ // Ignore async errors – the ready flag will still unblock consumers
14735
+ })
14736
+ .finally(() => {
14737
+ Atomics.store(requireReadyFlag, 0, 1);
14738
+ Atomics.notify(requireReadyFlag, 0);
14739
+ });
14740
+ }
14741
+ function ensureRequireReady() {
14742
+ if (!requireReadyFlag) {
14743
+ return;
14744
+ }
14745
+ if (Atomics.load(requireReadyFlag, 0) === 1) {
14746
+ return;
14747
+ }
14748
+ // Block until the asynchronous loader finishes initialising
14749
+ Atomics.wait(requireReadyFlag, 0, 0);
14750
+ }
14703
14751
  function getFsModule() {
14704
14752
  if (cachedFsModule) {
14705
14753
  return cachedFsModule;
@@ -14707,6 +14755,7 @@ function getFsModule() {
14707
14755
  if (!isNode) {
14708
14756
  throw new Error('File system access is not available in this environment');
14709
14757
  }
14758
+ ensureRequireReady();
14710
14759
  if (typeof require === 'function') {
14711
14760
  try {
14712
14761
  cachedFsModule = require(fsModuleSpecifier);
@@ -26570,11 +26619,6 @@ const DEFAULT_AUDIENCE = 'router-dev';
26570
26619
  const DEFAULT_TTL_SEC$1 = 3600;
26571
26620
  const DEFAULT_HMAC_SECRET_BYTES = 32;
26572
26621
  const ENCRYPTION_ALG = 'ECDH-ES';
26573
- const EXTENSION_REQUEST_OID = '1.2.840.113549.1.9.14';
26574
- const COMMON_NAME_OID = '2.5.4.3';
26575
- const ED25519_OID = '1.3.101.112';
26576
- const CSR_PEM_TAG = 'CERTIFICATE REQUEST';
26577
- const LOGICAL_URI_PREFIX = 'naylence://';
26578
26622
  function normalizeDefaultCryptoProviderOptions(options) {
26579
26623
  if (!options) {
26580
26624
  return {};
@@ -26840,76 +26884,6 @@ class DefaultCryptoProvider {
26840
26884
  has_chain: Boolean(certificateChainPem),
26841
26885
  });
26842
26886
  }
26843
- async createCsr(nodeId, physicalPath, logicals, subjectName) {
26844
- const trimmedNodeId = assertNonEmptyString(nodeId, 'nodeId');
26845
- const trimmedPhysicalPath = assertNonEmptyString(physicalPath, 'physicalPath');
26846
- try {
26847
- if (this.artifacts.signing.algorithm !== 'EdDSA') {
26848
- throw new Error('CSR creation only supported for Ed25519 signing keys in the default crypto provider');
26849
- }
26850
- const cryptoImpl = await ensureWebCrypto();
26851
- const privateKey = await cryptoImpl.subtle.importKey('pkcs8', pemToArrayBuffer(this.signingPrivatePem), {
26852
- name: 'Ed25519',
26853
- }, false, ['sign']);
26854
- const publicKeyDer = pemToArrayBuffer(this.signingPublicPem);
26855
- const subjectPkInfo = AsnConvert.parse(publicKeyDer, SubjectPublicKeyInfo);
26856
- const sanitizedLogicals = Array.isArray(logicals)
26857
- ? logicals.filter((value) => typeof value === 'string' && value.trim().length > 0)
26858
- : [];
26859
- const commonName = typeof subjectName === 'string' && subjectName.trim().length > 0
26860
- ? subjectName.trim()
26861
- : trimmedNodeId;
26862
- const subject = buildSubjectName(commonName);
26863
- const attributes = new Attributes();
26864
- if (sanitizedLogicals.length > 0) {
26865
- const san = new SubjectAlternativeName(sanitizedLogicals.map((logical) => new GeneralName({
26866
- uniformResourceIdentifier: `${LOGICAL_URI_PREFIX}${logical}`,
26867
- })));
26868
- const extensions = new Extensions([
26869
- new Extension({
26870
- extnID: id_ce_subjectAltName,
26871
- critical: false,
26872
- extnValue: new OctetString(AsnConvert.serialize(san)),
26873
- }),
26874
- ]);
26875
- attributes.push(new Attribute({
26876
- type: EXTENSION_REQUEST_OID,
26877
- values: [AsnConvert.serialize(extensions)],
26878
- }));
26879
- }
26880
- const requestInfo = new CertificationRequestInfo({
26881
- subject,
26882
- subjectPKInfo: subjectPkInfo,
26883
- attributes,
26884
- });
26885
- const requestInfoDer = AsnConvert.serialize(requestInfo);
26886
- const signature = await cryptoImpl.subtle.sign('Ed25519', privateKey, requestInfoDer);
26887
- const certificationRequest = new CertificationRequest({
26888
- certificationRequestInfo: requestInfo,
26889
- signatureAlgorithm: new AlgorithmIdentifier({
26890
- algorithm: ED25519_OID,
26891
- }),
26892
- signature: encodeBitString(signature),
26893
- });
26894
- certificationRequest.certificationRequestInfoRaw = requestInfoDer;
26895
- const csrDer = AsnConvert.serialize(certificationRequest);
26896
- const csrPem = arrayBufferToPem(csrDer, CSR_PEM_TAG);
26897
- logger$y.debug('csr_created', {
26898
- node_id: trimmedNodeId,
26899
- physical_path: trimmedPhysicalPath,
26900
- logical_count: sanitizedLogicals.length,
26901
- });
26902
- return csrPem;
26903
- }
26904
- catch (error) {
26905
- logger$y.error('csr_creation_failed', {
26906
- node_id: trimmedNodeId,
26907
- physical_path: trimmedPhysicalPath,
26908
- error: error instanceof Error ? error.message : String(error),
26909
- });
26910
- throw error;
26911
- }
26912
- }
26913
26887
  }
26914
26888
  async function buildProviderArtifacts(options) {
26915
26889
  const algorithm = normalizeAlgorithm(options.algorithm ?? readEnvAlgorithm());
@@ -27145,90 +27119,6 @@ function pemToDerBase64(pem) {
27145
27119
  // Ensure the output is valid base64 without whitespace
27146
27120
  return base64.replace(/\s+/g, '');
27147
27121
  }
27148
- let cryptoPromise = null;
27149
- async function ensureWebCrypto() {
27150
- if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto?.subtle) {
27151
- return globalThis.crypto;
27152
- }
27153
- if (!cryptoPromise) {
27154
- if (typeof process !== 'undefined' &&
27155
- typeof process.versions?.node === 'string') {
27156
- cryptoPromise = import('node:crypto').then((module) => {
27157
- const webcrypto = module.webcrypto;
27158
- if (!webcrypto || !webcrypto.subtle) {
27159
- throw new Error('WebCrypto API is not available in this Node.js runtime');
27160
- }
27161
- globalThis.crypto = webcrypto;
27162
- return webcrypto;
27163
- });
27164
- }
27165
- else {
27166
- cryptoPromise = Promise.reject(new Error('WebCrypto API is not available in this environment'));
27167
- }
27168
- }
27169
- return cryptoPromise;
27170
- }
27171
- function pemToArrayBuffer(pem) {
27172
- const normalized = pem
27173
- .replace(/-----BEGIN[^-]+-----/g, '')
27174
- .replace(/-----END[^-]+-----/g, '')
27175
- .replace(/\s+/g, '');
27176
- const bytes = base64ToBytes$1(normalized);
27177
- return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
27178
- }
27179
- function base64ToBytes$1(base64) {
27180
- if (typeof Buffer !== 'undefined') {
27181
- const buffer = Buffer.from(base64, 'base64');
27182
- const bytes = new Uint8Array(buffer.length);
27183
- for (let i = 0; i < buffer.length; i += 1) {
27184
- bytes[i] = buffer[i];
27185
- }
27186
- return bytes;
27187
- }
27188
- if (typeof atob === 'function') {
27189
- const binary = atob(base64);
27190
- const bytes = new Uint8Array(binary.length);
27191
- for (let i = 0; i < binary.length; i += 1) {
27192
- bytes[i] = binary.charCodeAt(i);
27193
- }
27194
- return bytes;
27195
- }
27196
- throw new Error('No base64 decoder available in this environment');
27197
- }
27198
- function arrayBufferToPem(buffer, tag) {
27199
- const base64 = bytesToBase64(new Uint8Array(buffer));
27200
- return `-----BEGIN ${tag}-----\n${formatPem(base64)}\n-----END ${tag}-----\n`;
27201
- }
27202
- function formatPem(base64) {
27203
- const lines = [];
27204
- for (let i = 0; i < base64.length; i += 64) {
27205
- lines.push(base64.slice(i, i + 64));
27206
- }
27207
- return lines.join('\n');
27208
- }
27209
- function encodeBitString(signature) {
27210
- const bytes = new Uint8Array(signature);
27211
- const bitString = new Uint8Array(bytes.length + 1);
27212
- bitString.set(bytes, 1);
27213
- return bitString.buffer;
27214
- }
27215
- function buildSubjectName(commonName) {
27216
- const attribute = new AttributeTypeAndValue({
27217
- type: COMMON_NAME_OID,
27218
- value: new AttributeValue({ utf8String: commonName }),
27219
- });
27220
- return new Name([new RelativeDistinguishedName([attribute])]);
27221
- }
27222
- function assertNonEmptyString(value, name) {
27223
- if (typeof value !== 'string') {
27224
- throw new TypeError(`${name} must be a string`);
27225
- }
27226
- const trimmed = value.trim();
27227
- if (trimmed.length === 0) {
27228
- throw new TypeError(`${name} must be a non-empty string`);
27229
- }
27230
- return trimmed;
27231
- }
27232
27122
  function cloneJson(value) {
27233
27123
  return JSON.parse(JSON.stringify(value));
27234
27124
  }
@@ -32388,7 +32278,7 @@ var inProcessFameFabricFactory = /*#__PURE__*/Object.freeze({
32388
32278
  });
32389
32279
 
32390
32280
  /**
32391
- * JWKS (JSON Web Key Set) API router for Express
32281
+ * JWKS (JSON Web Key Set) API plugin for Fastify
32392
32282
  *
32393
32283
  * Provides /.well-known/jwks.json endpoint for public key discovery
32394
32284
  * Used by OAuth2/JWT token verification
@@ -32471,23 +32361,22 @@ function filterKeysByType(jwksData, allowedTypes) {
32471
32361
  return { ...jwksData, keys: filteredKeys };
32472
32362
  }
32473
32363
  /**
32474
- * Create an Express router that exposes JWKS at /.well-known/jwks.json
32364
+ * Create a Fastify plugin that exposes JWKS at /.well-known/jwks.json
32475
32365
  *
32476
32366
  * @param options - Router configuration options
32477
- * @returns Express router with JWKS endpoint
32367
+ * @returns Fastify plugin with JWKS endpoint
32478
32368
  *
32479
32369
  * @example
32480
32370
  * ```typescript
32481
- * import express from 'express';
32371
+ * import Fastify from 'fastify';
32482
32372
  * import { createJwksRouter } from '@naylence/runtime';
32483
32373
  *
32484
- * const app = express();
32374
+ * const app = Fastify();
32485
32375
  * const cryptoProvider = new MyCryptoProvider();
32486
- * app.use(createJwksRouter({ cryptoProvider }));
32376
+ * app.register(createJwksRouter({ cryptoProvider }));
32487
32377
  * ```
32488
32378
  */
32489
32379
  function createJwksRouter(options = {}) {
32490
- const router = express.Router();
32491
32380
  const { getJwksJson, cryptoProvider, prefix = DEFAULT_PREFIX$2, keyTypes, } = normalizeCreateJwksRouterOptions(options);
32492
32381
  // Get JWKS data
32493
32382
  let jwks;
@@ -32510,26 +32399,172 @@ function createJwksRouter(options = {}) {
32510
32399
  key_types: allowedKeyTypes,
32511
32400
  total_keys: jwks.keys.length,
32512
32401
  });
32513
- // JWKS endpoint
32514
- router.get(`${prefix}/.well-known/jwks.json`, (_req, res) => {
32515
- const filteredJwks = filterKeysByType(jwks, allowedKeyTypes);
32516
- logger$l.debug('jwks_served', {
32517
- total_keys: jwks.keys.length,
32518
- filtered_keys: filteredJwks.keys.length,
32519
- });
32520
- res.json(filteredJwks);
32521
- });
32522
- return router;
32402
+ const plugin = async (instance) => {
32403
+ instance.get(`${prefix}/.well-known/jwks.json`, async (_request, reply) => {
32404
+ const filteredJwks = filterKeysByType(jwks, allowedKeyTypes);
32405
+ logger$l.debug('jwks_served', {
32406
+ total_keys: jwks.keys.length,
32407
+ filtered_keys: filteredJwks.keys.length,
32408
+ });
32409
+ reply.send(filteredJwks);
32410
+ });
32411
+ };
32412
+ return plugin;
32523
32413
  }
32524
32414
 
32525
32415
  /**
32526
- * OAuth2 client credentials and authorization code (PKCE) grant router for Express
32416
+ * OAuth2 client credentials and authorization code (PKCE) grant router for Fastify
32527
32417
  *
32528
32418
  * Provides /oauth/token and /oauth/authorize endpoints for local development and testing.
32529
32419
  * Implements OAuth2 client credentials grant with JWT token issuance and
32530
32420
  * OAuth2 authorization code grant with PKCE verification.
32531
32421
  */
32532
32422
  const logger$k = getLogger('naylence.fame.http.oauth2_token_router');
32423
+ class RouterCompat {
32424
+ constructor() {
32425
+ this.routes = [];
32426
+ }
32427
+ get(path, handler) {
32428
+ this.routes.push({ method: 'GET', path, handler });
32429
+ }
32430
+ post(path, handler) {
32431
+ this.routes.push({ method: 'POST', path, handler });
32432
+ }
32433
+ toPlugin() {
32434
+ return async (fastify) => {
32435
+ await fastify.register(formbody);
32436
+ for (const route of this.routes) {
32437
+ fastify.route({
32438
+ method: route.method,
32439
+ url: route.path,
32440
+ handler: async (request, reply) => {
32441
+ const compatRequest = toCompatRequest(request);
32442
+ const compatResponse = new FastifyResponseAdapter(reply);
32443
+ await route.handler(compatRequest, compatResponse);
32444
+ },
32445
+ });
32446
+ }
32447
+ };
32448
+ }
32449
+ }
32450
+ class FastifyResponseAdapter {
32451
+ constructor(reply) {
32452
+ this.reply = reply;
32453
+ }
32454
+ status(code) {
32455
+ this.reply.status(code);
32456
+ return this;
32457
+ }
32458
+ set(field, value) {
32459
+ if (field.toLowerCase() === 'set-cookie') {
32460
+ this.appendHeader(field, value);
32461
+ }
32462
+ else {
32463
+ this.reply.header(field, value);
32464
+ }
32465
+ return this;
32466
+ }
32467
+ type(contentType) {
32468
+ const normalized = contentType === 'html'
32469
+ ? 'text/html'
32470
+ : contentType === 'json'
32471
+ ? 'application/json'
32472
+ : contentType;
32473
+ this.reply.type(normalized);
32474
+ return this;
32475
+ }
32476
+ json(payload) {
32477
+ this.reply.send(payload);
32478
+ }
32479
+ send(payload) {
32480
+ this.reply.send(payload);
32481
+ }
32482
+ redirect(statusOrUrl, maybeUrl) {
32483
+ if (typeof statusOrUrl === 'number') {
32484
+ if (maybeUrl === undefined) {
32485
+ throw new Error('redirect url is required when status code is provided');
32486
+ }
32487
+ this.reply.status(statusOrUrl);
32488
+ this.reply.header('Location', maybeUrl);
32489
+ this.reply.send();
32490
+ }
32491
+ else {
32492
+ this.reply.redirect(statusOrUrl);
32493
+ }
32494
+ }
32495
+ cookie(name, value, options) {
32496
+ const serialized = serializeCookie(name, value, options);
32497
+ this.appendHeader('Set-Cookie', serialized);
32498
+ }
32499
+ appendHeader(name, value) {
32500
+ const existing = this.reply.getHeader(name);
32501
+ if (Array.isArray(existing)) {
32502
+ this.reply.header(name, [...existing, value]);
32503
+ }
32504
+ else if (typeof existing === 'string') {
32505
+ this.reply.header(name, [existing, value]);
32506
+ }
32507
+ else if (existing === undefined) {
32508
+ this.reply.header(name, value);
32509
+ }
32510
+ else {
32511
+ this.reply.header(name, [String(existing), value]);
32512
+ }
32513
+ }
32514
+ }
32515
+ function toCompatRequest(request) {
32516
+ const headers = {};
32517
+ for (const [key, value] of Object.entries(request.headers)) {
32518
+ if (typeof value === 'string') {
32519
+ headers[key.toLowerCase()] = value;
32520
+ }
32521
+ else if (Array.isArray(value)) {
32522
+ headers[key.toLowerCase()] = value.join(', ');
32523
+ }
32524
+ else if (value !== undefined && value !== null) {
32525
+ headers[key.toLowerCase()] = String(value);
32526
+ }
32527
+ else {
32528
+ headers[key.toLowerCase()] = undefined;
32529
+ }
32530
+ }
32531
+ return {
32532
+ body: request.body,
32533
+ headers,
32534
+ method: request.method,
32535
+ originalUrl: request.raw.url ?? request.url,
32536
+ query: request.query ?? {},
32537
+ };
32538
+ }
32539
+ function serializeCookie(name, value, options) {
32540
+ const segments = [
32541
+ `${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
32542
+ ];
32543
+ if (options.maxAge !== undefined) {
32544
+ const maxAgeMs = options.maxAge;
32545
+ const maxAgeSeconds = Math.floor(maxAgeMs / 1000);
32546
+ segments.push(`Max-Age=${maxAgeSeconds}`);
32547
+ const expires = new Date(Date.now() + maxAgeMs).toUTCString();
32548
+ segments.push(`Expires=${expires}`);
32549
+ }
32550
+ segments.push(`Path=${options.path ?? '/'}`);
32551
+ if (options.httpOnly) {
32552
+ segments.push('HttpOnly');
32553
+ }
32554
+ if (options.secure) {
32555
+ segments.push('Secure');
32556
+ }
32557
+ if (options.sameSite) {
32558
+ const normalized = options.sameSite.toLowerCase();
32559
+ const formatted = normalized === 'strict'
32560
+ ? 'Strict'
32561
+ : normalized === 'none'
32562
+ ? 'None'
32563
+ : 'Lax';
32564
+ segments.push(`SameSite=${formatted}`);
32565
+ }
32566
+ return segments.join('; ');
32567
+ }
32533
32568
  const DEFAULT_PREFIX$1 = '/oauth';
32534
32569
  const ENV_VAR_CLIENT_ID = 'FAME_JWT_CLIENT_ID';
32535
32570
  const ENV_VAR_CLIENT_SECRET = 'FAME_JWT_CLIENT_SECRET';
@@ -32951,11 +32986,11 @@ function respondInvalidClient(res) {
32951
32986
  });
32952
32987
  }
32953
32988
  /**
32954
- * Create an Express router that implements OAuth2 token and authorization endpoints
32989
+ * Create a Fastify plugin that implements OAuth2 token and authorization endpoints
32955
32990
  * with support for client credentials and authorization code (PKCE) grants.
32956
32991
  *
32957
32992
  * @param options - Router configuration options
32958
- * @returns Express router with OAuth2 token and authorization endpoints
32993
+ * @returns Fastify plugin with OAuth2 token and authorization endpoints
32959
32994
  *
32960
32995
  * Environment Variables:
32961
32996
  * FAME_JWT_CLIENT_ID: OAuth2 client identifier
@@ -32969,7 +33004,7 @@ function respondInvalidClient(res) {
32969
33004
  * FAME_OAUTH_CODE_TTL_SEC: Authorization code TTL in seconds (optional, default: 300)
32970
33005
  */
32971
33006
  function createOAuth2TokenRouter(options) {
32972
- const router = express.Router();
33007
+ const router = new RouterCompat();
32973
33008
  const { cryptoProvider, prefix = DEFAULT_PREFIX$1, issuer, audience, tokenTtlSec, allowedScopes: configAllowedScopes, algorithm: configAlgorithm, enablePkce: configEnablePkce, allowPublicClients: configAllowPublicClients, authorizationCodeTtlSec: configAuthorizationCodeTtlSec, enableDevLogin: configEnableDevLogin, devLoginUsername: configDevLoginUsername, devLoginPassword: configDevLoginPassword, devLoginSessionTtlSec: configDevLoginSessionTtlSec, devLoginCookieName: configDevLoginCookieName, devLoginSecureCookie: configDevLoginSecureCookie, devLoginTitle: configDevLoginTitle, } = normalizeCreateOAuth2TokenRouterOptions(options);
32974
33009
  if (!cryptoProvider) {
32975
33010
  throw new Error('cryptoProvider is required to create OAuth2 token router');
@@ -33268,7 +33303,7 @@ function createOAuth2TokenRouter(options) {
33268
33303
  };
33269
33304
  router.post(`${prefix}/logout`, logoutHandler);
33270
33305
  router.get(`${prefix}/logout`, logoutHandler);
33271
- router.post(`${prefix}/token`, async (req, res, next) => {
33306
+ router.post(`${prefix}/token`, async (req, res) => {
33272
33307
  try {
33273
33308
  cleanupAuthorizationCodes(authorizationCodes, Date.now());
33274
33309
  const { grant_type, client_id, client_secret, scope, audience: reqAudience, code, redirect_uri, code_verifier, } = req.body ?? {};
@@ -33453,7 +33488,7 @@ function createOAuth2TokenRouter(options) {
33453
33488
  }
33454
33489
  catch (error) {
33455
33490
  logger$k.error('oauth2_token_error', { error: error.message });
33456
- next(error);
33491
+ throw error;
33457
33492
  }
33458
33493
  });
33459
33494
  async function issueTokenResponse(params) {
@@ -33486,11 +33521,11 @@ function createOAuth2TokenRouter(options) {
33486
33521
  }
33487
33522
  return response;
33488
33523
  }
33489
- return router;
33524
+ return router.toPlugin();
33490
33525
  }
33491
33526
 
33492
33527
  /**
33493
- * OpenID Connect Discovery configuration router for Express
33528
+ * OpenID Connect Discovery configuration plugin for Fastify
33494
33529
  *
33495
33530
  * Provides /.well-known/openid-configuration endpoint for OAuth2/OIDC client auto-discovery
33496
33531
  */
@@ -33566,10 +33601,10 @@ function getAllowedScopes(configScopes) {
33566
33601
  return configScopes ?? ['node.connect'];
33567
33602
  }
33568
33603
  /**
33569
- * Create an Express router that implements OpenID Connect Discovery
33604
+ * Create a Fastify plugin that implements OpenID Connect Discovery
33570
33605
  *
33571
33606
  * @param options - Router configuration options
33572
- * @returns Express router with OpenID configuration endpoint
33607
+ * @returns Fastify plugin with OpenID configuration endpoint
33573
33608
  *
33574
33609
  * Environment Variables:
33575
33610
  * FAME_JWT_ISSUER: JWT issuer claim (optional)
@@ -33578,17 +33613,16 @@ function getAllowedScopes(configScopes) {
33578
33613
  *
33579
33614
  * @example
33580
33615
  * ```typescript
33581
- * import express from 'express';
33616
+ * import Fastify from 'fastify';
33582
33617
  * import { createOpenIDConfigurationRouter } from '@naylence/runtime';
33583
33618
  *
33584
- * const app = express();
33585
- * app.use(createOpenIDConfigurationRouter({
33619
+ * const app = Fastify();
33620
+ * app.register(createOpenIDConfigurationRouter({
33586
33621
  * issuer: 'https://auth.example.com',
33587
33622
  * }));
33588
33623
  * ```
33589
33624
  */
33590
33625
  function createOpenIDConfigurationRouter(options = {}) {
33591
- const router = express.Router();
33592
33626
  const { prefix = DEFAULT_PREFIX, issuer, baseUrl, tokenEndpointPath = '/oauth/token', jwksEndpointPath = '/.well-known/jwks.json', allowedScopes: configAllowedScopes, algorithm: configAlgorithm, } = normalizeOpenIDConfigurationRouterOptions(options);
33593
33627
  // Resolve configuration with environment variable priority
33594
33628
  const defaultIssuer = process.env[ENV_VAR_JWT_ISSUER] ?? issuer ?? 'https://auth.fame.fabric';
@@ -33604,29 +33638,30 @@ function createOpenIDConfigurationRouter(options = {}) {
33604
33638
  algorithm,
33605
33639
  allowedScopes,
33606
33640
  });
33607
- // OpenID Connect Discovery endpoint
33608
- router.get(`${prefix}/.well-known/openid-configuration`, (_req, res) => {
33609
- // Construct absolute URLs for endpoints
33610
- const tokenEndpoint = `${defaultBaseUrl.replace(/\/$/, '')}${tokenEndpointPath}`;
33611
- const jwksUri = `${defaultBaseUrl.replace(/\/$/, '')}${jwksEndpointPath}`;
33612
- const config = {
33613
- issuer: defaultIssuer,
33614
- token_endpoint: tokenEndpoint,
33615
- jwks_uri: jwksUri,
33616
- scopes_supported: allowedScopes,
33617
- response_types_supported: ['token'],
33618
- grant_types_supported: ['client_credentials'],
33619
- token_endpoint_auth_methods_supported: [
33620
- 'client_secret_basic',
33621
- 'client_secret_post',
33622
- ],
33623
- subject_types_supported: ['public'],
33624
- id_token_signing_alg_values_supported: [algorithm],
33625
- };
33626
- logger$j.debug('openid_config_served', { config });
33627
- res.json(config);
33628
- });
33629
- return router;
33641
+ const plugin = async (instance) => {
33642
+ instance.get(`${prefix}/.well-known/openid-configuration`, async (_request, reply) => {
33643
+ // Construct absolute URLs for endpoints
33644
+ const tokenEndpoint = `${defaultBaseUrl.replace(/\/$/, '')}${tokenEndpointPath}`;
33645
+ const jwksUri = `${defaultBaseUrl.replace(/\/$/, '')}${jwksEndpointPath}`;
33646
+ const config = {
33647
+ issuer: defaultIssuer,
33648
+ token_endpoint: tokenEndpoint,
33649
+ jwks_uri: jwksUri,
33650
+ scopes_supported: allowedScopes,
33651
+ response_types_supported: ['token'],
33652
+ grant_types_supported: ['client_credentials'],
33653
+ token_endpoint_auth_methods_supported: [
33654
+ 'client_secret_basic',
33655
+ 'client_secret_post',
33656
+ ],
33657
+ subject_types_supported: ['public'],
33658
+ id_token_signing_alg_values_supported: [algorithm],
33659
+ };
33660
+ logger$j.debug('openid_config_served', { config });
33661
+ reply.send(config);
33662
+ });
33663
+ };
33664
+ return plugin;
33630
33665
  }
33631
33666
 
33632
33667
  /**
@@ -33678,23 +33713,18 @@ async function getCryptoProvider() {
33678
33713
  return DefaultCryptoProvider.create();
33679
33714
  }
33680
33715
  /**
33681
- * Create and configure the OAuth2 Express application
33716
+ * Create and configure the OAuth2 Fastify application
33682
33717
  */
33683
33718
  async function createApp() {
33684
- const app = express();
33685
- // Middleware
33686
- app.use(express.json());
33687
- app.use(express.urlencoded({ extended: true }));
33719
+ const app = fastify({ logger: false });
33688
33720
  // Get crypto provider
33689
33721
  const cryptoProvider = await getCryptoProvider();
33690
33722
  // Add routers
33691
- app.use(createOAuth2TokenRouter({ cryptoProvider }));
33692
- app.use(createJwksRouter({ cryptoProvider }));
33693
- app.use(createOpenIDConfigurationRouter());
33723
+ app.register(createOAuth2TokenRouter({ cryptoProvider }));
33724
+ app.register(createJwksRouter({ cryptoProvider }));
33725
+ app.register(createOpenIDConfigurationRouter());
33694
33726
  // Health check endpoint
33695
- app.get('/health', (_req, res) => {
33696
- res.json({ status: 'ok' });
33697
- });
33727
+ app.get('/health', async () => ({ status: 'ok' }));
33698
33728
  return app;
33699
33729
  }
33700
33730
  /**
@@ -33722,27 +33752,29 @@ async function main() {
33722
33752
  });
33723
33753
  const app = await createApp();
33724
33754
  // Start server
33725
- app.listen(port, host, () => {
33726
- logger$i.info('oauth2_server_started', {
33727
- host,
33728
- port,
33729
- endpoints: {
33730
- token: '/oauth/token',
33731
- jwks: '/.well-known/jwks.json',
33732
- openid_config: '/.well-known/openid-configuration',
33733
- health: '/health',
33734
- },
33735
- });
33755
+ await app.listen({ port, host });
33756
+ logger$i.info('oauth2_server_started', {
33757
+ host,
33758
+ port,
33759
+ endpoints: {
33760
+ token: '/oauth/token',
33761
+ jwks: '/.well-known/jwks.json',
33762
+ openid_config: '/.well-known/openid-configuration',
33763
+ health: '/health',
33764
+ },
33736
33765
  });
33766
+ const shutdown = (signal) => {
33767
+ logger$i.info('oauth2_server_shutting_down', { signal });
33768
+ app
33769
+ .close()
33770
+ .catch((error) => logger$i.error('oauth2_server_shutdown_error', {
33771
+ error: error instanceof Error ? error.message : String(error),
33772
+ }))
33773
+ .finally(() => process.exit(0));
33774
+ };
33737
33775
  // Graceful shutdown
33738
- process.on('SIGINT', () => {
33739
- logger$i.info('oauth2_server_shutting_down', { signal: 'SIGINT' });
33740
- process.exit(0);
33741
- });
33742
- process.on('SIGTERM', () => {
33743
- logger$i.info('oauth2_server_shutting_down', { signal: 'SIGTERM' });
33744
- process.exit(0);
33745
- });
33776
+ process.on('SIGINT', () => shutdown('SIGINT'));
33777
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
33746
33778
  }
33747
33779
 
33748
33780
  const telemetryLogger = getLogger('naylence.fame.telemetry.base_trace_emitter');
@@ -34917,6 +34949,8 @@ const ENV_VAR_DIRECT_INPAGE_CHANNEL = 'FAME_DIRECT_INPAGE_CHANNEL';
34917
34949
  const ENV_VAR_ADMISSION_SERVICE_URL = 'FAME_ADMISSION_SERVICE_URL';
34918
34950
  const DEFAULT_INPAGE_CHANNEL = 'naylence-fabric';
34919
34951
  const PROFILE_NAME_WELCOME = 'welcome';
34952
+ const PROFILE_NAME_WELCOME_PKCE = 'welcome-pkce';
34953
+ const PROFILE_NAME_WELCOME_PKCE_ALIAS = 'welcome_pkce';
34920
34954
  const PROFILE_NAME_DIRECT = 'direct';
34921
34955
  const PROFILE_NAME_DIRECT_HTTP = 'direct-http';
34922
34956
  const PROFILE_NAME_DIRECT_INPAGE = 'direct-inpage';
@@ -34977,6 +35011,7 @@ function createOAuthPkceTokenProviderConfig() {
34977
35011
  }
34978
35012
  const welcomeIsRoot = Expressions.env(ENV_VAR_IS_ROOT, 'false');
34979
35013
  const welcomeTokenProvider = createOAuthTokenProviderConfig();
35014
+ const welcomePkceTokenProvider = createOAuthPkceTokenProviderConfig();
34980
35015
  const WELCOME_SERVICE_PROFILE = {
34981
35016
  type: 'WelcomeServiceClient',
34982
35017
  is_root: welcomeIsRoot,
@@ -34990,6 +35025,19 @@ const WELCOME_SERVICE_PROFILE = {
34990
35025
  tokenProvider: welcomeTokenProvider,
34991
35026
  },
34992
35027
  };
35028
+ const WELCOME_SERVICE_PKCE_PROFILE = {
35029
+ type: 'WelcomeServiceClient',
35030
+ is_root: welcomeIsRoot,
35031
+ isRoot: welcomeIsRoot,
35032
+ url: Expressions.env(ENV_VAR_ADMISSION_SERVICE_URL),
35033
+ supported_transports: ['websocket'],
35034
+ supportedTransports: ['websocket'],
35035
+ auth: {
35036
+ type: 'BearerTokenHeaderAuth',
35037
+ token_provider: welcomePkceTokenProvider,
35038
+ tokenProvider: welcomePkceTokenProvider,
35039
+ },
35040
+ };
34993
35041
  const directGrantTokenProvider = createOAuthTokenProviderConfig();
34994
35042
  const directGrant = {
34995
35043
  type: 'WebSocketConnectionGrant',
@@ -35095,6 +35143,8 @@ const NOOP_PROFILE = {
35095
35143
  };
35096
35144
  const PROFILE_MAP$1 = {
35097
35145
  [PROFILE_NAME_WELCOME]: WELCOME_SERVICE_PROFILE,
35146
+ [PROFILE_NAME_WELCOME_PKCE]: WELCOME_SERVICE_PKCE_PROFILE,
35147
+ [PROFILE_NAME_WELCOME_PKCE_ALIAS]: WELCOME_SERVICE_PKCE_PROFILE,
35098
35148
  [PROFILE_NAME_DIRECT]: DIRECT_PROFILE,
35099
35149
  [PROFILE_NAME_DIRECT_PKCE]: DIRECT_PKCE_PROFILE,
35100
35150
  [PROFILE_NAME_DIRECT_PKCE_ALIAS]: DIRECT_PKCE_PROFILE,