__tests__/sso-only.test.js
Rama main · réplica de lectura del repositorio original.
// SSO-ONLY mode. A platform-managed Ghost Browser (connected from the Tools tab) belongs to ONE
// platform user and must be entered ONLY through the platform SSO handoff. Local password accounts are
// disabled — otherwise anyone reaching the tool's URL before the owner's first sign-in could claim
// ownership via the "create the owner account" form. The full route stack pulls in Playwright etc. and
// isn't bootable headless, so these pin the load-bearing lines directly.
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
const server = readFileSync(fileURLToPath(new URL('../src/server.js', import.meta.url)), 'utf8');
const consoleJs = readFileSync(fileURLToPath(new URL('../public/js/console.js', import.meta.url)), 'utf8');
describe('SSO-only server gate', () => {
it('is enabled by SSO_ONLY but only when LEADFLOW is configured (never locks out with no SSO path)', () => {
expect(server).toMatch(/const SSO_ONLY = .*process\.env\.SSO_ONLY.*&&\s*!!process\.env\.LEADFLOW_JWT_SECRET/s);
});
it('blocks local signup when SSO-only, and login only until the owner has set a password', () => {
/* Signup stays shut: nobody may claim an instance before its owner's first hand-off. Login
is the fallback that never depends on the platform - it opens the moment a password exists. */
const signup = server.slice(server.indexOf("app.post('/api/auth/signup'"), server.indexOf("app.post('/api/auth/login'"));
const login = server.slice(server.indexOf("app.post('/api/auth/login'"), server.indexOf('ONE LOGIN'));
expect(signup).toMatch(/if \(SSO_ONLY\) return ssoOnlyBlock/);
expect(login).toMatch(/if \(SSO_ONLY && !accounts\.hasPassword\(\)\)/);
expect(login).not.toMatch(/if \(SSO_ONLY\) return ssoOnlyBlock/);
});
it('lets the signed-in owner set the password that opens the instance without the platform', () => {
expect(server).toMatch(/app\.post\('\/api\/auth\/password', authed/);
const state = server.slice(server.indexOf("app.get('/api/auth/state'"), server.indexOf("app.post('/api/auth/signup'"));
expect(state).toMatch(/hasPassword: accounts\.hasPassword\(\)/);
});
it('does NOT block the SSO route (that is the only way in)', () => {
const ssoRoute = server.slice(server.indexOf("app.post('/api/auth/sso'"), server.indexOf("app.post('/api/auth/logout'"));
expect(ssoRoute).not.toMatch(/SSO_ONLY/);
});
it('reports ssoOnly in /api/auth/state so the frontend can hide the password gate', () => {
const state = server.slice(server.indexOf("app.get('/api/auth/state'"), server.indexOf("app.post('/api/auth/signup'"));
expect(state).toMatch(/ssoOnly: SSO_ONLY/);
});
it('frontend KEEPS the email/password form when ssoOnly, and says what to do', () => {
/* A hand-off that failed used to leave a page with nothing to press. */
expect(consoleJs).toMatch(/if \(s\.ssoOnly\)/);
expect(consoleJs).toMatch(/password you set here/i);
expect(consoleJs).not.toMatch(/\['u', 'p', 'gateGo'/);
expect(consoleJs).toMatch(/\/api\/auth\/password/);
});
});