Conversation
📝 WalkthroughWalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
| @@ -33,6 +33,163 @@ | |||
| "npm": "^11.6.2" | |||
There was a problem hiding this comment.
🔄 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 | 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
| 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
| 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
| 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
There was a problem hiding this comment.
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
deploymentEnvis "staging" buthostNameuses "beta". Combined with.env.stagingpointing 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:devscript uses shell&for background process which:
- Doesn't work on Windows (cross-platform issue)
- Won't properly terminate child processes when the parent exits
- Is inconsistent with other scripts like
console:devthat useturboProposed 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 usinganytype per coding guidelines.The explicit
declare const require: anyviolates 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/uipackage exports proper types, consider using ES module import syntax with type assertions.As per coding guidelines: "Never use type
anyor cast to typeany. Always define the proper TypeScript types."apps/admin-web/src/components/layout/Sidebar.tsx (1)
36-38: Consider more precise route matching forisActivedetection.Using
pathname.startsWith(item.href)could cause false positives if future routes share prefixes (e.g.,/users-adminwould 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: Redundantcn()usage with single static class string.The
cnutility 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
cnis 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 toparseIntcalls.Using
parseIntwithout an explicit radix can lead to unexpected behavior if the input starts with0(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 shareduserSchemato avoid duplication.The
userSchemadefinition is duplicated between this file andsearch-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 includesnumberbutprocess.envonly contains strings.The
RawAppConfigtype allowsnumbervalues, butprocess.envonly providesstring | 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 toparseIntcalls.Same as the list-users router - specify radix
10for 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
connectToDatabasefunction 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 anaria-labelfor accessibility.The input relies on the placeholder for context, but screen readers benefit from an explicit
aria-labelwhen 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
useUsersQueryanduseSearchUsersQuerymount every render; if they fetch immediately, this doubles traffic. Consider adding anenabled/skipoption (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
Datevalues; DBnow()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);
| release: | ||
| name: Create Release | ||
| uses: ./.github/workflows/reusable-create-github-release.yml | ||
| secrets: inherit | ||
| with: | ||
| app: admin-api | ||
|
|
There was a problem hiding this comment.
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-apiAlso 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.
| 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 |
There was a problem hiding this comment.
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.
| 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.
| CORS_WEBSITE_URLS=https://console-admin.akash.network | ||
| WEBSITE_URL=https://console-admin.akash.network | ||
| LOG_LEVEL=debug |
There was a problem hiding this comment.
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.
| 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).
| PORT=3010 | ||
| CORS_WEBSITE_URLS=http://localhost:3011 |
There was a problem hiding this comment.
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=prettyAlso 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.
| 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 |
There was a problem hiding this comment.
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.
| 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.
| try { | ||
| const tokenResponse = await getAccessToken(req, res); | ||
| accessToken = tokenResponse.accessToken; | ||
| } catch { | ||
| return res.status(401).json({ error: "Unable to get access token" }); | ||
| } |
There was a problem hiding this comment.
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.
| const response = await fetch(targetUrl, { | ||
| method, | ||
| headers, | ||
| body | ||
| }); |
There was a problem hiding this comment.
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.
| 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.
| } catch { | ||
| return res.status(502).json({ error: "Proxy error" }); | ||
| } |
There was a problem hiding this comment.
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.
| } 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.
| export const getServerSideProps: GetServerSideProps = async ctx => { | ||
| const returnTo = ctx.query.returnTo; | ||
| const destination = `/api/auth/login${returnTo ? `?returnTo=${encodeURIComponent(String(returnTo))}` : ""}`; | ||
|
|
There was a problem hiding this comment.
🧩 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 -5Repository: 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 -10Repository: 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 2Repository: 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.jsonRepository: 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 -20Repository: 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 jsRepository: 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].tsRepository: 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.tsRepository: 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 3Repository: 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/profileafter callback. [1] - v4 (new routes):
/auth/login?returnTo=/dashboard→ redirects to/dashboardafter 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 -20Repository: 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 -40Repository: 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/nullRepository: 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/nullRepository: akash-network/console
Length of output: 140
🏁 Script executed:
# Check the index.tsx getServerSideProps
cat -n apps/admin-web/src/pages/index.tsxRepository: 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()
EOFRepository: 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.tsxRepository: 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)));
EOFRepository: 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 -20Repository: 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 5Repository: 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.
| 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).
| export interface UserStats { | ||
| totalUsers: number; | ||
| newUsersLast7Days: number; | ||
| activeUsersLast30Days: number; | ||
| } |
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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 extractingALLOWED_DOMAINto 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: Addenabled: !isSearchingtouseUsersQueryto prevent redundant fetches during search.
useUsersQueryexecutes on every render even whenisSearchingis true, causing unnecessary requests. Since onlysearchQueryResultis used during search (line 31), the list query runs but its data is discarded. Addenabled: !isSearchingto the hook options to align with the conditional fetching pattern already used inuseSearchUsersQueryanduseUserQuery. If adjusting hook signatures is out of scope, track as a follow‑up issue.
| export interface PaginatedUsers { | ||
| users: UserWithWallet[]; | ||
| total: number; | ||
| page: number; | ||
| pageSize: number; | ||
| } | ||
|
|
||
| export interface UserStats { | ||
| totalUsers: number; | ||
| newUsersLast7Days: number; | ||
| newUsersLast30Days: number; | ||
| activeUsersLast30Days: number; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check how the API routes transform the repository response
rg -n "totalPages|PaginatedUsers" apps/admin-api/src --type ts -C 3Repository: akash-network/console
Length of output: 2052
🏁 Script executed:
# Also check the frontend types to compare
cat apps/admin-web/src/types/user.tsRepository: 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 2Repository: 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.tsRepository: 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.tsRepository: akash-network/console
Length of output: 2078
🏁 Script executed:
# Check user-stats router implementation
cat apps/admin-api/src/analytics/routes/user-stats.router.tsRepository: 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).
Summary by CodeRabbit