Skip to content

feat: add Identity current-user experience - #3

Merged
DoMinhHHung merged 33 commits into
mainfrom
feat/identity-current-user-experience
Aug 5, 2026
Merged

feat: add Identity current-user experience#3
DoMinhHHung merged 33 commits into
mainfrom
feat/identity-current-user-experience

Conversation

@DoMinhHHung

@DoMinhHHung DoMinhHHung commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Objective

Replace the protected /app engineering placeholder with the first real authenticated BridgeWorks vertical slice. The page obtains the current Clerk session token only on the server, calls Identity through APISIX GET /api/v1/me, validates the response, and renders product-facing account and lifecycle states.

Backend contract

Verified from backend main (DoMinhHHung/bridgeworks, commit 81bb80f3252826f85c2f4fce7d58554d0d66a47c):

  • endpoint: GET /api/v1/me through APISIX;
  • request auth: Authorization: Bearer <Clerk session token>;
  • request correlation: forwarded or generated X-Request-Id;
  • success schema: strict id UUIDv7, public id_user matching ^bw[0-9]{12}$, nullable primary_email, exact status active, RFC 3339 created_at and updated_at;
  • error schema: strict {code,message,request_id,details};
  • 401 unauthorized → Clerk sign-in recovery;
  • 403 account_disabled → disabled;
  • 403 account_deleted → deleted;
  • 409 identity_not_ready → not ready, preserving Retry-After (currently 2);
  • 503 service_unavailable → service unavailable;
  • every current-user response is Cache-Control: no-store and varies on Authorization;
  • 401 exposes the stable Bearer challenge;
  • APISIX forwards Bearer auth and applies non-credentialed browser CORS; the frontend does not verify JWTs.

The current Identity route has no verified stable 429 error code. Structurally valid HTTP 429 responses are mapped by status without inventing an Identity code.

Architecture

  • src/lib/bridgeworks-api-config.ts: pure base-origin validation for missing, placeholder, malformed, and configured states;
  • src/lib/bridgeworks-api-config.server.ts: server-only environment boundary;
  • src/lib/bridgeworks-api-core.ts: bounded timeout, bounded body, cache: "no-store", credentials: "omit", no redirects, Bearer header, and request-ID forwarding;
  • src/lib/bridgeworks-api.server.ts: centralized server-only APISIX client;
  • src/features/current-user/current-user-contract.ts: strict Zod success/error decoding and stable status/code mapping;
  • src/features/current-user/current-user.service.server.ts: Clerk auth().getToken() flow, request context, typed result union, safe structured diagnostics;
  • src/features/current-user/current-user-overview.tsx: product UI mapping with no raw fetch logic.

No page or Client Component receives the Clerk token. The internal Identity UUID is validated but not rendered. Backend request IDs and Retry-After guidance are preserved. Invalid JSON, empty/oversized bodies, unsupported statuses, and malformed schemas stop at safe non-data states.

UI changes

  • landing page now presents product value rather than framework/testing status;
  • primary Create an account and secondary Sign in CTAs target real auth routes;
  • /app shows public ID, nullable primary email, active state, joined date, and last Identity update;
  • next-step copy is neutral and does not link to an unimplemented profile flow;
  • dedicated loading, not-ready, disabled, deleted, rate-limited, service-unavailable, configuration, malformed-response, and unexpected states;
  • unauthorized backend sessions return through Clerk sign-in with /app as return URL;
  • desktop/mobile navigation use one model and derive aria-current from the pathname;
  • restrained indigo brand accent and semantic success/warning/destructive/information tokens;
  • Clerk appearance aligned to BridgeWorks typography, radius, inputs, focus, errors, primary action, and social buttons.

Security

  • no client-side secret or token;
  • no token/email/raw response logging;
  • safe server diagnostics contain only state and request ID;
  • API configuration and authentication fail closed;
  • no custom JWT verification or refresh-token implementation;
  • no credentialed-CORS assumption and no Cookie forwarding;
  • no backend secrets or production domain;
  • no fake auth fallback, fake account data, fake metrics, or fake product routes.

Accessibility and responsive behavior

  • semantic landmarks, headings, definition list, status/alert roles, labelled request IDs, and time elements;
  • visible focus and 44px minimum interactive controls;
  • reduced-motion support and stable loading dimensions;
  • shared keyboard-accessible mobile sheet with Escape and focus return;
  • Playwright confirmed no horizontal overflow at 375, 768, 1024, and 1440 px in Chromium, Firefox, and WebKit;
  • axe smoke completed with no automatically detectable violations on /, /sign-in, /sign-up, and fail-closed /app in all three configured browsers.

