Skip to content

Elysia OIDC/OAuth proxy#101

Open
Its4Nik wants to merge 36 commits into
sqlite-wrapper-joinsfrom
elysia-auth
Open

Elysia OIDC/OAuth proxy#101
Its4Nik wants to merge 36 commits into
sqlite-wrapper-joinsfrom
elysia-auth

Conversation

@Its4Nik
Copy link
Copy Markdown
Owner

@Its4Nik Its4Nik commented Apr 22, 2026

Summary by Sourcery

Introduce a unified OIDC/OAuth authentication system for DockStat, exposing backend auth routes and middleware, React client context and protected routing, and integrating them into the API server and frontend app, while enhancing API documentation and type safety.

New Features:

  • Add @dockstat/auth package providing an OIDC/OAuth proxy with provider management, PKCE-based login, JWT issuance, and ElysiaJS routes.
  • Introduce shared auth middleware and helpers for Elysia (including WebSocket support) and integrate them into the API server to protect core routes.
  • Provide a React AuthProvider, hooks, and ProtectedRoute component plus a DockStat SignIn and auth callback flow with dynamic provider discovery.
  • Enable type-safe index creation in sqlite-wrapper via generic IndexColumn and createIndex typings and adjust DB API paths and tsconfig mappings.

Bug Fixes:

  • Fix Elysia path typings and plugin model schema by simplifying t.Omit usage and improving @dockstat/api path resolution.
  • Correct minor routing and path issues in the frontend (e.g., client configure route path) and temporarily stub out Accounts settings content.

Enhancements:

  • Add detailed README documentation for @dockstat/auth including backend, middleware, and React client usage and migration guidance.
  • Update OpenAPI and route metadata with summaries/descriptions, CORS credentials support, and bearerAuth security scheme.
  • Wire auth state into the DockStat UI layout/navbar/sidebar and configure react-query defaults for better caching and retries.
  • Improve logger formatting and request ID handling for clearer, less noisy logs.
  • Refine plugin install/unload API schemas and documentation responses, and add summaries for plugin frontend and metrics routes.

Build:

  • Add @dockstat/auth as a dependency to the API and frontend apps and wire up new auth-related libraries (openid-client, jose, elysia-oauth2) plus bump Elysia and Turbo versions.
  • Adjust auth and client package tsconfig lib/strict settings to support DOM APIs and new module exports, and export the new auth client entry point.

Documentation:

  • Replace the minimal @dockstat/auth README with comprehensive user-facing documentation covering features, API reference, backend/frontend integration, and migration notes.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added OAuth/OIDC authentication system with provider management and login flow
    • Sign-in page with provider selection and automatic token handling
    • Route protection for authenticated users with automatic redirects
    • Cross-tab authentication synchronization
  • Documentation

    • Expanded authentication package documentation with setup guides and API reference
  • Refactor

    • Simplified accounts settings interface; accounts management coming soon

Its4Nik added 12 commits April 20, 2026 13:45
- Refactor AuthHandler to use openid-client for OIDC integration
- Add OIDC providers table to manage client configurations
- Add auth routes to API for login/callback flows
- Add frontend auth hooks (useAuth) and protected route components
- Update project dependencies with elysia-oauth2 and openid-client
- Improve type safety for SQLite wrapper index creation
- Added `jose` to handle JWT creation for secure user session transmission
- Updated AuthHandler to include state, nonce, and PKCE verification cookies
- Refactored frontend auth callback to process JWT tokens instead of direct API response
- Added `FRONTEND_URL` environment variable for cross-origin redirects
- Enhanced logging in the auth flow for improved debugging
- Updated SignInPage UI to support dynamic provider discovery and selection
- Refactored ProtectedRoute to use updated auth logic
- Added `/auth/:providerId/logout` endpoint to `@dockstat/auth` to support OIDC end-session redirection
- Enhanced `useAuth` hook to manage provider ID tracking and handle logout redirects
- Updated `Navbar` and `Sidebar` components to display the authenticated user's identifier
- Added logout action to the UI sidebar
- Refactored `AuthHandler` to store `logout_url` and improve environment variable handling for JWT secrets
- Move configuration logic to ConfigService
- Move route definitions to separate file
- Update API entry point to use AuthHandler.routes property
- Bump turbo to 2.9.6 and bun-types to 1.3.13
- Add version field to @dockstat/auth package.json
…mprove plugin management

- Add missing OpenAPI summaries and descriptions to various API routes
- Clean up unused parameters in Docker client routes
- Refactor plugin installation type casting and enhance unloadPlugins documentation
- Implement `useAuth` React hook in `@dockstat/auth`
- Update auth route handlers to use Elysia `redirect` utility
- Add missing documentation to auth routes
- Add ProtectedRoute component for route guarding
- Reorganize @dockstat/auth/client exports
- Update layout to handle auth state and user display in navbar
- Refactor system stats route logic for better maintainability
- Add DOM lib to auth package for browser-based auth utilities
- Minor cleanup of route documentation summaries and internal code formatting
…ntext provider

- Add `createAuthMiddleware` to `@dockstat/auth` for JWT-based request authentication in ElysiaJS.
- Integrate authentication middleware into `DockStatAPI` via `apps/api`.
- Introduce `AuthProvider` in `@dockstat/auth/client` for improved React state management, including cross-tab sync and automatic token refresh.
- Add comprehensive documentation for new authentication middleware and React integration in `packages/auth/README.md`.
- Deprecate legacy hook-based authentication approach in favor of the new Context API.
- Add utility functions for WebSocket authentication and protected route handling.
…middleware

- Remove manual middleware usage in API routes and move to a centralized `.guard(authenticated(), ...)` pattern.
- Consolidate auth middleware into `middleware/auth.ts`.
- Update `DockStatAPI` to enforce authentication globally for core services via guards.
- Remove deprecated accounts UI components.
- Fix TS path mapping for `@dockstat/auth` package and adjust client-side routing to use the new `CreateRoutes` wrapper.
- Upgrade `elysia` to 1.4.28 and bump `@dockstat/auth` dependency versioning.
…lient [DOCK-123]

- Add auth_token support to Eden Treaty API client via Authorization header
- Persist auth_token to localStorage during sign-in
- Configure TanStack Query default options with staleTime, gcTime, and retry strategies
- Cleanup SignInPage logic to improve state handling and remove redundant user redirection logic
- Refactor `AuthHandler` to expose middleware via `getMiddlewareFunctions`
- Update API and Elysia plugins to use factory-provided middleware and proper OpenAPI security schemes
- Clean up `packages/auth` exports and deprecate old `useAuth` API
- Update JWT expiration time to 1 day
- Improve authorization header handling in `lib/api`
- Added comprehensive logging to the authentication middleware flow
- Reorder properties in Elysia openapi configuration
- Correct indentation and spacing in QueryClient, React Router, and auth middleware
- Improve readability of exported middleware functions
- Add missing whitespace in api.ts and other utility files
- Replace standard string concatenation with template literals in SignIn.tsx
- Remove unused imports in router.tsx and AuthProvider.tsx
- Update property access and prefix unused variable with underscore in middleware.ts for better linting compliance
@Its4Nik Its4Nik added this to the DockStat v1 - BETA milestone Apr 22, 2026
@Its4Nik Its4Nik self-assigned this Apr 22, 2026
@Its4Nik Its4Nik added this to DockStat Apr 22, 2026
@github-project-automation github-project-automation Bot moved this to Todo in DockStat Apr 22, 2026
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 22, 2026

Reviewer's Guide

Introduce a full OIDC/OAuth proxy/authentication system (@dockstat/auth) and integrate it end‑to‑end with the API and frontend, enforcing JWT‑based auth, adding React auth context/hooks, protected routing, and related documentation and typing updates.

Sequence diagram for OAuth login and callback flow

sequenceDiagram
  actor User
  participant Browser
  participant Frontend as DockStatFrontend
  participant AuthClient as AuthProviderClient
  participant API as DockStatAPI
  participant AuthRoutes as AuthRoutes_/auth
  participant OIDC as OIDCProvider

  User->>Browser: Navigate to protected route
  Browser->>Frontend: Request /protected
  Frontend->>AuthClient: useAuth() in ProtectedRoute
  AuthClient-->>Frontend: isAuthenticated = false
  Frontend-->>Browser: Redirect to /login

  User->>Browser: Click provider button
  Browser->>AuthClient: login(providerId)
  AuthClient->>Browser: window.location = API_BASE/auth/providerId/login

  Browser->>API: GET /auth/:providerId/login
  API->>AuthRoutes: Route handling
  AuthRoutes->>AuthRoutes: ConfigService.getConfig(providerId)
  AuthRoutes->>AuthRoutes: Generate state, nonce, PKCE
  AuthRoutes->>Browser: Set cookies state, nonce, pkce
  AuthRoutes-->>Browser: 302 Redirect to OIDC authorization_url

  Browser->>OIDC: GET authorization_url (user login)
  OIDC-->>Browser: Redirect to BASE_URL/auth/providerId/callback?code&state

  Browser->>API: GET /auth/:providerId/callback?code&state
  API->>AuthRoutes: Route handling
  AuthRoutes->>AuthRoutes: Validate cookies and state
  AuthRoutes->>OIDC: authorizationCodeGrant(meta, callbackUrl,...)
  OIDC-->>AuthRoutes: tokens
  AuthRoutes->>OIDC: fetchUserInfo(access_token, sub)
  OIDC-->>AuthRoutes: userInfo
  AuthRoutes->>AuthRoutes: createAuthToken(userInfo) (JWT)
  AuthRoutes-->>Browser: 302 Redirect to FRONTEND_URL/auth/providerId/callback?token=jwt

  Browser->>Frontend: GET /auth/providerId/callback?token=jwt
  Frontend->>AuthClient: AuthCallback processes token
  AuthClient->>Browser: Store auth_token and user in localStorage
  AuthClient-->>Browser: Redirect to original path

  Browser->>API: Subsequent API calls with Authorization: Bearer jwt
  API->>API: Middleware.verifyAuthToken
  API-->>Browser: Protected data
Loading

ER diagram for oidc_providers table

erDiagram
  OIDC_PROVIDERS {
    string id PK
    string issuer_url
    string client_id
    string client_secret
    string scopes
    date created_at
    string logout_url
  }
Loading

Class diagram for @dockstat/auth backend components

