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

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 +78 -166
  2. package/dist/browser/index.mjs +78 -166
  3. package/dist/cjs/naylence/fame/config/extended-fame-config.js +58 -2
  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 +58 -2
  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 +78 -166
  20. package/dist/node/index.mjs +78 -166
  21. package/dist/node/node.cjs +305 -251
  22. package/dist/node/node.mjs +305 -251
  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.914
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.914';
5376
5373
 
5377
5374
  /**
5378
5375
  * Fame errors module - Fame protocol specific error classes
@@ -14701,6 +14698,61 @@ 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
+ let cachedNodeRequire = typeof require === 'function' ? require : null;
14711
+ function fileUrlToPath(url) {
14712
+ try {
14713
+ const parsed = new URL(url);
14714
+ if (parsed.protocol !== 'file:') {
14715
+ return null;
14716
+ }
14717
+ let pathname = parsed.pathname;
14718
+ if (typeof process !== 'undefined' &&
14719
+ process.platform === 'win32' &&
14720
+ pathname.startsWith('/')) {
14721
+ pathname = pathname.slice(1);
14722
+ }
14723
+ return decodeURIComponent(pathname);
14724
+ }
14725
+ catch {
14726
+ return null;
14727
+ }
14728
+ }
14729
+ function getNodeRequire() {
14730
+ if (cachedNodeRequire) {
14731
+ return cachedNodeRequire;
14732
+ }
14733
+ if (!isNode) {
14734
+ return null;
14735
+ }
14736
+ const processBinding = process.binding;
14737
+ if (typeof processBinding !== 'function') {
14738
+ return null;
14739
+ }
14740
+ try {
14741
+ const moduleWrap = processBinding('module_wrap');
14742
+ if (typeof moduleWrap?.createRequire !== 'function') {
14743
+ return null;
14744
+ }
14745
+ const modulePathFromUrl = currentModuleUrl
14746
+ ? fileUrlToPath(currentModuleUrl)
14747
+ : null;
14748
+ const requireSource = modulePathFromUrl ?? `${process.cwd()}/.naylence-require-shim.js`;
14749
+ cachedNodeRequire = moduleWrap.createRequire(requireSource);
14750
+ return cachedNodeRequire;
14751
+ }
14752
+ catch {
14753
+ return null;
14754
+ }
14755
+ }
14704
14756
  function getFsModule() {
14705
14757
  if (cachedFsModule) {
14706
14758
  return cachedFsModule;
@@ -14708,9 +14760,10 @@ function getFsModule() {
14708
14760
  if (!isNode) {
14709
14761
  throw new Error('File system access is not available in this environment');
14710
14762
  }
14711
- if (typeof require === 'function') {
14763
+ const nodeRequire = typeof require === 'function' ? require : getNodeRequire();
14764
+ if (nodeRequire) {
14712
14765
  try {
14713
- cachedFsModule = require(fsModuleSpecifier);
14766
+ cachedFsModule = nodeRequire(fsModuleSpecifier);
14714
14767
  return cachedFsModule;
14715
14768
  }
14716
14769
  catch (error) {
@@ -26571,11 +26624,6 @@ const DEFAULT_AUDIENCE = 'router-dev';
26571
26624
  const DEFAULT_TTL_SEC$1 = 3600;
26572
26625
  const DEFAULT_HMAC_SECRET_BYTES = 32;
26573
26626
  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
26627
  function normalizeDefaultCryptoProviderOptions(options) {
26580
26628
  if (!options) {
26581
26629
  return {};
@@ -26841,76 +26889,6 @@ class DefaultCryptoProvider {
26841
26889
  has_chain: Boolean(certificateChainPem),
26842
26890
  });
26843
26891
  }
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
26892
  }
26915
26893
  async function buildProviderArtifacts(options) {
26916
26894
  const algorithm = normalizeAlgorithm(options.algorithm ?? readEnvAlgorithm());
@@ -27146,90 +27124,6 @@ function pemToDerBase64(pem) {
27146
27124
  // Ensure the output is valid base64 without whitespace
27147
27125
  return base64.replace(/\s+/g, '');
27148
27126
  }
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
27127
  function cloneJson(value) {
27234
27128
  return JSON.parse(JSON.stringify(value));
27235
27129
  }
@@ -32389,7 +32283,7 @@ var inProcessFameFabricFactory = /*#__PURE__*/Object.freeze({
32389
32283
  });
