immich-app/immich · warning · BadRequestException
Either password or pinCode is required
Error message
Either password or pinCode is required
What it means
BadRequestException (HTTP 400) thrown at the end of validatePinCode when neither dto.password nor dto.pinCode is truthy. The DTO (PinCodeResetSchema/PinCodeChangeSchema) marks both optional, so the runtime check is the only line of defense. Reached only when the user has a PIN set.
Source
Thrown at server/src/services/auth.service.ts:195
private validatePinCode(
user: { pinCode: string | null; password: string | null },
dto: { pinCode?: string; password?: string },
) {
if (!user.pinCode) {
throw new BadRequestException('User does not have a PIN code');
}
if (dto.password) {
if (!this.validateSecret(dto.password, user.password)) {
throw new BadRequestException('Wrong password');
}
} else if (dto.pinCode) {
if (!this.validateSecret(dto.pinCode, user.pinCode)) {
throw new BadRequestException('Wrong PIN code');
}
} else {
throw new BadRequestException('Either password or pinCode is required');
}
}
async adminSignUp(dto: SignUpDto): Promise<UserAdminResponseDto> {
const admin = await this.createUser({
isAdmin: true,
email: dto.email,
name: dto.name,
password: dto.password,
storageLabel: 'admin',
});
return mapUserAdmin(admin);
}
async authenticate({ headers, queryParams, metadata }: ValidateRequest): Promise<AuthDto> {
const authDto = await this.validate({ headers, queryParams });
const { adminRoute, sharedLinkRoute, uri } = metadata;View on GitHub (pinned to 199723261c)
Solutions
- Ensure the request body contains exactly one of `password` or `pinCode` (non-empty).
- Upgrade the client to send `pinCode` for unlock flows and `password` for reset flows per the API docs.
- Add a client-side check that one credential field is non-empty before POSTing.
- If using a generated SDK, regenerate it from the current open-api spec to pick up schema changes.
Example fix
// before
await api.authApi.resetPinCode({});
// after
if (!body.password && !body.pinCode) {
throw new Error('password or pinCode required');
}
await api.authApi.resetPinCode(body); Defensive patterns
Strategy: validation
Validate before calling
function validatePinResetBody(body: { password?: string; pinCode?: string }) {
if (!body.password && !body.pinCode) {
throw new Error('password or pinCode required');
}
} Type guard
function hasPinCredential(body: { password?: string; pinCode?: string }): body is { password: string } | { pinCode: string } {
return Boolean(body.password) || Boolean(body.pinCode);
} Try / catch
try {
await api.authApi.resetPinCode(body);
} catch (e) {
if (e.response?.data?.message === 'Either password or pinCode is required') {
showCredentialRequiredError();
} else throw e;
} Prevention
- Enforce non-empty password XOR pinCode before submitting.
- Disable the submit button until a credential is entered.
- Regenerate the SDK after schema changes so optional vs. required fields are clear.
When it happens
Trigger: PUT /auth/pin-code, DELETE /auth/pin-code, or POST /auth/session/unlock with body that omits both password and pinCode, or sends empty strings. Because PinCodeResetSchema has both as optional, the request passes DTO validation and fails here.
Common situations: Client bug submitting an empty form; serialization issue that drops falsy fields; client assumes the session is already elevated and sends no credentials; version mismatch where the client predates the password-or-pinCode requirement.
Related errors
- User already has a PIN code
- User does not have a PIN code
- Wrong PIN code
- Forbidden
- Missing required permission: ${requestedPermission}
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/ecac01afb48e1265.
Report an issue: GitHub.