Skip to content

feat: admin app#2665

Closed
baktun14 wants to merge 6 commits intomainfrom
features/admin-app
Closed

feat: admin app#2665
baktun14 wants to merge 6 commits intomainfrom
features/admin-app

Conversation

@baktun14
Copy link
Contributor

@baktun14 baktun14 commented Feb 3, 2026

Summary by CodeRabbit

  • New Features
    • Launched Admin Dashboard: user management with paginated listing, search, and detail views
    • Added analytics: user statistics (total, new 7d/30d, active 30d) shown in dashboard cards
    • Enforced admin authentication and domain/whitelist access controls for admin UI and API
    • New admin UI: responsive layout, sidebar navigation, theme toggle, and unauthorized/access-denied page

@baktun14 baktun14 requested a review from a team as a code owner February 3, 2026 23:56
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Adds two new Admin applications: an Admin API (Hono + TypeScript, Postgres/Drizzle, Auth0 admin auth, user management/analytics endpoints) and an Admin Web (Next.js dashboard, Auth0 integration, React Query client). Also adds CI release workflows, Helm values, Docker build services, env configs, and DI/config providers.

Changes

Cohort / File(s) Summary
Workflows
\.github/workflows/admin-api-release.yml, \.github/workflows/admin-web-release.yml, \.github/workflows/reusable-build-image.yml, \.github/workflows/reusable-deploy-k8s.yml
New per-app release/build/deploy workflows for admin-api and admin-web; reusable build/deploy updated to recognise admin-api/* and admin-web/* tag patterns and new app options.
Helm values
\.helm/admin-api-prod-values.yaml, \.helm/admin-api-staging-values.yaml, \.helm/admin-web-prod-values.yaml, \.helm/admin-web-staging-values.yaml
Added staging/production values files (deploymentEnv, hostName) for admin-api and admin-web.
Admin API: source & build
apps/admin-api/package.json, apps/admin-api/tsconfig*.json, apps/admin-api/tsup.config.ts, apps/admin-api/src/**
New Hono REST app: server bootstrap, routes (health, users list/search, analytics), OpenAPI wiring, DI providers, tsup/tsconfig, and server startup.
Admin API: DB & repo
apps/admin-api/src/db/schema.ts, apps/admin-api/src/db/dbConnection.ts, apps/admin-api/src/user/repositories/admin-user.repository.ts
Added Drizzle schema (Users, UserWallets, StripeTransactions), DB connection providers, and AdminUserRepository with list/search/get/stats queries.
Admin API: auth & config
apps/admin-api/src/auth/**, apps/admin-api/src/core/config/env.config.ts, apps/admin-api/src/core/providers/*, apps/admin-api/env/*, apps/admin-api/.env*
AdminAuthInterceptor implementing JWKS-verified JWT auth + domain/whitelist checks; Zod env schema and DI-registered config/raw-app-config/logging providers; many env files for environments.
Admin Web: source & build
apps/admin-web/package.json, apps/admin-web/tsconfig*.json, apps/admin-web/next.config.js, apps/admin-web/tsconfig.build.json
New Next.js app with build config, Next settings, PostCSS/Tailwind setup and env-schema build step.
Admin Web: UI components & pages
apps/admin-web/src/components/..., apps/admin-web/src/pages/*, apps/admin-web/src/styles/index.css
New AdminLayout, Sidebar, Header, ModeToggle, UserSearch, UserTable, pages for users/login/unauthorized/index, global providers and Document/_app.
Admin Web: client data & API proxy
apps/admin-web/src/services/admin-api.service.ts, apps/admin-web/src/queries/*, apps/admin-web/src/pages/api/proxy/[...path].ts, apps/admin-web/src/pages/api/auth/[...auth0].ts
Client-side adminApiService, React Query hooks for users/analytics, authenticated Next API proxy to admin API, and Auth0 auth handlers with domain gating.
Types & schemas
apps/admin-web/src/types/user.ts, apps/admin-web/src/config/*, apps/admin-api/src/core/config/env.config.ts
Type definitions and Zod schemas for browser/server/admin configs and env validation.
Docker & compose
packages/docker/docker-compose.build.yml
Added build services for admin-api and admin-web with workspace-specific build args and targets.
Monorepo scripts & ignores
package.json, apps/admin-web/.gitignore, apps/admin-web/.eslintignore
Added dev scripts for running admin apps and ignore rules for generated env schema and build artifacts.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser as Admin Web (Browser)
    participant NextApp as Next.js App (SSR)
    participant Auth0 as Auth0
    participant Cookie as Session Cookie

    User->>Browser: Visit /login
    Browser->>NextApp: GET /login
    NextApp->>Auth0: Redirect to Auth0 (audience, scopes)
    Auth0->>User: Show login UI
    User->>Auth0: Submit credentials
    Auth0->>NextApp: Callback (tokens)
    NextApp->>Cookie: Store session
    NextApp->>Browser: Redirect to /users
    Browser->>NextApp: GET /users (with cookie)
    NextApp->>NextApp: Validate session & render dashboard
Loading
sequenceDiagram
    participant Browser as Admin Web (Client)
    participant Proxy as Next.js Proxy /api/proxy/*
    participant AdminAPI as Admin API (Hono)
    participant JWKS as Auth0 JWKS
    participant DB as PostgreSQL

    Browser->>Proxy: GET /api/proxy/v1/admin/users?q=...
    Proxy->>Proxy: Read access token from session
    Proxy->>AdminAPI: GET /v1/admin/users/search (Authorization: Bearer)
    AdminAPI->>JWKS: Fetch signing key (cached)
    JWKS->>AdminAPI: Return key
    AdminAPI->>AdminAPI: Verify JWT, enforce whitelist/domain
    AdminAPI->>DB: Query users + wallets
    DB->>AdminAPI: Return rows
    AdminAPI->>Proxy: Return JSON response
    Proxy->>Browser: Return response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

Suggested reviewers

  • stalniy
  • ygrishajev

Poem

🐇 I hopped through code with nimble paws,

Added dashboards, routes, and auth applause.
JWKS and Postgres snug in rows,
Web and API where the admin goes.
Cheers — two apps sprout where the console grows! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'feat: admin app' is overly vague and does not clearly specify what the admin application includes or its primary purpose. Provide a more descriptive title that specifies key components, such as 'feat: add admin API and web dashboard with user management and analytics' or 'feat: implement admin panel with API backend and web UI'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch features/admin-app

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

@@ -33,6 +33,163 @@
"npm": "^11.6.2"
Copy link
Contributor

@github-actions github-actions bot Feb 3, 2026

Choose a reason for hiding this comment

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

🔄 Carefully review the package-lock.json diff

Resolve the comment if everything is ok

* apps/api                                                                                 3.27.2 -> 3.26.2
* apps/deploy-web                                                                          3.25.7 -> 3.25.3
* apps/indexer                                                                             1.24.4 -> 1.24.3
* apps/log-collector                                                                       2.12.5 -> 2.12.4
* apps/notifications                                                                       2.13.5 -> 2.13.4
* apps/provider-console                                                                    1.35.7 -> 1.35.6
* apps/provider-proxy                                                                      2.9.8 -> 2.9.7
* apps/stats-web                                                                           1.13.7 -> 1.13.6
* apps/tx-signer                                                                           1.0.5 -> 1.0.4
* node_modules/autoprefixer                                                                10.4.19 -> 10.4.23
* node_modules/browserslist                                                                4.24.4 -> 4.28.1
* node_modules/csstype                                                                     3.1.3 -> 3.2.3
* node_modules/electron-to-chromium                                                        1.5.119 -> 1.5.267
* node_modules/fast-glob                                                                   3.3.2 -> 3.3.3
* node_modules/fraction.js                                                                 4.3.7 -> 5.3.4
* node_modules/jiti                                                                        1.21.3 -> 1.21.7
* node_modules/lilconfig                                                                   2.1.0 -> 3.1.3
* node_modules/node-releases                                                               2.0.19 -> 2.0.27
* node_modules/postcss-nested/node_modules/postcss-selector-parser                         6.1.0 -> 6.1.2
* node_modules/postcss-nested                                                              6.0.1 -> 6.2.0
* node_modules/postcss                                                                     8.4.38 -> 8.5.6
* node_modules/source-map-js                                                               1.2.0 -> 1.2.1
* node_modules/tailwindcss/node_modules/postcss-selector-parser                            6.1.0 -> 6.1.2
* node_modules/tailwindcss                                                                 3.4.4 -> 3.4.19
* node_modules/update-browserslist-db                                                      1.1.3 -> 1.2.3
+ apps/admin-api/node_modules/lru-cache                                                    11.2.4  
+ apps/admin-api                                                                           1.0.0   
+ apps/admin-web/node_modules/@types/react-dom                                             18.3.7  
+ apps/admin-web/node_modules/@types/react                                                 18.3.27 
+ apps/admin-web/node_modules/lucide-react                                                 0.323.0 
+ apps/admin-web/node_modules/next-themes                                                  0.3.0   
+ apps/admin-web/node_modules/react-error-boundary                                         4.1.2   
+ apps/admin-web/node_modules/usehooks-ts                                                  3.1.1   
+ apps/admin-web                                                                           1.0.0   
+ node_modules/@akashnetwork/admin-api                                                     link:apps/admin-api
+ node_modules/@akashnetwork/admin-web                                                     link:apps/admin-web
+ node_modules/baseline-browser-mapping                                                    2.9.15  
- node_modules/normalize-range                                                             0.1.2   
- node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig         3.1.2   
- node_modules/tsup/node_modules/lilconfig                                                 3.1.3   

Comment on lines +12 to +18
name: Create Release
uses: ./.github/workflows/reusable-create-github-release.yml
secrets: inherit
with:
app: admin-api

build-beta:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Comment on lines +32 to +42
needs: build-beta
name: Deploy to beta
if: needs.build-beta.outputs.base_image_tag != ''
uses: ./.github/workflows/reusable-deploy-k8s.yml
secrets: inherit
with:
app: admin-api
appVersion: ${{ needs.build-beta.outputs.base_image_tag }}
environment: staging

build-prod:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Comment on lines +12 to +18
name: Create Release
uses: ./.github/workflows/reusable-create-github-release.yml
secrets: inherit
with:
app: admin-web

build-beta:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Comment on lines +32 to +42
needs: build-beta
name: Deploy to beta
if: needs.build-beta.outputs.base_image_tag != ''
uses: ./.github/workflows/reusable-deploy-k8s.yml
secrets: inherit
with:
app: admin-web
appVersion: ${{ needs.build-beta.outputs.base_image_tag }}
environment: staging

build-prod:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
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: 16

🤖 Fix all issues with AI agents
In @.github/workflows/admin-api-release.yml:
- Around line 11-17: Add explicit minimal GITHUB_TOKEN permissions at the
workflow level and grant stronger scopes only on jobs that need them: declare a
top-level permissions block with least privilege (e.g., id-token: write only if
OIDC is used, contents: read, packages: read) and then override in the reusable
release job "release" (which calls reusable-create-github-release.yml) to grant
the write scopes required for creating releases (e.g., contents: write and
packages: write) only for that job; apply the same pattern to other affected
jobs referenced in the workflow (the deploy/release jobs around the same
sections) so each job has just the permissions it needs.

In @.github/workflows/admin-web-release.yml:
- Around line 31-40: The deploy-beta job (job id deploy-beta, which uses
./.github/workflows/reusable-deploy-k8s.yml and depends on build-beta) is
missing an explicit permissions block; add a permissions section to the
deploy-beta job that mirrors the explicit permissions used in the release job
(e.g., the same keys and values for id-token, contents, packages, and secrets as
appropriate for deployment) so the reusable-deploy-k8s.yml run has explicit
GitHub Actions permissions rather than relying on defaults.

In `@apps/admin-api/env/.env`:
- Around line 1-3: Reorder the env variables to satisfy dotenv-linter by moving
LOG_LEVEL so the keys are alphabetically ordered; specifically, update the block
containing CORS_WEBSITE_URLS, WEBSITE_URL, and LOG_LEVEL so LOG_LEVEL appears
between CORS_WEBSITE_URLS and WEBSITE_URL (references: CORS_WEBSITE_URLS,
LOG_LEVEL, WEBSITE_URL).

In `@apps/admin-api/env/.env.sample`:
- Around line 9-10: The dotenv sample has keys out of the expected
alphabetical/group order causing dotenv-linter UnorderedKey warnings; reorder
the keys in apps/admin-api/env/.env.sample so that PORT and CORS_WEBSITE_URLS
(and the other entries around lines 42–44) follow the repository's canonical key
ordering while preserving the current section grouping—adjust entries named PORT
and CORS_WEBSITE_URLS accordingly to silence UnorderedKey.

In `@apps/admin-api/env/.env.unit.test`:
- Around line 1-12: The .env unit-test file has dotenv-linter UnorderedKey
warnings; reorder the environment keys (e.g., POSTGRES_DB_URI, PORT,
CORS_WEBSITE_URLS, AUTH0_JWKS_URI, AUTH0_ISSUER, AUTH0_AUDIENCE,
ADMIN_ALLOWED_DOMAINS, ADMIN_WHITELIST_EMAILS, LOG_LEVEL, STD_OUT_LOG_FORMAT,
NODE_ENV, DEPLOYMENT_ENV) into the canonical order required by our dotenv-linter
(typically alphabetical) so keys are consistently sorted and the linter warnings
disappear; ensure no duplicate keys and preserve values while changing only the
key order.

In `@apps/admin-api/src/auth/services/admin-auth.interceptor.ts`:
- Around line 106-112: The jwt.verify call in admin-auth.interceptor.ts (used in
the AdminAuthInterceptor) must explicitly restrict allowed algorithms to RS256
to prevent downgrade attacks; update the jwt.verify invocation that takes token,
signingKey and the options object (which currently has issuer and audience) to
include an algorithms array set to ['RS256'] so verification only accepts
RS256-signed tokens.

In `@apps/admin-api/src/rest-app.ts`:
- Around line 47-49: The array openApiRouters currently uses an explicit any
type and an eslint-disable; remove the eslint-disable and change the declaration
to use a non-generic OpenAPIHono[] type: declare openApiRouters as OpenAPIHono[]
= [listUsersRouter, searchUsersRouter, userStatsRouter] so the array matches the
routers' instantiated (non-generic) types without using any.

In `@apps/admin-api/src/user/repositories/admin-user.repository.ts`:
- Around line 41-43: listUsers allows page and pageSize to be <= 0 or unbounded
which can produce negative offsets or heavy queries; add input validation at the
start of listUsers (and mirror the same guard in searchUsers) to clamp or reject
values: ensure page is at least 1, enforce pageSize between 1 and a configured
MAX_PAGE_SIZE (e.g. 100), recompute offset = (page - 1) * pageSize after
clamping, and throw or default when inputs are invalid so the DB query never
receives negative limits or an excessive pageSize.
- Line 108: Remove the redundant inline comment "// Count matching users" from
the admin user repository code; locate the comment string in AdminUserRepository
(around the count operation or the method that performs user counting) and
delete it so the code is self-explanatory without the noisy restatement.

In `@apps/admin-web/src/components/users/UserTable.tsx`:
- Around line 83-97: When total is 0 the range and page text are incorrect;
update the rendering in UserTable (the JSX that uses page, pageSize, total,
totalPages) to guard against zero results: compute and display "Showing 0 to 0
of 0 users" (or similar) when total === 0, and show "Page 0 of 0" or "Page 0 of
0" consistently instead of "1 to 0" / "Page 1 of 0"; adjust the expressions
around (page - 1) * pageSize + 1, Math.min(page * pageSize, total), and the Page
{page} of {totalPages} span to conditionally render the zero-state values when
total === 0 (or compute safe derived values like start = total === 0 ? 0 : (page
- 1) * pageSize + 1 and end = total === 0 ? 0 : Math.min(page * pageSize,
total)).

In `@apps/admin-web/src/pages/_app.tsx`:
- Around line 14-36: The QueryClient is created at module scope (queryClient)
which can leak cache across SSR requests; move its instantiation into the App
component so each request gets a fresh QueryClient. Inside the App component
(function App) create the QueryClient per-render using a stable initializer
(e.g., useState or useRef with new QueryClient({ defaultOptions: { queries: {
refetchOnWindowFocus: false }}})) and pass that instance to QueryClientProvider
instead of the module-scoped queryClient; keep the same defaultOptions and
ensure creation only runs once per component instance.

In `@apps/admin-web/src/pages/api/proxy/`[...path].ts:
- Around line 68-72: The fetch call to targetUrl using method, headers, and body
can hang indefinitely; wrap the request with an AbortController, create a
timeout (e.g., 10s or configurable) that calls controller.abort(), pass
controller.signal to fetch, and clear the timeout on success; also handle the
abort/timeout error (detect AbortError) and return a 504/timeout response from
the proxy handler so the server doesn't hang. Ensure changes are applied around
the existing fetch invocation and use the same variables (targetUrl, method,
headers, body) and the controller.signal when calling fetch.
- Around line 95-97: The catch block that currently returns
res.status(502).json({ error: "Proxy error" }) swallows the exception; update it
to accept the error (catch (err)) and log the error via LoggerService before
returning the 502 response. Use the project LoggerService instance (or
import/create one if missing) to log the error with context (e.g., "Proxy error
in [...path] handler") and include the error object/details so debugging is
possible, then keep the existing res.status(502).json response.
- Around line 30-35: The catch block that wraps getAccessToken currently
swallows errors; update it to catch the error as a variable and log it via the
project's LoggerService before returning the 401. Specifically, modify the
try/catch around getAccessToken so the catch(e) calls LoggerService.error
(include context like "Unable to get access token" and the error object) and
then returns res.status(401).json({ error: "Unable to get access token" });;
ensure you import or instantiate the same LoggerService used elsewhere in the
API code so the log integrates with existing logging.

In `@apps/admin-web/src/pages/login/index.tsx`:
- Around line 3-6: The getServerSideProps implementation uses ctx.query.returnTo
without validating it, which can allow array coercion or open redirects; update
the logic that builds destination so it normalizes and validates returnTo: if
ctx.query.returnTo is an array, pick a single value (e.g., first) or treat as
undefined, then ensure the value is a relative internal path (starts with '/'
and not '//' and contains no scheme like "http://" or "https://"); reject or
fall back to the safe default if validation fails, then encodeURIComponent that
validated string when composing destination. Reference symbols:
getServerSideProps, ctx.query.returnTo, and destination (or reuse an existing
utility like getValidInternalReturnToUrl if available).

In `@apps/admin-web/src/types/user.ts`:
- Around line 28-32: The frontend UserStats interface is missing the
newUsersLast30Days field expected by the admin API; update the UserStats
interface (export interface UserStats) to include newUsersLast30Days: number so
the type matches the backend contract (or explicitly confirm and document if the
backend payload intentionally omits it). Ensure any usage of UserStats (e.g.,
components or API parsing) handles the added field.
🧹 Nitpick comments (15)
.helm/admin-api-staging-values.yaml (1)

1-3: Naming inconsistency: staging vs beta vs sandbox.

The deploymentEnv is "staging" but hostName uses "beta". Combined with .env.staging pointing to a "sandbox" URL, there appear to be three different terms for what might be the same environment tier. Consider standardizing the terminology across configuration files to reduce confusion.

apps/admin-api/env/.env.testnet (1)

1-2: Add trailing newline.

Missing trailing newline at end of file. While minor, this ensures consistent behavior across tools and version control systems.

Proposed fix
 RPC_NODE_ENDPOINT=https://testnetrpc.akashnet.net:443
 DEPLOYMENT_GRANT_DENOM=uakt
+
package.json (1)

8-10: Consider using turbo for consistent parallel execution.

The admin:dev script uses shell & for background process which:

  1. Doesn't work on Windows (cross-platform issue)
  2. Won't properly terminate child processes when the parent exits
  3. Is inconsistent with other scripts like console:dev that use turbo
Proposed fix using turbo for consistency
-    "admin:dev": "npm run dev -w apps/admin-web & npm run dev -w apps/admin-api",
-    "admin:dev:api": "npm run dev -w apps/admin-api",
-    "admin:dev:web": "npm run dev -w apps/admin-web",
+    "admin:dev": "turbo dev --filter=\"./apps/admin-web\" --filter=\"./apps/admin-api\"",
+    "admin:dev:api": "turbo dev --filter=\"./apps/admin-api\"",
+    "admin:dev:web": "turbo dev --filter=\"./apps/admin-web\"",
apps/admin-web/tailwind.config.ts (1)

1-3: Avoid using any type per coding guidelines.

The explicit declare const require: any violates the project's TypeScript guidelines. Consider adding proper type declarations for the UI library's Tailwind export or using a more specific type.

♻️ Suggested improvement
-// eslint-disable-next-line `@typescript-eslint/no-var-requires`, `@typescript-eslint/no-explicit-any`
-declare const require: any;
-export default require("@akashnetwork/ui/tailwind")("admin-web");
+// eslint-disable-next-line `@typescript-eslint/no-var-requires`
+declare const require: (id: string) => (appName: string) => Record<string, unknown>;
+export default require("@akashnetwork/ui/tailwind")("admin-web");

Alternatively, if the @akashnetwork/ui package exports proper types, consider using ES module import syntax with type assertions.

As per coding guidelines: "Never use type any or cast to type any. Always define the proper TypeScript types."

apps/admin-web/src/components/layout/Sidebar.tsx (1)

36-38: Consider more precise route matching for isActive detection.

Using pathname.startsWith(item.href) could cause false positives if future routes share prefixes (e.g., /users-admin would incorrectly match /users). Consider using exact match or segment-based comparison.

♻️ Optional: More precise route matching
 {navItems.map(item => {
-  const isActive = router.pathname.startsWith(item.href);
+  const isActive = router.pathname === item.href || router.pathname.startsWith(`${item.href}/`);
   const Icon = item.icon;
apps/admin-web/src/components/layout/AdminLayout.tsx (1)

58-58: Redundant cn() usage with single static class string.

The cn utility is designed for merging conditional classes. Using it with a single static string provides no benefit.

♻️ Simplify className
-              <div className={cn("container p-6")}>{children}</div>
+              <div className="container p-6">{children}</div>

If cn is no longer needed elsewhere, you can also remove the import.

apps/admin-api/src/analytics/routes/user-stats.router.ts (1)

15-31: Consider adding error responses to OpenAPI definition.

The route definition lacks error response schemas (e.g., 401 Unauthorized, 403 Forbidden, 500 Internal Server Error). This reduces API documentation completeness and client code generation quality.

📝 Suggested addition for error responses
   responses: {
       description: "User statistics",
       content: {
         "application/json": {
           schema: userStatsResponseSchema
         }
       }
-    }
+    },
+    401: {
+      description: "Unauthorized - Invalid or missing authentication"
+    },
+    500: {
+      description: "Internal server error"
+    }
   }
apps/admin-api/src/user/routes/list-users/list-users.router.ts (2)

58-58: Add radix parameter to parseInt calls.

Using parseInt without an explicit radix can lead to unexpected behavior if the input starts with 0 (octal interpretation in some environments). Always specify the radix for clarity and safety.

Proposed fix
-  const result = await repository.listUsers(parseInt(page), parseInt(pageSize));
+  const result = await repository.listUsers(parseInt(page, 10), parseInt(pageSize, 10));

6-19: Consider extracting shared userSchema to avoid duplication.

The userSchema definition is duplicated between this file and search-users.router.ts. Extracting it to a shared location (e.g., @src/user/schemas/user.schema.ts) would reduce duplication and ensure consistency.

apps/admin-api/src/core/providers/raw-app-config.provider.ts (1)

3-7: Type includes number but process.env only contains strings.

The RawAppConfig type allows number values, but process.env only provides string | undefined. While this won't cause runtime issues, the type could be more accurate.

Proposed fix
-export type RawAppConfig = Record<string, string | number | undefined>;
+export type RawAppConfig = Record<string, string | undefined>;
apps/admin-api/src/user/routes/search-users/search-users.router.ts (1)

59-59: Add radix parameter to parseInt calls.

Same as the list-users router - specify radix 10 for clarity and safety.

Proposed fix
-  const result = await repository.searchUsers(q, parseInt(page), parseInt(pageSize));
+  const result = await repository.searchUsers(q, parseInt(page, 10), parseInt(pageSize, 10));
apps/admin-api/src/db/dbConnection.ts (1)

29-35: Consider adding error handling for connection failures.

The connectToDatabase function will throw if the connection fails, but adding explicit error handling would provide clearer error messages and allow for retry logic or graceful degradation.

Proposed improvement
 export async function connectToDatabase(): Promise<void> {
   const client = container.resolve(APP_PG_CLIENT);
   logger.info("Connecting to database...");
-  // Test the connection by running a simple query
-  await client.unsafe("SELECT 1");
-  logger.info("Database connection established.");
+  try {
+    await client.unsafe("SELECT 1");
+    logger.info("Database connection established.");
+  } catch (error) {
+    logger.error("Failed to connect to database", error);
+    throw error;
+  }
 }
apps/admin-web/src/components/users/UserSearch.tsx (1)

12-18: Consider adding an aria-label for accessibility.

The input relies on the placeholder for context, but screen readers benefit from an explicit aria-label when there's no visible label element.

Proposed fix
-      <Input type="text" placeholder={placeholder} value={value} onChange={e => onChange(e.target.value)} className="pl-10" />
+      <Input type="text" placeholder={placeholder} value={value} onChange={e => onChange(e.target.value)} className="pl-10" aria-label={placeholder} />
apps/admin-web/src/pages/users/index.tsx (1)

22-31: Consider skipping the inactive query to avoid duplicate fetches.

Both useUsersQuery and useSearchUsersQuery mount every render; if they fetch immediately, this doubles traffic. Consider adding an enabled/skip option (or gating inside the hooks) so only the active query runs.

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

154-165: Prefer DB-side time windows for stats.

Line 155-164 uses app‑server Date values; DB now() keeps stats consistent if server clock drifts or DB timezone differs. Consider computing the windows in SQL.

⏱️ DB-side window example
-    const now = new Date();
-    const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString();
-    const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString();
-
     const [stats] = await this.db
       .select({
         totalUsers: count(),
-        newUsersLast7Days: count(sql`CASE WHEN ${schema.Users.createdAt} > ${sevenDaysAgo} THEN 1 END`),
-        newUsersLast30Days: count(sql`CASE WHEN ${schema.Users.createdAt} > ${thirtyDaysAgo} THEN 1 END`),
-        activeUsersLast30Days: count(sql`CASE WHEN ${schema.Users.lastActiveAt} > ${thirtyDaysAgo} THEN 1 END`)
+        newUsersLast7Days: count(sql`CASE WHEN ${schema.Users.createdAt} > now() - interval '7 days' THEN 1 END`),
+        newUsersLast30Days: count(sql`CASE WHEN ${schema.Users.createdAt} > now() - interval '30 days' THEN 1 END`),
+        activeUsersLast30Days: count(sql`CASE WHEN ${schema.Users.lastActiveAt} > now() - interval '30 days' THEN 1 END`)
       })
       .from(schema.Users);

Comment on lines +11 to +17
release:
name: Create Release
uses: ./.github/workflows/reusable-create-github-release.yml
secrets: inherit
with:
app: admin-api

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 | 🟠 Major

Scope GITHUB_TOKEN permissions for release/deploy jobs.

CodeQL flags missing permissions blocks; add explicit minimal permissions at workflow level and override where write access is required. Adjust deploy job scopes to match the reusable workflow’s needs (e.g., add id-token: write only if required).

Suggested fix
 name: Release Admin API

+permissions:
+  contents: read
+
 jobs:
   release:
+    permissions:
+      contents: write
     name: Create Release
     uses: ./.github/workflows/reusable-create-github-release.yml
     secrets: inherit
     with:
       app: admin-api

Also applies to: 31-41, 55-68

🤖 Prompt for AI Agents
In @.github/workflows/admin-api-release.yml around lines 11 - 17, Add explicit
minimal GITHUB_TOKEN permissions at the workflow level and grant stronger scopes
only on jobs that need them: declare a top-level permissions block with least
privilege (e.g., id-token: write only if OIDC is used, contents: read, packages:
read) and then override in the reusable release job "release" (which calls
reusable-create-github-release.yml) to grant the write scopes required for
creating releases (e.g., contents: write and packages: write) only for that job;
apply the same pattern to other affected jobs referenced in the workflow (the
deploy/release jobs around the same sections) so each job has just the
permissions it needs.

Comment on lines +31 to +40
deploy-beta:
needs: build-beta
name: Deploy to beta
if: needs.build-beta.outputs.base_image_tag != ''
uses: ./.github/workflows/reusable-deploy-k8s.yml
secrets: inherit
with:
app: admin-web
appVersion: ${{ needs.build-beta.outputs.base_image_tag }}
environment: staging
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 | 🟡 Minor

Add explicit permissions block to the deploy-beta job.

Similar to the release job, this job should define explicit permissions per the CodeQL warning.

Proposed fix
   deploy-beta:
     needs: build-beta
     name: Deploy to beta
     if: needs.build-beta.outputs.base_image_tag != ''
     uses: ./.github/workflows/reusable-deploy-k8s.yml
     secrets: inherit
+    permissions:
+      contents: read
+      pull-requests: read
     with:
       app: admin-web
       appVersion: ${{ needs.build-beta.outputs.base_image_tag }}
       environment: staging
📝 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
deploy-beta:
needs: build-beta
name: Deploy to beta
if: needs.build-beta.outputs.base_image_tag != ''
uses: ./.github/workflows/reusable-deploy-k8s.yml
secrets: inherit
with:
app: admin-web
appVersion: ${{ needs.build-beta.outputs.base_image_tag }}
environment: staging
deploy-beta:
needs: build-beta
name: Deploy to beta
if: needs.build-beta.outputs.base_image_tag != ''
uses: ./.github/workflows/reusable-deploy-k8s.yml
secrets: inherit
permissions:
contents: read
pull-requests: read
with:
app: admin-web
appVersion: ${{ needs.build-beta.outputs.base_image_tag }}
environment: staging
🤖 Prompt for AI Agents
In @.github/workflows/admin-web-release.yml around lines 31 - 40, The
deploy-beta job (job id deploy-beta, which uses
./.github/workflows/reusable-deploy-k8s.yml and depends on build-beta) is
missing an explicit permissions block; add a permissions section to the
deploy-beta job that mirrors the explicit permissions used in the release job
(e.g., the same keys and values for id-token, contents, packages, and secrets as
appropriate for deployment) so the reusable-deploy-k8s.yml run has explicit
GitHub Actions permissions rather than relying on defaults.

Comment on lines +1 to +3
CORS_WEBSITE_URLS=https://console-admin.akash.network
WEBSITE_URL=https://console-admin.akash.network
LOG_LEVEL=debug
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 | 🟡 Minor

Reorder LOG_LEVEL to satisfy dotenv-linter.

🛠️ Suggested update
 CORS_WEBSITE_URLS=https://console-admin.akash.network
-WEBSITE_URL=https://console-admin.akash.network
 LOG_LEVEL=debug
+WEBSITE_URL=https://console-admin.akash.network
📝 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
CORS_WEBSITE_URLS=https://console-admin.akash.network
WEBSITE_URL=https://console-admin.akash.network
LOG_LEVEL=debug
CORS_WEBSITE_URLS=https://console-admin.akash.network
LOG_LEVEL=debug
WEBSITE_URL=https://console-admin.akash.network
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 3-3: [UnorderedKey] The LOG_LEVEL key should go before the WEBSITE_URL key

(UnorderedKey)

🤖 Prompt for AI Agents
In `@apps/admin-api/env/.env` around lines 1 - 3, Reorder the env variables to
satisfy dotenv-linter by moving LOG_LEVEL so the keys are alphabetically
ordered; specifically, update the block containing CORS_WEBSITE_URLS,
WEBSITE_URL, and LOG_LEVEL so LOG_LEVEL appears between CORS_WEBSITE_URLS and
WEBSITE_URL (references: CORS_WEBSITE_URLS, LOG_LEVEL, WEBSITE_URL).

Comment on lines +9 to +10
PORT=3010
CORS_WEBSITE_URLS=http://localhost:3011
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 | 🟡 Minor

Reorder keys to satisfy dotenv-linter warnings.

This keeps section grouping intact while silencing UnorderedKey.

♻️ Proposed tweak
-PORT=3010
-CORS_WEBSITE_URLS=http://localhost:3011
+CORS_WEBSITE_URLS=http://localhost:3011
+PORT=3010
@@
-LOG_LEVEL=info
-STD_OUT_LOG_FORMAT=pretty
-NODE_ENV=development
+LOG_LEVEL=info
+NODE_ENV=development
+STD_OUT_LOG_FORMAT=pretty

Also applies to: 42-44

🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 10-10: [UnorderedKey] The CORS_WEBSITE_URLS key should go before the PORT key

(UnorderedKey)

🤖 Prompt for AI Agents
In `@apps/admin-api/env/.env.sample` around lines 9 - 10, The dotenv sample has
keys out of the expected alphabetical/group order causing dotenv-linter
UnorderedKey warnings; reorder the keys in apps/admin-api/env/.env.sample so
that PORT and CORS_WEBSITE_URLS (and the other entries around lines 42–44)
follow the repository's canonical key ordering while preserving the current
section grouping—adjust entries named PORT and CORS_WEBSITE_URLS accordingly to
silence UnorderedKey.

Comment on lines +1 to +12
POSTGRES_DB_URI=postgres://postgres:password@localhost:5432/console-users
PORT=3010
CORS_WEBSITE_URLS=http://localhost:3011
AUTH0_JWKS_URI=https://test-tenant.auth0.com/.well-known/jwks.json
AUTH0_ISSUER=https://test-tenant.auth0.com/
AUTH0_AUDIENCE=test-audience
ADMIN_ALLOWED_DOMAINS=akash.network
ADMIN_WHITELIST_EMAILS=
LOG_LEVEL=debug
STD_OUT_LOG_FORMAT=pretty
NODE_ENV=test
DEPLOYMENT_ENV=test
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 | 🟡 Minor

Fix dotenv-linter UnorderedKey warnings in unit-test env.

Reordering keys avoids lint noise (or failures if enforced).

♻️ Proposed reordering
-POSTGRES_DB_URI=postgres://postgres:password@localhost:5432/console-users
-PORT=3010
-CORS_WEBSITE_URLS=http://localhost:3011
-AUTH0_JWKS_URI=https://test-tenant.auth0.com/.well-known/jwks.json
-AUTH0_ISSUER=https://test-tenant.auth0.com/
-AUTH0_AUDIENCE=test-audience
-ADMIN_ALLOWED_DOMAINS=akash.network
-ADMIN_WHITELIST_EMAILS=
-LOG_LEVEL=debug
-STD_OUT_LOG_FORMAT=pretty
-NODE_ENV=test
-DEPLOYMENT_ENV=test
+ADMIN_ALLOWED_DOMAINS=akash.network
+ADMIN_WHITELIST_EMAILS=
+AUTH0_AUDIENCE=test-audience
+AUTH0_ISSUER=https://test-tenant.auth0.com/
+AUTH0_JWKS_URI=https://test-tenant.auth0.com/.well-known/jwks.json
+CORS_WEBSITE_URLS=http://localhost:3011
+DEPLOYMENT_ENV=test
+LOG_LEVEL=debug
+NODE_ENV=test
+PORT=3010
+POSTGRES_DB_URI=postgres://postgres:password@localhost:5432/console-users
+STD_OUT_LOG_FORMAT=pretty
📝 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
POSTGRES_DB_URI=postgres://postgres:password@localhost:5432/console-users
PORT=3010
CORS_WEBSITE_URLS=http://localhost:3011
AUTH0_JWKS_URI=https://test-tenant.auth0.com/.well-known/jwks.json
AUTH0_ISSUER=https://test-tenant.auth0.com/
AUTH0_AUDIENCE=test-audience
ADMIN_ALLOWED_DOMAINS=akash.network
ADMIN_WHITELIST_EMAILS=
LOG_LEVEL=debug
STD_OUT_LOG_FORMAT=pretty
NODE_ENV=test
DEPLOYMENT_ENV=test
ADMIN_ALLOWED_DOMAINS=akash.network
ADMIN_WHITELIST_EMAILS=
AUTH0_AUDIENCE=test-audience
AUTH0_ISSUER=https://test-tenant.auth0.com/
AUTH0_JWKS_URI=https://test-tenant.auth0.com/.well-known/jwks.json
CORS_WEBSITE_URLS=http://localhost:3011
DEPLOYMENT_ENV=test
LOG_LEVEL=debug
NODE_ENV=test
PORT=3010
POSTGRES_DB_URI=postgres://postgres:password@localhost:5432/console-users
STD_OUT_LOG_FORMAT=pretty
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 2-2: [UnorderedKey] The PORT key should go before the POSTGRES_DB_URI key

(UnorderedKey)


[warning] 3-3: [UnorderedKey] The CORS_WEBSITE_URLS key should go before the PORT key

(UnorderedKey)


[warning] 4-4: [UnorderedKey] The AUTH0_JWKS_URI key should go before the CORS_WEBSITE_URLS key

(UnorderedKey)


[warning] 5-5: [UnorderedKey] The AUTH0_ISSUER key should go before the AUTH0_JWKS_URI key

(UnorderedKey)


[warning] 6-6: [UnorderedKey] The AUTH0_AUDIENCE key should go before the AUTH0_ISSUER key

(UnorderedKey)


[warning] 7-7: [UnorderedKey] The ADMIN_ALLOWED_DOMAINS key should go before the AUTH0_AUDIENCE key

(UnorderedKey)


[warning] 8-8: [UnorderedKey] The ADMIN_WHITELIST_EMAILS key should go before the AUTH0_AUDIENCE key

(UnorderedKey)


[warning] 9-9: [UnorderedKey] The LOG_LEVEL key should go before the PORT key

(UnorderedKey)


[warning] 11-11: [UnorderedKey] The NODE_ENV key should go before the PORT key

(UnorderedKey)


[warning] 12-12: [UnorderedKey] The DEPLOYMENT_ENV key should go before the LOG_LEVEL key

(UnorderedKey)

🤖 Prompt for AI Agents
In `@apps/admin-api/env/.env.unit.test` around lines 1 - 12, The .env unit-test
file has dotenv-linter UnorderedKey warnings; reorder the environment keys
(e.g., POSTGRES_DB_URI, PORT, CORS_WEBSITE_URLS, AUTH0_JWKS_URI, AUTH0_ISSUER,
AUTH0_AUDIENCE, ADMIN_ALLOWED_DOMAINS, ADMIN_WHITELIST_EMAILS, LOG_LEVEL,
STD_OUT_LOG_FORMAT, NODE_ENV, DEPLOYMENT_ENV) into the canonical order required
by our dotenv-linter (typically alphabetical) so keys are consistently sorted
and the linter warnings disappear; ensure no duplicate keys and preserve values
while changing only the key order.

Comment on lines +30 to +35
try {
const tokenResponse = await getAccessToken(req, res);
accessToken = tokenResponse.accessToken;
} catch {
return res.status(401).json({ error: "Unable to get access token" });
}
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 | 🟡 Minor

Add error logging when token retrieval fails.

The catch block silently discards the error. Logging would aid debugging authentication issues in production.

🔧 Proposed fix
     try {
       const tokenResponse = await getAccessToken(req, res);
       accessToken = tokenResponse.accessToken;
-    } catch {
+    } catch (error) {
+      // Consider using LoggerService here for consistent logging
+      console.error("Failed to get access token:", error);
       return res.status(401).json({ error: "Unable to get access token" });
     }

Note: Per coding guidelines, LoggerService should be used instead of console.error. You may need to set up a logger instance for this API route.

🤖 Prompt for AI Agents
In `@apps/admin-web/src/pages/api/proxy/`[...path].ts around lines 30 - 35, The
catch block that wraps getAccessToken currently swallows errors; update it to
catch the error as a variable and log it via the project's LoggerService before
returning the 401. Specifically, modify the try/catch around getAccessToken so
the catch(e) calls LoggerService.error (include context like "Unable to get
access token" and the error object) and then returns res.status(401).json({
error: "Unable to get access token" });; ensure you import or instantiate the
same LoggerService used elsewhere in the API code so the log integrates with
existing logging.

Comment on lines +68 to +72
const response = await fetch(targetUrl, {
method,
headers,
body
});
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 | 🟡 Minor

Add a timeout to the fetch request to prevent indefinite hangs.

If the admin API becomes unresponsive, this proxy request will hang without a timeout, potentially exhausting server resources.

🛡️ Proposed fix using AbortController
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
+
     const response = await fetch(targetUrl, {
       method,
       headers,
-      body
+      body,
+      signal: controller.signal
     });
+
+    clearTimeout(timeoutId);
📝 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
const response = await fetch(targetUrl, {
method,
headers,
body
});
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 second timeout
const response = await fetch(targetUrl, {
method,
headers,
body,
signal: controller.signal
});
clearTimeout(timeoutId);
🤖 Prompt for AI Agents
In `@apps/admin-web/src/pages/api/proxy/`[...path].ts around lines 68 - 72, The
fetch call to targetUrl using method, headers, and body can hang indefinitely;
wrap the request with an AbortController, create a timeout (e.g., 10s or
configurable) that calls controller.abort(), pass controller.signal to fetch,
and clear the timeout on success; also handle the abort/timeout error (detect
AbortError) and return a 504/timeout response from the proxy handler so the
server doesn't hang. Ensure changes are applied around the existing fetch
invocation and use the same variables (targetUrl, method, headers, body) and the
controller.signal when calling fetch.

Comment on lines +95 to +97
} catch {
return res.status(502).json({ error: "Proxy error" });
}
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 | 🟡 Minor

Log proxy errors before returning 502.

Silent error handling makes debugging production issues difficult.

🔧 Proposed fix
-  } catch {
+  } catch (error) {
+    // Consider using LoggerService for consistent logging
+    console.error("Proxy error:", error);
     return res.status(502).json({ error: "Proxy error" });
   }

Per coding guidelines, LoggerService should be used for logging instead of console.error.

📝 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
} catch {
return res.status(502).json({ error: "Proxy error" });
}
} catch (error) {
loggerService.error("Proxy error:", error);
return res.status(502).json({ error: "Proxy error" });
}
🤖 Prompt for AI Agents
In `@apps/admin-web/src/pages/api/proxy/`[...path].ts around lines 95 - 97, The
catch block that currently returns res.status(502).json({ error: "Proxy error"
}) swallows the exception; update it to accept the error (catch (err)) and log
the error via LoggerService before returning the 502 response. Use the project
LoggerService instance (or import/create one if missing) to log the error with
context (e.g., "Proxy error in [...path] handler") and include the error
object/details so debugging is possible, then keep the existing
res.status(502).json response.

Comment on lines +3 to +6
export const getServerSideProps: GetServerSideProps = async ctx => {
const returnTo = ctx.query.returnTo;
const destination = `/api/auth/login${returnTo ? `?returnTo=${encodeURIComponent(String(returnTo))}` : ""}`;

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the actual file to understand the full context
find . -name "index.tsx" -path "*/pages/login/*" | head -5

Repository: akash-network/console

Length of output: 151


🏁 Script executed:

# Check if the file exists and examine its contents
cat -n apps/admin-web/src/pages/login/index.tsx 2>/dev/null || find . -path "*/pages/login*" -name "*.tsx" 2>/dev/null | head -10