Testing

Final GitHub Actions run 30994624563, job 92268616260, completed successfully on final head 8f2e1479c56f860117011d65cc7eb1bd0be65ded.

Exact validation:

  • pnpm install --frozen-lockfile — passed;
  • pnpm lint — passed;
  • pnpm typecheck — passed;
  • pnpm test — passed: 7 files, 69 tests;
  • pnpm build — passed; /app remains dynamically server-rendered and no Client Component imported a server-only module;
  • pnpm build-storybook — passed;
  • pnpm test:storybook — passed: 6 files, 25 tests;
  • pnpm check — passed, rerunning lint, typecheck, 69 unit tests, application build, and Storybook build;
  • pnpm test:e2e — passed: 24 tests total, 8 each in Chromium, Firefox, and WebKit.

Unit coverage includes API URL validation, strict success parsing, malformed success/error payloads, every verified backend lifecycle code, request-ID preservation, Retry-After seconds/date parsing, unsupported status, network failure, timeout, invalid JSON, empty response, oversized response, and no token/raw diagnostic exposure. Storybook contains ready, null email, not-ready, disabled, deleted, rate-limited, service-unavailable, unexpected, loading, mobile, and desktop presentations. Playwright covers product copy, auth CTAs, secretless auth-unavailable states, fail-closed /app, axe, and overflow at 375/768/1024/1440 in Chromium, Firefox, and WebKit.

The run emitted non-failing existing tooling warnings for the Vite native config-loader migration, Storybook chunk size, and GitHub Actions Node runtime migration. No package/tooling migration was added outside this vertical slice.

Manual authenticated smoke

Pending — not executed. The automated environment has no dedicated Clerk test instance, valid development credentials, or reachable local APISIX/Identity stack. README contains the full 15-step authenticated Identity checklist. No authenticated Clerk E2E pass is claimed.

Scope exclusions

  • profile business workflow;
  • jobs;
  • talent;
  • marketplace;
  • organizations;
  • billing;
  • messaging;
  • backend changes;
  • APISIX changes;
  • deployment.

Status

  • base: latest main at 3086aec3fa31439e9fba74f7123c71e4c97aee83;
  • final head SHA: 8f2e1479c56f860117011d65cc7eb1bd0be65ded;
  • draft: yes;
  • ready for review: no;
  • reviewers requested: none;
  • merged: no.

Summary by CodeRabbit

  • Tính năng mới

    • Ra mắt landing page BridgeWorks với thông điệp mới, lợi ích nổi bật và nút Đăng ký/Đăng nhập.
    • Bổ sung trang tổng quan tài khoản, hiển thị trạng thái danh tính, ngày tham gia và hướng dẫn xử lý.
    • Cải thiện điều hướng desktop/mobile với trạng thái trang hiện tại và hỗ trợ trợ năng.
  • Cải tiến giao diện

    • Cập nhật bảng màu, bo góc, thương hiệu và cấu hình giao diện đăng nhập.
    • Bổ sung màn hình tải và các thông báo lỗi rõ ràng hơn, kèm tùy chọn thử lại.
  • Tài liệu & Kiểm thử

    • Mở rộng hướng dẫn cài đặt, vận hành và quy trình kiểm tra.
    • Bổ sung kiểm thử cho nhiều trạng thái tài khoản và kích thước màn hình.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

PR thêm luồng current-user từ xác thực Clerk đến BridgeWorks API và giao diện tổng quan tài khoản. PR cũng thêm điều hướng dùng chung, cập nhật landing page, theme, tài liệu README và bước kiểm tra pnpm check trong CI.

Changes

Current-user account experience

