@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.
- package/dist/browser/index.cjs +72 -164
- package/dist/browser/index.mjs +72 -164
- package/dist/cjs/naylence/fame/config/extended-fame-config.js +52 -0
- package/dist/cjs/naylence/fame/http/jwks-api-router.js +16 -18
- package/dist/cjs/naylence/fame/http/oauth2-server.js +28 -31
- package/dist/cjs/naylence/fame/http/oauth2-token-router.js +153 -8
- package/dist/cjs/naylence/fame/http/openid-configuration-router.js +30 -32
- package/dist/cjs/naylence/fame/node/admission/admission-profile-factory.js +18 -0
- package/dist/cjs/naylence/fame/security/crypto/providers/default-crypto-provider.js +0 -162
- package/dist/cjs/version.js +2 -2
- package/dist/esm/naylence/fame/config/extended-fame-config.js +52 -0
- package/dist/esm/naylence/fame/http/jwks-api-router.js +16 -17
- package/dist/esm/naylence/fame/http/oauth2-server.js +28 -31
- package/dist/esm/naylence/fame/http/oauth2-token-router.js +153 -8
- package/dist/esm/naylence/fame/http/openid-configuration-router.js +30 -31
- package/dist/esm/naylence/fame/node/admission/admission-profile-factory.js +18 -0
- package/dist/esm/naylence/fame/security/crypto/providers/default-crypto-provider.js +0 -162
- package/dist/esm/version.js +2 -2
- package/dist/node/index.cjs +72 -164
- package/dist/node/index.mjs +72 -164
- package/dist/node/node.cjs +299 -249
- package/dist/node/node.mjs +299 -249
- package/dist/types/naylence/fame/http/jwks-api-router.d.ts +8 -8
- package/dist/types/naylence/fame/http/oauth2-server.d.ts +3 -3
- package/dist/types/naylence/fame/http/oauth2-token-router.d.ts +5 -5
- package/dist/types/naylence/fame/http/openid-configuration-router.d.ts +8 -8
- package/dist/types/naylence/fame/security/crypto/providers/default-crypto-provider.d.ts +0 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +4 -6
- package/dist/esm/naylence/fame/fastapi/oauth2-server.js +0 -205
- package/dist/types/naylence/fame/fastapi/oauth2-server.d.ts +0 -22
package/dist/node/node.cjs
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
32365
|
+
* Create a Fastify plugin that exposes JWKS at /.well-known/jwks.json
|
|
32476
32366
|
*
|
|
32477
32367
|
* @param options - Router configuration options
|
|
32478
|
-
* @returns
|
|
32368
|
+
* @returns Fastify plugin with JWKS endpoint
|
|
32479
32369
|
*
|
|
32480
32370
|
* @example
|
|
32481
32371
|
* ```typescript
|
|
32482
|
-
* import
|
|
32372
|
+
* import Fastify from 'fastify';
|
|
32483
32373
|
* import { createJwksRouter } from '@naylence/runtime';
|
|
32484
32374
|
*
|
|
32485
|
-
* const app =
|
|
32375
|
+
* const app = Fastify();
|
|
32486
32376
|
* const cryptoProvider = new MyCryptoProvider();
|
|
32487
|
-
* app.
|
|
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
|
-
|
|
32515
|
-
|
|
32516
|
-
|
|
32517
|
-
|
|
32518
|
-
|
|
32519
|
-
|
|
32520
|
-
|
|
32521
|
-
|
|
32522
|
-
|
|
32523
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
33605
|
+
* Create a Fastify plugin that implements OpenID Connect Discovery
|
|
33571
33606
|
*
|
|
33572
33607
|
* @param options - Router configuration options
|
|
33573
|
-
* @returns
|
|
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
|
|
33617
|
+
* import Fastify from 'fastify';
|
|
33583
33618
|
* import { createOpenIDConfigurationRouter } from '@naylence/runtime';
|
|
33584
33619
|
*
|
|
33585
|
-
* const app =
|
|
33586
|
-
* app.
|
|
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
|
-
|
|
33609
|
-
|
|
33610
|
-
|
|
33611
|
-
|
|
33612
|
-
|
|
33613
|
-
|
|
33614
|
-
|
|
33615
|
-
|
|
33616
|
-
|
|
33617
|
-
|
|
33618
|
-
|
|
33619
|
-
|
|
33620
|
-
|
|
33621
|
-
|
|
33622
|
-
|
|
33623
|
-
|
|
33624
|
-
|
|
33625
|
-
|
|
33626
|
-
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
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
|
|
33717
|
+
* Create and configure the OAuth2 Fastify application
|
|
33683
33718
|
*/
|
|
33684
33719
|
async function createApp() {
|
|
33685
|
-
const app =
|
|
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.
|
|
33693
|
-
app.
|
|
33694
|
-
app.
|
|
33724
|
+
app.register(createOAuth2TokenRouter({ cryptoProvider }));
|
|
33725
|
+
app.register(createJwksRouter({ cryptoProvider }));
|
|
33726
|
+
app.register(createOpenIDConfigurationRouter());
|
|
33695
33727
|
// Health check endpoint
|
|
33696
|
-
app.get('/health', (
|
|
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
|
-
|
|
33728
|
-
|
|
33729
|
-
|
|
33730
|
-
|
|
33731
|
-
|
|
33732
|
-
|
|
33733
|
-
|
|
33734
|
-
|
|
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
|
-
|
|
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,
|