forked from woorifisa-projects-3rd/Quostomize-FE
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauth.js
216 lines (190 loc) · 6.01 KB
/
auth.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import NextAuth from "next-auth"
import Credentials from "next-auth/providers/credentials"
import { redirect } from "next/dist/server/api-utils";
// Redis 또는 다른 저장소를 사용하는 것이 좋지만, 임시로 Map을 사용
// Singleton 패턴으로 토큰 갱신 상태 관리
class TokenRefreshManager {
static instance;
refreshPromise;
lastRefreshTime;
constructor() {}
static getInstance() {
if (!TokenRefreshManager.instance) {
TokenRefreshManager.instance = new TokenRefreshManager();
}
return TokenRefreshManager.instance;
}
async refreshToken(token) {
const currentTime = Date.now();
// 이미 진행 중인 리프레시가 있다면 해당 Promise 반환
if (this.refreshPromise) {
return this.refreshPromise;
}
// 마지막 리프레시로부터 5초 이내라면 현재 토큰 반환
if (currentTime - this.lastRefreshTime < 5000) {
return token;
}
this.refreshPromise = this.doRefresh(token);
try {
const result = await this.refreshPromise;
this.lastRefreshTime = Date.now();
return result;
} finally {
this.refreshPromise = null;
}
}
async doRefresh(token) {
try {
const response = await fetch(
`${process.env.SERVER_URL}/v1/api/auth/reissue`,
{
method: "POST",
headers: {
"Content-type": "application/json",
Cookie: `refreshToken=${token.refreshToken}`
},
cache: "no-store",
credentials: "include"
}
);
if (!response.ok) {
await signOut({
redirect: true,
redirectTo: "/login"
})
return;
}
const result = await response.json();
const setCookie = response.headers.get("set-cookie")?.split(";");
if (!setCookie) throw new Error('No cookie in response');
const newRefreshToken = setCookie[0].split("=")[1];
const expires = setCookie[2].split("=")[1];
const path = setCookie[3].split("=")[1];
return {
...token,
accessToken: result.accessToken,
refreshToken: newRefreshToken,
accessExpires: Date.now() + 1800000,
refreshExpires: expires,
path: path,
};
} catch (error) {
console.error('Token refresh failed:', error);
await signOut({
redirect: true,
redirectTo: "/login"
})
return;
}
}
}
export const authConfig = {
providers: [
Credentials({
credentials: {
memberLoginId: { label: "아이디", type: "text", placeholder: "아이디를 입력해주세요." },
memberPassword: { label: "비밀번호", type: "text" },
},
async authorize(credentials, req) {
const response = await fetch(
`${process.env.SERVER_URL}/login`,
{
method: "POST",
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({
memberLoginId: credentials.memberLoginId,
memberPassword: credentials.memberPassword
})
}
);
if (response.status >= 400) {
throw new Error("아이디, 비밀번호를 확인해주세요.")
}
const accessToken = response.headers.get("accessToken");
const setCookie = response.headers.get("set-cookie").split(";");
const refreshToken = setCookie[0].split("=")[1];
const expires = setCookie[2].split("=")[1];
const path = setCookie[3].split("=")[1];
const result = await response.json();
const memberId = result.memberId;
const memberRole = result.memberRole;
const cardStatus = result.cardStatus;
const memberName = result.memberName;
const traceId = result.traceId;
const user = {
id: memberId,
name: memberName,
role: memberRole,
cardStatus: cardStatus,
traceId: traceId,
accessToken: accessToken,
refreshToken: refreshToken,
accessExpires: new Date().valueOf() + 1800000,
refreshExpires: expires,
path: path,
}
return user;
}
})
],
secret: process.env.AUTH_SECRET,
pages: {
signIn: '/login', // 명시적으로 로그인 페이지 경로 지정
},
callbacks: {
async jwt({ token, account, user }) {
if (account && user) {
return {
...token,
memberId: user.id,
memberName: user.memberName,
memberRole: user.role,
cardStatus: user.cardStatus,
traceId : user.traceId,
accessToken: user.accessToken,
refreshToken: user.refreshToken,
accessExpires: user.accessExpires,
refreshExpires: user.refreshExpires,
path: user.path,
};
}
// 토큰 만료 시간 체크
const currentTime = Date.now();
if (token.accessExpires && currentTime + 120000 < token.accessExpires) {
return token;
}
// 토큰 리프레시 매니저를 통한 갱신
try {
return await TokenRefreshManager.getInstance().refreshToken(token);
} catch (error) {
await signOut({
redirectTo: "login",
redirect: true
})
return {
...token,
error: 'RefreshAccessTokenError',
};
}
},
async session({ session, token }) {
if (token) {
session.memberId = token.memberId;
session.memberName = token.memberName;
session.memberRole = token.memberRole;
session.cardStatus = token.cardStatus;
session.traceId = token.traceId;
session.accessToken = token.accessToken;
session.refreshToken = token.refreshToken;
session.accessExpires = token.accessExpires;
session.refreshExpires = token.refreshExpires;
session.path = token.path;
session.error = token.error;
}
return session;
},
},
};
export const { handlers, signIn, signOut, auth } = NextAuth(authConfig);