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