toeverything/AFFiNE · error · InvalidAuthState
invalid_auth_state
invalid_auth_state
Error message
Invalid auth state. You might start the auth progress from another device.
What it means
Thrown by `AuthChallengeStore.create` when `isValidCacheTtl(ttlMs)` is false — i.e. `ttlMs` is not a positive safe integer. The challenge (oauth state, open-app sign-in code, captcha, passkey) cannot be stored with an invalid expiry, so creation aborts. HTTP 400.
Source
Thrown at packages/backend/server/src/core/auth/challenge-store.ts:26
export type AuthChallengePurpose =
| 'oauth_state'
| 'open_app_sign_in'
| 'auth_session_exchange'
| 'captcha'
| 'passkey_registration'
| 'passkey_authentication';
@Injectable()
export class AuthChallengeStore {
constructor(private readonly cache: SessionCache) {}
async create<T>(
purpose: AuthChallengePurpose,
payload: T | ((token: string) => T),
ttlMs: number
): Promise<string> {
if (!isValidCacheTtl(ttlMs)) {
throw new InvalidAuthState();
}
const token = randomUUID();
const value =
typeof payload === 'function'
? (payload as (token: string) => T)(token)
: payload;
const stored = await this.cache.set(this.key(purpose, token), value, {
ttl: ttlMs,
});
if (!stored) {
throw new InvalidAuthState();
}
return token;
}
async get<T>(purpose: AuthChallengePurpose, token: string) {
return (await this.cache.get<T>(this.key(purpose, token))) ?? null;View on GitHub (pinned to 26c515e050)
Solutions
- Pass a positive integer milliseconds value, e.g. `60 * 1000`.
- Validate the TTL at the call site before calling `create`: `Number.isSafeInteger(ttl) && ttl > 0`.
- Audit the config source feeding the TTL (env var parsing) to ensure it produces a number.
Example fix
// before
await challenges.create('captcha', payload, cfg.captchaTtl /* string '120000' */);
// after
const ttl = Number(cfg.captchaTtl);
if (!Number.isSafeInteger(ttl) || ttl <= 0) throw new Error('bad ttl');
await challenges.create('captcha', payload, ttl); Defensive patterns
Strategy: validation
Validate before calling
function validTtl(ttl: unknown): ttl is number {
return Number.isSafeInteger(ttl) && (ttl as number) > 0;
}
if (!validTtl(ttlMs)) throw new Error(`ttl must be a positive integer, got ${ttlMs}`);
await challenges.create('captcha', payload, ttlMs); Type guard
function isValidCacheTtl(ttl: unknown): ttl is number {
return typeof ttl === 'number' && Number.isSafeInteger(ttl) && ttl > 0;
} Prevention
- Always validate TTL is a positive safe integer before passing to `create`.
- Parse config-derived TTLs with `Number(...)` and validate, never pass raw env strings.
- Unit-test challenge creation with edge-case TTLs (0, negative, fractional, NaN).
When it happens
Trigger: A caller passes `ttlMs` as 0, a negative number, a fractional value, `undefined`, `Infinity`, or a non-number (e.g. a string TTL read from config). Any new code path that constructs a challenge without validating the TTL will hit this.
Common situations: Config typo where a TTL is expressed as a string (`'60000'`) instead of a number, a refactor that drops the TTL argument, or a feature flag that yields `NaN`.
Related errors
- Failed to read image size
- ErrorCode.DefaultRuntimeError
- Invalid key for: ${key}
- SchemaValidateError
- bad_request
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/b7132eb2765908c6.
Report an issue: GitHub.