Repository: akash-network/console

Length of output: 587


🏁 Script executed:

# Search for `@auth0` package usage and version in package.json files
find . -name "package.json" -exec grep -l "auth0" {} \;

Repository: akash-network/console

Length of output: 149


🏁 Script executed:

# Also search for how returnTo is used elsewhere in the codebase
rg "returnTo" --type ts --type tsx -A 2 -B 2

Repository: akash-network/console

Length of output: 92


🏁 Script executed:

# Check package.json for auth0 version
grep -A 2 -B 2 "auth0" ./apps/admin-web/package.json

Repository: akash-network/console

Length of output: 247


🏁 Script executed:

# Search for API auth endpoint handling
find . -path "*/api/auth*" -name "*.ts" -o -path "*/api/auth*" -name "*.js" 2>/dev/null | head -20

Repository: akash-network/console

Length of output: 389


🏁 Script executed:

# Search for returnTo usage in the codebase with proper syntax
rg "returnTo" -A 2 -B 2 --type ts --type js

Repository: akash-network/console

Length of output: 36662


🏁 Script executed:

# Check the admin-web API auth handler to see how returnTo is handled
cat -n apps/admin-web/src/pages/api/auth/[...auth0].ts

Repository: akash-network/console

Length of output: 1328


🏁 Script executed:

