Skip to content

Commit 72d47d3

Browse files
committed
feat: refresh token
1 parent dba54c2 commit 72d47d3

File tree

15 files changed

+135
-57
lines changed

15 files changed

+135
-57
lines changed

client/src/app.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { AppLayout, AuthLayout, SettingLayout } from '@/components/layouts';
1919
import { TChannelQuery } from './types/channel';
2020
import { queryStringToObject } from './utils';
2121
import { queryChannelsOption } from './lib/query-options/channel';
22+
import { ROUTES } from './constants';
2223

2324
export const router = createBrowserRouter([
2425
{
@@ -41,20 +42,20 @@ export const router = createBrowserRouter([
4142
Component: AuthLayout,
4243
children: [
4344
{
44-
path: '/login',
45+
path: ROUTES.AUTH.LOGIN,
4546
Component: Login,
4647
index: true,
4748
},
4849
{
49-
path: '/register',
50+
path: ROUTES.AUTH.REGISTER,
5051
Component: Register,
5152
},
5253
{
53-
path: '/forgot-password',
54+
path: ROUTES.AUTH.FORGOT_PASS,
5455
Component: ForgotPassword,
5556
},
5657
{
57-
path: '/set-password',
58+
path: ROUTES.AUTH.RESET_PASS,
5859
Component: SetPassword,
5960
},
6061
],
@@ -71,7 +72,7 @@ export const router = createBrowserRouter([
7172
});
7273

7374
if (!user) {
74-
return redirect(`/login?${url.toString()}`);
75+
return redirect(`${ROUTES.AUTH.LOGIN}?${url.toString()}`);
7576
}
7677

7778
useUserStore.getState().setUser(user);
@@ -80,14 +81,14 @@ export const router = createBrowserRouter([
8081
},
8182
children: [
8283
{
83-
path: '/dashboard',
84+
path: ROUTES.PRIVATE.DASHBOARD,
8485
index: true,
8586
loader: async () => {
86-
return redirect('/chatbots');
87+
return redirect(ROUTES.PRIVATE.CHAT_BOT.INDEX);
8788
},
8889
},
8990
{
90-
path: '/chatbots',
91+
path: ROUTES.PRIVATE.CHAT_BOT.INDEX,
9192
element: <div>a</div>,
9293
loader: () => {
9394
useAppLayoutStore
@@ -98,7 +99,7 @@ export const router = createBrowserRouter([
9899
},
99100
},
100101
{
101-
path: '/channels',
102+
path: ROUTES.PRIVATE.CHANNEL.INDEX,
102103
Component: Channels,
103104
loader: async ({ request }) => {
104105
const query: TChannelQuery = queryStringToObject(
@@ -117,7 +118,7 @@ export const router = createBrowserRouter([
117118
},
118119
},
119120
{
120-
path: '/settings',
121+
path: ROUTES.PRIVATE.SETTING.INDEX,
121122
Component: SettingLayout,
122123
loader: async () => {
123124
const data =
@@ -132,25 +133,25 @@ export const router = createBrowserRouter([
132133
},
133134
children: [
134135
{
135-
path: 'mail',
136+
path: ROUTES.PRIVATE.SETTING.MAIL,
136137
Component: Mail,
137138
},
138139
{
139-
path: 'profiles',
140+
path: ROUTES.PRIVATE.SETTING.PROFILES,
140141
Component: Profiles,
141142
},
142143
{
143144
index: true,
144145
loader: async () => {
145-
return redirect('/settings/profiles');
146+
return redirect(ROUTES.PRIVATE.SETTING.PROFILES);
146147
},
147148
},
148149
],
149150
},
150151
],
151152
},
152153
{
153-
path: '/',
154+
path: ROUTES.PUBLIC.LANDING_PAGE,
154155
element: <div>Hi</div>,
155156
},
156157
],

client/src/components/forms/channel.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ const ChannelForm = ({
4141
const form = useForm<TChannelInput>({
4242
resolver: zodResolver(schema),
4343
mode: 'onChange',
44-
defaultValues,
44+
defaultValues: {
45+
active: true,
46+
...defaultValues,
47+
},
4548
});
4649
const { data: types } = useQuery(queryChannelTypesOption);
4750

client/src/components/forms/setting-mail.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ export const SettingMailForm = ({
6161
<Input
6262
{...field}
6363
placeholder={t('email.placeholder')}
64+
autoComplete="one-time-code"
6465
/>
6566
</FormControl>
6667
<FormMessage />
@@ -80,6 +81,7 @@ export const SettingMailForm = ({
8081
{...field}
8182
placeholder={t('password.placeholder')}
8283
type="password"
84+
autoComplete="one-time-code"
8385
/>
8486
</FormControl>
8587
<FormMessage />

client/src/constants/index.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,31 @@ export const ENDPOINTS = {
3131
TYPES: '/channel/types',
3232
},
3333
};
34+
35+
export const ROUTES = {
36+
AUTH: {
37+
LOGIN: '/login',
38+
REGISTER: '/register',
39+
FORGOT_PASS: '/forgot-password',
40+
RESET_PASS: '/set-password',
41+
},
42+
PUBLIC: {
43+
LANDING_PAGE: '/',
44+
},
45+
PRIVATE: {
46+
DASHBOARD: '/dashboard',
47+
CHANNEL: {
48+
INDEX: '/channels',
49+
CREATE: '/channels/create',
50+
EDIT: '/channels/edit',
51+
},
52+
CHAT_BOT: {
53+
INDEX: '/chatbots',
54+
},
55+
SETTING: {
56+
INDEX: '/settings',
57+
MAIL: '/settings/email',
58+
PROFILES: '/settings/profiles',
59+
},
60+
},
61+
};

client/src/lib/http-client.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { auth } from '@/apis/auth';
2+
import { ROUTES } from '@/constants';
13
import { ELang } from '@/types/share';
24
import axios from 'axios';
35

@@ -27,7 +29,36 @@ http_client.interceptors.response.use(
2729
function (response) {
2830
return response.data;
2931
},
30-
function (error) {
32+
async function (error) {
33+
const originalRequest = error.config;
34+
35+
if (
36+
Object.values(ROUTES.AUTH).some((route) =>
37+
window.location.pathname.includes(route)
38+
)
39+
) {
40+
return Promise.reject(error);
41+
}
42+
43+
if (error.response?.status === 401 && !originalRequest._retry) {
44+
try {
45+
originalRequest._retry = true;
46+
47+
const tokens = await auth.refreshToken().then((r) => {
48+
return r.data;
49+
});
50+
51+
http_client.defaults.headers.common['Authorization'] =
52+
`Bearer ${tokens?.accessToken}`;
53+
54+
return http_client(originalRequest);
55+
} catch (error) {
56+
const prevHref = window.location.href;
57+
window.location.href = `/login?redirect=${prevHref}`;
58+
return Promise.reject(error);
59+
}
60+
}
61+
3162
return Promise.reject(error);
3263
}
3364
);

client/src/lib/schema/channel.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { channelApi } from '@/apis/channel';
22
import { ChannelType } from '@/types/channel';
3+
import { useQuery } from '@tanstack/react-query';
34
import { useTranslation } from 'react-i18next';
45
import * as z from 'zod';
6+
import { queryChannelTypesOption } from '../query-options/channel';
57

68
export const useChannelSchema = () => {
79
const { t } = useTranslation('forms');
10+
const { data: types } = useQuery(queryChannelTypesOption);
811

912
return z
1013
.object({
@@ -33,9 +36,11 @@ export const useChannelSchema = () => {
3336
})
3437
.superRefine(async (data, ctx) => {
3538
if (data.credentials) {
36-
const type = await channelApi
37-
.getChannelType(data.channelTypeId)
38-
.then((r) => r.data);
39+
const type = types?.find((type) => type.id === data.channelTypeId);
40+
41+
if (!type) {
42+
return;
43+
}
3944

4045
switch (type.name) {
4146
case ChannelType.MESSENGER:

server/src/constants/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
export const MIN_PASSWORD_LENGTH = 8;
22
export const MAX_PASSWORD_LENGTH = 32;
33
export const MAX_ID_LENGTH = 36;
4-
export const TIME_EXPIRED_REFRESH_TOKEN = 60 * 60 * 24 * 30;
5-
export const TIME_EXPIRED_RESET_PASSWORD_TOKEN = 60 * 60 * 24;
6-
export const TIME_EXPIRED_ACCESS_TOKEN = 60 * 60 * 24;
4+
export const TIME_EXPIRED_REFRESH_TOKEN = 60 * 60 * 24 * 7; // 7 days
5+
export const TIME_EXPIRED_RESET_PASSWORD_TOKEN = 60 * 60 * 2; // 2 hours
6+
export const TIME_EXPIRED_ACCESS_TOKEN = 60;
77

88
// QUEUE KEY
99
export const QUEUE_KEYS = {

server/src/controllers/auth.controller.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,7 @@ export class AuthController {
100100
});
101101

102102
public logout = catchAsync(async (req: RequestWithUser, res) => {
103-
104-
await this.authService.logout(req.user?.id as string)
103+
await this.authService.logout(req.user?.id as string);
105104

106105
this.clearTokensCookie(res);
107106

@@ -129,7 +128,7 @@ export class AuthController {
129128
};
130129

131130
private clearTokensCookie = (res: Response) => {
132-
res.clearCookie('access_token',);
131+
res.clearCookie('access_token');
133132
res.clearCookie('refresh_token');
134133
};
135134
}

server/src/middlewares/auth.middleware.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const getAuthorization = (req: Request): string | null => {
1818
return null;
1919
};
2020

21-
export const auth = async (
21+
export const authMiddleware = async (
2222
req: RequestWithUser,
2323
res: Response,
2424
next: NextFunction
@@ -28,8 +28,6 @@ export const auth = async (
2828
const authService = Container.get(AuthService);
2929
const Authorization = getAuthorization(req);
3030

31-
32-
3331
if (Authorization) {
3432
const { id } = (await authService.verifyToken(
3533
Authorization,
@@ -52,7 +50,7 @@ export const auth = async (
5250
} else {
5351
next(
5452
new HttpException(
55-
StatusCodes.BAD_REQUEST,
53+
StatusCodes.UNAUTHORIZED,
5654
'Token đăng nhập không tồn tại'
5755
)
5856
);

server/src/middlewares/decrypt.middleware.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { decrypt } from '@/utils/crypto';
2+
import { NextFunction, Request, Response } from 'express';
23

3-
export const decryptMiddleware = (req, res, next) => {
4+
export const decryptMiddleware = (
5+
req: Request,
6+
_res: Response,
7+
next: NextFunction
8+
) => {
49
if (req.body?.encrypted) {
510
req.body = JSON.parse(decrypt(req.body.encrypted));
611
}

0 commit comments

Comments
 (0)