Skip to content

feat(api): 인증 기본 흐름 구현 - #63

Merged
ehlung merged 15 commits into
developfrom
feature/api-auth-core
Jun 23, 2026
Merged

feat(api): 인증 기본 흐름 구현#63
ehlung merged 15 commits into
developfrom
feature/api-auth-core

Conversation

@meteorqz6

@meteorqz6 meteorqz6 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

작업 내용

  • Prisma migration 및 seed 추가
  • Express error/validation 공통 구조 추가
  • Auth signup/login/logout/me 구현
  • JWT access token, refresh token cookie, token_hash/revoked_at 처리
  • API 문서와 OpenAPI auth 응답 갱신

검증

  • Postman signup/login/logout/me 확인
  • pnpm --filter @fragment/shared build
  • pnpm --filter @fragment/database build
  • pnpm --filter @fragment/api build
  • pnpm --filter @fragment/api lint

Closes #62

Summary by CodeRabbit

릴리스 노트

  • New Features
    • 회원가입/로그인/로그아웃/토큰 갱신/내 정보 조회 API 제공
    • JWT 액세스 토큰 도입 및 응답에 accessToken 포함
    • 리프레시 토큰 쿠키 기반 세션 지원(프로덕션 보안 옵션 적용) 및 크로스오리진 제어
    • Zod 기반 요청 검증과 통합 오류 응답(404/HTTP/서버 오류)
  • Bug Fixes
    • 인증/조직 권한 실패 및 토큰 검증 실패 시 오류 전달 방식·메시지 정비
  • Documentation
    • API 문서 및 OpenAPI 인증 응답/에러 코드(INTERNAL_SERVER_ERROR) 갱신
  • Chores
    • 환경 변수 템플릿 확장, DB 초기 마이그레이션/시드 반영

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

초기 Prisma ERD 마이그레이션(11개 테이블·인덱스·외래키)과 시드 데이터를 추가하고, JWT 기반 액세스 토큰 및 리프레시 토큰 유틸을 구현한다. 환경변수 및 공유 스키마에 토큰과 에러 코드를 확장한다. HttpError 클래스, errorHandler, validate 미들웨어를 통해 일관된 에러 처리 파이프라인을 수립한다. signup, login, logout, refresh, getMe 서비스 로직과 Express 핸들러를 연결하고, 리프레시 토큰 쿠키 유틸로 보안 저장소를 구현한다. 기존 require-auth·require-organization 미들웨어를 HttpError 기반으로 업그레이드하고 동일 출처 검증 미들웨어를 추가한다. 앱에 미들웨어와 라우팅을 등록하며, 패키지 의존성을 추가하고 API 문서 및 스키마를 갱신하여 API 계약을 반영한다.

Changes

JWT 인증 API 구현 및 초기 DB 스키마