# Check deploy-web's validation function in detail for reference
cat -n apps/deploy-web/src/utils/getValidInternalReturnToUrl/getValidInternalReturnToUrl.ts

Repository: akash-network/console

Length of output: 1590


🏁 Script executed:

# Search for any existing validation in admin-web
rg "returnTo" apps/admin-web --type ts --type js -A 3 -B 3

Repository: akash-network/console

Length of output: 1611


🌐 Web query:

@auth0/nextjs-auth0 handleLogin returnTo query parameter

💡 Result:

@auth0/nextjs-auth0 already supports a returnTo query param on the login endpoint.

  • v3 (Pages Router default): /api/auth/login?returnTo=/profile → redirects to /profile after callback. [1]
  • v4 (new routes): /auth/login?returnTo=/dashboard → redirects to /dashboard after callback. [2]

If you override the login handler (e.g., handleAuth({ login: ... })), you need to either set returnTo explicitly or forward the query param yourself:

App Router (Route Handler)

import { handleAuth, handleLogin } from "@auth0/nextjs-auth0";

export const GET = handleAuth({
  login: handleLogin((req) => {
    const url = new URL(req.url);
    return { returnTo: url.searchParams.get("returnTo") ?? "/" };
  }),
});

[4]

Static override example (no query forwarding)

