mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 16:16:59 +00:00
Config, policy, branding, OAuth setup, password and setup wizard writes used to answer 500 when the shared store couldn't be reached, and the wizard's token check failed outside any handler. They answer 503 with Retry-After now, through the helper that already turned a read-only refusal into 409. The step-session-secret test's wizard mock gains wizardSessionRefusal, which the setup routes now call.
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { detectSetupState } from '@/lib/setup/state';
|
|
import { authenticateWizardRequest, setupStoreUnavailableResponse } from '@/lib/setup/session';
|
|
import { isStoreUnavailableError } from '@/lib/shared-store';
|
|
import { configManager } from '@/lib/admin/config-manager';
|
|
import { isConfigReadOnly } from '@/lib/admin/paths';
|
|
import { SENSITIVE_CONFIG_KEYS } from '@/lib/admin/types';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
/**
|
|
* GET /api/setup/status - public endpoint that returns the wizard state
|
|
* and (if authenticated) the partial config saved by previous steps. The
|
|
* wizard polls this on load so a refresh resumes with prior values.
|
|
*
|
|
* Sensitive values (OAuth client secret, session secret) are NEVER sent
|
|
* back to the client - only a `<key>HasValue` boolean. Re-entering them
|
|
* after refresh is the price of not exposing them.
|
|
*/
|
|
export async function GET() {
|
|
await configManager.ensureLoaded();
|
|
const state = detectSetupState();
|
|
let authenticated = false;
|
|
try {
|
|
authenticated = state === 'bootstrap' ? await authenticateWizardRequest() : false;
|
|
} catch (error) {
|
|
if (isStoreUnavailableError(error)) return setupStoreUnavailableResponse();
|
|
throw error;
|
|
}
|
|
|
|
let partialConfig: Record<string, unknown> | null = null;
|
|
if (state === 'bootstrap' && authenticated) {
|
|
// Only echo back values the operator has actually saved during the
|
|
// wizard (admin overrides). System defaults must not flow back here,
|
|
// because the wizard has its own opinionated defaults (e.g. settings
|
|
// sync on by default) that we'd otherwise stomp.
|
|
const sources = configManager.getAllWithSources();
|
|
const safe: Record<string, unknown> = {};
|
|
for (const [key, info] of Object.entries(sources)) {
|
|
if (info.source !== 'admin') continue;
|
|
if (SENSITIVE_CONFIG_KEYS.has(key)) {
|
|
safe[`${key}HasValue`] = typeof info.value === 'string' && info.value.length > 0;
|
|
} else {
|
|
safe[key] = info.value;
|
|
}
|
|
}
|
|
partialConfig = safe;
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{
|
|
state,
|
|
authenticated,
|
|
readOnly: isConfigReadOnly(),
|
|
partialConfig,
|
|
},
|
|
{ headers: { 'Cache-Control': 'no-store' } },
|
|
);
|
|
}
|