SHA256
Reproduce browser-success/native-401 with unmodified upstream SESSION_SECURE=false behind HTTPS. Keep cookies in the existing HTTPS-only origin-bound encrypted jar regardless of the server Secure attribute; preserve TLS, redirect, expiry and origin protections. Distinguish credential/2FA rejection from missing/rejected session handoff and clear stale login errors on retry. Verify 13 JVM tests plus the 7-test real-server suite in both Secure/default-cookie modes, including bad-password then successful native login. Bump Android preview to 0.2.1/code 3; extend CI and document the defect without claiming access to the user's production server.
57 lines
2.8 KiB
JavaScript
57 lines
2.8 KiB
JavaScript
// Test-only reverse proxy around the unmodified pinned upstream application.
|
|
import fs from 'node:fs';
|
|
import http from 'node:http';
|
|
import https from 'node:https';
|
|
import path from 'node:path';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { randomBytes } from 'node:crypto';
|
|
import { once } from 'node:events';
|
|
import { startIdentityProvider } from './oidc-fixture.mjs';
|
|
|
|
const [upstream, directory] = process.argv.slice(2);
|
|
process.env.PORT = '0';
|
|
process.env.BIND_ADDRESS = '127.0.0.1';
|
|
process.env.DB_PATH = path.join(directory, 'fixture.db');
|
|
process.env.SESSION_SECRET = randomBytes(48).toString('hex');
|
|
// Exercise both explicit Secure cookies and upstream's default behind HTTPS.
|
|
const sessionSecure = process.env.YUVOMI_TEST_SESSION_SECURE ?? 'true';
|
|
if (!['true', 'false'].includes(sessionSecure)) throw new Error('Invalid fixture cookie mode');
|
|
process.env.SESSION_SECURE = sessionSecure;
|
|
process.env.DB_ENCRYPTION_KEY = '';
|
|
process.env.NODE_ENV = 'test';
|
|
process.env.LOG_LEVEL = 'warn';
|
|
const tls = { key: fs.readFileSync(path.join(directory, 'key.pem')), cert: fs.readFileSync(path.join(directory, 'cert.pem')) };
|
|
const idp = await startIdentityProvider(tls);
|
|
process.env.OIDC_ISSUER = idp.issuer;
|
|
process.env.OIDC_CLIENT_ID = idp.clientId;
|
|
process.env.OIDC_CLIENT_SECRET = idp.secret;
|
|
const { server } = await import(pathToFileURL(path.join(upstream, 'server/index.js')));
|
|
if (!server.listening) await once(server, 'listening');
|
|
let networkAvailable = true;
|
|
const proxy = https.createServer(tls, (request, response) => {
|
|
// Fault injection lives only in this loopback fixture, never in upstream/app code.
|
|
if (request.method === 'POST' && ['/__mobile_test/network/off', '/__mobile_test/network/on'].includes(request.url)) {
|
|
networkAvailable = request.url.endsWith('/on');
|
|
response.writeHead(200, { 'Content-Type': 'application/json' });
|
|
response.end('{}');
|
|
return;
|
|
}
|
|
if (!networkAvailable) { request.socket.destroy(); return; }
|
|
const backend = http.request({
|
|
hostname: '127.0.0.1', port: server.address().port,
|
|
method: request.method, path: request.url,
|
|
headers: { ...request.headers, 'x-forwarded-proto': 'https', 'x-forwarded-for': '127.0.0.1' },
|
|
}, incoming => {
|
|
response.writeHead(incoming.statusCode, incoming.headers);
|
|
incoming.pipe(response);
|
|
});
|
|
backend.on('error', () => { response.writeHead(502); response.end(); });
|
|
request.pipe(backend);
|
|
});
|
|
proxy.listen(0, '127.0.0.1');
|
|
await once(proxy, 'listening');
|
|
process.env.OIDC_REDIRECT_URI = `https://127.0.0.1:${proxy.address().port}/api/v1/auth/oidc/callback`;
|
|
idp.setRedirect(process.env.OIDC_REDIRECT_URI);
|
|
fs.writeFileSync(path.join(directory, 'ready.json'), JSON.stringify({ port: proxy.address().port, idpPort: idp.port }));
|
|
process.on('SIGTERM', () => { idp.close(); proxy.close(); server.close(); process.exit(0); });
|