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