Layer / File(s) Summary
Cấu hình và vận chuyển API
src/lib/bridgeworks-api-*
Thêm phân loại cấu hình API, gọi /api/v1/me, timeout, giới hạn response, request ID và xử lý lỗi transport.
Hợp đồng và ánh xạ trạng thái
src/features/current-user/current-user-contract*
Thêm schema Zod, trạng thái backend/view, xử lý Retry-After và ánh xạ các phản hồi thành trạng thái miền.
Service và giao diện tài khoản
src/features/current-user/*, src/app/(protected)/app/*
Thêm service server-only, tổng quan tài khoản, loading skeleton, các màn hình lỗi và kiểm thử cho toàn bộ trạng thái.
Điều hướng ứng dụng dùng chung
src/components/layout/app-navigation*, src/components/layout/app-shell*, src/components/layout/mobile-app-navigation.tsx
Sidebar và mobile navigation dùng chung danh sách route, nhận diện route hiện tại và aria-current.
Landing page và thương hiệu
src/app/page.tsx, src/app/layout.tsx, src/app/globals.css, tests/example.spec.ts
Cập nhật nội dung landing page, CTA, Clerk appearance, metadata, bảng màu và kiểm thử responsive.
Tài liệu và CI
README.md, .github/workflows/playwright.yml
README mô tả hợp đồng current-user và quy tắc repository. Frontend CI chạy thêm pnpm check.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Poem

Thỏ nhỏ chạy qua luồng API,
Request ID sáng giữa đêm dài.
Tài khoản hiện lên, lỗi được báo,
Điều hướng chung bước thật ngay.
CI kiểm tra, landing nở hoa.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Tiêu đề mô tả đúng thay đổi chính: bổ sung trải nghiệm current-user cho Identity trong vertical slice được xác thực.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/identity-current-user-experience

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

❤️ Share

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

@DoMinhHHung
DoMinhHHung marked this pull request as ready for review August 5, 2026 12:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (5)
tests/example.spec.ts (1)

77-82: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Kiểm tra tràn ngang trên <main>.

src/app/page.tsx Line 27 đặt overflow-x-hidden trên <main>. Một phần tử con quá rộng có thể bị cắt, nhưng document.documentElement.scrollWidth vẫn bằng clientWidth. Đo kích thước của <main> để bài kiểm thử phát hiện nội dung bị cắt.

Đề xuất thay đổi
-    const dimensions = await page.evaluate(() => ({
-      scrollWidth: document.documentElement.scrollWidth,
-      clientWidth: document.documentElement.clientWidth,
+    const dimensions = await page.locator("main").evaluate((main) => ({
+      scrollWidth: main.scrollWidth,
+      clientWidth: main.clientWidth,
     }));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/example.spec.ts` around lines 77 - 82, Update the overflow test in the
example spec to measure the <main> element instead of document.documentElement,
using the existing page.evaluate block and the main element as the source of
scrollWidth/clientWidth so hidden horizontal overflow under the main container
is detected even when the root document width stays unchanged.
.github/workflows/playwright.yml (1)

60-61: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Không chạy lặp cùng một bộ kiểm tra.

pnpm check đã chạy lint, typecheck, unit tests, application build và Storybook build. Workflow đã chạy các lệnh này tại Line 42-55. Line 60-61 vì vậy chạy lại năm tác vụ nặng và có thể làm vượt timeout-minutes: 35.

Hãy dùng pnpm check thay cho các bước riêng lẻ, hoặc bỏ bước aggregate. Giữ riêng pnpm test:storybookpnpm test:e2e.

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

In @.github/workflows/playwright.yml around lines 60 - 61, The workflow is
duplicating the frontend validation suite by running the same
lint/typecheck/unit/build/Storybook checks again through the aggregate step.
Update the Playwright workflow so the section around run `pnpm check` does not
repeat the checks already covered earlier; either keep only `pnpm check` for the
aggregate validation or remove that aggregate step entirely, while preserving
the separate `pnpm test:storybook` and `pnpm test:e2e` steps.
src/features/current-user/current-user-overview.tsx (1)

17-19: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Đặt timeZone rõ ràng cho định dạng ngày.

Intl.DateTimeFormat không có timeZone. Component này render trên server, nên ngày được tính theo múi giờ của tiến trình server. Nếu người dùng ở múi giờ khác, "Joined BridgeWorks" và "Identity last updated" (dòng 169-181) có thể lệch một ngày.

Thuộc tính dateTime của thẻ <time> đã giữ giá trị ISO gốc, nên chỉ cần cố định múi giờ hiển thị.

♻️ Đề xuất cố định múi giờ
 const dateFormatter = new Intl.DateTimeFormat("en", {
   dateStyle: "medium",
+  timeZone: "UTC",
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/current-user/current-user-overview.tsx` around lines 17 - 19,
Update the dateFormatter in the current-user overview component to specify the
intended fixed display time zone via Intl.DateTimeFormat options. Keep the
existing medium date formatting and the ISO value used by the time elements
unchanged.
src/features/current-user/current-user-loading.tsx (1)

3-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Bỏ aria-label trùng với text sr-only.

Phần tử role="status"aria-label và đồng thời chứa <span className="sr-only"> với cùng một chuỗi (dòng 25). aria-label thay thế nội dung khi trình đọc màn hình tính tên phần tử. Giữ một nguồn văn bản duy nhất là đủ.

♻️ Đề xuất giữ một nguồn văn bản
     <div
       role="status"
-      aria-label="Loading your BridgeWorks account"
       className="space-y-8 sm:space-y-10"
     >
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/current-user/current-user-loading.tsx` around lines 3 - 7,
Remove the aria-label attribute from the role="status" container in the
current-user loading component, keeping the existing sr-only span as the single
accessible text source. Preserve the status role and all other loading markup
unchanged.
src/features/current-user/current-user-contract.ts (1)

8-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Chuyển các schema Zod sang API mới và giữ z.object().strict().

Dự án dùng Zod 4 nên dùng z.email() thay z.string().email()z.iso.datetime({ offset: true }) thay z.string().datetime({ offset: true }). Không dùng z.strictObject(...).strict() không bị deprecated trong Zod 4; giữ z.object(...).strict() ở đây là đủ.

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

In `@src/features/current-user/current-user-contract.ts` around lines 8 - 32,
Update the Zod schemas in currentUserSchema and errorEnvelopeSchema to use the
Zod 4 API: replace the string-based email and datetime validators with z.email()
and z.iso.datetime({ offset: true }) in the existing z.object(...).strict()
definitions. Keep the current z.object(...).strict() pattern unchanged, and do
not switch to z.strictObject(...) since the review explicitly wants the
strictness preserved via .strict().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/`(protected)/app/page.tsx:
- Around line 17-22: Update the route guard around the experience status check
to call experience.redirectToSignIn with APP_ROUTE only for "signed-out". For
"unauthorized", allow rendering to continue so the existing unauthorized panel
in current-user-overview.tsx can prompt the user to sign in again without
creating a redirect loop.

In `@src/lib/bridgeworks-api-config.ts`:
- Around line 53-67: Update classifyBridgeWorksApiConfiguration() to require
https: for BridgeWorks API base URLs, while allowing http: only for explicitly
supported local development cases. In requestCurrentIdentityUser(), ensure the
Clerk Bearer token is never sent over an unprotected http: URL, or route such
requests through the configured secure proxy.

---

Nitpick comments:
In @.github/workflows/playwright.yml:
- Around line 60-61: The workflow is duplicating the frontend validation suite
by running the same lint/typecheck/unit/build/Storybook checks again through the
aggregate step. Update the Playwright workflow so the section around run `pnpm
check` does not repeat the checks already covered earlier; either keep only
`pnpm check` for the aggregate validation or remove that aggregate step
entirely, while preserving the separate `pnpm test:storybook` and `pnpm
test:e2e` steps.

In `@src/features/current-user/current-user-contract.ts`:
- Around line 8-32: Update the Zod schemas in currentUserSchema and
errorEnvelopeSchema to use the Zod 4 API: replace the string-based email and
datetime validators with z.email() and z.iso.datetime({ offset: true }) in the
existing z.object(...).strict() definitions. Keep the current
z.object(...).strict() pattern unchanged, and do not switch to
z.strictObject(...) since the review explicitly wants the strictness preserved
via .strict().

In `@src/features/current-user/current-user-loading.tsx`:
- Around line 3-7: Remove the aria-label attribute from the role="status"
container in the current-user loading component, keeping the existing sr-only
span as the single accessible text source. Preserve the status role and all
other loading markup unchanged.

In `@src/features/current-user/current-user-overview.tsx`:
- Around line 17-19: Update the dateFormatter in the current-user overview
component to specify the intended fixed display time zone via
Intl.DateTimeFormat options. Keep the existing medium date formatting and the
ISO value used by the time elements unchanged.

In `@tests/example.spec.ts`:
- Around line 77-82: Update the overflow test in the example spec to measure the
<main> element instead of document.documentElement, using the existing
page.evaluate block and the main element as the source of
scrollWidth/clientWidth so hidden horizontal overflow under the main container
is detected even when the root document width stays unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6089c027-4d8f-4938-be16-19b89696452f

📥 Commits

Reviewing files that changed from the base of the PR and between 3086aec and 8f2e147.

📒 Files selected for processing (28)
  • .github/workflows/playwright.yml
  • README.md
  • src/app/(protected)/app/error.tsx
  • src/app/(protected)/app/loading.tsx
  • src/app/(protected)/app/page.tsx
  • src/app/globals.css
  • src/app/layout.tsx
  • src/app/page.tsx
  • src/components/layout/app-navigation-link.tsx
  • src/components/layout/app-navigation.ts
  • src/components/layout/app-shell.stories.tsx
  • src/components/layout/app-shell.test.tsx
  • src/components/layout/app-shell.tsx
  • src/components/layout/mobile-app-navigation.tsx
  • src/features/current-user/current-user-contract.test.ts
  • src/features/current-user/current-user-contract.ts
  • src/features/current-user/current-user-loading.tsx
  • src/features/current-user/current-user-overview.stories.tsx
  • src/features/current-user/current-user-overview.test.tsx
  • src/features/current-user/current-user-overview.tsx
  • src/features/current-user/current-user.service.server.ts
  • src/lib/bridgeworks-api-config.server.ts
  • src/lib/bridgeworks-api-config.test.ts
  • src/lib/bridgeworks-api-config.ts
  • src/lib/bridgeworks-api-core.test.ts
  • src/lib/bridgeworks-api-core.ts
  • src/lib/bridgeworks-api.server.ts
  • tests/example.spec.ts
📜 Review details
🔇 Additional comments (26)
src/app/page.tsx (1)

1-23: LGTM!

Also applies to: 27-38, 40-85, 87-107

src/app/layout.tsx (1)

32-32: LGTM!

Also applies to: 66-85

src/app/globals.css (1)

30-38: LGTM!

Also applies to: 61-101, 104-144

tests/example.spec.ts (1)

4-25: LGTM!

README.md (2)

3-28: LGTM!

Also applies to: 39-109, 110-146, 147-156, 161-194, 196-211


37-37: 🔒 Security & Privacy

Security Misconfiguration (CWE-319): Cleartext Transmission of Sensitive Information

Giới hạn HTTP cho môi trường local.

README.md mô tả mọi origin http:// hoặc https:// là hợp lệ. Request current-user gửi Authorization: Bearer <Clerk session token> tại Line 65 và Line 116. Nếu môi trường triển khai dùng HTTP ngoài loopback, token đi qua kết nối không mã hóa. server-only, credentials: "omit"Cache-Control: no-store không bảo vệ hop mạng.

Chỉ cho phép HTTP với loopback trong phát triển, hoặc từ chối HTTP ở môi trường triển khai. Xác minh thêm redirect không chuyển Authorization tới HTTP hoặc host không được ủy quyền.

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 8 \
  'NEXT_PUBLIC_API_BASE_URL|http:|https:|redirect|Authorization|fetch\(' \
  src/lib/bridgeworks-api-config.ts \
  src/lib/bridgeworks-api-config.server.ts \
  src/lib/bridgeworks-api-core.ts \
  src/lib/bridgeworks-api.server.ts
src/lib/bridgeworks-api-config.server.ts (1)

1-12: LGTM!

src/features/current-user/current-user-overview.test.tsx (1)

1-89: LGTM!

src/features/current-user/current-user-overview.stories.tsx (1)

1-137: LGTM!

src/app/(protected)/app/error.tsx (1)

17-32: LGTM!

src/components/layout/app-navigation.ts (1)

1-25: LGTM!

src/components/layout/app-navigation-link.tsx (1)

1-36: LGTM!

src/components/layout/app-shell.tsx (1)

3-4: LGTM!

Also applies to: 17-21, 24-30, 46-50

src/components/layout/mobile-app-navigation.tsx (1)

3-10: LGTM!

Also applies to: 22-22, 31-31, 55-58, 76-99

src/components/layout/app-shell.test.tsx (1)

3-23: LGTM!

Also applies to: 37-39, 56-58

src/components/layout/app-shell.stories.tsx (1)

5-37: LGTM!

Also applies to: 54-56

src/lib/bridgeworks-api-config.test.ts (1)

5-52: LGTM!

src/lib/bridgeworks-api-core.ts (2)

37-81: LGTM!


100-111: 🔒 Security & Privacy | ⚡ Quick win

Sensitive Data Exposure (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: Internal

Reachability path
● Entry
  src/app/layout.tsx:26
  metadata
│
▼
● Hop
  src/app/(protected)/app/page.tsx:9
  metadata
│
▼
● Hop
  src/features/current-user/current-user-overview.tsx:107
  ReadyOverview
│
▼
● Hop
  src/features/current-user/current-user-contract.ts:89
  mapCurrentUserResponse
│
▼
● Sink
  src/lib/bridgeworks-api-core.ts

Ràng buộc HTTPS cho request mang token phiên.

requestBridgeWorksApi gắn Authorization: Bearer ${token} vào mọi request. baseUrl đến từ classifyBridgeWorksApiConfiguration, hàm này chấp nhận cả http:https: (xem src/lib/bridgeworks-api-config.ts:34-80). Nếu môi trường triển khai đặt NEXT_PUBLIC_API_BASE_URL là một origin http:// không phải loopback, token phiên Clerk đi qua kênh không mã hoá.

redirect: "manual"credentials: "omit" chặn rò rỉ qua redirect và cookie, nhưng không bảo đảm bảo mật truyền tải.

Đề xuất: chỉ cho phép http: với host loopback, hoặc chặn gắn credential khi protocol không phải https:.

🔒 Đề xuất chặn credential trên kênh không mã hoá
   try {
-    const response = await fetcher(new URL(path, `${baseUrl}/`), {
+    const url = new URL(path, `${baseUrl}/`);
+    if (
+      url.protocol !== "https:" &&
+      url.hostname !== "127.0.0.1" &&
+      url.hostname !== "localhost" &&
+      url.hostname !== "::1"
+    ) {
+      return { kind: "network-error", requestId };
+    }
+    const response = await fetcher(url, {
       method: "GET",

Chạy script sau để xác nhận cấu hình base URL trong repo và tài liệu triển khai:

#!/bin/bash
# Mục tiêu: kiểm tra mọi nơi khai báo NEXT_PUBLIC_API_BASE_URL và ràng buộc protocol.
rg -n -C4 'NEXT_PUBLIC_API_BASE_URL'
fd -H -t f -g '.env*' --exec rg -n 'API_BASE_URL' {} \;
rg -n -C6 'hasSupportedProtocol|protocol ===' --glob '*.ts'
src/lib/bridgeworks-api-core.test.ts (1)

8-131: LGTM!

src/lib/bridgeworks-api.server.ts (1)

19-40: LGTM!

src/features/current-user/current-user-contract.ts (1)

63-153: LGTM!

src/features/current-user/current-user-contract.test.ts (1)

42-201: LGTM!

src/features/current-user/current-user.service.server.ts (1)

37-109: LGTM!

src/app/(protected)/app/loading.tsx (1)

1-5: LGTM!

src/features/current-user/current-user-overview.tsx (1)

203-302: LGTM!

Comment on lines +17 to 22
if (
experience.status === "signed-out" ||
experience.status === "unauthorized"
) {
return experience.redirectToSignIn({ returnBackUrl: APP_ROUTE });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Trạng thái unauthorized có thể tạo vòng lặp chuyển hướng.

Trạng thái signed-outunauthorized được xử lý chung. Hai trạng thái này khác nhau:

  • signed-out: Clerk không có phiên. Chuyển hướng tới sign-in là đúng.
  • unauthorized: Clerk vẫn có phiên hợp lệ, nhưng backend Identity trả 401 hoặc getToken() trả null (xem src/features/current-user/current-user.service.server.ts dòng 68-79 và 94-99).

Nếu Clerk vẫn xem người dùng đã đăng nhập, trang sign-in chuyển hướng ngược về returnBackUrl: APP_ROUTE. Trang này gọi lại backend, nhận unauthorized lần nữa và chuyển hướng tiếp. Kết quả là vòng lặp chuyển hướng, không phải khôi phục phiên.

Hệ quả downstream: nhánh case "unauthorized" trong src/features/current-user/current-user-overview.tsx (dòng 239-256) đã có panel "Sign in again", nhưng nhánh này không bao giờ đạt tới từ route này.

Đề xuất: chỉ chuyển hướng cho signed-out, và render panel unauthorized để người dùng chủ động đăng nhập lại.

🐛 Đề xuất tách hai trạng thái
-  if (
-    experience.status === "signed-out" ||
-    experience.status === "unauthorized"
-  ) {
+  if (experience.status === "signed-out") {
     return experience.redirectToSignIn({ returnBackUrl: APP_ROUTE });
   }

Chạy script sau để xác nhận hành vi middleware và route sign-in khi phiên Clerk còn hiệu lực:

#!/bin/bash
# Mục tiêu: kiểm tra middleware, route sign-in và cách redirectToSignIn được dùng.
fd -t f 'middleware.*' --exec cat -n {} \;
rg -n -C6 'redirectToSignIn|SIGN_IN_ROUTE|afterSignInUrl|forceRedirectUrl'
fd -t d 'sign-in' --exec tree {} \;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/`(protected)/app/page.tsx around lines 17 - 22, Update the route
guard around the experience status check to call experience.redirectToSignIn
with APP_ROUTE only for "signed-out". For "unauthorized", allow rendering to
continue so the existing unauthorized panel in current-user-overview.tsx can
prompt the user to sign in again without creating a redirect loop.

Comment on lines +53 to +67
try {
const url = new URL(normalized);
const hasSupportedProtocol = url.protocol === "http:" || url.protocol === "https:";
const hasOriginOnlyPath = url.pathname === "/";

if (
!hasSupportedProtocol ||
!url.hostname ||
url.username ||
url.password ||
url.search ||
url.hash ||
!hasOriginOnlyPath
) {
throw new Error("invalid BridgeWorks API base URL");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg 'bridgeworks-api|me |AUTHORIZATION|Authorization|NEXT_PUBLIC_API_BASE_URL|CLERK|clerk' || true

echo
echo "== config files outline/contents =="
for f in $(git ls-files | rg 'src/lib/bridgeworks-api(-config\.server|\.server|-config)\.ts|src/lib/bridgeworks-api-config\.test\.ts$'); do
  echo "--- $f ---"
  wc -l "$f"
  sed -n '1,220p' "$f" | cat -n
done

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 6826


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- src/lib/bridgeworks-api-core.ts ---"
wc -l src/lib/bridgeworks-api-core.ts
sed -n '1,240p' src/lib/bridgeworks-api-core.ts | cat -n

echo
echo "--- call sites across repo ---"
rg -n "requestCurrentIdentityUser|requestBridgeWorksApi|/api/v1/me|Authorization|bearer|Bearer|setHeaders|followRedirect|redirect" src || true

Repository: DoMinhHHung/BridgeWorksApps

Length of output: 7879


Security Misconfiguration (CWE-319): Cleartext Transmission of Sensitive Information

Reachability: Internal

Reachability path
● Entry
  src/lib/bridgeworks-api-config.server.ts:8
  getBridgeWorksApiConfiguration
│
▼
● Sink
  src/lib/bridgeworks-api-config.ts

Không gửi Clerk token qua cấu hình http:.

classifyBridgeWorksApiConfiguration() coi http: là hợp lệ; requestCurrentIdentityUser() sau đó gọi /api/v1/me với Authorization: Bearer ... vào URL http: đó. Đặt giới hạn cấu hình thành https: khi có thể; nếu cần local http:, đảm bảo token không được gửi hoặc được bảo vệ qua proxy.

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

In `@src/lib/bridgeworks-api-config.ts` around lines 53 - 67, Update
classifyBridgeWorksApiConfiguration() to require https: for BridgeWorks API base
URLs, while allowing http: only for explicitly supported local development
cases. In requestCurrentIdentityUser(), ensure the Clerk Bearer token is never
sent over an unprotected http: URL, or route such requests through the
configured secure proxy.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8f2e1479c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +17 to +21
if (
experience.status === "signed-out" ||
experience.status === "unauthorized"
) {
return experience.redirectToSignIn({ returnBackUrl: APP_ROUTE });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid looping backend-unauthorized sessions

When Identity returns 401 while Clerk still considers the browser signed in, this branch sends the user to the sign-in route, but SignInPage immediately redirects any signed-in Clerk session back to /app (src/app/(auth)/sign-in/[[...sign-in]]/page.tsx:42-44). That makes backend-unauthorized recovery a redirect loop instead of an actionable unauthorized state; keep these sessions on an explicit recovery/sign-out flow or clear the Clerk session before sending them to sign-in.

Useful? React with 👍 / 👎.

@DoMinhHHung
DoMinhHHung merged commit 88ac0d0 into main Aug 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant