diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d70de2..36363f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2018,8 +2018,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001743: - resolution: {integrity: sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==} + caniuse-lite@1.0.30001745: + resolution: {integrity: sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -2183,8 +2183,8 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - detect-libc@2.1.0: - resolution: {integrity: sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==} + detect-libc@2.1.1: + resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} engines: {node: '>=8'} detect-node-es@1.1.0: @@ -5420,7 +5420,7 @@ snapshots: autoprefixer@10.4.21(postcss@8.5.6): dependencies: browserslist: 4.26.2 - caniuse-lite: 1.0.30001743 + caniuse-lite: 1.0.30001745 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -5465,7 +5465,7 @@ snapshots: browserslist@4.26.2: dependencies: baseline-browser-mapping: 2.8.6 - caniuse-lite: 1.0.30001743 + caniuse-lite: 1.0.30001745 electron-to-chromium: 1.5.223 node-releases: 2.0.21 update-browserslist-db: 1.1.3(browserslist@4.26.2) @@ -5495,7 +5495,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001743: {} + caniuse-lite@1.0.30001745: {} chalk@4.1.2: dependencies: @@ -5662,7 +5662,7 @@ snapshots: delayed-stream@1.0.0: {} - detect-libc@2.1.0: + detect-libc@2.1.1: optional: true detect-node-es@1.1.0: {} @@ -6505,7 +6505,7 @@ snapshots: '@swc/counter': 0.1.3 '@swc/helpers': 0.5.15 busboy: 1.6.0 - caniuse-lite: 1.0.30001743 + caniuse-lite: 1.0.30001745 postcss: 8.4.31 react: 19.1.1 react-dom: 19.1.1(react@19.1.1) @@ -6888,7 +6888,7 @@ snapshots: sharp@0.33.5: dependencies: color: 4.2.3 - detect-libc: 2.1.0 + detect-libc: 2.1.1 semver: 7.7.2 optionalDependencies: '@img/sharp-darwin-arm64': 0.33.5 diff --git a/src/app/api/auth/signin/route.ts b/src/app/api/auth/signin/route.ts index cb2a658..2acfabe 100644 --- a/src/app/api/auth/signin/route.ts +++ b/src/app/api/auth/signin/route.ts @@ -42,7 +42,7 @@ export async function POST(request: NextRequest) { sameSite: 'strict', }); - res.cookies.set('role', RoleEncryption({ role }), { + res.cookies.set('role', await RoleEncryption({ role }), { httpOnly: false, secure: process.env.NODE_ENV === 'production', expires: accessTokenExpires, diff --git a/src/app/api/reservation/machine/[machineId]/history/route.ts b/src/app/api/reservation/machine/[machineId]/history/route.ts index 4559858..851576b 100644 --- a/src/app/api/reservation/machine/[machineId]/history/route.ts +++ b/src/app/api/reservation/machine/[machineId]/history/route.ts @@ -3,9 +3,9 @@ import { NextRequest, NextResponse } from 'next/server'; export async function GET( request: NextRequest, - { params }: { params: { machineId: string } } + context: { params: { machineId: string } } ) { - const { machineId } = params; + const { machineId } = context.params; const accessToken = request.cookies.get('accessToken')?.value; const headers = accessToken ? { Authorization: `Bearer ${accessToken}` } diff --git a/src/app/my-page/page.tsx b/src/app/my-page/page.tsx index da4ef5d..d5477d5 100644 --- a/src/app/my-page/page.tsx +++ b/src/app/my-page/page.tsx @@ -153,7 +153,7 @@ export default function MyPage() { try { const response = await machineApi.getDevices(); - console.log("Machine status response:", response); + if (response.status === 200 && response.data) { const { washer, dryer } = response.data; const allMachines = [...washer, ...dryer]; diff --git a/src/middleware.ts b/src/middleware.ts index 5622446..51810e7 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -2,15 +2,15 @@ import RoleEncryption from '@/shared/lib/role-encryption'; import { NextRequest, NextResponse } from 'next/server'; export async function middleware(request: NextRequest) { - const accessToken = request.cookies.get('accessToken')?.value; - const refreshToken = request.cookies.get('refreshToken')?.value; + try { + const accessToken = request.cookies.get('accessToken')?.value; + const refreshToken = request.cookies.get('refreshToken')?.value; - if (!refreshToken) { - return NextResponse.redirect(new URL('/login', request.url)); - } + if (!refreshToken) { + return NextResponse.redirect(new URL('/login', request.url)); + } - if (!accessToken) { - try { + if (!accessToken) { const response = await fetch( `${process.env.NEXT_PUBLIC_BASE_URL}/auth/reissue`, { @@ -36,21 +36,11 @@ export async function middleware(request: NextRequest) { const accessTokenExpires = new Date(`${accessTokenExpiredAt}+09:00`); const refreshTokenExpires = new Date(`${refreshTokenExpiredAt}+09:00`); - if ( - isNaN(accessTokenExpires.getTime()) || - isNaN(refreshTokenExpires.getTime()) - ) { - console.warn( - 'Invalid expiration dates, continuing with existing session' - ); - return NextResponse.next(); - } - const nextResponse = NextResponse.next(); nextResponse.cookies.set('accessToken', newAccessToken, { httpOnly: true, - secure: process.env.NODE_ENV === 'production', + secure: !!process.env.NODE_ENV, expires: accessTokenExpires, sameSite: 'strict', path: '/', @@ -58,38 +48,39 @@ export async function middleware(request: NextRequest) { nextResponse.cookies.set('refreshToken', newRefreshToken, { httpOnly: true, - secure: process.env.NODE_ENV === 'production', + secure: !!process.env.NODE_ENV, expires: refreshTokenExpires, sameSite: 'strict', path: '/', }); - nextResponse.cookies.set('role', RoleEncryption({ role }), { + nextResponse.cookies.set('role', await RoleEncryption({ role }), { httpOnly: false, - secure: process.env.NODE_ENV === 'production', + secure: !!process.env.NODE_ENV, expires: accessTokenExpires, sameSite: 'strict', path: '/', }); - return nextResponse; - } else if (!data.success) { - const response = NextResponse.redirect(new URL('/login', request.url)); - response.cookies.delete('accessToken'); - response.cookies.delete('refreshToken'); - response.cookies.delete('role'); + nextResponse.cookies.set('preRole', role, { + httpOnly: false, + secure: !!process.env.NODE_ENV, + expires: accessTokenExpires, + sameSite: 'strict', + path: '/', + }); - return response; + return nextResponse; } - } catch (err) { - const response = NextResponse.redirect(new URL('/login', request.url)); - response.cookies.delete('accessToken'); - response.cookies.delete('refreshToken'); - response.cookies.delete('role'); - - return response; - console.log('Token refresh error, continuing with existing session'); } + } catch (err) { + const response = NextResponse.redirect(new URL('/login', request.url)); + response.cookies.delete('accessToken'); + response.cookies.delete('refreshToken'); + response.cookies.delete('role'); + response.cookies.delete('preRole'); + + return response; } return NextResponse.next(); @@ -97,6 +88,7 @@ export async function middleware(request: NextRequest) { export const config = { matcher: [ - '/((?!api/auth|login|register|forgot-password|_next/static|_next/image|favicon.ico|.*\\..*).*)', + '/((?!api|login|register|forgot-password|_next/static|_next/image|favicon.ico|.*\\..*).*)', ], + }; diff --git a/src/shared/lib/get-key.ts b/src/shared/lib/get-key.ts new file mode 100644 index 0000000..b3ce8a2 --- /dev/null +++ b/src/shared/lib/get-key.ts @@ -0,0 +1,17 @@ +export default async function GetKey() { + const textEncoder = new TextEncoder(); + + const raw = textEncoder.encode(process.env.ROLE_KEY!); + + const hashed = await crypto.subtle.digest('SHA-256', raw); + + return crypto.subtle.importKey( + 'raw', + hashed, + { + name: 'AES-CBC', + }, + false, + ['encrypt', 'decrypt'] + ); +} diff --git a/src/shared/lib/getRole.ts b/src/shared/lib/get-role.ts similarity index 100% rename from src/shared/lib/getRole.ts rename to src/shared/lib/get-role.ts diff --git a/src/shared/lib/reservation-store.ts b/src/shared/lib/reservation-store.ts index 2b44851..0411380 100644 --- a/src/shared/lib/reservation-store.ts +++ b/src/shared/lib/reservation-store.ts @@ -15,7 +15,7 @@ import { } from "./api-client"; import { MachineOperatingState } from "./machine-status"; import axios from "axios"; -import { getRole } from "./getRole"; +import { getRole } from "./get-role"; export type FloorType = 3 | 4 | 5; export type MachineType = "washing" | "dryer"; diff --git a/src/shared/lib/role-decryption.ts b/src/shared/lib/role-decryption.ts index dfd783b..4d08508 100644 --- a/src/shared/lib/role-decryption.ts +++ b/src/shared/lib/role-decryption.ts @@ -1,13 +1,23 @@ import { privateDecrypt } from "crypto"; import { UserRole } from "./auth-utils"; +import GetKey from "./get-key"; interface Props { role: string; } export default async function RoleDecryption({ role }: Props) { - return privateDecrypt( - process.env.ROLE_PRIVATE_KEY ?? "", - Buffer.from(role, "base64") - ).toString() as UserRole; + const [iv, cipher] = role.split("."); + const key = await GetKey(); + const textDecoder = new TextDecoder(); + + const decrypted = await crypto.subtle.decrypt( + { + name: "AES-CBC", + iv: Uint8Array.from(Buffer.from(iv, "base64")) + }, + key, + Uint8Array.from(Buffer.from(cipher, "base64")) + ); + return textDecoder.decode(decrypted) as UserRole; } diff --git a/src/shared/lib/role-encryption.ts b/src/shared/lib/role-encryption.ts index c5d4b5a..79eca7c 100644 --- a/src/shared/lib/role-encryption.ts +++ b/src/shared/lib/role-encryption.ts @@ -1,13 +1,24 @@ import { UserRole } from "./auth-utils"; -import { publicEncrypt } from "crypto"; +import GetKey from "./get-key"; interface Props { role: UserRole; } -export default function RoleEncryption({ role }: Props) { - return publicEncrypt( - process.env.ROLE_PUBLIC_KEY ?? "", - Buffer.from(role) - ).toString("base64"); +export default async function RoleEncryption({ role }: Props) { + const key = await GetKey(); + const textEncoder = new TextEncoder(); + const iv = crypto.getRandomValues(new Uint8Array(16)); + + const encodeRole = textEncoder.encode(role); + const cipher = await crypto.subtle.encrypt( + { + name: "AES-CBC", + iv + }, + key, + encodeRole + ); + + return Buffer.from(iv).toString("base64") + "." + Buffer.from(new Uint8Array(cipher)).toString("base64"); }