HeyPuter/puter · error · HttpError

unauthorized

unauthorized

Error message

Incorrect password.

What it means

Returned by POST /login when user.password === null — the account exists and is not suspended, but has no password set (legacy code 'unauthorized'). This typically means the account was created through an OAuth/passwordless flow and never had a local password, so a credential login is impossible. It is enforced before bcrypt.compare to avoid passing null to bcrypt.

Source

Thrown at src/backend/controllers/auth/AuthController.ts:422

        }
        if (
            user.username === 'system' &&
            !(this.config as { allow_system_login?: boolean })
                .allow_system_login
        ) {
            throw new HttpError(
                404,
                username ? 'Username not found.' : 'Email not found.',
                { legacyCode: 'not_found' },
            );
        }
        if (user.suspended) {
            throw new HttpError(401, 'This account is suspended.', {
                legacyCode: 'account_suspended',
            });
        }
        if (user.password === null) {
            throw new HttpError(401, 'Incorrect password.', {
                legacyCode: 'unauthorized',
            });
        }

        // Verify password
        const passwordMatch = await bcrypt.compare(
            password,
            user.password as string,
        );
        if (!passwordMatch) {
            throw new HttpError(401, 'Incorrect password.', {
                legacyCode: 'password_mismatch',
            });
        }

        const reauthAuthId = this.#extractAuthIdFromReauthToken(
            req.body.reauth_token,
        );

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Use the same OAuth/passwordless flow the account was created with (e.g. puter.auth.signIn()).
  2. Set a password on the account first via the password-reset or profile flow, then retry credential login.
  3. If migrating accounts, backfill passwords before exposing credential login.

Example fix

// before: account has no password set
await fetch('/login', { method:'POST', body:JSON.stringify({ username, password }) }); // -> 401 unauthorized

// after: use the OAuth flow the account was created with
const token = await puter.auth.signIn();
Defensive patterns

Strategy: try-catch

Try / catch

try { await login({ username, password }); }
catch (e) {
  if (e.code === 'unauthorized' && /password/i.test(e.message)) {
    /* account has no password — route to OAuth/passwordless sign-in */
  } else throw e;
}

Prevention

When it happens

Trigger: An account created via SSO/OAuth trying to log in by password; a migrated account whose password was never set; an account that had its password cleared.

Common situations: User signed up with Google/GitHub and now tries password login; admin created an account without setting a password; password-reset flow left password null.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/e3ebfb87054bd272. Report an issue: GitHub.