toeverything/AFFiNE · error · PasswordRequired
password_required
password_required
Error message
Password is required.
What it means
Thrown by CustomSetupController.createAdmin when input.password is falsy during first-admin creation. It runs after the server-initialized check and before password-complexity validation, so an empty password never reaches assertValidPassword.
Source
Thrown at packages/backend/server/src/core/selfhost/controller.ts:48
private readonly mutex: Mutex,
private readonly server: ServerService
) {}
@Public()
@Post('/create-admin-user')
async createAdmin(
@Req() req: Request,
@Res() res: Response,
@Body() input: CreateUserInput
) {
if (await this.server.initialized()) {
throw new ActionForbidden('First user already created');
}
validators.assertValidEmail(input.email);
if (!input.password) {
throw new PasswordRequired();
}
validators.assertValidPassword(
input.password,
this.config.auth.passwordRequirements
);
await using lock = await this.mutex.acquire('createFirstAdmin');
if (!lock) {
throw new InternalServerError();
}
const user = await this.models.user.create({
name: input.name || undefined,
email: input.email,
password: input.password,
registered: true,
});View on GitHub (pinned to 26c515e050)
Solutions
- Ensure the request body includes a non-empty 'password' field matching CreateUserInput.
- Add client-side required-field validation before submitting the setup form.
- Confirm the Content-Type is application/json so the body parses correctly into input.password.
Example fix
// before
const body = { email };
await fetch('/api/setup/create-admin-user', { method: 'POST', body: JSON.stringify(body) });
// after
if (!password) throw new Error('password required');
const body = { email, password };
await fetch('/api/setup/create-admin-user', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
}); Defensive patterns
Strategy: validation
Validate before calling
if (!input.password || typeof input.password !== 'string') {
return res.status(400).json({ code: 'password_required' });
} Type guard
function hasPassword(body: unknown): body is { password: string } {
return typeof (body as any)?.password === 'string' && (body as any).password.length > 0;
} Try / catch
try {
await createAdmin({ email, password });
} catch (e) {
if (e?.code === 'password_required') {
// mark the password field as invalid in the setup form
return setFieldError('password', 'Password is required');
}
throw e;
} Prevention
- Mark the password input as required and disable submit until non-empty.
- Send Content-Type: application/json so the body parses into the DTO.
- Cross-check field names against CreateUserInput in integration tests.
When it happens
Trigger: POST /api/setup/create-admin-user with password omitted, empty string, or null in the JSON body; form submission where the password field was left blank.
Common situations: Setup form submitted before the password field was filled; client-side validation disabled; curl/script that omitted the password key; JSON payload typo (e.g. 'passwd' instead of 'password').
Related errors
- action_forbidden
- internal_server_error
- A config file path is required
- invalid_app_config_input
- A migration name is required
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/5cee75a5e36e807d.
Report an issue: GitHub.