@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
|
@@ -57,6 +57,61 @@ const CONFIG_SEARCH_PATHS = [
|
|
|
57
57
|
];
|
|
58
58
|
const fsModuleSpecifier = String.fromCharCode(102) + String.fromCharCode(115);
|
|
59
59
|
let cachedFsModule = null;
|
|
60
|
+
// Capture this module's URL without triggering TypeScript's import.meta restriction on CJS builds
|
|
61
|
+
const currentModuleUrl = (() => {
|
|
62
|
+
try {
|
|
63
|
+
return (0, eval)('import.meta.url');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
let cachedNodeRequire = typeof require === 'function' ? require : null;
|
|
70
|
+
function fileUrlToPath(url) {
|
|
71
|
+
try {
|
|
72
|
+
const parsed = new URL(url);
|
|
73
|
+
if (parsed.protocol !== 'file:') {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
let pathname = parsed.pathname;
|
|
77
|
+
if (typeof process !== 'undefined' &&
|
|
78
|
+
process.platform === 'win32' &&
|
|
79
|
+
pathname.startsWith('/')) {
|
|
80
|
+
pathname = pathname.slice(1);
|
|
81
|
+
}
|
|
82
|
+
return decodeURIComponent(pathname);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function getNodeRequire() {
|
|
89
|
+
if (cachedNodeRequire) {
|
|
90
|
+
return cachedNodeRequire;
|
|
91
|
+
}
|
|
92
|
+
if (!logging_types_js_1.isNode) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const processBinding = process.binding;
|
|
96
|
+
if (typeof processBinding !== 'function') {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const moduleWrap = processBinding('module_wrap');
|
|
101
|
+
if (typeof moduleWrap?.createRequire !== 'function') {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const modulePathFromUrl = currentModuleUrl
|
|
105
|
+
? fileUrlToPath(currentModuleUrl)
|
|
106
|
+
: null;
|
|
107
|
+
const requireSource = modulePathFromUrl ?? `${process.cwd()}/.naylence-require-shim.js`;
|
|
108
|
+
cachedNodeRequire = moduleWrap.createRequire(requireSource);
|
|
109
|
+
return cachedNodeRequire;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
60
115
|
function getFsModule() {
|
|
61
116
|
if (cachedFsModule) {
|
|
62
117
|
return cachedFsModule;
|
|
@@ -64,9 +119,10 @@ function getFsModule() {
|
|
|
64
119
|
if (!logging_types_js_1.isNode) {
|
|
65
120
|
throw new Error('File system access is not available in this environment');
|
|
66
121
|
}
|
|
67
|
-
|
|
122
|
+
const nodeRequire = typeof require === 'function' ? require : getNodeRequire();
|
|
123
|
+
if (nodeRequire) {
|
|
68
124
|
try {
|
|
69
|
-
cachedFsModule =
|
|
125
|
+
cachedFsModule = nodeRequire(fsModuleSpecifier);
|
|
70
126
|
return cachedFsModule;
|
|
71
127
|
}
|
|
72
128
|
catch (error) {
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* JWKS (JSON Web Key Set) API
|
|
3
|
+
* JWKS (JSON Web Key Set) API plugin for Fastify
|
|
4
4
|
*
|
|
5
5
|
* Provides /.well-known/jwks.json endpoint for public key discovery
|
|
6
6
|
* Used by OAuth2/JWT token verification
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.createJwksRouter = createJwksRouter;
|
|
10
|
-
const tslib_1 = require("tslib");
|
|
11
|
-
const express_1 = tslib_1.__importDefault(require("express"));
|
|
12
10
|
const logging_js_1 = require("../util/logging.js");
|
|
13
11
|
const logger = (0, logging_js_1.getLogger)('naylence.fame.http.jwks_api_router');
|
|
14
12
|
const DEFAULT_PREFIX = '';
|
|
@@ -88,23 +86,22 @@ function filterKeysByType(jwksData, allowedTypes) {
|
|
|
88
86
|
return { ...jwksData, keys: filteredKeys };
|
|
89
87
|
}
|
|
90
88
|
/**
|
|
91
|
-
* Create
|
|
89
|
+
* Create a Fastify plugin that exposes JWKS at /.well-known/jwks.json
|
|
92
90
|
*
|
|
93
91
|
* @param options - Router configuration options
|
|
94
|
-
* @returns
|
|
92
|
+
* @returns Fastify plugin with JWKS endpoint
|
|
95
93
|
*
|
|
96
94
|
* @example
|
|
97
95
|
* ```typescript
|
|
98
|
-
* import
|
|
96
|
+
* import Fastify from 'fastify';
|
|
99
97
|
* import { createJwksRouter } from '@naylence/runtime';
|
|
100
98
|
*
|
|
101
|
-
* const app =
|
|
99
|
+
* const app = Fastify();
|
|
102
100
|
* const cryptoProvider = new MyCryptoProvider();
|
|
103
|
-
* app.
|
|
101
|
+
* app.register(createJwksRouter({ cryptoProvider }));
|
|
104
102
|
* ```
|
|
105
103
|
*/
|
|
106
104
|
function createJwksRouter(options = {}) {
|
|
107
|
-
const router = express_1.default.Router();
|
|
108
105
|
const { getJwksJson, cryptoProvider, prefix = DEFAULT_PREFIX, keyTypes, } = normalizeCreateJwksRouterOptions(options);
|
|
109
106
|
// Get JWKS data
|
|
110
107
|
let jwks;
|
|
@@ -127,14 +124,15 @@ function createJwksRouter(options = {}) {
|
|
|
127
124
|
key_types: allowedKeyTypes,
|
|
128
125
|
total_keys: jwks.keys.length,
|
|
129
126
|
});
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
const plugin = async (instance) => {
|
|
128
|
+
instance.get(`${prefix}/.well-known/jwks.json`, async (_request, reply) => {
|
|
129
|
+
const filteredJwks = filterKeysByType(jwks, allowedKeyTypes);
|
|
130
|
+
logger.debug('jwks_served', {
|
|
131
|
+
total_keys: jwks.keys.length,
|
|
132
|
+
filtered_keys: filteredJwks.keys.length,
|
|
133
|
+
});
|
|
134
|
+
reply.send(filteredJwks);
|
|
136
135
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return router;
|
|
136
|
+
};
|
|
137
|
+
return plugin;
|
|
140
138
|
}
|
|
@@ -26,7 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
exports.createApp = createApp;
|
|
27
27
|
exports.main = main;
|
|
28
28
|
const tslib_1 = require("tslib");
|
|
29
|
-
const
|
|
29
|
+
const fastify_1 = tslib_1.__importDefault(require("fastify"));
|
|
30
30
|
const oauth2_token_router_js_1 = require("./oauth2-token-router.js");
|
|
31
31
|
const jwks_api_router_js_1 = require("./jwks-api-router.js");
|
|
32
32
|
const openid_configuration_router_js_1 = require("./openid-configuration-router.js");
|
|
@@ -58,23 +58,18 @@ async function getCryptoProvider() {
|
|
|
58
58
|
return DefaultCryptoProvider.create();
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
|
-
* Create and configure the OAuth2
|
|
61
|
+
* Create and configure the OAuth2 Fastify application
|
|
62
62
|
*/
|
|
63
63
|
async function createApp() {
|
|
64
|
-
const app = (0,
|
|
65
|
-
// Middleware
|
|
66
|
-
app.use(express_1.default.json());
|
|
67
|
-
app.use(express_1.default.urlencoded({ extended: true }));
|
|
64
|
+
const app = (0, fastify_1.default)({ logger: false });
|
|
68
65
|
// Get crypto provider
|
|
69
66
|
const cryptoProvider = await getCryptoProvider();
|
|
70
67
|
// Add routers
|
|
71
|
-
app.
|
|
72
|
-
app.
|
|
73
|
-
app.
|
|
68
|
+
app.register((0, oauth2_token_router_js_1.createOAuth2TokenRouter)({ cryptoProvider }));
|
|
69
|
+
app.register((0, jwks_api_router_js_1.createJwksRouter)({ cryptoProvider }));
|
|
70
|
+
app.register((0, openid_configuration_router_js_1.createOpenIDConfigurationRouter)());
|
|
74
71
|
// Health check endpoint
|
|
75
|
-
app.get('/health', (
|
|
76
|
-
res.json({ status: 'ok' });
|
|
77
|
-
});
|
|
72
|
+
app.get('/health', async () => ({ status: 'ok' }));
|
|
78
73
|
return app;
|
|
79
74
|
}
|
|
80
75
|
/**
|
|
@@ -102,25 +97,27 @@ async function main() {
|
|
|
102
97
|
});
|
|
103
98
|
const app = await createApp();
|
|
104
99
|
// Start server
|
|
105
|
-
app.listen(port, host
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
});
|
|
100
|
+
await app.listen({ port, host });
|
|
101
|
+
logger.info('oauth2_server_started', {
|
|
102
|
+
host,
|
|
103
|
+
port,
|
|
104
|
+
endpoints: {
|
|
105
|
+
token: '/oauth/token',
|
|
106
|
+
jwks: '/.well-known/jwks.json',
|
|
107
|
+
openid_config: '/.well-known/openid-configuration',
|
|
108
|
+
health: '/health',
|
|
109
|
+
},
|
|
116
110
|
});
|
|
111
|
+
const shutdown = (signal) => {
|
|
112
|
+
logger.info('oauth2_server_shutting_down', { signal });
|
|
113
|
+
app
|
|
114
|
+
.close()
|
|
115
|
+
.catch((error) => logger.error('oauth2_server_shutdown_error', {
|
|
116
|
+
error: error instanceof Error ? error.message : String(error),
|
|
117
|
+
}))
|
|
118
|
+
.finally(() => process.exit(0));
|
|
119
|
+
};
|
|
117
120
|
// Graceful shutdown
|
|
118
|
-
process.on('SIGINT', () =>
|
|
119
|
-
|
|
120
|
-
process.exit(0);
|
|
121
|
-
});
|
|
122
|
-
process.on('SIGTERM', () => {
|
|
123
|
-
logger.info('oauth2_server_shutting_down', { signal: 'SIGTERM' });
|
|
124
|
-
process.exit(0);
|
|
125
|
-
});
|
|
121
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
122
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
126
123
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* OAuth2 client credentials and authorization code (PKCE) grant router for
|
|
3
|
+
* OAuth2 client credentials and authorization code (PKCE) grant router for Fastify
|
|
4
4
|
*
|
|
5
5
|
* Provides /oauth/token and /oauth/authorize endpoints for local development and testing.
|
|
6
6
|
* Implements OAuth2 client credentials grant with JWT token issuance and
|
|
@@ -9,11 +9,156 @@
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.createOAuth2TokenRouter = createOAuth2TokenRouter;
|
|
11
11
|
const tslib_1 = require("tslib");
|
|
12
|
-
const
|
|
12
|
+
const formbody_1 = tslib_1.__importDefault(require("@fastify/formbody"));
|
|
13
13
|
const node_crypto_1 = require("node:crypto");
|
|
14
14
|
const jwt_token_issuer_js_1 = require("../security/auth/jwt-token-issuer.js");
|
|
15
15
|
const logging_js_1 = require("../util/logging.js");
|
|
16
16
|
const logger = (0, logging_js_1.getLogger)('naylence.fame.http.oauth2_token_router');
|
|
17
|
+
class RouterCompat {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.routes = [];
|
|
20
|
+
}
|
|
21
|
+
get(path, handler) {
|
|
22
|
+
this.routes.push({ method: 'GET', path, handler });
|
|
23
|
+
}
|
|
24
|
+
post(path, handler) {
|
|
25
|
+
this.routes.push({ method: 'POST', path, handler });
|
|
26
|
+
}
|
|
27
|
+
toPlugin() {
|
|
28
|
+
return async (fastify) => {
|
|
29
|
+
await fastify.register(formbody_1.default);
|
|
30
|
+
for (const route of this.routes) {
|
|
31
|
+
fastify.route({
|
|
32
|
+
method: route.method,
|
|
33
|
+
url: route.path,
|
|
34
|
+
handler: async (request, reply) => {
|
|
35
|
+
const compatRequest = toCompatRequest(request);
|
|
36
|
+
const compatResponse = new FastifyResponseAdapter(reply);
|
|
37
|
+
await route.handler(compatRequest, compatResponse);
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
class FastifyResponseAdapter {
|
|
45
|
+
constructor(reply) {
|
|
46
|
+
this.reply = reply;
|
|
47
|
+
}
|
|
48
|
+
status(code) {
|
|
49
|
+
this.reply.status(code);
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
set(field, value) {
|
|
53
|
+
if (field.toLowerCase() === 'set-cookie') {
|
|
54
|
+
this.appendHeader(field, value);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
this.reply.header(field, value);
|
|
58
|
+
}
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
type(contentType) {
|
|
62
|
+
const normalized = contentType === 'html'
|
|
63
|
+
? 'text/html'
|
|
64
|
+
: contentType === 'json'
|
|
65
|
+
? 'application/json'
|
|
66
|
+
: contentType;
|
|
67
|
+
this.reply.type(normalized);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
json(payload) {
|
|
71
|
+
this.reply.send(payload);
|
|
72
|
+
}
|
|
73
|
+
send(payload) {
|
|
74
|
+
this.reply.send(payload);
|
|
75
|
+
}
|
|
76
|
+
redirect(statusOrUrl, maybeUrl) {
|
|
77
|
+
if (typeof statusOrUrl === 'number') {
|
|
78
|
+
if (maybeUrl === undefined) {
|
|
79
|
+
throw new Error('redirect url is required when status code is provided');
|
|
80
|
+
}
|
|
81
|
+
this.reply.status(statusOrUrl);
|
|
82
|
+
this.reply.header('Location', maybeUrl);
|
|
83
|
+
this.reply.send();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.reply.redirect(statusOrUrl);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
cookie(name, value, options) {
|
|
90
|
+
const serialized = serializeCookie(name, value, options);
|
|
91
|
+
this.appendHeader('Set-Cookie', serialized);
|
|
92
|
+
}
|
|
93
|
+
appendHeader(name, value) {
|
|
94
|
+
const existing = this.reply.getHeader(name);
|
|
95
|
+
if (Array.isArray(existing)) {
|
|
96
|
+
this.reply.header(name, [...existing, value]);
|
|
97
|
+
}
|
|
98
|
+
else if (typeof existing === 'string') {
|
|
99
|
+
this.reply.header(name, [existing, value]);
|
|
100
|
+
}
|
|
101
|
+
else if (existing === undefined) {
|
|
102
|
+
this.reply.header(name, value);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
this.reply.header(name, [String(existing), value]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function toCompatRequest(request) {
|
|
110
|
+
const headers = {};
|
|
111
|
+
for (const [key, value] of Object.entries(request.headers)) {
|
|
112
|
+
if (typeof value === 'string') {
|
|
113
|
+
headers[key.toLowerCase()] = value;
|
|
114
|
+
}
|
|
115
|
+
else if (Array.isArray(value)) {
|
|
116
|
+
headers[key.toLowerCase()] = value.join(', ');
|
|
117
|
+
}
|
|
118
|
+
else if (value !== undefined && value !== null) {
|
|
119
|
+
headers[key.toLowerCase()] = String(value);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
headers[key.toLowerCase()] = undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
body: request.body,
|
|
127
|
+
headers,
|
|
128
|
+
method: request.method,
|
|
129
|
+
originalUrl: request.raw.url ?? request.url,
|
|
130
|
+
query: request.query ?? {},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function serializeCookie(name, value, options) {
|
|
134
|
+
const segments = [
|
|
135
|
+
`${encodeURIComponent(name)}=${encodeURIComponent(value)}`,
|
|
136
|
+
];
|
|
137
|
+
if (options.maxAge !== undefined) {
|
|
138
|
+
const maxAgeMs = options.maxAge;
|
|
139
|
+
const maxAgeSeconds = Math.floor(maxAgeMs / 1000);
|
|
140
|
+
segments.push(`Max-Age=${maxAgeSeconds}`);
|
|
141
|
+
const expires = new Date(Date.now() + maxAgeMs).toUTCString();
|
|
142
|
+
segments.push(`Expires=${expires}`);
|
|
143
|
+
}
|
|
144
|
+
segments.push(`Path=${options.path ?? '/'}`);
|
|
145
|
+
if (options.httpOnly) {
|
|
146
|
+
segments.push('HttpOnly');
|
|
147
|
+
}
|
|
148
|
+
if (options.secure) {
|
|
149
|
+
segments.push('Secure');
|
|
150
|
+
}
|
|
151
|
+
if (options.sameSite) {
|
|
152
|
+
const normalized = options.sameSite.toLowerCase();
|
|
153
|
+
const formatted = normalized === 'strict'
|
|
154
|
+
? 'Strict'
|
|
155
|
+
: normalized === 'none'
|
|
156
|
+
? 'None'
|
|
157
|
+
: 'Lax';
|
|
158
|
+
segments.push(`SameSite=${formatted}`);
|
|
159
|
+
}
|
|
160
|
+
return segments.join('; ');
|
|
161
|
+
}
|
|
17
162
|
const DEFAULT_PREFIX = '/oauth';
|
|
18
163
|
const ENV_VAR_CLIENT_ID = 'FAME_JWT_CLIENT_ID';
|
|
19
164
|
const ENV_VAR_CLIENT_SECRET = 'FAME_JWT_CLIENT_SECRET';
|
|
@@ -435,11 +580,11 @@ function respondInvalidClient(res) {
|
|
|
435
580
|
});
|
|
436
581
|
}
|
|
437
582
|
/**
|
|
438
|
-
* Create
|
|
583
|
+
* Create a Fastify plugin that implements OAuth2 token and authorization endpoints
|
|
439
584
|
* with support for client credentials and authorization code (PKCE) grants.
|
|
440
585
|
*
|
|
441
586
|
* @param options - Router configuration options
|
|
442
|
-
* @returns
|
|
587
|
+
* @returns Fastify plugin with OAuth2 token and authorization endpoints
|
|
443
588
|
*
|
|
444
589
|
* Environment Variables:
|
|
445
590
|
* FAME_JWT_CLIENT_ID: OAuth2 client identifier
|
|
@@ -453,7 +598,7 @@ function respondInvalidClient(res) {
|
|
|
453
598
|
* FAME_OAUTH_CODE_TTL_SEC: Authorization code TTL in seconds (optional, default: 300)
|
|
454
599
|
*/
|
|
455
600
|
function createOAuth2TokenRouter(options) {
|
|
456
|
-
const router =
|
|
601
|
+
const router = new RouterCompat();
|
|
457
602
|
const { cryptoProvider, prefix = DEFAULT_PREFIX, 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);
|
|
458
603
|
if (!cryptoProvider) {
|
|
459
604
|
throw new Error('cryptoProvider is required to create OAuth2 token router');
|
|
@@ -752,7 +897,7 @@ function createOAuth2TokenRouter(options) {
|
|
|
752
897
|
};
|
|
753
898
|
router.post(`${prefix}/logout`, logoutHandler);
|
|
754
899
|
router.get(`${prefix}/logout`, logoutHandler);
|
|
755
|
-
router.post(`${prefix}/token`, async (req, res
|
|
900
|
+
router.post(`${prefix}/token`, async (req, res) => {
|
|
756
901
|
try {
|
|
757
902
|
cleanupAuthorizationCodes(authorizationCodes, Date.now());
|
|
758
903
|
const { grant_type, client_id, client_secret, scope, audience: reqAudience, code, redirect_uri, code_verifier, } = req.body ?? {};
|
|
@@ -937,7 +1082,7 @@ function createOAuth2TokenRouter(options) {
|
|
|
937
1082
|
}
|
|
938
1083
|
catch (error) {
|
|
939
1084
|
logger.error('oauth2_token_error', { error: error.message });
|
|
940
|
-
|
|
1085
|
+
throw error;
|
|
941
1086
|
}
|
|
942
1087
|
});
|
|
943
1088
|
async function issueTokenResponse(params) {
|
|
@@ -970,5 +1115,5 @@ function createOAuth2TokenRouter(options) {
|
|
|
970
1115
|
}
|
|
971
1116
|
return response;
|
|
972
1117
|
}
|
|
973
|
-
return router;
|
|
1118
|
+
return router.toPlugin();
|
|
974
1119
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* OpenID Connect Discovery configuration
|
|
3
|
+
* OpenID Connect Discovery configuration plugin for Fastify
|
|
4
4
|
*
|
|
5
5
|
* Provides /.well-known/openid-configuration endpoint for OAuth2/OIDC client auto-discovery
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.createOpenIDConfigurationRouter = createOpenIDConfigurationRouter;
|
|
9
|
-
const tslib_1 = require("tslib");
|
|
10
|
-
const express_1 = tslib_1.__importDefault(require("express"));
|
|
11
9
|
const logging_js_1 = require("../util/logging.js");
|
|
12
10
|
const logger = (0, logging_js_1.getLogger)('naylence.fame.http.openid_configuration_router');
|
|
13
11
|
const DEFAULT_PREFIX = '';
|
|
@@ -81,10 +79,10 @@ function getAllowedScopes(configScopes) {
|
|
|
81
79
|
return configScopes ?? ['node.connect'];
|
|
82
80
|
}
|
|
83
81
|
/**
|
|
84
|
-
* Create
|
|
82
|
+
* Create a Fastify plugin that implements OpenID Connect Discovery
|
|
85
83
|
*
|
|
86
84
|
* @param options - Router configuration options
|
|
87
|
-
* @returns
|
|
85
|
+
* @returns Fastify plugin with OpenID configuration endpoint
|
|
88
86
|
*
|
|
89
87
|
* Environment Variables:
|
|
90
88
|
* FAME_JWT_ISSUER: JWT issuer claim (optional)
|
|
@@ -93,17 +91,16 @@ function getAllowedScopes(configScopes) {
|
|
|
93
91
|
*
|
|
94
92
|
* @example
|
|
95
93
|
* ```typescript
|
|
96
|
-
* import
|
|
94
|
+
* import Fastify from 'fastify';
|
|
97
95
|
* import { createOpenIDConfigurationRouter } from '@naylence/runtime';
|
|
98
96
|
*
|
|
99
|
-
* const app =
|
|
100
|
-
* app.
|
|
97
|
+
* const app = Fastify();
|
|
98
|
+
* app.register(createOpenIDConfigurationRouter({
|
|
101
99
|
* issuer: 'https://auth.example.com',
|
|
102
100
|
* }));
|
|
103
101
|
* ```
|
|
104
102
|
*/
|
|
105
103
|
function createOpenIDConfigurationRouter(options = {}) {
|
|
106
|
-
const router = express_1.default.Router();
|
|
107
104
|
const { prefix = DEFAULT_PREFIX, issuer, baseUrl, tokenEndpointPath = '/oauth/token', jwksEndpointPath = '/.well-known/jwks.json', allowedScopes: configAllowedScopes, algorithm: configAlgorithm, } = normalizeOpenIDConfigurationRouterOptions(options);
|
|
108
105
|
// Resolve configuration with environment variable priority
|
|
109
106
|
const defaultIssuer = process.env[ENV_VAR_JWT_ISSUER] ?? issuer ?? 'https://auth.fame.fabric';
|
|
@@ -119,27 +116,28 @@ function createOpenIDConfigurationRouter(options = {}) {
|
|
|
119
116
|
algorithm,
|
|
120
117
|
allowedScopes,
|
|
121
118
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
119
|
+
const plugin = async (instance) => {
|
|
120
|
+
instance.get(`${prefix}/.well-known/openid-configuration`, async (_request, reply) => {
|
|
121
|
+
// Construct absolute URLs for endpoints
|
|
122
|
+
const tokenEndpoint = `${defaultBaseUrl.replace(/\/$/, '')}${tokenEndpointPath}`;
|
|
123
|
+
const jwksUri = `${defaultBaseUrl.replace(/\/$/, '')}${jwksEndpointPath}`;
|
|
124
|
+
const config = {
|
|
125
|
+
issuer: defaultIssuer,
|
|
126
|
+
token_endpoint: tokenEndpoint,
|
|
127
|
+
jwks_uri: jwksUri,
|
|
128
|
+
scopes_supported: allowedScopes,
|
|
129
|
+
response_types_supported: ['token'],
|
|
130
|
+
grant_types_supported: ['client_credentials'],
|
|
131
|
+
token_endpoint_auth_methods_supported: [
|
|
132
|
+
'client_secret_basic',
|
|
133
|
+
'client_secret_post',
|
|
134
|
+
],
|
|
135
|
+
subject_types_supported: ['public'],
|
|
136
|
+
id_token_signing_alg_values_supported: [algorithm],
|
|
137
|
+
};
|
|
138
|
+
logger.debug('openid_config_served', { config });
|
|
139
|
+
reply.send(config);
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
return plugin;
|
|
145
143
|
}
|
|
@@ -22,6 +22,8 @@ const ENV_VAR_DIRECT_INPAGE_CHANNEL = 'FAME_DIRECT_INPAGE_CHANNEL';
|
|
|
22
22
|
const ENV_VAR_ADMISSION_SERVICE_URL = 'FAME_ADMISSION_SERVICE_URL';
|
|
23
23
|
const DEFAULT_INPAGE_CHANNEL = 'naylence-fabric';
|
|
24
24
|
const PROFILE_NAME_WELCOME = 'welcome';
|
|
25
|
+
const PROFILE_NAME_WELCOME_PKCE = 'welcome-pkce';
|
|
26
|
+
const PROFILE_NAME_WELCOME_PKCE_ALIAS = 'welcome_pkce';
|
|
25
27
|
const PROFILE_NAME_DIRECT = 'direct';
|
|
26
28
|
const PROFILE_NAME_DIRECT_HTTP = 'direct-http';
|
|
27
29
|
const PROFILE_NAME_DIRECT_INPAGE = 'direct-inpage';
|
|
@@ -82,6 +84,7 @@ function createOAuthPkceTokenProviderConfig() {
|
|
|
82
84
|
}
|
|
83
85
|
const welcomeIsRoot = factory_1.Expressions.env(ENV_VAR_IS_ROOT, 'false');
|
|
84
86
|
const welcomeTokenProvider = createOAuthTokenProviderConfig();
|
|
87
|
+
const welcomePkceTokenProvider = createOAuthPkceTokenProviderConfig();
|
|
85
88
|
const WELCOME_SERVICE_PROFILE = {
|
|
86
89
|
type: 'WelcomeServiceClient',
|
|
87
90
|
is_root: welcomeIsRoot,
|
|
@@ -95,6 +98,19 @@ const WELCOME_SERVICE_PROFILE = {
|
|
|
95
98
|
tokenProvider: welcomeTokenProvider,
|
|
96
99
|
},
|
|
97
100
|
};
|
|
101
|
+
const WELCOME_SERVICE_PKCE_PROFILE = {
|
|
102
|
+
type: 'WelcomeServiceClient',
|
|
103
|
+
is_root: welcomeIsRoot,
|
|
104
|
+
isRoot: welcomeIsRoot,
|
|
105
|
+
url: factory_1.Expressions.env(ENV_VAR_ADMISSION_SERVICE_URL),
|
|
106
|
+
supported_transports: ['websocket'],
|
|
107
|
+
supportedTransports: ['websocket'],
|
|
108
|
+
auth: {
|
|
109
|
+
type: 'BearerTokenHeaderAuth',
|
|
110
|
+
token_provider: welcomePkceTokenProvider,
|
|
111
|
+
tokenProvider: welcomePkceTokenProvider,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
98
114
|
const directGrantTokenProvider = createOAuthTokenProviderConfig();
|
|
99
115
|
const directGrant = {
|
|
100
116
|
type: 'WebSocketConnectionGrant',
|
|
@@ -200,6 +216,8 @@ const NOOP_PROFILE = {
|
|
|
200
216
|
};
|
|
201
217
|
const PROFILE_MAP = {
|
|
202
218
|
[PROFILE_NAME_WELCOME]: WELCOME_SERVICE_PROFILE,
|
|
219
|
+
[PROFILE_NAME_WELCOME_PKCE]: WELCOME_SERVICE_PKCE_PROFILE,
|
|
220
|
+
[PROFILE_NAME_WELCOME_PKCE_ALIAS]: WELCOME_SERVICE_PKCE_PROFILE,
|
|
203
221
|
[PROFILE_NAME_DIRECT]: DIRECT_PROFILE,
|
|
204
222
|
[PROFILE_NAME_DIRECT_PKCE]: DIRECT_PKCE_PROFILE,
|
|
205
223
|
[PROFILE_NAME_DIRECT_PKCE_ALIAS]: DIRECT_PKCE_PROFILE,
|