classDiagram
  class AuthHandler {
    +QueryBuilder~ProvidersTable~ table
    +Logger logger
    +ConfigService configService
    +Elysia routes
    +AuthMiddlewareBundle middleware
    +AuthHandler(DB db, Logger logger)
  }

  class ConfigService {
    +QueryBuilder~ProvidersTable~ table
    +Logger logger
    +Map~string, Configuration~ issuerCache
    +ConfigService(QueryBuilder~ProvidersTable~ table, Logger logger)
    +getConfig(string providerId) Promise~OAuthConfig~
  }

  class OAuthConfig {
    +ClientMetadata config
    +Configuration meta
    +string scopes
  }

  class AuthMiddlewareBundle {
    +createAuthMiddleware() Elysia
    +authenticated(options) AuthRouteDecorator
    +createAuthenticatedWsHandler(options) WsHandler
    +getWsUser(ElysiaWS ws) AuthUser
    +handleWsAuthentication(ElysiaWS ws, string token) Promise~boolean~
    +isAuthenticatedUser(AuthUser user) boolean
    +withAuth(handler) Function
  }

  class AuthUser {
    +string sub
    +string email
    +string name
    +string picture
    +number iat
    +number exp
    +[key: string]: unknown
  }

  class ProvidersTable {
    +string id
    +string issuer_url
    +string client_id
    +string client_secret
    +string scopes
    +Date created_at
    +string logout_url
  }

  class JWTHelpers {
    +createAuthToken(userInfo) Promise~string~
    +verifyAuthToken(string token) Promise~JWTPayload | null~
  }

  class JWTPayload {
    +AuthUser user
    +number iat
    +number exp
  }

  class EnvConfig {
    +string BASE_URL
    +string FRONTEND_URL
    +Uint8Array JWT_SECRET
  }

  class AuthRoutesFactory {
    +createAuthRoutes(QueryBuilder~ProvidersTable~, Logger, ConfigService) Elysia
  }

  class Logger {
    +Logger spawn(string name) Logger
    +info(string msg)
    +warn(string msg)
    +error(string msg)
  }

  class DB {
    +createTable~T~(string name, schema, options) QueryBuilder~T~
  }

  class QueryBuilder~T~ {
    +insertAndGet(object row) T
    +select(string[] cols) QueryBuilder~T~
    +where(object where) QueryBuilder~T~
    +first() T | null
    +all() T[]
  }

  AuthHandler --> ConfigService : creates
  AuthHandler --> ProvidersTable : manages
  AuthHandler --> AuthMiddlewareBundle : uses
  AuthHandler --> AuthRoutesFactory : uses
  ConfigService --> ProvidersTable : reads
  ConfigService --> EnvConfig : uses BASE_URL
  AuthMiddlewareBundle --> AuthUser : produces
  AuthMiddlewareBundle --> JWTHelpers : uses verifyAuthToken
  JWTHelpers --> EnvConfig : uses JWT_SECRET
  AuthRoutesFactory --> ConfigService : uses
  AuthRoutesFactory --> JWTHelpers : uses createAuthToken
  AuthRoutesFactory --> ProvidersTable : reads
  AuthRoutesFactory --> EnvConfig : uses BASE_URL, FRONTEND_URL
  DB --> QueryBuilder~ProvidersTable~ : creates table
Loading

Class diagram for React auth client (AuthProvider and ProtectedRoute)

classDiagram
  class AuthProvider {
    +AuthState state
    +AuthProviderProps props
    +AuthProvider(AuthProviderProps props)
    +login(string providerId) void
    +logout() void
    +refreshToken() Promise~void~
    +clearError() void
  }

  class AuthState {
    +User user
    +string token
    +boolean loading
    +string error
    +boolean isAuthenticated
  }

  class AuthProviderProps {
    +ReactNode children
    +string apiBase
    +string tokenStorageKey
    +string userStorageKey
    +() => void onTokenExpired
  }

  class User {
    +string sub
    +string email
    +string name
    +string picture
    +[key: string]: unknown
  }

  class AuthContextType {
    +User user
    +string token
    +boolean loading
    +string error
    +boolean isAuthenticated
    +login(string providerId) void
    +logout() void
    +refreshToken() Promise~void~
    +clearError() void
  }

  class ProtectedRoute {
    +ReactNode children
    +ReactNode loadingComponent
    +string redirectTo
  }

  class AuthHooks {
    +useAuth() AuthContextType
    +useUser() User | null
    +useIsAuthenticated() boolean
    +useIsLoading() boolean
    +useAuthError() string | null
  }

  class LegacyUseAuth {
    <<deprecated>>
    +useAuth() AuthContextType
  }

  AuthProvider --> AuthContextType : provides
  AuthProvider --> User : stores
  AuthProvider --> AuthHooks : used by
  AuthProvider --> AuthState : manages
  ProtectedRoute --> AuthHooks : uses useAuth
  AuthHooks --> AuthContextType : returns
  LegacyUseAuth --> AuthHooks : wraps useAuth
Loading

File-Level Changes

Change Details Files
Add @dockstat/auth package implementing OIDC/OAuth proxy, JWT handling, middleware, and React client utilities.
  • Replace placeholder index with AuthHandler class that creates the oidc-providers table, wires ConfigService, HTTP routes, and middleware functions.
  • Implement ConfigService to load provider configuration from the DB, perform OIDC discovery with openid-client, cache issuer metadata, and construct redirect URIs based on BASE_URL.
  • Implement createAuthRoutes to expose provider management and OAuth endpoints (login, callback, logout), manage PKCE/state/nonce cookies, handle token exchange/userinfo, and issue short‑lived JWTs that redirect to FRONTEND_URL.
  • Add JWT helpers (createAuthToken, verifyAuthToken) using jose and env‑based secret, plus typed env helpers and Bun env declarations for BASE_URL/FRONTEND_URL/JWT_SECRET.
  • Add Elysia authentication/authorization middleware (createAuthMiddleware, authenticated, WS helpers) that verifies JWTs from headers/cookies, attaches user info to context, and provides decorators/guards and WS helpers.
  • Add React client side auth: AuthProvider context with token+user persistence, periodic token validation, URL callback handling, cross‑tab sync; hooks (useAuth/useUser/useIsAuthenticated/etc.), ProtectedRoute component, and legacy useAuth wrapper.
  • Update package.json/tsconfig in @dockstat/auth to expose new server/client entry points, include DOM libs, relax strictNullChecks, and add runtime deps (elysia, jose, openid-client).
packages/auth/src/index.ts
packages/auth/src/config.ts
packages/auth/src/routes.ts
packages/auth/src/middleware.ts
packages/auth/src/utils/jwt.ts
packages/auth/src/utils/env.ts
packages/auth/src/types.ts
packages/auth/src/client/AuthProvider.tsx
packages/auth/src/client/protectedRoute.tsx
packages/auth/src/client/useAuth.tsx
packages/auth/src/client/index.tsx
packages/auth/src/env.d.ts
packages/auth/package.json
packages/auth/tsconfig.json
Integrate new auth system into API app, securing routes, wiring middleware, and updating OpenAPI and env typing.
  • Create API‑level AuthHandler instance and export Middleware/authenticated helpers; use createAuthMiddleware globally on /api/v2 root and guard core routes (status, DB, Docker, plugins, misc, repos, themes, websockets, docknode, graph) with authenticated().
  • Mount AuthHandler.routes after guarded routes to expose /auth endpoints under /api/v2.
  • Remove legacy auth DB wiring from DockStatDB and delete previous auth server files.
  • Extend Prometheus, repositories, misc, plugin frontend, and plugin unload routes with OpenAPI detail/summary metadata and response typing improvements.
  • Adjust Docker client routes to remove redundant OpenAPI requestBody/parameters now implied by schemas/paths.
  • Update Elysia plugin setup to configure CORS with credentials and simplify openapi setup using an inline bearerAuth security scheme instead of custom OpenAPI builder; add auth env FRONTEND_URL typing.
  • Align TypeScript paths for @dockstat/api exports.
  • Add @dockstat/auth, elysia-oauth2, openid-client deps to apps/api, bump elysia and turbo versions in root package.
  • Add FRONTEND_URL to Bun env typings for the API app.
apps/api/src/auth.ts
apps/api/src/index.ts
apps/api/src/database/index.ts
apps/api/src/routes/repositories/index.ts
apps/api/src/routes/misc/index.ts
apps/api/src/routes/metrics/prometheus.ts
apps/api/src/routes/plugins/index.ts
apps/api/src/routes/plugins/frontend.ts
apps/api/src/routes/docker/client.ts
apps/api/src/elysia-plugins.ts
apps/api/src/env.d.ts
apps/api/src/models/plugins.ts
apps/api/tsconfig.json
apps/api/package.json
apps/api/src/auth/db.ts
apps/api/src/auth/index.ts
package.json
Add frontend authentication integration: provider discovery/login screen, callback handling, protected routing, and wiring into layout/navbar/sidebar.
  • Introduce AuthProvider usage in DockStatProviders to wrap the app with context configured to API base URL.
  • Replace react-router tree in DockStatRouter with CreateRoutes from new protectedRoute helper; configure most app pages as protected (redirecting to /login) and add explicit routes for SignInPage and AuthCallback.
  • Implement SignInPage that lists OIDC providers from api.auth.providers, allows search, and triggers login via useAuth; implement AuthCallback page that decodes JWT from query, stores user/token, and redirects back to saved path.
  • Add a local legacy useAuth hook in the app that reads user from localStorage and performs login/logout redirects against /auth endpoints (note: this co‑exists with package AuthProvider).
  • Create protectedRoute helper in app that wraps protected routes with @dockstat/auth/client ProtectedRoute, handling loading and redirectTo.
  • Update Layout and Navbar/Sidebar components to accept auth props, derive display name from user (name/email/sub), show user in sidebar header, and render a logout icon/button when authenticated.
  • Wire Navbar to pass auth data (user string, logout fn) from useAuth hook; pass same auth object into Sidebar.
  • Simplify Settings Accounts slide by replacing AccountsSettingsSlide content with placeholder div, effectively removing old account management UI.
  • Add @dockstat/auth dependency and TS path mapping for frontend, and enhance React Query client defaults (staleTime/gcTime/retries) for better caching under auth.
  • Update DockStat API client to add Authorization Bearer header from localStorage auth_token and include credentials for cookie‑based auth; log token discovery for debugging.
apps/dockstat/src/providers/index.tsx
apps/dockstat/src/router.tsx
apps/dockstat/src/layout/index.tsx
apps/dockstat/src/hooks/useAuth.ts
apps/dockstat/src/lib/protectedRoute.tsx
apps/dockstat/src/lib/api.ts
apps/dockstat/src/pages/SignIn.tsx
apps/dockstat/src/pages/settings.tsx
packages/ui/src/components/Navbar/Navbar.tsx
packages/ui/src/components/Sidebar/Sidebar.tsx
apps/dockstat/src/contexts/queryClient.ts
apps/dockstat/package.json
apps/dockstat/tsconfig.json
Document the new @dockstat/auth package and OIDC/OAuth flow in detail.
  • Replace minimal README with full description of @dockstat/auth, features, environment configuration, DB schema, and all /auth endpoints semantics.
  • Add backend integration examples showing AuthHandler setup, middleware usage, authenticated decorator/guards, and WebSocket auth middleware.
  • Add frontend React integration docs for new context‑based approach (AuthProvider, hooks, ProtectedRoute, automatic callback handling, token refresh, cross‑tab syncing), plus deprecation/migration guide for legacy hook approach.
  • Describe new exports for backend (AuthHandler/middleware/types) and client (AuthProvider/hooks/ProtectedRoute/legacy hook).
  • Add contributing guidelines and project metadata at the end of the README.