export const GET = handleAuth({
  login: handleLogin({ returnTo: "/profile" }),
});

[3]


🏁 Script executed:

# Check if there's a similar getValidInternalReturnToUrl utility in admin-web
find apps/admin-web -name "*returnTo*" -o -name "*redirect*" 2>/dev/null | head -20

Repository: akash-network/console

Length of output: 47


🏁 Script executed:

# Verify the exact Next.js query param behavior with arrays
grep -r "ctx.query" apps/admin-web/src/pages --type ts -A 2 -B 2 | head -40

Repository: akash-network/console

Length of output: 182


🏁 Script executed:

# Search for ctx.query usage in admin-web pages
find apps/admin-web/src/pages -name "*.ts" -o -name "*.tsx" | xargs grep -l "ctx.query" 2>/dev/null

Repository: akash-network/console

Length of output: 105


🏁 Script executed:

# Check what getServerSideProps pages exist in admin-web
find apps/admin-web/src/pages -name "*.tsx" -o -name "*.ts" | xargs grep -l "getServerSideProps" 2>/dev/null

Repository: akash-network/console

Length of output: 140


🏁 Script executed:

# Check the index.tsx getServerSideProps
cat -n apps/admin-web/src/pages/index.tsx

Repository: akash-network/console

Length of output: 423


