Feat(client&extension): 온보딩<->익스텐션 데이터 통신 작업#99
Conversation
Walkthrough온보딩-익스텐션 연동을 추가했다. 익스텐션 설치 시 크롬 계정 이메일을 저장하고 온보딩 페이지를 이메일 쿼리로 연다. 온보딩 완료 후 발급된 토큰을 window.postMessage로 전달하면 컨텐츠 스크립트가 백그라운드로 포워딩해 크롬 스토리지에 저장한다. 관련 권한과 키 변경을 반영했다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as 사용자
participant Ext as Extension (Background)
participant Chrome as Chrome Identity
participant Onb as Onboarding Page
rect rgba(230,245,255,0.6)
note over Ext: 설치 이벤트
User->>Ext: 확장 설치
Ext->>Chrome: getProfileUserInfo()
Chrome-->>Ext: { email }
Ext->>Ext: chrome.storage.local.set({ userEmail: email })
Ext->>Onb: open http://localhost:5173/onboarding?email=<email>
end
sequenceDiagram
autonumber
participant Web as Client (Onboarding Web)
participant Win as window
participant CS as Content Script
participant BG as Background
participant Store as chrome.storage.local
rect rgba(242,255,237,0.6)
note over Web: 회원가입 성공
Web->>Win: postMessage({ type: "SET_TOKEN", token })
Win-->>CS: window message
CS->>BG: chrome.runtime.sendMessage({ type: "SET_TOKEN", token })
BG->>Store: set({ token })
Store-->>BG: ok
BG-->>CS: "Token saved!" (log)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
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. Comment |
|
✅ Storybook chromatic 배포 확인: |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
apps/extension/manifest.json (1)
15-20: 콘텐츠 스크립트/호스트 권한을 최소 권한으로 축소하세요현재 <all_urls>는 과도합니다。온보딩/웹앱 도메인으로 한정하면 공격면이 크게 줄어듭니다.
- "content_scripts": [ - { - "matches": ["<all_urls>"], - "js": ["src/content.js"] - } - ], - "host_permissions": ["<all_urls>"] + "content_scripts": [ + { + "matches": ["https://www.pinback.today/*", "http://localhost:5173/*"], + "js": ["src/content.js"] + } + ], + "host_permissions": ["https://www.pinback.today/*", "http://localhost:5173/*"]apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx (2)
63-65: Firebase initializeApp 중복 호출 위험컴포넌트 렌더마다 initializeApp을 호출하면 app/duplicate-app 에러가 납니다. 모듈 스코프로 이동하고 getApps 가드로 보호하세요.
-import { initializeApp } from 'firebase/app'; +import { initializeApp, getApp, getApps } from 'firebase/app'; @@ -const MainCard = () => { +const firebaseApp = getApps().length ? getApp() : initializeApp(firebaseConfig); +const MainCard = () => { @@ - const app = initializeApp(firebaseConfig); - const messaging = getMessaging(app); + const messaging = getMessaging(firebaseApp);
137-145: remindTime 상태 갱신 직후 값을 사용해 요청하는 버그setState는 비동기입니다. normalize 결과를 지역 변수로 만들어 바로 사용하세요.
- const raw = AlarmsType[alarmSelected - 1].time; - setRemindTime(normalizeTime(raw)); - - postSignData({ - "email": userEmail, - "remindDefault": remindTime, + const raw = AlarmsType[alarmSelected - 1].time; + const normalized = normalizeTime(raw); + setRemindTime(normalized); + postSignData({ + "email": userEmail, + "remindDefault": normalized, "fcmToken": fcmToken, },apps/extension/src/apis/axiosInstance.ts (1)
64-66: 하드코딩된 이메일로 토큰 재발급 요청401/403에서 test@gmail.com으로 토큰을 요청하고 있습니다. 저장된 userEmail을 사용해야 합니다.
- const newToken = await fetchToken('test@gmail.com'); + const storedEmail = await new Promise<string | undefined>((resolve) => { + chrome.storage.local.get('userEmail', (r) => resolve(r.userEmail)); + }); + const newToken = await fetchToken(storedEmail);
🧹 Nitpick comments (5)
apps/extension/src/hooks/useSaveBookmarks.ts (1)
32-43: 북마크 생성 성공/오류 확인 전에 창을 닫고 있습니다chrome.bookmarks.create 콜백에서 lastError 확인 후 닫도록 바꿔야 에러를 놓치지 않습니다. Promise로 감싼 뒤 await 처리 권장.
- chrome.bookmarks.create( - { - parentId: '1', - title: params.title || params.url, - url: params.url, - }, - (newBookmark) => { - console.log('크롬 북마크바에 저장 완료: ', newBookmark); - } - ); - window.close(); + await new Promise<void>((resolve, reject) => { + chrome.bookmarks.create( + { + parentId: '1', + title: params.title || params.url, + url: params.url, + }, + (newBookmark) => { + if (chrome.runtime.lastError) { + return reject(chrome.runtime.lastError); + } + console.log('크롬 북마크바에 저장 완료: ', newBookmark); + resolve(); + } + ); + }); + window.close();apps/extension/src/pages/MainPop.tsx (1)
41-43: alert 대신 디자인 시스템 컴포넌트 사용 고려브라우저 alert는 거칠게 느껴집니다. DS의 Modal/Toast로 교체하면 일관된 UX를 유지할 수 있습니다. 창 닫기는 사용자 확인 후 진행하세요.
apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx (1)
52-58: URL 쿼리 파싱은 react-router의 useLocation으로 처리글로벌 location 의존은 리렌더 트리거가 약합니다. useLocation으로 안전하게 파싱하세요. 이메일 유효성 검증도 추가하세요.
+import { useLocation } from 'react-router-dom'; @@ - useEffect(() => { - const params = new URLSearchParams(location.search); + const location = useLocation(); + useEffect(() => { + const params = new URLSearchParams(location.search); const emailParam = params.get("email"); - if (emailParam) { + if (emailParam && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailParam)) { setUserEmail(emailParam); } - }, [location.search]); + }, [location.search]);apps/extension/src/apis/axiosInstance.ts (1)
30-33: userEmail 미존재 시 대비 로직 보강userEmail이 없으면 fetchToken이 실패합니다. 초기 온보딩 전 경로에서는 요청을 스킵하거나 명확한 오류를 내세요.
- const email = await new Promise<string | undefined>((resolve) => { + const email = await new Promise<string | undefined>((resolve) => { chrome.storage.local.get('userEmail', (result) => resolve(result.userEmail)); }); + if (!email) { + // 온보딩 전: 인증 필요 없는 요청만 허용 + if (isNoAuth) return config; + throw new Error('No userEmail in chrome.storage.local'); + }apps/client/src/shared/apis/queries.ts (1)
64-72: postMessage 채널 하드닝 제안type만으로는 부족합니다. nonce/txId를 추가해 재사용/오용을 방지하고, 서버 응답에 포함된 임시 nonce와 매칭하는 방식을 고려하세요.
- const sendTokenToExtension = (token: string) => { + const sendTokenToExtension = (token: string) => { + const nonce = crypto.getRandomValues(new Uint32Array(1))[0].toString(16); window.postMessage( { type: 'SET_TOKEN', token, + nonce, }, window.location.origin ); };
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx(2 hunks)apps/client/src/shared/apis/queries.ts(1 hunks)apps/extension/manifest.json(1 hunks)apps/extension/src/App.tsx(1 hunks)apps/extension/src/apis/axiosInstance.ts(1 hunks)apps/extension/src/background.ts(1 hunks)apps/extension/src/content.ts(1 hunks)apps/extension/src/hooks/useSaveBookmarks.ts(1 hunks)apps/extension/src/pages/MainPop.tsx(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-07-08T11:47:10.642Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#30
File: apps/extension/src/App.tsx:10-21
Timestamp: 2025-07-08T11:47:10.642Z
Learning: In apps/extension/src/App.tsx, the InfoBox component currently uses a hardcoded external URL for the icon prop as a temporary static placeholder. The plan is to replace this with dynamic favicon extraction from bookmarked websites in future iterations.
Applied to files:
apps/extension/src/App.tsx
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.
Applied to files:
apps/extension/src/pages/MainPop.tsx
📚 Learning: 2025-07-15T20:00:13.756Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#80
File: apps/client/src/shared/components/ui/modalPop/ModalPop.tsx:36-41
Timestamp: 2025-07-15T20:00:13.756Z
Learning: In apps/client/src/shared/components/ui/modalPop/ModalPop.tsx, the InfoBox component uses hardcoded values for title, location, and icon URL as temporary test data. These should be replaced with dynamic data from props when implementing actual functionality and should be marked with TODO comments for future changes.
Applied to files:
apps/extension/src/pages/MainPop.tsx
🪛 ast-grep (0.38.6)
apps/client/src/shared/apis/queries.ts
[warning] 73-73: Detected potential storage of sensitive information in browser localStorage. Sensitive data like email addresses, personal information, or authentication tokens should not be stored in localStorage as it's accessible to any script.
Context: localStorage.setItem('token', newToken)
Note: [CWE-312] Cleartext Storage of Sensitive Information [REFERENCES]
- https://owasp.org/www-community/vulnerabilities/HTML5_Security_Cheat_Sheet
- https://cwe.mitre.org/data/definitions/312.html
(browser-storage-sensitive-data)
🪛 GitHub Check: lint
apps/extension/src/background.ts
[warning] 21-21:
Unexpected console statement
[warning] 6-6:
Unexpected console statement
🔇 Additional comments (4)
apps/extension/manifest.json (1)
9-9: 유지: 'identity.email' 권한은 유효하고 필요함
chrome.identity.getProfileUserInfo가 이메일을 반환하려면 manifest에 "identity.email" 권한 선언이 필요합니다(단순 "identity"만으로는 이메일이 빈값일 수 있음). 사용자가 Chrome에 로그인/동기화되어 있어야 하며 accountStatus 옵션(예: ANY)을 활용할 수 있습니다.
파일: apps/extension/manifest.json — permissions 배열(라인 9) 그대로 유지.apps/extension/src/background.ts (3)
2-3: 설치 이벤트 분기는 적절합니다초기 설치에만 온보딩을 트리거하는 조건 분기 로직은 의도와 맞습니다.
4-4: 확인: chrome.identity.getProfileUserInfo는 MV3에서 deprecated 아님 — 이메일 반환은 권한/로그인 상태에 의존요약: deprecated 아님. 이메일이 빈 문자열로 반환되는 경우 — (1) 사용자가 Chrome에 로그인된 프로필(주계정)이 없음, (2) manifest에 "identity.email" 권한이 선언되지 않음, (3) accountStatus 기본값("SYNC") 사용 시 프로필이 동기화되지 않으면 빈 값이 될 수 있음. 조치: manifest에 "identity.email" (및 보통 "identity") 추가하고, 동기화되지 않은 프로필을 포함하려면 getProfileUserInfo 호출 시 {accountStatus: "ANY"} 사용을 검토. 위치: apps/extension/src/background.ts (라인 4) — 해당 호출의 권한/옵션 확인 필요.
6-6: console.log 제거 및 개발 환경 전용 가드 적용토큰/이메일 로그는 삭제하고, 나머지 console 호출은 개발 환경에서만 허용하세요 (import.meta.env.DEV).
위치: apps/extension/src/background.ts — lines 6, 21
-console.log('백그라운드 기능'); +if (import.meta.env.DEV) console.log('background boot');검증 불가: lint 실행 실패 (turbo: not found · node_modules 없음 · Corepack 활성화 필요). 로컬에서 lint 재실행 결과 첨부.
| postSignData({ | ||
| "email": "tesdfdfsst@gmail.com", // TODO : 익스텐션에게서 메일 받기 | ||
| "email": userEmail, | ||
| "remindDefault": remindTime, | ||
| "fcmToken": fcmToken, | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
이메일 누락 시 가입 요청 차단
userEmail이 비어있으면 요청을 막고 사용자 안내하세요.
- postSignData({
+ if (!userEmail) {
+ alert('이메일 정보가 없어 회원가입을 진행할 수 없어요.');
+ return;
+ }
+ postSignData({📝 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.
| postSignData({ | |
| "email": "tesdfdfsst@gmail.com", // TODO : 익스텐션에게서 메일 받기 | |
| "email": userEmail, | |
| "remindDefault": remindTime, | |
| "fcmToken": fcmToken, | |
| }, | |
| if (!userEmail) { | |
| alert('이메일 정보가 없어 회원가입을 진행할 수 없어요.'); | |
| return; | |
| } | |
| postSignData({ | |
| "email": userEmail, | |
| "remindDefault": remindTime, | |
| "fcmToken": fcmToken, | |
| }, |
🤖 Prompt for AI Agents
In apps/client/src/pages/onBoarding/components/funnel/MainCard.tsx around lines
140-144, the signup call is made even when userEmail is empty; add a guard that
validates userEmail (e.g., check trimmed length) before calling postSignData,
and if it's missing return early and show the user an inline error or toast
message indicating the email is required; ensure the function exits without
making the request when validation fails.
| if (newToken) { | ||
| localStorage.setItem('token', newToken); | ||
| sendTokenToExtension(newToken); | ||
| } |
There was a problem hiding this comment.
토큰을 localStorage에 저장하는 것은 XSS에 취약
가능하다면 서버가 HttpOnly Secure 쿠키로 토큰(또는 세션)을 설정하도록 전환하세요. 프론트에서는 localStorage 보관을 제거합니다.
대안:
- 서버: /auth/signup 응답 시 Set-Cookie(HttpOnly; Secure; SameSite=Strict; Path=/)로 액세스/리프레시 토큰 설정.
- 클라이언트: Authorization 헤더 제거, 쿠키 기반 인증으로 전환.
- 불가 시: 토큰을 메모리 저장 + 짧은 만료 + 엄격한 CSP로 리스크 완화.
🧰 Tools
🪛 ast-grep (0.38.6)
[warning] 73-73: Detected potential storage of sensitive information in browser localStorage. Sensitive data like email addresses, personal information, or authentication tokens should not be stored in localStorage as it's accessible to any script.
Context: localStorage.setItem('token', newToken)
Note: [CWE-312] Cleartext Storage of Sensitive Information [REFERENCES]
- https://owasp.org/www-community/vulnerabilities/HTML5_Security_Cheat_Sheet
- https://cwe.mitre.org/data/definitions/312.html
(browser-storage-sensitive-data)
|
|
||
| const handleDuplicateRightClick = () => { | ||
| window.location.href = "/dashboard"; | ||
| window.location.href = "https://www.pinback.today/"; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
외부 페이지 이동은 chrome.tabs.create를 사용하세요
popup 내부에서 window.location.href 변경은 UX가 어색합니다. 새 탭을 여는 쪽이 일반적이며, 환경별 베이스 URL로 관리하세요.
- const handleDuplicateRightClick = () => {
- window.location.href = "https://www.pinback.today/";
- };
+ const handleDuplicateRightClick = () => {
+ const url = import.meta.env.VITE_WEB_BASE_URL ?? "https://www.pinback.today/";
+ chrome.tabs.create({ url });
+ };📝 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.
| window.location.href = "https://www.pinback.today/"; | |
| const handleDuplicateRightClick = () => { | |
| const url = import.meta.env.VITE_WEB_BASE_URL ?? "https://www.pinback.today/"; | |
| chrome.tabs.create({ url }); | |
| }; |
🤖 Prompt for AI Agents
In apps/extension/src/App.tsx around line 27, replace the direct
window.location.href assignment with chrome.tabs.create to open
"https://www.pinback.today/" in a new tab using a base URL taken from your
environment/config (do not hardcode); after creating the tab, close the popup
(window.close()) to match extension UX. Also ensure the extension has the "tabs"
permission or use chrome.tab API appropriate to your manifest version and pull
the base URL from a central config/env var so it can vary by environment.
| chrome.identity.getProfileUserInfo(function (info) { | ||
| chrome.storage.local.set({ 'userEmail': info.email }, () => { | ||
| console.log(info.email); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
이메일 수집 시 PII 로그 제거 + 빈 이메일/에러 처리 필요
- 사용자 이메일을 콘솔에 출력하면 PII 노출 위험이 큽니다.
- 크롬에 로그인되어 있지 않으면
info.email이 빈 문자열일 수 있어 가드가 필요합니다. chrome.storage.local.set후runtime.lastError체크가 없습니다.
아래처럼 수정해 주세요.
- chrome.identity.getProfileUserInfo(function (info) {
- chrome.storage.local.set({ 'userEmail': info.email }, () => {
- console.log(info.email);
- });
+ chrome.identity.getProfileUserInfo(function (info) {
+ const email = info?.email?.trim();
+ if (email) {
+ chrome.storage.local.set({ userEmail: email }, () => {
+ if (chrome.runtime.lastError) {
+ console.warn('userEmail 저장 실패:', chrome.runtime.lastError.message);
+ }
+ });
+ } else {
+ console.warn('Chrome 프로필 이메일을 사용할 수 없습니다(비로그인/권한 미부여).');
+ }📝 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.
| chrome.identity.getProfileUserInfo(function (info) { | |
| chrome.storage.local.set({ 'userEmail': info.email }, () => { | |
| console.log(info.email); | |
| }); | |
| chrome.identity.getProfileUserInfo(function (info) { | |
| const email = info?.email?.trim(); | |
| if (email) { | |
| chrome.storage.local.set({ email }, () => { | |
| if (chrome.runtime.lastError) { | |
| console.warn('email 저장 실패:', chrome.runtime.lastError.message); | |
| } | |
| }); | |
| } else { | |
| console.warn('Chrome 프로필 이메일을 사용할 수 없습니다(비로그인/권한 미부여).'); | |
| } | |
| }); |
🧰 Tools
🪛 GitHub Check: lint
[warning] 6-6:
Unexpected console statement
🤖 Prompt for AI Agents
In apps/extension/src/background.ts around lines 4-7, remove the console.log
that prints info.email to avoid PII exposure; add a guard that checks if
info.email is a non-empty string before calling chrome.storage.local.set (skip
storing if empty and handle accordingly); after calling
chrome.storage.local.set, check chrome.runtime.lastError and handle/log it via
chrome.runtime.lastError.message (do not log the email itself); ensure any error
or empty-email paths are handled gracefully (e.g., set a flag like
userEmailAvailable: false or return early).
| setTimeout(() => { | ||
| chrome.tabs.create({ | ||
| url: `http://localhost:5173/onboarding?email=${info.email}`, | ||
| }); | ||
| }, 1000); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
이메일을 URL 쿼리로 전달하지 마세요(PII 유출 위험) + 불필요한 setTimeout 제거
- 이메일을 쿼리스트링으로 넘기면 서버 로그/리퍼러/분석 도구로 쉽게 유출될 수 있습니다.
- 1초 대기는 비결정적입니다. 탭 생성 콜백에서 바로 처리하세요.
- 권장: 탭만 열고, 컨텐츠 스크립트로 이메일을 전달해 페이지로 postMessage 브리징.
- setTimeout(() => {
- chrome.tabs.create({
- url: `http://localhost:5173/onboarding?email=${info.email}`,
- });
- }, 1000);
+ chrome.tabs.create({ url: 'http://localhost:5173/onboarding' }, (tab) => {
+ const email = info?.email?.trim();
+ if (!email || !tab?.id) return;
+ // 컨텐츠 스크립트가 페이지로 window.postMessage('SET_EMAIL') 포워딩
+ chrome.tabs.sendMessage(
+ tab.id,
+ { type: 'SET_EMAIL', email },
+ () => void chrome.runtime.lastError // 컨텐츠 스크립트 로드 전 호출시 에러 무시
+ );
+ });컨텐츠 스크립트 측에서는 SET_EMAIL 수신 → window.postMessage로 페이지에 전달하도록 맞춰 주세요.
📝 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.
| setTimeout(() => { | |
| chrome.tabs.create({ | |
| url: `http://localhost:5173/onboarding?email=${info.email}`, | |
| }); | |
| }, 1000); | |
| chrome.tabs.create({ url: 'http://localhost:5173/onboarding' }, (tab) => { | |
| const email = info?.email?.trim(); | |
| if (!email || !tab?.id) return; | |
| // 컨텐츠 스크립트가 페이지로 window.postMessage('SET_EMAIL') 포워딩 | |
| chrome.tabs.sendMessage( | |
| tab.id, | |
| { type: 'SET_EMAIL', email }, | |
| () => void chrome.runtime.lastError | |
| ); | |
| }); |
| return true; // async 응답 | ||
| setTimeout(() => { | ||
| chrome.tabs.create({ | ||
| url: `http://localhost:5173/onboarding?email=${info.email}`, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
개발용 localhost URL 하드코딩 제거
배포 시 깨집니다. 환경변수/설정으로 분리하세요.
적용 예(파일 상단 등 외부 추가 코드):
// 외부 추가 코드
const ONBOARDING_ORIGIN = import.meta.env.VITE_CLIENT_ORIGIN ?? 'https://app.pinback.io';
const ONBOARDING_URL = new URL('/onboarding', ONBOARDING_ORIGIN).toString();해당 라인 교체:
- url: `http://localhost:5173/onboarding?email=${info.email}`,
+ url: ONBOARDING_URL,🤖 Prompt for AI Agents
In apps/extension/src/background.ts around line 10, the onboarding URL is
hardcoded to localhost which will break in production; define an
environment-backed origin constant at the top of the file (e.g., read
import.meta.env.VITE_CLIENT_ORIGIN with a production fallback like
https://app.pinback.io), build the onboarding base URL using that origin and the
/onboarding path, then replace the hardcoded string with code that appends the
email query param to that constructed URL so runtime origin is configurable via
env.
| chrome.runtime.onMessage.addListener((message) => { | ||
| if (message.type === 'SET_TOKEN') { | ||
| chrome.storage.local.set({ 'token': message.token }, () => { | ||
| console.log('Token saved!', message.token); | ||
| }); | ||
| } | ||
| }); No newline at end of file |
There was a problem hiding this comment.
🛠️ Refactor suggestion
토큰 콘솔 출력 제거 + 검증/응답 처리 + 발신자 검증 추가
- 토큰을 콘솔에 남기면 심각한 보안 리스크입니다.
- 메시지 스키마 검증과 저장 성공/실패 응답을 추가해 신뢰성을 높이세요.
- 내부 발신자만 허용하도록
sender.id를 점검하세요.
-chrome.runtime.onMessage.addListener((message) => {
- if (message.type === 'SET_TOKEN') {
- chrome.storage.local.set({ 'token': message.token }, () => {
- console.log('Token saved!', message.token);
- });
- }
-});
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+ if (message?.type !== 'SET_TOKEN') return;
+ if (sender?.id && sender.id !== chrome.runtime.id) {
+ sendResponse({ ok: false, error: 'unauthorized_sender' });
+ return;
+ }
+ const token = typeof message.token === 'string' ? message.token.trim() : '';
+ if (!token) {
+ sendResponse({ ok: false, error: 'invalid_token' });
+ return;
+ }
+ chrome.storage.local.set({ token }, () => {
+ if (chrome.runtime.lastError) {
+ sendResponse({ ok: false, error: chrome.runtime.lastError.message });
+ } else {
+ sendResponse({ ok: true });
+ }
+ });
+ return true; // 비동기 응답 유지
+});참고: 이슈 #85 요구사항에 따라 refreshToken도 함께 저장해야 하면 동일 스키마로 확장하세요.
🧰 Tools
🪛 GitHub Check: lint
[warning] 21-21:
Unexpected console statement
🤖 Prompt for AI Agents
In apps/extension/src/background.ts around lines 18-24, remove the console.log
that prints the token, validate the incoming message schema (ensure message.type
=== 'SET_TOKEN' and required fields like token and optional refreshToken are
strings), verify the sender by checking sender?.id === chrome.runtime.id before
proceeding, persist token (and refreshToken if present) via
chrome.storage.local.set and in its callback send a success or failure response
back to the sender (including an error message on failure), and ensure any
exceptions are caught and result in a failure response; expand schema and
storage to include refreshToken if Issue #85 requires it.
| window.addEventListener('message', (event) => { | ||
| if (event.source !== window) return; | ||
| if (event.data.type === 'SET_TOKEN') { | ||
| chrome.runtime.sendMessage({ | ||
| type: 'SET_TOKEN', | ||
| token: event.data.token, | ||
| }); | ||
| } | ||
| }); No newline at end of file |
There was a problem hiding this comment.
🛠️ Refactor suggestion
postMessage 출처 검증 없음 → 임의 사이트가 토큰 주입 가능
콘텐츠 스크립트가 모든 사이트(<all_urls>)에서 메시지를 수신합니다. origin 화이트리스트 및 페이로드 검증이 필요합니다.
-window.addEventListener('message', (event) => {
+const ALLOWED_ORIGINS = ['https://www.pinback.today', 'http://localhost:5173'];
+window.addEventListener('message', (event) => {
if (event.source !== window) return;
- if (event.data.type === 'SET_TOKEN') {
+ if (!ALLOWED_ORIGINS.includes(event.origin)) return;
+ if (event.data?.type === 'SET_TOKEN' && typeof event.data.token === 'string') {
chrome.runtime.sendMessage({
type: 'SET_TOKEN',
token: event.data.token,
});
}
});📝 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.
| window.addEventListener('message', (event) => { | |
| if (event.source !== window) return; | |
| if (event.data.type === 'SET_TOKEN') { | |
| chrome.runtime.sendMessage({ | |
| type: 'SET_TOKEN', | |
| token: event.data.token, | |
| }); | |
| } | |
| }); | |
| const ALLOWED_ORIGINS = ['https://www.pinback.today', 'http://localhost:5173']; | |
| window.addEventListener('message', (event) => { | |
| if (event.source !== window) return; | |
| if (!ALLOWED_ORIGINS.includes(event.origin)) return; | |
| if (event.data?.type === 'SET_TOKEN' && typeof event.data.token === 'string') { | |
| chrome.runtime.sendMessage({ | |
| type: 'SET_TOKEN', | |
| token: event.data.token, | |
| }); | |
| } | |
| }); |
🤖 Prompt for AI Agents
In apps/extension/src/content.ts around lines 2 to 10, the window message
handler accepts messages from any origin and blindly forwards tokens to the
extension; add origin and payload validation: define a whitelist array of
allowed origins and immediately return unless event.origin is in that whitelist,
verify event.source === window (keep) and that event.data is an object with type
=== 'SET_TOKEN' and that event.data.token is a non-empty string (optionally
validate format/length), then only forward the sanitized token to
chrome.runtime.sendMessage; also drop or log unexpected messages and avoid
forwarding if validation fails.
| chrome.storage.local.get('email', (result) => { | ||
| resolve(result.email); | ||
| }); | ||
| chrome.storage.local.get('userEmail', (result) => resolve(result.userEmail)); |
There was a problem hiding this comment.
storage이거 key email로 되어있지 않나요?
There was a problem hiding this comment.
넹네! 이거 익스텐션 크롬스토리지랑 그냥 웹 로컬스토리지 각각의 키 구분할라고 (제가 헷갈려서..ㅎ)
익스텐션 쪽은 userEmail로 했씁니당!
There was a problem hiding this comment.
그렇다면 익스텐션 axios interceptor 확인해보셔야 할 것 같아요! 이전에 인터셉터 설정할때 email로 통일한 기억이..!
📌 Related Issues
📄 Tasks
[플로우] 는 다음과 같습니당!
⭐ PR Point (To Reviewer)
1. 익스텐션의 크롬 권한을 최대한 활용!!
(1)
"identity","identity.email"권한을 추가해서! 이메일을 받아오기(2)
chrome.runtime.onInstalled.addListener를 활용해서, 익스텐션 설치 시점 활용하기!(3) content.ts 스크립트는, 온보딩이든 대시보드이든 무조건 항상 ui 뒤에서 작동하고 있어서! 이 스크립트로, 메시지를 주고받을 수 있어요!
2. 익스텐션은 로컬 스토리지가 없어요!
이와 동일한 크롬 스토리지 활용하기!
📷 Screenshot
Summary by CodeRabbit
신규 기능
개선
기타(권한/설정)