hcengineering/platform · error · PlatformError
BadRequest
BadRequest
Error message
platform.status.BadRequest
What it means
Thrown by the password-setup (requestPasswordSetup) operation when the target account already has a password hash and salt. The setup flow is only for accounts that never had a password (e.g. signup-via-email accounts), because it bypasses the old-password check in changePassword.
Source
Thrown at server/account/src/operations.ts:1581
* email+password as a secondary sign-in method.
*
* Requires authentication (session token). Only valid for accounts that have
* no password set — accounts with an existing password must use changePassword.
*/
export async function requestPasswordSetup (
ctx: MeasureContext,
db: AccountDB,
branding: Branding | null,
token: string
): Promise<void> {
const { account: accountUuid } = decodeTokenVerbose(ctx, token)
// Guard: reject if the account already has a password. The setup flow
// bypasses the old-password requirement in changePassword, so it must only
// be accessible to accounts that have no password yet.
const existingAccount = await getAccount(db, accountUuid)
if (existingAccount?.hash != null && existingAccount?.salt != null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))
}
ctx.info('Requesting password setup', { accountUuid })
const emailSocialId = await db.socialId.findOne({
type: SocialIdType.EMAIL,
personUuid: accountUuid
})
if (emailSocialId == null) {
ctx.error('Email social id not found for account', { accountUuid })
throw new PlatformError(
new Status(Severity.ERROR, platform.status.SocialIdNotFound, { value: '', type: SocialIdType.EMAIL })
)
}
const { mailURL, mailAuth } = getMailUrl()
const front = getFrontUrl(branding)View on GitHub (pinned to 63e28dc964)
Solutions
- Route the user to the normal password-reset (restore) flow instead of password setup.
- Use changePassword with the existing old password when the account already has credentials.
- Invalidate stale setup links/tokens in emails.
- Check account.hash/salt client-side if account info is available before requesting setup.
Example fix
// before: always call setup flow
await accountClient.requestPasswordSetup(accountUuid)
// after: only when no password set yet
if (existingAccount?.hash == null && existingAccount?.salt == null) {
await accountClient.requestPasswordSetup(accountUuid)
} else {
await accountClient.requestRestore(ctx, email)
} Defensive patterns
Strategy: validation
Validate before calling
const acct = await getAccount(db, accountUuid)
const needsSetup = acct?.hash == null && acct?.salt == null
if (!needsSetup) throw new Error('Account already has a password; use restore flow') Type guard
function hasNoPassword(a: { hash?: string | null; salt?: string | null }): boolean { return a.hash == null && a.salt == null } Try / catch
try {
await accountClient.requestPasswordSetup(ctx, branding, accountUuid)
} catch (err) {
if (err instanceof PlatformError && err.status.code === platform.status.BadRequest) {
await accountClient.requestRestore(ctx, email) // fall back to reset flow
} else throw err
} Prevention
- Expire setup links after first use.
- Detect password-exists state in the UI and hide the setup link.
- Never reuse setup endpoints for password resets.
When it happens
Trigger: Calling the password-setup request endpoint for an accountUuid whose existingAccount.hash != null && salt != null.
Common situations: User already completed setup and clicks the setup link again; stale or reused setup links; client mistakenly routing a password-reset through the setup endpoint; duplicated signup.
Related errors
- account.status.BadRequest
- account.status.BadRequest
- Failed to load server config
- getDisplayMedia not supported
- No screen access granted
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/2a6a80763c647a8f.
Report an issue: GitHub.