packages/auth/README.md
Improve sqlite-wrapper generics and logger formatting to support new auth use cases and better logs.
  • Make DB.createIndex and helper createIndex generic over record type and adjust IndexColumn typing to accept keyof T, improving type safety when creating indices for typed tables like oidc-providers.
  • Tweak Logger.format to adjust request tag placement and avoid noisy IDs shorter than 3 characters, ensuring cleaner log prefixes for auth flows.
  • Refine colorByReqID to ignore very short request IDs, reducing useless coloring/noise in logs.
packages/sqlite-wrapper/src/index.ts
packages/sqlite-wrapper/src/lib/index/createIndex.ts
packages/sqlite-wrapper/src/types.ts
packages/logger/src/Logger.ts
packages/logger/src/utils.ts

Possibly linked issues

  • #Auth in @dockstat/api: PR delivers the OIDC/OAuth-based auth system, JWT tokens, middleware, and UI flows requested by the auth issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b7922602-47f7-4966-a70f-d6fd802db7b4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch elysia-auth

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

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 7 issues, and left some high level feedback:

  • In apps/dockstat/src/router.tsx, the route for ConfigureClientsPage changed from /clients/configure to /client/configure; if this isn’t intentional it will break existing navigation/bookmarks and should be corrected back to the plural path.
  • In apps/dockstat/src/lib/api.ts, getHeaders() logs the raw auth_token to the console and the headers are computed only once when creating the Treaty client; consider removing the token logging and switching to a per-request interceptor or dynamic header injection so updated tokens are picked up without recreating the client.
  • Authentication state is now managed both by AuthProvider (which parses tokens from the URL/localStorage) and the AuthCallback component / local useAuth hook under apps/dockstat, which also decode/store the token; consolidating on the context-based @dockstat/auth/client flow and removing duplicate token parsing/storage logic will reduce the risk of subtle inconsistencies.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `apps/dockstat/src/router.tsx`, the route for `ConfigureClientsPage` changed from `/clients/configure` to `/client/configure`; if this isn’t intentional it will break existing navigation/bookmarks and should be corrected back to the plural path.
- In `apps/dockstat/src/lib/api.ts`, `getHeaders()` logs the raw `auth_token` to the console and the headers are computed only once when creating the Treaty client; consider removing the token logging and switching to a per-request interceptor or dynamic header injection so updated tokens are picked up without recreating the client.
- Authentication state is now managed both by `AuthProvider` (which parses tokens from the URL/localStorage) and the `AuthCallback` component / local `useAuth` hook under `apps/dockstat`, which also decode/store the token; consolidating on the context-based `@dockstat/auth/client` flow and removing duplicate token parsing/storage logic will reduce the risk of subtle inconsistencies.

## Individual Comments

### Comment 1
<location path="apps/dockstat/src/layout/index.tsx" line_range="34-38" />
<code_context>
     theme,
   } = useLayout()

+  const { user, logout } = useAuth()
+
   const heading = useContext(PageHeadingContext).heading
</code_context>
<issue_to_address>
**issue (bug_risk):** Layout is using a different auth hook than the new AuthProvider, which will desync auth state and redirects.

Layout imports `useAuth` from `@/hooks/useAuth`, but the app is wrapped with `AuthProvider` from `@dockstat/auth/client`. As a result, Navbar/Sidebar use a different `user/logout` source than the context, and `ProtectedRoute` ends up with a separate `user/isAuthenticated` state that can fall out of sync (e.g. logout in one place not updating the other). Please switch Layout to use `useAuth` from `@dockstat/auth/client` so all components share the same auth state.
</issue_to_address>

### Comment 2
<location path="apps/dockstat/src/lib/api.ts" line_range="4-7" />
<code_context>
-import { type Treaty, treaty } from "@elysiajs/eden"
+import { treaty } from "@elysiajs/eden"
+
+const getHeaders = () => {
+  const token = localStorage.getItem("auth_token")
+  console.log(`Found auth token: ${token}`)
+  return token ? { authorization: `Bearer ${token}` } : {}
+}

</code_context>
<issue_to_address>
**🚨 issue (security):** Leaking tokens via console.log and using a static Authorization header snapshot are security/behavioral issues.

Two issues:
1) `console.log("Found auth token: ${token}")` prints the raw bearer token, which is a credential leak via logs/devtools. Remove this log or redact the token.
2) `headers: getHeaders()` is only evaluated when the client is created, so header values won’t track login/logout/refresh. Ensure the latest token is read per request (e.g., a wrapper `fetch`, recreating the client on auth changes, or using per-call header overrides), otherwise auth will behave inconsistently.
</issue_to_address>

### Comment 3
<location path="apps/dockstat/src/router.tsx" line_range="24" />
<code_context>
+        { element: <DockNodePage />, path: "/node" },
+        { element: <NodeStacksPage />, path: "/node/stacks" },
+        { element: <ClientsPage />, path: "/clients" },
+        { element: <ConfigureClientsPage />, path: "/client/configure" },
+        { element: <PluginIdPage />, path: "/p/:pluginId/*" },
+        { element: <ExtensionsIndex />, path: "/extensions" },
</code_context>
<issue_to_address>
**issue (bug_risk):** The ConfigureClients route path changed from `/clients/configure` to `/client/configure`, which looks unintended.

This change will break any existing navigation or deep links that still use `/clients/configure`. If that path is still expected to work, either revert to the plural route or add a redirect/alias so both paths remain valid.
</issue_to_address>

### Comment 4
<location path="packages/auth/src/routes.ts" line_range="18-21" />
<code_context>
+  return new Elysia({ detail: { tags: ["Auth"] }, prefix: "/auth" })
+    .post(
+      "/providers",
+      ({ body }) =>
+        table.insertAndGet({
+          ...(body as object),
+          scopes: (body as { scopes: string }).scopes ? (body as { scopes: string }).scopes : null,
+        }),
+      {
</code_context>
<issue_to_address>
**issue (bug_risk):** Overriding `scopes` to `null` on provider creation defeats the DB default and may cause runtime issues.

The insert currently forces `scopes` to `null` when not provided:
```ts
scopes: (body as { scopes: string }).scopes ? body.scopes : null
```
This overrides the table’s default (`"openid profile email"`) so `scopes` can be `null` even though downstream code (e.g. `configService`) expects a string.

Instead, omit `scopes` when it’s not supplied so the DB default is used, e.g.:
```ts
const { scopes, ...rest } = body
return table.insertAndGet({
  ...rest,
  ...(scopes ? { scopes } : {}),
})
```
This preserves the default and avoids unexpected `null` handling downstream.
</issue_to_address>

### Comment 5
<location path="packages/logger/src/utils.ts" line_range="70" />
<code_context>
 }

 export function colorByReqID(rawReqId: string) {
-  let reqId = rawReqId
+  let reqId = rawReqId.length >= 3 ? rawReqId : ""
   let from = ""

</code_context>
<issue_to_address>
**suggestion (bug_risk):** Silently discarding short request IDs can make correlation harder and may hide `from` information.

Clearing `reqId` when `rawReqId.length < 3` means valid short IDs are dropped and any `from` segment in them (e.g. `"1|ui"`) is no longer parsed. If the goal is to reject invalid IDs, consider validating by structure (e.g. requiring `|`) instead of length, or at least logging when an ID is discarded so unexpected input can be detected.

Suggested implementation:

```typescript
export function colorByReqID(rawReqId: string) {
  let reqId = rawReqId
  let from = ""

  // If the ID is very short *and* doesn't contain a `|` segment separator, treat it as invalid.
  // We log this in non-production so unexpected input can be investigated without affecting prod noise.
  if (reqId.length < 3 && !reqId.includes("|")) {
    if (process.env.NODE_ENV !== "production") {
      console.warn(`[logger] Discarding short request id without source: "${rawReqId}"`)
    }
    reqId = ""
  }

```

If this package already has a central logging utility (instead of using `console` directly), you should:
1. Replace `console.warn` with the appropriate logger call (e.g. `logger.warn` or similar).
2. Ensure the chosen logger call respects the current log level configuration so these warnings can be filtered in production if desired.
</issue_to_address>

### Comment 6
<location path="apps/dockstat/src/pages/SignIn.tsx" line_range="52" />
<code_context>
+  }
+}
+
+export function AuthCallback() {
+  const [searchParams] = useSearchParams()
+  const navigate = useNavigate()
</code_context>
<issue_to_address>
**issue (complexity):** Consider delegating auth callback, redirect handling, and file structure to the shared auth client so this page remains a thin UI wrapper instead of re‑implementing authentication logic locally.

You’ve effectively re‑implemented parts of the new auth client in this file, which does increase complexity and creates two sources of truth.

### 1. Delegate callback handling to the auth client

Instead of decoding the JWT, touching `localStorage`, and dealing with redirect in `AuthCallback`, expose that logic from `@dockstat/auth/client` (or re‑use existing API if it already exists).

For example, in `@dockstat/auth/client`:

```ts
// auth/client.ts
export function useAuth() {
  // ...
  const handleAuthCallback = async (params: URLSearchParams) => {
    const token = params.get("token");
    if (!token) throw new Error("Missing token");

    // existing shared logic:
    // - decode token
    // - extract user
    // - write to localStorage
    // - manage auth_redirect (if still needed) or central redirect
  };

  return { login, handleAuthCallback, /* ... */ };
}
```

Then simplify `AuthCallback` to a pure UI shell that delegates:

```tsx
// AuthCallbackPage.tsx
import { useAuth } from "@dockstat/auth/client";
import { useEffect, useState } from "react";
import { useNavigate, useSearchParams } from "react-router";

export function AuthCallbackPage() {
  const { handleAuthCallback } = useAuth();
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    (async () => {
      try {
        await handleAuthCallback(searchParams);
        navigate("/"); // or let handleAuthCallback decide redirect
      } catch (err) {
        console.error("Auth callback error:", err);
        setError("Failed to process authentication. Please try again.");
      }
    })();
  }, [searchParams, handleAuthCallback, navigate]);

  // keep your existing loading/error UI
}
```

This removes the manual JWT parsing and localStorage access from the page and guarantees a single place where token semantics live.

### 2. Centralize redirect handling

Right now, `AuthCallback` uses `auth_redirect` in `localStorage` and `ProtectedRoute` / `AuthProvider` also handle redirects. That’s two redirect systems.

Once `handleAuthCallback` lives in the auth client, you can also centralize redirect there:

```ts
// inside handleAuthCallback
const redirect = localStorage.getItem("auth_redirect") || "/";
localStorage.removeItem("auth_redirect");
navigate(redirect); // or update auth state so ProtectedRoute handles it
```

Then the page no longer knows about `auth_redirect`:

```tsx
// in AuthCallbackPage effect
await handleAuthCallback(searchParams);
// navigate only if the auth client doesn't already do it
```

And the login page (`SignInPage`) can just call `login(providerId)` as you already do, without worrying about redirect concerns.

### 3. Split callback vs sign‑in UI

`AuthCallback` and `SignInPage` are distinct concerns; splitting them will make each file easier to reason about and keeps this file closer to a pure UI layer.

```tsx
// src/pages/auth/AuthCallbackPage.tsx
export function AuthCallbackPage() {
  // callback-only logic + UI
}

// src/pages/auth/SignInPage.tsx
export function SignInPage() {
  // your existing sign-in UI only
}

export default SignInPage;
```

Then update your routes to use the new `AuthCallbackPage` component for the callback path.

This keeps all existing behavior (login providers list, callback flow, redirects) but pushes the complex/auth‑specific logic into the shared auth client, leaving these pages as thin UI wrappers.
</issue_to_address>

### Comment 7
<location path="apps/dockstat/src/hooks/useAuth.ts" line_range="11" />
<code_context>
+  [key: string]: unknown
+}
+
+export function useAuth() {
+  const [user, setUser] = useState<User | null>(null)
+  const [loading, setLoading] = useState(true)
</code_context>
<issue_to_address>
**issue (complexity):** Consider removing this custom `useAuth` implementation and instead re-exporting or thinly wrapping the existing shared auth hook to avoid duplicating logic.

You can reduce complexity here by removing this custom `useAuth` and delegating to the existing context-based auth API instead of reimplementing it.

Since `@dockstat/auth/client` already exposes a canonical `useAuth`, you can:

1. **Delete this local hook** and
2. **Re-export** the shared hook (or adapt it with a thin wrapper if the call sites depend on this exact shape).

For example, instead of:

```ts
// src/hooks/useAuth.ts
import { useEffect, useState } from "react"

interface User {
  sub: string
  email?: string
  name?: string
  picture?: string
  [key: string]: unknown
}

export function useAuth() {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(true)
  const [error] = useState<string | null>(null)
  // ... localStorage, login/logout redirects ...
  return { error, loading, login, logout, user }
}
```

use a re-export:

```ts
// src/hooks/useAuth.ts
export { useAuth } from "@dockstat/auth/client"
// or from "packages/auth/src/client/useAuth"
```

If consumers expect specific properties (e.g. `user`, `loading`, `error`, `login`, `logout`), keep a very thin adapter that simply forwards to the shared hook:

```ts
// src/hooks/useAuth.ts
import { useAuth as useSharedAuth } from "@dockstat/auth/client"

export function useAuth() {
  const { user, isLoading, error, login, logout } = useSharedAuth()

  return {
    user,
    loading: isLoading,
    error,
    login,
    logout,
  }
}
```

This keeps behavior aligned with `AuthProvider` (tokens, refresh, redirects) while preserving the existing call sites’ shape, avoiding two divergent auth flows.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread apps/dockstat/src/layout/index.tsx
Comment thread apps/dockstat/src/lib/api.ts Outdated
Comment thread apps/dockstat/src/router.tsx Outdated
Comment thread packages/auth/src/routes.ts Outdated
Comment thread packages/logger/src/utils.ts
Comment thread apps/dockstat/src/pages/SignIn.tsx Outdated
Comment thread apps/dockstat/src/hooks/useAuth.ts Outdated
Its4Nik added 2 commits April 22, 2026 10:48
- Add `cryptr` dependency to encrypt/decrypt OAuth client secrets.
- Update auth routes and config service to handle secret transformation.
- Introduce `CRYPTO_SECRET` environment variable for encryption key management.
- Refactor `authenticated` middleware type hint.
- Replace manual SVG with `ArrowRight` icon in `SignIn` page.
Reordered imports in config.ts and routes.ts to maintain consistency and updated type casting syntax in routes.ts for improved readability.
Copy link
Copy Markdown

@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: 11

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/logger/src/utils.ts (1)

70-77: ⚠️ Potential issue | 🟡 Minor

Validate ID length after splitting id|from, not before.

Line 70 applies the minimum-length rule to the raw combined string. Cases like "a|proxy" still pass and produce a 1-char request ID. Apply the length check after extracting the actual ID segment.

🔧 Proposed fix
 export function colorByReqID(rawReqId: string) {
-  let reqId = rawReqId.length >= 3 ? rawReqId : ""
+  let reqId = rawReqId
   let from = ""

   if (reqId.includes("|")) {
     const parts = reqId.split("|")
     reqId = String(parts[0])
     from = String(parts[1])
   }
+  reqId = reqId.length >= 3 ? reqId : ""

   const hash = stringToHash(reqId)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/logger/src/utils.ts` around lines 70 - 77, The current logic applies
the minimum-length check to rawReqId before splitting, allowing short IDs like
"a|proxy"; change the flow in the function handling rawReqId so you first split
rawReqId on "|" when present (use reqId, from variables), then validate the
length of the extracted request ID segment (reqId) and only keep it if it meets
the minimum-length requirement; update any assignments that previously set reqId
= "" based on rawReqId to instead re-evaluate after the split so the actual ID
is validated.
🟠 Major comments (18)
apps/dockstat/src/pages/settings.tsx-49-49 (1)

49-49: ⚠️ Potential issue | 🟠 Major

Restore actionable Accounts content or remove the Accounts slide for now.

Line 49 currently renders a static placeholder (<div>Accounts</div>), which leaves users at a dead-end while the UI still advertises account management (Line 30). This is a user-facing regression; either wire a minimal functional Accounts panel or temporarily remove the Accounts slide/description until it’s ready.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/pages/settings.tsx` at line 49, The "Accounts" slide
currently renders a static placeholder ("Accounts" key with
<div>Accounts</div>), leaving a dead-end; either remove this slide/entry from
the settings slides/descriptions array or replace the placeholder with a minimal
actionable Accounts panel: render current account state (e.g., "No accounts
connected" or list of connected accounts), add a "Connect account" and
optionally "Disconnect" button, and wire those buttons to your existing account
handlers (e.g., connectAccount / disconnectAccount or handleConnectAccount /
handleDisconnectAccount) so the UI actually performs actions rather than showing
a static div.
packages/auth/tsconfig.json-24-25 (1)

24-25: ⚠️ Potential issue | 🟠 Major

Reconsider disabling strictNullChecks in auth-critical code.

Setting strictNullChecks: false while strict: true is enabled explicitly overrides one of the most valuable strict checks. This is particularly concerning in authentication code where null/undefined values (e.g., missing tokens, uninitialized user state) can lead to security vulnerabilities or runtime errors.

Consider fixing the underlying type issues instead of disabling the check. If there are specific areas causing friction, you could use explicit type assertions or optional chaining locally rather than globally weakening type safety.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/tsconfig.json` around lines 24 - 25, The tsconfig overrides
disable a key safety check—remove the "strictNullChecks": false setting so
"strict": true enforces null/undefined checks; then fix the resulting type
errors in auth-related modules (e.g., authentication token parsing, user state
initialization, and functions handling optional values) by adding precise types,
optional chaining, non-null assertions only where proven safe, or explicit type
guards, targeting the files that fail compilation after re-enabling
strictNullChecks and updating functions that handle tokens/user to return
properly typed values instead of allowing null/undefined.
apps/dockstat/src/providers/index.tsx-11-11 (1)

11-11: ⚠️ Potential issue | 🟠 Major

Hardcoded localhost URL will break in non-development environments.

The apiBase is hardcoded to http://localhost:3030/api/v2, which will fail in staging, production, or any non-local deployment. This should be configurable via an environment variable.

🔧 Proposed fix
-      <AuthProvider apiBase="http://localhost:3030/api/v2">
+      <AuthProvider apiBase={import.meta.env.VITE_API_BASE_URL || "http://localhost:3030/api/v2"}>

Ensure VITE_API_BASE_URL (or equivalent) is set in your environment configuration for each deployment target.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/providers/index.tsx` at line 11, Replace the hardcoded
apiBase on AuthProvider with a configurable env var: read VITE_API_BASE_URL (or
a fallback like process.env.API_BASE_URL) and pass that value into the
AuthProvider apiBase prop instead of "http://localhost:3030/api/v2"; update any
initialization code that references AuthProvider to use the resolved env value
and ensure VITE_API_BASE_URL is documented for each deployment environment.
apps/dockstat/src/lib/api.ts-4-8 (1)

4-8: ⚠️ Potential issue | 🟠 Major

Remove token logging - security risk.

Logging the JWT token to the console exposes sensitive credentials in browser dev tools. This should be removed before production.

🔒 Proposed fix
 const getHeaders = () => {
   const token = localStorage.getItem("auth_token")
-  console.log(`Found auth token: ${token}`)
   return token ? { authorization: `Bearer ${token}` } : {}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/lib/api.ts` around lines 4 - 8, The getHeaders function
currently logs the raw JWT from localStorage
(localStorage.getItem("auth_token")) which is a security risk; remove the
console.log call so the token is never printed, and ensure getHeaders still
returns the same header shape (authorization: `Bearer ${token}`) when token
exists; update only the getHeaders function to eliminate any logging of the
token or sensitive data.
apps/api/src/index.ts-26-26 (1)

26-26: ⚠️ Potential issue | 🟠 Major

Request logging only covers authenticated requests.

RequestLogger is inside the authentication guard, so auth failures and requests to unguarded endpoints (like AuthHandler.routes) won't be logged. This creates observability gaps for debugging authentication issues and monitoring all API traffic.

Consider moving RequestLogger before the guard to capture all requests.

♻️ Proposed fix
 export const DockStatAPI = new Elysia({ precompile: false, prefix: "/api/v2" })
   .use(Middleware)
   .use(DockStatElysiaPlugins)
+  .use(RequestLogger)
   .guard(authenticated(), (app) => {
     return app
-      .use(RequestLogger)
       .use(MetricsMiddleware)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/index.ts` at line 26, Request logging is currently registered
after the authentication guard so unauthenticated requests and routes like
AuthHandler.routes aren't logged; move the RequestLogger middleware registration
so it is applied before the authentication guard is mounted (i.e., register
.use(RequestLogger) prior to the code that attaches the auth guard/middleware
and before mounting AuthHandler.routes) to ensure all incoming requests are
captured.
apps/dockstat/src/hooks/useAuth.ts-40-47 (1)

40-47: ⚠️ Potential issue | 🟠 Major

logout() may construct invalid URL when providerId is null.

If auth_provider_id was never stored in localStorage, providerId will be null, resulting in a request to /auth/null/logout which will fail. Add a guard or fallback handling.

🐛 Proposed fix
   const logout = () => {
     localStorage.removeItem("user")
     const providerId = localStorage.getItem("auth_provider_id")
     localStorage.removeItem("auth_provider_id")
-    const loc = window.location.href
     setUser(null)
-    window.location.href = `${API_BASE}/auth/${providerId}/logout?redirectUri=${loc}`
+    if (providerId) {
+      const loc = encodeURIComponent(window.location.href)
+      window.location.href = `${API_BASE}/auth/${providerId}/logout?redirectUri=${loc}`
+    } else {
+      // Fallback: just redirect to login if no provider known
+      window.location.href = "/login"
+    }
   }

Note: The redirectUri should also be URL-encoded to handle special characters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/hooks/useAuth.ts` around lines 40 - 47, logout currently
reads auth_provider_id from localStorage and builds window.location.href using
`${API_BASE}/auth/${providerId}/logout?redirectUri=${loc}`, which can produce
`/auth/null/logout` if providerId is missing and may break with unencoded
redirectUri; update the logout function to check providerId (from
localStorage.getItem("auth_provider_id")) and if missing either use a safe
default provider id or perform a local sign-out flow (remove user, setUser(null)
and navigate to a generic post-logout route) instead of calling the API with
"null", and ensure the redirectUri (loc) is URL-encoded (encodeURIComponent)
before appending; keep the removals of "user" and "auth_provider_id" and use
API_BASE and setUser as currently referenced.
packages/auth/src/index.ts-28-28 (1)

28-28: ⚠️ Potential issue | 🟠 Major

logout_url constraint mismatch with API schema.

The column is defined as notNull: true, but the route in packages/auth/src/routes.ts defines logout_url: t.MaybeEmpty(t.String()), allowing empty/null values. This will cause database INSERT failures when clients don't provide a logout_url.

Either make the column nullable or require logout_url in the API schema.

🐛 Option 1: Make column nullable (if logout_url is optional)
-        logout_url: column.text({ notNull: true }),
+        logout_url: column.text({ notNull: false }),
🐛 Option 2: Require logout_url in API (in routes.ts)
-          logout_url: t.MaybeEmpty(t.String()),
+          logout_url: t.String(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/index.ts` at line 28, The DB column definition for
logout_url in packages/auth/src/index.ts is declared with notNull: true but the
API schema in packages/auth/src/routes.ts uses logout_url:
t.MaybeEmpty(t.String()), causing a mismatch; fix by either making the column
nullable (change logout_url: column.text({ notNull: true }) to logout_url:
column.text() or column.text({ notNull: false })) so empty/null values can be
persisted, or update the route/type to require the field (change logout_url:
t.MaybeEmpty(t.String()) to logout_url: t.String() or appropriate non-empty
validator) so the API always supplies a value—pick one approach and apply it
consistently in the logout_url declaration in index.ts and the corresponding
route/schema in routes.ts.
packages/auth/src/routes.ts-19-22 (1)

19-22: ⚠️ Potential issue | 🟠 Major

Normalize logout_url the same way as scopes.

logout_url is optional here, but empty strings are passed through unchanged. The logout route later treats any non-null value as a URL, so a provider saved with logout_url: "" will fail on new URL(logoutUrl).

Also applies to: 231-233

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/routes.ts` around lines 19 - 22, Normalize the optional
logout_url the same way scopes are normalized: when calling table.insertAndGet
(and the other create/update block that mirrors lines 231-233), coerce
logout_url to null if it's an empty string instead of passing the empty string
through; i.e., check (body as { logout_url: string }).logout_url and set
logout_url to that value or null, similar to how scopes is handled, so the
downstream logout route's new URL(logoutUrl) call never receives an empty
string.
packages/auth/README.md-69-80 (1)

69-80: ⚠️ Potential issue | 🟠 Major

The schema docs no longer match the implementation.

The README describes an oidc-providers table with logout_url required, but packages/auth/src/index.ts creates a providers table and logout_url is nullable. These setup instructions will send migrations in the wrong direction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/README.md` around lines 69 - 80, The README schema is out of
sync with the code: the implementation in packages/auth/src/index.ts creates a
table named providers and makes logout_url nullable, while README documents an
oidc-providers table with logout_url REQUIRED; update packages/auth/README.md to
match the actual implementation by changing the table name to "providers" and
marking logout_url as nullable (remove NOT NULL), and ensure other field
names/types (id, issuer_url, client_id, client_secret, scopes, created_at) match
the definitions used in packages/auth/src/index.ts so migrations/instructions
remain correct.
packages/auth/src/middleware.ts-217-239 (1)

217-239: ⚠️ Potential issue | 🟠 Major

createAuthenticatedWsHandler() rejects every socket by default.

The implementation only reads a token when options.extractToken is supplied, so calling createAuthenticatedWsHandler() without arguments always ends in verifyWsToken(null) and a 1008 close.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/middleware.ts` around lines 217 - 239, The
createAuthenticatedWsHandler implementation currently only calls
options.extractToken and passes null to verifyWsToken when no extractor is
provided, causing every socket to be rejected; update
createAuthenticatedWsHandler so it falls back to a default extractor (e.g., call
a local default function or existing extractor like extractTokenFromWs) when
options.extractToken is undefined, assign its result to token before calling
verifyWsToken(token), and keep the rest of the flow (closing with 1008 if
verifyWsToken returns falsy) unchanged so unauthenticated sockets are only
rejected when no valid token is found.
apps/dockstat/src/pages/SignIn.tsx-262-284 (1)

262-284: ⚠️ Potential issue | 🟠 Major

Don't let one bad issuer_url crash the whole sign-in page.

getProviderInfo() already tolerates invalid URLs, but the card body calls new URL(provider.issuer_url) unguarded. A single malformed provider record will take down the entire provider grid.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/pages/SignIn.tsx` around lines 262 - 284, The provider card
currently calls new URL(provider.issuer_url) unguarded which can throw for
malformed issuer_url and crash the page; update the provider rendering (inside
the filteredProviders.map callback that uses
getProviderInfo(provider.issuer_url) and handleLogin(provider.id)) to safely
derive the hostname by either validating/try-catch wrapping new
URL(provider.issuer_url) or using a helper (e.g., safeHostnameFromUrl) that
returns a fallback (empty string or provider.issuer_url) when parsing fails, and
render that fallback in the <p> instead of calling new URL(...) directly so a
single bad provider record cannot break the whole grid.
packages/auth/src/client/AuthProvider.tsx-212-216 (1)

212-216: ⚠️ Potential issue | 🟠 Major

Decode the callback token as base64url, not plain base64.

atob(parts[1]) fails on valid JWT payload segments containing -, _, or omitted padding. Normalize and pad the segment before parsing so legitimate callbacks don't get rejected.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/client/AuthProvider.tsx` around lines 212 - 216, The JWT
payload is being decoded with atob on a base64url segment which fails for '-'
'_' and missing padding; in AuthProvider.tsx where token is split and parts[1]
is parsed (see variables token, parts, payload, user/User), normalize the middle
segment from base64url to base64 (replace '-'→'+' and '_'→'/'), add '=' padding
until length % 4 == 0, then atob the normalized string and JSON.parse the result
to obtain payload.user; update that decode logic accordingly so valid JWTs with
base64url encoding are accepted.
packages/auth/src/client/AuthProvider.tsx-139-171 (1)

139-171: ⚠️ Potential issue | 🟠 Major

This refresh loop will log users out after ~4 minutes.

The client polls ${apiBase}/auth/verify, but this PR's auth router does not define that endpoint, so every interval becomes a non-OK response that falls into logout(). It also assumes a 5-minute token lifetime while createAuthToken() now issues 1d JWTs.

Also applies to: 193-199

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/client/AuthProvider.tsx` around lines 139 - 171, The
token-check loop in AuthProvider.tsx calls a non-existent
`${apiBase}/auth/verify` every interval causing false non-OK responses and
logout; update the logic in the token-validation/refresh routine to (1) stop
polling a hardcoded non-existent endpoint—either call an existing endpoint (e.g.
`/auth/me` or your real refresh route) instead of `/auth/verify`, or remove the
fetch entirely and decode the current JWT (use a jwt-decode helper) to read exp
and schedule refresh only when near expiry, (2) only treat 401 responses as
token-expired (do not logout on 404/other errors), and (3) make the poll/refresh
interval derive from the token TTL (from createAuthToken() / exp) or make it
configurable rather than assuming 5 minutes; touch the functions/blocks around
AuthProvider, the token validation/fetch call, onTokenExpired, and logout to
implement these changes.
packages/auth/README.md-11-18 (1)

11-18: ⚠️ Potential issue | 🟠 Major

The documented JWT lifetime is stale.

This README still says tokens expire in 5 minutes and should be refreshed every 4 minutes, but packages/auth/src/utils/jwt.ts now issues 1d tokens. That mismatch will push integrators toward an unnecessary refresh loop.

Also applies to: 158-163, 443-447

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/README.md` around lines 11 - 18, Update the README to match the
actual token lifetime emitted by the code: replace the stale "5 minutes /
refresh every 4 minutes" wording with the current "1d" expiry and remove the
unnecessary refresh recommendation; specifically update all occurrences called
out in the comment and any other mentions; cross-check against the token
issuance in src/utils/jwt.ts (the function that signs/creates JWTs, e.g.,
signJwt/generateToken) which sets the TTL to "1d" so the README wording and
examples reflect that exact value.
packages/auth/src/client/AuthProvider.tsx-114-117 (1)

114-117: ⚠️ Potential issue | 🟠 Major

URL-encode redirectUri before concatenating it.

window.location.href often contains its own query string. Building /logout?redirectUri=${currentLocation} will truncate or corrupt the redirect target at the first & or #.

Possible fix
-        const logoutUrl = `${apiBase}/auth/${providerId}/logout?redirectUri=${currentLocation}`
+        const logoutUrl = `${apiBase}/auth/${providerId}/logout?redirectUri=${encodeURIComponent(currentLocation)}`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/client/AuthProvider.tsx` around lines 114 - 117, The logout
URL is built by concatenating currentLocation directly into logoutUrl which can
break if the location contains query params or fragments; update the code that
constructs logoutUrl (the block using providerId, logoutUrl, and currentLocation
in AuthProvider.tsx—likely inside the logout handler) to URL-encode the redirect
target using encodeURIComponent(currentLocation) before concatenation (i.e., use
?redirectUri=${encodeURIComponent(currentLocation)}) so the redirectUri query
parameter is safe.
apps/dockstat/src/pages/SignIn.tsx-67-74 (1)

67-74: ⚠️ Potential issue | 🟠 Major

Handle JWT payloads as base64url before decoding.

atob() expects padded base64, but JWT segments are base64url and often omit padding. This will throw for valid tokens from some providers and break the callback flow.

Possible fix
-      const base64Url = token.split(".")[1]
-      const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/")
+      const base64Url = token.split(".")[1]
+      const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/")
+      const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=")
       const jsonPayload = decodeURIComponent(
-        atob(base64)
+        atob(paddedBase64)
           .split("")
           .map((c) => `%${(`00${c.charCodeAt(0).toString(16)}`).slice(-2)}`)
           .join("")
       )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/pages/SignIn.tsx` around lines 67 - 74, The JWT payload
decoding assumes standard base64 and may fail for base64url without padding;
update the SignIn.tsx decoding flow that computes base64Url/base64/jsonPayload
(where token is the JWT) to first convert the base64url segment to base64 by
replacing "-"->"+" and "_"->"/" and then add the required "=" padding to make
the length a multiple of 4 before calling atob; ensure the padded base64 is what
you pass into atob so valid tokens from all providers decode correctly.
apps/dockstat/src/pages/SignIn.tsx-159-173 (1)

159-173: ⚠️ Potential issue | 🟠 Major

Clear the stale error state before retrying providers.

After the first failure, error stays truthy forever. A later successful api.auth.providers.get() call updates providers, but the page keeps rendering the error card because success never resets error.

Possible fix
   const fetchProviders = async () => {
     try {
+      setError(null)
       setProvidersLoading(true)
       const response = await api.auth.providers.get()
       if (response.status === 200 && response.data) {
         setProviders(response.data)
+        setError(null)
       } else {
         setError("Failed to load authentication providers")
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/pages/SignIn.tsx` around lines 159 - 173, In
fetchProviders, clear the previous error before attempting the network call so a
later successful response removes the error UI: call setError(null) (or
setError("")) right before or immediately after setProvidersLoading(true) in the
fetchProviders function so on success the providers UI renders without the stale
error; ensure you reference the existing state setters setError and setProviders
in fetchProviders and do not remove the existing try/catch/finally logic.
packages/auth/src/routes.ts-69-76 (1)

69-76: ⚠️ Potential issue | 🟠 Major

Use scope, not scopes, in the authorization request parameters.

The buildAuthorizationUrl() function expects the standard OAuth 2.0 authorization parameter scope (singular) as a space-delimited string, per OpenID Connect specification. The code currently passes scopes as a key, which causes the scope parameter to be omitted from the authorization URL sent to the provider. This can result in providers rejecting the request or failing to include requested claims (such as openid) in the token response.

Change line 74 from scopes, to scope: scopes, (or rename the variable if needed).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/routes.ts` around lines 69 - 76, The params object for the
authorization request is using the wrong key: change the parameter key from
"scopes" to "scope" so the authorization URL includes the space-delimited scope
string expected by buildAuthorizationUrl; update the params declaration (the
object containing code_challenge, code_challenge_method, nonce, redirect_uri,
scopes, state) to use scope: scopes (or rename the variable from scopes to scope
and pass scope) so buildAuthorizationUrl receives the proper OAuth/OpenID
Connect parameter.
🟡 Minor comments (7)
packages/auth/src/client/protectedRoute.tsx-22-30 (1)

22-30: ⚠️ Potential issue | 🟡 Minor

Query parameters and hash are not preserved on redirect.

Only window.location.pathname is stored. If a user accesses /settings?tab=security#advanced while unauthenticated, they'll be redirected to just /settings after login, losing the ?tab=security#advanced portion.

🔧 Proposed fix
   if (!isAuthenticated || !user) {
     // Save current location for post-login redirect
-    localStorage.setItem("auth_redirect", window.location.pathname)
+    localStorage.setItem("auth_redirect", window.location.pathname + window.location.search + window.location.hash)
     return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/client/protectedRoute.tsx` around lines 22 - 30, The
redirect currently saves only window.location.pathname so query string and hash
are dropped; update the logic in the ProtectedRoute (where isAuthenticated/user
are checked and localStorage.setItem("auth_redirect", ... ) is called) to save
the full relative URL by concatenating window.location.pathname +
window.location.search + window.location.hash (or otherwise constructing the
full path) so users are redirected back including query parameters and fragment
after login.
packages/ui/src/components/Sidebar/Sidebar.tsx-148-155 (1)

148-155: ⚠️ Potential issue | 🟡 Minor

Add aria-label for accessibility.

The logout button only contains an icon without any text or accessible label. Screen reader users won't know what this button does.

♿ Proposed fix
                   <Button
+                    aria-label="Logout"
                     className="h-8 w-8 p-0 mr-2"
                     onClick={auth.logout}
                     size="sm"
                     variant="ghost"
                   >
                     <LogOut size={16} />
                   </Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/components/Sidebar/Sidebar.tsx` around lines 148 - 155, The
logout Button in Sidebar (the Button with onClick={auth.logout} that renders the
<LogOut /> icon) lacks an accessible name; add an aria-label prop (e.g.
aria-label="Log out" or an i18n string) to the Button so screen readers announce
its purpose; update the Button component where <LogOut /> is rendered to include
aria-label and ensure it remains visually unchanged.
apps/dockstat/src/contexts/queryClient.ts-9-12 (1)

9-12: ⚠️ Potential issue | 🟡 Minor

Misleading comment contradicts the actual configuration.

The comment states "Don't refetch on window focus, mount, or reconnect" but refetchOnMount and refetchOnReconnect are both set to true. Update the comment to accurately reflect the behavior.

📝 Proposed fix
       refetchOnMount: true,
       refetchOnReconnect: true,
-      // Don't refetch on window focus, mount, or reconnect - only when explicitly invalidated
+      // Don't refetch on window focus - refetch on mount and reconnect
       refetchOnWindowFocus: false,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/contexts/queryClient.ts` around lines 9 - 12, The inline
comment incorrectly states "Don't refetch on window focus, mount, or reconnect"
while the config sets refetchOnMount: true and refetchOnReconnect: true; update
the comment above refetchOnWindowFocus/refetchOnMount/refetchOnReconnect to
accurately describe current behavior (e.g., "Refetch on mount and reconnect, but
not on window focus") or change the boolean flags to match the original
intent—modify the comment to reference refetchOnMount, refetchOnReconnect, and
refetchOnWindowFocus so it clearly reflects the actual configuration.
apps/dockstat/src/lib/protectedRoute.tsx-17-39 (1)

17-39: ⚠️ Potential issue | 🟡 Minor

Missing key props on dynamically rendered Route elements.

React requires unique key props when rendering arrays of elements. Both protectedRoutes and routes maps are missing keys, which will cause React warnings and may lead to unexpected re-rendering behavior.

💡 Proposed fix
       {(protectedRoutes ?? []).map((r) => {
         return (
           <Route
+            key={r.path}
             element={
               <PRoute
                 loadingComponent={r.loadingComponent}
                 redirectTo={"/login"}
               >
                 {r.element}
               </PRoute>
             }
             path={r.path}
           />
         )
       })}
       {(routes ?? []).map((r) => {
         return (
           <Route
+            key={r.path}
             element={r.element}
             path={r.path}
           />
         )
       })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/lib/protectedRoute.tsx` around lines 17 - 39, The two array
renders mapping over protectedRoutes and routes are missing React key props on
the Route elements; update both map callbacks that return <Route ... /> to
include a unique key (e.g., key={r.path} or key={r.key} and fall back to the
array index if necessary) so each Route rendered by the protectedRoute component
receives a stable unique key; apply this to the Route inside the protectedRoutes
map (the one wrapping PRoute) and the Route inside the routes map.
packages/auth/README.md-245-253 (1)

245-253: ⚠️ Potential issue | 🟡 Minor

Several examples won't render or compile as written.

The guard example has invalid chaining syntax, the legacy section opens nested fences, and the callback section starts with a doubled code fence. Those issues are already surfacing in markdownlint and make the examples unsafe to copy-paste.

Also applies to: 450-460, 557-565

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/README.md` around lines 245 - 253, The README examples contain
invalid chaining and malformed fenced-code blocks; fix the guard snippet to use
valid callback chaining by calling guard(authenticated(), (app) =>
app.get("/protected", ({ user }) => `Hello, ${user.name}!`)) (referencing
Elysia, createAuthMiddleware, guard, authenticated and get), close and not nest
triple-backtick fences in the legacy section, and remove the doubled code-fence
at the start of the callback section so all fenced blocks open and close exactly
once; apply the same cleanup to the other occurrences noted (lines ~450-460 and
~557-565).
apps/dockstat/src/pages/SignIn.tsx-288-293 (1)

288-293: ⚠️ Potential issue | 🟡 Minor

Add an accessible name or mark the arrow SVG decorative.

This is the current Biome a11y failure. If the icon is decorative, add aria-hidden="true"; otherwise provide a <title>.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/pages/SignIn.tsx` around lines 288 - 293, The SVG inside
the SignIn component is missing an accessible name; either mark it decorative by
adding aria-hidden="true" to the <svg> element (the one with className
"flex-shrink-0 w-5 h-5 text-secondary-text") or provide an accessible name by
adding a <title> element inside the same <svg> and ensure the svg has role="img"
and aria-labelledby referencing that title; update the SVG accordingly in
SignIn.tsx.
packages/auth/src/middleware.ts-91-99 (1)

91-99: ⚠️ Potential issue | 🟡 Minor

Replace the any type with the explicit AuthContext shape.

The any type at line 98 is a lint blocker and loses type safety. The AuthContext interface is already defined in this file (lines 16-19) with the required isAuthenticated and user properties. Type the context parameter as AuthContext & { set: any } or define a more specific type that includes Elysia's set property to restore type safety for destructured values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/middleware.ts` around lines 91 - 99, Replace the loose any
in the authenticated middleware's beforeHandle parameter with the explicit
AuthContext shape: change the parameter type used in beforeHandle to AuthContext
& { set: any } (or create a small interface that extends AuthContext with the
set method) so the destructured { isAuthenticated, set } uses the defined
AuthContext types; update the beforeHandle signature in the authenticated
function accordingly and import/ reference the existing AuthContext interface
defined earlier in this file.
🧹 Nitpick comments (9)
packages/auth/package.json (1)

21-25: Consider adding react to peerDependenciesMeta as optional.

The ./client export uses React components (AuthProvider, hooks), but react isn't listed in peerDependencies or marked as optional. If a server-only consumer imports from the root . export, they shouldn't need React installed. Consider adding React as an optional peer dependency for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/package.json` around lines 21 - 25, The package.json should
mark React as an optional peer so server-only consumers importing the root
export don't need React installed; add an entry for "react": { "optional": true
} under "peerDependenciesMeta" in the package.json and ensure this aligns with
the fact that the ./client export exposes React items like AuthProvider and
hooks (so only clients will require React). Update the peerDependenciesMeta
section to include "react" as optional and verify the consumer-facing ./client
exports remain unchanged.
apps/dockstat/tsconfig.json (1)

14-14: Consider adopting the two-pattern approach for consistency with apps/api/tsconfig.json.

All imports of @dockstat/auth in the app use subpath imports (@dockstat/auth/client), so the current wildcard pattern works correctly. However, for consistency with apps/api/tsconfig.json and to support potential bare imports in the future, consider splitting the pattern:

♻️ Suggested pattern
     "paths": {
       "@/*": ["./src/*"],
       "@dockstat/api": ["../api/src/*"],
-      "@dockstat/auth": ["../../packages/auth/src/*"],
+      "@dockstat/auth": ["../../packages/auth/src/index.ts"],
+      "@dockstat/auth/*": ["../../packages/auth/src/*"],
       "@WSS": ["./src/lib/websocketEffects/index.ts"],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/tsconfig.json` at line 14, Update the tsconfig path mapping for
the "@dockstat/auth" entry to use the two-pattern approach: keep the existing
wildcard mapping for subpath imports and add an additional pattern that points
to the package root to support bare imports; modify the "@dockstat/auth" paths
in apps/dockstat/tsconfig.json so both the client subpath (e.g., client/*) and
the package root are covered (mirror the approach used in
apps/api/tsconfig.json).
apps/api/src/elysia-plugins.ts (1)

8-10: Consider explicitly setting CORS origin for clarity.

Line 9 enables credentialed CORS. The @elysiajs/cors library by default reflects the request Origin header when credentials: true, which safely supports cross-origin requests. However, explicitly setting the origin improves clarity and avoids relying on default behavior.

Suggested approach (optional)
 .use(
   cors({
     credentials: true,
+    origin: Bun.env.FRONTEND_URL ?? "http://localhost:3000",
   })
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/elysia-plugins.ts` around lines 8 - 10, The cors plugin call
currently only sets credentials: true (cors({ credentials: true })) which relies
on default origin reflection; update the cors configuration to explicitly set
the allowed origin(s) instead of relying on the default—e.g., provide a static
origin string or a origin-check function that reads allowed origins from
env/config and returns the request Origin when allowed; modify the cors(...)
invocation in elysia-plugins.ts to include this explicit origin setting
alongside credentials to make allowed origins deterministic and clear.
apps/dockstat/src/router.tsx (1)

23-24: Inconsistent path naming: /clients vs /client/configure.

The clients index route uses plural /clients but the configure route uses singular /client/configure. Consider aligning these for consistency and predictable URL structure.

♻️ Suggested fix
         { element: <ClientsPage />, path: "/clients" },
-        { element: <ConfigureClientsPage />, path: "/client/configure" },
+        { element: <ConfigureClientsPage />, path: "/clients/configure" },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/router.tsx` around lines 23 - 24, Route paths are
inconsistent: the index route uses plural "/clients" while ConfigureClientsPage
is mounted at singular "/client/configure"; update the route definition for
ConfigureClientsPage to use a consistent plural path (e.g.,
"/clients/configure") so URLs align with ClientsPage, changing the path string
in the router where ConfigureClientsPage is referenced.
packages/auth/src/utils/jwt.ts (1)

4-11: Consider typing userInfo parameter.

While the biome-ignore is acceptable for flexibility, consider defining at minimum an interface for the expected user properties (e.g., sub, email, name) to improve maintainability and catch integration issues early.

💡 Suggested improvement
+interface UserInfo {
+  sub?: string
+  email?: string
+  name?: string
+  [key: string]: unknown
+}
+
-// biome-ignore lint/suspicious/noExplicitAny: a
-export async function createAuthToken(userInfo: any): Promise<string> {
+export async function createAuthToken(userInfo: UserInfo): Promise<string> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/utils/jwt.ts` around lines 4 - 11, Define a minimal typed
interface for the expected user payload (e.g., AuthUser with properties like
sub: string, email?: string, name?: string) and change the createAuthToken
signature from userInfo: any to userInfo: AuthUser; update usages of
SignJWT/SignJWT payload typing if applicable so the token payload is strongly
typed (referencing createAuthToken, SignJWT and JWT_SECRET to locate the code)
to improve type safety and maintainability.
apps/api/src/routes/plugins/index.ts (1)

69-69: Unnecessary type cast bypasses validation.

The cast body as DBPluginShemaT is redundant if PluginModel.installPluginBody already matches DBPluginShemaT. If they differ, this cast hides a potential runtime mismatch between the validated body and what savePlugin expects. Consider aligning the body schema with DBPluginShema directly or removing the cast.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/routes/plugins/index.ts` at line 69, The handler for the POST
/install route is casting the request body to DBPluginShemaT which bypasses
validation; update the route to pass the validated body type instead of forcing
a cast: either remove the cast and pass body directly to
PluginHandler.savePlugin if PluginModel.installPluginBody already produces
DBPluginShemaT, or adjust PluginModel.installPluginBody to match DBPluginShemaT
and then pass the typed result; ensure the call to PluginHandler.savePlugin uses
the actual validated object (refer to .post("/install", ({ body }) =>
PluginHandler.savePlugin(...)), PluginModel.installPluginBody, and
DBPluginShemaT).
apps/dockstat/src/hooks/useAuth.ts (1)

14-14: Unused error state.

The error state is declared but setError is never called anywhere in the hook. Either remove the unused state or implement error handling for failed operations.

♻️ Remove unused state or implement error handling
-  const [error] = useState<string | null>(null)
+  const [error, setError] = useState<string | null>(null)
+
+  // Then use setError in try/catch blocks, e.g.:
+  // } catch (e) {
+  //   setError(e instanceof Error ? e.message : "Authentication failed")
+  // }

Or simply remove if not needed:

-  const [error] = useState<string | null>(null)
...
-  return { error, loading, login, logout, user }
+  return { loading, login, logout, user }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dockstat/src/hooks/useAuth.ts` at line 14, The hook declares an unused
state "error" (const [error] = useState<string | null>(null)) inside useAuth but
never updates it via a setError, so either remove the unused state or add proper
error handling: either delete the declaration entirely to avoid dead state, or
change it to const [error, setError] = useState<string | null>(null) and call
setError(...) in catch blocks or failure branches inside useAuth (where async
ops like login, fetchUser, or token refresh occur) and expose error from the
hook return so callers can consume it.
packages/auth/src/config.ts (2)

32-43: OIDC configuration cache has no expiration.

The issuerCache stores discovered OIDC configurations indefinitely. OIDC providers may rotate keys or update endpoints, and stale cached metadata could cause authentication failures. Consider adding a TTL or periodic cache invalidation.

♻️ Example: Add TTL to cache entries
-  issuerCache = new Map<string, client.Configuration>()
+  issuerCache = new Map<string, { config: client.Configuration; cachedAt: number }>()
+  private readonly CACHE_TTL_MS = 60 * 60 * 1000 // 1 hour

   // In getConfig:
-    let meta = this.issuerCache.get(row.issuer_url)
+    const cached = this.issuerCache.get(row.issuer_url)
+    let meta = cached && (Date.now() - cached.cachedAt < this.CACHE_TTL_MS) 
+      ? cached.config 
+      : undefined

   // When caching:
-      this.issuerCache.set(row.issuer_url, meta)
+      this.issuerCache.set(row.issuer_url, { config: meta, cachedAt: Date.now() })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/config.ts` around lines 32 - 43, The issuerCache currently
holds OIDC metadata indefinitely, which can become stale; update the caching
logic around issuerCache and client.discovery so each cache entry stores a
timestamp (or TTL) and meta object, check the age before using cached meta in
the block that reads this. If the cached entry is expired, call
client.discovery(new URL(row.issuer_url), row.client_id, row.client_secret)
again, replace the cache entry (including new timestamp/expiry), and log that
discovery was refreshed (you can still log token_endpoint via
meta.serverMetadata().token_endpoint); ensure the expiry/TTL value is
configurable or set to a sensible default.

25-30: Verbose logging on every config fetch may impact performance and clutter logs.

These INFO-level logs fire on every getConfig() call, which happens per authentication request. Consider using DEBUG level for the detailed configuration dump, keeping only essential info at INFO.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/config.ts` around lines 25 - 30, The current getConfig()
implementation logs full OAuth details at INFO every call (lines showing
this.logger.info and fields like providerId, BASE_URL, row.issuer_url,
row.client_id, row.client_secret, row.scopes); change these verbose calls to use
DEBUG level instead (e.g., this.logger.debug) and leave only essential
high-level info (if any) at INFO to avoid per-request log noise, keeping the
existing redaction of client_secret unchanged; update calls in the
function/method that contains getConfig() and any related logger.info
invocations for providerId/BASE_URL to use the debug log level.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2263e21b-1a57-4678-85cd-fcc1cd93e336

📥 Commits

Reviewing files that changed from the base of the PR and between 67412d8 and 08ada01.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (62)
  • apps/api/package.json
  • apps/api/src/auth.ts
  • apps/api/src/auth/db.ts
  • apps/api/src/auth/index.ts
  • apps/api/src/database/index.ts
  • apps/api/src/elysia-plugins.ts
  • apps/api/src/env.d.ts
  • apps/api/src/index.ts
  • apps/api/src/models/plugins.ts
  • apps/api/src/routes/docker/client.ts
  • apps/api/src/routes/metrics/prometheus.ts
  • apps/api/src/routes/misc/index.ts
  • apps/api/src/routes/plugins/frontend.ts
  • apps/api/src/routes/plugins/index.ts
  • apps/api/src/routes/repositories/index.ts
  • apps/api/tsconfig.json
  • apps/dockstat/package.json
  • apps/dockstat/src/components/settings/accounts/index.tsx
  • apps/dockstat/src/components/settings/accounts/sections/adminModal.tsx
  • apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx
  • apps/dockstat/src/components/settings/accounts/sections/oauthProviders.tsx
  • apps/dockstat/src/components/settings/accounts/sections/useAccounts.tsx
  • apps/dockstat/src/components/settings/accounts/sections/users.tsx
  • apps/dockstat/src/contexts/queryClient.ts
  • apps/dockstat/src/hooks/useAuth.ts
  • apps/dockstat/src/layout/index.tsx
  • apps/dockstat/src/lib/api.ts
  • apps/dockstat/src/lib/protectedRoute.tsx
  • apps/dockstat/src/pages/SignIn.tsx
  • apps/dockstat/src/pages/settings.tsx
  • apps/dockstat/src/providers/index.tsx
  • apps/dockstat/src/router.tsx
  • apps/dockstat/tsconfig.json
  • package.json
  • packages/auth/README.md
  • packages/auth/package.json
  • packages/auth/src/client/AuthProvider.tsx
  • packages/auth/src/client/index.ts
  • packages/auth/src/client/index.tsx
  • packages/auth/src/client/protectedRoute.tsx
  • packages/auth/src/client/useAuth.tsx
  • packages/auth/src/config.ts
  • packages/auth/src/env.d.ts
  • packages/auth/src/index.ts
  • packages/auth/src/middleware.ts
  • packages/auth/src/routes.ts
  • packages/auth/src/server/db/index.ts
  • packages/auth/src/server/db/types.ts
  • packages/auth/src/server/db/utils.ts
  • packages/auth/src/server/handler/index.ts
  • packages/auth/src/server/index.ts
  • packages/auth/src/types.ts
  • packages/auth/src/utils/env.ts
  • packages/auth/src/utils/jwt.ts
  • packages/auth/tsconfig.json
  • packages/logger/src/Logger.ts
  • packages/logger/src/utils.ts
  • packages/sqlite-wrapper/src/index.ts
  • packages/sqlite-wrapper/src/lib/index/createIndex.ts
  • packages/sqlite-wrapper/src/types.ts
  • packages/ui/src/components/Navbar/Navbar.tsx
  • packages/ui/src/components/Sidebar/Sidebar.tsx
💤 Files with no reviewable changes (14)
  • apps/api/src/auth/db.ts
  • apps/api/src/database/index.ts
  • packages/auth/src/server/handler/index.ts
  • packages/auth/src/server/db/index.ts
  • packages/auth/src/server/db/utils.ts
  • apps/dockstat/src/components/settings/accounts/sections/users.tsx
  • packages/auth/src/server/db/types.ts
  • apps/dockstat/src/components/settings/accounts/sections/oauthProviders.tsx
  • apps/api/src/auth/index.ts
  • apps/dockstat/src/components/settings/accounts/sections/adminModal.tsx
  • apps/dockstat/src/components/settings/accounts/sections/apiKeys.tsx
  • apps/dockstat/src/components/settings/accounts/sections/useAccounts.tsx
  • apps/dockstat/src/components/settings/accounts/index.tsx
  • apps/api/src/routes/docker/client.ts

Comment thread apps/api/src/index.ts Outdated
Comment thread apps/api/src/routes/misc/index.ts Outdated
Comment thread apps/api/src/routes/plugins/index.ts
Comment thread apps/dockstat/src/lib/api.ts Outdated
Comment thread packages/auth/package.json
Comment on lines +1 to +7
declare module "bun" {
interface Env {
BASE_URL: string
JWT_SECRET: string
FRONTEND_URL: string
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how JWT_SECRET is used and if there's startup validation
cat packages/auth/src/utils/env.ts

Repository: Its4Nik/DockStat

Length of output: 372


🏁 Script executed:

# Find all files using JWT_SECRET to understand how it's used
rg "JWT_SECRET" --type ts --type js -B 2 -A 2

Repository: Its4Nik/DockStat

Length of output: 1580


🏁 Script executed:

# Check for any startup/initialization files that might validate environment variables
fd -e ts -e js | xargs rg "throw|error|validate" | grep -i "env\|secret" | head -20

Repository: Its4Nik/DockStat

Length of output: 2086


🏁 Script executed:

# Look for any package.json scripts or configuration that might indicate env validation
cat packages/auth/package.json 2>/dev/null || echo "File not found"

Repository: Its4Nik/DockStat

Length of output: 628


🏁 Script executed:

# Check the structure of packages/auth to understand the context better
find packages/auth -type f -name "*.ts" -o -name "*.js" | head -20

Repository: Its4Nik/DockStat

Length of output: 290


Type declaration mismatches runtime behavior; JWT_SECRET fallback is a critical security risk.

Two issues:

  1. Type/runtime mismatch: These are declared as required string, but packages/auth/src/utils/env.ts provides fallback defaults for all three via the || operator. TypeScript won't warn about missing environment variables at compile time, and the fallbacks silently take effect at runtime.

  2. Critical security risk: The JWT_SECRET fallback is "your-secret-key-change-in-production". This hardcoded weak secret is used directly in SignJWT.sign() and jwtVerify(). If JWT_SECRET is not set in the environment, tokens become trivially forgeable in production. The type declaration falsely suggests this is required.

Fix: Either throw at startup if JWT_SECRET is not set (recommended), or make the type string | undefined and conditionally fail/reject token operations if the proper secret is not available.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/env.d.ts` around lines 1 - 7, The Env declaration currently
marks BASE_URL, JWT_SECRET, FRONTEND_URL as required strings but runtime code in
packages/auth/src/utils/env.ts provides fallbacks and a hardcoded JWT_SECRET;
change the contract and runtime to eliminate the unsafe fallback: update the
module "bun" interface Env to make JWT_SECRET string | undefined (and optionally
BASE_URL/FRONTEND_URL as undefined if they also have fallbacks), then modify the
codepaths that use JWT_SECRET (notably where SignJWT.sign and jwtVerify are
called) to explicitly check for presence of process.env.JWT_SECRET and
throw/exit at startup (or reject token operations) with a clear error if it's
missing instead of using "your-secret-key-change-in-production". Ensure all
references to JWT_SECRET are updated to the non-null-checked flow so there are
no implicit fallbacks.

Comment on lines +16 to +36
.post(
"/providers",
({ body }) =>
table.insertAndGet({
...(body as object),
scopes: (body as { scopes: string }).scopes ? (body as { scopes: string }).scopes : null,
}),
{
body: t.Object({
client_id: t.String(),
client_secret: t.String(),
issuer_url: t.String(),
logout_url: t.MaybeEmpty(t.String()),
scopes: t.MaybeEmpty(t.String()),
}),
detail: {
description: "Create a new OAuth/OIDC provider",
summary: "Create Provider",
},
}
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Protect provider management separately from the public auth flow.

As written, anyone who can reach this router can enumerate providers and register new ones. Because login/callback/logout must stay public, these management endpoints need their own guard or a separate protected router.

Also applies to: 206-215

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth/src/routes.ts` around lines 16 - 36, The provider management
endpoints (e.g., the POST handler for "/providers" that calls table.insertAndGet
and the similar routes around lines 206-215) are currently exposed on the public
auth router; move or protect them behind an admin/auth guard by either mounting
them on a separate protected router or wrapping the handlers with an
authorization middleware/check (e.g., requireAdmin or ensureAuthenticated) so
only authorized users can call the provider management handlers; update the
route definitions (the "/providers" POST and the provider-listing endpoints) to
use that guard and return 401/403 when unauthorized.

Comment thread packages/auth/src/routes.ts Outdated
Comment thread packages/auth/src/routes.ts Outdated
Comment thread packages/auth/src/utils/env.ts Outdated
Its4Nik added 5 commits April 22, 2026 11:57
- Introduce `LocalUsersTable` and implement database storage for local accounts
- Add registration, login, and exists check endpoints to the Auth service
- Implement password hashing using argon2id and secure token generation
- Update encryption utility to support asynchronous operations
- Add UI for local login on the SignIn page, conditionally displayed if local users exist
- Update AuthHandler to manage user repository and provide routes
- Refactor authentication middleware to use compatible schema types
- Add `api-keys` table to `AuthHandler` for storing hashed API keys
- Implement API key validation middleware in `packages/auth`
- Add support for `Authorization: Api-Key <key>` and `X-API-Key` headers
- Add `/api-keys` CRUD routes for key generation, listing, and revocation
- Create `SignInPage` and `AuthCallback` pages in `apps/dockstat`
- Add UI logic to hide sidebar/navbar on the login route
- Update `CommandResult` type definition in `apps/docknode`
…[DS-000]

- Remove unused CommandResult import in docknode/index.ts.
- Define explicit ApiClient type for the Elysia eden treaty in dockstat/lib/api.ts.
- Replace label elements with p tags in SignIn.tsx to resolve hydration/DOM nesting warnings.
- Remove unused redirect parameter from the login auth route handler.
- Introduced a modernized, dark-themed login interface with animated background orbs and glassmorphism effects.
- Integrated brand logo into the authentication flow.
- Refactored layout to handle conditional padding for the login page specifically.
- Enhanced the visual feedback for loading, error, and provider-selection states.
- Cleaned up redundant comments and improved input field styling.
- Add new `SignInPage` component with `AnimatedIconBackground` and `HeroPanel`
- Add `FeaturePill`, `LocalLoginForm`, and `ProviderList` components for modular auth UI
- Integrate `react-simple-icons` for provider branding
- Remove legacy `apps/dockstat/src/pages/auth/SignIn.tsx`
- Add `DOCKSTAT_API_PORT` to API env and update `auth` package env keys to `DOCKSTAT_AUTH_*`
- Clean up Dockerfile and minor layout component styles
Its4Nik added 17 commits April 22, 2026 20:58
- Introduce `getAuthHeaders` utility to retrieve and format Authorization headers.
- Update `eden` query and mutation hooks to support passing custom `opts` (headers).
- Refactor all API service calls and mutation definitions to include auth headers.
- Remove redundant/deprecated Eden helper files in favor of unified `opts` support.
- Improve error feedback in `AddClient` component.
- Fix client secret decryption issue in OIDC config.
…ogging

[#DS-001]

- Refactor RequestLogger into a factory function to support per-request state tracking.
- Add comprehensive error logging with duration tracking for API requests.
- Introduce `LocalRegistration` component and user mutation hooks for the sign-in page.
- Add `/auth/local/register` and `/auth/local/allow-guest` endpoints to the AuthHandler.
- Add administrative control to toggle guest registration.
- Optimize `SignInBg` component by switching from Framer Motion to pure CSS animations for improved performance.
- Update `AuthHandler` to manage guest registration status dynamically.
- Update database default configuration schema to include registration settings.
…grade Eden client

- Refactored `requestLogger` to use a `WeakMap` for per-request state tracking (`startTime` and `reqId`).
- Updated `AuthHandler` and middleware to inject the state map, enabling context-aware logging and auth checks.
- Overhauled `EdenClient` to handle automatic authorization headers and unified toast notifications for mutations.
- Updated `onError` handler to globalize error processing.
- Enhanced API registration logic to include automated onboarding for the first user.
…AUTH-001]

- Replace manual header injection and `eden` global object usage with `EdenClientContext` throughout the codebase.
- Implement token management via `EdenClientContext` in `Layout`, `AuthCallback`, and authentication-related hooks.
- Remove redundant `useAuth` hook and consolidate header management logic.
- Standardize `eden.query` and `eden.mutate` calls to consume the client from context.
* Adjust dev scripts and turbo config

- Use `docker-compose` syntax in docker-up script
- Silence docker output when running API dev server
- Add global passthrough for DOCKSTAT_LOGGER_IGNORE_MESSAGES

* Update tooling and clean up code

- Switch to `docker compose` v2
- Expand globalPassThroughEnv in turbo.json
- Remove unused variables and imports
- Use template literals for strings
- Fix void return in AuthHandler
- Add Biome ignore comments
This prevents token leakage through browser history, referrer
headers, and server logs. Also adds auth requirements to provider
endpoints, improves JWT secret validation, fixes CPU calculation
units, and cleans up excessive OAuth logging.
* Pass OAuth token via HttpOnly cookie instead of URL

This prevents token leakage through browser history, referrer
headers, and server logs. Also adds auth requirements to provider
endpoints, improves JWT secret validation, fixes CPU calculation
units, and cleans up excessive OAuth logging.

* Migrate to bun-types and update Bun configuration

* Prefix auth env vars and refactor timeout

---------

Signed-off-by: ItsNik <info@itsnik.de>
Combine login and registration into tabs
Inline form components into SignIn page
Update ProviderList styling and empty state
Allow unauthenticated access to providers endpoint
Update auth package dependencies and environment variables
Replace framer-motion and complex animations
with lightweight CSS. Add more provider icons
and update sign-in page logic.
- Add name and icon fields to providers table and API
- Add delete endpoints for providers and users
- Add list users endpoint
- Improve auth callback page with animated background
- Replace accounts settings placeholder with component
- Fix Tailwind important syntax for v4 compatibility
- Treat auth callback routes as login pages in layout
Adds UI sections for managing local users, API keys, and
OAuth providers. Includes query and mutation hooks for
account data operations.
Add server-side token verification via /auth/verify endpoint,
replacing client-side JWT decoding. Also auto-select the register
tab when no local users exist and add API key security scheme
to OpenAPI docs.
Replace hardcoded URL-based provider icon detection with
database-driven icon mapping. Centralize provider types in
the shared @dockstat/auth package.
- Add enableRegistration setting with live toggle in general settings
- Pass allowGuestRegistration to AuthHandler and support runtime updates
- Add create local user dialog with validation in accounts section
- Fix auth cookie path and send token via Authorization header in
  callback verification
- Extract reusable AdditionalSettingsCard component
- Memoize active/revoked API key filtering
- Add null check for OAuth provider icons
Display dynamic error messages in HeroPanel with auto-dismiss. Add
parseApiDate utility to handle ISO strings, Unix timestamps, and Date
objects. Serialize Unix timestamps to ISO format in backend responses.
Enhance background icon animations with multi-axis drifting. Prevent
users from deleting their own account in settings.
- Add error handler middleware with request ID logging
- Refactor auth forms: remove Card wrapper, centralize error handling
- Move SignIn page to separate component
- Add minimal development script
- Update error display utilities and imports
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant