Skip to content

refactor: moves user registraction logic under user controller#1796

Merged
stalniy merged 1 commit intomainfrom
refactor/user-register
Aug 14, 2025
Merged

refactor: moves user registraction logic under user controller#1796
stalniy merged 1 commit intomainfrom
refactor/user-register

Conversation

@stalniy
Copy link
Contributor

@stalniy stalniy commented Aug 11, 2025

Why

To make logic more transparent and add tests on db level to cover different edge cases. Ref: #1735

Summary by CodeRabbit

  • New Features

    • Added POST /v1/register-user and GET /v1/user/me (included in OpenAPI) and a public User schema.
  • Refactor

    • Auth callback now registers/refreshes users and merges profile data.
    • Frontend onboarding uses a centralized storage key and injected URL service; logout now uses in-app logout that clears tokens and redirects.
  • Tests

    • New comprehensive tests for user registration, wallet/anonymous flows, and auth interceptor behavior.
  • Documentation

    • Deprecated old token info route in favor of new endpoints.

@stalniy stalniy requested a review from a team as a code owner August 11, 2025 12:48
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 11, 2025

Walkthrough

Adds user registration and current-user endpoints and schemas, implements UserService with upsert/anonymous migration and wallet transfer, passes client context from AuthInterceptor, updates repository utilities, and adjusts web Auth0 callback/profile flows, DI, logout, and onboarding storage key usage.

Changes

Cohort / File(s) Summary of changes
API router registration
apps/api/src/app.ts
Registers getCurrentUserRouter and registerUserRouter in OpenAPI handlers.
User routes & schemas
apps/api/src/user/routes/register-user/register-user.router.ts, apps/api/src/user/routes/get-current-user/get-current-user.router.ts, apps/api/src/user/routes/index.ts, apps/api/src/user/schemas/user.schema.ts
Adds POST /v1/register-user and GET /v1/user/me OpenAPI routes and exports; introduces UserSchema and related types.
User controller
apps/api/src/user/controllers/user/user.controller.ts
Adds registerUser and getCurrentUser endpoints; injects UserService and UserAuthTokenService; adjusts getById return typing.
User service & tests
apps/api/src/user/services/user/user.service.ts, apps/api/src/user/services/user/user.service.spec.ts
New UserService.registerUser implementing upsert with username-retry, anonymous migration, wallet transfer, analytics/logging; adds comprehensive tests.
User repository
apps/api/src/user/repositories/user/user.repository.ts
markAsActive now accepts { throttleTimeSeconds, ip?, fingerprint? }; adds upsert and toInput; truncates userAgent and conditionally updates lastIp/lastFingerprint.
Auth interceptor & tests
apps/api/src/auth/services/auth.interceptor.ts, apps/api/src/auth/services/auth.interceptor.spec.ts
Injects ExecutionContextService into AuthInterceptor; passes ip and fingerprint when marking users active; tests mock execution context.
Repository utility
apps/api/src/core/repositories/base.repository.ts
Adds exported isUniqueViolation(error: unknown): error is PostgresError type guard (Postgres code 23505).
Legacy route note
apps/api/src/routers/userRouter.ts
Adds deprecation JSDoc on /tokenInfo recommending /v1/register-user.
Test helper
apps/api/test/services/wallet-testing.service.ts
Updates helper to call /v1/register-user and return body.data from the response.
Web Auth0 / profile flow
apps/deploy-web/src/pages/api/auth/[...auth0].ts
Adds callback/afterCallback that POSTs to /v1/register-user (propagating auth headers), switches profile fetch to GET /v1/user/me, merges returned user into session, and refines error handling and typings.
Web DI & AuthService
apps/deploy-web/src/services/app-di-container/app-di-container.ts, apps/deploy-web/src/services/auth/auth.service.ts, apps/deploy-web/src/components/layout/AccountMenu.tsx
AuthService now constructed with a URL service provider and gains logout() which clears local storage and redirects; DI updated; AccountMenu uses authService.logout().
Onboarding refactor (web)
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx, .../OnboardingContainer.spec.tsx, apps/deploy-web/src/services/storage/keys.ts
Replaces direct UrlService dependency with useServices().urlService; uses ONBOARDING_STEP_KEY constant for storage; updates tests/mocks and DI shape.

Sequence Diagram(s)

sequenceDiagram
  actor Client
  participant API as API Router
  participant UC as UserController
  participant US as UserService
  participant UR as UserRepository
  participant AW as UserWalletRepository
  participant AN as Analytics/Logger

  Client->>API: POST /v1/register-user (wantedUsername, email, ...)
  API->>UC: registerUser(data)
  UC->>US: registerUser(data)
  US->>UR: upsert(user details)
  alt anonymousUserId provided
    US->>UR: load anonymous user
    US->>AW: tryToTransferWallet(anon -> new)
  end
  US->>AN: track/log registration
  US-->>UC: user subset
  UC-->>API: { data: user }
  API-->>Client: 200 JSON
Loading
sequenceDiagram
  actor Client
  participant API as API Router
  participant UC as UserController

  Client->>API: GET /v1/user/me
  API->>UC: getCurrentUser()
  UC-->>API: { data: user }
  API-->>Client: 200 JSON
Loading
sequenceDiagram
  participant Browser
  participant Next as Next.js Auth API
  participant Auth0 as Auth0
  participant MainAPI as Mainnet API

  Browser->>Next: /api/auth/callback
  Next->>Auth0: handleCallback (afterCallback)
  Note right of Next: afterCallback calls /v1/register-user with Authorization (and optional x-anonymous-authorization)
  Next->>MainAPI: POST /v1/register-user
  MainAPI-->>Next: { data: user }
  Next->>Auth0: finalize session (merge user)
  Auth0-->>Browser: redirect with session
  Browser->>Next: /api/auth/me (profile)
  Next->>MainAPI: GET /v1/user/me
  MainAPI-->>Next: { data: user }
  Next-->>Browser: session with merged user
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • baktun14

Poem

I thump my paw and code a tune,
New routes hop out beneath the moon.
Register here, fetch me there,
Anonymous hops handled with care.
I twitch my nose—logout set—carrots saved, away we fare. 🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/user-register

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Status, Documentation and Community

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

@stalniy stalniy force-pushed the refactor/user-register branch 2 times, most recently from b355236 to 0db2ba4 Compare August 11, 2025 13:09
@codecov
Copy link

codecov bot commented Aug 11, 2025

Codecov Report

❌ Patch coverage is 72.07792% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.27%. Comparing base (ace47e6) to head (9a4b605).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
apps/deploy-web/src/pages/api/auth/[...auth0].ts 0.00% 29 Missing and 2 partials ⚠️
apps/api/src/user/services/user/user.service.ts 90.90% 4 Missing ⚠️
apps/deploy-web/src/services/auth/auth.service.ts 62.50% 3 Missing ⚠️
...s/api/src/user/controllers/user/user.controller.ts 87.50% 2 Missing ⚠️
...s/deploy-web/src/components/layout/AccountMenu.tsx 33.33% 2 Missing ⚠️
...routes/get-current-user/get-current-user.router.ts 87.50% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (30.76%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1796      +/-   ##
==========================================
- Coverage   42.47%   42.27%   -0.21%     
==========================================
  Files         934      933       -1     
  Lines       26288    26242      -46     
  Branches     6899     6909      +10     
==========================================
- Hits        11167    11094      -73     
- Misses      13961    13987      +26     
- Partials     1160     1161       +1     
Flag Coverage Δ *Carryforward flag
api 81.09% <93.13%> (-0.13%) ⬇️
deploy-web 20.77% <30.76%> (+<0.01%) ⬆️
log-collector ?
notifications 87.59% <ø> (ø) Carriedforward from ace47e6
provider-console 81.48% <ø> (ø) Carriedforward from ace47e6
provider-proxy 84.75% <ø> (ø) Carriedforward from ace47e6

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
apps/api/src/app.ts 87.27% <100.00%> (ø)
apps/api/src/auth/services/auth.interceptor.ts 97.40% <100.00%> (+0.10%) ⬆️
apps/api/src/core/repositories/base.repository.ts 80.85% <100.00%> (+0.85%) ⬆️
apps/api/src/routers/userRouter.ts 38.00% <ø> (ø)
.../api/src/user/repositories/user/user.repository.ts 97.95% <100.00%> (+0.52%) ⬆️
.../user/routes/register-user/register-user.router.ts 100.00% <100.00%> (ø)
apps/api/src/user/schemas/user.schema.ts 100.00% <100.00%> (ø)
...arding/OnboardingContainer/OnboardingContainer.tsx 93.65% <100.00%> (ø)
.../src/services/app-di-container/app-di-container.ts 85.10% <100.00%> (ø)
apps/deploy-web/src/services/storage/keys.ts 100.00% <100.00%> (ø)
... and 6 more

... and 9 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🔭 Outside diff range comments (4)
apps/api/src/routers/userRouter.ts (1)

68-68: Remove the any cast from container.resolve to comply with TS guidelines.

Avoid any. Let tsyringe infer the type or provide a proper generic.

-    const anonymousUserId = await container.resolve<any>(AuthTokenService).getValidUserId(anonymousBearer);
+    const authTokenService = container.resolve(AuthTokenService);
+    const anonymousUserId = await authTokenService.getValidUserId(anonymousBearer);
apps/api/src/auth/services/auth.interceptor.spec.ts (1)

95-101: Inline the setup input type per test guidelines.

The coding guidelines require an inline type for setup’s single parameter and no shared interface. Inline the type and drop the interface.

-  function setup(input?: SetupInput) {
+  function setup(input?: { user?: UserOutput; apiKey?: string }) {
...
-  interface SetupInput {
-    user?: UserOutput;
-    apiKey?: string;
-  }

Also applies to: 172-175

apps/api/test/services/wallet-testing.service.ts (1)

125-141: Handle non-2xx response and surface errors in test helper

Better fail fast in tests with a clear message if registration fails. This avoids hard-to-trace JSON parsing or shape errors.

Apply this diff to add error handling:

-    const userResponse = await this.app.request(`/v1/registerUser`, {
+    const userResponse = await this.app.request(`/v1/registerUser`, {
       method: "POST",
       headers: new Headers({
         "Content-Type": "application/json",
         Authorization: `Bearer ${access_token}`
       }),
       body: JSON.stringify({
         wantedUsername: decoded.nickname,
         email: decoded.email,
         emailVerified: decoded.email_verified,
         subscribedToNewsletter: false
       })
     });
-    const body = await userResponse.json();
-
-    return { user: body.data, token: access_token };
+    if (!userResponse.ok) {
+      const text = await userResponse.text();
+      throw new Error(`registerUser failed: ${userResponse.status} ${userResponse.statusText} - ${text}`);
+    }
+    const body = await userResponse.json();
+    return { user: body.data, token: access_token };
apps/api/src/user/controllers/user/user.controller.ts (1)

47-54: Unify return type of getById

The union Promise<{ data: UserSchema } | GetUserResponseOutput> needlessly complicates the contract and docs. The method returns { data: user } anyway.

-  async getById({ id }: GetUserParams): Promise<{ data: UserSchema } | GetUserResponseOutput> {
+  async getById({ id }: GetUserParams): Promise<{ data: UserSchema }> {

Ensure route/OpenAPI for this endpoint also reflects the full UserSchema if that's what you return.

🧹 Nitpick comments (15)
apps/deploy-web/src/components/layout/AccountMenu.tsx (1)

17-17: Optional: use DI-provided urlService for all links for consistency

You’ve introduced useServices; consider replacing direct UrlService.* calls with urlService.* across this component to standardize URL construction via DI.

apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1)

179-185: Optional: prefer typed mocks for urlService

Consider using jest-mock-extended to strictly type the mock urlService (onboarding, signup) and avoid casting through unknown.

Example:

import { mockDeep } from "jest-mock-extended";
const mockUrlService = mockDeep<typeof UrlService>();
mockUrlService.onboarding.mockReturnValue("/onboarding");
mockUrlService.signup.mockReturnValue("/signup");
apps/api/src/routers/userRouter.ts (1)

42-44: Deprecation notice is good; consider adding runtime deprecation headers on the endpoint.

To assist client migration, add Deprecation/Sunset/Link headers in the handler to provide machine-readable hints.

Example diff within the same handler:

 userRequiredRouter.post("/tokenInfo", async c => {
+  // Mark endpoint as deprecated for clients
+  c.header("Deprecation", "true");
+  // Optional: RFC 8594 Sunset header with a date
+  // c.header("Sunset", new Date("2025-12-31").toUTCString());
+  // Optional: provide successor link
+  c.header("Link", '</v1/registerUser>; rel="successor-version"');
   const userId = getCurrentUserId(c);
apps/api/src/user/repositories/user/user.repository.ts (1)

128-134: Tighten override signature and visibility for toInput.

Make it an override of BaseRepository.toInput with the correct visibility and return type to ensure type safety across inherited calls.

-  toInput(payload: Partial<UserInput>): Partial<UserInput> {
+  protected override toInput(payload: Partial<UserInput>): Partial<typeof this.table.$inferInsert> {
     if (!payload.lastUserAgent) return payload;
     return {
       ...payload,
       lastUserAgent: payload.lastUserAgent.slice(0, userAgentMaxLength)
     };
   }
apps/api/src/core/repositories/base.repository.ts (1)

226-228: Guard looks good; optionally broaden to handle drivers that don’t throw PostgresError.

If the runtime uses pg (DatabaseError) or rethrows plain objects, the current check will miss 23505. Consider a fallback shape check without using any.

-export function isUniqueViolation(error: unknown): error is PostgresError {
-  return error instanceof PostgresError && error.code === "23505";
-}
+export function isUniqueViolation(error: unknown): error is PostgresError {
+  if (error instanceof PostgresError) return error.code === "23505";
+  // Fallback: accept objects that expose a 23505 code
+  return typeof error === "object" && error !== null && "code" in error && (error as { code?: unknown }).code === "23505";
+}
apps/api/src/user/routes/get-current-user/get-current-user.router.ts (1)

8-25: Document 401 for unauthenticated access

getCurrentUser is protected; include 401 in OpenAPI to reflect reality.

   responses: {
       description: "Returns the logged in user",
       body: {
         content: {
           "application/json": {
             schema: z.object({ data: UserSchema })
           }
         }
       }
     },
+    401: {
+      description: "Unauthorized"
+    }
   }
apps/api/src/user/schemas/user.schema.ts (1)

24-38: Value/type name collision on UserSchema may confuse consumers

You export both a value (Zod schema) and a type with the same name UserSchema. TS allows it, but it’s easy to import the wrong symbol and accidentally pull in the value instead of the type (or vice versa).

Consider:

  • Keep Zod as UserSchema (value) and export the type as User (preferred), or
  • Rename Zod to UserSchemaZ and keep type as UserSchema.

Additionally, consider adding .openapi() hints on fields if you want richer docs (e.g., email format).

apps/api/src/user/routes/register-user/register-user.router.ts (1)

22-48: API naming consistency

Most endpoints follow kebab-case or resource-oriented paths (/v1/user/me, /v1/anonymous-users). /v1/registerUser deviates.

Consider adding an alias (keeping current path for backward compatibility):

  • POST /v1/users/register
  • or POST /v1/users

Also, document error responses (e.g., 400 validation error, 401 unauthorized, 409 username conflict).

Example:

   responses: {
       description: "Returns the registered user",
       body: {
         content: {
           "application/json": {
             schema: registerUserResponseSchema
           }
         }
       }
-    }
+    },
+    400: { description: "Invalid input" },
+    401: { description: "Unauthorized" },
+    409: { description: "Username conflict" }
   }
apps/api/src/user/services/user/user.service.spec.ts (2)

155-159: Avoid dynamic RegExp in tests; prefer startsWith for clarity

Minor nit: new RegExp(^${conflictingUsername}) is overkill and flagged by static analysis. startsWith is simpler and avoids regex footguns.

-      expect(newUsers[0].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[0].username.startsWith(conflictingUsername)).toBe(true);
@@
-      expect(newUsers[1].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[1].username.startsWith(conflictingUsername)).toBe(true);

265-271: Setup helper: allow overrides via a single param to match test guidelines

Tests follow the setup pattern and use jest-mock-extended correctly. To align fully with the stated guidelines, accept a single param with inline type for overrides.

-  function setup() {
-    const analyticsService = mock<AnalyticsService>();
-    const logger = mock<LoggerService>();
-    const service = new UserService(container.resolve(UserRepository), analyticsService, container.resolve(UserWalletRepository), logger);
-
-    return { service, analyticsService, logger };
-  }
+  function setup(params: { analyticsService?: AnalyticsService; logger?: LoggerService } = {}) {
+    const analyticsService = params.analyticsService ?? mock<AnalyticsService>();
+    const logger = params.logger ?? mock<LoggerService>();
+    const service = new UserService(container.resolve(UserRepository), analyticsService, container.resolve(UserWalletRepository), logger);
+    return { service, analyticsService, logger };
+  }
apps/api/src/user/services/user/user.service.ts (2)

65-68: Align analytics event names and payloads.

Logs use ANONYMOUS_USER_REGISTERED/USER_REGISTERED, analytics tracks "user_registered". Consider unifying the event name across both and include useful properties (e.g., isAnonymous) for consistent observability.

-    const event = isAnonymous ? "ANONYMOUS_USER_REGISTERED" : "USER_REGISTERED";
-    this.logger.info({ event, id: user.id, userId: user.userId });
-    this.analyticsService.track(user.id, "user_registered");
+    const event = isAnonymous ? "ANONYMOUS_USER_REGISTERED" : "USER_REGISTERED";
+    this.logger.info({ event, id: user.id, userId: user.userId });
+    this.analyticsService.track(event, { userId: user.userId, isAnonymous });

Confirm AnalyticsService.track signature; adjust accordingly if it expects (userId, eventName, props).


72-85: Remove self-referential return type assertion for clarity.

Using as Awaited<ReturnType<this["registerUser"]>> inside registerUser is a code smell and harms readability. Prefer an explicit DTO type.

Define a reusable type and use it as the return type, then remove the assertion:

// near the top of the file
export type RegisterUserResult = {
  id: string;
  userId: string;
  username: string;
  email: string;
  emailVerified: boolean;
  stripeCustomerId: string | null;
  bio: string | null;
  subscribedToNewsletter: boolean;
  youtubeUsername: string | null;
  twitterUsername: string | null;
  githubUsername: string | null;
};

// signature
async registerUser(data: RegisterUserInput): Promise<RegisterUserResult> {
  // ...
  return { id, userId, username, email, emailVerified, stripeCustomerId, bio, subscribedToNewsletter, youtubeUsername, twitterUsername, githubUsername };
}
apps/deploy-web/src/pages/api/auth/[...auth0].ts (3)

37-78: Harden registration flow in afterCallback.

  • subscribedToNewsletter should accept boolean or string values from user_metadata.
  • Minor: consider using the existing request interceptor for anonymous header to avoid duplicating header logic here.
-                  subscribedToNewsletter: user_metadata?.subscribedToNewsletter === "true"
+                  subscribedToNewsletter:
+                    user_metadata?.subscribedToNewsletter === true ||
+                    user_metadata?.subscribedToNewsletter === "true"

Confirm whether user_metadata.subscribedToNewsletter is consistently a string in your Auth0 tenant; if so, we can keep the original comparison.


53-64: Consider using relative URL with consoleApiHttpClient’s baseURL.

Since consoleApiHttpClient can be configured with baseURL, you can simplify and avoid coupling to apiUrlService here. Also, to avoid leaking headers object shape, pass headers as a plain object literal.

-              const userSettings = await services.consoleApiHttpClient.post<{ data: UserSettings }>(
-                `${services.apiUrlService.getBaseApiUrlFor("mainnet")}/v1/registerUser`,
+              const userSettings = await services.consoleApiHttpClient.post<{ data: UserSettings }>(
+                "/v1/registerUser",
                 {
                   wantedUsername: session.user.nickname,
                   email: session.user.email,
                   emailVerified: session.user.email_verified,
                   subscribedToNewsletter: user_metadata?.subscribedToNewsletter === "true"
                 },
                 {
-                  headers: headers.toJSON()
+                  headers: { ...headers.toJSON() }
                 }
               );

If consoleApiHttpClient is intentionally created without baseURL on the server, ignore this suggestion.


110-110: Session merge is fine; consider normalizing property names.

Merging server-returned keys directly into session.user is fine as long as UserSettings keys don’t collide with Auth0’s reserved keys. If there’s any risk, namespace them under session.user.profile.

Validate that id/username/etc. don’t shadow claims required by @auth0/nextjs-auth0.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between d15c527 and 0db2ba4.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (20)
  • apps/api/src/app.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.spec.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.ts (3 hunks)
  • apps/api/src/core/repositories/base.repository.ts (2 hunks)
  • apps/api/src/routers/userRouter.ts (1 hunks)
  • apps/api/src/user/controllers/user/user.controller.ts (4 hunks)
  • apps/api/src/user/repositories/user/user.repository.ts (4 hunks)
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts (1 hunks)
  • apps/api/src/user/routes/index.ts (1 hunks)
  • apps/api/src/user/routes/register-user/register-user.router.ts (1 hunks)
  • apps/api/src/user/schemas/user.schema.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.spec.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.ts (1 hunks)
  • apps/api/test/services/wallet-testing.service.ts (2 hunks)
  • apps/deploy-web/src/components/layout/AccountMenu.tsx (3 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (7 hunks)
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts (4 hunks)
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts (1 hunks)
  • apps/deploy-web/src/services/auth/auth.service.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/routers/userRouter.ts
  • apps/api/test/services/wallet-testing.service.ts
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts
  • apps/api/src/core/repositories/base.repository.ts
  • apps/deploy-web/src/components/layout/AccountMenu.tsx
  • apps/deploy-web/src/services/auth/auth.service.ts
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts
  • apps/api/src/app.ts
  • apps/api/src/user/repositories/user/user.repository.ts
  • apps/api/src/user/routes/register-user/register-user.router.ts
  • apps/api/src/user/schemas/user.schema.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
  • apps/api/src/user/services/user/user.service.ts
  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/api/src/user/routes/index.ts
  • apps/api/src/user/controllers/user/user.controller.ts
  • apps/api/src/auth/services/auth.interceptor.spec.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
  • apps/api/src/auth/services/auth.interceptor.ts
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/routers/userRouter.ts
  • apps/api/test/services/wallet-testing.service.ts
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts
  • apps/api/src/core/repositories/base.repository.ts
  • apps/deploy-web/src/components/layout/AccountMenu.tsx
  • apps/deploy-web/src/services/auth/auth.service.ts
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts
  • apps/api/src/app.ts
  • apps/api/src/user/repositories/user/user.repository.ts
  • apps/api/src/user/routes/register-user/register-user.router.ts
  • apps/api/src/user/schemas/user.schema.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
  • apps/api/src/user/services/user/user.service.ts
  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/api/src/user/routes/index.ts
  • apps/api/src/user/controllers/user/user.controller.ts
  • apps/api/src/auth/services/auth.interceptor.spec.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
  • apps/api/src/auth/services/auth.interceptor.ts
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/api/src/auth/services/auth.interceptor.spec.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
apps/{deploy-web,provider-console}/**/*.spec.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/query-by-in-tests.mdc)

Use queryBy methods instead of getBy methods in test expectations in .spec.tsx files

Files:

  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-21T08:24:24.269Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-07-21T08:24:24.269Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't use `jest.mock()` to mock dependencies in test files. Instead, use `jest-mock-extended` to create mocks and pass mocks as dependencies to the service under test.

Applied to files:

  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
🧬 Code Graph Analysis (12)
apps/api/src/user/routes/get-current-user/get-current-user.router.ts (3)
apps/api/src/user/schemas/user.schema.ts (1)
  • GetUserResponseOutputSchema (14-20)
apps/api/src/core/services/open-api-hono-handler/open-api-hono-handler.ts (1)
  • OpenApiHonoHandler (11-26)
apps/api/src/user/controllers/user/user.controller.ts (1)
  • getCurrentUser (81-85)
apps/deploy-web/src/components/layout/AccountMenu.tsx (2)
apps/deploy-web/src/context/ServicesProvider/ServicesProvider.tsx (1)
  • useServices (27-29)
apps/deploy-web/src/components/shared/CustomDropdownLinkItem.tsx (1)
  • CustomDropdownLinkItem (12-21)
apps/deploy-web/src/services/auth/auth.service.ts (1)
apps/deploy-web/src/config/auth.config.ts (1)
  • ANONYMOUS_USER_TOKEN_KEY (2-2)
apps/deploy-web/src/services/app-di-container/app-di-container.ts (1)
apps/deploy-web/src/services/auth/auth.service.ts (1)
  • AuthService (7-30)
apps/api/src/app.ts (2)
apps/api/src/user/routes/register-user/register-user.router.ts (1)
  • registerUserRouter (49-52)
apps/api/src/user/routes/get-current-user/get-current-user.router.ts (1)
  • getCurrentUserRouter (26-28)
apps/api/src/user/repositories/user/user.repository.ts (1)
apps/api/src/user/model-schemas/user/user.schema.ts (1)
  • userAgentMaxLength (6-6)
apps/api/src/user/routes/register-user/register-user.router.ts (5)
apps/api/src/user/services/user/user.service.ts (2)
  • RegisterUserInput (139-149)
  • registerUser (19-85)
apps/api/src/user/schemas/user.schema.ts (2)
  • UserSchema (24-36)
  • UserSchema (38-38)
apps/api/src/dashboard/routes/leases-duration/leases-duration.router.ts (1)
  • route (15-37)
apps/api/src/core/services/open-api-hono-handler/open-api-hono-handler.ts (1)
  • OpenApiHonoHandler (11-26)
apps/api/src/user/controllers/user/user.controller.ts (1)
  • registerUser (60-78)
apps/api/src/user/services/user/user.service.ts (4)
apps/api/src/user/repositories/user/user.repository.ts (1)
  • UserOutput (11-13)
apps/deploy-web/src/services/analytics/analytics.service.ts (1)
  • AnalyticsService (141-266)
apps/api/src/user/routes/register-user/register-user.router.ts (1)
  • RegisterUserInput (15-15)
apps/api/src/core/repositories/base.repository.ts (1)
  • isUniqueViolation (226-228)
apps/api/src/user/services/user/user.service.spec.ts (2)
apps/api/src/user/routes/register-user/register-user.router.ts (1)
  • RegisterUserInput (15-15)
apps/api/src/user/services/user/user.service.ts (1)
  • RegisterUserInput (139-149)
apps/api/src/user/controllers/user/user.controller.ts (6)
apps/api/src/user/routes/get-anonymous-user/get-anonymous-user.router.ts (1)
  • GetUserParams (11-11)
apps/api/src/user/schemas/user.schema.ts (3)
  • UserSchema (24-36)
  • UserSchema (38-38)
  • GetUserResponseOutput (22-22)
apps/api/src/user/routes/register-user/register-user.router.ts (2)
  • RegisterUserInput (15-15)
  • RegisterUserResponse (20-20)
apps/api/src/user/services/user/user.service.ts (1)
  • RegisterUserInput (139-149)
apps/notifications/src/interfaces/rest/services/auth/auth.service.ts (1)
  • userId (18-25)
apps/api/src/auth/services/auth.service.ts (1)
  • Protected (39-55)
apps/api/src/auth/services/auth.interceptor.ts (1)
apps/api/src/user/controllers/user/user.controller.ts (1)
  • httpContext (31-33)
apps/deploy-web/src/pages/api/auth/[...auth0].ts (3)
apps/deploy-web/src/services/app-di-container/browser-di-container.ts (1)
  • services (22-54)
apps/deploy-web/src/services/app-di-container/server-di-container.service.ts (1)
  • services (22-32)
apps/deploy-web/src/types/user.ts (1)
  • UserSettings (5-16)
🪛 GitHub Actions: Deploy Web CI
apps/deploy-web/src/services/auth/auth.service.ts

[error] 456-456: Cannot find module '../storage/keys' from 'src/services/auth/auth.service.ts'. This error occurred during running 'npm run test:cov'. Ensure the module exists at src/services/storage/keys.ts and that the relative import path is correct.

apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx

[error] 9-9: Type error: Cannot find module '@src/services/storage/keys' or its corresponding type declarations. (import at OnboardingContainer.tsx:9:37). Build step 'npm run build -w apps/deploy-web' failed due to this error.

🪛 GitHub Check: validate / validate-app
apps/api/src/user/routes/register-user/register-user.router.ts

[failure] 6-6:
Dependency cycle detected

apps/api/src/user/controllers/user/user.controller.ts

[failure] 11-11:
Dependency cycle detected

🪛 ast-grep (0.38.6)
apps/api/src/user/services/user/user.service.spec.ts

[warning] 155-155: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 157-157: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🪛 GitHub Actions: API CI
apps/api/src/user/controllers/user/user.controller.ts

[error] 11-11: ESLint: Dependency cycle detected (import-x/no-cycle).

🔇 Additional comments (16)
apps/deploy-web/src/components/layout/AccountMenu.tsx (1)

31-31: LGTM: logout flow now goes through AuthService

Using authService from DI and delegating logout is consistent with the new architecture.

Also applies to: 103-103

apps/deploy-web/src/services/app-di-container/app-di-container.ts (1)

47-47: LGTM: inject urlService into AuthService

Constructor wiring matches the new AuthService signature and keeps URL concerns inside DI.

apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (1)

47-47: LGTM: urlService + ONBOARDING_STEP_KEY integration

  • Reads/writes remove hard-coded key in favor of ONBOARDING_STEP_KEY.
  • Signup/onboarding URLs now flow via urlService from DI.

Also applies to: 128-131, 50-50, 67-67, 97-97, 117-117

apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1)

188-191: LGTM: tests updated to mock useServices.urlService

Mocks align with the new DI shape; no jest.mock() used, setup stays at the bottom per guidelines.

Also applies to: 199-200

apps/api/src/user/routes/index.ts (1)

3-4: LGTM — re-exports align with new endpoints.

No issues.

apps/api/src/auth/services/auth.interceptor.spec.ts (1)

8-8: LGTM — ExecutionContextService import for test context is appropriate.

apps/api/src/app.ts (1)

76-76: Routers wired correctly; OpenAPI inclusion looks good

Importing and registering createAnonymousUserRouter/getAnonymousUserRouter/registerUserRouter/getCurrentUserRouter is consistent with the routing pattern and middleware order. No issues found.

Also applies to: 126-127

apps/api/src/user/routes/get-current-user/get-current-user.router.ts (1)

26-28: Handler is fine

Resolves controller via DI and returns JSON 200. Good.

apps/api/src/user/routes/register-user/register-user.router.ts (1)

49-52: Handler looks good

Validates body via zod-openapi and delegates to controller. Good.

apps/api/src/auth/services/auth.interceptor.ts (2)

29-38: ExecutionContextService injection: LGTM

Order in app.ts initializes RequestContextInterceptor before AuthInterceptor, so HTTP_CONTEXT should be available. No issues.


117-122: Mark activity with context: LGTM

Throttle logic preserved; passing ip and fingerprint is safe with optional chaining. Ensure UserRepository.markAsActive signature accepts these optional fields.

Would you like me to scan repository for markAsActive signature to ensure it matches { throttleTimeSeconds: number; ip?: string; fingerprint?: string }?

apps/api/src/user/controllers/user/user.controller.ts (1)

80-85: getCurrentUser: LGTM

Authorization asserted; returns current user wrapped in { data }. Align route schema accordingly (see router comment).

apps/api/src/user/services/user/user.service.ts (2)

61-63: Double-check wallet transfer condition.

Transfer only occurs when isAnonymous is false. Given the flow, when the anonymous record was successfully upgraded (isAnonymous === true), upsert should target the same row and no transfer is needed. Please verify this holds across all DB states to avoid missing a required transfer.

If there are cases where upsert creates a new row despite a prior anonymous update, we should drop the isAnonymous guard and rely solely on id inequality.


139-149: Rename service input type and align client-context fields

There’s a name collision between the router’s RegisterUserInput (in apps/api/src/user/routes/register-user/register-user.router.ts) and the service’s RegisterUserInput (in apps/api/src/user/services/user/user.service.ts), and the service currently requires ip, userAgent, and fingerprint as non-optional strings even though ctx may not always supply them. To address both issues:

• In apps/api/src/user/services/user/user.service.ts (around lines 139–149), rename the interface and make the context fields optional if your TRPC context can be undefined:

- export interface RegisterUserInput {
+ // avoid colliding with router’s type
+ export interface RegisterUserServiceInput {
    anonymousUserId?: string;
    userId: string;
    wantedUsername: string;
    email: string;
    emailVerified: boolean;
    subscribedToNewsletter: boolean;
-   ip: string;
-   userAgent: string;
-   fingerprint: string;
+   ip?: string;
+   userAgent?: string;
+   fingerprint?: string;
  }

• Update the registerUser method signature to accept RegisterUserServiceInput and adjust all its callers.
• Verify your TRPC context type—if ctx.ip, ctx.userAgent, or ctx.fingerprint are guaranteed to be defined, you can keep them required; otherwise leave them optional or enforce defaults.

apps/deploy-web/src/pages/api/auth/[...auth0].ts (2)

47-51: Propagating anonymous token header: ensure we’re forwarding only the anonymous token.

req.headers.authorization may sometimes hold non-anonymous credentials. If there’s any chance of mixing tokens, add a simple guard to ensure the header contains an anonymous token (e.g., a distinct prefix) before forwarding.

If the anonymous token is indistinguishable, consider passing it via a dedicated cookie or header from the browser flow to avoid confusion.


103-104: Good switch to GET /v1/user/me.

Fetching the current user profile via a dedicated endpoint is clearer and avoids overloading token endpoints. LGTM.

Comment on lines +13 to +23
responses: {
200: {
description: "Returns the logged in user",
body: {
content: {
"application/json": {
schema: GetUserResponseOutputSchema
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Response schema mismatch: route documents only {data: {id}} but controller returns full user

Controller returns { data: UserSchema }. The route currently points to GetUserResponseOutputSchema with just id, which will produce incorrect OpenAPI.

Use UserSchema in the response:

-import { GetUserResponseOutputSchema } from "@src/user/schemas/user.schema";
+import { z } from "zod";
+import { UserSchema } from "@src/user/schemas/user.schema";
@@
   responses: {
       description: "Returns the logged in user",
-      body: {
+      body: {
         content: {
           "application/json": {
-            schema: GetUserResponseOutputSchema
+            schema: z.object({ data: UserSchema })
           }
         }
       }
     }
   }
📝 Committable suggestion

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

Suggested change
responses: {
200: {
description: "Returns the logged in user",
body: {
content: {
"application/json": {
schema: GetUserResponseOutputSchema
}
}
}
}
// Replace this import:
-import { GetUserResponseOutputSchema } from "@src/user/schemas/user.schema";
// With these:
+import { z } from "zod";
+import { UserSchema } from "@src/user/schemas/user.schema";
// … elsewhere in this file …
responses: {
200: {
description: "Returns the logged in user",
body: {
content: {
"application/json": {
// Use the full UserSchema instead of only {id}
- schema: GetUserResponseOutputSchema
+ schema: z.object({ data: UserSchema })
}
}
}
}
}
🤖 Prompt for AI Agents
In apps/api/src/user/routes/get-current-user/get-current-user.router.ts around
lines 13 to 23, the OpenAPI response uses GetUserResponseOutputSchema which only
documents { data: { id } } but the controller returns the full user object;
update the response schema to use the full UserSchema (or the exported schema
that represents the complete user response) so the route documentation matches
the controller output, and ensure the proper import/alias for UserSchema is
present and referenced in the responses section.

@stalniy stalniy force-pushed the refactor/user-register branch from 0db2ba4 to 632a08c Compare August 12, 2025 16:19
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (3)
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (2)

134-138: Avoid any in test types; use minimal concrete types.

This violates the guideline to never use any in TS. Define the shapes actually used by the component.

Apply:

-  input: {
-      paymentMethods?: any[];
-      user?: any;
-      windowLocation?: Partial<Location>;
-      windowHistory?: Partial<History>;
-    } = {}
+  input: {
+      paymentMethods?: Array<{ id: string; type: string }>;
+      user?: { emailVerified?: boolean; stripeCustomerId?: string };
+      windowLocation?: Partial<Location>;
+      windowHistory?: Partial<History>;
+    } = {}

179-185: Drop double assertion and spreading real service into a mock.

as unknown as bypasses type safety, and spreading UrlService risks pulling in real behavior. Provide a minimal, typed mock instead.

Use a precise type:

-    const mockUrlService = {
-      ...UrlService,
-      onboarding: jest.fn(),
-      signup: jest.fn()
-    } as unknown as typeof UrlService;
+    const mockUrlService: Pick<typeof UrlService, "onboarding" | "signup"> = {
+      onboarding: jest.fn(),
+      signup: jest.fn()
+    };
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (1)

50-56: Validate saved step using numeric members; avoid enum “/2” heuristic.

Object.keys(enum).length/2 relies on TS enum emit details and is brittle. Prefer counting numeric values explicitly.

-    if (savedStep) {
-      const step = parseInt(savedStep, 10);
-      if (step >= 0 && step < Object.keys(OnboardingStepIndex).length / 2) {
-        setCurrentStep(step);
-      }
-    }
+    if (savedStep) {
+      const step = parseInt(savedStep, 10);
+      const stepCount = Object.values(OnboardingStepIndex).filter(v => typeof v === "number").length;
+      if (!Number.isNaN(step) && step >= 0 && step < stepCount) {
+        setCurrentStep(step);
+      }
+    }
♻️ Duplicate comments (1)
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (1)

9-9: Import path fix resolves prior pipeline failure.

The ONBOARDING_STEP_KEY import now points to an existing module, addressing the earlier “Cannot find module '@src/services/storage/keys'” build error.

🧹 Nitpick comments (7)
apps/api/src/user/services/user/user.service.spec.ts (5)

16-16: Fix test name: it contradicts the input (userId is provided).

The test title says “no userId is provided” but the payload includes a userId (and the type requires it). Update the description for clarity.

Apply this diff:

-it("registers a new user if no anonymousUserId and no userId is provided", async () => {
+it("registers a new user when anonymousUserId is not provided", async () => {

64-70: Strengthen assertion to prove we updated the same anonymous user record

You already reload by id and compare objects, but explicitly asserting the id didn’t change eliminates ambiguity and guards against accidental “create new” flows.

Apply this diff:

-      const user = await service.registerUser(input);
-      const reloadedUser = await container.resolve(UserRepository).findById(anonymousUser.id);
+      const user = await service.registerUser(input);
+      expect(user.id).toBe(anonymousUser.id);
+      const reloadedUser = await container.resolve(UserRepository).findById(anonymousUser.id);

154-158: Avoid dynamic RegExp; use startsWith for clearer intent and to quiet static analysis

Even though the variable is a constant in this test, replacing the regex with startsWith improves readability and avoids ReDoS lint warnings.

Apply this diff:

-      expect(newUsers[0].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[0].username.startsWith(conflictingUsername)).toBe(true);
-      expect(newUsers[1].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[1].username.startsWith(conflictingUsername)).toBe(true);

188-190: Assert wallet record identity did not change during transfer

Add an assertion that the same wallet row was moved (id unchanged). This better validates a transfer vs. re-creation.

Apply this diff:

       const updatedWallet = await container.resolve(UserWalletRepository).findById(wallet.id);
+      expect(updatedWallet?.id).toBe(wallet.id);
       expect(updatedWallet?.userId).toBe(newUser.id);

265-271: Align setup() with our test guideline to accept a single, inline-typed parameter

Our spec guideline prefers a setup function that accepts a single parameter with inline type definition (no return type annotation). This enables per-test overrides without shared state.

Apply this diff:

-  function setup() {
-    const analyticsService = mock<AnalyticsService>();
-    const logger = mock<LoggerService>();
-    const service = new UserService(container.resolve(UserRepository), analyticsService, container.resolve(UserWalletRepository), logger);
-
-    return { service, analyticsService, logger };
-  }
+  function setup(
+    { analyticsService = mock<AnalyticsService>(), logger = mock<LoggerService>() }: {
+      analyticsService?: AnalyticsService;
+      logger?: LoggerService;
+    } = {}
+  ) {
+    const service = new UserService(
+      container.resolve(UserRepository),
+      analyticsService,
+      container.resolve(UserWalletRepository),
+      logger
+    );
+    return { service, analyticsService, logger };
+  }
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1)

171-177: Prefer typed mocks via jest-mock-extended for stronger guarantees.

Guideline: use jest-mock-extended in tests. It improves type inference for mocks like analyticsService/urlService and prevents accidental API drift.

Outside the selected lines, add:

import { mock } from "jest-mock-extended";

Then replace ad-hoc mocks:

const mockAnalyticsService = mock<{ track: (event: string, props?: Record<string, unknown>) => void }>();
const mockUrlService = mock<Pick<typeof UrlService, "onboarding" | "signup">>();
mockUrlService.onboarding.mockReturnValue("/onboarding");
mockUrlService.signup.mockReturnValue("/signup");

And keep:

- const mockUseServices = jest.fn().mockReturnValue({
+ const mockUseServices = jest.fn().mockReturnValue({
    analyticsService: mockAnalyticsService,
    urlService: mockUrlService
  });

Also applies to: 188-191

apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (1)

89-95: DRY the step name mapping used in analytics.

stepNames is duplicated in two places, risking drift. Hoist to a constant.

Apply locally:

-      const stepNames = ["free_trial", "signup", "email_verification", "payment_method", "welcome"];
       analyticsService.track("onboarding_step_started", {
         category: "onboarding",
-        step: stepNames[step],
+        step: STEP_NAMES[step],
         step_index: step
       });
-      const stepNames = ["free_trial", "signup", "email_verification", "payment_method", "welcome"];
       analyticsService.track("onboarding_step_completed", {
         category: "onboarding",
-        step: stepNames[step],
+        step: STEP_NAMES[step],
         step_index: step
       });

Outside the selected ranges, add near the enum:

const STEP_NAMES = ["free_trial", "signup", "email_verification", "payment_method", "welcome"] as const;

Also applies to: 104-109

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0db2ba4 and 632a08c.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (21)
  • apps/api/src/app.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.spec.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.ts (3 hunks)
  • apps/api/src/core/repositories/base.repository.ts (2 hunks)
  • apps/api/src/routers/userRouter.ts (1 hunks)
  • apps/api/src/user/controllers/user/user.controller.ts (4 hunks)
  • apps/api/src/user/repositories/user/user.repository.ts (4 hunks)
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts (1 hunks)
  • apps/api/src/user/routes/index.ts (1 hunks)
  • apps/api/src/user/routes/register-user/register-user.router.ts (1 hunks)
  • apps/api/src/user/schemas/user.schema.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.spec.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.ts (1 hunks)
  • apps/api/test/services/wallet-testing.service.ts (2 hunks)
  • apps/deploy-web/src/components/layout/AccountMenu.tsx (3 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (7 hunks)
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts (4 hunks)
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts (1 hunks)
  • apps/deploy-web/src/services/auth/auth.service.ts (2 hunks)
  • apps/deploy-web/src/services/storage/keys.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (17)
  • apps/deploy-web/src/components/layout/AccountMenu.tsx
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts
  • apps/api/src/routers/userRouter.ts
  • apps/api/src/core/repositories/base.repository.ts
  • apps/api/src/auth/services/auth.interceptor.ts
  • apps/api/src/auth/services/auth.interceptor.spec.ts
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts
  • apps/api/test/services/wallet-testing.service.ts
  • apps/deploy-web/src/services/auth/auth.service.ts
  • apps/api/src/user/schemas/user.schema.ts
  • apps/api/src/app.ts
  • apps/api/src/user/repositories/user/user.repository.ts
  • apps/api/src/user/controllers/user/user.controller.ts
  • apps/api/src/user/services/user/user.service.ts
  • apps/api/src/user/routes/index.ts
  • apps/api/src/user/routes/register-user/register-user.router.ts
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/deploy-web/src/services/storage/keys.ts
  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/deploy-web/src/services/storage/keys.ts
  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/user/services/user/user.service.spec.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
apps/{deploy-web,provider-console}/**/*.spec.tsx

📄 CodeRabbit Inference Engine (.cursor/rules/query-by-in-tests.mdc)

Use queryBy methods instead of getBy methods in test expectations in .spec.tsx files

Files:

  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-21T08:24:24.269Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/no-jest-mock.mdc:0-0
Timestamp: 2025-07-21T08:24:24.269Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't use `jest.mock()` to mock dependencies in test files. Instead, use `jest-mock-extended` to create mocks and pass mocks as dependencies to the service under test.

Applied to files:

  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
🧬 Code Graph Analysis (2)
apps/api/src/user/services/user/user.service.spec.ts (1)
apps/api/src/user/services/user/user.service.ts (1)
  • RegisterUserInput (139-149)
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (1)
apps/deploy-web/src/services/storage/keys.ts (1)
  • ONBOARDING_STEP_KEY (1-1)
🪛 ast-grep (0.38.6)
apps/api/src/user/services/user/user.service.spec.ts

[warning] 155-155: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 157-157: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
🔇 Additional comments (5)
apps/api/src/user/services/user/user.service.spec.ts (1)

1-6: Good functional test harness and mocking strategy

Side-effect test setup + jest-mock-extended mocks align with our testing guidelines. Container-resolved repositories enable realistic DB-level coverage.

apps/deploy-web/src/services/storage/keys.ts (1)

1-1: No stray “onboardingStep” references found
The centralized ONBOARDING_STEP_KEY is the sole source of truth—no hard-coded occurrences remain outside apps/deploy-web/src/services/storage/keys.ts.

apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1)

142-169: Ensure global window overrides are always restored.

setup() mutates window.location/history but most tests don’t call cleanup(), risking cross-test leakage.

Either:

  • Call cleanup() at the end of every test, or
  • Modify setup() to wrap render and auto-restore in a finally block on unmount, or
  • Have setup() return a dispose() and use try/finally in tests.

Example (test usage):

const { cleanup, child } = setup();
try {
  // assertions
} finally {
  cleanup();
}

Also applies to: 207-216

apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (2)

169-175: Reassess disabling the “Payment Method” step when none exist.

If the step is disabled when paymentMethods.length === 0, users may not be able to reach the UI needed to add the first method. Confirm UX: often the step is enabled but its “Next/Continue” is gated until a method is added.

Option if needed:

-      isDisabled: paymentMethods.length === 0
+      isDisabled: false

and rely on the existing guard in handleStepChange that blocks advancing to WELCOME when no payment methods exist.


116-119: Clear persisted state, then navigate: good.

Removing ONBOARDING_STEP_KEY before redirect avoids resuming onboarding after completion.

@stalniy stalniy force-pushed the refactor/user-register branch from 632a08c to 13852f7 Compare August 12, 2025 16:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
apps/api/src/user/controllers/user/user.controller.ts (1)

48-54: Simplify return type; avoid union and router-dependent types

getById always returns { data }, so the union with GetUserResponseOutput is unnecessary and pulls in a router-dependent type. Inline the param type to avoid the router import.

-  async getById({ id }: GetUserParams): Promise<{ data: UserSchema } | GetUserResponseOutput> {
+  async getById({ id }: { id: string }): Promise<{ data: UserSchema }> {
     const user = await this.userRepository.accessibleBy(this.authService.ability, "read").findById(id);
 
     assert(user, 404);
 
     return { data: user };
   }
♻️ Duplicate comments (3)
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (1)

128-131: Use URL API to build signup URL robustly (handles absolute/relative paths and existing query strings)

String concatenation breaks if signup() already includes query params or onboarding() returns an absolute URL. Use URL/URLSearchParams to avoid edge cases.

-    const returnUrl = `${window.location.origin}${urlService.onboarding(true)}`;
-    const signupUrl = `${urlService.signup()}?returnTo=${encodeURIComponent(returnUrl)}`;
-    window.location.href = signupUrl;
+    const onboardingPath = urlService.onboarding(true);
+    const returnUrl = new URL(onboardingPath, window.location.origin).toString();
+    const signup = new URL(urlService.signup(), window.location.origin);
+    signup.searchParams.set("returnTo", returnUrl);
+    window.location.href = signup.toString();
apps/api/src/user/controllers/user/user.controller.ts (2)

10-13: Break the import cycle: controller should not import router types

Importing RegisterUserInput/RegisterUserResponse and GetUserParams from routers introduces a dependency cycle (router imports controller). Use service types or inline shapes, and return a structural response.

-import type { GetUserParams } from "@src/user/routes/get-anonymous-user/get-anonymous-user.router";
-import type { RegisterUserInput, RegisterUserResponse } from "@src/user/routes/register-user/register-user.router";
-import { AnonymousUserResponseOutput, GetUserResponseOutput, UserSchema } from "@src/user/schemas/user.schema";
+import type { RegisterUserInput as ServiceRegisterUserInput } from "@src/user/services/user/user.service";
+import { AnonymousUserResponseOutput, UserSchema } from "@src/user/schemas/user.schema";

60-78: Decouple from router types and validate anonymous token explicitly

  • Use the service input type (or a narrowed pick) instead of router types to avoid cycles.
  • If the anonymous auth header is present but invalid, return 401 to prevent silently treating it as “no anonymous user”.
-  async registerUser(data: RegisterUserInput): Promise<RegisterUserResponse> {
+  async registerUser(
+    data: Pick<ServiceRegisterUserInput, "wantedUsername" | "email" | "emailVerified" | "subscribedToNewsletter">
+  ): Promise<{ data: UserSchema }> {
     const { req, env, var: httpVars } = this.httpContext;
     const [userId, anonymousUserId] = await Promise.all([
       this.userAuthTokenService.getValidUserId(req.header("authorization") || "", env),
       this.anonymousUserAuthService.getValidUserId(req.header("x-anonymous-authorization") || "")
     ]);
+    // If an anonymous token was provided but failed validation, reject the request
+    if (req.header("x-anonymous-authorization")) {
+      assert(anonymousUserId, 401, "Invalid anonymous user token");
+    }
     const user = await this.userService.registerUser({
       anonymousUserId,
       userId,
       wantedUsername: data.wantedUsername,
       email: data.email,
       emailVerified: data.emailVerified,
       subscribedToNewsletter: !!data.subscribedToNewsletter,
       ip: httpVars.clientInfo?.ip,
       userAgent: httpVars.clientInfo?.userAgent,
       fingerprint: httpVars.clientInfo?.fingerprint
     });
     return { data: user };
   }
🧹 Nitpick comments (6)
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (4)

49-56: Harden localStorage read and enum bounds check

Access to localStorage may throw (e.g., blocked/disabled). Also, rely on numeric enum members instead of Object.keys(...)/2 for bounds.

Apply this diff:

   useEffect(() => {
-    const savedStep = localStorage.getItem(ONBOARDING_STEP_KEY);
-    if (savedStep) {
-      const step = parseInt(savedStep, 10);
-      if (step >= 0 && step < Object.keys(OnboardingStepIndex).length / 2) {
-        setCurrentStep(step);
-      }
-    }
+    let savedStep: string | null = null;
+    try {
+      savedStep = localStorage.getItem(ONBOARDING_STEP_KEY);
+    } catch {}
+    if (savedStep) {
+      const step = Number.parseInt(savedStep, 10);
+      const maxIndex = Object.values(OnboardingStepIndex).filter(v => typeof v === "number").length;
+      if (Number.isFinite(step) && step >= 0 && step < maxIndex) {
+        setCurrentStep(step as OnboardingStepIndex);
+      }
+    }

65-72: Guard localStorage.setItem when persisting EMAIL_VERIFICATION step

Avoid potential runtime errors if storage is unavailable.

-      localStorage.setItem(ONBOARDING_STEP_KEY, OnboardingStepIndex.EMAIL_VERIFICATION.toString());
+      try {
+        localStorage.setItem(ONBOARDING_STEP_KEY, OnboardingStepIndex.EMAIL_VERIFICATION.toString());
+      } catch {}

96-98: Guard localStorage.setItem on step change

Same rationale as above; prevents hard failures in restricted environments.

-      localStorage.setItem(ONBOARDING_STEP_KEY, step.toString());
+      try {
+        localStorage.setItem(ONBOARDING_STEP_KEY, step.toString());
+      } catch {}

116-119: Guard localStorage.removeItem on completion

Minor hardening; keeps completion flow resilient.

-    localStorage.removeItem(ONBOARDING_STEP_KEY);
+    try {
+      localStorage.removeItem(ONBOARDING_STEP_KEY);
+    } catch {}
apps/api/src/user/services/user/user.service.spec.ts (1)

156-158: Avoid dynamic RegExp in tests; prefer startsWith for clarity and safety

Constructing RegExp from variable input is unnecessary here and flagged by static analysis. Use startsWith to assert the prefix.

-      expect(newUsers[0].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[0].username.startsWith(conflictingUsername)).toBe(true);
@@
-      expect(newUsers[1].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[1].username.startsWith(conflictingUsername)).toBe(true);
apps/api/src/auth/services/auth.interceptor.ts (1)

117-122: Avoid magic string for HTTP_CONTEXT and revisit error/PII handling

Suggestions:

  • Replace the "HTTP_CONTEXT" magic string with a shared typed token/const to avoid typos and improve discoverability.
  • If the DB write in markAsActive fails, consider setting the lastUserActivityCache only after a successful write to prevent long silences; or explicitly decide to prefer reduced DB load over guaranteed persistence and document the trade-off.
  • Ensure ip and fingerprint usage aligns with your privacy policy (storage duration, hashing/anonymization of fingerprint if needed).
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 632a08c and 13852f7.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (21)
  • apps/api/src/app.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.spec.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.ts (3 hunks)
  • apps/api/src/core/repositories/base.repository.ts (2 hunks)
  • apps/api/src/routers/userRouter.ts (1 hunks)
  • apps/api/src/user/controllers/user/user.controller.ts (4 hunks)
  • apps/api/src/user/repositories/user/user.repository.ts (4 hunks)
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts (1 hunks)
  • apps/api/src/user/routes/index.ts (1 hunks)
  • apps/api/src/user/routes/register-user/register-user.router.ts (1 hunks)
  • apps/api/src/user/schemas/user.schema.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.spec.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.ts (1 hunks)
  • apps/api/test/services/wallet-testing.service.ts (2 hunks)
  • apps/deploy-web/src/components/layout/AccountMenu.tsx (3 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (7 hunks)
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts (4 hunks)
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts (1 hunks)
  • apps/deploy-web/src/services/auth/auth.service.ts (2 hunks)
  • apps/deploy-web/src/services/storage/keys.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (16)
  • apps/api/src/user/schemas/user.schema.ts
  • apps/api/src/routers/userRouter.ts
  • apps/deploy-web/src/services/storage/keys.ts
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts
  • apps/api/src/app.ts
  • apps/deploy-web/src/components/layout/AccountMenu.tsx
  • apps/api/src/user/routes/register-user/register-user.router.ts
  • apps/api/test/services/wallet-testing.service.ts
  • apps/api/src/user/services/user/user.service.ts
  • apps/api/src/auth/services/auth.interceptor.spec.ts
  • apps/api/src/core/repositories/base.repository.ts
  • apps/deploy-web/src/services/auth/auth.service.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts
  • apps/api/src/user/repositories/user/user.repository.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/user/routes/index.ts
  • apps/api/src/auth/services/auth.interceptor.ts
  • apps/api/src/user/controllers/user/user.controller.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
  • apps/api/src/user/services/user/user.service.spec.ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/user/routes/index.ts
  • apps/api/src/auth/services/auth.interceptor.ts
  • apps/api/src/user/controllers/user/user.controller.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
  • apps/api/src/user/services/user/user.service.spec.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/user/services/user/user.service.spec.ts
🪛 ast-grep (0.38.6)
apps/api/src/user/services/user/user.service.spec.ts

[warning] 155-155: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 157-157: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🔇 Additional comments (7)
apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (3)

9-9: Using a shared storage key constant — LGTM

Importing ONBOARDING_STEP_KEY centralizes the key and avoids string duplication.


33-38: Expose useRouter via DEPENDENCIES for testability — good change

Keeping router acquisition behind DEPENDENCIES aligns with your DI pattern and simplifies unit testing.


47-47: Switch to urlService from useServices — aligned with DI changes

Consistent with the updated ServicesProvider pattern and improves composability.

apps/api/src/user/routes/index.ts (1)

3-4: LGTM: route exports added

The new route re-exports look correct and maintain the pattern used in this barrel file.

apps/api/src/auth/services/auth.interceptor.ts (3)

36-38: Constructor injection updated — LGTM

Adding ExecutionContextService to the interceptor’s constructor is consistent with its usage below.


117-122: Pass HTTP context (ip/fingerprint) to userRepository.markAsActive — LGTM

Good use of ExecutionContextService and optional chaining to safely enrich activity tracking with client info.


11-11: ExecutionContextService DI registration and HTTP_CONTEXT setup confirmed

  • apps/api/src/core/services/execution-context/execution-context.service.ts
    @singleton() decorator is applied on the ExecutionContextService class.
  • apps/api/src/core/services/request-context-interceptor/request-context.interceptor.ts (lines 15–18)
    • executionContextService.runWithContext wraps each request and calls set("HTTP_CONTEXT", c) before next().

The HTTP_CONTEXT key will therefore be populated on each request prior to AuthInterceptor execution. No further action required.

@stalniy stalniy force-pushed the refactor/user-register branch from 13852f7 to 36cdea4 Compare August 13, 2025 03:56
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (2)
apps/deploy-web/src/pages/api/auth/[...auth0].ts (2)

24-35: Sanitize and validate returnTo to prevent open-redirects and bad decode inputs

The from query param is decoded without validation and forwarded to Auth0 as returnTo. Malformed encodings can throw, and unvalidated absolute/external URLs can enable open-redirects. Prefer a safe, relative path-only fallback.

Apply this diff:

-      const returnUrl = decodeURIComponent((req.query.from as string) ?? "/");
+      const from = Array.isArray(req.query.from) ? req.query.from[0] : req.query.from;
+      let returnTo = "/";
+      if (typeof from === "string") {
+        try {
+          const decoded = decodeURIComponent(from);
+          // only allow same-origin relative paths
+          if (decoded.startsWith("/") && !decoded.startsWith("//")) {
+            returnTo = decoded;
+          }
+        } catch {
+          // ignore invalid encoding and fall back to "/"
+        }
+      }
@@
-      await handleLogin(req, res, {
-        returnTo: returnUrl,
+      await handleLogin(req, res, {
+        returnTo,
         // Reduce the scope to minimize session data
         authorizationParams: {
           scope: "openid profile email"
         }
       });

118-127: Avoid any in catch and use safe property access for status/message

Replace error: any with unknown and derive status/message via narrow type checks.

-      } catch (error: any) {
-        let severity: SeverityLevel = "error";
-        if (error?.status && error.status >= 400 && error.status < 500) {
-          severity = "warning";
-          res.status(400).send({ message: error.message });
-        } else {
-          res.status(503).send({ message: "An unexpected error occurred. Please try again later." });
-        }
-        services.errorHandler.reportError({ severity, error, tags: { category: "auth0", event: "AUTH_PROFILE_ERROR" } });
-      }
+      } catch (error: unknown) {
+        let severity: SeverityLevel = "error";
+        const status = typeof (error as { status?: unknown })?.status === "number" ? (error as { status: number }).status : undefined;
+        if (status && status >= 400 && status < 500) {
+          severity = "warning";
+          const message =
+            typeof (error as { message?: unknown })?.message === "string"
+              ? (error as { message: string }).message
+              : "Bad Request";
+          res.status(400).send({ message });
+        } else {
+          res.status(503).send({ message: "An unexpected error occurred. Please try again later." });
+        }
+        services.errorHandler.reportError({ severity, error, tags: { category: "auth0", event: "AUTH_PROFILE_ERROR" } });
+      }
♻️ Duplicate comments (3)
apps/api/src/user/repositories/user/user.repository.ts (2)

96-106: Normalize update path, avoid writing undefineds, and set updated_at in upsert

Update currently uses raw data for set:, bypassing toInput (e.g., user agent truncation) and may write undefineds.

[ suggest_essential_refactor ]
Apply this diff:

   async upsert(data: UserInput): Promise<UserOutput> {
-    const [item] = await this.cursor
+    const insertChanges = this.toInput(data);
+    const updateChanges = Object.fromEntries(Object.entries(insertChanges).filter(([, v]) => v !== undefined)) as PgUpdateSetSource<
+      typeof this.table
+    >;
+    const [item] = await this.cursor
       .insert(this.table)
-      .values(this.toInput(data))
+      .values(insertChanges)
       .onConflictDoUpdate({
         target: [this.table.userId],
-        set: data
+        set: {
+          ...updateChanges,
+          updated_at: sql`now()`
+        }
       })
       .returning();
     return this.toOutput(item);
   }

Additionally, consider normalizing in create() as well to ensure lastUserAgent is consistently truncated:

// outside selected lines; apply near line 31
const [item] = await this.cursor.insert(this.table).values(this.toInput(input)).returning();

62-71: Throttle condition ignores NULL lastActiveAt; first updates will never mark active

Rows with lastActiveAt = NULL won’t match lt(NULL, …). Include an isNull branch.

Apply this diff:

-import { and, desc, eq, isNull, lt, lte, SQL, sql } from "drizzle-orm";
+import { and, desc, eq, isNull, lt, lte, or, SQL, sql } from "drizzle-orm";
@@
       .where(
         and(
           // keep new line
           eq(this.table.id, id),
-          lt(this.table.lastActiveAt, sql`now() - make_interval(secs => ${options.throttleTimeSeconds})`)
+          or(
+            isNull(this.table.lastActiveAt),
+            lt(this.table.lastActiveAt, sql`now() - make_interval(secs => ${options.throttleTimeSeconds})`)
+          )
         )
       );
apps/api/src/user/services/user/user.service.spec.ts (1)

265-271: Align setup() with testing guideline: accept a single parameter with inline type

Per repo rules for *.spec.ts: setup should accept a single parameter with inline type to allow overrides.

[ suggest_essential_refactor ]

-  function setup() {
-    const analyticsService = mock<AnalyticsService>();
-    const logger = mock<LoggerService>();
+  function setup(overrides: { analyticsService?: AnalyticsService; logger?: LoggerService } = {}) {
+    const analyticsService = overrides.analyticsService ?? mock<AnalyticsService>();
+    const logger = overrides.logger ?? mock<LoggerService>();
     const service = new UserService(container.resolve(UserRepository), analyticsService, container.resolve(UserWalletRepository), logger);
 
     return { service, analyticsService, logger };
   }
🧹 Nitpick comments (6)
apps/deploy-web/src/services/auth/auth.service.ts (3)

4-4: Prefer path alias over relative path for storage keys import

Use the configured @src alias (as used elsewhere) to avoid brittle relative paths and keep imports consistent across the codebase.

-import { ONBOARDING_STEP_KEY } from "../storage/keys";
+import { ONBOARDING_STEP_KEY } from "@src/services/storage/keys";

27-27: Consider replace() to avoid back navigation into authenticated state

Using replace prevents the previous page from being reachable via the back button after logout, which is often desired.

-    window.location.href = this.urlService.logout();
+    window.location.replace(this.urlService.logout());

31-32: Export LogoutUrlService for DI typing reuse

Exporting the interface allows other modules (e.g., DI container) to type the provider explicitly without duplicating the shape.

-interface LogoutUrlService {
+export interface LogoutUrlService {
   logout(): string;
 }
apps/deploy-web/src/pages/api/auth/[...auth0].ts (2)

132-141: Remove explicit any from NextApiResponse

Don’t use any. NextApiResponse’s default generic is sufficient here.

-function rewriteLocalRedirect(res: NextApiResponse<any>, config: Pick<ServerEnvConfig, "AUTH0_REDIRECT_BASE_URL" | "AUTH0_ISSUER_BASE_URL">) {
+function rewriteLocalRedirect(res: NextApiResponse, config: Pick<ServerEnvConfig, "AUTH0_REDIRECT_BASE_URL" | "AUTH0_ISSUER_BASE_URL">) {
   const redirect = res.redirect;
 
-  res.redirect = function rewriteLocalRedirect(urlOrStatus: string | number, maybeUrl?: string): NextApiResponse<any> {
+  res.redirect = function rewriteLocalRedirect(urlOrStatus: string | number, maybeUrl?: string): NextApiResponse {
     const code = typeof urlOrStatus === "string" ? 302 : urlOrStatus;
     const inputUrl = typeof urlOrStatus === "string" ? urlOrStatus : maybeUrl;
     const rewritten = config.AUTH0_REDIRECT_BASE_URL ? inputUrl!.replace(config.AUTH0_ISSUER_BASE_URL, config.AUTH0_REDIRECT_BASE_URL || "") : inputUrl!;
 
     return redirect.apply(this, [code, rewritten]);
   };
 }

1-1: Update the header comment to reflect .ts file

Tiny mismatch can be confusing during debugging and code search.

-// pages/api/auth/[...auth0].js
+// pages/api/auth/[...auth0].ts
apps/api/src/user/services/user/user.service.spec.ts (1)

156-158: Avoid variable-driven RegExp in tests; use startsWith instead

Using a dynamic RegExp is unnecessary and can trigger ReDoS linters. startsWith is simpler and precise.

-      expect(newUsers[0].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[0].username?.startsWith(conflictingUsername)).toBe(true);
@@
-      expect(newUsers[1].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[1].username?.startsWith(conflictingUsername)).toBe(true);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 13852f7 and 36cdea4.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (21)
  • apps/api/src/app.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.spec.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.ts (3 hunks)
  • apps/api/src/core/repositories/base.repository.ts (2 hunks)
  • apps/api/src/routers/userRouter.ts (1 hunks)
  • apps/api/src/user/controllers/user/user.controller.ts (4 hunks)
  • apps/api/src/user/repositories/user/user.repository.ts (4 hunks)
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts (1 hunks)
  • apps/api/src/user/routes/index.ts (1 hunks)
  • apps/api/src/user/routes/register-user/register-user.router.ts (1 hunks)
  • apps/api/src/user/schemas/user.schema.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.spec.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.ts (1 hunks)
  • apps/api/test/services/wallet-testing.service.ts (2 hunks)
  • apps/deploy-web/src/components/layout/AccountMenu.tsx (3 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (7 hunks)
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts (4 hunks)
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts (1 hunks)
  • apps/deploy-web/src/services/auth/auth.service.ts (2 hunks)
  • apps/deploy-web/src/services/storage/keys.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
  • apps/deploy-web/src/components/layout/AccountMenu.tsx
  • apps/api/src/routers/userRouter.ts
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts
  • apps/api/src/user/controllers/user/user.controller.ts
  • apps/deploy-web/src/services/storage/keys.ts
  • apps/api/src/user/schemas/user.schema.ts
  • apps/api/src/user/services/user/user.service.ts
  • apps/api/src/user/routes/index.ts
  • apps/api/src/user/routes/register-user/register-user.router.ts
  • apps/api/src/auth/services/auth.interceptor.spec.ts
  • apps/api/test/services/wallet-testing.service.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
  • apps/api/src/app.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/api/src/core/repositories/base.repository.ts
  • apps/api/src/auth/services/auth.interceptor.ts
  • apps/deploy-web/src/services/auth/auth.service.ts
  • apps/api/src/user/repositories/user/user.repository.ts
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts
  • apps/api/src/user/services/user/user.service.spec.ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/api/src/core/repositories/base.repository.ts
  • apps/api/src/auth/services/auth.interceptor.ts
  • apps/deploy-web/src/services/auth/auth.service.ts
  • apps/api/src/user/repositories/user/user.repository.ts
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts
  • apps/api/src/user/services/user/user.service.spec.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/user/services/user/user.service.spec.ts
🧠 Learnings (5)
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : `setup` function should accept a single parameter with inline type definition

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Use `setup` function instead of `beforeEach` in test files

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : `setup` function creates an object under test and returns it

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't specify return type of `setup` function

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't use shared state in `setup` function

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
🧬 Code Graph Analysis (5)
apps/api/src/auth/services/auth.interceptor.ts (2)
apps/api/src/user/controllers/user/user.controller.ts (1)
  • httpContext (31-33)
apps/notifications/src/interfaces/rest/services/auth/auth.service.ts (1)
  • userId (18-25)
apps/deploy-web/src/services/auth/auth.service.ts (2)
apps/deploy-web/src/config/auth.config.ts (1)
  • ANONYMOUS_USER_TOKEN_KEY (2-2)
apps/deploy-web/src/services/storage/keys.ts (1)
  • ONBOARDING_STEP_KEY (1-1)
apps/api/src/user/repositories/user/user.repository.ts (1)
apps/api/src/user/model-schemas/user/user.schema.ts (1)
  • userAgentMaxLength (6-6)
apps/deploy-web/src/pages/api/auth/[...auth0].ts (4)
apps/deploy-web/src/services/app-di-container/browser-di-container.ts (1)
  • services (22-54)
apps/deploy-web/src/services/app-di-container/server-di-container.service.ts (1)
  • services (22-32)
apps/deploy-web/src/types/user.ts (1)
  • UserSettings (5-16)
packages/logging/src/servicies/logger/logger.service.ts (1)
  • error (107-109)
apps/api/src/user/services/user/user.service.spec.ts (3)
apps/api/src/user/routes/register-user/register-user.router.ts (1)
  • RegisterUserInput (15-15)
apps/api/src/user/services/user/user.service.ts (1)
  • RegisterUserInput (139-149)
apps/deploy-web/src/services/analytics/analytics.service.ts (1)
  • AnalyticsService (141-266)
🪛 ast-grep (0.38.6)
apps/api/src/user/services/user/user.service.spec.ts

[warning] 155-155: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 157-157: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
  • GitHub Check: validate (apps/stats-web) / should-validate-unsafe / should-validate
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
🔇 Additional comments (7)
apps/deploy-web/src/services/auth/auth.service.ts (2)

7-7: Good decoupling via interface for UrlService dependency

Switching to a minimal LogoutUrlService interface resolves the previous typeof misuse and improves testability.


23-26: Logout: correct SSR guard and cleanup-before-redirect order

The window check prevents SSR issues, and clearing storage before navigation ensures cleanup is not skipped.

apps/deploy-web/src/pages/api/auth/[...auth0].ts (1)

74-77: Type the caught error as unknown

Per coding guidelines for TS files: avoid any/implicit any. Explicitly type error as unknown.

[ suggest_essential_refactor ]

-      } catch (error) {
+      } catch (error: unknown) {
         services.errorHandler.reportError({ error, tags: { category: "auth0", event: "AUTH_CALLBACK_ERROR" } });
         throw error;
       }
apps/api/src/core/repositories/base.repository.ts (1)

226-228: Useful and correct unique-violation type guard

The guard is precise and leverages the Postgres error code 23505. Good addition for upsert/collision handling.

apps/api/src/auth/services/auth.interceptor.ts (2)

36-38: DI: ExecutionContextService injection is appropriate

Wiring ExecutionContextService into the interceptor aligns with controller usage patterns and enables passing contextual client info.


117-123: Propagate client context (ip/fingerprint) to markAsActive

Good use of optional chaining with a throttled update. Complements the repository’s new signature.

apps/api/src/user/repositories/user/user.repository.ts (1)

128-134: Input normalization for lastUserAgent is correct

Truncation at userAgentMaxLength is guarded and minimal. LGTM.

baktun14
baktun14 previously approved these changes Aug 13, 2025
@stalniy stalniy force-pushed the refactor/user-register branch from 36cdea4 to 7d186cf Compare August 13, 2025 17:50
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🔭 Outside diff range comments (3)
apps/deploy-web/src/pages/api/auth/[...auth0].ts (3)

24-31: Harden returnTo handling: decoding safety and open-redirect guard

Protect against invalid percent-encoding and restrict to internal paths to avoid open redirects.

-      const returnUrl = decodeURIComponent((req.query.from as string) ?? "/");
+      const rawFrom = Array.isArray(req.query.from) ? req.query.from[0] : req.query.from;
+      let safeReturnUrl = "/";
+      if (typeof rawFrom === "string") {
+        try {
+          const decoded = decodeURIComponent(rawFrom);
+          // only allow relative internal paths
+          safeReturnUrl = decoded.startsWith("/") ? decoded : "/";
+        } catch {
+          safeReturnUrl = "/";
+        }
+      }
@@
-      await handleLogin(req, res, {
-        returnTo: returnUrl,
+      await handleLogin(req, res, {
+        returnTo: safeReturnUrl,
         // Reduce the scope to minimize session data
         authorizationParams: {
           scope: "openid profile email"
         }
       });

118-127: Disallow any in catch; use unknown and narrow safely

Per guidelines, avoid any. Narrow the error via type checks.

-      } catch (error: any) {
-        let severity: SeverityLevel = "error";
-        if (error?.status && error.status >= 400 && error.status < 500) {
-          severity = "warning";
-          res.status(400).send({ message: error.message });
-        } else {
-          res.status(503).send({ message: "An unexpected error occurred. Please try again later." });
-        }
-        services.errorHandler.reportError({ severity, error, tags: { category: "auth0", event: "AUTH_PROFILE_ERROR" } });
+      } catch (unknownError: unknown) {
+        let severity: SeverityLevel = "error";
+        const hasStatus = (e: unknown): e is { status: number; message?: string } =>
+          typeof e === "object" && e !== null && "status" in e && typeof (e as { status: unknown }).status === "number";
+        if (hasStatus(unknownError) && unknownError.status >= 400 && unknownError.status < 500) {
+          severity = "warning";
+          res.status(400).send({ message: unknownError.message ?? "Bad Request" });
+        } else {
+          res.status(503).send({ message: "An unexpected error occurred. Please try again later." });
+        }
+        services.errorHandler.reportError({
+          severity,
+          error: unknownError,
+          tags: { category: "auth0", event: "AUTH_PROFILE_ERROR" }
+        });
       }

29-35: Ensure Auth0 audience is passed to handleLogin for an access token

It looks like you have AUTH0_AUDIENCE defined in your env schemas, but you’re not passing it into handleLogin. Without an audience param, Auth0 won’t issue an API access token and session.accessToken will remain undefined.

• File: apps/deploy-web/src/pages/api/auth/[...auth0].ts (around lines 29–35)
• Add the audience from process.env.AUTH0_AUDIENCE into authorizationParams

Proposed diff:

       await handleLogin(req, res, {
         returnTo: returnUrl,
         // Reduce the scope to minimize session data
         authorizationParams: {
-          scope: "openid profile email"
+          scope: "openid profile email",
+          audience: process.env.AUTH0_AUDIENCE
         }
       });

• Confirm that AUTH0_AUDIENCE is set in your runtime environment (and matches the value in apps/deploy-web/src/config/env-config.schema.ts).

♻️ Duplicate comments (5)
apps/deploy-web/src/services/auth/auth.service.ts (1)

7-7: Good decoupling: interface-based dependency instead of typeof value import

Switching to a minimal LogoutUrlService interface resolves the prior misuse of typeof UrlService with a type-only import and avoids a value import dependency.

apps/api/src/user/repositories/user/user.repository.ts (2)

47-71: Fix throttle condition: lastActiveAt NULL rows never update (regression).

If lastActiveAt can be NULL (new/legacy users), lt(NULL, …) evaluates to NULL and the row is not matched. Add an OR isNull(lastActiveAt) to allow the first update to go through.

Apply this diff within the where clause:

       .where(
-        and(
-          // keep new line
-          eq(this.table.id, id),
-          lt(this.table.lastActiveAt, sql`now() - make_interval(secs => ${options.throttleTimeSeconds})`)
-        )
+        and(
+          // keep new line
+          eq(this.table.id, id),
+          or(
+            isNull(this.table.lastActiveAt),
+            lt(this.table.lastActiveAt, sql`now() - make_interval(secs => ${options.throttleTimeSeconds})`)
+          )
+        )
       );

Additionally, ensure or is imported:

-import { and, desc, eq, isNull, lt, lte, SQL, sql } from "drizzle-orm";
+import { and, desc, eq, isNull, lt, lte, or, SQL, sql } from "drizzle-orm";

96-106: Normalize and filter update payload in upsert; avoid raw data on update.

Insert path uses toInput but update path uses raw data. This bypasses normalization (e.g., lastUserAgent truncation) and may write undefineds. Use a single normalized object for both paths and filter undefineds.

   async upsert(data: UserInput): Promise<UserOutput> {
-    const [item] = await this.cursor
+    const insertChanges = this.toInput(data);
+    const updateChanges = Object.fromEntries(
+      Object.entries(insertChanges).filter(([, v]) => v !== undefined)
+    ) as PgUpdateSetSource<typeof this.table>;
+
+    const [item] = await this.cursor
       .insert(this.table)
-      .values(this.toInput(data))
+      .values(insertChanges)
       .onConflictDoUpdate({
         target: [this.table.userId],
-        set: data
+        set: updateChanges
       })
       .returning();
     return this.toOutput(item);
   }
apps/api/src/user/services/user/user.service.spec.ts (1)

265-271: Align setup() with testing guideline: accept a single parameter with inline type.

Supports dependency overrides per test and follows project rules for *.spec.ts files.

-  function setup() {
-    const analyticsService = mock<AnalyticsService>();
-    const logger = mock<LoggerService>();
+  function setup(overrides: { analyticsService?: AnalyticsService; logger?: LoggerService } = {}) {
+    const analyticsService = overrides.analyticsService ?? mock<AnalyticsService>();
+    const logger = overrides.logger ?? mock<LoggerService>();
     const service = new UserService(container.resolve(UserRepository), analyticsService, container.resolve(UserWalletRepository), logger);
 
     return { service, analyticsService, logger };
   }
apps/deploy-web/src/pages/api/auth/[...auth0].ts (1)

47-51: Forward the correct anonymous header and validate Bearer format

This still reads req.headers.authorization; the browser sets x-anonymous-authorization. Forwarding the wrong header risks leaking unrelated auth headers. Validate it’s a Bearer token before forwarding.

Apply this diff:

-              const anonymousAuthorization = req.headers.authorization;
-
-              if (anonymousAuthorization) {
-                headers.set("x-anonymous-authorization", anonymousAuthorization);
-              }
+              const anonymousAuthorizationHeader =
+                (req.headers["x-anonymous-authorization"] as string | string[] | undefined) ?? req.headers.authorization;
+              const anonymousAuthorization = Array.isArray(anonymousAuthorizationHeader)
+                ? anonymousAuthorizationHeader[0]
+                : anonymousAuthorizationHeader;
+              if (typeof anonymousAuthorization === "string" && /^Bearer\s+\S+$/i.test(anonymousAuthorization)) {
+                headers.set("x-anonymous-authorization", anonymousAuthorization);
+              }
🧹 Nitpick comments (8)
apps/deploy-web/src/services/auth/auth.service.ts (3)

4-4: Prefer path alias for consistency and resilience

Use the existing @src alias (already used on Line 3) to avoid fragile relative paths and keep imports consistent across the codebase.

-import { ONBOARDING_STEP_KEY } from "../storage/keys";
+import { ONBOARDING_STEP_KEY } from "@src/services/storage/keys";

27-27: Use location.replace to prevent back navigation after logout

Replace location assignment with replace() to avoid adding a history entry that can lead users back into a stale authenticated page.

-    window.location.href = this.urlService.logout();
+    window.location.replace(this.urlService.logout());

31-32: Consider exporting the LogoutUrlService interface

Exporting enables strongly-typed DI wiring and easier mocking in tests across modules. If you want to keep it internal, ignore.

-interface LogoutUrlService {
+export interface LogoutUrlService {
   logout(): string;
 }
apps/api/src/user/services/user/user.service.spec.ts (2)

16-16: Fix test title to match behavior.

The test provides a userId, so the description is misleading.

-    it("registers a new user if no anonymousUserId and no userId is provided", async () => {
+    it("registers a new user when no anonymousUserId is provided", async () => {

155-158: Avoid dynamic RegExp in tests; use startsWith for clarity and speed.

Reduces complexity and silences static analysis warnings about variable-based regex.

-      expect(newUsers[0].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[0].username.startsWith(conflictingUsername)).toBe(true);
@@
-      expect(newUsers[1].username).toMatch(new RegExp(`^${conflictingUsername}`));
+      expect(newUsers[1].username.startsWith(conflictingUsername)).toBe(true);
apps/deploy-web/src/pages/api/auth/[...auth0].ts (2)

42-42: Prefer camelCase and add a precise type for metadata

Follow TS naming conventions and avoid implicitly any-typed values by typing the metadata shape.

-              const user_metadata = session.user["https://console.akash.network/user_metadata"];
+              const userMetadata =
+                session.user["https://console.akash.network/user_metadata"] as
+                  | { subscribedToNewsletter?: string }
+                  | undefined;
@@
-                  subscribedToNewsletter: user_metadata?.subscribedToNewsletter === "true"
+                  subscribedToNewsletter: userMetadata?.subscribedToNewsletter === "true"

Also applies to: 59-60


1-1: Fix header comment to reflect TypeScript file

The file is .ts, not .js.

-// pages/api/auth/[...auth0].js
+// pages/api/auth/[...auth0].ts
apps/api/src/user/controllers/user/user.controller.ts (1)

80-85: Optional: Drop redundant assert under @Protected

@Protected already enforces authentication. The additional assert(this.authService.currentUser, 401) is likely redundant. If you keep it for belt-and-suspenders, it’s fine; otherwise, remove for clarity.

   @Protected([{ action: "read", subject: "User" }])
   async getCurrentUser(): Promise<{ data: UserSchema }> {
-    assert(this.authService.currentUser, 401);
-
     return { data: this.authService.currentUser as UserSchema };
   }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 36cdea4 and 9a4b605.

⛔ Files ignored due to path filters (1)
  • apps/api/test/functional/__snapshots__/docs.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (21)
  • apps/api/src/app.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.spec.ts (2 hunks)
  • apps/api/src/auth/services/auth.interceptor.ts (3 hunks)
  • apps/api/src/core/repositories/base.repository.ts (2 hunks)
  • apps/api/src/routers/userRouter.ts (1 hunks)
  • apps/api/src/user/controllers/user/user.controller.ts (4 hunks)
  • apps/api/src/user/repositories/user/user.repository.ts (4 hunks)
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts (1 hunks)
  • apps/api/src/user/routes/index.ts (1 hunks)
  • apps/api/src/user/routes/register-user/register-user.router.ts (1 hunks)
  • apps/api/src/user/schemas/user.schema.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.spec.ts (1 hunks)
  • apps/api/src/user/services/user/user.service.ts (1 hunks)
  • apps/api/test/services/wallet-testing.service.ts (2 hunks)
  • apps/deploy-web/src/components/layout/AccountMenu.tsx (3 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx (1 hunks)
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx (7 hunks)
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts (4 hunks)
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts (1 hunks)
  • apps/deploy-web/src/services/auth/auth.service.ts (2 hunks)
  • apps/deploy-web/src/services/storage/keys.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (16)
  • apps/api/src/user/routes/index.ts
  • apps/deploy-web/src/services/storage/keys.ts
  • apps/api/src/routers/userRouter.ts
  • apps/deploy-web/src/components/layout/AccountMenu.tsx
  • apps/api/src/core/repositories/base.repository.ts
  • apps/api/src/user/routes/register-user/register-user.router.ts
  • apps/api/src/user/routes/get-current-user/get-current-user.router.ts
  • apps/deploy-web/src/services/app-di-container/app-di-container.ts
  • apps/api/src/user/schemas/user.schema.ts
  • apps/api/src/auth/services/auth.interceptor.ts
  • apps/api/src/app.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.tsx
  • apps/api/src/auth/services/auth.interceptor.spec.ts
  • apps/api/src/user/services/user/user.service.ts
  • apps/api/test/services/wallet-testing.service.ts
  • apps/deploy-web/src/components/onboarding/OnboardingContainer/OnboardingContainer.spec.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

Never use type any or cast to type any. Always define the proper TypeScript types.

Files:

  • apps/deploy-web/src/services/auth/auth.service.ts
  • apps/api/src/user/controllers/user/user.controller.ts
  • apps/api/src/user/repositories/user/user.repository.ts
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts
  • apps/api/src/user/services/user/user.service.spec.ts
**/*.{js,ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/general.mdc)

**/*.{js,ts,tsx}: Never use deprecated methods from libraries.
Don't add unnecessary comments to the code

Files:

  • apps/deploy-web/src/services/auth/auth.service.ts
  • apps/api/src/user/controllers/user/user.controller.ts
  • apps/api/src/user/repositories/user/user.repository.ts
  • apps/deploy-web/src/pages/api/auth/[...auth0].ts
  • apps/api/src/user/services/user/user.service.spec.ts
**/*.spec.{ts,tsx}

📄 CodeRabbit Inference Engine (.cursor/rules/no-jest-mock.mdc)

Don't use jest.mock() to mock dependencies in test files. Instead, use jest-mock-extended to create mocks and pass mocks as dependencies to the service under test.

**/*.spec.{ts,tsx}: Use setup function instead of beforeEach in test files
setup function must be at the bottom of the root describe block in test files
setup function creates an object under test and returns it
setup function should accept a single parameter with inline type definition
Don't use shared state in setup function
Don't specify return type of setup function

Files:

  • apps/api/src/user/services/user/user.service.spec.ts
🧠 Learnings (5)
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : `setup` function should accept a single parameter with inline type definition

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Use `setup` function instead of `beforeEach` in test files

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : `setup` function creates an object under test and returns it

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't specify return type of `setup` function

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
📚 Learning: 2025-07-21T08:25:07.474Z
Learnt from: CR
PR: akash-network/console#0
File: .cursor/rules/setup-instead-of-before-each.mdc:0-0
Timestamp: 2025-07-21T08:25:07.474Z
Learning: Applies to **/*.spec.{ts,tsx} : Don't use shared state in `setup` function

Applied to files:

  • apps/api/src/user/services/user/user.service.spec.ts
🧬 Code Graph Analysis (5)
apps/deploy-web/src/services/auth/auth.service.ts (2)
apps/deploy-web/src/config/auth.config.ts (1)
  • ANONYMOUS_USER_TOKEN_KEY (2-2)
apps/deploy-web/src/services/storage/keys.ts (1)
  • ONBOARDING_STEP_KEY (1-1)
apps/api/src/user/controllers/user/user.controller.ts (6)
apps/api/src/user/routes/get-anonymous-user/get-anonymous-user.router.ts (1)
  • GetUserParams (11-11)
apps/api/src/user/schemas/user.schema.ts (3)
  • UserSchema (24-36)
  • UserSchema (38-38)
  • GetUserResponseOutput (22-22)
apps/api/src/user/routes/register-user/register-user.router.ts (2)
  • RegisterUserInput (15-15)
  • RegisterUserResponse (20-20)
apps/api/src/user/services/user/user.service.ts (1)
  • RegisterUserInput (139-149)
apps/notifications/src/interfaces/rest/services/auth/auth.service.ts (1)
  • userId (18-25)
apps/api/src/auth/services/auth.service.ts (1)
  • Protected (39-55)
apps/api/src/user/repositories/user/user.repository.ts (1)
apps/api/src/user/model-schemas/user/user.schema.ts (1)
  • userAgentMaxLength (6-6)
apps/deploy-web/src/pages/api/auth/[...auth0].ts (2)
apps/deploy-web/src/services/app-di-container/server-di-container.service.ts (1)
  • services (22-32)
apps/deploy-web/src/types/user.ts (1)
  • UserSettings (5-17)
apps/api/src/user/services/user/user.service.spec.ts (3)
apps/api/src/user/routes/register-user/register-user.router.ts (1)
  • RegisterUserInput (15-15)
apps/api/src/user/services/user/user.service.ts (1)
  • RegisterUserInput (139-149)
apps/deploy-web/src/services/analytics/analytics.service.ts (2)
  • analyticsService (274-291)
  • AnalyticsService (141-266)
🪛 ast-grep (0.38.6)
apps/api/src/user/services/user/user.service.spec.ts

[warning] 155-155: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)


[warning] 157-157: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^${conflictingUsername})
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: validate / validate-app
  • GitHub Check: test-build
🔇 Additional comments (6)
apps/deploy-web/src/services/auth/auth.service.ts (1)

23-26: Correct logout order and SSR guard

Clearing storage before navigation and guarding for non-browser environments is the right sequence.

apps/api/src/user/repositories/user/user.repository.ts (2)

80-88: Confirm whether NULL lastActiveAt should be treated as stale.

paginateStaleAnonymousUsers excludes NULL lastActiveAt rows; if “never active” anonymous users should be considered stale, include isNull(lastActiveAt) in the filter.

Proposed adjustment (if desired):

-      const clauses = [isNull(this.table.userId), lte(this.table.lastActiveAt, subDays(new Date(), inactivityInDays))];
+      const clauses = [
+        isNull(this.table.userId),
+        or(isNull(this.table.lastActiveAt), lte(this.table.lastActiveAt, subDays(new Date(), inactivityInDays)))
+      ];

Also add or to the drizzle import if not already present.


128-134: LGTM: input normalization bounds user agent length.

Truncating lastUserAgent to userAgentMaxLength is correct and minimizes DB/storage issues.

apps/deploy-web/src/pages/api/auth/[...auth0].ts (1)

53-67: Nice integration of register-user and user/me flows

Registering on callback and merging user settings, then refetching and merging on profile, is cohesive and aligns with the new controller endpoints.

Also applies to: 103-111

apps/api/src/user/controllers/user/user.controller.ts (2)

7-7: LGTM: Importing UserAuthTokenService is appropriate

This aligns with the new flow where user tokens are validated at the controller level. No issues here.


26-29: LGTM: DI additions look correct

Injecting ExecutionContextService, UserService, and UserAuthTokenService is consistent with the new responsibilities of the controller. No issues detected.

@stalniy stalniy merged commit b16573b into main Aug 14, 2025
77 of 80 checks passed
@stalniy stalniy deleted the refactor/user-register branch August 14, 2025 02:18
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.

2 participants

Comments