32390
32284
 
32391
32285
  /**
32392
- * JWKS (JSON Web Key Set) API router for Express
32286
+ * JWKS (JSON Web Key Set) API plugin for Fastify
32393
32287
  *
32394
32288
  * Provides /.well-known/jwks.json endpoint for public key discovery
32395
32289
  * Used by OAuth2/JWT token verification
@@ -32472,23 +32366,22 @@ function filterKeysByType(jwksData, allowedTypes) {
32472
32366
  return { ...jwksData, keys: filteredKeys };
32473
32367
  }
32474
32368
  /**
32475
- * Create an Express router that exposes JWKS at /.well-known/jwks.json
32369
+ * Create a Fastify plugin that exposes JWKS at /.well-known/jwks.json
32476
32370
  *
32477
32371
  * @param options - Router configuration options
32478
- * @returns Express router with JWKS endpoint
32372
+ * @returns Fastify plugin with JWKS endpoint
32479
32373
  *
32480
32374
  * @example
32481
32375
  * ```typescript
32482
- * import express from 'express';
32376
+ * import Fastify from 'fastify';
32483
32377
  * import { createJwksRouter } from '@naylence/runtime';
32484
32378
  *
32485
- * const app = express();
32379
+ * const app = Fastify();
32486
32380
  * const cryptoProvider = new MyCryptoProvider();
32487
- * app.use(createJwksRouter({ cryptoProvider }));
32381
+ * app.register(createJwksRouter({ cryptoProvider }));
32488
32382
  * ```
32489
32383
  */
32490
32384
  function createJwksRouter(options = {}) {
32491
- const router = express.Router();
32492
32385
  const { getJwksJson, cryptoProvider, prefix = DEFAULT_PREFIX$2, keyTypes, } = normalizeCreateJwksRouterOptions(options);
32493
32386
  // Get JWKS data
32494
32387
  let jwks;
@@ -32511,26 +32404,172 @@ function createJwksRouter(options = {}) {
32511
32404
  key_types: allowedKeyTypes,
32512
32405
  total_keys: jwks.keys.length,
32513
32406
  });
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;
32407
+ const plugin = async (instance) => {
32408
+ instance.get(`${prefix}/.well-known/jwks.json`, async (_request, reply) => {
32409
+ const filteredJwks = filterKeysByType(jwks, allowedKeyTypes);
32410
+ logger$l.debug('jwks_served', {
32411
+ total_keys: jwks.keys.length,
32412
+ filtered_keys: filteredJwks.keys.length,
32413
+ });
32414
+ reply.send(filteredJwks);
32415
+ });
32416
+ };
32417
+ return plugin;
32524
32418
  }
32525
32419
 
32526
32420
  /**
32527
- * OAuth2 client credentials and authorization code (PKCE) grant router for Express
32421
+ * OAuth2 client credentials and authorization code (PKCE) grant router for Fastify
32528
32422
  *
32529
32423
  * Provides /oauth/token and /oauth/authorize endpoints for local development and testing.
32530
32424
  * Implements OAuth2 client credentials grant with JWT token issuance and
32531
32425
  * OAuth2 authorization code grant with PKCE verification.
32532
32426
  */
32533
32427
  const logger$k = getLogger('naylence.fame.http.oauth2_token_router');
