Clearer RefreshTokenError reasons and login page message.
This commit is contained in:
parent
57355fae1f
commit
90feb0e963
@ -4,7 +4,7 @@ import { useRouter } from '@tanstack/react-router'
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { ApiError } from './api/ApiError'
|
import { ApiError } from './api/ApiError'
|
||||||
import ExpiredTokenError from './api/ExpiredTokenError'
|
import ExpiredTokenError from './api/ExpiredTokenError'
|
||||||
import refresh, { ExpiredRefreshTokenError } from './api/refresh'
|
import refresh, { RefreshTokenError } from './api/refresh'
|
||||||
import LoginView from './api/types/LoginView'
|
import LoginView from './api/types/LoginView'
|
||||||
import { useAuth } from './auth'
|
import { useAuth } from './auth'
|
||||||
|
|
||||||
@ -21,14 +21,14 @@ const AuthAwareQueryClientProvider = ({ children }: React.PropsWithChildren) =>
|
|||||||
try {
|
try {
|
||||||
refreshResult = await refresh()
|
refreshResult = await refresh()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ExpiredRefreshTokenError) {
|
if (error instanceof RefreshTokenError) {
|
||||||
console.log('refresh-token expired')
|
console.log(`RefreshTokenError: ${error.reason}`)
|
||||||
setCurrentlyRefreshing(false)
|
setCurrentlyRefreshing(false)
|
||||||
clearToken()
|
clearToken()
|
||||||
await router.navigate({
|
await router.navigate({
|
||||||
to: '/login',
|
to: '/login',
|
||||||
search: {
|
search: {
|
||||||
expired: true,
|
reason: error.reason,
|
||||||
redirect: router.state.location.href
|
redirect: router.state.location.href
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import { ApiError } from './ApiError'
|
import { ApiError } from './ApiError'
|
||||||
|
import LoginExceptionView from './types/LoginExceptionView'
|
||||||
import LoginView, { RawLoginView } from './types/LoginView'
|
import LoginView, { RawLoginView } from './types/LoginView'
|
||||||
|
|
||||||
export class ExpiredRefreshTokenError extends ApiError {
|
export type RefreshTokenErrorReason = 'INVALID_REFRESH_TOKEN' | 'EXPIRED_REFRESH_TOKEN' | 'NO_REFRESH_TOKEN'
|
||||||
constructor() {
|
|
||||||
super(401, 'Expired refresh token.')
|
export class RefreshTokenError extends ApiError {
|
||||||
Object.setPrototypeOf(this, ExpiredRefreshTokenError.prototype)
|
constructor(public reason: RefreshTokenErrorReason) {
|
||||||
|
super(401, 'Refresh token error.')
|
||||||
|
Object.setPrototypeOf(this, RefreshTokenError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +34,8 @@ const refresh = async (): Promise<LoginView> => {
|
|||||||
expires: new Date(rawExpires)
|
expires: new Date(rawExpires)
|
||||||
}
|
}
|
||||||
} else if (response.status === 401) {
|
} else if (response.status === 401) {
|
||||||
throw new ExpiredRefreshTokenError()
|
const { reason } = (await response.json()) as LoginExceptionView
|
||||||
|
throw new RefreshTokenError(reason as RefreshTokenErrorReason)
|
||||||
} else {
|
} else {
|
||||||
throw new ApiError(response.status, response.statusText)
|
throw new ApiError(response.status, response.statusText)
|
||||||
}
|
}
|
||||||
|
6
src/api/types/LoginExceptionView.ts
Normal file
6
src/api/types/LoginExceptionView.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
interface LoginExceptionView {
|
||||||
|
reason: 'INVALID_CREDENTIALS' | 'INVALID_REFRESH_TOKEN' | 'EXPIRED_REFRESH_TOKEN' | 'NO_REFRESH_TOKEN'
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoginExceptionView
|
@ -10,7 +10,7 @@ const Login = () => {
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { redirect, expired } = useSearch({ from: '/login' })
|
const { redirect, reason } = useSearch({ from: '/login' })
|
||||||
|
|
||||||
const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
@ -31,10 +31,19 @@ const Login = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let message: string | null = null
|
||||||
|
if (reason !== undefined) {
|
||||||
|
if (reason === 'INVALID_REFRESH_TOKEN' || reason === 'EXPIRED_REFRESH_TOKEN') {
|
||||||
|
message = 'Your session has expired. Please login again.'
|
||||||
|
} else {
|
||||||
|
message = 'Please login to view this page.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>Login Page</h2>
|
<h2>Login Page</h2>
|
||||||
{expired ? <p>Your session has expired. Please login again.</p> : null}
|
{message !== null ? <p>{message}</p> : null}
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<label htmlFor="username">Username</label>
|
<label htmlFor="username">Username</label>
|
||||||
<input id="username" name="username" type="text" />
|
<input id="username" name="username" type="text" />
|
||||||
@ -52,11 +61,11 @@ const Login = () => {
|
|||||||
|
|
||||||
export const Route = createFileRoute('/login')({
|
export const Route = createFileRoute('/login')({
|
||||||
validateSearch: z.object({
|
validateSearch: z.object({
|
||||||
expired: z.boolean().optional().catch(false),
|
reason: z.enum(['INVALID_REFRESH_TOKEN', 'EXPIRED_REFRESH_TOKEN', 'NO_REFRESH_TOKEN']).optional(),
|
||||||
redirect: z.string().optional().catch('')
|
redirect: z.string().optional().catch('')
|
||||||
}),
|
}),
|
||||||
beforeLoad({ context, search }) {
|
beforeLoad({ context, search }) {
|
||||||
if (!(search.expired || context.auth.token === null)) {
|
if (!(search.reason !== undefined || context.auth.token === null)) {
|
||||||
throw redirect({ to: '/recipes' })
|
throw redirect({ to: '/recipes' })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user