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

  1. Ensure the request body includes a non-empty 'password' field matching CreateUserInput.
  2. Add client-side required-field validation before submitting the setup form.
  3. 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

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


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/5cee75a5e36e807d. Report an issue: GitHub.