From ceb7fe144034c460771b330dfd07ab515cd94575 Mon Sep 17 00:00:00 2001 From: solufa Date: Tue, 17 Sep 2024 09:12:53 +0900 Subject: [PATCH] fix: add accessToken to cookie --- client/features/auth/AuthLoader.tsx | 8 +++++--- client/public/docs/openapi.json | 8 ++++++-- server/api/private/hooks.ts | 4 ++-- server/api/session/controller.ts | 16 +++++++++++----- server/api/session/index.ts | 2 +- server/service/app.ts | 4 ++-- server/service/constants.ts | 2 +- server/tests/api/apiClient.ts | 7 +++++-- server/tests/api/public.test.ts | 20 ++++++++++++++------ 9 files changed, 47 insertions(+), 24 deletions(-) diff --git a/client/features/auth/AuthLoader.tsx b/client/features/auth/AuthLoader.tsx index 20920ec..7051e0c 100644 --- a/client/features/auth/AuthLoader.tsx +++ b/client/features/auth/AuthLoader.tsx @@ -13,10 +13,12 @@ export const AuthLoader = () => { const { setLoading } = useLoading(); const { setAlert } = useAlert(); const updateCookie = useCallback(async () => { - const jwt = await fetchAuthSession().then((e) => e.tokens?.idToken?.toString()); + const tokens = await fetchAuthSession().then((e) => e.tokens); - if (jwt !== undefined) { - await apiClient.session.$post({ body: { jwt } }); + if (tokens !== undefined && tokens.idToken !== undefined) { + await apiClient.session.$post({ + body: { idToken: tokens.idToken.toString(), accessToken: tokens.accessToken.toString() }, + }); await apiClient.private.me.$get().then(setUser); } else { setUser(null); diff --git a/client/public/docs/openapi.json b/client/public/docs/openapi.json index 3947747..9ac8c99 100644 --- a/client/public/docs/openapi.json +++ b/client/public/docs/openapi.json @@ -692,12 +692,16 @@ "schema": { "type": "object", "properties": { - "jwt": { + "idToken": { + "type": "string" + }, + "accessToken": { "type": "string" } }, "required": [ - "jwt" + "accessToken", + "idToken" ] } } diff --git a/server/api/private/hooks.ts b/server/api/private/hooks.ts index aa52605..c80a0e1 100644 --- a/server/api/private/hooks.ts +++ b/server/api/private/hooks.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import type { UserDto } from 'common/types/user'; import { userUseCase } from 'domain/user/useCase/userUseCase'; -import { COOKIE_NAME } from 'service/constants'; +import { COOKIE_NAMES } from 'service/constants'; import type { JwtUser } from 'service/types'; import { defineHooks } from './$relay'; @@ -12,7 +12,7 @@ export default defineHooks(() => ({ req.user = await req .jwtVerify({ onlyCookie: true }) .then((jwtUser) => { - const token = req.cookies[COOKIE_NAME]; + const token = req.cookies[COOKIE_NAMES.accessToken]; assert(token); return userUseCase.findOrCreateUser(jwtUser, token); diff --git a/server/api/session/controller.ts b/server/api/session/controller.ts index 65f1909..6d33ff9 100644 --- a/server/api/session/controller.ts +++ b/server/api/session/controller.ts @@ -1,6 +1,6 @@ import type { CookieSerializeOptions } from '@fastify/cookie'; import assert from 'assert'; -import { COOKIE_NAME } from 'service/constants'; +import { COOKIE_NAMES } from 'service/constants'; import { z } from 'zod'; import type { Methods } from '.'; import { defineController } from './$relay'; @@ -18,7 +18,7 @@ const options: CookieSerializeOptions = { export default defineController((fastify) => ({ post: { - validators: { body: z.object({ jwt: z.string() }) }, + validators: { body: z.object({ idToken: z.string(), accessToken: z.string() }) }, hooks: { preHandler: (req, reply, done) => { assert(req.body); @@ -26,9 +26,14 @@ export default defineController((fastify) => ({ const decoded = z .object({ payload: z.object({ exp: z.number() }).passthrough() }) .passthrough() - .parse(fastify.jwt.decode(req.body.jwt)); + .parse(fastify.jwt.decode(req.body.idToken)); - reply.setCookie(COOKIE_NAME, req.body.jwt, { + reply.setCookie(COOKIE_NAMES.idToken, req.body.idToken, { + ...options, + expires: new Date(decoded.payload.exp * 1000), + }); + + reply.setCookie(COOKIE_NAMES.accessToken, req.body.accessToken, { ...options, expires: new Date(decoded.payload.exp * 1000), }); @@ -41,7 +46,8 @@ export default defineController((fastify) => ({ delete: { hooks: { preHandler: (_, reply, done) => { - reply.clearCookie(COOKIE_NAME, options); + reply.clearCookie(COOKIE_NAMES.idToken, options); + reply.clearCookie(COOKIE_NAMES.accessToken, options); done(); }, }, diff --git a/server/api/session/index.ts b/server/api/session/index.ts index be1b654..1e52bde 100644 --- a/server/api/session/index.ts +++ b/server/api/session/index.ts @@ -2,7 +2,7 @@ import type { DefineMethods } from 'aspida'; export type Methods = DefineMethods<{ post: { - reqBody: { jwt: string }; + reqBody: { idToken: string; accessToken: string }; resBody: { status: 'success' }; }; delete: { diff --git a/server/service/app.ts b/server/service/app.ts index 94ce890..77e4e9b 100644 --- a/server/service/app.ts +++ b/server/service/app.ts @@ -10,7 +10,7 @@ import type { FastifyInstance, FastifyRequest } from 'fastify'; import Fastify from 'fastify'; import buildGetJwks from 'get-jwks'; import server from '../$server'; -import { COOKIE_NAME } from './constants'; +import { COOKIE_NAMES } from './constants'; import { CustomError } from './customAssert'; import { API_BASE_PATH, @@ -29,7 +29,7 @@ export const init = (): FastifyInstance => { fastify.register(cookie); fastify.register(fastifyJwt, { - cookie: { cookieName: COOKIE_NAME, signed: false }, + cookie: { cookieName: COOKIE_NAMES.idToken, signed: false }, decode: { complete: true }, secret: (_: FastifyRequest, token: TokenOrHeader) => { assert('header' in token); diff --git a/server/service/constants.ts b/server/service/constants.ts index f7c1868..b81d011 100644 --- a/server/service/constants.ts +++ b/server/service/constants.ts @@ -1 +1 @@ -export const COOKIE_NAME = 'session'; +export const COOKIE_NAMES = { idToken: 'idToken', accessToken: 'accessToken' }; diff --git a/server/tests/api/apiClient.ts b/server/tests/api/apiClient.ts index cd94635..265c751 100644 --- a/server/tests/api/apiClient.ts +++ b/server/tests/api/apiClient.ts @@ -5,7 +5,7 @@ import { } from '@aws-sdk/client-cognito-identity-provider'; import api from 'api/$api'; import axios from 'axios'; -import { COOKIE_NAME } from 'service/constants'; +import { COOKIE_NAMES } from 'service/constants'; import { API_BASE_PATH, COGNITO_USER_POOL_CLIENT_ID, @@ -47,7 +47,10 @@ export const createSessionClients = async (option?: { const cookie = await cognitoClient .send(command2) - .then((res) => `${COOKIE_NAME}=${res.AuthenticationResult?.IdToken}`); + .then( + (res) => + `${COOKIE_NAMES.idToken}=${res.AuthenticationResult?.IdToken};${COOKIE_NAMES.accessToken}=${res.AuthenticationResult?.AccessToken}`, + ); const agent = axios.create({ baseURL, headers: { cookie, 'Content-Type': 'text/plain' } }); diff --git a/server/tests/api/public.test.ts b/server/tests/api/public.test.ts index b56993a..11fa852 100644 --- a/server/tests/api/public.test.ts +++ b/server/tests/api/public.test.ts @@ -1,5 +1,6 @@ import { createSigner } from 'fast-jwt'; -import { COOKIE_NAME } from 'service/constants'; +import { COOKIE_NAMES } from 'service/constants'; +import { ulid } from 'ulid'; import { expect, test } from 'vitest'; import { createSessionClients, noCookieClient } from './apiClient'; import { DELETE, GET, POST } from './utils'; @@ -21,10 +22,16 @@ test(GET(noCookieClient.health), async () => { }); test(POST(noCookieClient.session), async () => { - const jwt = createSigner({ key: 'dummy' })({ exp: Math.floor(Date.now() / 1000) + 100 }); - const res = await noCookieClient.session.post({ body: { jwt } }); - - expect(res.headers['set-cookie'][0].startsWith(`${COOKIE_NAME}=${jwt};`)).toBeTruthy(); + const idToken = createSigner({ key: 'dummy' })({ exp: Math.floor(Date.now() / 1000) + 100 }); + const accessToken = ulid(); + const res = await noCookieClient.session.post({ body: { idToken, accessToken } }); + + expect( + res.headers['set-cookie'][0].startsWith(`${COOKIE_NAMES.idToken}=${idToken};`), + ).toBeTruthy(); + expect( + res.headers['set-cookie'][1].startsWith(`${COOKIE_NAMES.accessToken}=${accessToken};`), + ).toBeTruthy(); expect(res.body.status === 'success').toBeTruthy(); }); @@ -32,6 +39,7 @@ test(DELETE(noCookieClient.session), async () => { const apiClient = await createSessionClients(); const res = await apiClient.session.delete(); - expect(res.headers['set-cookie'][0].startsWith(`${COOKIE_NAME}=;`)).toBeTruthy(); + expect(res.headers['set-cookie'][0].startsWith(`${COOKIE_NAMES.idToken}=;`)).toBeTruthy(); + expect(res.headers['set-cookie'][1].startsWith(`${COOKIE_NAMES.accessToken}=;`)).toBeTruthy(); expect(res.body.status === 'success').toBeTruthy(); });