🏁 Script executed:

# Verify the exact behavior - what does String() do with array from ctx.query?
python3 << 'EOF'
# Simulate what String() does to a Next.js query array
test_cases = [
    "http://evil.com",
    ["http://evil.com"],
    ["/dashboard", "http://evil.com"],
    "/dashboard"
]

for case in test_cases:
    result = str(case) if isinstance(case, str) else str(case)
    print(f"Input: {case}")
    print(f"String() result: {result}")
    print()
EOF

Repository: akash-network/console

Length of output: 326


🏁 Script executed:

# Check the auth0 library source to see if it validates returnTo
cat -n apps/admin-web/src/pages/login/index.tsx

Repository: akash-network/console

Length of output: 587


🏁 Script executed:

# Let's verify what String() actually does with arrays in TypeScript/JavaScript
node << 'EOF'
const testArray = ['http://evil.com'];
const testString = '/dashboard';
const testArrayMultiple = ['https://evil.com', '/dashboard'];

console.log("Array single:", String(testArray));
console.log("String:", String(testString));
console.log("Array multiple:", String(testArrayMultiple));

// Check encodeURIComponent behavior
console.log("\nAfter encodeURIComponent:");
console.log("Array single:", encodeURIComponent(String(testArray)));
console.log("Array multiple:", encodeURIComponent(String(testArrayMultiple)));
EOF

