feat(12-06): expose non-secret DB name via GET /api/setup/status (gap 3)
- status returns { setupComplete, dbName } from process.env.DB_NAME (null fallback)
- only the DB name; never DB_HOST/DB_USER/DB_PASSWORD
- SetupStatusResponse carries dbName?: string | null for the PWA read-only field
This commit is contained in:
@@ -88,7 +88,10 @@ setupRouter.get('/status', async (c) => {
|
|||||||
// completion (D-10). Using it here keeps the status response in sync with the guard
|
// completion (D-10). Using it here keeps the status response in sync with the guard
|
||||||
// without a second DB read pattern (covers setup_complete AND effective-config).
|
// without a second DB read pattern (covers setup_complete AND effective-config).
|
||||||
const locked = await isSetupLocked();
|
const locked = await isSetupLocked();
|
||||||
return c.json({ setupComplete: locked });
|
// Gap 3 (backend): surface the NON-SECRET DB name so the PWA can render a read-only
|
||||||
|
// field giving the "database connection verified" row an on-screen referent. Only the
|
||||||
|
// database NAME is exposed — never DB_HOST/DB_USER/DB_PASSWORD (connection secrets/topology).
|
||||||
|
return c.json({ setupComplete: locked, dbName: process.env.DB_NAME ?? null });
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -251,6 +251,23 @@ describe('GET /api/setup/status', () => {
|
|||||||
expect(body.setupComplete).toBe(false);
|
expect(body.setupComplete).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Gap 3 (backend): status exposes the NON-SECRET DB name as an on-screen referent
|
||||||
|
// for the "database connection verified" row. No DB_HOST/DB_USER/DB_PASSWORD.
|
||||||
|
it('returns the non-secret dbName from process.env.DB_NAME (gap 3)', async () => {
|
||||||
|
process.env.DB_NAME = 'familysync_test';
|
||||||
|
const app = await getApp();
|
||||||
|
const res = await app.fetch(jsonRequest('GET', '/api/setup/status'));
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const bodyText = await res.text();
|
||||||
|
const body = JSON.parse(bodyText) as { setupComplete: boolean; dbName?: string | null };
|
||||||
|
expect(body.dbName).toBe('familysync_test');
|
||||||
|
// No connection secrets/topology may leak into the status response.
|
||||||
|
const password = process.env.DB_PASSWORD;
|
||||||
|
if (password) {
|
||||||
|
expect(bodyText).not.toContain(password);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('returns { setupComplete: true } after app_config.setup_complete is set', async () => {
|
it('returns { setupComplete: true } after app_config.setup_complete is set', async () => {
|
||||||
// Directly set setup_complete in DB (simulates completed setup)
|
// Directly set setup_complete in DB (simulates completed setup)
|
||||||
await db
|
await db
|
||||||
|
|||||||
@@ -537,6 +537,12 @@ export async function saveMyCredential(payload: SaveMyCredentialPayload): Promis
|
|||||||
*/
|
*/
|
||||||
export interface SetupStatusResponse {
|
export interface SetupStatusResponse {
|
||||||
setupComplete: boolean;
|
setupComplete: boolean;
|
||||||
|
/**
|
||||||
|
* Non-secret database name (process.env.DB_NAME) surfaced for the wizard's
|
||||||
|
* read-only "database connection verified" referent (gap 3). Never includes
|
||||||
|
* DB_HOST/DB_USER/DB_PASSWORD.
|
||||||
|
*/
|
||||||
|
dbName?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user