{"record":{"id":"763e5cdd88ede18f","repo":"unslothai/unsloth","slug":"password-change-required","errorCode":null,"errorMessage":"Password change required","messagePattern":"Password change required","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"studio/backend/auth/authentication.py","lineNumber":299,"sourceCode":"\n    record = get_user_and_secret(subject)\n    if record is None:\n        raise HTTPException(\n            status_code = status.HTTP_401_UNAUTHORIZED,\n            detail = \"Invalid or expired token\",\n        )\n\n    _salt, _pwd_hash, jwt_secret, must_change_password = record\n    try:\n        payload = jwt.decode(token, jwt_secret, algorithms = [ALGORITHM])\n        if payload.get(\"sub\") != subject:\n            raise HTTPException(\n                status_code = status.HTTP_401_UNAUTHORIZED,\n                detail = \"Invalid token payload\",\n            )\n        is_desktop = payload.get(\"desktop\") is True\n        if must_change_password and not allow_password_change and not is_desktop:\n            raise HTTPException(\n                status_code = status.HTTP_403_FORBIDDEN,\n                detail = \"Password change required\",\n            )\n        return subject, credential_generation(jwt_secret)\n    except jwt.InvalidTokenError:\n        raise HTTPException(\n            status_code = status.HTTP_401_UNAUTHORIZED,\n            detail = \"Invalid or expired token\",\n        )\n","sourceCodeStart":281,"sourceCodeEnd":309,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/auth/authentication.py#L281-L309","documentation":"HTTP 403 raised when the authenticated user's record has must_change_password set, the request is not on an allowed password-change route, and the token is not a desktop token. It enforces a password-reset gate: until the user sets a new password, all other API access is refused.","triggerScenarios":"An admin reset the user's password (setting the must-change flag) and the user keeps calling normal endpoints with the old still-valid JWT; first-login password policies; the user skips the forced change-password screen in a custom client.","commonSituations":"Password-expiry policies; security incidents forcing credential rotation; custom API clients that never implemented the change-password step and only call regular endpoints.","solutions":["Complete the password change via the change-password endpoint/UI; the flag clears and normal access resumes.","Custom clients should detect 403 with detail 'Password change required' and route the user to the change-password flow.","Admins: confirm the flag is intended (it is usually set by an explicit reset action)."],"exampleFix":"# before\nresp = requests.get(f\"{base}/api/things\", headers=auth)\n\n# after\nresp = requests.get(f\"{base}/api/things\", headers=auth)\nif resp.status_code == 403 and 'Password change required' in resp.text:\n    requests.post(f\"{base}/auth/change-password\", json={\"old\": old, \"new\": new}, headers=auth)","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    client.get('/api/data')\nexcept HTTPStatusError as e:\n    if e.response.status_code == 403 and 'Password change required' in e.response.text:\n        prompt_password_change()  # complete the flow, then retry","preventionTips":["Implement the change-password flow in custom clients, not just the official UI.","Treat this 403 distinctly from permission denials: it is a workflow gate, not an ACL.","After admin-forced resets, drive users straight to the change-password screen."],"tags":["authorization","password","http-403","studio"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}