-
Notifications
You must be signed in to change notification settings - Fork 1
[FE-Feat] 논의 id 난수화 #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FE-Feat] 논의 id 난수화 #365
Changes from all commits
3145902
94b6336
abc43cc
0f1e4ef
13746d3
640052f
971923c
b885cf1
d559d20
9103920
32d3cba
1cc053a
c903cc4
6439af0
8194048
5bd47d6
978fadb
6d15865
6482843
533c0f1
32d223e
0c3ed1d
19e24e6
2105f2c
9f86875
d2b9f44
46a012f
bd63602
05ff8a2
5fec840
0be4ed1
1f423ea
02fcae2
f7bc78d
d85c283
1ca97f9
35052d3
2d2c58e
8f78da1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ node_modules | |
| dist | ||
| dist-ssr | ||
| *.local | ||
| .env | ||
|
|
||
| # Editor directories and files | ||
| .vscode/* | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import process from 'node:process'; | ||
| import { fileURLToPath } from 'node:url'; | ||
|
|
||
| import path from 'path'; | ||
|
|
||
| import { isProduction } from './utils/isProduction'; | ||
|
|
||
| const dirname = path.dirname(fileURLToPath(import.meta.url)); | ||
|
|
||
| if (!isProduction) { | ||
| const dotenv = await import('dotenv'); | ||
| dotenv.config({ path: path.resolve(dirname, '../.env') }); | ||
| } | ||
|
|
||
| export const serverEnv = { | ||
| AES_GCM_KEY_OF_DISCUSSION_ID: process.env.AES_GCM_KEY_OF_DISCUSSION_ID, | ||
| BASE_URL: process.env.BASE_URL, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { Buffer } from 'node:buffer'; | ||
|
|
||
| import { serverEnv } from '../../envconfig'; | ||
| import { aesGcm } from '../../utils/cipher'; | ||
| import { base64Url } from '../../utils/stringTransformer'; | ||
|
|
||
| const aesGcmKey = serverEnv.AES_GCM_KEY_OF_DISCUSSION_ID; | ||
| if (!aesGcmKey) { | ||
| throw new Error('환경 변수 AES_GCM_KEY_OF_DISCUSSION_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]); | ||
| } | ||
| return parts.join('/'); | ||
| }; | ||
|
|
||
| export const encryptDiscussionId = (text: string) => { | ||
| 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; | ||
| }; | ||
|
|
||
| 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); | ||
| }; | ||
|
Comment on lines
+30
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import type { RequestHandler } from 'express'; | ||
| import { createProxyMiddleware } from 'http-proxy-middleware'; | ||
|
|
||
| import { serverEnv } from '../../envconfig'; | ||
| import { decodeDiscussionIdOfUrl } from './discussionCipher'; | ||
| import { | ||
| DISCUSSION_CREATE_ENDPOINT, | ||
| handleProxyReq, | ||
| handleProxyRes, | ||
| rewriteDiscussionPath, | ||
| } from './proxyHandlers'; | ||
|
|
||
| export const discussionIdTransformer = createProxyMiddleware({ | ||
| target: `${serverEnv.BASE_URL}/api`, | ||
| changeOrigin: true, | ||
| selfHandleResponse: true, | ||
| pathRewrite: rewriteDiscussionPath, | ||
| on: { | ||
| proxyReq: handleProxyReq, | ||
| proxyRes: handleProxyRes, | ||
| }, | ||
| }); | ||
|
Comment on lines
+13
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainVerify BASE_URL configuration and consider security implications. The proxy middleware configuration looks correct, but there are a few considerations:
🏁 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.tsLength 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"
doneLength of output: 912 Ensure BASE_URL is validated and enforce size limits There is currently no validation on Recommended actions:
These changes will prevent invalid 🤖 Prompt for AI Agents |
||
|
|
||
| export const discussionIdVerifier: RequestHandler = (req, res, next) => { | ||
| if (req.path.startsWith(DISCUSSION_CREATE_ENDPOINT)) { | ||
| try { | ||
| decodeDiscussionIdOfUrl(req.path); | ||
| return next(); | ||
| } catch { | ||
| res.status(404).json({ code: 'NOT_FOUND', message: 'Invalid discussion id' }); | ||
| return; | ||
| } | ||
| } | ||
| return next(); | ||
| }; | ||
There was a problem hiding this comment.
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
🤖 Prompt for AI Agents