feat(auth): first account on a new server needs the setup token from the server log (M462 #4982)
test-go / test (push) Successful in 2m3s
test-web / test (push) Successful in 1m14s
test-go / integration (push) Successful in 4m38s
release / Build signed APK (releases and dev) (push) Successful in 5m36s
release / Build + push container image (push) Successful in 1m24s
release / Verify release artifacts (tag releases only) (push) Skipped

While no accounts exist, the server mints a random setup token at boot and
logs it. Registering the first account (which becomes admin) must carry it,
so whoever reaches a freshly exposed instance first cannot claim it. The
register page asks GET /api/auth/setup-status and shows a "Setup token"
field in place of the invite field while setup is pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 09:35:52 -04:00
co-authored by Claude Opus 5.5
parent 46194a609d
commit 2f3fbccab6
7 changed files with 276 additions and 16 deletions
+10
View File
@@ -47,10 +47,19 @@ export async function login(username: string, password: string): Promise<void> {
void sendTimezoneIfStale();
}
/**
* Whether the server has no accounts yet, in which case the first
* registration must carry the setup token printed in the server log.
*/
export async function getSetupStatus(): Promise<{ setup_required: boolean }> {
return api.get<{ setup_required: boolean }>('/api/auth/setup-status');
}
export async function register(opts: {
username: string;
password: string;
inviteToken?: string;
setupToken?: string;
displayName?: string;
}): Promise<void> {
const body: Record<string, string> = {
@@ -58,6 +67,7 @@ export async function register(opts: {
password: opts.password,
};
if (opts.inviteToken) body.invite_token = opts.inviteToken;
if (opts.setupToken) body.setup_token = opts.setupToken;
if (opts.displayName) body.display_name = opts.displayName;
const res = await api.post<LoginResponse>('/api/auth/register', body);
setUser(res.user);