Layer / File(s) Summary
초기 ERD 마이그레이션 및 시드 데이터
packages/database/prisma/migrations/20260622134729_init_erd/migration.sql, packages/database/prisma/migrations/migration_lock.toml, packages/database/prisma/seed.ts, packages/database/package.json
PostgreSQL 초기 마이그레이션으로 ENUM 2종(DayOfWeek, ScheduleStatus)과 11개 테이블(users, refresh_tokens, organizations, workers 등)을 생성하고 유니크 인덱스·복합 인덱스·외래키(CASCADE, SET NULL)를 설정한다. 시드 스크립트는 관리자 사용자(bcryptjs 12라운드 해시), 조직(프래그먼트 카페), 평일/토요일 영업시간(09:0022:00), 3명의 근무자(W001W003), 스케줄 계획 기간(2026-06-22~07-05), 요일별 최소 인원 규칙(MON/FRI/SAT)을 upsert/createMany로 구성한다.
JWT·리프레시 토큰 유틸 및 환경 설정
.env.example, apps/api/src/modules/auth/auth.tokens.ts, packages/shared/src/schemas/auth.ts, packages/shared/src/schemas/common.ts
액세스 토큰 생성(JWT sub에 사용자 ID 문자열 저장, 기본 만료 15m)·검증(jwt.verifyBigInt 변환), 리프레시 토큰 생성(base64url randomBytes(64)), SHA-256 해시, 만료 계산(기본 7일) 함수를 구현한다. 환경변수에 JWT_ACCESS_SECRET, JWT_ACCESS_EXPIRES_IN, REFRESH_TOKEN_EXPIRES_DAYS를 추가하고, 공유 authSessionResponseSchemaaccessToken 필드, errorCodeSchemaINTERNAL_SERVER_ERROR 값을 추가한다.
HttpError 클래스, 에러 핸들러 및 validate 미들웨어
apps/api/src/errors/http-error.ts, apps/api/src/middlewares/error-handler.ts, apps/api/src/middlewares/validate.ts
HttpError(statusCode, code, message) 클래스와 isHttpError 타입 가드를 정의한다. errorHandlerHttpError 여부에 따라 (1) 해당 상태 코드 및 code/message JSON 또는 (2) 500 상태의 INTERNAL_SERVER_ERROR 고정 메시지로 응답한다. validate 미들웨어는 Zod safeParsereq.body/query/params를 검증해 실패 시 400 VALIDATION_ERROR 에러를 next()로 전달한다.
인증 서비스 핵심 로직
apps/api/src/modules/auth/auth.service.ts
createAuthSession 헬퍼가 액세스·리프레시 토큰 생성, 리프레시 토큰 해시 후 DB 저장, 조직 존재 여부 조회를 수행한다. signup은 이메일 중복 체크(409), bcryptjs 해시(12라운드) 후 사용자 생성, 유니크 제약 위반(P2002)도 409으로 변환. login은 이메일 조회 실패(401), 비밀번호 비교 실패(401)를 검증. logout은 리프레시 토큰 미제공(401), 미존재/revoked/만료 여부 확인(401) 후 revokedAt 갱신. refreshSession은 리프레시 토큰 검증 후 새 세션 생성. getMe는 사용자·조직 조회(미존재 시 401).
리프레시 토큰 쿠키 유틸, 인증 핸들러 및 라우트
apps/api/src/modules/auth/auth.cookies.ts, apps/api/src/modules/auth/auth.handlers.ts, apps/api/src/routes/auth.routes.ts
리프레시 토큰 쿠키(httpOnly: true, path: "/", 프로덕션에서 secure: truesameSite: "none", 개발환경에서 sameSite: "lax")를 설정·삭제하는 헬퍼를 정의한다. signupHandler(201)·loginHandler(200)은 서비스 호출 후 리프레시 토큰 쿠키 설정 및 JSON 응답, logoutHandler(204)는 쿠키 제거, refreshHandler(200)는 쿠키 토큰 재검증 및 재설정, meHandler(200)는 req.user 확인 후 결과 반환. /signup·/loginvalidate 미들웨어로 Zod 검증 후 핸들러, /logout·/refresh·/me는 핸들러 직접 연결.
기존 인증 미들웨어 HttpError 업그레이드 및 동일 출처 검증
apps/api/src/middlewares/require-auth.ts, apps/api/src/middlewares/require-organization.ts, apps/api/src/middlewares/require-same-origin.ts
require-auth는 Bearer 토큰 미제공(401, "인증이 필요합니다.") 및 검증 실패(401, "유효하지 않은 인증 토큰입니다.") 시 HttpErrornext()로 전달한다. require-organization은 인증 미존재(401)와 조직 미존재(403, ORGANIZATION_REQUIRED) 시 HttpError를 직접 생성해 전달한다. createRequireSameOriginOrigin 헤더 및 Referer fallback으로 요청 출처를 검증하고, 허용 출처와 일치하지 않으면 403 Forbidden HttpError를 발생시킨다.
앱 와이어링, 패키지 의존성, 스키마 및 API 문서 갱신
apps/api/src/app.ts, apps/api/package.json, apps/api/src/routes/index.ts, docs/openapi.yaml, docs/API.md
createAppcookieParser(), /auth 라우트 마운트, 404 HttpError 응답, errorHandler를 순서대로 등록한다. 프로덕션 환경에서 WEB_APP_ORIGIN 필수 검증 후 CORS 설정(credentials: true)을 수행한다. package.jsonbcryptjs, cookie-parser, jsonwebtoken, zod 및 타입 패키지를 추가하고 기존 typescript-eslint 항목은 제거한다. OpenAPI 스키마의 AuthSessionResponseaccessToken 필드(string, 필수)를 추가하고, 에러 응답에 INTERNAL_SERVER_ERROR 코드를 추가하며 API 응답 예시에 반영한다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60분

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 핵심 변경 사항인 인증 기본 흐름 구현을 명확하게 나타내며, 간결하고 구체적입니다.
Description check ✅ Passed 작업 내용, 검증 방법, 이슈 참조가 포함되었으나, 템플릿의 '개요', '변경 유형', '영향 범위', '체크리스트' 등 구조화된 섹션을 완전히 따르지 않았습니다.
Linked Issues check ✅ Passed PR은 #62의 모든 핵심 요구사항을 충족합니다: Prisma 마이그레이션/시드, Express 공통 error/validation 구조, Auth signup/login/logout/me 구현, JWT/refresh token 처리, Postman 검증, 빌드/린트 통과.
Out of Scope Changes check ✅ Passed POST /auth/refresh 엔드포인트가 원래 #62 스코프에 명시되지 않았으나, PR 목표 문서에서 팀 합의로 포함되었음이 확인되며, 나머지 변경은 모두 #62의 요구사항 범위 내입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/api-auth-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@meteorqz6 meteorqz6 changed the title API 인증 기본 흐름 구현 feat(api): 인증 기본 흐름 구현 Jun 23, 2026
@meteorqz6 meteorqz6 self-assigned this Jun 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/app.ts`:
- Line 29: The errorHandler middleware registered at the app.use(errorHandler)
call is returning error responses in an incorrect format that doesn't match the
API contract. The handler is currently returning responses with a nested
structure like { error: { code, message } }, but the API contract requires {
statusCode, errorCode, message } format. Modify the errorHandler function to
restructure the error response to match the required format by mapping the
response fields appropriately so that statusCode, errorCode, and message are at
the top level of the response object instead of nested under an error property.
- Around line 19-23: The cors() middleware at the beginning of the app
configuration is using default settings which do not allow credentials (cookies)
in cross-origin requests. Since the application uses HTTP-only cookie-based
refresh token authentication with separate deployments for Web (Vercel) and API
(Railway), you need to configure the cors() call to explicitly enable
credentials and whitelist the production web origin. Replace the cors() call
with a configuration object that sets credentials to true and specifies the
allowed origin from your production Vercel deployment environment variable or
configuration.

In `@apps/api/src/middlewares/error-handler.ts`:
- Around line 8-24: The error response format in the error-handler middleware is
inconsistent with the project API contract standard. In the isHttpError
conditional block where res.status() is called and in the generic 500 error
response block, change the JSON response structure from the nested format {
error: { code, message } } to the standard format { statusCode, errorCode,
message }. Specifically, map error.statusCode to the statusCode field,
error.code to the errorCode field, and keep error.message as the message field.
Apply this same restructuring to both the HttpError case and the generic server
error case to ensure all error responses follow the required contract format.

In `@apps/api/src/modules/auth/auth.cookies.ts`:
- Around line 5-10: The refreshTokenCookieOptions object currently uses a fixed
sameSite value of "lax" which can cause refresh token cookies to not be sent on
cross-site requests in production when the frontend (Vercel) makes requests to
the API (Railway). Modify the sameSite property to conditionally vary based on
NODE_ENV: set it to "none" for production environments, and keep it as "lax" for
development. Additionally, ensure that when sameSite is "none", the secure flag
is explicitly set to true to comply with browser requirements for SameSite=None
cookies.

In `@apps/api/src/modules/auth/auth.service.ts`:
- Line 69: The error codes used in the auth.service.ts file such as
EMAIL_ALREADY_EXISTS, INVALID_CREDENTIALS, and REFRESH_TOKEN_INVALID do not
conform to the project's standard error code contract. Replace these custom
error codes with the standardized constants defined in
common/constants/error-codes.ts. Import the error code constants from the
common/constants/error-codes.ts file and update all instances where non-standard
error codes are used (the HttpError throw statements at lines 69, 91-92, 97-98,
105-106, and 114-115) to use the appropriate standard error code constants
instead of hardcoded string values.
- Around line 64-80: The email uniqueness check has a race condition where
concurrent requests can bypass the findUnique check and hit a database unique
constraint error that propagates as 500 instead of 409. Wrap the
prisma.user.create call in a try-catch block to handle Prisma unique constraint
violations on the email field, and throw an HttpError with 409 status code and
DUPLICATE_EMAIL error code when the email unique constraint is violated,
ensuring consistent error handling at the API layer regardless of timing.

In `@apps/api/src/modules/auth/auth.tokens.ts`:
- Around line 54-60: The getRefreshTokenExpiresAt function does not validate the
REFRESH_TOKEN_EXPIRES_DAYS value after converting it to a number, which can
result in NaN or negative values that break the expiration date calculation. Add
validation after the Number conversion to ensure the days variable is a positive
integer, and throw an error or return a default value if validation fails. This
prevents invalid expiration dates from being silently calculated with corrupted
values.
- Line 6: The DEFAULT_REFRESH_TOKEN_EXPIRES_DAYS constant in the auth.tokens.ts
file is set to 14 days, but according to the JWT token policy guidelines, the
refresh token expiration should be 7 days. Change the value of
DEFAULT_REFRESH_TOKEN_EXPIRES_DAYS from 14 to 7 to align with the established
security policy for refresh token validity.

In `@apps/api/src/routes/auth.routes.ts`:
- Around line 35-36: The POST /auth/refresh endpoint is missing from the auth
router despite being documented in docs/openapi.yaml and docs/API.md, causing a
contract mismatch. Add a new route handler for POST /auth/refresh in the
authRoutes definition (following the same pattern as logoutHandler and
meHandler) that accepts the refresh token from the request cookie and returns
renewed access and refresh tokens. Ensure this route is added at the same level
as the existing logout and me routes to maintain consistency.

In `@packages/database/prisma/seed.ts`:
- Line 174: The seed completion log in the console.info call is outputting the
plaintext password from SEED_USER.password, which exposes credentials to log
repositories and CI artifacts. Remove the password interpolation from the log
message and only log the email address or other non-sensitive information to
complete the seed operation safely.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0a16609b-0037-4d57-8365-58e79958193f

📥 Commits

Reviewing files that changed from the base of the PR and between 67c41c5 and 5189a53.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • .env.example
  • apps/api/package.json
  • apps/api/src/app.ts
  • apps/api/src/errors/http-error.ts
  • apps/api/src/middlewares/error-handler.ts
  • apps/api/src/middlewares/validate.ts
  • apps/api/src/modules/auth/auth.cookies.ts
  • apps/api/src/modules/auth/auth.handlers.ts
  • apps/api/src/modules/auth/auth.service.ts
  • apps/api/src/modules/auth/auth.tokens.ts
  • apps/api/src/routes/auth.routes.ts
  • docs/API.md
  • docs/openapi.yaml
  • packages/database/package.json
  • packages/database/prisma/migrations/20260622134729_init_erd/migration.sql
  • packages/database/prisma/migrations/migration_lock.toml
  • packages/database/prisma/seed.ts
  • packages/shared/src/schemas/auth.ts

Comment thread apps/api/src/app.ts Outdated
Comment thread apps/api/src/app.ts
Comment thread apps/api/src/middlewares/error-handler.ts
Comment thread apps/api/src/modules/auth/auth.cookies.ts
Comment thread apps/api/src/modules/auth/auth.service.ts Outdated
Comment thread apps/api/src/modules/auth/auth.service.ts Outdated
Comment thread apps/api/src/modules/auth/auth.tokens.ts Outdated
Comment thread apps/api/src/modules/auth/auth.tokens.ts
Comment thread apps/api/src/routes/auth.routes.ts Outdated
Comment thread packages/database/prisma/seed.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/shared/src/schemas/common.ts`:
- Line 45: The ErrorResponse enum in docs/openapi.yaml is missing the
INTERNAL_SERVER_ERROR value that is defined in the errorCodeSchema in
packages/shared/src/schemas/common.ts and returned by the error handler
middleware. Update the ErrorResponse enum in docs/openapi.yaml (around lines
261-275) to include INTERNAL_SERVER_ERROR so that the OpenAPI documentation
accurately reflects all error codes that can be returned by the API
implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a224e435-5d35-40aa-96b1-d5105d65af5f

📥 Commits

Reviewing files that changed from the base of the PR and between 5189a53 and debc680.

📒 Files selected for processing (9)
  • .env.example
  • apps/api/src/app.ts
  • apps/api/src/middlewares/error-handler.ts
  • apps/api/src/modules/auth/auth.cookies.ts
  • apps/api/src/modules/auth/auth.service.ts
  • apps/api/src/modules/auth/auth.tokens.ts
  • docs/API.md
  • packages/database/prisma/seed.ts
  • packages/shared/src/schemas/common.ts

Comment thread packages/shared/src/schemas/common.ts
@ehlung

ehlung commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

A+B 통합 정리로 develop의 guard/utils 작업을 auth 브랜치에 병합했고, 에러 처리는 HttpError + ERROR_CODES 기준으로 통일했습니다.

추가로 CodeRabbit이 언급한 POST /auth/refresh는 현재 PR의 기존 구현 범위(signup/login/logout/me)를 넘어서는 refresh token rotation 정책 결정이 필요하므로, 팀 내 합의 후 이번 PR에 포함할지 별도 작업으로 분리할지 결정하겠습니다.

@ehlung

ehlung commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

추가 마무리 작업 반영했습니다.

  • refresh token rotation 정리: 새 세션 발급 시 기존 활성 refresh token revoke 처리
  • GET /auth/me를 공통 requireAuth 미들웨어 기반으로 정리
  • POST /auth/refresh 구현 및 refresh cookie 재발급 처리
  • API base path를 문서 계약과 맞춰 /api prefix 구조로 정리

검증:

  • pnpm --filter @fragment/shared build
  • pnpm --filter @fragment/database build
  • pnpm --filter @fragment/api build
  • pnpm --filter @fragment/api lint

@ehlung ehlung self-assigned this Jun 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/modules/auth/auth.service.ts`:
- Around line 41-58: The refresh token rotation has a race condition where
concurrent requests with the same token can both pass validation and commit,
causing the later request to revoke the earlier request's newly created token.
Refactor the transaction at lines 41-58 to atomically validate and consume the
token in a single operation instead of reading validity separately beforehand.
Modify the updateMany call for refreshToken to include conditions for revokedAt:
null and expiresAt greater than current time, then capture the count of updated
records. Only proceed with creating the new refresh token within the same
transaction if the update count equals 1, ensuring that exactly one matching
token was found and revoked.

In `@apps/api/src/routes/auth.routes.ts`:
- Line 40: The `/refresh` POST route at line 40 with the refreshHandler is
vulnerable to CSRF attacks because it uses cookies for session rotation with
SameSite=None and CORS credentials enabled. Add CSRF protection by implementing
Origin/Referer validation in the common middleware before the refreshHandler
middleware chain. The validation should check that the incoming Origin or
Referer header matches the WEB_APP_ORIGIN environment variable, or alternatively
implement double-submit CSRF token validation by checking a CSRF token from
request headers against a token stored in cookies. Apply this validation
middleware to the refreshHandler to prevent cross-site requests from triggering
token rotation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 60cb5019-f221-4b72-b6c5-b1d23a9277b7

📥 Commits

Reviewing files that changed from the base of the PR and between 740ea39 and 63a36be.

📒 Files selected for processing (5)
  • apps/api/src/app.ts
  • apps/api/src/modules/auth/auth.handlers.ts
  • apps/api/src/modules/auth/auth.service.ts
  • apps/api/src/routes/auth.routes.ts
  • apps/api/src/routes/index.ts

Comment thread apps/api/src/modules/auth/auth.service.ts Outdated
Comment thread apps/api/src/routes/auth.routes.ts Outdated
@ehlung

ehlung commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

CodeRabbit 리뷰 대응 추가 반영했습니다.

  • refresh token rotation을 조건부 consume 방식으로 정리해 동시 refresh 요청에서 한 요청만 성공하도록 보완
  • logout도 동일한 consumeRefreshToken 흐름을 사용하도록 정리
  • cookie 기반 logout/refresh 요청에 Origin/Referer 검증 미들웨어 추가
  • WEB_APP_ORIGIN 비교 기준을 URL origin으로 정규화

검증:

  • pnpm --filter @fragment/shared build
  • pnpm --filter @fragment/database build
  • pnpm --filter @fragment/api build
  • pnpm --filter @fragment/api lint

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/middlewares/require-same-origin.ts`:
- Around line 31-38: The issue is that the current code treats the parsing
failure of a malicious header like "Origin: null" the same as a completely
missing header. You need to differentiate between the two cases in the
require-same-origin middleware. Modify the logic so that if the Origin or
Referer header is actually present as a string in req.headers, you attempt to
parse it with getHeaderOrigin, and if parsing fails or the result doesn't match
the normalizedAllowedOrigin, you reject the request (call next with an error or
deny it). Only when both Origin and Referer headers are completely absent should
you allow the request to pass through (for Postman/curl clients). The key change
is checking for header existence first before allowing any pass-through.

In `@apps/api/src/modules/auth/auth.service.ts`:
- Around line 160-175: The logout function is not idempotent because
consumeRefreshToken throws an error when the token is already expired or
revoked, preventing the cookie cleanup from completing. To fix this, modify the
logout function to handle the case where the refresh token has already been
consumed or is expired gracefully. Either wrap the consumeRefreshToken call in a
try-catch block and allow the logout to continue even if the token is already
consumed, or check the token's expiration and consumption state before calling
consumeRefreshToken, ensuring the function completes successfully and allows the
calling handler to clear the cookie regardless of the token's current state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 48c57b4a-f2ea-4631-b495-54aaf9a0eb39

📥 Commits

Reviewing files that changed from the base of the PR and between 63a36be and 89d4ef4.

📒 Files selected for processing (3)
  • apps/api/src/middlewares/require-same-origin.ts
  • apps/api/src/modules/auth/auth.service.ts
  • apps/api/src/routes/auth.routes.ts

Comment thread apps/api/src/middlewares/require-same-origin.ts
Comment on lines +160 to +175
export async function logout(prisma: PrismaClient, refreshToken?: string) {
if (!refreshToken) {
throw createInvalidRefreshTokenError();
}

const tokenHash = hashRefreshToken(refreshToken);
const storedToken = await prisma.refreshToken.findUnique({
where: { tokenHash },
});

if (!storedToken) {
throw createInvalidRefreshTokenError();
}

await consumeRefreshToken(prisma, storedToken.id, new Date());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

만료·폐기된 refresh 토큰으로 로그아웃하면 쿠키가 정리되지 않습니다.

consumeRefreshTokencount !== 1이면 401을 던집니다. 토큰이 이미 만료(7일 경과)되었거나 회전으로 폐기된 상태에서 로그아웃을 호출하면, logoutHandlerclearRefreshTokenCookie에 도달하기 전에 401로 빠져 무효 쿠키가 그대로 남습니다. 로그아웃은 멱등하게 처리해 항상 쿠키가 비워지도록 하는 편이 안전합니다.

보완 예시
   const tokenHash = hashRefreshToken(refreshToken);
   const storedToken = await prisma.refreshToken.findUnique({
     where: { tokenHash },
   });

-  if (!storedToken) {
-    throw createInvalidRefreshTokenError();
-  }
-
-  await consumeRefreshToken(prisma, storedToken.id, new Date());
+  if (!storedToken) {
+    return;
+  }
+
+  await prisma.refreshToken.updateMany({
+    where: { id: storedToken.id, revokedAt: null },
+    data: { revokedAt: new Date() },
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/modules/auth/auth.service.ts` around lines 160 - 175, The logout
function is not idempotent because consumeRefreshToken throws an error when the
token is already expired or revoked, preventing the cookie cleanup from
completing. To fix this, modify the logout function to handle the case where the
refresh token has already been consumed or is expired gracefully. Either wrap
the consumeRefreshToken call in a try-catch block and allow the logout to
continue even if the token is already consumed, or check the token's expiration
and consumption state before calling consumeRefreshToken, ensuring the function
completes successfully and allows the calling handler to clear the cookie
regardless of the token's current state.

@ehlung

ehlung commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

추가 리뷰 보완 반영했습니다.

  • logout은 refresh rotation용 consume 로직에서 분리해 기존 유효성 검증 후 revoke 흐름으로 되돌림
  • same-origin 검사에서 Origin/Referer 헤더 존재 여부와 파싱 실패를 구분하도록 보완
  • Origin/Referer 없는 요청 허용은 로컬 Postman/curl 테스트용 임시 정책임을 TODO 주석으로 명시

검증:

  • pnpm --filter @fragment/shared build
  • pnpm --filter @fragment/database build
  • pnpm --filter @fragment/api build
  • pnpm --filter @fragment/api lint

@ehlung
ehlung merged commit 0f8c4e4 into develop Jun 23, 2026
2 checks passed
@ehlung
ehlung deleted the feature/api-auth-core branch June 23, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(api): 인증 기본 흐름 구현

2 participants