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