Repository: akash-network/console

Length of output: 274


🏁 Script executed:

# Let's check if the admin-web auth handler actually processes the returnTo from the login page
cat -n apps/admin-web/src/pages/api/auth/[...auth0].ts | head -20

Repository: akash-network/console

Length of output: 863


🏁 Script executed:

# Double-check: does handleLogin use the returnTo from query or only from the options?
rg "handleLogin" apps/admin-web/src/pages/api/auth --type ts -A 5 -B 5

Repository: akash-network/console

Length of output: 1015


Validate returnTo to avoid open redirects and array coercion.

ctx.query.returnTo can be an array and currently lacks validation. When coerced with String(), arrays become comma-separated values (e.g., ['http://evil.com', '/dashboard']http://evil.com,/dashboard). Although the backend auth handler currently hardcodes returnTo: "/users" and mitigates the issue, validating at the frontend follows defense-in-depth principles and prevents future vulnerabilities if the backend changes. Normalize to a single value and only allow relative paths before encoding, matching the pattern used in deploy-web's getValidInternalReturnToUrl utility.

Suggested fix
 export const getServerSideProps: GetServerSideProps = async ctx => {
-  const returnTo = ctx.query.returnTo;
-  const destination = `/api/auth/login${returnTo ? `?returnTo=${encodeURIComponent(String(returnTo))}` : ""}`;
+  const rawReturnTo = ctx.query.returnTo;
+  const returnTo = Array.isArray(rawReturnTo) ? rawReturnTo[0] : rawReturnTo;
+  const safeReturnTo =
+    typeof returnTo === "string" && returnTo.startsWith("/") && !returnTo.startsWith("//")
+      ? returnTo
+      : undefined;
+  const destination = `/api/auth/login${safeReturnTo ? `?returnTo=${encodeURIComponent(safeReturnTo)}` : ""}`;
📝 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
export const getServerSideProps: GetServerSideProps = async ctx => {
const returnTo = ctx.query.returnTo;
const destination = `/api/auth/login${returnTo ? `?returnTo=${encodeURIComponent(String(returnTo))}` : ""}`;
export const getServerSideProps: GetServerSideProps = async ctx => {
const rawReturnTo = ctx.query.returnTo;
const returnTo = Array.isArray(rawReturnTo) ? rawReturnTo[0] : rawReturnTo;
const safeReturnTo =
typeof returnTo === "string" && returnTo.startsWith("/") && !returnTo.startsWith("//")
? returnTo
: undefined;
const destination = `/api/auth/login${safeReturnTo ? `?returnTo=${encodeURIComponent(safeReturnTo)}` : ""}`;
🤖 Prompt for AI Agents
In `@apps/admin-web/src/pages/login/index.tsx` around lines 3 - 6, The
getServerSideProps implementation uses ctx.query.returnTo without validating it,
which can allow array coercion or open redirects; update the logic that builds
destination so it normalizes and validates returnTo: if ctx.query.returnTo is an
array, pick a single value (e.g., first) or treat as undefined, then ensure the
value is a relative internal path (starts with '/' and not '//' and contains no
scheme like "http://" or "https://"); reject or fall back to the safe default if
validation fails, then encodeURIComponent that validated string when composing
destination. Reference symbols: getServerSideProps, ctx.query.returnTo, and
destination (or reuse an existing utility like getValidInternalReturnToUrl if
available).

Comment on lines +28 to +32
export interface UserStats {
totalUsers: number;
newUsersLast7Days: number;
activeUsersLast30Days: number;
}
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 | 🟡 Minor

Align UserStats with the API contract (missing newUsersLast30Days).

The admin API’s UserStats includes newUsersLast30Days; the frontend type is narrower and will drop that field if used elsewhere. Consider adding it (or confirm the backend payload differs).

🛠️ Suggested update
 export interface UserStats {
   totalUsers: number;
   newUsersLast7Days: number;
+  newUsersLast30Days: number;
   activeUsersLast30Days: number;
 }
📝 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
export interface UserStats {
totalUsers: number;
newUsersLast7Days: number;
activeUsersLast30Days: number;
}
export interface UserStats {
totalUsers: number;
newUsersLast7Days: number;
newUsersLast30Days: number;
activeUsersLast30Days: number;
}
🤖 Prompt for AI Agents
In `@apps/admin-web/src/types/user.ts` around lines 28 - 32, The frontend
UserStats interface is missing the newUsersLast30Days field expected by the
admin API; update the UserStats interface (export interface UserStats) to
include newUsersLast30Days: number so the type matches the backend contract (or
explicitly confirm and document if the backend payload intentionally omits it).
Ensure any usage of UserStats (e.g., components or API parsing) handles the
added field.

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

🤖 Fix all issues with AI agents
In `@apps/admin-api/src/user/repositories/admin-user.repository.ts`:
- Around line 23-35: The paginated response is missing totalPages and UserStats
is inconsistent with the API: update the list and search routers
(list-users.router.ts and search-users.router.ts) to include totalPages by
computing Math.ceil(total / pageSize) and returning it alongside users, total,
page, and pageSize so PaginatedUsers matches the frontend; for UserStats either
stop sending newUsersLast30Days from user-stats.router.ts or add
newUsersLast30Days to the frontend UserStats type so the API and type
definitions are consistent (pick one and apply consistently).
🧹 Nitpick comments (3)
apps/admin-web/src/pages/api/auth/[...auth0].ts (1)

43-46: Consider using a type guard for cleaner error handling.

The current error type extraction works but could be more robust with a proper type guard.

♻️ Optional: Type guard for Auth0 errors
+interface Auth0Error extends Error {
+  status: number;
+}
+
+function isAuth0Error(error: unknown): error is Auth0Error {
+  return error instanceof Error && "status" in error && typeof (error as Auth0Error).status === "number";
+}
+
   } catch (error: unknown) {
-    const status = error instanceof Error && "status" in error ? (error as { status: number }).status : 500;
+    const status = isAuth0Error(error) ? error.status : 500;
     const message = error instanceof Error ? error.message : "Unknown error";
     res.status(status).end(message);
   }
apps/admin-web/src/components/layout/AdminLayout.tsx (1)

12-12: Consider extracting ALLOWED_DOMAIN to a shared constant.

This constant is duplicated in apps/admin-web/src/pages/api/auth/[...auth0].ts. Extracting it to a shared config file would ensure consistency and simplify maintenance.

♻️ Suggested approach

Create a shared constants file:

// apps/admin-web/src/config/auth.ts
export const ALLOWED_DOMAIN = "akash.network";

Then import it in both files.

apps/admin-web/src/pages/users/index.tsx (1)

15-33: Add enabled: !isSearching to useUsersQuery to prevent redundant fetches during search.

useUsersQuery executes on every render even when isSearching is true, causing unnecessary requests. Since only searchQueryResult is used during search (line 31), the list query runs but its data is discarded. Add enabled: !isSearching to the hook options to align with the conditional fetching pattern already used in useSearchUsersQuery and useUserQuery. If adjusting hook signatures is out of scope, track as a follow‑up issue.

Comment on lines +23 to +35
export interface PaginatedUsers {
users: UserWithWallet[];
total: number;
page: number;
pageSize: number;
}

export interface UserStats {
totalUsers: number;
newUsersLast7Days: number;
newUsersLast30Days: number;
activeUsersLast30Days: number;
}
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check how the API routes transform the repository response
rg -n "totalPages|PaginatedUsers" apps/admin-api/src --type ts -C 3

Repository: akash-network/console

Length of output: 2052


🏁 Script executed:

# Also check the frontend types to compare
cat apps/admin-web/src/types/user.ts

Repository: akash-network/console

Length of output: 959


🏁 Script executed:

# Check API controllers/routes that use PaginatedUsers
rg -n "PaginatedUsers|admin-user.repository" apps/admin-api/src --type ts -C 2

Repository: akash-network/console

Length of output: 3325


🏁 Script executed:

# Check list-users router implementation
cat apps/admin-api/src/user/routes/list-users/list-users.router.ts

Repository: akash-network/console

Length of output: 2009


🏁 Script executed:

# Check search-users router implementation
cat apps/admin-api/src/user/routes/search-users/search-users.router.ts

Repository: akash-network/console

Length of output: 2078


🏁 Script executed:

# Check user-stats router implementation
cat apps/admin-api/src/analytics/routes/user-stats.router.ts

Repository: akash-network/console

Length of output: 1122


Backend API response missing totalPages field; frontend expects it. Additionally, backend includes newUsersLast30Days in UserStats but frontend type does not define it.

The backend routers (list-users.router.ts and search-users.router.ts) return paginated responses without totalPages, but the frontend PaginatedUsers type requires it. The user-stats.router.ts sends newUsersLast30Days in the response, which the frontend UserStats type does not include.

For PaginatedUsers: Add totalPages: Math.ceil(total / pageSize) to the response in both list and search routers.

For UserStats: Either remove newUsersLast30Days from the API response or add it to the frontend type definition to maintain consistency.

🤖 Prompt for AI Agents
In `@apps/admin-api/src/user/repositories/admin-user.repository.ts` around lines
23 - 35, The paginated response is missing totalPages and UserStats is
inconsistent with the API: update the list and search routers
(list-users.router.ts and search-users.router.ts) to include totalPages by
computing Math.ceil(total / pageSize) and returning it alongside users, total,
page, and pageSize so PaginatedUsers matches the frontend; for UserStats either
stop sending newUsersLast30Days from user-stats.router.ts or add
newUsersLast30Days to the frontend UserStats type so the API and type
definitions are consistent (pick one and apply consistently).

@baktun14 baktun14 closed this Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments