Some checks failed
homelab-k8s-services/tcb_devportal/pipeline/head There was a failure building this commit
- headers: add blob: to connect-src (CSP regression) - scenario: rename SUCCESS → PASSED in SCENARIO_STATUS constant - auth.query-options: export authKeys.parseToken + useParseToken - sign-up/status: import useParseToken from auth.query-options so jest.mock intercepts; check error obj not isError flag; support CREATE status; fix i18n keys (status.create.title/description, btn.done)
125 lines
3.2 KiB
TypeScript
125 lines
3.2 KiB
TypeScript
// hooks/useAuth.ts
|
|
|
|
import {
|
|
AUTH_INFO_KEY,
|
|
AUTH_REFRESH_FAILED_EVENT,
|
|
} from '@/constants/system'
|
|
import { useRouter } from '@/i18n/navigation'
|
|
import { BaseAPIResponse } from '@/models/api/common'
|
|
import { useGetUrlLoginMutation } from '@/share/layout/end-user/header/hook'
|
|
import { useMutation, useQuery } from '@tanstack/react-query'
|
|
import { AxiosError } from 'axios'
|
|
import { useCallback } from 'react'
|
|
import {
|
|
AuthInfoBase,
|
|
AuthInfoBaseResponse,
|
|
AuthInfoFullSchema,
|
|
LogoutRequest,
|
|
} from './auth.schema'
|
|
import authApi from './auth.service'
|
|
|
|
export const authKeys = {
|
|
all: ['auth'] as const,
|
|
verifyToken: (token?: string) =>
|
|
[...authKeys.all, token, 'verifyToken'] as const,
|
|
parseToken: (token: string) =>
|
|
[...authKeys.all, token, 'parseToken'] as const,
|
|
}
|
|
|
|
export function useVerifyToken(token: string) {
|
|
return useQuery({
|
|
queryKey: authKeys.verifyToken(token),
|
|
queryFn: () => authApi.verifyToken(token),
|
|
retry: false,
|
|
})
|
|
}
|
|
|
|
export function useParseToken(token: string) {
|
|
return useQuery({
|
|
queryKey: authKeys.parseToken(token),
|
|
queryFn: () => authApi.parseToken(token),
|
|
retry: false,
|
|
})
|
|
}
|
|
|
|
const clearAuthAndLogout = () => {
|
|
localStorage.removeItem(AUTH_INFO_KEY)
|
|
}
|
|
|
|
export const useRefresh = (
|
|
onSuccessCallback: (data: AuthInfoBase) => void,
|
|
onErrorCallback: () => void
|
|
) => {
|
|
return useMutation<
|
|
AuthInfoBaseResponse,
|
|
AxiosError
|
|
>({
|
|
mutationFn: () => authApi.refresh(),
|
|
onSuccess: (data) => onSuccessCallback(data.data),
|
|
onError: () => {
|
|
clearAuthAndLogout()
|
|
onErrorCallback()
|
|
},
|
|
})
|
|
}
|
|
export const useLogout = (onSuccess: () => void) => {
|
|
const mutate = useGetUrlLoginMutation()
|
|
return useMutation<BaseAPIResponse, AxiosError, LogoutRequest>({
|
|
mutationFn: (body) => authApi.logout(body),
|
|
onSuccess: () => {
|
|
onSuccess()
|
|
mutate.mutate()
|
|
},
|
|
})
|
|
}
|
|
|
|
export const useTokenRefresh = () => {
|
|
const router = useRouter()
|
|
const refreshToken = useCallback(async (): Promise<AuthInfoBaseResponse> => {
|
|
const savedAuthInfoLocal = localStorage.getItem(AUTH_INFO_KEY)
|
|
if (!savedAuthInfoLocal) {
|
|
throw new Error('Cannot find auth information to refresh')
|
|
}
|
|
const authInfo = JSON.parse(savedAuthInfoLocal)
|
|
|
|
try {
|
|
const newAuthInfo = await authApi.refresh()
|
|
const newAuthInfoFull = AuthInfoFullSchema.parse({
|
|
...newAuthInfo.data,
|
|
user_info: authInfo.user_info,
|
|
})
|
|
localStorage.setItem(AUTH_INFO_KEY, JSON.stringify(newAuthInfoFull))
|
|
return newAuthInfo
|
|
} catch (e) {
|
|
if (
|
|
e instanceof AxiosError &&
|
|
e.response?.status === 401 &&
|
|
typeof window !== 'undefined'
|
|
) {
|
|
window.dispatchEvent(new CustomEvent(AUTH_REFRESH_FAILED_EVENT))
|
|
}
|
|
throw e
|
|
}
|
|
}, [])
|
|
|
|
const refreshTokenWithRetry = useCallback(
|
|
async (onSuccess?: () => void) => {
|
|
try {
|
|
await refreshToken()
|
|
onSuccess?.()
|
|
} catch (refreshError) {
|
|
clearAuthAndLogout()
|
|
throw refreshError
|
|
}
|
|
},
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
[router]
|
|
)
|
|
|
|
return {
|
|
refreshToken,
|
|
refreshTokenWithRetry,
|
|
clearAuthAndLogout,
|
|
}
|
|
}
|