Auth Recently Changed
User Registration
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-001 | Register with email, password, and phone | Not logged in, at /register | 1. Enter email "newuser@example.com" 2. Enter full name "John Doe" 3. Enter phone "+1234567890" 4. Enter password "SecurePass123!" 5. Confirm password "SecurePass123!" 6. Enter organization name "Acme Corp" 7. Click "Sign Up" button | Success toast appears. User redirected to /verify. Email and phone verification gates shown. | High |
| AUTH-002 | Register with Google OAuth | Not logged in, at /register | 1. Click "Continue with Google" button 2. Complete Google OAuth flow in popup | User redirected to /chat after OAuth success. Account created with Google email and name. | High |
| AUTH-003 | Validation: passwords do not match | Not logged in, at /register | 1. Fill all fields with valid data 2. Enter password "SecurePass123!" 3. Enter confirm password "DifferentPass456!" 4. Click "Sign Up" | Error message "Passwords do not match." displayed below confirm password field. Form not submitted. | High |
| AUTH-004 | Validation: invalid phone number | Not logged in, at /register | 1. Fill all fields with valid data 2. Enter phone number "123" (invalid format) 3. Click "Sign Up" | Error message "Please enter a valid mobile number." displayed. Form not submitted. | High |
| AUTH-005 | Validation: email already exists | Not logged in, at /register. Email "existing@example.com" already registered | 1. Enter email "existing@example.com" 2. Fill all other fields 3. Click "Sign Up" | Error message displayed: "Email already registered." User remains on /register form. | High |
| AUTH-006 | Validation: required fields empty | Not logged in, at /register | 1. Leave email field empty 2. Click "Sign Up" | HTML5 validation: "Please fill in this field." shown on email input. Form not submitted. | Medium |
| AUTH-007 | Validation: weak password | Not logged in, at /register | 1. Fill all fields 2. Enter password "123" (too weak) 3. Click "Sign Up" | Error displayed (if backend enforces password policy): "Password does not meet security requirements." | Medium |
| AUTH-008 | Rate limit: multiple registration attempts | Not logged in, at /register | 1. Attempt to register 6 times in quick succession (within 1 hour) | After 5 successful attempts, 6th attempt shows error: "Too many registration attempts. Please try again later." | Medium |
User Login
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-009 | Login with email and password (web) | Not logged in, user registered, at /login | 1. Enter email "user@example.com" in identifier field 2. Enter password "SecurePass123!" 3. Click "Sign In" | Success toast "Logged in successfully." User redirected to /chat. Access and refresh tokens stored as httponly cookies. | High |
| AUTH-010 | Login with phone and password (web) | Not logged in, user registered with phone, at /login | 1. Enter phone number "+1234567890" in identifier field 2. Enter password "SecurePass123!" 3. Click "Sign In" | User logged in and redirected to /chat. | High |
| AUTH-011 | Login with Google OAuth | Not logged in, user has Google account linked, at /login | 1. Click "Sign In with Google" button 2. Complete Google OAuth flow | User redirected to /chat. Session authenticated. | High |
| AUTH-012 | Login: invalid credentials | Not logged in, at /login | 1. Enter email "user@example.com" 2. Enter password "WrongPassword123" 3. Click "Sign In" | Error message displayed: "Invalid credentials." User remains on /login. No tokens set. | High |
| AUTH-013 | Login: account suspended | Not logged in, user account suspended, at /login | 1. Enter email "suspended@example.com" 2. Enter correct password 3. Click "Sign In" | User account_status is 'suspended'. User redirected to /account-locked page instead of /chat. | High |
| AUTH-014 | Login: account deactivated | Not logged in, user account deactivated, at /login | 1. Enter email "deactivated@example.com" 2. Enter correct password 3. Click "Sign In" | User account_status is 'deactivated'. User redirected to /account-locked page. | High |
| AUTH-015 | Login: non-existent email | Not logged in, at /login | 1. Enter email "nonexistent@example.com" 2. Enter any password 3. Click "Sign In" | Error message displayed: "Invalid credentials." (does not reveal whether account exists). | High |
| AUTH-016 | Validation: empty email field | Not logged in, at /login | 1. Leave email field empty 2. Click "Sign In" | HTML5 validation error: "Please fill in this field." Form not submitted. | Medium |
| AUTH-017 | Validation: empty password field | Not logged in, at /login | 1. Enter email "user@example.com" 2. Leave password empty 3. Click "Sign In" | HTML5 validation error: "Please fill in this field." Form not submitted. | Medium |
| AUTH-018 | Password visibility toggle | Not logged in, at /login | 1. Click in password field 2. Type "SecurePass123" 3. Click eye icon to show password | Password text becomes visible (input type changes to "text"). Eye icon changes to crossed-eye icon. | Low |
| AUTH-019 | Rate limit: multiple login attempts | Not logged in, at /login | 1. Attempt login with wrong password 11 times within 15 minutes | After 10 failed attempts, 11th attempt shows error: "Too many login attempts. Please try again in a few minutes." Further login attempts blocked for 15 min. | Medium |
| AUTH-020 | Error message display: Google sign-in failed | Not logged in, redirected from Google with error, at /login?error=google_failed | 1. Page loads | Error message displayed: "Google sign-in failed. Please try again." | Low |
| AUTH-021 | Login with invite token: user without default org | Registered user removed from all orgs, at /login with invite_token in request | 1. Enter email and password 2. Pass invite_token=VALID_TOKEN in login request | User logs in. No default organization found. If invite_token valid and email matches: membership restored to invited org, user accepted as member, user redirected to /chat with invited org set as current. Success logged: "login_invite_membership_restored". | High |
| AUTH-022 | Login with invite token: invalid invite | Registered user at /login, sending invalid invite token | 1. Enter email and password 2. Pass invite_token=INVALID_TOKEN in login request | Login fails. Error message displayed: "Invalid or expired invite link." User remains on /login. | High |
| AUTH-023 | Login with invite token: email mismatch | Registered user at /login, invite sent to different email | 1. User logs in with their email/password 2. Pass invite_token=TOKEN_FOR_DIFFERENT_EMAIL in login request | Login fails. Error message displayed: "This invite was sent to a different email address." User remains on /login. | High |
| AUTH-024 | Login with invite token: org not found | Registered user at /login, invite org deleted | 1. User logs in with correct credentials 2. Pass invite_token=TOKEN_FOR_DELETED_ORG in login request | Login fails. Error message displayed: "Organization not found." | Medium |
| AUTH-025 | Login with invite token: user limit exceeded | Registered user at /login, target org at user capacity | 1. User logs in with correct credentials 2. Pass invite_token=TOKEN_FOR_FULL_ORG in login request | Login fails. Error message displayed: "Organization has reached maximum member limit." User remains on /login. | Medium |
Mobile Login (Native Clients)
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-108 | Mobile login with email and password | Not logged in, native mobile app, user registered | 1. Enter email "user@example.com" in identifier field 2. Enter password "SecurePass123!" 3. Tap "Sign In" button | HTTP 200 response with JSON body containing: access_token, refresh_token (as JSON string, not cookie), user object, organization object, memberships array, expires_in (in seconds), permissions array, role object. Mobile app stores tokens in secure storage. User logged in. | High |
| AUTH-109 | Mobile login with phone and password | Not logged in, native mobile app, user registered with phone | 1. Enter phone "+1234567890" in identifier field 2. Enter password "SecurePass123!" 3. Tap "Sign In" button | HTTP 200 response with MobileAuthResponse JSON: access_token, refresh_token, user, organization, active_org_id, memberships, expires_in, permissions, role. Mobile app can store tokens securely. User authenticated. | High |
| AUTH-110 | Mobile login: invalid credentials | Not logged in, native mobile app, at login screen | 1. Enter email "user@example.com" 2. Enter password "WrongPassword123" 3. Tap "Sign In" | HTTP 401 response with error detail: "Invalid credentials." Login fails. Tokens not returned. Error message shown to user. | High |
| AUTH-111 | Mobile login: account suspended | Not logged in, native mobile app, user account suspended | 1. Enter email "suspended@example.com" 2. Enter correct password 3. Tap "Sign In" | HTTP 403 response with detail: "Your account has been suspended. Contact support." Login fails. Error message shown. | High |
| AUTH-112 | Mobile login: account deactivated | Not logged in, native mobile app, user account deactivated | 1. Enter email "deactivated@example.com" 2. Enter correct password 3. Tap "Sign In" | HTTP 403 response with detail: "This account no longer exists." Login fails. | High |
| AUTH-113 | Mobile login with invite token | Not logged in, native mobile app, user registered, invite_token provided | 1. Enter email "user@example.com" 2. Enter password "SecurePass123!" 3. Include invite_token=VALID_TOKEN in request 4. Tap "Sign In" | HTTP 200 with MobileAuthResponse. User membership restored to invited org. organization field in response reflects invited org. Log entry: "mobile_login_success". | High |
| AUTH-114 | Mobile login response includes expires_in | Native mobile app, successful login | 1. Complete mobile login flow | Response includes expires_in field = ACCESS_TOKEN_EXPIRE_MINUTES * 60 (seconds). Mobile app uses this value to schedule token refresh before expiry. | High |
| AUTH-115 | Mobile login: no invite token | Native mobile app, user logs in without invite token | 1. Enter credentials 2. No invite_token passed 3. Tap "Sign In" | HTTP 200 with MobileAuthResponse. User default organization returned (is_default=true). organization field reflects default org. | High |
MCP (Machine-to-Machine) OAuth Token Minting
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-126 | MCP access token: minted with resource claim | MCP client authenticated, requesting access token at /auth/mcp/token endpoint | 1. MCP client sends request with valid credentials/secret 2. Server processes mint_mcp_access_token flow 3. Access token generated | HTTP 200 response. Access token JWT includes claims: aud = OAUTH_API_RESOURCE, resource = OAUTH_API_RESOURCE, issuer = OAUTH_ISSUER_URL (in addition to standard user_id, org_id, scopes, exp, iat). Token can be used by downstream services to validate API resource audience. | High |
| AUTH-127 | MCP access token: resource claim matches configured API resource | System configured with OAUTH_API_RESOURCE = "https://api.bizbotify.com", MCP client mints token | 1. MCP client authenticates 2. Request /auth/mcp/token 3. Decode returned access_token JWT | JWT aud claim = "https://api.bizbotify.com". JWT resource claim = "https://api.bizbotify.com". Downstream API services validate that resource claim matches expected audience. | High |
| AUTH-128 | MCP access token: issuer claim set correctly | System configured with OAUTH_ISSUER_URL = "https://auth.bizbotify.com", MCP client mints token | 1. MCP client authenticates 2. Request /auth/mcp/token 3. Decode returned access_token JWT | JWT issuer claim = "https://auth.bizbotify.com". Token issuer claim matches configured auth server URL. Downstream services validate issuer to prevent token forgery. | High |
| AUTH-129 | MCP access token: includes scopes claim | MCP client requesting token with specific scopes = ["contacts:read", "chats:write"] | 1. MCP client sends request with scopes 2. /auth/mcp/token processes request | HTTP 200 response with access_token JWT. JWT scopes claim = ["contacts:read", "chats:write"]. Downstream services check scopes to enforce permission boundaries. | High |
| AUTH-130 | MCP access token: includes org_id claim | MCP client authenticated to organization "org_123" | 1. MCP client authenticates with org context 2. Request /auth/mcp/token | HTTP 200 response. JWT org_id claim = "org_123". Downstream API routes use org_id from token to scope database queries and enforce multi-tenancy. | High |
| AUTH-131 | MCP access token: includes user_id claim | MCP client authenticated as user "user_456" | 1. MCP client authenticates with user context 2. Request /auth/mcp/token | HTTP 200 response. JWT user_id claim = "user_456" (as string). Downstream services log audit trail using user_id from token. | High |
| AUTH-132 | MCP access token: token expiry set correctly | MCP client mints access token | 1. MCP client authenticates 2. Request /auth/mcp/token 3. Decode JWT and check exp claim | JWT exp claim = current time + (ACCESS_TOKEN_EXPIRE_MINUTES * 60 seconds). Token expiry matches configured lifetime. Downstream services reject expired tokens. | High |
| AUTH-133 | MCP token without required scopes fails | MCP client authenticated but requesting /contacts API without contacts:read scope | 1. MCP client mints token with scopes = [] (no scopes) 2. Client attempts API request to /contacts with token | API request fails with HTTP 403. Error detail: "Missing required scope: contacts:read." Token has no scopes to satisfy endpoint requirement. | High |
| AUTH-134 | MCP token issuer claim prevents cross-origin token use | Access token minted by Auth Server A with issuer="https://auth-a.com", used against Auth Server B | 1. Token minted by server A (issuer claim = "https://auth-a.com") 2. Downstream service B validates token 3. issuer claim checked against expected authority | Token rejected. Error: "Invalid token issuer." Issuer claim mismatch prevents token reuse across different auth servers. | Medium |
| AUTH-135 | MCP token resource audience validation | Access token with resource="https://api.bizbotify.com", used against different API audience | 1. Token minted with resource claim = "https://api.bizbotify.com" 2. Downstream service validates that token.resource matches expected audience | If service expects audience "https://different-api.com": token rejected with "Invalid token audience." If service expects "https://api.bizbotify.com": token accepted. | Medium |
Password Management
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-026 | Forgot password: email sent | Not logged in, at /forgot-password | 1. Enter email "user@example.com" 2. Click "Send Reset Link" | Success page shown: "Check your inbox" message. Email sent to "user@example.com" with password reset link. | High |
| AUTH-027 | Forgot password: non-existent email | Not logged in, at /forgot-password | 1. Enter email "nonexistent@example.com" 2. Click "Send Reset Link" | Same success page shown as AUTH-026 (does not reveal whether email exists). No email sent. | High |
| AUTH-028 | Forgot password: rate limit | Not logged in, at /forgot-password | 1. Attempt to send reset link 6 times in 1 hour | After 5 attempts, 6th attempt shows error: "Too many password reset requests. Please try again later." | Medium |
| AUTH-029 | Reset password: valid token | Not logged in, email verification token valid, at /reset-password?token=VALID_TOKEN | 1. Click password reset link from email | Page loads with password reset form. Token auto-verified in background. | High |
| AUTH-030 | Reset password: new password set | Valid password reset token in URL, at /reset-password?token=VALID_TOKEN | 1. Enter new password "NewSecure123!" 2. Confirm password "NewSecure123!" 3. Click "Reset Password" | Success message shown. User redirected to /login. Old password no longer works; new password allows login. | High |
| AUTH-031 | Reset password: token expired | Password reset token expired, at /reset-password?token=EXPIRED_TOKEN | 1. Page loads | Error message shown: "Verification link is invalid or has expired." Link to /forgot-password provided. | High |
| AUTH-032 | Reset password: invalid token | Password reset token malformed, at /reset-password?token=INVALID | 1. Page loads | Error message shown: "Verification link is invalid or has expired." | High |
| AUTH-033 | Reset password: passwords do not match | Valid password reset token, at /reset-password?token=VALID_TOKEN | 1. Enter password "NewSecure123!" 2. Enter confirm password "Different456!" 3. Click "Reset Password" | Error message: "Passwords do not match." Form not submitted. | Medium |
| AUTH-034 | Change password: logged in user | Logged in user, at /settings or account page (requires navigation) | 1. Go to account settings 2. Click "Change Password" 3. Enter current password "OldPass123!" 4. Enter new password "NewPass456!" 5. Confirm new password "NewPass456!" 6. Click "Change Password" | Success toast shown: "Password changed successfully." | High |
| AUTH-035 | Change password: wrong current password | Logged in user, at password change form | 1. Enter current password "WrongPassword" 2. Enter new password "NewPass456!" 3. Confirm "NewPass456!" 4. Click "Change Password" | Error message: "Current password is incorrect." Password not changed. | High |
Email Verification
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-036 | Email verification: auto-submit on verify link click | Registered user, email unverified, at /verify-email?token=VALID_TOKEN | 1. Click email verification link from inbox | Page shows "Verifying your email..." spinner briefly. Success icon and "Email verified!" message shown. After 1.5 seconds, redirected to /verify (phone gate) or /chat (if phone also verified). | High |
| AUTH-037 | Email verification: invalid token | At /verify-email?token=INVALID_TOKEN | 1. Page loads | Error page shown: "Verification failed. Verification link is invalid or has expired." Link to /verify provided. | High |
| AUTH-038 | Email verification: expired token | At /verify-email?token=EXPIRED_TOKEN | 1. Page loads | Error page shown: "Verification failed. Verification link is invalid or has expired." | High |
| AUTH-039 | Email verification: missing token in URL | At /verify-email without token parameter | 1. Page loads | Error page shown: "Verification failed. Invalid verification link." | High |
| AUTH-040 | Email verification: resend verification email | Logged in user at /verify, email unverified | 1. Click "Resend verification email" link | Success toast shown: "Verification email sent." Email resent to user email. Resend button disabled for 60 seconds with countdown. | High |
| AUTH-041 | Email verification gate: user stays on /verify until email verified | Logged in user with email unverified, at /verify | 1. Attempt to navigate to /chat directly (via URL bar) | User redirected back to /verify. Cannot access app until email verified. | High |
Phone Verification
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-042 | Phone OTP: request OTP sent | Logged in user at /verify, email verified, phone unverified | 1. Page shows phone verification form 2. OTP auto-requested on page load | OTP sent to user's phone via WhatsApp. Message shows "Enter the 6-digit code sent to your phone." | High |
| AUTH-043 | Phone OTP: valid 6-digit code | Logged in user at /verify with OTP sent | 1. Receive OTP "123456" on WhatsApp 2. Enter digits "123456" in 6 OTP input fields 3. OTP auto-submits after 6th digit entered | Success toast shown. User redirected to /chat. phone_verified flag set to true. | High |
| AUTH-044 | Phone OTP: auto-focus on next field | Logged in user at /verify, OTP form displayed | 1. Click 1st OTP input 2. Type "1" 3. Focus auto-moves to 2nd input | 2nd input field focused. Cursor visible in 2nd field. | Low |
| AUTH-045 | Phone OTP: backspace to previous field | Logged in user at /verify, OTP form with partial input | 1. Focus is on 3rd OTP input 2. Press Backspace on empty 3rd input | Focus moves to 2nd input field. | Low |
| AUTH-046 | Phone OTP: paste all 6 digits at once | Logged in user at /verify, OTP form focused on 1st input | 1. Copy "123456" from clipboard 2. Paste into OTP field (Ctrl+V) | All 6 digits populated across all fields automatically. Paste event prevented from default. | Low |
| AUTH-047 | Phone OTP: non-numeric input rejected | Logged in user at /verify, OTP form | 1. Click OTP input 2. Type "abc123!" | Only numeric characters "123" accepted. Non-numeric chars filtered out. | Low |
| AUTH-048 | Phone OTP: invalid code | Logged in user at /verify, wrong OTP code entered | 1. Enter OTP "999999" 2. Form auto-submits after 6 digits | Error message shown: "Invalid OTP. Please try again." Fields cleared. Resend option available. | High |
| AUTH-049 | Phone OTP: resend OTP | Logged in user at /verify, OTP expired or not received | 1. Wait 10 seconds 2. Click "Resend OTP" button | OTP resent to WhatsApp. Button disabled for 60 seconds with countdown timer. Success message shown: "OTP sent." | High |
| AUTH-050 | Phone OTP: rate limit on resend | Logged in user at /verify, attempted resend 11 times | 1. Click "Resend OTP" 11 times rapidly | After 10 successful resends within 15 minutes, 11th attempt shows error: "Too many OTP requests. Please try again later." Resend blocked. | Medium |
| AUTH-051 | Phone OTP: skip phone verification (optional) | Logged in user at /verify with email verified, phone optional | 1. Click "Skip for now" button (if available) | User redirected to /chat. Phone verification can be completed later from settings. | Medium |
| AUTH-052 | Phone verification gate: user must verify phone | Logged in user with email verified, phone unverified | 1. Try to navigate to /chat | User redirected to /verify. Phone verification required before app access. | High |
| AUTH-136 | Phone OTP: pending phone number change on request | Logged in user at /verify, requesting OTP with new phone number | 1. User calls /send-phone-otp endpoint with new phone "+9876543210" 2. Existing phone on user is "+1234567890" | OTP sent to new phone "+9876543210". New phone number NOT immediately written to user.phone. Instead, stored as pending_phone in OTP record. User.phone remains "+1234567890" until verification succeeds. Log shows "phone_otp_pending_change". | High |
| AUTH-137 | Phone OTP: existing phone conflict detection | Logged in user at /verify, requesting OTP with phone already registered to another user | 1. User calls /send-phone-otp with phone "+5555555555" 2. Another user already has phone "+5555555555" on their account | HTTP 409 response. Error detail: "Phone number already in use." OTP not sent. Pending phone change not created. | High |
| AUTH-138 | Phone OTP: verify succeeds, pending phone becomes primary phone | Logged in user at /verify with pending phone change requested | 1. User received OTP for pending phone "+9876543210" 2. User enters correct OTP "123456" 3. Click verify button | HTTP 200 response. Verification succeeds. Pending phone "+9876543210" is now set as user.phone. User.phone_verified = true. If phone changed (new phone != old phone), clear_internal_team_link_for_user is called. Log shows "phone_number_updated_on_verify". User redirected to /chat. | High |
| AUTH-139 | Phone OTP: verify fails, pending phone not applied | Logged in user with pending phone change | 1. User enters incorrect OTP "999999" 2. Click verify button | HTTP 400 response. Error detail: "Invalid or expired OTP." Verification fails. Pending phone NOT applied to user.phone. User remains with original phone number. User can retry with correct OTP. | High |
| AUTH-140 | Phone OTP: verify second-time duplicate check | User A has phone "+1111111111", User B requests OTP for same phone, then enters correct OTP | 1. User B at /verify calls /send-phone-otp with phone "+1111111111" (pending_phone set) 2. User B enters correct OTP 3. During verify_phone, pending_phone "+1111111111" is checked against User A | HTTP 409 response in /verify_phone endpoint. Error detail: "Phone number already in use." Verification rejected. User B's phone not updated. Prevents race condition where pending phone becomes conflicted between request and verify. | High |
Session & Token Management
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-053 | Cookies set after web login | User logs in via web /login endpoint | 1. Complete web login flow | Browser dev tools > Application > Cookies shows: - access_token cookie (httponly, secure, samesite=lax, max_age = ACCESS_TOKEN_EXPIRE_MINUTES * 60) - refresh_token cookie (httponly, secure, samesite=lax, max_age = REFRESH_TOKEN_EXPIRE_DAYS * 86400) | High |
| AUTH-054 | Access token expiry | Logged in user, access token near expiry | 1. Wait for access token to expire 2. Make API request to protected endpoint | Request fails with 401. Refresh token automatically used to obtain new access token. Request retried. User session continues without logout. | High |
| AUTH-055 | Refresh token expiry | Logged in user, refresh token expired | 1. Wait for refresh token to expire 2. Make API request | Request fails with 401. User redirected to /login. Must re-authenticate. | High |
| AUTH-056 | Logout clears cookies | Logged in user at /chat | 1. Click account menu > "Logout" 2. Confirm logout | Access and refresh token cookies deleted. User redirected to /login. No longer authenticated. | High |
| AUTH-057 | Silent refresh on route navigation | Logged in user with near-expiry access token, navigating pages | 1. Access token expires in 30 seconds 2. User clicks link to different page 3. Refresh happens automatically | Page loads successfully without user seeing 401 error. Refresh is silent (no loading indicator). | Medium |
| AUTH-058 | Bearer token in Authorization header (mobile/native) | Mobile app with access token from mobile login endpoint | 1. Make API request with Authorization header: "Bearer |
Request succeeds. Server accepts Bearer token from Authorization header. User authenticated without cookies. Returns 200 with user/org data. | High |
| AUTH-059 | Bearer token takes precedence over cookies | Request with both Authorization Bearer header and access_token cookie | 1. Send request with both Bearer token in header and access_token cookie 2. Make request to /auth/me | Bearer token in Authorization header takes precedence. If Bearer token valid, user authenticates via Bearer. Cookie ignored. | High |
| AUTH-060 | Missing Bearer token returns 401 | Request without credentials | 1. Make request to protected endpoint without Authorization header and without access_token cookie | HTTP 401 response. Error detail: "Not authenticated." User not authenticated. | High |
| AUTH-061 | Invalid Bearer token format returns 401 | Request with malformed Authorization header | 1. Send Authorization header: "Bearer invalid_token_xyz" 2. Request to protected endpoint | HTTP 401 response. Error detail: "Not authenticated." Invalid token rejected. | High |
| AUTH-062 | Org ID resolution from Bearer token | Mobile app making request with Bearer token, no X-Organization-ID header or cookies | 1. Decode Bearer token JWT payload 2. Extract org_id claim 3. Make API request to protected endpoint | Server extracts org_id from Bearer token JWT. request.state.org_id set. API response scoped to organization from token. Org-specific data returned. | High |
Organization & Member Management
| ID | Test Case | Preconditions | Steps | Expected Result | Priority |
|---|---|---|---|---|---|
| AUTH-063 | Organization created on registration | User completes registration | 1. Register new account with org name "Acme Corp" 2. Complete verification | Organization "Acme Corp" created. User is default owner/admin of org. user_role = "admin" in UserOrganization. | High |
| AUTH-064 | List organizations for user | Logged in user member of 2 orgs | 1. (Backend call simulated) Fetch /auth/me | Response includes organizations array with both org details. | High |
| AUTH-065 | Invite member to organization | Logged in as Admin, at /settings > Members | 1. Click "Invite Member" button 2. Enter email "newmember@example.com" 3. Select role "Agent" from dropdown 4. Click "Send Invite" | Success toast: "Invite sent to newmember@example.com." Invitation email sent with acceptance link. Member listed as "Pending" until they accept. | High |
| AUTH-066 | Invite validation: invalid email | Logged in as Admin, at invite form | 1. Enter email "notanemail" 2. Click "Send Invite" | Error: "Please enter a valid email address." Form not submitted. | High |
| AUTH-067 | Invite validation: already member | Logged in as Admin, inviting existing member | 1. Enter email of current member 2. Click "Send Invite" | Error: "User is already a member of this organization." | Medium |
| AUTH-068 | Accept invitation | Invited user receives email with acceptance link, at /invites/accept?token=VALID_TOKEN | 1. Click acceptance link in email | Page auto-processes acceptance. Success message shown: "Invitation accepted!" User now appears as active member in org. Can access org in app. | High |
| AUTH-069 | Accept invitation: invalid token | At /invites/accept?token=INVALID_TOKEN | 1. Page loads | Error message: "Invalid or expired invitation link." | High |
| AUTH-070 | Accept invitation: already accepted | At /invites/accept?token=ALREADY_USED_TOKEN | 1. Page loads | Error message: "This invitation has already been used." | Medium |