Skip to content

Conversation

@dioo1461
Copy link
Contributor

@dioo1461 dioo1461 commented Mar 27, 2025

#️⃣ 연관된 이슈>

📝 작업 내용> 이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)

image

image

  • api 요청 및 응답을 가로채는 proxy 미들웨어를 작성하였습니다. 개괄적인 동작은 아래와 같습니다.
    • 요청 url에 discussionId가 포함되어 있는지 검사하고, 존재한다면 인코딩합니다.
    • 응답 body 객체에 discussionId 필드가 포함되어 있는지 검사하고, 존재한다면 디코딩합니다.
  • 인코딩 파이프라인은 다음과 같습니다.
    • discussionId → AES-GCM → url-safe base64 → ciphertext

🙏 여기는 꼭 봐주세요! > 리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

  • 환경 변수에 변경사항이 있습니다. 추후 디스코드에 올려두겠습니다!
  • strict mode 때문에 로그인 요청이 동시에 두 번 보내져서 종종 서버 에러가 발생합니다. main.tsx에서 StrictMode 해제 후 테스트해보시는 것을 권장드립니다.
  • AES 알고리즘이 암호화 시에 무작위 nonce 값을 사용하다 보니, 같은 id에 대해 출력값이 달라지는 문제가 있습니다. (물론 출력값이 달라도 디코딩하면 같은 id를 반환합니다)
    • 동일 입력 → 동일 출력 특성을 가진 암호화 알고리즘을 사용하면 해결되지만, 보안적으로 좀더 취약합니다. 암호문이 달라질 수 있다는 점이 UX에 문제가 될까요?

Summary by CodeRabbit

  • New Features

    • Improved error handling by redirecting users to a "Not Found" page when a requested resource is missing.
    • Added support for environment variable management and HTTP proxy middleware.
    • Enhanced security by encrypting and decrypting discussion IDs in URLs and API responses.
  • Bug Fixes

    • Ensured consistent handling of discussion IDs as strings across all relevant features and components.
  • Chores

    • Updated dependencies and configuration for improved environment management and build processes.
    • Updated .gitignore to exclude environment files from version control.
  • Documentation

    • Minor updates to maintain consistency and clarity in type definitions and component interfaces.

@coderabbitai
Copy link

coderabbitai bot commented Mar 27, 2025

Walkthrough

This update introduces a system-wide migration from numeric to string-based discussion IDs across all client and server modules. It also adds environment configuration, encryption utilities, and middleware for secure discussion ID handling in the server. New dependencies for environment management and HTTP proxying are included, with supporting utilities and middleware for proxying and transforming discussion IDs.

Changes

Files / Areas Change Summary
frontend/package.json, frontend/apps/server/package.json Added dotenv and http-proxy-middleware dependencies; updated formatting; removed one devDependency.
frontend/.gitignore Now ignores .env files.
frontend/apps/client/src/features/discussion/api/invitationApi.ts, .../api/keys.ts, .../api/mutations.ts, ... Changed all discussionId types from number to string in APIs, hooks, and related exports.
.../api/queries.ts, .../api/queryOptions.ts, .../ui/DiscussionInviteCard/SubmitForm.tsx, .../index.tsx Continued discussionId type migration to string in hooks, UI props, and interfaces.
.../model/index.ts, .../model/finishedSchedules.ts, .../model/ongoingSchedules.ts, .../model/upcomingSchedules.ts Changed schema fields for discussionId and id from number to string.
.../ui/OngoingSchedules/RecommendedSchedules.tsx, .../ui/OngoingSchedules/ScheduleDetails.tsx Updated component prop types for discussionId to string.
.../features/timeline-schedule/model/index.ts Changed discussionId property in schema to string.
frontend/apps/client/src/features/discussion/api/index.ts Refactored type imports and changed response parsing to use instance method.
frontend/apps/client/src/utils/error/HTTPError.ts, .../handleError.ts Added isNotFoundError method and enhanced error handling for 404 errors.
frontend/apps/server/src/utils/object.ts Added generic recursive object value manipulation utility.
frontend/apps/server/src/utils/stringTransformer.ts Added base64 URL-safe encode/decode utility.
frontend/apps/server/src/envconfig.ts Added environment variable loader and exported config object.
frontend/apps/server/src/utils/isProduction.ts Added utility for production environment detection.
frontend/apps/server/tsup.config.ts Enabled bundling and marked some modules as external.
frontend/apps/server/src/index.ts Added JSON body parsing and mounted new discussion ID middlewares.
frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts Added AES-GCM-based discussion ID encryption/decryption utilities.
frontend/apps/server/src/middlewares/discussionId/index.ts Added proxy middleware and request verifier for discussion ID handling.
frontend/apps/server/src/middlewares/discussionId/proxyHandlers.ts Added proxy request/response handlers for encrypting/decrypting discussion IDs in payloads.
frontend/apps/server/vitest.config.ts Removed Vitest configuration file.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server (Express)
    participant ProxyMiddleware
    participant Backend API

    Client->>Server (Express): HTTP request with encrypted discussionId
    Server (Express)->>ProxyMiddleware: Passes request to /api route
    ProxyMiddleware->>ProxyMiddleware: Decrypts discussionId in path/body
    ProxyMiddleware->>Backend API: Forwards request with decrypted discussionId
    Backend API-->>ProxyMiddleware: Responds with JSON (plain discussionId)
    ProxyMiddleware->>ProxyMiddleware: Encrypts discussionId in response
    ProxyMiddleware-->>Server (Express): Returns modified response
    Server (Express)-->>Client: Sends response with encrypted discussionId
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇
A stringy change for every ID,
Encryption dances secretly.
Proxies guard the server’s gate,
While dotenv whispers the config’s fate.
From schemas deep to UI bright,
The numbers vanish out of sight—
Secure and tidy, code’s delight!

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/fe/encode-discussion-id

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@dioo1461 dioo1461 marked this pull request as ready for review March 28, 2025 05:52
@dioo1461 dioo1461 requested a review from hamo-o as a code owner March 28, 2025 05:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (14)
frontend/server/middlewares/discussionIdTransformer/index.js (1)

1-14: Verify target URL for different environments

The middleware is well-structured, but the API target URL is hardcoded to http://localhost:8080/api. This might cause issues in different environments (staging, production).

Consider making the target URL configurable via environment variables to support deployment across different environments.

- target: 'http://localhost:8080/api',
+ target: process.env.API_URL || 'http://localhost:8080/api',
frontend/src/features/discussion/ui/DiscussionInviteCard/SubmitForm.tsx (1)

35-35: Redundant toString() conversion

Since discussionId is now a string, the toString() conversion is redundant and can be removed.

- navigate({ to: '/discussion/$id', params: { id: discussionId.toString() } });
+ navigate({ to: '/discussion/$id', params: { id: discussionId } });
frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (1)

32-32: Consider removing redundant toString() calls

Now that discussionId is typed as a string, the .toString() calls may be unnecessary. While they don't cause functional issues (they're safe to keep as a defensive measure), they could be removed for code cleanliness.

- const { discussion } = useDiscussionQuery(discussionId.toString());
+ const { discussion } = useDiscussionQuery(discussionId);

- <Link params={{ id: discussionId.toString() }} to='/discussion/$id'>
+ <Link params={{ id: discussionId }} to='/discussion/$id'>

Also applies to: 59-59

frontend/server/utils/object.js (1)

13-13: Consider handling non-string values more explicitly

While String() will convert most values safely, you might want to add more explicit handling for certain edge cases like undefined or complex objects that don't convert well to strings.

- newData[key] = manipulateFunc(String(object[key]));
+ newData[key] = object[key] !== undefined ? manipulateFunc(String(object[key])) : object[key];

Alternatively, add a comment explaining that the conversion is expected and handled appropriately by the manipulateFunc.

frontend/server/utils/collectResponse.js (1)

3-8: Consider removing commented code

While keeping the old implementation as a reference can be helpful during development, it's generally better to remove commented-out code before merging to production. The git history will preserve the old implementation if needed for reference.

-// export const collectResponse = (proxyRes) => new Promise((resolve, reject) => {
-//   const chunks = [];
-//   proxyRes.on('data', chunk => chunks.push(chunk));
-//   proxyRes.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
-//   proxyRes.on('error', reject);
-// });
frontend/server/utils/stringTransformer.js (2)

3-3: Unused constant KEY

This constant is defined but not used anywhere in the file. Consider removing it if it's not needed or document its intended purpose if it will be used in future implementations.

-const KEY = 'uniform-length-transformer-key';

5-9: Remove or complete commented code

There's commented code that doesn't appear to be used. Either complete the implementation if it's needed or remove it to keep the codebase clean.

-// export const uniformLengthTransformer = (outputLength) => {
-//   (text) => {
-    
-//   };
-// };
frontend/server/index.js (1)

44-44: Error handling could be improved

The error handling has been changed to directly end the response instead of passing to the next middleware. Consider adding proper error logging and sending a structured error response with an appropriate status code.

-      res.end(e);
+      console.error('Server error:', e);
+      res.status(500).json({ error: 'Internal Server Error' });
frontend/server/utils/cipher.js (2)

14-34: Well-implemented AES-GCM encryption function

The encryption function correctly uses crypto.randomBytes for IV generation, properly configures the cipher, and returns all necessary components (IV, content, tag) with appropriate encoding. Good job documenting the function with JSDoc comments.

