feat(auth): ALLOW_SELF_REGISTER toggle + public /auth/config endpoint (0.42.0)
Lets operators run a closed-membership deployment by setting
ALLOW_SELF_REGISTER=false (default true, so existing deploys are
unaffected). When off, POST /auth/register returns 403 forbidden. The
rate-limit token is consumed BEFORE the disabled check so the timing
doesn't distinguish enabled-but-rejected from disabled — closes the
toggle-state probe channel.
New public GET /auth/config returns { self_register_enabled: bool }
so the frontend can render its register affordances correctly
without conflating "disabled" with "rate-limited" (which a probe
attempt would).
Frontend: a lightweight reactive `authConfig` store loads the flag
once on root-layout mount (and again on /register direct navigation,
which bypasses the layout's onMount). Header hides the Register link
when the toggle is off; /register renders a "self-registration is
disabled — ask an administrator" notice instead of the form.
Admin-create endpoint that pairs with this toggle is intentionally
not in this PR — it lands as the next branch (feat/admin-user-create).
The toggle alone is independently useful for deployments that want
to lock down enrollment without yet wiring an admin UI.
This commit is contained in:
37
frontend/src/lib/auth-config.svelte.ts
Normal file
37
frontend/src/lib/auth-config.svelte.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
// Anonymous-relevant auth policy (currently just whether self-
|
||||
// registration is enabled). Loaded once per browser session on root-
|
||||
// layout mount, then read reactively from `authConfig.self_register_enabled`.
|
||||
//
|
||||
// Defaults to `self_register_enabled = true` while loading so the
|
||||
// register link doesn't flash off-and-on for the default-open case.
|
||||
// If the fetch fails (network blip, backend restart), the stale value
|
||||
// is kept — there's no per-request retry. A new tab will retry on its
|
||||
// own mount.
|
||||
//
|
||||
// Same browser-only contract as `session.svelte.ts` — see that file's
|
||||
// SSR comment.
|
||||
|
||||
import { browser } from '$app/environment';
|
||||
import { getAuthConfig } from './api/auth';
|
||||
|
||||
class AuthConfigStore {
|
||||
self_register_enabled = $state(true);
|
||||
loaded = $state(false);
|
||||
private loading = false;
|
||||
|
||||
async load(): Promise<void> {
|
||||
if (this.loaded || this.loading || !browser) return;
|
||||
this.loading = true;
|
||||
try {
|
||||
const cfg = await getAuthConfig();
|
||||
this.self_register_enabled = cfg.self_register_enabled;
|
||||
this.loaded = true;
|
||||
} catch {
|
||||
// Keep optimistic default; next page mount will retry.
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const authConfig = new AuthConfigStore();
|
||||
Reference in New Issue
Block a user