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