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