Files
resolutionflow/frontend/src/api/auth.ts
Michael Chihlas aad554bb9c feat(ui): handle session_expired_{idle,absolute} in axios interceptor
Seventh commit in the session-expiration-policy series. Wires the
backend taxonomy from commit 2 through to the frontend so users see
the right page (calm banner vs plain logout) when the refresh path
fails for different reasons.

- types/auth.ts: Token gains idle_expires_at + absolute_expires_at
  (Optional ISO 8601 strings). The next commit adds the
  useAuthSessionExpiry hook that reads these.
- api/auth.ts: OAuthCallbackResponse mirrors the same two fields.
- api/client.ts: refresh-failure handler now branches on the response
  detail. session_expired_idle and session_expired_absolute both
  redirect to /login?reason=session_expired (commit 8 adds the
  banner that reads the query param); any other detail (most
  commonly invalid_refresh_token) goes to plain /login. The bare
  redirect is guarded against re-firing when the user is already on
  /login. The refresh-success path now forwards the two new fields
  into setTokens so the store stays current as the session ages.
- pages/OAuthCallbackPage.tsx: setTokens({...}) spreads
  idle_expires_at + absolute_expires_at from the OAuth response.

No new tests — authStore.test still 2/2, tsc clean. The
useAuthSessionExpiry hook and the SessionExpiryToast that consume
the new fields land in commit 8 alongside the AccountSecurity page.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 16:33:56 -04:00

116 lines
3.3 KiB
TypeScript

import apiClient from './client'
import type { Token, User, UserCreate, UserLogin, UserUpdate } from '@/types'
export interface OAuthCallbackResponse {
access_token: string
refresh_token: string
token_type: string
is_new_user: boolean
idle_expires_at?: string | null
absolute_expires_at?: string | null
}
export const authApi = {
async register(data: UserCreate): Promise<User> {
const response = await apiClient.post<User>('/auth/register', data)
return response.data
},
async login(data: UserLogin): Promise<Token> {
const response = await apiClient.post<Token>('/auth/login/json', data)
return response.data
},
async refresh(): Promise<Token> {
const refreshToken = localStorage.getItem('refresh_token')
const response = await apiClient.post<Token>('/auth/refresh', null, {
headers: {
Authorization: `Bearer ${refreshToken}`,
},
})
return response.data
},
async me(): Promise<User> {
const response = await apiClient.get<User>('/auth/me')
return response.data
},
async logout(): Promise<void> {
await apiClient.post('/auth/logout')
},
async changePassword(currentPassword: string, newPassword: string): Promise<void> {
await apiClient.post('/auth/password/change', {
current_password: currentPassword,
new_password: newPassword,
})
},
async forgotPassword(email: string): Promise<void> {
await apiClient.post('/auth/password/forgot', { email })
},
async verifyResetToken(token: string): Promise<{ valid: boolean; email: string | null }> {
const response = await apiClient.post<{ valid: boolean; email: string | null }>('/auth/password/verify-reset-token', { token })
return response.data
},
async resetPassword(token: string, newPassword: string): Promise<void> {
await apiClient.post('/auth/password/reset', {
token,
new_password: newPassword,
})
},
async updateProfile(data: UserUpdate): Promise<User> {
const response = await apiClient.patch<User>('/auth/me', data)
return response.data
},
async getVerificationStatus(): Promise<{ enabled: boolean }> {
const response = await apiClient.get<{ enabled: boolean }>('/auth/email/verification-status')
return response.data
},
async sendVerificationEmail(): Promise<void> {
await apiClient.post('/auth/email/send-verification')
},
async verifyEmail(token: string): Promise<void> {
await apiClient.post('/auth/email/verify', { token })
},
async googleCallback(
code: string,
options?: { accountInviteCode?: string; invitedEmail?: string },
): Promise<OAuthCallbackResponse> {
const response = await apiClient.post<OAuthCallbackResponse>(
'/auth/google/callback',
{
code,
account_invite_code: options?.accountInviteCode,
invited_email: options?.invitedEmail,
},
)
return response.data
},
async microsoftCallback(
code: string,
options?: { accountInviteCode?: string; invitedEmail?: string },
): Promise<OAuthCallbackResponse> {
const response = await apiClient.post<OAuthCallbackResponse>(
'/auth/microsoft/callback',
{
code,
account_invite_code: options?.accountInviteCode,
invited_email: options?.invitedEmail,
},
)
return response.data
},
}
export default authApi