Extract the operational Model Delivery PWA
This commit is contained in:
commit
958f8d5ff1
46 files changed
+6446
No files matched your search
@@ -0,0 +1,2 @@
|
||||
// Only used against model_delivery_test. This is not a default application account.
|
||||
export const testAccount = { username: 'test.admin', password: 'Test-only-password-932!', displayName: 'משתמש בדיקה' };
|
||||
@@ -0,0 +1,55 @@
|
||||
// Test-only identity provider. Never imported or started by application code.
|
||||
import { createServer } from 'node:http';
|
||||
import { createHash, randomBytes } from 'node:crypto';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { generateKeyPair, exportJWK, SignJWT } from 'jose';
|
||||
|
||||
export async function startMockOidc(port = 0) {
|
||||
const { privateKey, publicKey } = await generateKeyPair('RS256');
|
||||
const jwk = { ...await exportJWK(publicKey), kid: 'test-key', use: 'sig', alg: 'RS256' };
|
||||
const codes = new Map(), refreshTokens = new Set(), accessTokens = new Set();
|
||||
const controls = { badNonce: false, denyRefresh: false, refreshCount: 0, omitName: false, omitRefreshToken: false, subject: 'synology-test-user', groups: ['model-delivery-users'] };
|
||||
let issuer = '';
|
||||
const server = createServer(async (req, res) => {
|
||||
const profile = { sub: controls.subject, username: 'test.sso', ...(controls.omitName ? {} : { name: 'טום בדיקה' }), email: 'test@example.invalid', groups: controls.groups };
|
||||
const json = (data, status = 200) => { res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' }); res.end(JSON.stringify(data)); };
|
||||
const url = new URL(req.url, issuer);
|
||||
try {
|
||||
if (url.pathname === '/.well-known/openid-configuration') return json({ issuer, authorization_endpoint: `${issuer}/authorize`, token_endpoint: `${issuer}/token`, userinfo_endpoint: `${issuer}/userinfo`, jwks_uri: `${issuer}/jwks`, response_types_supported: ['code'], grant_types_supported: ['authorization_code', 'refresh_token'], subject_types_supported: ['public'], id_token_signing_alg_values_supported: ['RS256'], token_endpoint_auth_methods_supported: ['client_secret_post'], scopes_supported: ['openid', 'profile', 'email', 'groups', 'offline_access'], claims_supported: ['sub', 'username', 'name', 'email', 'groups'], code_challenge_methods_supported: ['S256'] });
|
||||
if (url.pathname === '/jwks') return json({ keys: [jwk] });
|
||||
if (url.pathname === '/authorize') {
|
||||
const values = Object.fromEntries(url.searchParams);
|
||||
if (values.client_id !== 'model-test' || values.redirect_uri !== 'http://localhost:4174/api/auth/callback' || values.code_challenge_method !== 'S256' || !values.code_challenge || !values.state || !values.nonce) return json({ error: 'invalid_request' }, 400);
|
||||
const code = randomBytes(24).toString('hex'); codes.set(code, values);
|
||||
const redirect = new URL(values.redirect_uri); redirect.searchParams.set('code', code); redirect.searchParams.set('state', values.state);
|
||||
res.writeHead(302, { location: redirect.href }); return res.end();
|
||||
}
|
||||
if (url.pathname === '/token' && req.method === 'POST') {
|
||||
let raw = ''; for await (const chunk of req) raw += chunk;
|
||||
const fields = new URLSearchParams(raw);
|
||||
if (fields.get('client_id') !== 'model-test' || fields.get('client_secret') !== 'mock-test-secret') return json({ error: 'invalid_client' }, 401);
|
||||
let values;
|
||||
if (fields.get('grant_type') === 'authorization_code') {
|
||||
values = codes.get(fields.get('code')); codes.delete(fields.get('code'));
|
||||
if (!values || values.redirect_uri !== fields.get('redirect_uri') || createHash('sha256').update(fields.get('code_verifier') ?? '').digest('base64url') !== values.code_challenge) return json({ error: 'invalid_grant' }, 400);
|
||||
} else if (fields.get('grant_type') === 'refresh_token') {
|
||||
if (controls.denyRefresh || !refreshTokens.delete(fields.get('refresh_token'))) return json({ error: 'invalid_grant' }, 400);
|
||||
controls.refreshCount++;
|
||||
} else return json({ error: 'unsupported_grant_type' }, 400);
|
||||
const access_token = randomBytes(32).toString('hex'), refresh_token = randomBytes(32).toString('hex');
|
||||
accessTokens.add(access_token); refreshTokens.add(refresh_token);
|
||||
const id_token = values ? await new SignJWT({ ...profile, nonce: controls.badNonce ? 'wrong-nonce' : values.nonce }).setProtectedHeader({ alg: 'RS256', kid: jwk.kid }).setIssuer(issuer).setAudience('model-test').setIssuedAt().setExpirationTime('1h').sign(privateKey) : undefined;
|
||||
return json({ access_token, ...(!controls.omitRefreshToken ? { refresh_token } : {}), id_token, token_type: 'Bearer', expires_in: 3600, scope: 'openid profile email groups offline_access' });
|
||||
}
|
||||
if (url.pathname === '/userinfo' && accessTokens.has(req.headers.authorization?.replace(/^Bearer /, ''))) return json(profile);
|
||||
return json({ error: 'not_found' }, 404);
|
||||
} catch { return json({ error: 'server_error' }, 500); }
|
||||
});
|
||||
await new Promise(resolve => server.listen(port, '127.0.0.1', resolve));
|
||||
issuer = `http://localhost:${server.address().port}`;
|
||||
return { issuer, controls, close: () => new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve())) };
|
||||
}
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
||||
const provider = await startMockOidc(4005);
|
||||
console.log(`Test OIDC provider ready at ${provider.issuer}`);
|
||||
}
|
||||
Reference in new issue
Block a user