32428
+ class RouterCompat {
32429
+ constructor() {
32430
+ this.routes = [];
32431
+ }
32432
+ get(path, handler) {
32433
+ this.routes.push({ method: 'GET', path, handler });
32434
+ }
32435
+ post(path, handler) {
32436
+ this.routes.push({ method: 'POST', path, handler });
32437
+ }
32438
+ toPlugin() {
32439
+ return async (fastify) => {
32440
+ await fastify.register(formbody);
32441
+ for (const route of this.routes) {
32442
+ fastify.route({
32443
+ method: route.method,
32444
+ url: route.path,
32445
+ handler: async (request, reply) => {
32446
+ const compatRequest = toCompatRequest(request);
32447
+ const compatResponse = new FastifyResponseAdapter(reply);
32448
+ await route.handler(compatRequest, compatResponse);
32449
+ },
32450
+ });
32451
+ }
32452
+ };
32453
+ }
32454
+ }
32455
+ class FastifyResponseAdapter {
32456
+ constructor(reply) {
32457
+ this.reply = reply;
32458
+ }
32459
+ status(code) {
32460
+ this.reply.status(code);
32461
+ return this;
32462
+ }
32463
+ set(field, value) {
32464
+ if (field.toLowerCase() === 'set-cookie') {
32465
+ this.appendHeader(field, value);
32466
+ }
32467
+ else {
32468
+ this.reply.header(field, value);
32469
+ }
32470
+ return this;
32471
+ }
32472
+ type(contentType) {
32473
+ const normalized = contentType === 'html'
32474
+ ? 'text/html'
32475
+ : contentType === 'json'
32476
+ ? 'application/json'
32477
+ : contentType;
32478
+ this.reply.type(normalized);
32479
+ return this;
32480
+ }
32481
+ json(payload) {
32482
+ this.reply.send(payload);
32483
+ }
32484
+ send(payload) {
32485
+ this.reply.send(payload);
32486
+ }
32487
+ redirect(statusOrUrl, maybeUrl) {
32488
+ if (typeof statusOrUrl === 'number') {
32489
+ if (maybeUrl === undefined) {
32490
+ throw new Error('redirect url is required when status code is provided');
32491
+ }
32492
+ this.reply.status(statusOrUrl);
32493
+ this.reply.header('Location', maybeUrl);
32494
+ this.reply.send();
32495
+ }
32496
+ else {
32497
+ this.reply.redirect(statusOrUrl);
32498
+ }
32499
+ }
32500
+ cookie(name, value, options) {
32501
+ const serialized = serializeCookie(name, value, options);
32502
+ this.appendHeader('Set-Cookie', serialized);
32503
+ }
32504
+ appendHeader(name, value) {
32505
+ const existing = this.reply.getHeader(name);
32506
+ if (Array.isArray(existing)) {
32507
+ this.reply.header(name, [...existing, value]);
32508
+ }
32509
+ else if (typeof existing === 'string') {
32510
+ this.reply.header(name, [existing, value]);
32511
+ }
32512
+ else if (existing === undefined) {
32513
+ this.reply.header(name, value);
32514
+ }
32515
+ else {
32516
+ this.reply.header(name, [String(existing), value]);
32517
+ }
32518
+ }
32519
+ }
32520
+ function toCompatRequest(request) {
32521
+ const headers = {};
32522
+ for (const [key, value] of Object.entries(request.headers)) {
32523
+ if (typeof value === 'string') {
32524
+ headers[key.toLowerCase()] = value;
32525
+ }
32526
+ else if (Array.isArray(value)) {
32527
+ headers[key.toLowerCase()] = value.join(', ');
32528
+ }
32529
+ else if (value !== undefined && value !== null) {
32530
+ headers[key.toLowerCase()] = String(value);
32531
+ }
32532
+ else {
32533
+ headers[key.toLowerCase()] = undefined;
32534
+ }
32535
+ }
32536
+ return {
32537
+ body: request.body,
32538
+ headers,
32539
+ method: request.method,
32540
+ originalUrl: request.raw.url ?? request.url,
32541
+ query: request.query ?? {},
32542
+ };
32543
+ }
32544
+ function serializeCookie(name, value, options) {
32545
+ const segments = [
32546
+ `${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
32547
+ ];
32548
+ if (options.maxAge !== undefined) {
32549
+ const maxAgeMs = options.maxAge;
32550
+ const maxAgeSeconds = Math.floor(maxAgeMs / 1000);
32551
+ segments.push(`Max-Age=${maxAgeSeconds}`);
32552
+ const expires = new Date(Date.now() + maxAgeMs).toUTCString();
32553
+ segments.push(`Expires=${expires}`);
32554
+ }
32555
+ segments.push(`Path=${options.path ?? '/'}`);
32556
+ if (options.httpOnly) {
32557
+ segments.push('HttpOnly');
32558
+ }
32559
+ if (options.secure) {
32560
+ segments.push('Secure');
32561
+ }
32562
+ if (options.sameSite) {
32563
+ const normalized = options.sameSite.toLowerCase();
32564
+ const formatted = normalized === 'strict'
32565
+ ? 'Strict'
32566
+ : normalized === 'none'
32567
+ ? 'None'
32568
+ : 'Lax';
32569
+ segments.push(`SameSite=${formatted}`);
32570
+ }
32571
+ return segments.join('; ');
32572
+ }
32534
32573
  const DEFAULT_PREFIX$1 = '/oauth';
32535
32574
  const ENV_VAR_CLIENT_ID = 'FAME_JWT_CLIENT_ID';
32536
32575
  const ENV_VAR_CLIENT_SECRET = 'FAME_JWT_CLIENT_SECRET';
@@ -32952,11 +32991,11 @@ function respondInvalidClient(res) {
32952
32991
  });
32953
32992
  }
32954
32993
  /**
32955
- * Create an Express router that implements OAuth2 token and authorization endpoints
32994
+ * Create a Fastify plugin that implements OAuth2 token and authorization endpoints
32956
32995
  * with support for client credentials and authorization code (PKCE) grants.
32957
32996
  *
32958
32997
  * @param options - Router configuration options
32959
- * @returns Express router with OAuth2 token and authorization endpoints
32998
+ * @returns Fastify plugin with OAuth2 token and authorization endpoints
32960
32999
  *
32961
33000
  * Environment Variables:
32962
33001
  * FAME_JWT_CLIENT_ID: OAuth2 client identifier
@@ -32970,7 +33009,7 @@ function respondInvalidClient(res) {
32970
33009
  * FAME_OAUTH_CODE_TTL_SEC: Authorization code TTL in seconds (optional, default: 300)
32971
33010
  */
32972
33011
  function createOAuth2TokenRouter(options) {
32973
- const router = express.Router();
33012
+ const router = new RouterCompat();
32974
33013
  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
33014
  if (!cryptoProvider) {
32976
33015
  throw new Error('cryptoProvider is required to create OAuth2 token router');
@@ -33269,7 +33308,7 @@ function createOAuth2TokenRouter(options) {
33269
33308
  };
33270
33309
  router.post(`${prefix}/logout`, logoutHandler);
33271
33310
  router.get(`${prefix}/logout`, logoutHandler);
33272
- router.post(`${prefix}/token`, async (req, res, next) => {
33311
+ router.post(`${prefix}/token`, async (req, res) => {
33273
33312
  try {
33274
33313
  cleanupAuthorizationCodes(authorizationCodes, Date.now());
33275
33314
  const { grant_type, client_id, client_secret, scope, audience: reqAudience, code, redirect_uri, code_verifier, } = req.body ?? {};
@@ -33454,7 +33493,7 @@ function createOAuth2TokenRouter(options) {
33454
33493
  }
33455
33494
  catch (error) {
33456
33495
  logger$k.error('oauth2_token_error', { error: error.message });
33457
- next(error);
33496
+ throw error;
33458
33497
  }
33459
33498
  });
33460
33499
  async function issueTokenResponse(params) {
@@ -33487,11 +33526,11 @@ function createOAuth2TokenRouter(options) {
33487
33526
  }
33488
33527
  return response;
33489
33528
  }
33490
- return router;
33529
+ return router.toPlugin();
33491
33530
  }
33492
33531
 
33493
33532
  /**
33494
- * OpenID Connect Discovery configuration router for Express
33533
+ * OpenID Connect Discovery configuration plugin for Fastify
33495
33534
  *
33496
33535
  * Provides /.well-known/openid-configuration endpoint for OAuth2/OIDC client auto-discovery
33497
33536
  */
@@ -33567,10 +33606,10 @@ function getAllowedScopes(configScopes) {
33567
33606
  return configScopes ?? ['node.connect'];
33568
33607
  }
33569
33608
  /**
33570
- * Create an Express router that implements OpenID Connect Discovery
33609
+ * Create a Fastify plugin that implements OpenID Connect Discovery
33571
33610
  *
33572
33611
  * @param options - Router configuration options
33573
- * @returns Express router with OpenID configuration endpoint
33612
+ * @returns Fastify plugin with OpenID configuration endpoint
33574
33613
  *
33575
33614
  * Environment Variables:
33576
33615
  * FAME_JWT_ISSUER: JWT issuer claim (optional)
@@ -33579,17 +33618,16 @@ function getAllowedScopes(configScopes) {
33579
33618
  *
33580
33619
  * @example
33581
33620
  * ```typescript
33582
- * import express from 'express';
33621
+ * import Fastify from 'fastify';
33583
33622
  * import { createOpenIDConfigurationRouter } from '@naylence/runtime';
33584
33623
  *
33585
- * const app = express();
33586
- * app.use(createOpenIDConfigurationRouter({
33624
+ * const app = Fastify();
33625
+ * app.register(createOpenIDConfigurationRouter({
33587
33626
  * issuer: 'https://auth.example.com',
33588
33627
  * }));
33589
33628
  * ```
33590
33629
  */
33591
33630
  function createOpenIDConfigurationRouter(options = {}) {
33592
- const router = express.Router();
33593
33631
  const { prefix = DEFAULT_PREFIX, issuer, baseUrl, tokenEndpointPath = '/oauth/token', jwksEndpointPath = '/.well-known/jwks.json', allowedScopes: configAllowedScopes, algorithm: configAlgorithm, } = normalizeOpenIDConfigurationRouterOptions(options);
33594
33632
  // Resolve configuration with environment variable priority
33595
33633
  const defaultIssuer = process.env[ENV_VAR_JWT_ISSUER] ?? issuer ?? 'https://auth.fame.fabric';
@@ -33605,29 +33643,30 @@ function createOpenIDConfigurationRouter(options = {}) {
33605
33643
  algorithm,
33606
33644
  allowedScopes,
33607
33645
  });
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;
33646
+ const plugin = async (instance) => {
33647
+ instance.get(`${prefix}/.well-known/openid-configuration`, async (_request, reply) => {
33648
+ // Construct absolute URLs for endpoints
33649
+ const tokenEndpoint = `${defaultBaseUrl.replace(/\/$/, '')}${tokenEndpointPath}`;
33650
+ const jwksUri = `${defaultBaseUrl.replace(/\/$/, '')}${jwksEndpointPath}`;
33651
+ const config = {
33652
+ issuer: defaultIssuer,
33653
+ token_endpoint: tokenEndpoint,
33654
+ jwks_uri: jwksUri,
33655
+ scopes_supported: allowedScopes,
33656
+ response_types_supported: ['token'],
33657
+ grant_types_supported: ['client_credentials'],
33658
+ token_endpoint_auth_methods_supported: [
33659
+ 'client_secret_basic',
33660
+ 'client_secret_post',
33661
+ ],
33662
+ subject_types_supported: ['public'],
33663
+ id_token_signing_alg_values_supported: [algorithm],
33664
+ };
33665
+ logger$j.debug('openid_config_served', { config });
33666
+ reply.send(config);
33667
+ });
33668
+ };
33669
+ return plugin;
33631
33670
  }
33632
33671
 
33633
33672
  /**
@@ -33679,23 +33718,18 @@ async function getCryptoProvider() {
33679
33718
  return DefaultCryptoProvider.create();
33680
33719
  }
33681
33720
  /**
33682
- * Create and configure the OAuth2 Express application
33721
+ * Create and configure the OAuth2 Fastify application
33683
33722
  */
33684
33723
  async function createApp() {
33685
- const app = express();
33686
- // Middleware
33687
- app.use(express.json());
33688
- app.use(express.urlencoded({ extended: true }));
33724
+ const app = fastify({ logger: false });
33689
33725
  // Get crypto provider
33690
33726
  const cryptoProvider = await getCryptoProvider();
33691
33727
  // Add routers
33692
- app.use(createOAuth2TokenRouter({ cryptoProvider }));
33693
- app.use(createJwksRouter({ cryptoProvider }));
33694
- app.use(createOpenIDConfigurationRouter());
33728
+ app.register(createOAuth2TokenRouter({ cryptoProvider }));
33729
+ app.register(createJwksRouter({ cryptoProvider }));
33730
+ app.register(createOpenIDConfigurationRouter());
33695
33731
  // Health check endpoint
33696
- app.get('/health', (_req, res) => {
33697
- res.json({ status: 'ok' });
33698
- });
33732
+ app.get('/health', async () => ({ status: 'ok' }));
33699
33733
  return app;
33700
33734
  }
33701
33735
  /**
@@ -33723,27 +33757,29 @@ async function main() {
33723
33757
  });
33724
33758
  const app = await createApp();
33725
33759
  // 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
- });
33760
+ await app.listen({ port, host });
33761
+ logger$i.info('oauth2_server_started', {
33762
+ host,
33763
+ port,
33764
+ endpoints: {
33765
+ token: '/oauth/token',
33766
+ jwks: '/.well-known/jwks.json',
33767
+ openid_config: '/.well-known/openid-configuration',
33768
+ health: '/health',
33769
+ },
33737
33770
  });
33771
+ const shutdown = (signal) => {
33772
+ logger$i.info('oauth2_server_shutting_down', { signal });
33773
+ app
33774
+ .close()
33775
+ .catch((error) => logger$i.error('oauth2_server_shutdown_error', {
33776
+ error: error instanceof Error ? error.message : String(error),
33777
+ }))
33778
+ .finally(() => process.exit(0));
33779
+ };
33738
33780
  // 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
- });
33781
+ process.on('SIGINT', () => shutdown('SIGINT'));
33782
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
33747
33783
  }
33748
33784
 
33749
33785
  const telemetryLogger = getLogger('naylence.fame.telemetry.base_trace_emitter');
@@ -34918,6 +34954,8 @@ const ENV_VAR_DIRECT_INPAGE_CHANNEL = 'FAME_DIRECT_INPAGE_CHANNEL';
34918
34954
  const ENV_VAR_ADMISSION_SERVICE_URL = 'FAME_ADMISSION_SERVICE_URL';
34919
34955
  const DEFAULT_INPAGE_CHANNEL = 'naylence-fabric';
34920
34956
  const PROFILE_NAME_WELCOME = 'welcome';
34957
+ const PROFILE_NAME_WELCOME_PKCE = 'welcome-pkce';
34958
+ const PROFILE_NAME_WELCOME_PKCE_ALIAS = 'welcome_pkce';
34921
34959
  const PROFILE_NAME_DIRECT = 'direct';
34922
34960
  const PROFILE_NAME_DIRECT_HTTP = 'direct-http';
34923
34961
  const PROFILE_NAME_DIRECT_INPAGE = 'direct-inpage';
@@ -34978,6 +35016,7 @@ function createOAuthPkceTokenProviderConfig() {
34978
35016
  }
34979
35017
  const welcomeIsRoot = factory.Expressions.env(ENV_VAR_IS_ROOT, 'false');
34980
35018
  const welcomeTokenProvider = createOAuthTokenProviderConfig();
35019
+ const welcomePkceTokenProvider = createOAuthPkceTokenProviderConfig();
34981
35020
  const WELCOME_SERVICE_PROFILE = {
34982
35021
  type: 'WelcomeServiceClient',
34983
35022
  is_root: welcomeIsRoot,
@@ -34991,6 +35030,19 @@ const WELCOME_SERVICE_PROFILE = {
34991
35030
  tokenProvider: welcomeTokenProvider,
34992
35031
  },
34993
35032
  };
35033
+ const WELCOME_SERVICE_PKCE_PROFILE = {
35034
+ type: 'WelcomeServiceClient',
35035
+ is_root: welcomeIsRoot,
35036
+ isRoot: welcomeIsRoot,
35037
+ url: factory.Expressions.env(ENV_VAR_ADMISSION_SERVICE_URL),
35038
+ supported_transports: ['websocket'],
35039
+ supportedTransports: ['websocket'],
35040
+ auth: {
35041
+ type: 'BearerTokenHeaderAuth',
35042
+ token_provider: welcomePkceTokenProvider,
35043
+ tokenProvider: welcomePkceTokenProvider,
35044
+ },
35045
+ };
34994
35046
  const directGrantTokenProvider = createOAuthTokenProviderConfig();
34995
35047
  const directGrant = {
34996
35048
  type: 'WebSocketConnectionGrant',
@@ -35096,6 +35148,8 @@ const NOOP_PROFILE = {
35096
35148
  };
35097
35149
  const PROFILE_MAP$1 = {
35098
35150
  [PROFILE_NAME_WELCOME]: WELCOME_SERVICE_PROFILE,
35151
+ [PROFILE_NAME_WELCOME_PKCE]: WELCOME_SERVICE_PKCE_PROFILE,
35152
+ [PROFILE_NAME_WELCOME_PKCE_ALIAS]: WELCOME_SERVICE_PKCE_PROFILE,
35099
35153
  [PROFILE_NAME_DIRECT]: DIRECT_PROFILE,
35100
35154
  [PROFILE_NAME_DIRECT_PKCE]: DIRECT_PKCE_PROFILE,
35101
35155
  [PROFILE_NAME_DIRECT_PKCE_ALIAS]: DIRECT_PKCE_PROFILE,