@enterprisestandard/esv 0.0.5-beta.20260115.2 → 0.0.5-beta.20260115.4
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/iam/index.d.ts +8 -5
- package/dist/iam/index.js +5755 -664
- package/dist/iam/index.js.map +1 -1
- package/dist/index.d.ts +90 -10
- package/dist/index.js +6897 -152
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts +0 -36
- package/dist/runner.js +11407 -283
- package/dist/runner.js.map +1 -1
- package/dist/server/index.d.ts +88 -14
- package/dist/server/index.js +1387 -33
- package/dist/server/index.js.map +1 -1
- package/dist/sso/index.d.ts +8 -5
- package/dist/sso/index.js +365 -357
- package/dist/sso/index.js.map +1 -1
- package/dist/{types.d.ts → types-Bn1pr_xY.d.ts} +13 -11
- package/dist/workload/index.d.ts +8 -5
- package/dist/workload/index.js +393 -403
- package/dist/workload/index.js.map +1 -1
- package/package.json +2 -4
- package/dist/iam/index.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/runner.d.ts.map +0 -1
- package/dist/server/crypto.d.ts +0 -46
- package/dist/server/crypto.d.ts.map +0 -1
- package/dist/server/crypto.js +0 -134
- package/dist/server/crypto.js.map +0 -1
- package/dist/server/iam.d.ts +0 -11
- package/dist/server/iam.d.ts.map +0 -1
- package/dist/server/iam.js +0 -402
- package/dist/server/iam.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/server.d.ts +0 -66
- package/dist/server/server.d.ts.map +0 -1
- package/dist/server/server.js +0 -223
- package/dist/server/server.js.map +0 -1
- package/dist/server/sso.d.ts +0 -11
- package/dist/server/sso.d.ts.map +0 -1
- package/dist/server/sso.js +0 -428
- package/dist/server/sso.js.map +0 -1
- package/dist/server/state.d.ts +0 -137
- package/dist/server/state.d.ts.map +0 -1
- package/dist/server/state.js +0 -152
- package/dist/server/state.js.map +0 -1
- package/dist/server/vault.d.ts +0 -11
- package/dist/server/vault.d.ts.map +0 -1
- package/dist/server/vault.js +0 -92
- package/dist/server/vault.js.map +0 -1
- package/dist/server/workload.d.ts +0 -19
- package/dist/server/workload.d.ts.map +0 -1
- package/dist/server/workload.js +0 -226
- package/dist/server/workload.js.map +0 -1
- package/dist/sso/index.d.ts.map +0 -1
- package/dist/tenant/index.d.ts +0 -17
- package/dist/tenant/index.d.ts.map +0 -1
- package/dist/tenant/index.js +0 -300
- package/dist/tenant/index.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -75
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -139
- package/dist/utils.js.map +0 -1
- package/dist/workload/index.d.ts.map +0 -1
package/dist/server/server.js
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ESV Mock Server
|
|
3
|
-
*
|
|
4
|
-
* A zero-dependency mock server that simulates Vault, SSO/IDP, IAM/SCIM,
|
|
5
|
-
* and Workload Identity services for testing Enterprise Standard integrations.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```typescript
|
|
9
|
-
* import { startServer, stopServer } from '@enterprisestandard/esv/server';
|
|
10
|
-
*
|
|
11
|
-
* // Start before tests
|
|
12
|
-
* await startServer();
|
|
13
|
-
*
|
|
14
|
-
* // Initialize with ES_VAULT_URL, ES_VAULT_TOKEN, and ES_VAULT_PATH set
|
|
15
|
-
* const es = await enterpriseStandard(undefined, {
|
|
16
|
-
* vaultUrl: process.env.ES_VAULT_URL,
|
|
17
|
-
* vaultToken: process.env.ES_VAULT_TOKEN,
|
|
18
|
-
* vaultPath: process.env.ES_VAULT_PATH,
|
|
19
|
-
* });
|
|
20
|
-
*
|
|
21
|
-
* // Stop after tests
|
|
22
|
-
* await stopServer();
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
import { createServer } from 'node:http';
|
|
26
|
-
import { initializeKeys } from './crypto.js';
|
|
27
|
-
import { handleIamRequest } from './iam.js';
|
|
28
|
-
import { handleSsoRequest } from './sso.js';
|
|
29
|
-
import { resetState } from './state.js';
|
|
30
|
-
import { handleVaultRequest } from './vault.js';
|
|
31
|
-
import { handleWhoamiRequest, handleWorkloadRequest } from './workload.js';
|
|
32
|
-
/**
|
|
33
|
-
* Handle webhook requests for tenant status updates
|
|
34
|
-
*/
|
|
35
|
-
async function handleWebhook(req, res) {
|
|
36
|
-
if (req.method !== 'POST') {
|
|
37
|
-
res.writeHead(405, { 'Content-Type': 'application/json' });
|
|
38
|
-
res.end(JSON.stringify({ error: 'Method not allowed' }));
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
// Read request body
|
|
42
|
-
let body = '';
|
|
43
|
-
req.on('data', (chunk) => {
|
|
44
|
-
body += chunk.toString();
|
|
45
|
-
});
|
|
46
|
-
req.on('end', () => {
|
|
47
|
-
try {
|
|
48
|
-
const payload = JSON.parse(body);
|
|
49
|
-
// Just acknowledge the webhook - we don't need to do anything with it for testing
|
|
50
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
51
|
-
res.end(JSON.stringify({ received: true, payload }));
|
|
52
|
-
}
|
|
53
|
-
catch (_error) {
|
|
54
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
55
|
-
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
const DEFAULT_PORT = 3555;
|
|
60
|
-
const DEFAULT_HOST = 'localhost';
|
|
61
|
-
let server = null;
|
|
62
|
-
let isStarted = false;
|
|
63
|
-
/**
|
|
64
|
-
* Request router
|
|
65
|
-
*/
|
|
66
|
-
async function handleRequest(req, res, verbose) {
|
|
67
|
-
const url = new URL(req.url || '/', `http://${req.headers.host}`);
|
|
68
|
-
const pathname = url.pathname;
|
|
69
|
-
if (verbose) {
|
|
70
|
-
console.log(`[ESV Server] ${req.method} ${pathname}`);
|
|
71
|
-
}
|
|
72
|
-
// Add CORS headers for local development
|
|
73
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
74
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
|
75
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Vault-Token');
|
|
76
|
-
// Handle preflight requests
|
|
77
|
-
if (req.method === 'OPTIONS') {
|
|
78
|
-
res.writeHead(204);
|
|
79
|
-
res.end();
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
try {
|
|
83
|
-
// Route to appropriate handler
|
|
84
|
-
if (pathname.startsWith('/vault')) {
|
|
85
|
-
handleVaultRequest(req, res, pathname);
|
|
86
|
-
}
|
|
87
|
-
else if (pathname.startsWith('/sso')) {
|
|
88
|
-
await handleSsoRequest(req, res, pathname);
|
|
89
|
-
}
|
|
90
|
-
else if (pathname.startsWith('/iam')) {
|
|
91
|
-
await handleIamRequest(req, res, pathname);
|
|
92
|
-
}
|
|
93
|
-
else if (pathname.startsWith('/workload')) {
|
|
94
|
-
await handleWorkloadRequest(req, res, pathname);
|
|
95
|
-
}
|
|
96
|
-
else if (pathname === '/api/whoami') {
|
|
97
|
-
// Mock whoami endpoint for testing workload authentication independently
|
|
98
|
-
handleWhoamiRequest(req, res);
|
|
99
|
-
}
|
|
100
|
-
else if (pathname === '/webhook') {
|
|
101
|
-
// Webhook endpoint for tenant status updates
|
|
102
|
-
handleWebhook(req, res);
|
|
103
|
-
}
|
|
104
|
-
else if (pathname === '/health' || pathname === '/') {
|
|
105
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
106
|
-
res.end(JSON.stringify({ status: 'ok', service: 'esv-mock-server' }));
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
110
|
-
res.end(JSON.stringify({ error: 'Not found' }));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
console.error('[ESV Server] Error handling request:', error);
|
|
115
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
116
|
-
res.end(JSON.stringify({ error: 'Internal server error' }));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Start the ESV mock server
|
|
121
|
-
*
|
|
122
|
-
* @param options - Server configuration options
|
|
123
|
-
* @returns Promise that resolves when the server is listening
|
|
124
|
-
*/
|
|
125
|
-
export function startServer(options = {}) {
|
|
126
|
-
const { port = DEFAULT_PORT, host = DEFAULT_HOST, verbose = false } = options;
|
|
127
|
-
if (isStarted && server) {
|
|
128
|
-
if (verbose) {
|
|
129
|
-
console.log('[ESV Server] Server already running');
|
|
130
|
-
}
|
|
131
|
-
return Promise.resolve();
|
|
132
|
-
}
|
|
133
|
-
return new Promise((resolve, reject) => {
|
|
134
|
-
try {
|
|
135
|
-
// Initialize cryptographic keys
|
|
136
|
-
initializeKeys();
|
|
137
|
-
// Reset state for fresh start
|
|
138
|
-
resetState();
|
|
139
|
-
server = createServer((req, res) => {
|
|
140
|
-
handleRequest(req, res, verbose).catch((error) => {
|
|
141
|
-
console.error('[ESV Server] Unhandled error:', error);
|
|
142
|
-
if (!res.headersSent) {
|
|
143
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
144
|
-
res.end(JSON.stringify({ error: 'Internal server error' }));
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
server.on('error', (error) => {
|
|
149
|
-
if (error.code === 'EADDRINUSE') {
|
|
150
|
-
// Port already in use - maybe server is already running
|
|
151
|
-
if (verbose) {
|
|
152
|
-
console.log(`[ESV Server] Port ${port} already in use, assuming server is running`);
|
|
153
|
-
}
|
|
154
|
-
isStarted = true;
|
|
155
|
-
resolve();
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
reject(error);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
server.listen(port, host, () => {
|
|
162
|
-
isStarted = true;
|
|
163
|
-
console.log(`[ESV Server] Running at http://${host}:${port}/`);
|
|
164
|
-
console.log('[ESV Server] Endpoints:');
|
|
165
|
-
console.log(` - Vault: http://${host}:${port}/vault/v1/secret/data/esv/config`);
|
|
166
|
-
console.log(` - SSO: http://${host}:${port}/sso/*`);
|
|
167
|
-
console.log(` - IAM: http://${host}:${port}/iam/*`);
|
|
168
|
-
console.log(` - Workload: http://${host}:${port}/workload/*`);
|
|
169
|
-
console.log(` - Whoami: http://${host}:${port}/api/whoami`);
|
|
170
|
-
console.log(` - Webhook: http://${host}:${port}/webhook`);
|
|
171
|
-
resolve();
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
catch (error) {
|
|
175
|
-
reject(error);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Stop the ESV mock server
|
|
181
|
-
*
|
|
182
|
-
* @returns Promise that resolves when the server has stopped
|
|
183
|
-
*/
|
|
184
|
-
export function stopServer() {
|
|
185
|
-
return new Promise((resolve, reject) => {
|
|
186
|
-
if (!server) {
|
|
187
|
-
isStarted = false;
|
|
188
|
-
resolve();
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
server.close((error) => {
|
|
192
|
-
if (error) {
|
|
193
|
-
// Ignore errors if server wasn't running
|
|
194
|
-
if (error.code === 'ERR_SERVER_NOT_RUNNING') {
|
|
195
|
-
server = null;
|
|
196
|
-
isStarted = false;
|
|
197
|
-
resolve();
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
reject(error);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
server = null;
|
|
204
|
-
isStarted = false;
|
|
205
|
-
console.log('[ESV Server] Stopped');
|
|
206
|
-
resolve();
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Check if the server is running
|
|
212
|
-
*/
|
|
213
|
-
export function isServerRunning() {
|
|
214
|
-
return isStarted;
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Get the server URL
|
|
218
|
-
*/
|
|
219
|
-
export function getServerUrl(options = {}) {
|
|
220
|
-
const { port = DEFAULT_PORT, host = DEFAULT_HOST } = options;
|
|
221
|
-
return `http://${host}:${port}`;
|
|
222
|
-
}
|
|
223
|
-
//# sourceMappingURL=server.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server/sso.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SSO/OIDC endpoint handlers for the ESV mock server
|
|
3
|
-
*
|
|
4
|
-
* Implements a minimal OIDC provider for testing SSO integrations.
|
|
5
|
-
*/
|
|
6
|
-
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
7
|
-
/**
|
|
8
|
-
* Handle SSO requests
|
|
9
|
-
*/
|
|
10
|
-
export declare function handleSsoRequest(req: IncomingMessage, res: ServerResponse, pathname: string): Promise<void>;
|
|
11
|
-
//# sourceMappingURL=sso.d.ts.map
|
package/dist/server/sso.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sso.d.ts","sourceRoot":"","sources":["../../src/server/sso.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAqCjE;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDjH"}
|
package/dist/server/sso.js
DELETED
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SSO/OIDC endpoint handlers for the ESV mock server
|
|
3
|
-
*
|
|
4
|
-
* Implements a minimal OIDC provider for testing SSO integrations.
|
|
5
|
-
*/
|
|
6
|
-
import { generateRandomString, generateUUID, getJwks, signJwt, verifyCodeChallenge } from './crypto.js';
|
|
7
|
-
import { createSession, deleteAuthCode, deleteRefreshToken, deleteSession, getAuthCode, getRefreshToken, getTestUser, storeAuthCode, storeRefreshToken, } from './state.js';
|
|
8
|
-
const ISSUER = 'http://localhost:3555/sso';
|
|
9
|
-
const TOKEN_EXPIRY = 3600; // 1 hour
|
|
10
|
-
const REFRESH_EXPIRY = 86400; // 24 hours
|
|
11
|
-
const CODE_EXPIRY = 300; // 5 minutes
|
|
12
|
-
/**
|
|
13
|
-
* Parse request body as URL-encoded form data
|
|
14
|
-
*/
|
|
15
|
-
async function parseFormBody(req) {
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
let body = '';
|
|
18
|
-
req.on('data', (chunk) => {
|
|
19
|
-
body += chunk.toString();
|
|
20
|
-
});
|
|
21
|
-
req.on('end', () => {
|
|
22
|
-
resolve(new URLSearchParams(body));
|
|
23
|
-
});
|
|
24
|
-
req.on('error', reject);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Handle SSO requests
|
|
29
|
-
*/
|
|
30
|
-
export async function handleSsoRequest(req, res, pathname) {
|
|
31
|
-
// Remove /sso prefix
|
|
32
|
-
const ssoPath = pathname.replace(/^\/sso/, '');
|
|
33
|
-
// GET /sso/authorize - Authorization endpoint
|
|
34
|
-
if (req.method === 'GET' && ssoPath === '/authorize') {
|
|
35
|
-
await handleAuthorize(req, res);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// POST /sso/token - Token endpoint
|
|
39
|
-
if (req.method === 'POST' && ssoPath === '/token') {
|
|
40
|
-
await handleToken(req, res);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
// GET /sso/certs - JWKS endpoint
|
|
44
|
-
if (req.method === 'GET' && ssoPath === '/certs') {
|
|
45
|
-
handleCerts(res);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
// POST /sso/revoke - Token revocation
|
|
49
|
-
if (req.method === 'POST' && ssoPath === '/revoke') {
|
|
50
|
-
await handleRevoke(req, res);
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
// GET /sso/userinfo - User info endpoint
|
|
54
|
-
if (req.method === 'GET' && ssoPath === '/userinfo') {
|
|
55
|
-
handleUserInfo(req, res);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
// GET /sso/logout - End session endpoint
|
|
59
|
-
if (req.method === 'GET' && ssoPath === '/logout') {
|
|
60
|
-
handleLogout(req, res);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
// POST /sso/logout - Back-channel logout
|
|
64
|
-
if (req.method === 'POST' && ssoPath === '/logout/backchannel') {
|
|
65
|
-
await handleBackChannelLogout(req, res);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
// GET /sso/.well-known/openid-configuration
|
|
69
|
-
if (req.method === 'GET' && ssoPath === '/.well-known/openid-configuration') {
|
|
70
|
-
handleOpenIDConfig(res);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
74
|
-
res.end(JSON.stringify({ error: 'not_found' }));
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Authorization endpoint - issues authorization codes
|
|
78
|
-
*
|
|
79
|
-
* In a real implementation, this would show a login form.
|
|
80
|
-
* For testing, we auto-authenticate with the test user.
|
|
81
|
-
*/
|
|
82
|
-
async function handleAuthorize(req, res) {
|
|
83
|
-
const url = new URL(req.url || '', `http://${req.headers.host}`);
|
|
84
|
-
const params = url.searchParams;
|
|
85
|
-
const clientId = params.get('client_id');
|
|
86
|
-
const redirectUri = params.get('redirect_uri');
|
|
87
|
-
const responseType = params.get('response_type');
|
|
88
|
-
const scope = params.get('scope') || 'openid';
|
|
89
|
-
const state = params.get('state');
|
|
90
|
-
const codeChallenge = params.get('code_challenge');
|
|
91
|
-
const codeChallengeMethod = params.get('code_challenge_method');
|
|
92
|
-
// Validate required parameters
|
|
93
|
-
if (!clientId || !redirectUri || !responseType) {
|
|
94
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
95
|
-
res.end(JSON.stringify({
|
|
96
|
-
error: 'invalid_request',
|
|
97
|
-
error_description: 'Missing required parameters',
|
|
98
|
-
}));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (responseType !== 'code') {
|
|
102
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
103
|
-
res.end(JSON.stringify({
|
|
104
|
-
error: 'unsupported_response_type',
|
|
105
|
-
error_description: 'Only code response type is supported',
|
|
106
|
-
}));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
// Generate authorization code
|
|
110
|
-
const testUser = getTestUser();
|
|
111
|
-
const code = generateRandomString(32);
|
|
112
|
-
const authCode = {
|
|
113
|
-
code,
|
|
114
|
-
userId: testUser.id,
|
|
115
|
-
clientId,
|
|
116
|
-
redirectUri,
|
|
117
|
-
scope,
|
|
118
|
-
codeChallenge: codeChallenge || undefined,
|
|
119
|
-
codeChallengeMethod: codeChallengeMethod || undefined,
|
|
120
|
-
state: state || undefined,
|
|
121
|
-
createdAt: new Date(),
|
|
122
|
-
expiresAt: new Date(Date.now() + CODE_EXPIRY * 1000),
|
|
123
|
-
};
|
|
124
|
-
storeAuthCode(authCode);
|
|
125
|
-
// Redirect back with code
|
|
126
|
-
const redirectUrl = new URL(redirectUri);
|
|
127
|
-
redirectUrl.searchParams.set('code', code);
|
|
128
|
-
if (state) {
|
|
129
|
-
redirectUrl.searchParams.set('state', state);
|
|
130
|
-
}
|
|
131
|
-
res.writeHead(302, { Location: redirectUrl.toString() });
|
|
132
|
-
res.end();
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Token endpoint - exchanges codes for tokens or refreshes tokens
|
|
136
|
-
*/
|
|
137
|
-
async function handleToken(req, res) {
|
|
138
|
-
const body = await parseFormBody(req);
|
|
139
|
-
const grantType = body.get('grant_type');
|
|
140
|
-
if (grantType === 'authorization_code') {
|
|
141
|
-
await handleAuthorizationCodeGrant(body, res);
|
|
142
|
-
}
|
|
143
|
-
else if (grantType === 'refresh_token') {
|
|
144
|
-
await handleRefreshTokenGrant(body, res);
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
148
|
-
res.end(JSON.stringify({
|
|
149
|
-
error: 'unsupported_grant_type',
|
|
150
|
-
error_description: 'Only authorization_code and refresh_token are supported',
|
|
151
|
-
}));
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Handle authorization code exchange
|
|
156
|
-
*/
|
|
157
|
-
async function handleAuthorizationCodeGrant(body, res) {
|
|
158
|
-
const code = body.get('code');
|
|
159
|
-
const redirectUri = body.get('redirect_uri');
|
|
160
|
-
const codeVerifier = body.get('code_verifier');
|
|
161
|
-
if (!code || !redirectUri) {
|
|
162
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
163
|
-
res.end(JSON.stringify({
|
|
164
|
-
error: 'invalid_request',
|
|
165
|
-
error_description: 'Missing code or redirect_uri',
|
|
166
|
-
}));
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
const authCode = getAuthCode(code);
|
|
170
|
-
if (!authCode) {
|
|
171
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
172
|
-
res.end(JSON.stringify({
|
|
173
|
-
error: 'invalid_grant',
|
|
174
|
-
error_description: 'Invalid or expired authorization code',
|
|
175
|
-
}));
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
// Validate redirect URI
|
|
179
|
-
if (authCode.redirectUri !== redirectUri) {
|
|
180
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
181
|
-
res.end(JSON.stringify({
|
|
182
|
-
error: 'invalid_grant',
|
|
183
|
-
error_description: 'Redirect URI mismatch',
|
|
184
|
-
}));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
// Validate PKCE if present
|
|
188
|
-
if (authCode.codeChallenge && authCode.codeChallengeMethod === 'S256') {
|
|
189
|
-
if (!codeVerifier || !verifyCodeChallenge(codeVerifier, authCode.codeChallenge)) {
|
|
190
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
191
|
-
res.end(JSON.stringify({
|
|
192
|
-
error: 'invalid_grant',
|
|
193
|
-
error_description: 'Invalid code verifier',
|
|
194
|
-
}));
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
// Delete used code
|
|
199
|
-
deleteAuthCode(code);
|
|
200
|
-
// Create session
|
|
201
|
-
const sessionId = generateUUID();
|
|
202
|
-
createSession({
|
|
203
|
-
id: sessionId,
|
|
204
|
-
userId: authCode.userId,
|
|
205
|
-
createdAt: new Date(),
|
|
206
|
-
lastActivityAt: new Date(),
|
|
207
|
-
});
|
|
208
|
-
// Generate tokens
|
|
209
|
-
const testUser = getTestUser();
|
|
210
|
-
const tokens = generateTokens(testUser, authCode.clientId, authCode.scope, sessionId);
|
|
211
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
212
|
-
res.end(JSON.stringify(tokens));
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Handle refresh token grant
|
|
216
|
-
*/
|
|
217
|
-
async function handleRefreshTokenGrant(body, res) {
|
|
218
|
-
const refreshToken = body.get('refresh_token');
|
|
219
|
-
if (!refreshToken) {
|
|
220
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
221
|
-
res.end(JSON.stringify({
|
|
222
|
-
error: 'invalid_request',
|
|
223
|
-
error_description: 'Missing refresh_token',
|
|
224
|
-
}));
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
const tokenData = getRefreshToken(refreshToken);
|
|
228
|
-
if (!tokenData || tokenData.expiresAt < new Date()) {
|
|
229
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
230
|
-
res.end(JSON.stringify({
|
|
231
|
-
error: 'invalid_grant',
|
|
232
|
-
error_description: 'Invalid or expired refresh token',
|
|
233
|
-
}));
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
// Delete old refresh token (rotation)
|
|
237
|
-
deleteRefreshToken(refreshToken);
|
|
238
|
-
// Generate new tokens
|
|
239
|
-
const testUser = getTestUser();
|
|
240
|
-
const tokens = generateTokens(testUser, tokenData.clientId, tokenData.scope, tokenData.sessionId);
|
|
241
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
242
|
-
res.end(JSON.stringify(tokens));
|
|
243
|
-
}
|
|
244
|
-
/**
|
|
245
|
-
* Generate access token, ID token, and refresh token
|
|
246
|
-
*/
|
|
247
|
-
function generateTokens(user, clientId, scope, sessionId) {
|
|
248
|
-
const now = Math.floor(Date.now() / 1000);
|
|
249
|
-
// ID Token claims
|
|
250
|
-
const idTokenClaims = {
|
|
251
|
-
iss: ISSUER,
|
|
252
|
-
sub: user.id,
|
|
253
|
-
aud: clientId,
|
|
254
|
-
nonce: generateRandomString(16),
|
|
255
|
-
sid: sessionId,
|
|
256
|
-
auth_time: now,
|
|
257
|
-
email: user.email,
|
|
258
|
-
email_verified: true,
|
|
259
|
-
name: user.name,
|
|
260
|
-
given_name: user.givenName,
|
|
261
|
-
family_name: user.familyName,
|
|
262
|
-
preferred_username: user.userName,
|
|
263
|
-
picture: user.picture,
|
|
264
|
-
};
|
|
265
|
-
// Access token claims
|
|
266
|
-
const accessTokenClaims = {
|
|
267
|
-
iss: ISSUER,
|
|
268
|
-
sub: user.id,
|
|
269
|
-
aud: clientId,
|
|
270
|
-
scope,
|
|
271
|
-
sid: sessionId,
|
|
272
|
-
};
|
|
273
|
-
const idToken = signJwt(idTokenClaims, TOKEN_EXPIRY);
|
|
274
|
-
const accessToken = signJwt(accessTokenClaims, TOKEN_EXPIRY);
|
|
275
|
-
const refreshTokenValue = generateRandomString(64);
|
|
276
|
-
// Store refresh token
|
|
277
|
-
const refreshToken = {
|
|
278
|
-
token: refreshTokenValue,
|
|
279
|
-
userId: user.id,
|
|
280
|
-
clientId,
|
|
281
|
-
scope,
|
|
282
|
-
sessionId,
|
|
283
|
-
createdAt: new Date(),
|
|
284
|
-
expiresAt: new Date(Date.now() + REFRESH_EXPIRY * 1000),
|
|
285
|
-
};
|
|
286
|
-
storeRefreshToken(refreshToken);
|
|
287
|
-
return {
|
|
288
|
-
access_token: accessToken,
|
|
289
|
-
token_type: 'Bearer',
|
|
290
|
-
expires_in: TOKEN_EXPIRY,
|
|
291
|
-
refresh_token: refreshTokenValue,
|
|
292
|
-
refresh_expires_in: REFRESH_EXPIRY,
|
|
293
|
-
id_token: idToken,
|
|
294
|
-
scope,
|
|
295
|
-
session_state: sessionId,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* JWKS endpoint
|
|
300
|
-
*/
|
|
301
|
-
function handleCerts(res) {
|
|
302
|
-
const jwks = getJwks();
|
|
303
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
304
|
-
res.end(JSON.stringify(jwks));
|
|
305
|
-
}
|
|
306
|
-
/**
|
|
307
|
-
* Token revocation endpoint
|
|
308
|
-
*/
|
|
309
|
-
async function handleRevoke(req, res) {
|
|
310
|
-
const body = await parseFormBody(req);
|
|
311
|
-
const token = body.get('token');
|
|
312
|
-
const tokenTypeHint = body.get('token_type_hint');
|
|
313
|
-
if (token) {
|
|
314
|
-
if (tokenTypeHint === 'refresh_token' || !tokenTypeHint) {
|
|
315
|
-
deleteRefreshToken(token);
|
|
316
|
-
}
|
|
317
|
-
// Access tokens are stateless, can't be revoked
|
|
318
|
-
}
|
|
319
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
320
|
-
res.end(JSON.stringify({ success: true }));
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* User info endpoint
|
|
324
|
-
*/
|
|
325
|
-
function handleUserInfo(req, res) {
|
|
326
|
-
// Extract and validate token
|
|
327
|
-
const authHeader = req.headers.authorization;
|
|
328
|
-
if (!authHeader?.startsWith('Bearer ')) {
|
|
329
|
-
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
330
|
-
res.end(JSON.stringify({ error: 'invalid_token' }));
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
// For simplicity, we don't validate the token here - just return test user
|
|
334
|
-
const user = getTestUser();
|
|
335
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
336
|
-
res.end(JSON.stringify({
|
|
337
|
-
sub: user.id,
|
|
338
|
-
email: user.email,
|
|
339
|
-
email_verified: true,
|
|
340
|
-
name: user.name,
|
|
341
|
-
given_name: user.givenName,
|
|
342
|
-
family_name: user.familyName,
|
|
343
|
-
preferred_username: user.userName,
|
|
344
|
-
picture: user.picture,
|
|
345
|
-
}));
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* End session / logout endpoint
|
|
349
|
-
*/
|
|
350
|
-
function handleLogout(req, res) {
|
|
351
|
-
const url = new URL(req.url || '', `http://${req.headers.host}`);
|
|
352
|
-
const postLogoutRedirectUri = url.searchParams.get('post_logout_redirect_uri');
|
|
353
|
-
if (postLogoutRedirectUri) {
|
|
354
|
-
res.writeHead(302, { Location: postLogoutRedirectUri });
|
|
355
|
-
res.end();
|
|
356
|
-
}
|
|
357
|
-
else {
|
|
358
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
359
|
-
res.end('<html><body><h1>Logged out</h1></body></html>');
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Back-channel logout endpoint
|
|
364
|
-
*/
|
|
365
|
-
async function handleBackChannelLogout(req, res) {
|
|
366
|
-
const body = await parseFormBody(req);
|
|
367
|
-
const logoutToken = body.get('logout_token');
|
|
368
|
-
if (!logoutToken) {
|
|
369
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
370
|
-
res.end(JSON.stringify({ error: 'invalid_request', error_description: 'Missing logout_token' }));
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
// Parse the logout token to get sid
|
|
374
|
-
try {
|
|
375
|
-
const parts = logoutToken.split('.');
|
|
376
|
-
if (parts.length >= 2) {
|
|
377
|
-
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));
|
|
378
|
-
if (payload.sid) {
|
|
379
|
-
deleteSession(payload.sid);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
catch {
|
|
384
|
-
// Ignore parsing errors
|
|
385
|
-
}
|
|
386
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
387
|
-
res.end(JSON.stringify({ success: true }));
|
|
388
|
-
}
|
|
389
|
-
/**
|
|
390
|
-
* OpenID Configuration endpoint
|
|
391
|
-
*/
|
|
392
|
-
function handleOpenIDConfig(res) {
|
|
393
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
394
|
-
res.end(JSON.stringify({
|
|
395
|
-
issuer: ISSUER,
|
|
396
|
-
authorization_endpoint: `${ISSUER}/authorize`,
|
|
397
|
-
token_endpoint: `${ISSUER}/token`,
|
|
398
|
-
userinfo_endpoint: `${ISSUER}/userinfo`,
|
|
399
|
-
jwks_uri: `${ISSUER}/certs`,
|
|
400
|
-
end_session_endpoint: `${ISSUER}/logout`,
|
|
401
|
-
revocation_endpoint: `${ISSUER}/revoke`,
|
|
402
|
-
response_types_supported: ['code'],
|
|
403
|
-
subject_types_supported: ['public'],
|
|
404
|
-
id_token_signing_alg_values_supported: ['RS256'],
|
|
405
|
-
scopes_supported: ['openid', 'profile', 'email'],
|
|
406
|
-
token_endpoint_auth_methods_supported: ['client_secret_post', 'client_secret_basic'],
|
|
407
|
-
claims_supported: [
|
|
408
|
-
'sub',
|
|
409
|
-
'iss',
|
|
410
|
-
'aud',
|
|
411
|
-
'exp',
|
|
412
|
-
'iat',
|
|
413
|
-
'nonce',
|
|
414
|
-
'email',
|
|
415
|
-
'email_verified',
|
|
416
|
-
'name',
|
|
417
|
-
'given_name',
|
|
418
|
-
'family_name',
|
|
419
|
-
'preferred_username',
|
|
420
|
-
'picture',
|
|
421
|
-
'sid',
|
|
422
|
-
],
|
|
423
|
-
code_challenge_methods_supported: ['S256'],
|
|
424
|
-
backchannel_logout_supported: true,
|
|
425
|
-
backchannel_logout_session_supported: true,
|
|
426
|
-
}));
|
|
427
|
-
}
|
|
428
|
-
//# sourceMappingURL=sso.js.map
|