diff --git a/apps/api/src/db/schema.ts b/apps/api/src/db/schema.ts index 26fb295..7222172 100644 --- a/apps/api/src/db/schema.ts +++ b/apps/api/src/db/schema.ts @@ -7,10 +7,6 @@ export const users = sqliteTable('users', { userImage: text('user_image'), createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(), updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(), - accessToken: text('access_token'), - refreshToken: text('refresh_token'), - tokenType: text('token_type'), - expireAt: integer('expire_at', { mode: 'timestamp_ms' }), }, (table) => { return { idxUserId: uniqueIndex('idx_users_user_id').on(table.userId), diff --git a/apps/api/src/services/auth/v1/route.ts b/apps/api/src/services/auth/v1/route.ts index b371c36..1b5ab8b 100644 --- a/apps/api/src/services/auth/v1/route.ts +++ b/apps/api/src/services/auth/v1/route.ts @@ -58,9 +58,12 @@ type SessionPayload = PrefixRoot; + +type SecuredSessionPayload = PrefixRoot; @@ -110,7 +113,7 @@ const withPrevUrl: MiddlewareHandler<{ const stateCookie = getCookie(c, 'state'); if (stateCookie) { - const jwt = await jose.compactDecrypt(getCookie(c, 'state')!, c.var.privateKey); + const jwt = await jose.compactDecrypt(stateCookie, c.var.privateKey); const payload = await verifyToken(c, jwt.plaintext); prevUrl = payload['http:cheda.kr/state'].url; @@ -176,18 +179,23 @@ const withSession: MiddlewareHandler<{ try { const sessionId = getCookie(c, 'session_id'); - if (!sessionId) throw new InvalidToken(); + const sessionSid = getCookie(c, 'session_sid'); + if (!sessionId || !sessionSid) throw new InvalidToken(); const payload = await verifyToken(c, sessionId); - const user = payload['http:cheda.kr/user']; + let user = payload['http:cheda.kr/user']; const threshold = 1000 * 60 * 10; - if (new Date(user.expireAt).getTime() <= Date.now() + threshold) { + if (new Date(payload.exp! * 1000).getTime() <= Date.now() + threshold) { + const securedToken = await decryptToken(c, sessionSid); + const securedPayload = await verifyToken(c, securedToken); + const { refreshToken } = securedPayload['http:cheda.kr/user']; + const url = new URL('https://nid.naver.com/oauth2.0/token'); url.searchParams.append('grant_type', 'refresh_token'); url.searchParams.append('client_id', c.env.OAUTH_CLIENT_ID_NAVER); url.searchParams.append('client_secret', c.env.OAUTH_CLIENT_SECRET_NAVER); - url.searchParams.append('refresh_token', user.refreshToken); + url.searchParams.append('refresh_token', refreshToken); const response = await fetch(url); const result = await response.json() as RefreshTokenResponse; @@ -201,24 +209,23 @@ const withSession: MiddlewareHandler<{ fetch('https://openapi.naver.com/v1/nid/verify?info=true', { headers }).then(r => r.json()) as Promise ]); - const userPatch = { + user = { + userId: meResult.response.id, userName: meResult.response.nickname, userImage: meResult.response.profile_image, accessToken: result.access_token, - tokenType: result.token_type, - expireAt: new Date(verifyResult.response.expire_date), - updatedAt: new Date(), }; + const expires = new Date(verifyResult.response.expire_date); - const db = drizzle(c.env.DB); - await db.update(usersTable) - .set(userPatch) - .where(eq(usersTable.userId, meResult.response.id)); - - const expires = new Date(Date.now() + parseInt(result.expires_in) * 1000); - const jwt = await signToken(c, { ...user, ...userPatch }, expires); + const session = await signToken( + c, + prefixRoot(JWT_PREFIX, { + user, + }) satisfies SessionPayload, + expires + ); - setCookie(c, 'session_id', jwt, { + setCookie(c, 'session_id', session, { expires, ...c.env.DEV ? {} : { secure: true, @@ -226,7 +233,7 @@ const withSession: MiddlewareHandler<{ }, }); } - c.set('session', { user: payload['http:cheda.kr/user'] }); + c.set('session', { user }); } catch (e) { if (e instanceof InvalidToken) { deleteCookie(c, 'session_id'); @@ -263,10 +270,6 @@ app.get('/logout', withPrevUrl, async (c) => { if (!sessionId) { return c.redirect(c.var.prevUrl); } - - const payload = await verifyToken(c, sessionId); - const user = payload['http:cheda.kr/user']; - /* const url = new URL('https://nid.naver.com/oauth2.0/token'); url.searchParams.append('grant_type', 'delete'); @@ -279,15 +282,6 @@ app.get('/logout', withPrevUrl, async (c) => { const result = await response.json() as DeleteTokenRespone; */ - const db = drizzle(c.env.DB); - await db.update(usersTable) - .set({ - accessToken: null, - expireAt: null, - updatedAt: new Date(), - }) - .where(eq(usersTable.userId, user.userId)); - deleteCookie(c, 'session_id'); return c.redirect(c.var.prevUrl); @@ -377,52 +371,75 @@ app.get('/callback', async (c) => { fetch('https://openapi.naver.com/v1/nid/verify?info=true', { headers }).then(r => r.json()) as Promise ]); - const db = drizzle(c.env.DB); - + const now = new Date(); const user = { userId: meResult.response.id, userName: meResult.response.nickname, userImage: meResult.response.profile_image, - createdAt: new Date(), - updatedAt: new Date(), + createdAt: now, + updatedAt: now, accessToken: result.access_token, refreshToken: result.refresh_token, tokenType: result.token_type, - expireAt: new Date(verifyResult.response.expire_date), }; + const expires = new Date(verifyResult.response.expire_date); + const db = drizzle(c.env.DB); try { await db.insert(usersTable) - .values(user); + .values({ + userId: meResult.response.id, + userName: meResult.response.nickname, + userImage: meResult.response.profile_image, + createdAt: now, + updatedAt: now, + }); } catch (e) { await db.update(usersTable) .set({ + userName: meResult.response.nickname, + userImage: meResult.response.profile_image, + updatedAt: now, + }) + .where(eq(usersTable.userId, user.userId)); + } + + const session = await signToken( + c, + prefixRoot(JWT_PREFIX, { + user: { + userId: user.userId, userName: user.userName, userImage: user.userImage, accessToken: user.accessToken, + }, + }) satisfies SessionPayload, + expires + ); + + const weekLater = new Date(Date.now() + 1000 * 60 * 60 * 24 * 7); + let securedSession = await signToken( + c, + prefixRoot(JWT_PREFIX, { + user: { refreshToken: user.refreshToken, - tokenType: user.tokenType, - expireAt: user.expireAt, - updatedAt: user.updatedAt, - }) - .where(eq(usersTable.userId, user.userId)); - } + }, + }) satisfies SecuredSessionPayload, + weekLater, + ); + securedSession = await encryptToken(c, securedSession); - const payload = prefixRoot(JWT_PREFIX, { - user: { - userId: user.userId, - userName: user.userName, - userImage: user.userImage, - accessToken: user.accessToken, - expireAt: user.expireAt, - updatedAt: user.updatedAt, + setCookie(c, 'session_id', session, { + expires, + ...c.env.DEV ? {} : { + secure: true, + domain: '.cheda.kr', }, }); - const jwt = await signToken(c, payload, user.expireAt); - - setCookie(c, 'session_id', jwt, { - expires: new Date(Date.now() + parseInt(result.expires_in) * 1000), + setCookie(c, 'session_sid', securedSession, { + httpOnly: true, + expires, ...c.env.DEV ? {} : { secure: true, domain: '.cheda.kr',