Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughThis pull request migrates the CAPTCHA system from hash-based verification to a Proof-of-Work (PoW) model. Changes span backend verification logic, database schema, frontend routing, and analytics views. Old CAPTCHA configuration pages are removed; a new marketing landing page and per-project CAPTCHA analytics dashboard are introduced. Changes
Sequence DiagramsequenceDiagram
actor Client
participant Backend as Backend Controller
participant CaptchaService as CAPTCHA Service
participant Redis
participant ClickHouse as Verification Logic
Client->>Backend: POST /captcha/generate { pid }
activate Backend
Backend->>CaptchaService: generateChallenge(pid)
activate CaptchaService
CaptchaService->>CaptchaService: Compute challenge hash
CaptchaService->>CaptchaService: Get project difficulty (pid)
CaptchaService->>Redis: Store challenge + difficulty (TTL)
Redis-->>CaptchaService: Stored
CaptchaService-->>Backend: { challenge, difficulty }
deactivate CaptchaService
Backend-->>Client: { challenge, difficulty }
deactivate Backend
Note over Client: Client solves PoW:<br/>finds nonce where<br/>hash(challenge+nonce)<br/>has N leading zeros
Client->>Backend: POST /captcha/verify { challenge, nonce, solution, pid }
activate Backend
Backend->>CaptchaService: verifyPoW(challenge, nonce, solution)
activate CaptchaService
CaptchaService->>Redis: Get stored difficulty for challenge
Redis-->>CaptchaService: difficulty
CaptchaService->>ClickHouse: hasValidPrefix(hash, difficulty)
ClickHouse-->>CaptchaService: Valid ✓ / Invalid ✗
alt PoW Valid
CaptchaService->>CaptchaService: generateToken(pid, challenge, timestamp)
CaptchaService-->>Backend: token
Backend-->>Client: { token }
else PoW Invalid
Backend-->>Client: 403 Forbidden
end
deactivate CaptchaService
deactivate Backend
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas requiring extra attention:
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings, 1 inconclusive)
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 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (11)
web/app/pages/Project/View/components/ProjectSidebar.tsx (1)
32-34: Consider tightening typings for icon color maps
ICON_COLORSandGROUP_ICON_COLORScorrectly gaincaptchaentries and will style the new group/tab consistently.If you want to catch typos at compile time, consider typing these as something like
Record<ProjectTabKey | 'webAnalytics' | 'productAnalytics', string>(or a dedicated union for group ids) instead ofRecord<string, string>, so missing/extra keys surface during refactors.Also applies to: 39-43
backend/apps/cloud/src/project/dto/update-project.dto.ts (1)
35-47: AligncaptchaDifficultyvalidation/docs with UI optionsDTO and docs advertise a 1–6 range (
@Min(1),minimum: 1), but the settings UI (ProjectSettingsCAPTCHA tab) only allows 2–6. Consider either:
- Exposing difficulty
1in the UI, or- Tightening the DTO to
Min(2)and updating the description to “Range: 2–6”.Keeping these in sync avoids confusing/hidden states depending on whether clients use the UI or call the API directly.
backend/apps/cloud/src/project/entity/project.entity.ts (1)
72-81: MakecaptchaSecretKeyexplicitly nullable in the entity
captchaSecretKeyis used as a presence flag (“if set, CAPTCHA is enabled”) and is initialized withdefault: null, but:
- The TypeScript type is
string(notstring | null), and- The column options omit
nullable: true, unlikepasswordHashbelow.For consistency and to avoid surprises when the key is absent/cleared, consider:
// CAPTCHA secret key - if set, CAPTCHA is enabled for this project @ApiProperty() @Column('varchar', { default: null, length: CAPTCHA_SECRET_KEY_LENGTH, nullable: true, }) captchaSecretKey: string | null(Ensuring the underlying migration also treats this column as nullable.)
backend/apps/cloud/src/project/project.controller.ts (2)
746-779: Tighten typing insecretGento avoid// @ts-ignore
secretGencorrectly validates PID, checks access, generates a new secret, and clears Redis. The only smell is the// @ts-ignorebeforeprojectService.update:// @ts-ignore await this.projectService.update({ id: pid }, { captchaSecretKey: secret })You can avoid the ignore by leveraging a typed helper or a cast, e.g.:
await this.projectService.update( { id: pid }, { captchaSecretKey: secret } as Partial<Project>, )or by using a dedicated
updateProject(pid, { captchaSecretKey: secret })helper from the service. This keeps the route type-safe without changing runtime behavior.
1850-1859: Avoid redundantawaitinsidePromise.allfor data-existence checksThe new
isCaptchaDataExistsflag is wired correctly, but the pattern:const [isDataExists, isErrorDataExists, isCaptchaDataExists] = await Promise.all([ !_isEmpty(await this.projectService.getPIDsWhereAnalyticsDataExists([id])), !_isEmpty(await this.projectService.getPIDsWhereErrorsDataExists([id])), !_isEmpty(await this.projectService.getPIDsWhereCaptchaDataExists([id])), ])runs the three service calls sequentially because of the inner
awaits. A more idiomatic (and concurrently executed) version would be:const [analyticsPids, errorPids, captchaPids] = await Promise.all([ this.projectService.getPIDsWhereAnalyticsDataExists([id]), this.projectService.getPIDsWhereErrorsDataExists([id]), this.projectService.getPIDsWhereCaptchaDataExists([id]), ]) const isDataExists = !_isEmpty(analyticsPids) const isErrorDataExists = !_isEmpty(errorPids) const isCaptchaDataExists = !_isEmpty(captchaPids)This keeps behavior identical but avoids unnecessary latency on this hot path.
Also applies to: 1895-1898
web/app/pages/Project/View/components/CaptchaChart.tsx (1)
1-57: CaptchaChart implementation is solid; consider makingchartIdconfigurableThe component correctly defers to
getSettingsCaptcha, short-circuits whenchartis absent, and passes a well-scopeddepsarray intoMainChart. Only minor nit:chartIdis hard-coded to'captcha-chart', which could cause collisions if multiple captcha charts are ever rendered simultaneously.If you foresee that scenario, consider either:
- Accepting
chartIdas a prop, or- Deriving a unique ID (e.g., from
chartTypeor PID) to avoid clashes inuseChartManager.Otherwise, this looks good.
web/app/pages/Project/Settings/ProjectSettings.tsx (1)
14-25: CAPTCHA settings flow is well-integrated; align difficulty range with backendThe new CAPTCHA tab integrates cleanly:
- It’s only visible on cloud (
visible: !isSelfhosted).loadProjectseedscaptchaSecretKeyandcaptchaDifficulty.- Secret generation/regeneration uses
reGenerateCaptchaSecretKey(id)with appropriate confirmation for regen.- Difficulty changes are pushed via
updateProject(id, { captchaDifficulty })and gated behind an explicit Save.Two minor points to consider:
Difficulty range mismatch vs backend DTO
Here the UI only exposes difficulty values 2–6:
items={[ { value: 2, label: t('...veryEasy') }, { value: 3, label: t('...easy') }, { value: 4, label: t('...medium') }, { value: 5, label: t('...hard') }, { value: 6, label: t('...veryHard') }, ]}while
UpdateProjectDtoand the entity describe/validate a 1–6 range. If 1 is intentionally not user-facing, it would be clearer to update the DTO validation/docs (e.g.Min(2), “Range: 2–6”) so API and UI tell the same story. Otherwise, consider adding the “1” option here.Optional UX polish
- Key generation / regeneration currently has no loading state. If calls to
reGenerateCaptchaSecretKeycan be slow, you might eventually want anisRegeneratingflag to disable the buttons and show a spinner, similar toisSavingDifficulty.None of these are blockers; the overall CAPTCHA settings integration looks solid.
Also applies to: 31-47, 321-361, 380-385, 438-442, 988-1090, 1198-1217
web/app/pages/Project/View/ViewProject.tsx (2)
390-433: Captcha context wiring looks consistent; consider exposing a refresh helper only if needed laterThe addition of
captchaRefreshTriggertoViewProjectContextType,defaultViewProjectContext, state, and provider value is internally consistent and type-safe; consumers likeCaptchaViewcan now react to refreshes without touching the main analytics state.If more components eventually need to initiate a captcha refresh, you might later factor this into a small context helper (e.g.
refreshCaptcha()) instead of exposing the raw counter, but that’s optional at this stage.Also applies to: 540-541, 3414-3415
1461-1534: Captcha tab integration is correct; minor optional tweaks to loading/auto‑refresh behavior
- The new
PROJECT_TABS.captchaentry (withShieldCheckIcon) is correctly limited to the non‑selfhosted branch and flows throughProjectSidebar/MobileTabSelector.- The extended
!project.isDataExistsguard to special‑case(activeTab === PROJECT_TABS.captcha && project.isCaptchaDataExists)avoids blocking the captcha tab when only captcha data exists, without changing behavior for other tabs.- The
refreshStatsbranch that bumpscaptchaRefreshTriggeris a clean way to letCaptchaViewre‑fetch independently of the main analytics loaders.Two minor, non‑blocking considerations:
isAutoRefreshingis set but never reset on the captcha branch (sincedataLoadingis not used there). It currently only gates the topLoadingBar, so this is mostly cosmetic; if auto refreshes for captcha become more prominent, you may want to explicitly clearisAutoRefreshingwhen the captcha reload completes.analyticsLoadingis never driven by the captcha data path; that’s fine today because it’s used only for the main analytics tabs, but worth keeping in mind if you ever unify loading indicators across all project tabs.Also applies to: 2611-2667, 3318-3323, 4298-4299
backend/apps/cloud/src/captcha/captcha.controller.ts (1)
39-47: Verify endpoint PoW flow is coherent and matches the new challenge APIThe controller changes line up cleanly with the new PoW model:
/generatenow validatespidand delegates togenerateChallenge(pid)./verifycorrectly destructures{ challenge, nonce, solution, pid }, validates the project, runsverifyPoW, and on success issues a token viagenerateToken(pid, challenge, timestamp).- Dummy PIDs (
ALWAYS_PASS/ALWAYS_FAIL) are handled explicitly and no longer rely on hash-based behavior.One optional thing to consider is trimming the logged
verifyDtoto avoid noisy logs (e.g. log onlypidand a shortened challenge), but functionally the handler looks good.Also applies to: 58-61, 67-80, 82-98, 109-115
web/app/pages/Project/View/components/CaptchaView.tsx (1)
77-88: Reduce duplication by reusing shared helpers and trimming unused hooksA couple of small refactors would simplify this component:
Reuse existing date formatting helper instead of re‑implementing
getFormatDate
There’s already a sharedgetFormatDateinViewProject.helpers.tsxused by other loaders. Importing and reusing it here will keep date formatting consistent and avoid yet another hand‑rolled variant.-import { Filter } from '../interfaces/traffic' -import { Panel } from '../Panels' -import { parseFilters } from '../utils/filters' -import { ViewProjectContext } from '../ViewProject' -import { deviceIconMapping } from '../ViewProject.helpers' +import { Filter } from '../interfaces/traffic' +import { Panel } from '../Panels' +import { parseFilters } from '../utils/filters' +import { ViewProjectContext } from '../ViewProject' +import { deviceIconMapping, getFormatDate } from '../ViewProject.helpers' ... - const getFormatDate = (date: Date) => { - const yyyy = date.getFullYear() - let mm: string | number = date.getMonth() + 1 - let dd: string | number = date.getDate() - if (dd < 10) dd = `0${dd}` - if (mm < 10) mm = `0${mm}` - return `${yyyy}-${mm}-${dd}` - }Drop the local
useSizecall if you don’t actually use the measured size
You currently do:const [ref] = useSize() as any ... <div ref={ref}>but
rotateXAxisis derived fromsizeinViewProjectContext, not from this hook’s measurement. Unless you plan to use this local size for something else, you can removeuseSizehere and therefon the outer<div>to avoid an unnecessary observer.-import useSize from '~/hooks/useSize' ... - const tnMapping = typeNameMapping(t) - const [ref] = useSize() as any + const tnMapping = typeNameMapping(t) ... - return ( - <div ref={ref}> + return ( + <div>These are purely cleanup changes and won’t affect behavior.
Also applies to: 158-170, 179-356
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (47)
backend/apps/cloud/src/captcha/captcha.controller.ts(4 hunks)backend/apps/cloud/src/captcha/captcha.service.ts(9 hunks)backend/apps/cloud/src/captcha/dtos/generate.dto.ts(1 hunks)backend/apps/cloud/src/captcha/dtos/manual.dto.ts(2 hunks)backend/apps/cloud/src/captcha/interfaces/generated-captcha.ts(1 hunks)backend/apps/cloud/src/project/dto/create-project.dto.ts(0 hunks)backend/apps/cloud/src/project/dto/project.dto.ts(0 hunks)backend/apps/cloud/src/project/dto/update-project.dto.ts(2 hunks)backend/apps/cloud/src/project/entity/project.entity.ts(1 hunks)backend/apps/cloud/src/project/project.controller.ts(4 hunks)backend/apps/cloud/src/project/project.service.ts(2 hunks)backend/apps/community/src/user/user.controller.ts(0 hunks)backend/migrations/mysql/2025_12_06_captcha_difficulty.sql(1 hunks)backend/migrations/mysql/2025_12_06_remove_captcha_flags.sql(1 hunks)web/app/App.tsx(1 hunks)web/app/api/index.ts(1 hunks)web/app/components/Footer/index.tsx(1 hunks)web/app/components/Header/index.tsx(2 hunks)web/app/lib/constants/index.ts(1 hunks)web/app/lib/models/Project.ts(2 hunks)web/app/pages/Captcha/Settings/CaptchaSettings.tsx(0 hunks)web/app/pages/Captcha/Settings/index.tsx(0 hunks)web/app/pages/Captcha/View/ViewCaptcha.helpers.tsx(0 hunks)web/app/pages/Captcha/View/ViewCaptcha.tsx(0 hunks)web/app/pages/Captcha/View/index.tsx(0 hunks)web/app/pages/Captcha/index.tsx(1 hunks)web/app/pages/Dashboard/Dashboard.tsx(2 hunks)web/app/pages/Dashboard/ProjectCard.tsx(5 hunks)web/app/pages/ErrorTracking/index.tsx(0 hunks)web/app/pages/Performance/index.tsx(0 hunks)web/app/pages/Project/Settings/ProjectSettings.tsx(7 hunks)web/app/pages/Project/View/ViewProject.helpers.tsx(2 hunks)web/app/pages/Project/View/ViewProject.tsx(10 hunks)web/app/pages/Project/View/components/CaptchaChart.tsx(1 hunks)web/app/pages/Project/View/components/CaptchaView.tsx(1 hunks)web/app/pages/Project/View/components/ProjectSidebar.tsx(5 hunks)web/app/pages/UserSettings/UserSettings.tsx(1 hunks)web/app/routes/captcha.tsx(1 hunks)web/app/routes/captchas.$id.tsx(0 hunks)web/app/routes/captchas.new.tsx(0 hunks)web/app/routes/captchas.settings.$id.tsx(0 hunks)web/app/ui/Input.tsx(1 hunks)web/app/ui/icons/BackgroundSvg.tsx(0 hunks)web/app/utils/analytics.ts(0 hunks)web/app/utils/routes.ts(1 hunks)web/app/utils/server.ts(1 hunks)web/public/locales/en.json(4 hunks)
💤 Files with no reviewable changes (15)
- backend/apps/cloud/src/project/dto/create-project.dto.ts
- backend/apps/cloud/src/project/dto/project.dto.ts
- backend/apps/community/src/user/user.controller.ts
- web/app/utils/analytics.ts
- web/app/pages/ErrorTracking/index.tsx
- web/app/routes/captchas.new.tsx
- web/app/pages/Performance/index.tsx
- web/app/pages/Captcha/Settings/index.tsx
- web/app/pages/Captcha/View/index.tsx
- web/app/pages/Captcha/View/ViewCaptcha.helpers.tsx
- web/app/pages/Captcha/View/ViewCaptcha.tsx
- web/app/routes/captchas.settings.$id.tsx
- web/app/pages/Captcha/Settings/CaptchaSettings.tsx
- web/app/routes/captchas.$id.tsx
- web/app/ui/icons/BackgroundSvg.tsx
🧰 Additional context used
🧬 Code graph analysis (10)
web/app/routes/captcha.tsx (1)
web/app/lib/constants/index.ts (2)
isSelfhosted(399-399)isDisableMarketingPages(401-402)
web/app/pages/Project/View/components/ProjectSidebar.tsx (1)
web/app/lib/constants/index.ts (1)
PROJECT_TABS(491-493)
web/app/pages/Captcha/index.tsx (2)
web/app/lib/constants/index.ts (1)
DOCS_URL(366-366)web/app/components/marketing/DitchGoogle.tsx (1)
DitchGoogle(8-64)
web/app/pages/Project/View/components/CaptchaChart.tsx (2)
web/app/pages/Project/View/ViewProject.helpers.tsx (1)
getSettingsCaptcha(1961-1961)web/app/pages/Project/View/components/MainChart.tsx (1)
MainChart(16-42)
web/app/pages/Project/View/ViewProject.helpers.tsx (2)
web/app/lib/constants/index.ts (6)
chartTypes(921-924)TimeFormat(323-326)tbsFormatMapper24h(315-321)tbsFormatMapper(291-297)tbsFormatMapperTooltip24h(307-313)tbsFormatMapperTooltip(299-305)web/app/utils/generic.ts (1)
nFormatter(20-24)
web/app/pages/Project/Settings/ProjectSettings.tsx (3)
web/app/lib/constants/index.ts (1)
isSelfhosted(399-399)backend/apps/cloud/src/project/project.service.ts (1)
updateProject(1590-1592)web/app/api/index.ts (2)
updateProject(418-424)reGenerateCaptchaSecretKey(1156-1162)
web/app/pages/Project/View/components/CaptchaView.tsx (6)
web/app/pages/Project/View/ViewProject.helpers.tsx (1)
deviceIconMapping(1646-1654)web/app/providers/ThemeProvider.tsx (1)
useTheme(29-37)web/app/pages/Project/View/ViewProject.tsx (1)
ViewProjectContext(435-435)web/app/pages/Project/View/utils/filters.tsx (1)
parseFilters(97-133)web/app/api/index.ts (1)
getCaptchaData(855-865)web/app/lib/constants/index.ts (3)
BROWSER_LOGO_MAP(931-978)OS_LOGO_MAP(980-1006)OS_LOGO_MAP_DARK(1008-1013)
web/app/pages/Project/View/ViewProject.tsx (1)
web/app/lib/constants/index.ts (1)
PROJECT_TABS(491-493)
backend/apps/cloud/src/captcha/captcha.controller.ts (1)
backend/apps/cloud/src/captcha/captcha.service.ts (1)
DUMMY_PIDS(31-34)
backend/apps/cloud/src/captcha/captcha.service.ts (3)
backend/apps/community/src/common/utils.ts (1)
hash(79-85)backend/apps/cloud/src/common/utils.ts (1)
hash(22-28)backend/apps/cloud/src/captcha/interfaces/generated-captcha.ts (1)
GeneratedChallenge(1-4)
🔇 Additional comments (33)
web/app/pages/UserSettings/UserSettings.tsx (1)
39-39: LGTM! Clean import consolidation.The duplicate Text import has been properly consolidated into a single import statement, improving code cleanliness without affecting functionality.
web/app/pages/Project/View/components/ProjectSidebar.tsx (3)
3-10: ShieldCheckIcon import fits existing icon usageThe added
ShieldCheckIconimport aligns with how other lucide icons are used in this file and in the new CAPTCHA group. No issues here.
218-228: CAPTCHA group logic looks consistent with existing groupingThe CAPTCHA group:
- Filters on
PROJECT_TABS.captcha, matching the pattern used for other groups.- Uses its own icon and color via
ShieldCheckIconandGROUP_ICON_COLORS.captcha.- Starts collapsed via
defaultExpanded: falsebut will auto-expand when active due to the sharedCollapsibleGroupeffect.Assuming
PROJECT_TABS.captchaand thecommon.captchai18n key are defined elsewhere, this is a clean, non‑breaking extension of the sidebar.
238-238: Tooltip width tweak is safe and improves truncation behaviorAdding
className='max-w-full'toTooltipmatches theTextnode’smax-w-fulland should prevent layout overflow/clipping for long project names without affecting behavior.web/app/pages/Dashboard/ProjectCard.tsx (4)
148-148: LGTM! Dependency array correctly updated.The removal of
project.isCaptchaProjectfrom the dependency array is correct since this field is no longer used in the badges calculation. All other dependencies are properly included.
261-261: Line 261 is not a change addressing CAPTCHA projects.The review comment appears to be based on a misunderstanding.
isCaptchaProjectdoes not exist in the codebase—neither in current code nor in git history. The live visitors MiniCard at line 261 renders unconditionally with theliveprop, and there is no evidence of conditional rendering being removed based on CAPTCHA project type. The backend/live-visitorsAPI endpoint does not implement CAPTCHA-specific filtering for this data.Likely an incorrect or invalid review comment.
240-240: TheisCaptchaProjectfield has been removed from the database schema via migration, eliminating all CAPTCHA-specific logic from the project system. The label consolidation is part of this cleanup and does not present any concern for existing functionality.Likely an incorrect or invalid review comment.
203-203: Verify backend routing is updated to match the consolidated project routing.The pathname now always uses
routes.project, removing the conditional CAPTCHA-specific routing that existed in the previous hash-based implementation. Confirm that the backend has been updated to handle PoW CAPTCHA projects through the standard project route and that all related routing logic throughout the application is consistent with this change.backend/migrations/mysql/2025_12_06_captcha_difficulty.sql (1)
1-6: LGTM! Migration adds captchaDifficulty column appropriately.The migration correctly adds the
captchaDifficultycolumn with a sensible default value of 4. TheTINYINT UNSIGNEDtype is appropriate for storing difficulty levels (0-255 range), and the default aligns with the PoW requirements mentioned in the comments.backend/apps/cloud/src/captcha/dtos/manual.dto.ts (1)
1-37: LGTM! DTO correctly reflects PoW-based verification model.The changes properly restructure the verification payload for Proof-of-Work CAPTCHA:
hash→challenge: Clearer naming for the server-generated challengecode→solution: More descriptive for the resulting hash- New
noncefield: Essential for PoW verificationAll fields have appropriate validation decorators and API documentation.
web/app/utils/server.ts (1)
223-223: LGTM! Comment correctly updated to reflect route changes.The removal of
new_captchafrom the comment aligns with the CAPTCHA feature restructuring. The newcaptchaLandingroute relies on default title behavior, which is appropriate for a marketing page.web/app/lib/models/Project.ts (1)
176-192: LGTM! Project interface correctly updated for new CAPTCHA model.The changes properly reflect the migration from flag-based to secret-key-based CAPTCHA:
captchaDifficulty: Stores the PoW difficulty levelisCaptchaDataExists: Indicates whether analytics data exists for this project's CAPTCHAThe removal of
isCaptchaProjectandisCaptchaEnabled(mentioned in the summary) aligns with the backend entity changes and simplifies the model.web/app/api/index.ts (1)
410-416: All call sites correctly updated after removingisCaptchaparameter.The removal is complete and properly implemented. Both call sites in the frontend (Onboarding.tsx and Dashboard.tsx) pass only
nameand optionalorganisationId, matching the new signature. No remaining code passes the removedisCaptchaparameter. This aligns with the backend changes that moved CAPTCHA configuration to post-creation settings.web/app/pages/Captcha/index.tsx (1)
1-182: LGTM! Well-structured CAPTCHA marketing page.The implementation follows established patterns and best practices:
- Proper i18n integration for all user-facing text — all 21 translation keys are present in the locale files
- Reuses existing marketing components (DitchGoogle, MarketingPricing)
- Responsive design with dark mode support
- Clean component structure with hero, features, and how-it-works sections
- Correct use of internal routing and external documentation links
backend/migrations/mysql/2025_12_06_remove_captcha_flags.sql (1)
1-5: Verify migration dependencies and data migration strategy before deployment.This migration drops
isCaptchaProjectandisCaptchaEnabledcolumns without preserving existing CAPTCHA project settings. Before running this migration, confirm:
Migration ordering: All data migrations that convert existing flag-based CAPTCHA projects to the new secret-key-based model have completed and
captchaSecretKeyis populated for all projects that previously hadisCaptchaProject=trueorisCaptchaEnabled=true.Backend deployment: Code expecting the new schema (without these columns) must be deployed and active before this migration executes, or the migration must be rolled back if incompatible code is running.
Data audit: Verify no projects will lose CAPTCHA configuration by checking that all projects with the old flags have a non-null
captchaSecretKeyvalue before dropping the columns.backend/apps/cloud/src/project/project.service.ts (2)
1185-1232: LGTM! Captcha data existence check follows established pattern.The new
getPIDsWhereCaptchaDataExistsmethod correctly mirrors the implementation pattern ofgetPIDsWhereAnalyticsDataExistsandgetPIDsWhereErrorsDataExists. The SQL query properly checks for captcha data existence using parameterized queries.
324-324: Field migration is complete and consistent across the codebase.The
captchaDifficultyfield (number) has successfully replacedisCaptchaEnabledthroughout the codebase. All consuming code properly expects and handles the numeric type: the entity and DTO define it asnumber, the controller assigns it correctly, the captcha service uses it with a sensible default fallback, and the UI state management and rendering logic all handle it appropriately. No stray references to the old field remain.web/app/pages/Project/View/ViewProject.helpers.tsx (1)
1804-1941: LGTM! Chart configuration follows established patterns.The
getSettingsCaptchafunction is well-implemented and consistent with other chart configuration helpers in the file:
- Properly builds data columns from
chart.xandchart.results- Calculates optimal Y-axis ticks using the existing
calculateOptimalTickshelper- Configures regions for dashed lines on recent data
- Provides consistent tooltip and legend formatting
- Color choice
#16a34a(green-600) is semantically appropriate for captcha metricsweb/app/lib/constants/index.ts (1)
488-488: LGTM! Captcha tab correctly added to production tabs only.The addition of the
captchatab toPRODUCTION_PROJECT_TABS(and not toSELFHOSTED_PROJECT_TABS) correctly reflects the PR's objective to update only Cloud/Enterprise Edition code.web/app/App.tsx (1)
65-65: LGTM! Header suppression appropriate for landing page.Adding
routesPath.captchaLandingtoroutesWithOutHeaderis consistent with other marketing/landing pages (main, performance, errorTracking) that also suppress the default header for custom hero sections.backend/apps/cloud/src/captcha/dtos/generate.dto.ts (1)
1-12: LGTM! DTO simplified by removing theme option.Removing the
themeproperty streamlines theGenerateDtoto only require the project ID, which aligns with the migration from hash-based to PoW-based CAPTCHA. The remaining validation onpidis appropriate.web/app/components/Footer/index.tsx (1)
35-35: LGTM! Footer link updated to use internal captcha route.Changing the captcha link from the external
CAPTCHA_URLto the internalroutesPath.captchaLandingis consistent with the new internal captcha landing page introduced in this PR.web/app/routes/captcha.tsx (1)
1-22: LGTM! Route correctly handles selfhosted and marketing page restrictions.The captcha landing route implementation is clean and follows Remix conventions:
- Sitemap configuration appropriately excludes the page for selfhosted/disabled marketing scenarios
- Loader redirect to
/login(HTTP 302) is the correct approach for restricted environments- Route structure is consistent with other marketing pages in the codebase
backend/apps/cloud/src/captcha/interfaces/generated-captcha.ts (1)
1-4: Interface rename successfully completed across the codebase.The migration from
GeneratedCaptchatoGeneratedChallengeis complete. Verification confirms:
- No remaining references to the old interface name
- Old field names (
.data,.hash) are not accessed anywhere in captcha-related code- The new interface is properly imported in
captcha.service.tsand used as the return type forgenerateChallenge()- New fields (
challenge,difficulty) are correctly implemented and utilizedAll consumers have been updated successfully.
web/app/utils/routes.ts (1)
1-9: NewcaptchaLandingroute looks consistentAdding
captchaLanding: '/captcha'fits existing routing conventions and matches how the Header and other components now link to the CAPTCHA landing experience.web/app/pages/Dashboard/Dashboard.tsx (1)
13-14: Consolidated overall stats fetch is saneSwitching to a single
getOverallStatscall overprojects.map((p) => p.id)simplifies the dashboard live-update path and removes the captcha-specific stats branch, while still honoring the hostname-navigation guard insideupdateOverallStats. No issues spotted here.Also applies to: 346-360
web/app/components/Header/index.tsx (1)
34-35: Usingroutes.captchaLandingin header solutions is appropriateReplacing the CAPTCHA solution link with
routes.captchaLandingand droppingCAPTCHA_URLkeeps navigation internal to the app and reuses your existing “internal vs external link” logic. This is a clean integration with the new route.Also applies to: 73-76
backend/apps/cloud/src/project/project.controller.ts (2)
320-338: Unified project cap check looks goodChecking
_size(user.projects) >= maxProjectsregardless of project type simplifies quota enforcement and matches the semantics of “total projects per account/owner”. No functional issues here.
1746-1748:captchaDifficultyupdate path is straightforwardConditionally assigning
project.captchaDifficultywhenprojectDTO.captchaDifficulty !== undefinedis consistent with the DTO’s optional field and avoids unintentionally resetting the value. Given the class-validator constraints on the DTO, this is safe.web/app/pages/Project/View/components/CaptchaView.tsx (1)
48-76: CaptchaView integrates cleanly with existing dashboard context and refresh semanticsThe component is wired appropriately:
- It consumes
period,timeBucket,dateRange,captchaRefreshTrigger,timeFormat, andsizefromViewProjectContext, and usescaptchaRefreshTriggerto respond to global refreshes.- Data loading mirrors the main dashboards: first load gated by
analyticsLoading, subsequent reloads bydataLoading, withNoEventson empty results.- Filters are parsed from
searchParamsviaparseFilters, andgetFilterLinktoggles filter query params in a way consistent with other panels.- Chart rendering via
CaptchaChartand the four panel blocks (cc,dv,br,os) reuse existingPanel,CCRow, device icon mapping, and logo maps, providing a consistent UI with the other analytics tabs.Functionally this looks solid and in line with the rest of the project view implementation.
Also applies to: 89-143, 171-177, 179-357
web/public/locales/en.json (3)
217-261: Well-structured CAPTCHA landing page localization.The new
captchaPagesection includes comprehensive copy for a marketing landing page covering slogans, features (privacy, open-source, customizable, easy to use), integration steps, and a preview. Structure follows established patterns in the file and content is clear and professional.
1241-1262: Verify naming consistency between account-level and project-level CAPTCHA settings.There's a minor naming inconsistency between
profileSettings.captchakeys andproject.settings.captchakeys:
- Account level:
captchaSecretKey,generateSecretKey,regenerateSecretKey(fully qualified names)- Project level:
secretKey,generateKey,regenerateKey(short names, relying on parent context)This pattern difference may be intentional (account vs. project scope), but please verify it aligns with how these keys are referenced in the UI components and backend schema.
Also applies to: 727-734
114-114: Dashboard, metric, and project settings labels are well-integrated.Additions to
common.captcha,dashboard.captcha,project.captchaCompletions, project settings tab, and project captcha configuration follow established naming and structure conventions. The difficulty levels (veryEasy,easy,medium,hard,veryHard) align well with a Proof-of-Work model.Also applies to: 834-834, 964-964, 1234-1234, 1362-1370
Changes
Community Edition support
Database migrations
Documentation
Summary by CodeRabbit
New Features
Bug Fixes & Changes
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.