However, consider adding validation for the key length to ensure it's 32 bytes as required for AES-256-GCM.

  encrypt: (text, key) => {
    try {
+      // Validate key length
+      if (key.length !== 32) {
+        throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.');
+      }
      // 12바이트 IV 생성 (AES-GCM의 권장 길이)
      const iv = crypto.randomBytes(12);

46-63: Add input validation to decrypt function

The decrypt function should validate that all required properties (iv, content, tag) exist in the encryptedData object before attempting to use them.

  decrypt: (encryptedData, key) => {
    try {
+      // Validate input
+      if (!encryptedData || !encryptedData.iv || !encryptedData.content || !encryptedData.tag) {
+        throw new Error('Invalid encrypted data: missing required properties');
+      }
+      // Validate key length
+      if (key.length !== 32) {
+        throw new Error('Invalid key length. AES-256-GCM requires a 32-byte key.');
+      }
      const iv = Buffer.from(encryptedData.iv, 'base64');
frontend/server/middlewares/discussionIdTransformer/discussionCipher.js (1)

7-14: Consider making the target index configurable

The hardcoded targetIdx = 3 assumes a fixed URL structure. This could be brittle if the URL structure changes in the future.

- export const decodeDiscussionIdOfUrl = (url) => {
-   const parts = url.split('/'); // ex: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
-   const targetIdx = 3; // discussionId가 위치하는 index
+ export const decodeDiscussionIdOfUrl = (url, targetIdx = 3) => {
+   const parts = url.split('/'); // ex: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
+   // targetIdx: index where discussionId is located (default: 3)
frontend/server/middlewares/discussionIdTransformer/proxyHandlers.js (3)

11-12: Add more descriptive comments for the endpoints

The regex pattern is defined but not well documented. Adding a comment explaining what URL patterns it's intended to match would improve code maintainability.

 const DISCUSSION_CREATE_ENDPOINT = '/v1/discussion';
-const DISCUSSION_GET_ENDPOINT_REGEX = /^\/v1\/discussion\/([^/]+)\/?$/;
+// Matches URLs like '/v1/discussion/{discussionId}' or '/v1/discussion/{discussionId}/'
+const DISCUSSION_GET_ENDPOINT_REGEX = /^\/v1\/discussion\/([^/]+)\/?$/;

21-29: Improve req.body validation

The current check handles objects but might not properly handle edge cases. Consider adding explicit null checks.

 export const handleProxyReq = (proxyReq, req, res) => {
-  if (req.body && typeof req.body === 'object') {
+  if (req.body && typeof req.body === 'object' && req.body !== null) {

35-39: Consider standardizing API response field names

The comment mentions that some APIs use 'id' instead of 'discussionId', requiring conditional logic. Consider standardizing these field names in your API responses for better maintainability.

While this solution works, having inconsistent field names ('id' vs 'discussionId') across APIs increases complexity and potential for bugs. If possible, standardize your API responses to use a consistent field name for discussion identifiers.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d33db15 and a189181ad5fb1e318f2b3a8218dd436edb099b92.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (36)
  • frontend/package.json (2 hunks)
  • frontend/server/envconfig.js (1 hunks)
  • frontend/server/index.js (3 hunks)
  • frontend/server/middlewares/discussionIdTransformer/discussionCipher.js (1 hunks)
  • frontend/server/middlewares/discussionIdTransformer/index.js (1 hunks)
  • frontend/server/middlewares/discussionIdTransformer/proxyHandlers.js (1 hunks)
  • frontend/server/utils/cipher.js (1 hunks)
  • frontend/server/utils/collectResponse.js (1 hunks)
  • frontend/server/utils/object.js (1 hunks)
  • frontend/server/utils/stringTransformer.js (1 hunks)
  • frontend/src/features/discussion/api/invitationApi.ts (1 hunks)
  • frontend/src/features/discussion/api/keys.ts (1 hunks)
  • frontend/src/features/discussion/api/mutations.ts (1 hunks)
  • frontend/src/features/discussion/api/queries.ts (1 hunks)
  • frontend/src/features/discussion/api/queryOptions.ts (1 hunks)
  • frontend/src/features/discussion/model/index.ts (2 hunks)
  • frontend/src/features/discussion/ui/DiscussionInviteCard/SubmitForm.tsx (1 hunks)
  • frontend/src/features/discussion/ui/DiscussionInviteCard/index.tsx (2 hunks)
  • frontend/src/features/shared-schedule/model/finishedSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/ongoingSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/model/upcomingSchedules.ts (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/RecommendedSchedules.tsx (1 hunks)
  • frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (1 hunks)
  • frontend/src/features/timeline-schedule/api/index.ts (1 hunks)
  • frontend/src/features/timeline-schedule/api/keys.ts (1 hunks)
  • frontend/src/features/timeline-schedule/api/queries.ts (1 hunks)
  • frontend/src/features/timeline-schedule/api/queryOptions.ts (1 hunks)
  • frontend/src/features/timeline-schedule/model/index.ts (1 hunks)
  • frontend/src/features/timeline-schedule/ui/index.tsx (1 hunks)
  • frontend/src/pages/CandidateSchedulePage/Header.tsx (1 hunks)
  • frontend/src/pages/CandidateSchedulePage/index.tsx (1 hunks)
  • frontend/src/pages/DiscussionPage/DiscussionInvitePage/index.tsx (1 hunks)
  • frontend/src/pages/MyCalendarPage/DiscussionContext.ts (1 hunks)
  • frontend/src/pages/MyCalendarPage/index.tsx (1 hunks)
  • frontend/src/pages/UpcomingScheduleDetailPage/index.tsx (1 hunks)
  • frontend/src/routes/_main/discussion/invite/$id.tsx (1 hunks)
🧰 Additional context used
🧬 Code Definitions (6)
frontend/server/middlewares/discussionIdTransformer/index.js (1)
frontend/server/middlewares/discussionIdTransformer/proxyHandlers.js (6)
  • rewriteDiscussionPath (14-19)
  • rewriteDiscussionPath (14-19)
  • handleProxyReq (21-29)
  • handleProxyReq (21-29)
  • handleProxyRes (31-51)
  • handleProxyRes (31-51)
frontend/src/features/discussion/api/invitationApi.ts (1)
frontend/src/features/discussion/model/invitation.ts (1)
  • InvitationResponseSchema (5-16)
frontend/server/envconfig.js (1)
frontend/server/index.js (1)
  • dirname (10-10)
frontend/server/utils/cipher.js (1)
frontend/server/middlewares/discussionIdTransformer/discussionCipher.js (5)
  • aesGcm (18-18)
  • key (17-17)
  • key (25-25)
  • iv (27-27)
  • encryptedData (28-28)
frontend/src/routes/_main/discussion/invite/$id.tsx (1)
frontend/src/features/discussion/api/queryOptions.ts (1)
  • invitationQueryOption (6-9)
frontend/server/middlewares/discussionIdTransformer/proxyHandlers.js (2)
frontend/server/middlewares/discussionIdTransformer/discussionCipher.js (6)
  • decodeDiscussionIdOfUrl (7-14)
  • decodeDiscussionIdOfUrl (7-14)
  • decryptDiscussionId (24-30)
  • decryptDiscussionId (24-30)
  • encryptDiscussionId (16-22)
  • encryptDiscussionId (16-22)
frontend/server/utils/collectResponse.js (2)
  • collectResponse (24-34)
  • collectResponse (24-34)
🔇 Additional comments (41)
frontend/src/features/shared-schedule/model/ongoingSchedules.ts (1)

6-6: Type change to support encoded discussion IDs

This change from z.number() to z.string() aligns with the PR objective of encoding discussion IDs. Since the encoded IDs will be string values produced by the AES-GCM encryption and base64 encoding, this type change is necessary to properly validate the new format.

frontend/src/features/shared-schedule/model/upcomingSchedules.ts (1)

6-6: Type change to support encoded discussion IDs

Changing from z.number() to z.string() ensures that the schema can validate encoded discussion IDs correctly. This change is consistent with the similar modifications made in other schema files, maintaining type consistency across the application.

frontend/src/features/shared-schedule/model/finishedSchedules.ts (1)

6-6: Type change to support encoded discussion IDs

The modification from z.number() to z.string() completes the pattern of changes to discussion ID types across all schedule-related schema files. This ensures that the finished schedules data structure properly handles the new encoded ID format.

frontend/src/pages/UpcomingScheduleDetailPage/index.tsx (1)

27-27: Removed number conversion for discussion ID

This change correctly removes the Number() conversion previously applied to the discussion ID, allowing the encoded string ID to be passed directly to the TimelineScheduleModal component. This aligns with the changes made to the component's type definitions.

frontend/src/features/timeline-schedule/model/index.ts (1)

35-35: Type change looks appropriate.

Changing discussionId from z.number() to z.string() aligns with the PR's goal of encoding discussion IDs. Since encoded IDs will be handled as strings rather than numbers, this change ensures proper type checking and validation.

frontend/src/routes/_main/discussion/invite/$id.tsx (1)

22-22: Correctly removed ID conversion.

The change appropriately removes the numeric conversion of the discussion ID parameter, which aligns with the ID encoding implementation described in the PR. The ID is now correctly passed as a string to the query option.

frontend/src/pages/DiscussionPage/DiscussionInvitePage/index.tsx (1)

25-25: Correctly removed type coercion.

The removal of the unary plus operator (+) is appropriate as the discussion ID is now being handled as a string rather than a number, which aligns with the PR's implementation of ID encoding.

frontend/src/features/timeline-schedule/api/queryOptions.ts (1)

6-6: Parameter type change is consistent.

Changing the discussionId parameter type from number to string is consistent with the other changes in this PR. This ensures that encoded discussion IDs are correctly handled throughout the application.

frontend/package.json (1)

55-55: New dependencies added for discussion ID encryption and middleware.

These new dependencies align with the PR objectives of implementing a proxy middleware for encoding/decoding discussion IDs using AES-GCM encryption:

  • dotenv will handle the environment variables for encryption keys
  • http-proxy-middleware will intercept API requests/responses to transform discussion IDs

Also applies to: 64-64

frontend/src/features/timeline-schedule/api/keys.ts (1)

6-6:

Details

✅ Verification successful

Type change from number to string for discussionId parameter.

This change is consistent with the PR objectives to encode discussion IDs, transforming them from numbers to encrypted strings using AES-GCM and base64 encoding.


🏁 Script executed:

#!/bin/bash
# Verify that all other instances of discussionId in timeline-schedule feature have been updated to string type
rg "discussionId: number" -t ts --context 3 src/features/timeline-schedule/

Length of output: 149


Action Required: Verify 'discussionId' Type Consistency

The change in frontend/src/features/timeline-schedule/api/keys.ts from a numeric to a string type for the discussionId parameter aligns with the PR objectives of encoding discussion IDs. However, the initial verification command pointed to an incorrect directory. Please re-run the above script (or a similar check) to ensure that no instances of discussionId: number remain within the timeline-schedule feature.


🏁 Script executed:

#!/bin/bash
# Ensure the keys file exists in the updated path and search for any numeric discussionId usage

FILE="frontend/src/features/timeline-schedule/api/keys.ts"

if [ ! -f "$FILE" ]; then
  echo "File not found: $FILE"
  exit 1
fi

# Search for any instance of discussionId typed as number in the timeline-schedule feature
rg "discussionId: number" -t ts --context 3 frontend/src/features/timeline-schedule/

Length of output: 336


Discussion ID Type Update Verified

The refactored type change in frontend/src/features/timeline-schedule/api/keys.ts (i.e., changing discussionId from number to string) is consistent with the encryption and encoding objectives of the PR. A search for discussionId: number within the timeline-schedule feature did not return any results, confirming that other instances have been updated accordingly.

frontend/src/features/discussion/api/keys.ts (1)

27-27:

Details

✅ Verification successful

Type change from number to string for invitationQueryKey's discussionId parameter.

This change aligns with the PR objectives to encode all discussion IDs using AES-GCM encryption and base64 encoding. This transformation requires the type to be updated from number to string.


🏁 Script executed:

#!/bin/bash
# Check if there are any remaining instances where discussionId is still typed as number in the discussion feature
rg "discussionId: number" -t ts --context 3 src/features/discussion/

Length of output: 135


Could you please run the above command to verify that no outdated type declarations remain in the discussion feature? The previous search targeted a non-existent directory, so a manual check using this corrected path is needed. Once confirmed, we can fully approve the change to using a string for the invitationQueryKey's discussionId parameter.

Action Required:

  • Re-run the provided script with the correct path.
  • Manually verify the output to ensure there are no instances of discussionId: number remaining in frontend/src/features/discussion/.

🏁 Script executed:

#!/bin/bash
# Re-run the search for any instances of discussionId typed as number using the corrected path.
rg "discussionId: number" -t ts --context 3 frontend/src/features/discussion/

Length of output: 201


Approved: Discussion ID Type Updated

  • Verified that invitationQueryKey now accepts a string for the discussionId parameter.
  • A re-run of the search in frontend/src/features/discussion/ did not return any outdated instances of discussionId: number, ensuring the change aligns with the encryption and encoding requirements.

This update meets the PR objectives.

frontend/src/features/timeline-schedule/ui/index.tsx (2)

24-24: Type change from number to string for discussionId prop.

This change is consistent with the PR objectives to encode discussion IDs as strings using AES-GCM encryption. This type update ensures the component correctly handles the encrypted string format of discussion IDs.


40-40: Function call using the updated string discussionId type.

The useCandidateDetailQuery is correctly using the string-typed discussionId with no changes needed to the function call itself, showing good compatibility with the type change.

frontend/src/features/shared-schedule/ui/OngoingSchedules/RecommendedSchedules.tsx (2)

68-68: Type change for discussionId is consistent with the application-wide changes

This change from number to string for discussionId is aligned with the PR objective to encode discussion IDs. The change ensures type consistency when using the encrypted string IDs.


79-81:

Details

✅ Verification successful

Verify that toString() call is still needed

Since discussionId is now a string, the toString() call in the Link params might be redundant. However, this is harmless as it ensures the value is always a string even if the type system is bypassed.


🏁 Script executed:

#!/bin/bash
# Check if there are other places in the codebase where discussionId.toString() is used
rg "discussionId\.toString\(\)" --type ts,tsx

Length of output: 78


Action Required: Verify discussionId Type Consistency

The original search command failed due to unrecognized file types. Please re-run the updated command above to search for instances of discussionId.toString() within .ts and .tsx files. If no matches are found, it confirms that discussionId is consistently a string in the codebase, which makes the call to toString() redundant (although harmless).


🏁 Script executed:

#!/bin/bash
rg "discussionId\.toString\(\)" -g "*.ts" -g "*.tsx"

Length of output: 1043


DiscussionId.toString() Usage Consistency Verified

We've confirmed via search that the .toString() call is used consistently across multiple components (e.g., in UpcomingScheduleListItem.tsx, ScheduleCard.tsx, ScheduleDetails.tsx, etc.). Since discussionId is now a string throughout the codebase, these calls are redundant. However, they also serve as a defensive safeguard in case type guarantees are bypassed. If you're confident in the type system's integrity, you can safely remove these conversions for clarity, or leave them in place without adverse effects.

frontend/src/features/discussion/api/queries.ts (1)

111-111: Type change for discussionId parameter is consistent with other functions

The parameter type change from number to string for useInviteInfoQuery maintains consistency with other functions in this file that also use string-typed discussion IDs.

frontend/src/features/timeline-schedule/api/queries.ts (1)

7-7: Type change for discussionId parameter aligns with the PR objective

Changing the discussionId parameter type from number to string is consistent with the PR's goal of using encoded discussion IDs across the application.

frontend/src/pages/CandidateSchedulePage/index.tsx (2)

20-20: Removed Number conversion to match the new type

Correctly removed the Number(id) conversion since the discussionId is now expected to be a string. This change is consistent with the updated component props.


28-28: Removed Number conversion to match the new type

Correctly removed the Number(id) conversion since the discussionId is now expected to be a string. This change is consistent with the updated component props.

frontend/server/envconfig.js (1)

1-15: Good implementation of environment variable management

This file correctly sets up environment variable loading for the AES-GCM key used in discussion ID encryption. The implementation follows best practices:

  • Uses ES modules syntax
  • Properly resolves the path to the .env.local file
  • Exports only the needed environment variable

The environment configuration is well-structured and aligns with the PR objective of implementing discussion ID encryption.

frontend/src/features/discussion/ui/DiscussionInviteCard/SubmitForm.tsx (1)

14-19: Type change to support encoded discussion IDs

The change from number to string for discussionId properly supports the new encoded ID format.

frontend/src/features/discussion/api/mutations.ts (1)

33-33: Type change to support encoded discussion IDs

Changing the discussionId type from number to string aligns with the PR objective of encoding discussion IDs. This ensures type safety throughout the API call chain.

frontend/src/features/discussion/model/index.ts (2)

39-39: Type change aligns with ID encryption

Changing the ID type from number to string is consistent with the PR objective of encoding discussion IDs. This type change is necessary since the encoded IDs will be string-based after the AES-GCM encryption and base64 encoding.


80-80: Type change for discussionId is consistent

Changing discussionId to a string type follows the same pattern applied to the id field earlier in this file. This ensures consistency in how discussion identifiers are handled throughout the application.

frontend/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (1)

27-27: Type change aligns with encrypted IDs implementation

The change from number to string for the discussionId type is consistent with the overall project changes for discussion ID encoding.

frontend/server/utils/object.js (1)

1-20: Well-designed recursive object manipulation utility

This utility function is well-structured for recursively processing objects and transforming specific key values. It properly handles various data types (arrays, objects, primitives) and maintains immutability by creating new objects rather than modifying the original.

The implementation correctly:

  1. Processes arrays using map
  2. Checks for null/non-object values
  3. Creates a new object to avoid mutation
  4. Applies the manipulation function only to the specified keys
  5. Recursively processes nested structures

This seems to be a core function for the ID transformation middleware described in the PR objectives.

frontend/src/features/discussion/api/queryOptions.ts (1)

6-6: Type change consistent with ID encryption strategy

Changing the discussionId parameter type from number to string aligns with the overall approach of using string-based encrypted IDs. This ensures type consistency between the API calls, query keys, and data models.

frontend/src/features/discussion/api/invitationApi.ts (2)

6-6: Type change aligns with discussion ID encryption

Changing discussionId type from number to string is consistent with the PR objective of encoding discussion IDs. This ensures that encrypted IDs (which will be strings) can be properly handled by the API functions.


11-11: Type change consistent with the encryption scheme

The parameter type change from number to string for discussionId is appropriate and matches the changes made in the getInvitationInfo method, maintaining consistency across the API interface.

frontend/src/pages/MyCalendarPage/index.tsx (1)

20-20: State type updated for encoded discussion ID

Changing the selectedId state from number | null to string | null is consistent with the PR's approach of encoding discussion IDs as strings. This change ensures that the DiscussionProvider context will correctly handle the encrypted ID format.

frontend/server/utils/collectResponse.js (2)

12-23: Well-documented utility function

The JSDoc comments are comprehensive and explain the purpose, parameters, return values, and potential errors of the function. This is excellent practice for maintainability.


24-34: Modern and efficient implementation for response collection

The implementation using async/await with proper error handling is clean and efficient. This utility function will be essential for the middleware to process and potentially transform responses containing discussion IDs.

Some observations:

  • Modern async iteration is used instead of event listeners
  • Proper error handling with informative messages
  • Efficiently collects and processes chunks of data
frontend/src/features/timeline-schedule/api/index.ts (1)

8-8: Type change looks good: number → string for discussionId parameter

The change from number to string for the discussionId parameter aligns with the PR objective of encoding discussion IDs. This ensures compatibility with the new encoded format being used throughout the application.

frontend/server/utils/stringTransformer.js (2)

19-33: Base64URL implementation looks good

The base64Url implementation correctly handles URL-safe encoding and decoding, including proper padding management. The code is well-commented and follows best practices.


11-17:

Details

❓ Verification inconclusive

Non-cryptographic hash function implementation

The implemented hash function is a simple non-cryptographic hash that may not be suitable for security-sensitive operations. If this is used for any security purpose, consider using a more robust cryptographic hash function from the Node.js crypto module.


🏁 Script executed:

#!/bin/bash
# Check if this hash function is used in security contexts
rg -A 3 -B 3 "hash\(" --glob="!*/node_modules/*" --glob="!*/frontend/server/utils/stringTransformer.js"

Length of output: 105


Attention: Verify Non-Cryptographic Hash Usage

In frontend/server/utils/stringTransformer.js (lines 11–17), the hash function is implemented as a simple non-cryptographic hash. This implementation is typically acceptable for operations like caching or general text transformation. However, it is important to ensure that this function is not inadvertently used in any security-sensitive context.

  • The initial grep check (excluding this file) produced no results, which suggests that the function is not invoked in contexts that imply cryptographic responsibility.
  • Action Required: Please manually verify all usages of the hash function within the codebase to confirm it isn’t being used for any security-critical operations. If it turns out that security-sensitive hashing is required, consider replacing this implementation with a robust cryptographic hash function from Node.js’s crypto module.
frontend/server/index.js (2)

20-23: Middleware setup for discussion ID transformation looks good

The addition of express.json() middleware and the discussionIdTransformer for API routes is well-structured. The JSON parsing middleware is correctly placed before the transformer to ensure request bodies are properly parsed.


29-33: API route bypass logic properly implemented

The conditional check to bypass the HTML rendering logic for API routes is correct and necessary for the middleware to handle API requests appropriately.

frontend/server/utils/cipher.js (1)

1-64:

Details

✅ Verification successful

Consider the randomness impact on user experience

As mentioned in the PR objectives, using a random nonce (IV) in AES-GCM means the same input will produce different outputs each time. While this enhances security, it may impact user experience if users expect consistent output for the same ID.

If consistent URLs are a priority, you might want to implement an alternative approach such as using a deterministic IV derived from the discussion ID itself, though this would reduce security. The current implementation prioritizes security, which is generally a good choice.


🌐 Web query:

What are the security implications of using a deterministic IV in AES-GCM encryption?

💡 Result:

Using a deterministic Initialization Vector (IV) in AES-GCM encryption introduces critical security considerations. AES-GCM requires IV uniqueness for each encryption under the same key to prevent catastrophic security failures. Here’s a breakdown of the implications:

Key Security Risks

  1. IV Reuse Vulnerability:

    • Reusing an IV with the same key allows attackers to recover the authentication key ( H ), compromising both confidentiality and integrity[2][9]. For example, if two ciphertexts share an IV and key, XORing them reveals the XOR of their plaintexts[10].
    • IV collisions also reduce security bounds, enabling practical attacks like polynomial root-solving to decrypt data without knowing the key[3][4].
  2. Predictability Concerns:

    • Deterministic IVs (e.g., counters) risk predictability if not securely implemented. Predictable IVs simplify algebraic cryptanalysis, especially in large-scale systems[1][6].
    • Example: AWS encountered operational risks when systems encrypted ( 2^{32} ) messages in under two seconds, overwhelming counter-based IVs and forcing frequent rekeying[4].
  3. Multi-User Weaknesses:

    • Distributed systems sharing keys face challenges partitioning IV counter spaces. Predictable IVs across users/applications exacerbate risks of cross-context attacks[4][7].

Operational Challenges

  • State Management: Deterministic IVs require non-volatile storage to track counters securely, complicating FIPS compliance and disaster recovery[4].
  • Scalability Limits: Systems with high throughput (e.g., IoT networks) risk counter exhaustion, leading to forced key rotations or IV reuse[4][6].
  • Complexity in Uniqueness Guarantees: Unique IV generation demands coordination in distributed environments, increasing design complexity[1][6].

Best Practices for Mitigation

  • Use Random IVs: A 96-bit random IV reduces collision risk (birthday bound of ( 2^{-32} )) and simplifies implementation[5][6].
  • Hybrid Deterministic-Random IVs: Combine a fixed secret XORed with a counter to add unpredictability while maintaining uniqueness[1][6].
  • Key Rotation: Limit key usage to ( 2^{32} ) encryptions to avoid counter overflow[4][6][10].

Comparative Security

Aspect Deterministic IV Random IV
Uniqueness Required via counters/fixed fields Probabilistic (birthday bound)
Predictability Risk if implementation is flawed High entropy by design
Operational Overhead Complex state management Simpler, stateless

In summary, deterministic IVs in AES-GCM introduce risks of IV reuse and predictability, demanding rigorous implementation safeguards. Random IVs or hybrid approaches with secret salts are preferred for most applications[1][4][6]. Systems requiring determinism (e.g., file encryption) must enforce strict IV uniqueness and key rotation policies.

Citations:


Security Versus Consistency in IV Generation

The current implementation correctly uses a random 12-byte IV to ensure unique nonces for each encryption, a critical requirement for AES-GCM's security. While this randomness means that encrypting the same input will produce different outputs—potentially impacting scenarios where consistent output (e.g., consistent URLs) is expected—switching to a deterministic IV (such as one derived from a discussion ID) would expose the system to significant vulnerabilities. Deterministic IVs can lead to IV reuse and make the encryption predictable, thereby weakening both confidentiality and integrity.

If consistent output is a business requirement, any alternative approach must incorporate additional safeguards (such as incorporating secret salts or designing a hybrid method) to mitigate these risks. However, such changes should be weighed very carefully against the potential for catastrophic security failures that deterministic IVs can introduce.

frontend/src/features/discussion/ui/DiscussionInviteCard/index.tsx (1)

15-15: Type change from number to string looks good

The change from number to string for discussionId properly supports the new ID encoding approach. This change aligns with the PR objective to implement encoded discussion IDs.

Also applies to: 64-64

frontend/src/pages/MyCalendarPage/DiscussionContext.ts (1)

7-8: Type change from number to string is consistent

The change from number to string for both selectedId and setSelectedId maintains consistency with the discussion ID encoding approach implemented across the codebase.

frontend/server/middlewares/discussionIdTransformer/discussionCipher.js (1)

16-22:

Details

❓ Verification inconclusive

AES-GCM produces different outputs for the same input

As noted in the PR objectives, using AES-GCM with a random nonce (IV) will produce different ciphertexts for the same input. This is secure but may create UX issues if users expect consistent URLs.

If consistent output is required, consider using a deterministic encryption approach or a fixed IV (though this reduces security). Please verify which approach better suits your requirements:


🌐 Web query:

What are the security implications of using a fixed IV with AES-GCM versus using a random IV?

💡 Result:

Using a fixed IV with AES-GCM creates catastrophic security risks, while random IVs introduce probabilistic risks that require careful management. Here's a breakdown of the implications:

Fixed IV Security Risks

  • Authentication breakdown: Reusing an IV (nonce) with the same key in AES-GCM allows attackers to forge valid authentication tags, compromising data integrity. This enables tampering with ciphertext undetected[3][6].
  • Confidentiality compromise: A reused IV causes reuse of the CTR keystream, allowing attackers to:
    • Recover plaintext via XOR analysis of ciphertexts[3][6]
    • Derive the authentication key (H) if two messages use the same IV[6]
  • Practical attacks: Demonstrated exploits include web application vulnerabilities where static nonces enabled forged ciphertext injection and data exfiltration[6].

Random IV Considerations

  • Collision probability: With 96-bit IVs, the birthday paradox implies a ≈50% collision chance after encrypting 2^48 messages[1][3]. In multi-key systems (e.g., cloud services), this risk grows exponentially:

    Keys Messages/Key Total Collision Risk
    1 2^32 2^-32
    128 2^29 2^-26[1]
  • Operational challenges:

    • Requires strict key rotation policies to maintain security bounds[1][5]
    • High-throughput systems (>2^32 messages/sec) can't practically rotate keys fast enough[1]

Comparative Risks

Aspect Fixed IV Random IV
Authentication Completely broken on reuse Secure if IVs are unique
Confidentiality Keystream reuse allows plaintext recovery Secure until IV collision occurs
Practicality Simple implementation Requires robust IV management
NIST Compliance Prohibited Allowed with usage limits[1][4]

Mitigation Strategies

  • Use deterministic IV constructions like AES-GCM-SIV (though not FIPS-approved)[1]
  • Implement key derivation schemes to reduce IV reuse probability[1]
  • Combine 12-byte fixed prefix with 4-byte counter for high-volume systems[4][5]
  • Avoid AES-GCM for extremely high-throughput use cases (>1 billion encryptions/key)[1]

The choice between fixed and random IVs isn't binary – deterministic approaches with counters often provide better security than purely random IVs in large-scale systems[1][5]. However, even deterministic implementations must ensure global uniqueness across distributed environments to prevent counter reuse[1][4].

Citations:


Action Required: Confirm Encryption Consistency Requirements vs. Security Tradeoffs

The current implementation correctly generates a random IV with AES-GCM, ensuring robust security by producing non-deterministic ciphertexts even for identical inputs. Note that this non-determinism may lead to UX challenges if consistent URLs or identifiers are expected. However, switching to a fixed IV to achieve determinism is strongly discouraged due to severe security risks (e.g., keystream reuse, authentication tag forgery, and potential plaintext recovery).

  • Security Impact: A fixed IV undermines AES-GCM's guarantees, as repeated nonces allow attackers to compromise both confidentiality and integrity.
  • Alternatives: For consistent outputs without sacrificing security, consider:
    • A deterministic encryption mode (such as AES-GCM-SIV), or
    • A nonce construction strategy (e.g., combining a fixed prefix with a counter) that ensures uniqueness.
  • Next Steps: Verify if your application's requirements favor deterministic outputs over the inherent randomness of secure AES-GCM. If consistent output is mandatory, adopt a deterministic encryption scheme rather than resorting to a fixed IV.

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

Proper type update for encoded discussion ID

The change from number to string for discussionId in the HeaderProps interface aligns with the encryption implementation. However, I notice that while discussionId is defined in the interface, it's not actually used in the component rendering.


🏁 Script executed:

#!/bin/bash
# Check if discussionId is used elsewhere in the Header component or its children
rg -A 2 -B 2 "discussionId" --type tsx frontend/src/pages/CandidateSchedulePage/Header.tsx
rg -A 2 -B 2 "discussionId" --type tsx frontend/src/features/discussion/ui/DiscussionConfirmButton

Length of output: 245


Action Required: Unused Discussion ID Prop Detected

The type update for discussionId (changed from number to string) properly aligns it with the encryption implementation. However, a revised search using a broader file pattern indicates that discussionId is defined in the HeaderProps interface but isn’t referenced in the component’s rendering or its immediate children. Please verify whether this prop is intended for future use; if not, consider removing it to keep the interface clean.

  • Confirm if discussionId should be integrated into the rendering logic.
  • If the prop is redundant, remove it from the interface.

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Error message construction issue in decrypt function

Similar to the encrypt function, the Error constructor doesn't concatenate arguments in the decrypt function.

-      throw new Error('aesGcm: decrypt failed with error:', err);
+      throw new Error(`aesGcm: decrypt failed with error: ${err.message}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
throw new Error('aesGcm: decrypt failed with error:', err);
throw new Error(`aesGcm: decrypt failed with error: ${err.message}`);

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Error message construction issue

The Error constructor doesn't concatenate arguments. The error details will be lost with the current implementation.

-      throw new Error('aesGcm: encrypt failed with error:', err);
+      throw new Error(`aesGcm: encrypt failed with error: ${err.message}`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
throw new Error('aesGcm: encrypt failed with error:', err);
throw new Error(`aesGcm: encrypt failed with error: ${err.message}`);

Comment on lines 24 to 30
Copy link

@coderabbitai coderabbitai bot Mar 28, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for invalid cipherText

The decryptDiscussionId function will throw an error if cipherText is invalid or malformed. Consider adding try-catch blocks to handle potential decryption failures gracefully.

 export const decryptDiscussionId = (cipherText) => {
-  const key = Buffer.from(serverEnv.AES_GCM_KEY_OF_DISCUSSION_ID, 'base64');
-  const concat = base64Url.decode(cipherText);
-  const [iv, content, tag] = concat.split('.');
-  const encryptedData = { iv, content, tag };
-  return aesGcm.decrypt(encryptedData, key);
+  try {
+    const key = Buffer.from(serverEnv.AES_GCM_KEY_OF_DISCUSSION_ID, 'base64');
+    const concat = base64Url.decode(cipherText);
+    const [iv, content, tag] = concat.split('.');
+    // Validate that all components are present
+    if (!iv || !content || !tag) {
+      throw new Error('Invalid cipher format');
+    }
+    const encryptedData = { iv, content, tag };
+    return aesGcm.decrypt(encryptedData, key);
+  } catch (error) {
+    console.error('Failed to decrypt discussion ID:', error);
+    return cipherText; // Return original or a fallback value
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const decryptDiscussionId = (cipherText) => {
const key = Buffer.from(serverEnv.AES_GCM_KEY_OF_DISCUSSION_ID, 'base64');
const concat = base64Url.decode(cipherText);
const [iv, content, tag] = concat.split('.');
const encryptedData = { iv, content, tag };
return aesGcm.decrypt(encryptedData, key);
};
export const decryptDiscussionId = (cipherText) => {
try {
const key = Buffer.from(serverEnv.AES_GCM_KEY_OF_DISCUSSION_ID, 'base64');
const concat = base64Url.decode(cipherText);
const [iv, content, tag] = concat.split('.');
// Validate that all components are present
if (!iv || !content || !tag) {
throw new Error('Invalid cipher format');
}
const encryptedData = { iv, content, tag };
return aesGcm.decrypt(encryptedData, key);
} catch (error) {
console.error('Failed to decrypt discussion ID:', error);
return cipherText; // Return original or a fallback value
}
};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@coderabbitai
aesGcm 모듈 내부에서 encrypt, decrypt 시 에러 핸들링을 수행하고 있습니다. decryptDiscussionId에서도 에러 핸들링을 해주어야 할 필요가 있을까요?

Copy link

Choose a reason for hiding this comment

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

@dioo1461 aesGcm 모듈 내부에 에러 핸들링이 있지만, 해당 모듈은 에러를 잡아서 처리하기보다는 다시 throw하는 방식으로 구현되어 있습니다:

// aesGcm.decrypt 내부
try {
  // 복호화 로직...
} catch (err) {
  throw new Error(`Decryption failed: ${err.message}`);
}

decryptDiscussionId 함수에서 추가 에러 핸들링이 필요한 이유는:

  1. base64Url.decode나 split 등의 작업에서 발생하는 에러는 aesGcm에서 처리하지 않습니다
  2. URL 처리 과정에서 사용되는 함수이므로 실패 시 전체 요청 처리가 중단될 수 있습니다
  3. 잘못된 형식의 cipherText가 들어올 경우 애플리케이션 흐름이 중단되지 않도록 하는 것이 안전합니다

에러 핸들링을 추가하면 비정상적인 입력에도 애플리케이션이 계속 작동할 수 있어 사용자 경험이 향상됩니다.


✏️ Learnings added
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#365
File: frontend/server/middlewares/discussionIdTransformer/discussionCipher.js:24-30
Timestamp: 2025-03-28T06:06:04.491Z
Learning: The decryptDiscussionId function can benefit from additional error handling despite the existing error handling in the aesGcm module, since aesGcm rethrows errors rather than handling them with fallbacks. Error handling for the URL processing-related functions improves application resilience.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 31 to 51
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance error handling with specific error types

The current try-catch block catches all errors and falls back to returning the original data. Consider adding more granular error handling for better debugging.

   try {
     const result = manipulateValueOfObject(
       targetKey,
       JSON.parse(stringData), 
       encryptDiscussionId,
     );
     res.end(JSON.stringify(result));
   } catch (err) {
-    // 변환 실패 시 원본 데이터를 그대로 반환
+    // Log the specific error type for better debugging
+    console.error('Error processing response:', err.message);
+    if (err instanceof SyntaxError) {
+      console.warn('Failed to parse JSON response');
+    } else {
+      console.warn('Failed to manipulate or encrypt discussion ID');
+    }
+    // Return original data if transformation fails
     res.end(stringData);
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const handleProxyRes = async (proxyRes, req, res) => {
const stringData = await collectResponse(proxyRes);
res.statusCode = proxyRes.statusCode;
res.setHeader('Content-Type', proxyRes.headers['Content-type'] || 'application/json');
// discussion id 필드명을 'discussionId'가 아닌 'id'로 반환하는 API가 존재하여, 이에 대해 분기처리
const isDiscussionApi = (
req.url === DISCUSSION_CREATE_ENDPOINT || DISCUSSION_GET_ENDPOINT_REGEX.test(req.url)
);
const targetKey = isDiscussionApi ? 'id' : 'discussionId';
try {
const result = manipulateValueOfObject(
targetKey,
JSON.parse(stringData),
encryptDiscussionId,
);
res.end(JSON.stringify(result));
} catch (err) {
// 변환 실패 시 원본 데이터를 그대로 반환
res.end(stringData);
};
};
export const handleProxyRes = async (proxyRes, req, res) => {
const stringData = await collectResponse(proxyRes);
res.statusCode = proxyRes.statusCode;
res.setHeader('Content-Type', proxyRes.headers['Content-type'] || 'application/json');
// discussion id 필드명을 'discussionId'가 아닌 'id'로 반환하는 API가 존재하여, 이에 대해 분기처리
const isDiscussionApi = (
req.url === DISCUSSION_CREATE_ENDPOINT || DISCUSSION_GET_ENDPOINT_REGEX.test(req.url)
);
const targetKey = isDiscussionApi ? 'id' : 'discussionId';
try {
const result = manipulateValueOfObject(
targetKey,
JSON.parse(stringData),
encryptDiscussionId,
);
res.end(JSON.stringify(result));
} catch (err) {
// Log the specific error type for better debugging
console.error('Error processing response:', err.message);
if (err instanceof SyntaxError) {
console.warn('Failed to parse JSON response');
} else {
console.warn('Failed to manipulate or encrypt discussion ID');
}
// Return original data if transformation fails
res.end(stringData);
};
};

@dioo1461 dioo1461 added the 🖥️ FE Frontend label Mar 29, 2025
@dioo1461 dioo1461 added this to the 💪8차 스프린트 milestone Mar 29, 2025
Copy link
Contributor

Choose a reason for hiding this comment

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

p3: .env 로 변경해야 할 거 같아요. 암호화 키 값은 FE_ENV에 저장하면 좋을 거 같아요

dioo1461 added 22 commits June 16, 2025 00:55
 login 요청에 대해 path를 'api/v1/login' 이 아닌 'v1/login'으로 식별하여 was에 전송하던 것이 원인이었음
@dioo1461 dioo1461 force-pushed the feature/fe/encode-discussion-id branch from a189181 to bd63602 Compare June 15, 2025 15:57
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (3)
frontend/apps/client/src/features/shared-schedule/model/upcomingSchedules.ts (1)

21-22: 🛠️ Refactor suggestion

Remove the lingering number union before it leaks back in

The rest of the PR migrates discussionId to string; keeping z.number() here defeats the type-safety you just gained (callers will continue to send numbers).

-  // TODO: discussionId 인코딩 적용 후 z.string()으로 변경
-  discussionId: z.union([z.string(), z.number()]),
+  discussionId: z.string(), // encoded discussion id

Even if the BE still returns numbers in niche paths, it is better to coerce at the boundary than relax the contract here.

frontend/apps/client/src/features/discussion/api/index.ts (1)

61-71: ⚠️ Potential issue

response.parse will crash – wrong object invoked

request.* returns the server payload, which has no parse method.
We still need the Zod schema (or equivalent) to perform validation.

-    const response = await request.post(`/api/v1/discussion/${id}/confirm`, { body });
-    return response.parse(response);
+    const raw = await request.post(`/api/v1/discussion/${id}/confirm`, { body });
+    return DiscussionConfirmResponse.parse(raw);

Same fix applies to getDiscussionConfirm.

Remember to import the value (not type) of DiscussionConfirmResponse:

-import type { ... , DiscussionConfirmResponse, ... } from '../model';
+import { DiscussionConfirmResponse } from '../model';
+import type {
+  DiscussionCalendarRequest,
+  DiscussionCalendarResponse,
+  DiscussionConfirmRequest,
+  /* rest unchanged */
+} from '../model';

Without this adjustment, the code will not compile.

frontend/apps/client/src/features/discussion/api/invitationApi.ts (1)

6-17: 💡 Verification agent

🧩 Analysis chain

API signature updated – verify call-sites & consider spelling fix

  1. All callers must now pass a string. A quick ripgrep search can confirm there are no lingering numeric arguments.
  2. The method name postInviatationJoin contains a typo (“Inviatation”). Consider renaming to postInvitationJoin before the typo spreads.
-export const invitationApi = {
-  getInvitationInfo: async (discussionId: string) => {
+export const invitationApi = {
+  getInvitationInfo: async (discussionId: string) => {
...
-  postInviatationJoin: async (discussionId: string, password?: string) => {
+  postInvitationJoin: async (discussionId: string, password?: string) => {

🏁 Script executed:

#!/bin/bash
set -e
echo "Searching for getInvitationInfo calls"
rg -n -C2 "getInvitationInfo\(" 
echo "Searching for postInviatationJoin calls"
rg -n -C2 "postInviatationJoin\(" 
echo "Searching for postInvitationJoin calls"
rg -n -C2 "postInvitationJoin\(" 
echo "Searching for any invitationApi usage"
rg -n -C2 "invitationApi"

Length of output: 3613


Spelling fix & call‐site update for invitationApi

  1. Rename the method in
    frontend/apps/client/src/features/discussion/api/invitationApi.ts
    - postInviatationJoin: async (discussionId: string, password?: string) => {
    + postInvitationJoin: async (discussionId: string, password?: string) => {
  2. Update its invocation in
    frontend/apps/client/src/features/discussion/api/mutations.ts (around line 34)
    - invitationApi.postInviatationJoin(body.discussionId, body.password)
    + invitationApi.postInvitationJoin(body.discussionId, body.password)
  3. Verified all existing callers (e.g. in queryOptions.ts and mutations.ts) pass discussionId as a string.
  4. No numeric‐literal invocations of getInvitationInfo were found.
🧹 Nitpick comments (8)
frontend/apps/client/src/features/shared-schedule/model/upcomingSchedules.ts (1)

8-14: Consider introducing a shared EncodedDiscussionIdSchema for consistency

discussionId is now a string in several schemas. Copy-pasting the literal z.string() everywhere will become error-prone once you need tighter validation (e.g. non-empty, base64 check).
A small helper such as:

// @utils/zodSchemas.ts
export const EncodedDiscussionIdSchema = z.string().min(1);

would let you replace every occurrence with EncodedDiscussionIdSchema, guaranteeing that a future change is one-liner.

frontend/apps/client/src/features/shared-schedule/model/ongoingSchedules.ts (1)

5-5: Reuse a central schema instead of hard-coding

Same comment as before – replacing z.string() with a shared EncodedDiscussionIdSchema keeps all schedule models in sync and prevents accidental divergence.

frontend/apps/client/src/features/timeline-schedule/model/index.ts (1)

34-34: Validate the encoded format

If the encrypted ID is always URL-safe base64, consider tightening the schema with a regex:

discussionId: z.string().regex(/^[A-Za-z0-9_-]+$/),

so malformed IDs are caught early.

frontend/apps/client/src/features/shared-schedule/model/finishedSchedules.ts (1)

6-6: Nit: keep the comment trail consistent

For parity with the other models, drop a short comment explaining that the ID is an AES-GCM + base64 token. Future readers will not have to dig through the PR.

frontend/apps/client/src/features/discussion/ui/DiscussionInviteCard/SubmitForm.tsx (1)

34-34: Avoid redundant toString() on an already‐string ID

discussionId is now typed as string, so the extra conversion is unnecessary:

-          navigate({ to: '/discussion/$id', params: { id: discussionId.toString() } });
+          navigate({ to: '/discussion/$id', params: { id: discussionId } });

Small win: one less allocation and slightly clearer intent.

frontend/apps/client/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (1)

28-28: Redundant .toString() calls can be dropped
discussionId is already a string; calling .toString() twice is unnecessary extra work and a small readability hit.

-const { discussion } = useDiscussionQuery(discussionId.toString());
+const { discussion } = useDiscussionQuery(discussionId);

-<Link params={{ id: discussionId.toString() }} to='/discussion/$id'>
+<Link params={{ id: discussionId }} to='/discussion/$id'>

Also applies to: 55-55

frontend/apps/client/src/features/shared-schedule/ui/OngoingSchedules/RecommendedSchedules.tsx (2)

24-27: .toString() is no longer required here either
discussion.id is already string; drop the conversion for consistency.

-const { calendar: candidates = [] } = useDiscussionCalendarQuery(
-  discussion.id.toString(), { size: 3 }, ONGOING_SCHEDULE_DETAIL_GC_TIME,
-);
+const { calendar: candidates = [] } = useDiscussionCalendarQuery(
+  discussion.id, { size: 3 }, ONGOING_SCHEDULE_DETAIL_GC_TIME,
+);

58-71: Prop & router params: remove redundant conversions
After changing discussionId to string, these conversions can be simplified.

-Link
-  params={{ id: discussionId.toString() }}
+Link
+  params={{ id: discussionId }}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a189181ad5fb1e318f2b3a8218dd436edb099b92 and bd63602.

📒 Files selected for processing (16)
  • frontend/.gitignore (1 hunks)
  • frontend/apps/client/src/features/discussion/api/index.ts (2 hunks)
  • frontend/apps/client/src/features/discussion/api/invitationApi.ts (1 hunks)
  • frontend/apps/client/src/features/discussion/api/keys.ts (1 hunks)
  • frontend/apps/client/src/features/discussion/api/mutations.ts (1 hunks)
  • frontend/apps/client/src/features/discussion/api/queries.ts (1 hunks)
  • frontend/apps/client/src/features/discussion/api/queryOptions.ts (1 hunks)
  • frontend/apps/client/src/features/discussion/model/index.ts (2 hunks)
  • frontend/apps/client/src/features/discussion/ui/DiscussionInviteCard/SubmitForm.tsx (1 hunks)
  • frontend/apps/client/src/features/discussion/ui/DiscussionInviteCard/index.tsx (2 hunks)
  • frontend/apps/client/src/features/shared-schedule/model/finishedSchedules.ts (1 hunks)
  • frontend/apps/client/src/features/shared-schedule/model/ongoingSchedules.ts (1 hunks)
  • frontend/apps/client/src/features/shared-schedule/model/upcomingSchedules.ts (1 hunks)
  • frontend/apps/client/src/features/shared-schedule/ui/OngoingSchedules/RecommendedSchedules.tsx (1 hunks)
  • frontend/apps/client/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (1 hunks)
  • frontend/apps/client/src/features/timeline-schedule/model/index.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • frontend/.gitignore
  • frontend/apps/client/src/features/discussion/ui/DiscussionInviteCard/index.tsx
🔇 Additional comments (8)
frontend/apps/client/src/features/discussion/api/queries.ts (1)

111-112:

Details

✅ Verification successful

Double-check downstream typing of discussionId

useInviteInfoQuery now expects a string, but the underlying invitationQueryOptioninvitationApi.getInvitationInfo chain might still declare the parameter as number.
A quick grep will spot any mismatches:

If anything shows up, bump them to string to avoid silent any widening.


🏁 Script executed:

#!/bin/bash
# Find TypeScript declarations that still accept number for getInvitationInfo / discussionId
rg -n $'(getInvitationInfo|getInvitationJoin|postInviatationJoin).*number'

Length of output: 75


Let’s dig into the actual declarations for invitationQueryOption and the API methods (the original grep may have missed typos/spelling). We’ll pull a few lines around each definition:


🏁 Script executed:

#!/bin/bash
# Show the signature & usage of invitationQueryOption
rg -n "invitationQueryOption" -C 3

# Find and inspect the API methods that take discussionId
rg -n "(getInvitationInfo|getInvitationJoin|postInvitationJoin)" -C 5

Length of output: 11972


Type consistency confirmed for discussionId

All downstream functions—including invitationQueryOption, invitationApi.getInvitationInfo, and postInviatationJoin—are already declared with discussionId: string. A code-wide search found no remaining number parameters for these methods, so no further changes are needed.

frontend/apps/client/src/features/discussion/api/queryOptions.ts (1)

8-11: Confirm that all call-sites now pass a string

The signature switch to string looks good, but any lingering number usages will now type-error or force as unknown as. Please run a quick grep/TS compile to ensure nothing still expects number.

frontend/apps/client/src/features/discussion/api/keys.ts (1)

27-27: LGTM – consistent string key

Key generator correctly mirrors the new ID type.

frontend/apps/client/src/features/discussion/api/mutations.ts (1)

30-35: Mutation type updated – ensure backend accepts string

discussionId is now forwarded as a string; verify that invitationApi.postInviatationJoin and the backend route also expect a stringified ID.

frontend/apps/client/src/features/shared-schedule/ui/OngoingSchedules/ScheduleDetails.tsx (2)

23-23: Prop type update is consistent with the global refactor
discussionId is now declared as string, matching the new model definition. 👍


47-51: Consider normalising discussion.id to avoid mixed-type URIs
discussion.id is now typed as string; confirm that no .toString() is hiding an underlying number elsewhere to keep URL generation predictable.

frontend/apps/client/src/features/discussion/model/index.ts (2)

40-41: Conversion of id to string matches API encryption output
Good alignment with the encrypted / randomised ID feature.


80-90:

Details

❌ Incorrect review comment

DiscussionConfirmResponse schema is no longer exported – potential breaking change
Other modules that previously imported this schema for runtime validation will now fail to compile.

-const DiscussionConfirmResponse = z.object({ ... });
+export const DiscussionConfirmResponse = z.object({ ... });

🏁 Script executed:

#!/bin/bash
set -e

echo "=== Export statements in model/index.ts ==="
rg "^export" -n frontend/apps/client/src/features/discussion/model/index.ts || true

echo
echo "=== File content lines 60-120 in model/index.ts ==="
sed -n '60,120p' frontend/apps/client/src/features/discussion/model/index.ts || true

echo
echo "=== Occurrences of DiscussionConfirmResponse in codebase ==="
rg "DiscussionConfirmResponse" -n .

Length of output: 4662


Ignore: No value-level imports of the schema exist

  • Only the type alias (export type DiscussionConfirmResponse) is imported elsewhere via import type
  • The Zod schema (const DiscussionConfirmResponse = …) isn’t used or imported in any other module
  • No runtime imports are broken, so no change is needed here

Likely an incorrect or invalid review comment.

@dioo1461
Copy link
Contributor Author

dioo1461 commented Jun 16, 2025

변경사항 있습니다!!

  1. .env.local -> .env 로 환경변수 파일명 수정해주세요!
  2. server 워크스페이스 루트에 .env 파일 생성 후, AES_GCM_KEY_OF_DISCUSSION_ID='WM0Uhu4/wuJUxJ5c320EUj4Bo+5Fz6cYo1doOB2+oPI=' 추가해주세요! (키값은 임의로 생성된 32byte 문자열입니다, dev 환경에서만 쓸거니 노출해도 상관 X, 근데 배포환경에서는 키값을 뭘로할지도 생각해보긴 해야겠네요)
  3. client에서 사용되는 env에서, VITE_API_URL 의 값을 VITE_CLIENT_URL의 값과 동일하게 바꿔주세요! (∵이제 모든 api 요청이 express 서버를 거치게 됩니다. 백엔드에 직접 요청하지 않음)

/+ 현재 client 패키지에서 사용하는 .env 파일 위치가 frontend 루트로 지정돼 있는데, client 워크스페이스 루트에서 .env를 관리하면 어떨까 싶습니다!

Copy link
Contributor

@hamo-o hamo-o left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!!
ㅜㅜ 서버는 진짜 코드 읽으면서 배우네요. 코드 스타일 관련해서만 피드백 남겼습니다..!

+ discussionIdTransformer 폴더 대신, middlewares/discussionId/ 만들어서 도메인별로 미들웨어를 관리하면 어떨까요?

Copy link
Contributor

Choose a reason for hiding this comment

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

p3;
/ 이나 . 등을 상수로 관리해도 좋을 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

/는 URL 문법에 따른 분리자이므로 상수화하는 게 오히려 가독성을 해칠 수 있을 것 같습니다..! 다만 암호문의 분리자로 사용하는 .는 유지보수 측면에서 효과가 있을 것 같아요. 반영하겠습니다!

Copy link
Contributor

Choose a reason for hiding this comment

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

넵 좋습니다!
그런데 생각해 보니 함수명과 매개변수명의 url 이라는 워딩이 조금 어색한 것 같습니다. path로 대체하면 어떨까요?

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아 좋습니다! path로 명명하는 게 더 명확하겠네요

Comment on lines +10 to +12
if (!targetObject || typeof targetObject !== 'object') {
return targetObject;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

p3; 요 조건을 최상단으로 올리는게 가독성이 더 좋을 것 같아요.

Comment on lines 13 to 22
const newData: Record<string, unknown> = {}; for (const key in targetObject) {
// 만약 target key이면 해당 값에 대해 변환 함수 적용
if (key === targetKey) {
newData[key] = manipulateFunc(String(targetObject[key]));
} else {
// 그 외의 값은 재귀적으로 처리
newData[key] = manipulateValueOfObject(targetKey, targetObject[key], manipulateFunc);
}
}
return newData as T;
Copy link
Contributor

Choose a reason for hiding this comment

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

p3;
reduce 고차함수 활용이 더 깔끔해 보입니다!

Suggested change
const newData: Record<string, unknown> = {}; for (const key in targetObject) {
// 만약 target key이면 해당 값에 대해 변환 함수 적용
if (key === targetKey) {
newData[key] = manipulateFunc(String(targetObject[key]));
} else {
// 그 외의 값은 재귀적으로 처리
newData[key] = manipulateValueOfObject(targetKey, targetObject[key], manipulateFunc);
}
}
return newData as T;
return Object.entries(targetObject).reduce((newData, [key, value]) => {
if (key === targetKey) newData[key] = manipulateFunc(String(value));
else newData[key] = manipulateValueOfObject(targetKey, value, manipulateFunc);
return newData;
}, {} as Record<string, unknown>) as T;

Comment on lines 9 to 11
while (encodedText.length % 4) {
encodedText += '=';
}
Copy link
Contributor

Choose a reason for hiding this comment

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

p3; 4의 배수 만들어주기. 요런 유틸로 따로 뺄 수 있을 것 같습니다.

Suggested change
while (encodedText.length % 4) {
encodedText += '=';
}
const padBase64 = (str: string): string => {
const padLength = (4 - (str.length % 4)) % 4;
return str + '='.repeat(padLength);
};

dts: false,
bundle: false,
bundle: true,
external: ['vite', 'dotenv'],
Copy link
Contributor

Choose a reason for hiding this comment

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

p3;
서버에서는 개발환경에서만 vite를 쓰고 있는데 넣으신 이유가 있을까요?

Copy link
Contributor Author

@dioo1461 dioo1461 Jul 13, 2025

Choose a reason for hiding this comment

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

 if (!isProduction) {
    const { createServer } = await import('vite');
    ...

서버 코드에 dev일 때에만 vite를 동적 import하긴 하지만, tsup이 번들링할 때 런타임에서의 분기는 고려하지 않기 때문에 해당 구문(await import('vite'))까지 함께 처리하려고 시도합니다.
때문에 tsup은 vite 코드를 함께 번들링하려고 하는데, server 워크스페이스에는 vite가 설치되어있지 않기 때문에 빌드에 실패합니다.

tsup.config.ts에서 bundle: true로 정의하고 있었는데, 이 옵션을 키면 프로젝트에 포함된 모든 외부 의존성을 번들에 함께 묶는다고 합니다. 이를 false로 바꿔주어 해결했습니다.
저희 배포할 때 node_modules 설치해주는 과정이 있으니까 괜찮겠죠..?

+ 정정합니다.. bundle: false 설정하니 외부 의존성뿐만 아니라 index.js가 참조하는 모든 모듈을 번들에서 제외해버리네요..
결국 다시 원래대로 돌렸습니다. 더 좋은 방법을 알고 계시다면 알려주세요,,

Copy link
Contributor

Choose a reason for hiding this comment

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

아 말씀하신게 맞습니다! 지금 코드 유지하면 될 것 같습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵!!

return Buffer.concat(chunks).toString('utf-8');
} catch (err) {
if (err instanceof Error) {
throw new Error(`Failed to collect response: ${err.message}`);
Copy link
Contributor

Choose a reason for hiding this comment

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

추후에 서버에서 던져지는 에러들은 따로 클래스로 만들어도 좋을 것 같네요.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
frontend/apps/server/src/utils/stringTransformer.ts (1)

17-20: 패딩 함수 구현이 올바릅니다.

Base64 패딩 계산 로직이 정확하게 구현되었습니다.

frontend/apps/server/src/utils/object.ts (1)

1-18: 구현이 깔끔하고 잘 작성되었습니다.

이전 리뷰 의견이 잘 반영되어 reduce 고차함수를 활용한 깔끔한 구현이 되었습니다. 제네릭 타입 보존과 재귀적 처리도 올바르게 구현되었습니다.

frontend/apps/server/src/discussionIdTransformer/discussionCipher.ts (1)

30-36: 복호화 함수의 입력 검증 및 오류 처리 강화 필요

학습된 피드백에 따르면, aesGcm 모듈이 오류를 다시 던지므로 추가적인 오류 처리가 필요합니다. 또한 split('.') 결과가 정확히 3개 요소를 포함하는지 검증해야 합니다.

다음과 같이 개선해보세요:

export const decryptDiscussionId = (cipherText: string) => {
+  if (!cipherText || typeof cipherText !== 'string') {
+    throw new Error('암호문이 유효하지 않습니다.');
+  }
+  
  const key = Buffer.from(aesGcmKey, 'base64');
-  const concat = base64Url.decode(cipherText);
-  const [iv, content, tag] = concat.split('.');
+  
+  try {
+    const concat = base64Url.decode(cipherText);
+    const parts = concat.split('.');
+    
+    if (parts.length !== 3) {
+      throw new Error('암호문 형식이 올바르지 않습니다.');
+    }
+    
+    const [iv, content, tag] = parts;
+    if (!iv || !content || !tag) {
+      throw new Error('암호문 구성 요소가 누락되었습니다.');
+    }
+    
    const encryptedData = { iv, content, tag };
    return aesGcm.decrypt(encryptedData, key);
+  } catch (error) {
+    throw new Error(`Discussion ID 복호화 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`);
+  }
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd63602 and 1f423ea.

⛔ Files ignored due to path filters (1)
  • frontend/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • frontend/apps/server/package.json (1 hunks)
  • frontend/apps/server/src/discussionIdTransformer/discussionCipher.ts (1 hunks)
  • frontend/apps/server/src/utils/object.ts (1 hunks)
  • frontend/apps/server/src/utils/stringTransformer.ts (1 hunks)
  • frontend/apps/server/vitest.config.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • frontend/apps/server/vitest.config.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#365
File: frontend/server/middlewares/discussionIdTransformer/discussionCipher.js:24-30
Timestamp: 2025-03-28T06:06:04.683Z
Learning: The decryptDiscussionId function can benefit from additional error handling despite the existing error handling in the aesGcm module, since aesGcm rethrows errors rather than handling them with fallbacks. Error handling for the URL processing-related functions improves application resilience.
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#53
File: frontend/src/components/Input/MultiInput.tsx:50-59
Timestamp: 2025-02-04T03:10:35.809Z
Learning: User dioo1461 prefers to receive code reviews and responses in Korean language.
frontend/apps/server/package.json (1)
Learnt from: hamo-o
PR: softeer5th/Team4-enDolphin#411
File: frontend/apps/client/package.json:7-7
Timestamp: 2025-05-27T17:33:56.448Z
Learning: In the Team4-enDolphin project, the server uses Vite as middleware in development mode, so the `dev` script in frontend/apps/client/package.json only needs to start the server (`pnpm --filter server start`) rather than running separate Vite dev server and server processes.
frontend/apps/server/src/discussionIdTransformer/discussionCipher.ts (1)
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#365
File: frontend/server/middlewares/discussionIdTransformer/discussionCipher.js:24-30
Timestamp: 2025-03-28T06:06:04.683Z
Learning: The decryptDiscussionId function can benefit from additional error handling despite the existing error handling in the aesGcm module, since aesGcm rethrows errors rather than handling them with fallbacks. Error handling for the URL processing-related functions improves application resilience.
🧬 Code Graph Analysis (1)
frontend/apps/server/src/discussionIdTransformer/discussionCipher.ts (3)
frontend/apps/server/src/envconfig.ts (1)
  • serverEnv (11-13)
frontend/apps/server/src/utils/cipher.ts (1)
  • aesGcm (4-73)
frontend/apps/server/src/utils/stringTransformer.ts (1)
  • base64Url (3-15)
🔇 Additional comments (4)
frontend/apps/server/package.json (2)

12-14: http-proxy-middleware 런타임 의존성 추가 👍

프록시 미들웨어 구현을 위해 http-proxy-middleware ^3.0.3dependencies에 추가한 결정이 적절합니다. 해당 패키지는 자체적으로 타입 선언을 포함하므로 별도 @types 패키지가 필요 없습니다.


15-19: Vitest 설정 제거 → 테스트 공백 여부 확인 필요

@endolphin/vitest-config가 빠지면서 서버 애플리케이션의 테스트 러너 설정이 사라졌습니다.
테스트 커버리지가 완전히 소실되지 않았는지, 혹은 다른 위치(루트 등)로 통합했는지 확인을 권장합니다.

frontend/apps/server/src/discussionIdTransformer/discussionCipher.ts (2)

7-10: 환경 변수 검증이 잘 구현되었습니다.

모듈 로드 시점에 필수 환경 변수를 검증하는 것은 좋은 방식입니다.


22-28: 암호화 로직이 올바르게 구현되었습니다.

AES-GCM 암호화와 Base64 URL-safe 인코딩을 조합한 구현이 보안 요구사항을 잘 충족합니다.

Comment on lines 12 to 20
export const decodeDiscussionIdOfUrl = (path: string) => {
// (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
const parts = path.split('/');
const targetIdx = 3; // discussionId가 위치하는 index
if (parts.length > targetIdx) {
parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
}
return parts.join('/');
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

URL 경로 파싱의 견고성 개선 필요

하드코딩된 인덱스 3을 사용하는 것은 다양한 URL 패턴에 대해 취약할 수 있습니다. 경로 구조가 예상과 다를 경우 잘못된 세그먼트가 복호화될 수 있습니다.

더 견고한 구현을 위해 다음과 같이 개선해보세요:

export const decodeDiscussionIdOfUrl = (path: string) => {
+  if (!path || typeof path !== 'string') {
+    return path;
+  }
  const parts = path.split('/'); 
  const targetIdx = 3; // discussionId가 위치하는 index
-  if (parts.length > targetIdx) {
+  if (parts.length > targetIdx && parts[targetIdx]) {
+    try {
      parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
+    } catch (error) {
+      console.warn('Discussion ID 복호화 실패:', error);
+      // 복호화 실패 시 원본 경로 반환
+      return path;
+    }
  }
  return parts.join('/');
};
🤖 Prompt for AI Agents
In frontend/apps/server/src/discussionIdTransformer/discussionCipher.ts around
lines 12 to 20, the current code uses a hardcoded index 3 to locate the
discussionId in the URL path, which is fragile for varying URL structures. To
fix this, identify the 'discussion' segment dynamically in the path array, then
decrypt the segment immediately following it. This approach ensures the correct
segment is decrypted regardless of URL pattern changes, improving robustness.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
frontend/apps/server/src/middlewares/discussionIdTransformer/index.ts (1)

7-8: TODO 주석을 제거하세요

이미 serverEnv.BASE_URL을 사용하여 환경변수로 관리하고 있으므로 TODO 주석이 더 이상 유효하지 않습니다.

 export const discussionIdTransformer = createProxyMiddleware({
-  // TODO: env로 분리
   target: `${serverEnv.BASE_URL}/api`,
frontend/apps/server/src/middlewares/discussionIdTransformer/proxyHandlers.ts (1)

8-12: import 경로에서 .js 확장자를 제거하세요

TypeScript 프로젝트에서 .js 확장자는 불필요하며 잠재적인 문제를 일으킬 수 있습니다.

 import {
   decodeDiscussionIdOfUrl,
   decryptDiscussionId,
   encryptDiscussionId,
-} from './discussionCipher.js';
+} from './discussionCipher';
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f423ea and 1ca97f9.

📒 Files selected for processing (8)
  • frontend/apps/server/src/envconfig.ts (1 hunks)
  • frontend/apps/server/src/index.ts (2 hunks)
  • frontend/apps/server/src/middlewares/discussionIdTransformer/discussionCipher.ts (1 hunks)
  • frontend/apps/server/src/middlewares/discussionIdTransformer/index.ts (1 hunks)
  • frontend/apps/server/src/middlewares/discussionIdTransformer/proxyHandlers.ts (1 hunks)
  • frontend/apps/server/src/utils/isProduction.ts (1 hunks)
  • frontend/apps/server/src/utils/stringTransformer.ts (1 hunks)
  • frontend/apps/server/tsup.config.ts (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • frontend/apps/server/src/utils/isProduction.ts
  • frontend/apps/server/src/envconfig.ts
  • frontend/apps/server/src/utils/stringTransformer.ts
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#365
File: frontend/server/middlewares/discussionIdTransformer/discussionCipher.js:24-30
Timestamp: 2025-03-28T06:06:04.683Z
Learning: The decryptDiscussionId function can benefit from additional error handling despite the existing error handling in the aesGcm module, since aesGcm rethrows errors rather than handling them with fallbacks. Error handling for the URL processing-related functions improves application resilience.
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#53
File: frontend/src/components/Input/MultiInput.tsx:50-59
Timestamp: 2025-02-04T03:10:35.809Z
Learning: User dioo1461 prefers to receive code reviews and responses in Korean language.
frontend/apps/server/tsup.config.ts (3)
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#426
File: frontend/packages/ui/vite.config.ts:6-0
Timestamp: 2025-06-13T07:39:57.840Z
Learning: `frontend/packages/ui/vite.config.ts` 에서 `defineConfig` 를 `vitest/config` 로부터 가져오는 것은 정상적인 패턴이다. `vitest/config` 는 Vite의 `defineConfig` 를 re-export 하며, `test` 옵션 타입을 추가로 제공한다.
Learnt from: hamo-o
PR: softeer5th/Team4-enDolphin#411
File: frontend/apps/client/package.json:7-7
Timestamp: 2025-05-27T17:33:56.448Z
Learning: In the Team4-enDolphin project, the server uses Vite as middleware in development mode, so the `dev` script in frontend/apps/client/package.json only needs to start the server (`pnpm --filter server start`) rather than running separate Vite dev server and server processes.
Learnt from: hamo-o
PR: softeer5th/Team4-enDolphin#89
File: frontend/src/routes/__root.tsx:18-20
Timestamp: 2025-02-04T09:08:12.489Z
Learning: In the Team4-enDolphin project, environment-specific features should be lazy-loaded and conditionally imported based on `defaultENV.MODE` to optimize bundle size and enable tree-shaking in production builds.
frontend/apps/server/src/index.ts (4)
Learnt from: hamo-o
PR: softeer5th/Team4-enDolphin#89
File: frontend/src/routes/__root.tsx:18-20
Timestamp: 2025-02-04T09:08:12.489Z
Learning: In the Team4-enDolphin project, environment-specific features should be lazy-loaded and conditionally imported based on `defaultENV.MODE` to optimize bundle size and enable tree-shaking in production builds.
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#365
File: frontend/server/middlewares/discussionIdTransformer/discussionCipher.js:24-30
Timestamp: 2025-03-28T06:06:04.683Z
Learning: The decryptDiscussionId function can benefit from additional error handling despite the existing error handling in the aesGcm module, since aesGcm rethrows errors rather than handling them with fallbacks. Error handling for the URL processing-related functions improves application resilience.
Learnt from: hamo-o
PR: softeer5th/Team4-enDolphin#411
File: frontend/apps/client/package.json:7-7
Timestamp: 2025-05-27T17:33:56.448Z
Learning: In the Team4-enDolphin project, the server uses Vite as middleware in development mode, so the `dev` script in frontend/apps/client/package.json only needs to start the server (`pnpm --filter server start`) rather than running separate Vite dev server and server processes.
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#426
File: frontend/packages/ui/vite.config.ts:6-0
Timestamp: 2025-06-13T07:39:57.840Z
Learning: `frontend/packages/ui/vite.config.ts` 에서 `defineConfig` 를 `vitest/config` 로부터 가져오는 것은 정상적인 패턴이다. `vitest/config` 는 Vite의 `defineConfig` 를 re-export 하며, `test` 옵션 타입을 추가로 제공한다.
frontend/apps/server/src/middlewares/discussionIdTransformer/index.ts (1)
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#365
File: frontend/server/middlewares/discussionIdTransformer/discussionCipher.js:24-30
Timestamp: 2025-03-28T06:06:04.683Z
Learning: The decryptDiscussionId function can benefit from additional error handling despite the existing error handling in the aesGcm module, since aesGcm rethrows errors rather than handling them with fallbacks. Error handling for the URL processing-related functions improves application resilience.
frontend/apps/server/src/middlewares/discussionIdTransformer/discussionCipher.ts (1)
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#365
File: frontend/server/middlewares/discussionIdTransformer/discussionCipher.js:24-30
Timestamp: 2025-03-28T06:06:04.683Z
Learning: The decryptDiscussionId function can benefit from additional error handling despite the existing error handling in the aesGcm module, since aesGcm rethrows errors rather than handling them with fallbacks. Error handling for the URL processing-related functions improves application resilience.
frontend/apps/server/src/middlewares/discussionIdTransformer/proxyHandlers.ts (1)
Learnt from: dioo1461
PR: softeer5th/Team4-enDolphin#365
File: frontend/server/middlewares/discussionIdTransformer/discussionCipher.js:24-30
Timestamp: 2025-03-28T06:06:04.683Z
Learning: The decryptDiscussionId function can benefit from additional error handling despite the existing error handling in the aesGcm module, since aesGcm rethrows errors rather than handling them with fallbacks. Error handling for the URL processing-related functions improves application resilience.
🧬 Code Graph Analysis (3)
frontend/apps/server/src/index.ts (1)
frontend/apps/server/src/middlewares/discussionIdTransformer/index.ts (1)
  • discussionIdTransformer (6-16)
frontend/apps/server/src/middlewares/discussionIdTransformer/discussionCipher.ts (3)
frontend/apps/server/src/envconfig.ts (1)
  • serverEnv (15-18)
frontend/apps/server/src/utils/cipher.ts (1)
  • aesGcm (4-73)
frontend/apps/server/src/utils/stringTransformer.ts (1)
  • base64Url (3-15)
frontend/apps/server/src/middlewares/discussionIdTransformer/proxyHandlers.ts (3)
frontend/apps/server/src/middlewares/discussionIdTransformer/discussionCipher.ts (3)
  • decodeDiscussionIdOfUrl (12-20)
  • decryptDiscussionId (30-36)
  • encryptDiscussionId (22-28)
frontend/apps/server/src/utils/object.ts (1)
  • manipulateValueOfObject (1-18)
frontend/apps/server/src/utils/collectResponse.ts (1)
  • collectResponse (15-30)
🔇 Additional comments (5)
frontend/apps/server/tsup.config.ts (1)

11-12: 번들링 설정이 적절합니다!

bundle: true로 설정하고 동적으로 import되는 vite와 환경변수 로딩을 위한 dotenv를 external로 지정한 것은 올바른 접근입니다.

frontend/apps/server/src/index.ts (1)

74-77: 미들웨어 설정이 올바르게 구성되었습니다!

express.json()을 프록시 미들웨어보다 먼저 적용한 것이 적절하며, 주석으로 각 미들웨어의 목적을 명확히 설명한 점이 좋습니다.

frontend/apps/server/src/middlewares/discussionIdTransformer/discussionCipher.ts (1)

22-28: 암호화 구현이 적절합니다!

AES-GCM 암호화 컴포넌트들을 점(.)으로 연결하고 URL-safe base64로 인코딩하는 방식이 안전합니다.

frontend/apps/server/src/middlewares/discussionIdTransformer/proxyHandlers.ts (2)

53-69: API 응답 처리가 실용적으로 구현되었습니다!

서로 다른 API 엔드포인트의 응답 형식 차이를 적절히 처리하고 있으며, 변환 실패 시 원본 데이터를 반환하는 방어적 프로그래밍이 좋습니다.


17-22: rewriteDiscussionPath은 생성·조회 경로 모두 처리합니다

rewriteDiscussionPath/v1/discussion 접두사로 시작하는 모든 요청(/v1/discussion/v1/discussion/{id})에 대해 decodeDiscussionIdOfUrl을 호출하므로, DISCUSSION_GET_ENDPOINT_REGEX에 매칭되는 조회 경로도 정상적으로 처리됩니다. 별도 수정은 필요 없습니다.

Likely an incorrect or invalid review comment.

Comment on lines 12 to 20
export const decodeDiscussionIdOfUrl = (path: string) => {
// (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
const parts = path.split('/');
const targetIdx = 3; // discussionId가 위치하는 index
if (parts.length > targetIdx) {
parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
}
return parts.join('/');
};
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

decryptDiscussionId 오류 처리를 추가하세요

decryptDiscussionId가 실패할 경우를 대비한 오류 처리가 필요합니다. 잘못된 형식의 ID나 복호화 실패 시 적절한 처리가 없으면 전체 요청이 실패할 수 있습니다.

 export const decodeDiscussionIdOfUrl = (path: string) => {
   // (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
   const parts = path.split('/'); 
   const targetIdx = 3; // discussionId가 위치하는 index
   if (parts.length > targetIdx) {
-    parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
+    try {
+      parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
+    } catch (error) {
+      // 복호화 실패 시 원본 경로 반환 또는 에러 전파 결정 필요
+      console.error('Discussion ID 복호화 실패:', error);
+      // Option 1: 원본 경로 반환
+      // return path;
+      // Option 2: 에러 재발생
+      throw error;
+    }
   }
   return parts.join('/');
 };

Add error handling around decryptDiscussionId

decryptDiscussionId can throw (e.g., on invalid or malformed IDs), which would cause the entire request to fail. Wrap the call in a try/catch to handle decryption errors gracefully—either by returning the original path segment or by propagating a controlled error.

Locations to update:

  • frontend/apps/server/src/middlewares/discussionIdTransformer/discussionCipher.ts: around line 16 (inside decodeDiscussionIdOfUrl)

Suggested diff:

 export const decodeDiscussionIdOfUrl = (path: string) => {
   // (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
   const parts = path.split('/');
   const targetIdx = 3; // discussionId가 위치하는 index
   if (parts.length > targetIdx) {
-    parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
+    try {
+      parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
+    } catch (error) {
+      console.error('Discussion ID 복호화 실패:', error);
+      // 복호화 실패 시 원본 경로 그대로 반환하거나, 필요에 따라 에러 재전파:
+      // return path;
+      throw error;
+    }
   }
   return parts.join('/');
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const decodeDiscussionIdOfUrl = (path: string) => {
// (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
const parts = path.split('/');
const targetIdx = 3; // discussionId가 위치하는 index
if (parts.length > targetIdx) {
parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
}
return parts.join('/');
};
export const decodeDiscussionIdOfUrl = (path: string) => {
// (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
const parts = path.split('/');
const targetIdx = 3; // discussionId가 위치하는 index
if (parts.length > targetIdx) {
try {
parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
} catch (error) {
console.error('Discussion ID 복호화 실패:', error);
// 복호화 실패 시 원본 경로 그대로 반환하거나, 필요에 따라 에러 재전파:
// return path;
throw error;
}
}
return parts.join('/');
};
🤖 Prompt for AI Agents
In
frontend/apps/server/src/middlewares/discussionIdTransformer/discussionCipher.ts
around lines 12 to 20, the call to decryptDiscussionId can throw errors on
invalid input, causing request failures. Wrap the decryptDiscussionId call
inside a try/catch block; if an error occurs, either keep the original path
segment unchanged or throw a controlled error with a clear message. This will
ensure graceful error handling without breaking the entire request.

dioo1461 added 2 commits July 25, 2025 20:43
bff에서 해당 요청이 백엔드에 보내지지 않도록 가로채어, 클라이언트에 404를 상태코드로 가지는 response를 반환하도록 처리함
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (6)
frontend/apps/server/src/middlewares/discussionId/index.ts (1)

24-35: Improve error handling and consider path validation.

The verifier middleware has good basic error handling, but consider these improvements:

  1. The error message "Invalid discussion id" might be too generic - consider logging the actual error for debugging
  2. The hard-coded 404 status might not always be appropriate for decryption failures
  3. Consider validating the path structure before attempting decryption
export const discussionIdVerifier: RequestHandler = (req, res, next) => {
  if (req.path.startsWith(DISCUSSION_CREATE_ENDPOINT)) {
    try {
      decodeDiscussionIdOfUrl(req.path);
      return next();
-    } catch {
+    } catch (error) {
+      // Log the actual error for debugging while keeping user-facing message generic
+      console.error('Discussion ID verification failed:', error);
      res.status(404).json({ code: 'NOT_FOUND', message: 'Invalid discussion id' });
      return;
    }
  }
  return next();
};
frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts (1)

22-28: Consider adding input validation for encryption function.

The encryption function should validate its input to prevent potential issues with empty or invalid strings.

export const encryptDiscussionId = (text: string) => {
+  if (!text || text.trim() === '') {
+    throw new Error('Discussion ID cannot be empty');
+  }
  const key = Buffer.from(aesGcmKey, 'base64');
  const { iv, content, tag } = aesGcm.encrypt(text, key);
  const concat = `${iv}.${content}.${tag}`;
  const cipherText = base64Url.encode(concat);
  return cipherText;
};
frontend/apps/server/src/middlewares/discussionId/proxyHandlers.ts (4)

14-16: Consider making endpoint patterns more maintainable.

The hard-coded endpoint constants are good, but consider centralizing all API endpoint patterns in a single configuration file for better maintainability.


17-22: Path rewriting logic is correct but could be more explicit.

The path rewriting logic is sound, but the function name and logic could be more explicit about what it does.

export const rewriteDiscussionPath = (path: string): string => {
-  if (path.startsWith(DISCUSSION_CREATE_ENDPOINT)) {
+  // Only rewrite paths that contain discussion IDs that need decryption
+  if (path.startsWith(DISCUSSION_CREATE_ENDPOINT) && path !== DISCUSSION_CREATE_ENDPOINT) {
    return decodeDiscussionIdOfUrl(path);
  }
  return path;
};

24-41: Good request handling but missing content-type validation.

The request handling logic is well-implemented with proper fallback for non-object bodies. However, consider adding content-type validation for security.

export const handleProxyReq = (
  proxyReq: ClientRequest,
  req: Request,
): void => {
+  // Validate content-type for JSON requests
+  const contentType = req.headers['content-type'];
+  if (contentType && !contentType.includes('application/json')) {
+    req.pipe(proxyReq);
+    return;
+  }
+  
  if (!req.body || typeof req.body !== 'object') {
    req.pipe(proxyReq);
    return;
  }
  
  const data = manipulateValueOfObject('discussionId', req.body, decryptDiscussionId);
  const bodyString = JSON.stringify(data);
  const { 'content-length': _, ...headers } = req.headers;
+  // Set correct content-length for modified body
+  proxyReq.setHeader('content-length', Buffer.byteLength(bodyString));
  for (const[key, value] of Object.entries(headers)) {
    if (value) proxyReq.setHeader(key, value);
  }
  proxyReq.write(bodyString);
  proxyReq.end();
};

43-69: Response handling is robust but consider logging transformation failures.

The response handling logic is well-implemented with proper error fallback. The manual field name handling for different APIs is a practical solution to API inconsistencies.

export const handleProxyRes = async (
  proxyRes: IncomingMessage & { headers: IncomingHttpHeaders },
  req: Request, 
  res: ServerResponse,
) => {
  const statusCode = proxyRes.statusCode ?? 502;
  const { 'content-length': _, ...headers } = proxyRes.headers;
  res.writeHead(statusCode, headers);
  const stringData = await collectResponse(proxyRes);

  // discussion id의 필드명을 'discussionId'가 아닌 'id'로 반환하는 API가 존재하여, 이에 대해 manual하게 분기처리
  const isDiscussionApi = (
    req.url === DISCUSSION_CREATE_ENDPOINT || DISCUSSION_GET_ENDPOINT_REGEX.test(req.url)
  );
  const targetKey = isDiscussionApi ? 'id' : 'discussionId';
  try {
    const result = manipulateValueOfObject(
      targetKey,
      JSON.parse(stringData), 
      encryptDiscussionId,
    );
    res.end(JSON.stringify(result));
  } catch (err) {
+    // Log transformation failures for debugging
+    console.warn('Failed to transform discussion IDs in response:', err);
    // 변환 실패 시 원본 데이터를 그대로 반환
    res.end(stringData);
  };
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35052d3 and 8f78da1.

📒 Files selected for processing (6)
  • frontend/apps/client/src/utils/error/HTTPError.ts (1 hunks)
  • frontend/apps/client/src/utils/error/handleError.ts (1 hunks)
  • frontend/apps/server/src/index.ts (2 hunks)
  • frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts (1 hunks)
  • frontend/apps/server/src/middlewares/discussionId/index.ts (1 hunks)
  • frontend/apps/server/src/middlewares/discussionId/proxyHandlers.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • frontend/apps/client/src/utils/error/HTTPError.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/apps/server/src/index.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
frontend/apps/server/src/middlewares/discussionId/index.ts (3)
frontend/apps/server/src/envconfig.ts (1)
  • serverEnv (15-18)
frontend/apps/server/src/middlewares/discussionId/proxyHandlers.ts (4)
  • rewriteDiscussionPath (17-22)
  • handleProxyReq (24-41)
  • handleProxyRes (43-69)
  • DISCUSSION_CREATE_ENDPOINT (14-14)
frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts (1)
  • decodeDiscussionIdOfUrl (12-20)
frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts (3)
frontend/apps/server/src/envconfig.ts (1)
  • serverEnv (15-18)
frontend/apps/server/src/utils/cipher.ts (1)
  • aesGcm (4-73)
frontend/apps/server/src/utils/stringTransformer.ts (1)
  • base64Url (3-15)
frontend/apps/server/src/middlewares/discussionId/proxyHandlers.ts (3)
frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts (3)
  • decodeDiscussionIdOfUrl (12-20)
  • decryptDiscussionId (30-36)
  • encryptDiscussionId (22-28)
frontend/apps/server/src/utils/object.ts (1)
  • manipulateValueOfObject (1-18)
frontend/apps/server/src/utils/collectResponse.ts (1)
  • collectResponse (15-30)
🔇 Additional comments (4)
frontend/apps/client/src/utils/error/handleError.ts (2)

27-27: Consider the UX implications of global 404 redirects.

The implementation follows the established pattern for error handling, which is good for consistency. However, redirecting to /not-found for all 404 errors might be too aggressive - not every 404 response should trigger a full page redirect (e.g., failed API calls for optional data, background requests, etc.).

Consider whether this global redirect behavior aligns with your UX requirements, or if it should be more selective based on the context of the request.


32-32: Good formatting improvement.

The added blank line improves readability by providing visual separation between the error type check and the notification logic.

frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts (1)

7-10: Good practice: Early key validation prevents runtime failures.

The early validation of the AES-GCM key at module load time is excellent - it ensures the application fails fast if misconfigured rather than failing silently during runtime.

frontend/apps/server/src/middlewares/discussionId/proxyHandlers.ts (1)

54-57: URL Matching Logic Verified
The current logic correctly distinguishes the create (POST /v1/discussion) and get (GET /v1/discussion/:id) endpoints—using id—and defaults to discussionId for all other routes under /v1/discussion/.... We discovered these discussion-related endpoints in the codebase:

  • /v1/discussion
  • /v1/discussion/${discussionId}
  • /v1/discussion/${discussionId}/candidate-event/(calendar|rank|details)
  • /v1/discussion/${discussionId}/participants
  • /v1/discussion/${discussionId}/invite
  • /v1/discussion/${discussionId}/join
  • /v1/discussion/${id}/(confirm|restart|role|shared-event)

The DISCUSSION_GET_ENDPOINT_REGEX only matches the exact /v1/discussion/:id path (with optional trailing slash), so all deeper paths fall into the discussionId branch as intended. No gaps found.

Comment on lines +12 to +20
export const decodeDiscussionIdOfUrl = (path: string) => {
// (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
const parts = path.split('/');
const targetIdx = 3; // discussionId가 위치하는 index
if (parts.length > targetIdx) {
parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
}
return parts.join('/');
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider making the URL structure more flexible and add validation.

The current implementation assumes a fixed URL structure with the discussion ID always at index 3. This could be fragile if the API structure changes.

export const decodeDiscussionIdOfUrl = (path: string) => {
  const parts = path.split('/'); 
-  const targetIdx = 3; // discussionId가 위치하는 index
-  if (parts.length > targetIdx) {
-    parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
-  }
+  const targetIdx = 3; // discussionId가 위치하는 index
+  if (parts.length <= targetIdx) {
+    throw new Error('Invalid URL structure for discussion ID extraction');
+  }
+  if (!parts[targetIdx] || parts[targetIdx].trim() === '') {
+    throw new Error('Discussion ID not found in expected URL position');
+  }
+  parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
  return parts.join('/');
};
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const decodeDiscussionIdOfUrl = (path: string) => {
// (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
const parts = path.split('/');
const targetIdx = 3; // discussionId가 위치하는 index
if (parts.length > targetIdx) {
parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
}
return parts.join('/');
};
export const decodeDiscussionIdOfUrl = (path: string) => {
// (출력 예시) parts: ['', 'v1', 'discussion', '{encoded_discussion_id}', 'invite']
const parts = path.split('/');
const targetIdx = 3; // discussionId가 위치하는 index
if (parts.length <= targetIdx) {
throw new Error('Invalid URL structure for discussion ID extraction');
}
if (!parts[targetIdx] || parts[targetIdx].trim() === '') {
throw new Error('Discussion ID not found in expected URL position');
}
parts[targetIdx] = decryptDiscussionId(parts[targetIdx]);
return parts.join('/');
};
🤖 Prompt for AI Agents
In frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts around
lines 12 to 20, the code assumes the discussion ID is always at index 3 in the
URL path, which is fragile. Refactor the function to dynamically locate the
discussion ID segment by searching for the 'discussion' keyword in the path
parts array, then decrypt the following segment if it exists. Also, add
validation to ensure the expected segments exist before attempting decryption to
avoid errors with unexpected URL structures.

Comment on lines +30 to +36
export const decryptDiscussionId = (cipherText: string) => {
const key = Buffer.from(aesGcmKey, 'base64');
const concat = base64Url.decode(cipherText);
const [iv, content, tag] = concat.split('.');
const encryptedData = { iv, content, tag };
return aesGcm.decrypt(encryptedData, key);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add proper error handling and input validation for decryption.

The decryption function needs better error handling and input validation to prevent crashes and provide meaningful error messages.

export const decryptDiscussionId = (cipherText: string) => {
+  if (!cipherText || cipherText.trim() === '') {
+    throw new Error('Cipher text cannot be empty');
+  }
+  
+  try {
    const key = Buffer.from(aesGcmKey, 'base64');
    const concat = base64Url.decode(cipherText);
    const [iv, content, tag] = concat.split('.');
+    
+    if (!iv || !content || !tag) {
+      throw new Error('Invalid cipher text format');
+    }
+    
    const encryptedData = { iv, content, tag };
    return aesGcm.decrypt(encryptedData, key);
+  } catch (error) {
+    if (error instanceof Error) {
+      throw new Error(`Failed to decrypt discussion ID: ${error.message}`);
+    }
+    throw new Error('Failed to decrypt discussion ID: Unknown error');
+  }
};
🤖 Prompt for AI Agents
In frontend/apps/server/src/middlewares/discussionId/discussionCipher.ts around
lines 30 to 36, the decryptDiscussionId function lacks input validation and
error handling. Add checks to ensure the cipherText is a valid string and
properly formatted before processing. Wrap the decryption logic in a try-catch
block to catch and handle any errors, returning a clear error message or
handling the failure gracefully instead of allowing the function to crash.

Comment on lines +13 to +22
export const discussionIdTransformer = createProxyMiddleware({
target: `${serverEnv.BASE_URL}/api`,
changeOrigin: true,
selfHandleResponse: true,
pathRewrite: rewriteDiscussionPath,
on: {
proxyReq: handleProxyReq,
proxyRes: handleProxyRes,
},
});
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify BASE_URL configuration and consider security implications.

The proxy middleware configuration looks correct, but there are a few considerations:

  1. The selfHandleResponse: true setting is appropriate since you're manually handling responses for encryption
  2. Consider adding request/response size limits to prevent potential DoS attacks
  3. Verify that serverEnv.BASE_URL is properly validated and doesn't allow malicious redirects

🏁 Script executed:

#!/bin/bash
# Check if BASE_URL validation exists in envconfig
rg -A 5 -B 5 "BASE_URL.*validation|validate.*BASE_URL" frontend/apps/server/src/

Length of output: 82


🏁 Script executed:

#!/bin/bash
# Locate and inspect the serverEnv definition to verify BASE_URL validation
file=$(rg -l "export const serverEnv" -R frontend/apps/server/src | head -n 1)
echo "Found serverEnv in: $file"
sed -n '1,200p' "$file"

Length of output: 601


🏁 Script executed:

#!/bin/bash
# Inspect imports in discussionIdTransformer to locate serverEnv source
sed -n '1,50p' frontend/apps/server/src/middlewares/discussionId/index.ts

Length of output: 1034


🏁 Script executed:

#!/bin/bash
# Locate envconfig file defining serverEnv
env_file=$(fd 'envconfig\.(ts|js)$' frontend/apps/server/src)
echo "Found envconfig file(s):"
echo "$env_file"
# Display the definition of serverEnv including any validation logic
for file in $env_file; do
  echo "==== $file ===="
  sed -n '1,200p' "$file"
done

Length of output: 912


Ensure BASE_URL is validated and enforce size limits

There is currently no validation on process.env.BASE_URL in envconfig.ts, and the proxy middleware has no request/response size limits. Both gaps can expose the service to misconfiguration or DoS vectors.

Recommended actions:

  • In frontend/apps/server/src/envconfig.ts: validate that BASE_URL is present and a well-formed HTTP(S) URL. For example, use Zod or a simple regex:
    import { z } from 'zod';
    
    const EnvSchema = z.object({
      BASE_URL: z.string().url(),
      AES_GCM_KEY_OF_DISCUSSION_ID: z.string().min(16),
    });
    export const serverEnv = EnvSchema.parse({
      BASE_URL: process.env.BASE_URL,
      AES_GCM_KEY_OF_DISCUSSION_ID: process.env.AES_GCM_KEY_OF_DISCUSSION_ID,
    });
  • In your Express setup (before the proxy), limit payload size:
    import express from 'express';
    const app = express();
    app.use(express.json({ limit: '1mb' }));
    app.use(express.urlencoded({ limit: '1mb', extended: true }));
    // then mount discussionIdTransformer…
  • Optionally, if you need per-route limits, wrap the proxy in a size-checking middleware or configure your upstream API gateway to cap request/response bodies.

These changes will prevent invalid BASE_URL values and mitigate large-payload DoS risks.

🤖 Prompt for AI Agents
In frontend/apps/server/src/envconfig.ts, add validation for BASE_URL to ensure
it is present and a valid HTTP(S) URL using a schema validation library like
Zod. Then, in the Express server setup before mounting the
discussionIdTransformer proxy middleware in
frontend/apps/server/src/middlewares/discussionId/index.ts, add middleware to
limit JSON and URL-encoded payload sizes to 1mb to prevent large-payload DoS
risks. Optionally, consider adding per-route size limits or configuring the API
gateway for further protection.

@dioo1461 dioo1461 merged commit 32557d7 into dev Jul 27, 2025
2 checks passed
@dioo1461 dioo1461 deleted the feature/fe/encode-discussion-id branch July 27, 2025 12:09
dioo1461 added a commit that referenced this pull request Jul 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🖥️ FE Frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants