# Password

Password set, reset, and change operations

## Set password for guest users

> Sets password for guest users (users with \`user\_type=guest\`, typically\
> created during checkout without registration).\
> \
> \*\*Access Control:\*\*\
> \- Anonymous users: Returns \`redirect\_url\` to home page\
> \- Registered users (already have password): Returns \`redirect\_url\` to password change page\
> \- Guest users: Can set password and become registered users\
> \
> \*\*User Type Transition:\*\*\
> Upon successful password set, user's \`user\_type\` is changed from \`guest\` to \`registered\`.\
> \
> \*\*Password Validation:\*\*\
> Password strength validated according to \`AUTH\_PASSWORD\_VALIDATORS\` setting\
> (Dynamic Configuration).\
> \
> \*\*Response Behavior:\*\*\
> \- Non-AJAX requests: HTTP 302 redirect to \`location\` URL\
> \- AJAX requests (X-Requested-With: XMLHttpRequest): JSON with \`location\` field

```json
{"openapi":"3.1.0","info":{"title":"Users API - Authentication, Password, Orders, and Hooks","version":"1.0.0"},"tags":[{"name":"Password","description":"Password set, reset, and change operations"}],"servers":[{"description":"Default commerce site","url":"https://{commerce_url}","variables":{"commerce_url":{"default":"sandbox.akinon.com","description":"Commerce storefront hostname"}}}],"security":[{"SessionCookie":[]}],"components":{"securitySchemes":{"SessionCookie":{"type":"apiKey","in":"cookie","name":"sessionid","description":"Session cookie for authentication"}},"parameters":{"SessionCookieHeader":{"name":"Cookie","in":"header","required":false,"schema":{"type":"string"},"description":"Session cookie in format `sessionid=<value>`.\nMay use site-specific cookie name (e.g., `osessionid`)."},"CSRFTokenHeader":{"name":"X-CSRFToken","in":"header","required":true,"schema":{"type":"string"},"description":"CSRF token for state-changing requests"}},"schemas":{"PasswordSetRequest":{"type":"object","required":["password1","password2"],"properties":{"password1":{"type":"string","format":"password","description":"New password"},"password2":{"type":"string","format":"password","description":"Confirm new password (must match password1).\nValidated against `AUTH_PASSWORD_VALIDATORS` setting."}}}}},"paths":{"/users/password/set/":{"post":{"tags":["Password"],"operationId":"setPassword","summary":"Set password for guest users","description":"Sets password for guest users (users with `user_type=guest`, typically\ncreated during checkout without registration).\n\n**Access Control:**\n- Anonymous users: Returns `redirect_url` to home page\n- Registered users (already have password): Returns `redirect_url` to password change page\n- Guest users: Can set password and become registered users\n\n**User Type Transition:**\nUpon successful password set, user's `user_type` is changed from `guest` to `registered`.\n\n**Password Validation:**\nPassword strength validated according to `AUTH_PASSWORD_VALIDATORS` setting\n(Dynamic Configuration).\n\n**Response Behavior:**\n- Non-AJAX requests: HTTP 302 redirect to `location` URL\n- AJAX requests (X-Requested-With: XMLHttpRequest): JSON with `location` field","parameters":[{"$ref":"#/components/parameters/SessionCookieHeader"},{"$ref":"#/components/parameters/CSRFTokenHeader"},{"name":"X-Requested-With","in":"header","required":false,"schema":{"type":"string","enum":["XMLHttpRequest"]},"description":"Set to 'XMLHttpRequest' for AJAX requests to receive JSON response"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordSetRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/PasswordSetRequest"}}}},"responses":{"200":{"description":"Password set successfully (AJAX request).\nReturns location URL for redirect.","content":{"application/json":{"schema":{"type":"object","properties":{"location":{"type":"string","description":"URL to redirect (typically same endpoint)"}}}}}},"302":{"description":"Password set successfully (non-AJAX request), redirects to location"},"400":{"description":"Validation errors (AJAX request):\n- Password mismatch (password1 != password2)\n- Weak password (fails AUTH_PASSWORD_VALIDATORS)","content":{"application/json":{"schema":{"type":"object","properties":{"form_errors":{"type":"object","description":"Field-level validation errors","additionalProperties":{"type":"array","items":{"type":"string"}}}}}}}}}}}}}
```

## Set password with SMS OTP verification (guest users)

> Sets password for authenticated guest users with phone number verification\
> via SMS OTP. This is a two-step process that transitions guest users to\
> registered users.\
> \
> \*\*Step 1 - Request OTP:\*\*\
> Send \`phone\`, \`password1\`, \`password2\` without \`code\`.\
> System validates fields, sends SMS verification code, and stores\
> confirmation data in session. Returns 202 status.\
> \
> \*\*Step 2 - Verify and Set Password:\*\*\
> Send all fields including \`code\`. System verifies the code against\
> session confirmation data. If valid:\
> \- Password is set and validated against \`AUTH\_PASSWORD\_VALIDATORS\`\
> \- User type changes from \`guest\` to \`registered\`\
> \- Phone number is saved to user profile\
> \- \`verified\_phone\` attribute is set to \`true\`\
> \- Old password is saved to user profile history\
> Returns 200 with success message.\
> \
> \*\*Resend OTP:\*\*\
> Use \`resend: true\` to request a new code. Subject to SMS resend interval\
> restrictions (configured via \`SMS\_OTP\_RESENT\_TIME\_GAP\` system setting).\
> Returns 202 status.\
> \
> \*\*Phone Validation:\*\*\
> Phone must be unique among active users (uses \`UNIQUE\_VALIDATOR\_PHONE\_MESSAGE\`\
> dynamic setting for error message).\
> \
> \*\*Password Validation:\*\*\
> Password strength validated according to \`AUTH\_PASSWORD\_VALIDATORS\` setting\
> (Dynamic Configuration).\
> \
> Throttling scope: \`register\`.

```json
{"openapi":"3.1.0","info":{"title":"Users API - Authentication, Password, Orders, and Hooks","version":"1.0.0"},"tags":[{"name":"Password","description":"Password set, reset, and change operations"}],"servers":[{"description":"Default commerce site","url":"https://{commerce_url}","variables":{"commerce_url":{"default":"sandbox.akinon.com","description":"Commerce storefront hostname"}}}],"security":[{"SessionCookie":[]}],"components":{"securitySchemes":{"SessionCookie":{"type":"apiKey","in":"cookie","name":"sessionid","description":"Session cookie for authentication"}},"parameters":{"SessionCookieHeader":{"name":"Cookie","in":"header","required":false,"schema":{"type":"string"},"description":"Session cookie in format `sessionid=<value>`.\nMay use site-specific cookie name (e.g., `osessionid`)."},"CSRFTokenHeader":{"name":"X-CSRFToken","in":"header","required":true,"schema":{"type":"string"},"description":"CSRF token for state-changing requests"}},"schemas":{"PasswordSetSmsOtpRequest":{"type":"object","required":["password1","password2","phone"],"properties":{"password1":{"type":"string","format":"password","description":"New password"},"password2":{"type":"string","format":"password","description":"Confirm new password (must match password1)"},"phone":{"type":"string","maxLength":60,"description":"Phone number for SMS verification"},"code":{"type":"string","description":"SMS verification code. Omit on first request\nto receive OTP."},"resend":{"type":"boolean","default":false,"description":"Set to true to request a new verification code"}}}}},"paths":{"/users/password-sms-otp/set/":{"post":{"tags":["Password"],"operationId":"setPasswordWithSmsOtp","summary":"Set password with SMS OTP verification (guest users)","description":"Sets password for authenticated guest users with phone number verification\nvia SMS OTP. This is a two-step process that transitions guest users to\nregistered users.\n\n**Step 1 - Request OTP:**\nSend `phone`, `password1`, `password2` without `code`.\nSystem validates fields, sends SMS verification code, and stores\nconfirmation data in session. Returns 202 status.\n\n**Step 2 - Verify and Set Password:**\nSend all fields including `code`. System verifies the code against\nsession confirmation data. If valid:\n- Password is set and validated against `AUTH_PASSWORD_VALIDATORS`\n- User type changes from `guest` to `registered`\n- Phone number is saved to user profile\n- `verified_phone` attribute is set to `true`\n- Old password is saved to user profile history\nReturns 200 with success message.\n\n**Resend OTP:**\nUse `resend: true` to request a new code. Subject to SMS resend interval\nrestrictions (configured via `SMS_OTP_RESENT_TIME_GAP` system setting).\nReturns 202 status.\n\n**Phone Validation:**\nPhone must be unique among active users (uses `UNIQUE_VALIDATOR_PHONE_MESSAGE`\ndynamic setting for error message).\n\n**Password Validation:**\nPassword strength validated according to `AUTH_PASSWORD_VALIDATORS` setting\n(Dynamic Configuration).\n\nThrottling scope: `register`.","parameters":[{"$ref":"#/components/parameters/SessionCookieHeader"},{"$ref":"#/components/parameters/CSRFTokenHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordSetSmsOtpRequest"}}}},"responses":{"200":{"description":"Password set successfully (Step 2 completed)","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string","description":"Success message"}}}}}},"202":{"description":"SMS verification code sent (Step 1 or resend).\nConfirmation data stored in session.","content":{"application/json":{"schema":{"type":"object","description":"Returns submitted data (without code) or empty response."}}}},"400":{"description":"Validation errors:\n- Password mismatch (password1 != password2)\n- Weak password (fails AUTH_PASSWORD_VALIDATORS)\n- Phone already exists for another active user\n- Phone format invalid\n- Incorrect OTP code during verification"},"401":{"description":"Authentication required (must be logged in)"},"429":{"description":"Too many requests:\n- Throttling limit exceeded (register scope)\n- SMS resend interval not elapsed"}}}}}}
```

## Request password reset via email

> Initiates password reset flow by sending a reset link via email.\
> \
> \*\*User Filtering:\*\*\
> Only sends email to users who:\
> \- Are registered users (not guest users)\
> \- Have verified email addresses\
> \- Are active users\
> \
> \*\*Email Content:\*\*\
> Email is rendered using template from \`RESET\_EMAIL\_HTML\_TEMPLATE\` setting\
> (Dynamic Configuration). The email contains:\
> \- Password reset link with unique token\
> \- Frontend ID in extra context (from \`X-Frontend-ID\` header if valid)\
> \
> \*\*Security:\*\*\
> Always returns success (200) to prevent email enumeration, regardless of\
> whether the email exists in the system.\
> \
> \*\*User Translation:\*\*\
> Email is sent in user's preferred language (\`user.language\_code\`).\
> \
> Throttling scope: \`password-reset\`.

```json
{"openapi":"3.1.0","info":{"title":"Users API - Authentication, Password, Orders, and Hooks","version":"1.0.0"},"tags":[{"name":"Password","description":"Password set, reset, and change operations"}],"servers":[{"description":"Default commerce site","url":"https://{commerce_url}","variables":{"commerce_url":{"default":"sandbox.akinon.com","description":"Commerce storefront hostname"}}}],"paths":{"/users/password/reset/":{"post":{"tags":["Password"],"operationId":"resetPasswordEmail","summary":"Request password reset via email","description":"Initiates password reset flow by sending a reset link via email.\n\n**User Filtering:**\nOnly sends email to users who:\n- Are registered users (not guest users)\n- Have verified email addresses\n- Are active users\n\n**Email Content:**\nEmail is rendered using template from `RESET_EMAIL_HTML_TEMPLATE` setting\n(Dynamic Configuration). The email contains:\n- Password reset link with unique token\n- Frontend ID in extra context (from `X-Frontend-ID` header if valid)\n\n**Security:**\nAlways returns success (200) to prevent email enumeration, regardless of\nwhether the email exists in the system.\n\n**User Translation:**\nEmail is sent in user's preferred language (`user.language_code`).\n\nThrottling scope: `password-reset`.","parameters":[{"name":"X-Frontend-ID","in":"header","required":false,"schema":{"type":"string"},"description":"Frontend identifier for multi-frontend setups.\nMust be in `VALID_FRONTEND_IDS` setting (Dynamic Configuration).\nPassed to email template context as `frontend_id`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetEmailRequest"}}}},"responses":{"200":{"description":"Reset email sent if address exists.\nResponse is identical for existing and non-existing emails\nto prevent enumeration.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","description":"Confirmation message"}}}}}},"429":{"description":"Too many reset requests"}}}}},"components":{"schemas":{"PasswordResetEmailRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Email address for password reset link"}}}}}}
```

## Request password reset via SMS

> Initiates password reset flow by sending a reset SMS with a password\
> reset link to the provided phone number.\
> \
> \*\*User Lookup:\*\*\
> Searches for active users by phone number. Only sends SMS if:\
> \- Phone number exists in the system\
> \- User is active\
> \
> \*\*SMS Content:\*\*\
> SMS contains:\
> \- Site name\
> \- Password reset URL with token (format: \`/password-reset/{uid}/{token}/\`)\
> \- User information\
> \
> \*\*Token Generation:\*\*\
> A secure, time-limited token is generated for password reset confirmation.\
> \
> \*\*Security:\*\*\
> Always returns success message (200) to prevent phone number enumeration,\
> regardless of whether the phone exists in the system.\
> \
> Throttling scope: \`password-reset\`.

```json
{"openapi":"3.1.0","info":{"title":"Users API - Authentication, Password, Orders, and Hooks","version":"1.0.0"},"tags":[{"name":"Password","description":"Password set, reset, and change operations"}],"servers":[{"description":"Default commerce site","url":"https://{commerce_url}","variables":{"commerce_url":{"default":"sandbox.akinon.com","description":"Commerce storefront hostname"}}}],"paths":{"/users/password/reset-with-phone/":{"post":{"tags":["Password"],"operationId":"resetPasswordPhone","summary":"Request password reset via SMS","description":"Initiates password reset flow by sending a reset SMS with a password\nreset link to the provided phone number.\n\n**User Lookup:**\nSearches for active users by phone number. Only sends SMS if:\n- Phone number exists in the system\n- User is active\n\n**SMS Content:**\nSMS contains:\n- Site name\n- Password reset URL with token (format: `/password-reset/{uid}/{token}/`)\n- User information\n\n**Token Generation:**\nA secure, time-limited token is generated for password reset confirmation.\n\n**Security:**\nAlways returns success message (200) to prevent phone number enumeration,\nregardless of whether the phone exists in the system.\n\nThrottling scope: `password-reset`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordResetPhoneRequest"}}}},"responses":{"200":{"description":"Success response (always returned regardless of phone existence).\nSMS is sent only if phone belongs to an active user.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"string","description":"Confirmation message (translated)"}}}}}},"400":{"description":"Validation errors:\n- Phone format is invalid"},"429":{"description":"Throttling limit exceeded (password-reset scope)"}}}}},"components":{"schemas":{"PasswordResetPhoneRequest":{"type":"object","required":["phone"],"properties":{"phone":{"type":"string","maxLength":60,"description":"Phone number for password reset SMS"}}}}}}
```

## Change password for authenticated user

> Changes password for the currently authenticated user.\
> Requires verification of the old password and two matching new password entries.\
> \
> \*\*Password Validation:\*\*\
> New password is validated against \`AUTH\_PASSWORD\_VALIDATORS\` setting\
> (Dynamic Configuration).\
> \
> \*\*Side Effects:\*\*\
> \- Old password is saved to user profile history\
> \- User type is updated to \`registered\` if not already\
> \- Notification email is sent asynchronously\
> \
> \*\*Email Notification:\*\*\
> A confirmation email is sent to the user with subject "Password Changed".\
> CC/BCC recipients can be configured via \`EMAIL\_BCC\_AND\_CC\` setting.\
> \
> \*\*Audit Logging:\*\*\
> Password change events are logged for security audit purposes.\
> Password values are not stored in audit logs.\
> \
> \*\*Error Handling:\*\*\
> Invalid old password returns "Invalid password." error message.

```json
{"openapi":"3.1.0","info":{"title":"Users API - Authentication, Password, Orders, and Hooks","version":"1.0.0"},"tags":[{"name":"Password","description":"Password set, reset, and change operations"}],"servers":[{"description":"Default commerce site","url":"https://{commerce_url}","variables":{"commerce_url":{"default":"sandbox.akinon.com","description":"Commerce storefront hostname"}}}],"security":[{"SessionCookie":[]}],"components":{"securitySchemes":{"SessionCookie":{"type":"apiKey","in":"cookie","name":"sessionid","description":"Session cookie for authentication"}},"parameters":{"SessionCookieHeader":{"name":"Cookie","in":"header","required":false,"schema":{"type":"string"},"description":"Session cookie in format `sessionid=<value>`.\nMay use site-specific cookie name (e.g., `osessionid`)."},"CSRFTokenHeader":{"name":"X-CSRFToken","in":"header","required":true,"schema":{"type":"string"},"description":"CSRF token for state-changing requests"}},"schemas":{"PasswordChangeRequest":{"type":"object","required":["old_password","new_password1","new_password2"],"properties":{"old_password":{"type":"string","format":"password","description":"Current password"},"new_password1":{"type":"string","format":"password","description":"New password"},"new_password2":{"type":"string","format":"password","description":"Confirm new password (must match new_password1)"}}}}},"paths":{"/users/password/change/":{"post":{"tags":["Password"],"operationId":"changePassword","summary":"Change password for authenticated user","description":"Changes password for the currently authenticated user.\nRequires verification of the old password and two matching new password entries.\n\n**Password Validation:**\nNew password is validated against `AUTH_PASSWORD_VALIDATORS` setting\n(Dynamic Configuration).\n\n**Side Effects:**\n- Old password is saved to user profile history\n- User type is updated to `registered` if not already\n- Notification email is sent asynchronously\n\n**Email Notification:**\nA confirmation email is sent to the user with subject \"Password Changed\".\nCC/BCC recipients can be configured via `EMAIL_BCC_AND_CC` setting.\n\n**Audit Logging:**\nPassword change events are logged for security audit purposes.\nPassword values are not stored in audit logs.\n\n**Error Handling:**\nInvalid old password returns \"Invalid password.\" error message.","parameters":[{"$ref":"#/components/parameters/SessionCookieHeader"},{"$ref":"#/components/parameters/CSRFTokenHeader"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PasswordChangeRequest"}}}},"responses":{"200":{"description":"Password changed successfully","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string","description":"Success confirmation message (translated)"}}}}}},"400":{"description":"Validation errors:\n- Invalid old password (\"Invalid password.\")\n- Password mismatch (new_password1 != new_password2)\n- Weak password (fails AUTH_PASSWORD_VALIDATORS)","content":{"application/json":{"schema":{"type":"object","properties":{"old_password":{"type":"array","items":{"type":"string"}},"new_password2":{"type":"array","items":{"type":"string"}}}}}}},"401":{"description":"Authentication required"}}}}}}
```
