Skip to content

Identity Provider Authentication Integration - #95

Merged
johlju merged 67 commits into
mainfrom
main-with-sqlserver-and-auth
Apr 24, 2026
Merged

Identity Provider Authentication Integration#95
johlju merged 67 commits into
mainfrom
main-with-sqlserver-and-auth

Conversation

@johlju

@johlju johlju commented Apr 22, 2026

Copy link
Copy Markdown
Member

Description

This branch is based on the branch for the PR #92. We can merge this PR in instead of PR #92.

Screenshots (if applicable)

Related Issues

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement (improves performance without changing functionality)
  • Dependency update (updating libraries or tools)

Testing

  • npm run check passes locally
  • All existing tests still pass
  • Manual testing completed
  • UI tested on desktop and mobile (if applicable)

Checklist

  • Documentation updated as needed

Checklist

  • Code follows the project style guidelines (Biome)
  • Tests added/updated as needed
  • Self-review of code completed
  • Comments added for complex logic
  • No hardcoded strings (use translations if i18n is added)

This change is Reviewable

johlju and others added 25 commits April 20, 2026 14:18
- Updated SKILL.md to reflect changes in code structure and specifications.
- Modified .vscode/settings.json for database connection settings.
- Revised AGENTS.md to clarify UI change documentation.
- Adjusted CONTRIBUTING.md to align with the new SQL Server workflow.
- Updated README.md to reflect the current tech stack and development setup.
- Revised architecture documentation (arkitekturbeskrivning-kravhantering.md) for SQL Server integration.
- Updated database schema documentation (database-schema.md) to reflect TypeORM usage.
- Revised GitHub Codespaces documentation for SQL Server setup.
- Updated MCP server documentation (mcp-server-contributor-guide.md and mcp-server-user-guide.md) for SQL Server integration.
- Adjusted reference data documentation (reference-data-and-ai.md) for TypeORM changes.
- Updated SQL Server developer workflow documentation (sql-server-developer-workflow.md) for new commands and environment variables.
- Revised quality testing documentation (QUALITY.md) to reflect the current implementation.
- Deleted the requirements DAL lifecycle test file due to redundancy.
- Updated database mock implementations in various test files to use `getRequestSqlServerDataSource` instead of `getRequestDatabase`.
- Removed the requirements route test file as it was no longer needed.
- Adjusted the risk levels and taxonomy routes tests to reflect the new database mock.
- Refactored the UI settings tests to use a mock SQL Server database and updated terminology loading and saving logic.
…DP management

feat: add new dependencies for session management and OpenID Connect support
- Added centralized auth configuration to manage environment variables and authentication settings.
- Introduced login state management using signed cookies for PKCE verifier and OIDC state.
- Created functions to verify MCP bearer tokens and handle JWT verification.
- Implemented OIDC discovery and configuration caching for efficient token handling.
- Enhanced role parsing from OIDC claims to support various formats and legacy shapes.
- Integrated session management with iron-session to store minimal validated OIDC claims.
- Updated navigation component to include authentication menu for both desktop and mobile views.
- Added Docker Compose configuration for local OIDC IdP development using Keycloak.
- Updated internationalization messages to include authentication-related strings.
- Enhanced middleware to enforce authentication for API routes and handle unauthenticated requests.
- Added tests and setup for authentication-related functionalities.
@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a complete OIDC-based authentication stack: dev Keycloak IdP and devcontainer forwarding; server/browser PKCE login, callback, logout; short-lived login-state and encrypted session cookies; MCP Bearer-token JWT verification with JWKS caching; centralized auth libraries (config/oidc/session/mcp/roles/hsa-id/csrf/audit); proxy enforcement and header stripping; many client components migrated to apiFetch; extensive tests and docs/i18n updates.

Changes

Cohort / File(s) Summary
Devcontainer & local IdP
\.devcontainer/.env.example, \.devcontainer/Dockerfile, \.devcontainer/devcontainer.json, \.devcontainer/docker-compose.yml, \.devcontainer/elevated/devcontainer.json, \.devcontainer/elevated/docker-compose.yml, \.devcontainer/start-keycloak-forwarder.sh, docker-compose.idp.yml, dev/keycloak/realm-kravhantering-dev.json
Adds a Keycloak idp service, env vars for admin/host-port, socat-based localhost forwarder script, forwarded port metadata, realm-import mount, and codex tmp volume. Review port mappings, start-keycloak-forwarder script, and realm import mount permissions.
Env samples
\.env.example, \.env.development, \.env.prodlike, \.devcontainer/.env.example
Replaces SQLite example with MSSQL connection examples and derived DB_* vars; adds comprehensive AUTH_/OIDC and iron-session variables and guidance; documents prod unsafe-issuer opt-in and devcontainer Keycloak vars.
Core auth libs
lib/auth/config.ts, lib/auth/oidc.ts, lib/auth/session.ts, lib/auth/login-state.ts, lib/auth/mcp-token.ts, lib/auth/roles.ts, lib/auth/hsa-id.ts, lib/auth/csrf.ts, lib/auth/audit.ts
New centralized auth surface: env-driven fail-closed config, OIDC discovery/caching, iron-session helpers, short-lived PKCE login-state, MCP JWT verifier + JWKS cache, role parsing/display-name, HSA-id validation, CSRF same-origin/X-Requested-With assertions, and structured security-audit logging (with redaction). Check exported types, error classes, caching reset helpers, and redaction rules.
Auth HTTP helpers
lib/http/api-fetch.ts, lib/http/response-message.ts
Adds apiFetch wrapper (injects X-Requested-With for mutating calls and merges headers) and a robust readResponseMessage helper for extracting error messages from Responses.
Auth routes
app/api/auth/login/route.ts, app/api/auth/callback/route.ts, app/api/auth/logout/route.ts, app/api/auth/me/route.ts
New dynamic Next.js routes implementing PKCE login redirect, token-exchange callback with claim validation and session write (cookie-size guarding), logout (local destroy + optional IdP end-session), and /api/auth/me. Verify state/nonce/PKCE handling, required claim checks, session contents, and emitted audit events.
Proxy & request-context / MCP integration
proxy.ts, lib/requirements/auth.ts, lib/mcp/http.ts, lib/mcp/server.ts
Middleware now enforces auth, expands matcher to include API paths, strips impersonation headers, requires Bearer for /api/mcp; adds attachVerifiedActor WeakMap; makes createRequestContext/getBaseContext async so consumers await resolved actor contexts. Check header mutations and async control-flow.
MCP token flow
lib/auth/mcp-token.ts, lib/mcp/http.ts
Adds verifyMcpBearerToken with JWKS caching, McpAuthError class, security-audit events, and short-circuit 401 handling for MCP requests. Includes JWKS cache reset helper for tests.
Session tooling & diagnostics
lib/auth/session.ts, lib/auth/login-state.ts
Implements iron-session integration, session data types and diagnostics, isSignedIn guard, estimateSerializedSessionCookieLength, and login-state cookie helpers. Confirm cookie options and TTL enforcement.
Client UI & API migration
components/AuthMenu.tsx, components/Navigation.tsx, many app/[locale]/*-client.tsx, components/AiRequirementGenerator.tsx, components/RequirementForm.tsx, components/PackageLocalRequirementDetailClient.tsx
Adds AuthMenu and integrates it into Navigation; migrates numerous client components to use shared apiFetch; implements logout UI behavior and useAuthMe hook. Review ARIA, focus/keyboard behavior, and apiFetch header expectations.
Requirements service & route adjustments
lib/requirements/service.ts, many app/api/requirements/**/route.ts
Default authorization now pluggable via createDefaultAuthorizationService; toHttpErrorPayload maps 401/403 to structured payloads; many routes now await createRequestContext inside try blocks. Confirm error mapping and default authorization plumbing.
Tests, tests infra & mocks
tests/support/oidc-mock.ts, vitest.setup.ts, many tests/unit/**, tests/quality/**, playwright.config.ts, playwright.prodlike.config.ts, package.json scripts
Adds in-process OIDC mock, extensive unit/integration tests for auth helpers/routes, forces AUTH_ENABLED=false in test setup, updates tests for async context and apiFetch, and adds idp/noauth scripts. Verify test env overrides and Playwright noauth usage.
Docs, i18n & AI prompt localization
docs/*.md, messages/en.json, messages/sv.json, lib/ai/requirement-prompt.ts
Extensive auth design and developer workflow docs, i18n keys for auth UI and AI prompt localization; refactors AI prompt generation to locale-driven lookups and adds outputRules in localized messages. Validate new i18n keys and AI prompt rule alignment.
Dev tooling & deps
package.json, cspell.jsonc
Adds runtime deps: iron-session, jose, openid-client; dev deps: oidc-provider + types; new scripts (dev:noauth, idp:up, idp:down, start:prodlike:noauth); updates package versions and overrides; expands cspell allowlist. Review dependency and CI/test implications.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant Proxy as rgba(70,130,180,0.5) Proxy
    participant Login as rgba(34,139,34,0.5) /api/auth/login
    participant IdP as rgba(128,0,128,0.5) Keycloak
    participant Callback as rgba(34,139,34,0.5) /api/auth/callback
    participant Session as rgba(210,180,140,0.5) Encrypted Session Cookie

    Browser->>Proxy: Request protected page
    Proxy->>Proxy: Check session & auth config
    alt not signed in
        Proxy-->>Browser: 302 -> /api/auth/login
        Browser->>Login: GET /api/auth/login (generate PKCE/state/nonce)
        Login->>Session: Save short-lived login-state cookie
        Login-->>Browser: 302 -> IdP authorize URL
        Browser->>IdP: Authorization request (code_challenge,state,nonce)
        IdP-->>Browser: 302 -> /api/auth/callback?code=...
        Browser->>Callback: GET /api/auth/callback?code=...
        Callback->>Session: Load login-state, validate state/nonce
        Callback->>IdP: Token exchange (code + code_verifier)
        IdP-->>Callback: tokens + id_token
        Callback->>Callback: Validate ID token claims (sub, names, employeeHsaId, roles)
        Callback->>Session: Persist main encrypted session cookie
        Callback-->>Browser: 302 -> returnTo
    end
    Browser->>Proxy: Subsequent request with session cookie
    Proxy->>Session: Validate session
    Proxy->>Browser: Forward to app
Loading
sequenceDiagram
    participant MCPClient as MCP Client
    participant Proxy as rgba(70,130,180,0.5) Proxy
    participant MCPRoute as rgba(34,139,34,0.5) /api/mcp
    participant Verifier as rgba(255,140,0,0.5) verifyMcpBearerToken
    participant JWKS as rgba(128,0,128,0.5) IdP JWKS
    participant MCPServer as rgba(210,180,140,0.5) MCP Server

    MCPClient->>Proxy: POST /api/mcp with Authorization: Bearer <JWT>
    Proxy->>Proxy: Enforce Bearer requirement for /api/mcp
    Proxy->>MCPRoute: Forward request
    MCPRoute->>Verifier: verifyMcpBearerToken(request)
    Verifier->>JWKS: Fetch or use cached JWKS
    Verifier->>Verifier: jwtVerify(... issuer/audience/tolerance)
    alt token valid
        Verifier-->>MCPRoute: Verified actor
        MCPRoute->>MCPServer: createRequestContext(request,'mcp') (await)
        MCPServer->>MCPServer: Read attached actor from WeakMap
        MCPServer-->>MCPRoute: Process request as verified actor
    else invalid
        Verifier-->>MCPRoute: Throw McpAuthError (401)
        MCPRoute-->>MCPClient: 401 + WWW-Authenticate: Bearer
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

@github-actions

Copy link
Copy Markdown
Contributor

Quality checks failed! Please fix the following issues:

  • Formatting: Run npm run format:check locally to see and fix issues
  • Spell Check: Run npm run spell:check locally to see and fix issues
  • Lint: Run npm run lint locally to see and fix issues
  • Markdown Lint: Run npm run lint:md locally to see and fix issues
  • Type Check: Run npm run type-check locally to see and fix issues
  • Python Type Check: Run npm run lint:py locally to see and fix issues

You can also run npm run check to run all checks at once.

@github-actions

Copy link
Copy Markdown
Contributor

Integration tests failed! Please check the test results and fix any issues.

You can run integration tests locally with:

  • npm run test:integration - Test against dev server
  • npm run test:integration:prodlike - Test against the built app
  • npm run test:integration:ui - Run with Playwright UI for debugging

Check the artifacts for detailed test reports.

@codecov

codecov Bot commented Apr 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.17469% with 200 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.82%. Comparing base (12ee6d4) to head (4be4043).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
components/AuthMenu.tsx 77.07% 35 Missing and 1 partial ⚠️
app/api/auth/login/route.ts 0.00% 33 Missing ⚠️
lib/auth/oidc.ts 0.00% 28 Missing ⚠️
[...e]/requirements/[id]/requirement-detail-client.tsx](https://app.codecov.io/gh/viscalyx/Kravhantering/pull/95?src=pr&el=tree&filepath=app%2F%5Blocale%5D%2Frequirements%2F%5Bid%5D%2Frequirement-detail-client.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=viscalyx#diff-YXBwL1tsb2NhbGVdL3JlcXVpcmVtZW50cy9baWRdL3JlcXVpcmVtZW50LWRldGFpbC1jbGllbnQudHN4) 67.64% 11 Missing ⚠️
lib/auth/roles.ts 80.35% 11 Missing ⚠️
lib/auth/session.ts 84.12% 10 Missing ⚠️
lib/auth/audit.ts 87.09% 8 Missing ⚠️
lib/auth/login-state.ts 0.00% 7 Missing ⚠️
...requirement-packages/[slug]/package-edit-panel.tsx 45.45% 3 Missing and 3 partials ⚠️
app/api/auth/callback/route.ts 95.52% 6 Missing ⚠️
... and 18 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #95      +/-   ##
==========================================
+ Coverage   55.10%   56.82%   +1.72%     
==========================================
  Files         241      257      +16     
  Lines       15885    16880     +995     
  Branches     6041     6489     +448     
==========================================
+ Hits         8753     9592     +839     
- Misses       7019     7175     +156     
  Partials      113      113              
Files with missing lines Coverage Δ
app/[locale]/admin/admin-client.tsx 90.74% <100.00%> (ø)
app/[locale]/owners/owners-client.tsx 100.00% <100.00%> (ø)
...age-item-statuses/package-item-statuses-client.tsx 82.99% <100.00%> (ø)
...characteristics/quality-characteristics-client.tsx 96.26% <100.00%> (ø)
...quirement-packages/requirement-packages-client.tsx 83.08% <100.00%> (ø)
...sponsibility-areas/responsibility-areas-client.tsx 95.23% <100.00%> (ø)
app/[locale]/risk-levels/risk-levels-client.tsx 78.35% <100.00%> (ø)
[...locale]/usage-scenarios/usage-scenarios-client.tsx](https://app.codecov.io/gh/viscalyx/Kravhantering/pull/95?src=pr&el=tree&filepath=app%2F%5Blocale%5D%2Fusage-scenarios%2Fusage-scenarios-client.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=viscalyx#diff-YXBwL1tsb2NhbGVdL3VzYWdlLXNjZW5hcmlvcy91c2FnZS1zY2VuYXJpb3MtY2xpZW50LnRzeA==) 82.69% <100.00%> (ø)
app/api/auth/me/route.ts 100.00% <100.00%> (ø)
app/api/requirement-transitions/[id]/route.ts 100.00% <100.00%> (ø)
... and 38 more

... and 2 files with indirect coverage changes

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

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@johlju

johlju commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
lib/auth/audit.ts (1)

107-129: Duck-typed Request detection is fragile — consider instanceof Request.

typeof (input as Request).headers?.get === 'function' will also match any SecurityEventRequest-shaped object that happens to carry a headers field with a get method (e.g., a future caller passing a plain object with a Map-like headers). Since this module runs in a modern runtime where Request is globally available, input instanceof Request is both safer and clearer. The current SecurityEventRequest has no headers, so risk today is low — flagging as defensive hygiene.

♻️ Proposed tightening
-  if (typeof (input as Request).headers?.get === 'function') {
-    const req = input as Request
+  if (input instanceof Request) {
+    const req = input
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/auth/audit.ts` around lines 107 - 129, The duck-typing in
normalizeRequest that checks typeof (input as Request).headers?.get ===
'function' can misidentify non-Request objects; replace that check with a proper
runtime Request check using instanceof Request (i.e., treat input as a Request
only if input instanceof Request) so the Request-specific logic (new
URL(req.url).pathname, req.method, headers.get(...)) runs only for real Request
instances and otherwise return input as SecurityEventRequest; update references
to req and the returned SecurityEventRequest accordingly.
lib/ai/requirement-prompt.ts (1)

243-253: Consider validating locale parity for all prompt keys.

Only ai.prompt.defaultInstruction is resolved at module load; the other required keys (ai.prompt.system.intro, ai.prompt.system.taxonomyIntro, ai.prompt.system.headings.*, ai.prompt.system.outputRules, ai.prompt.noUsageScenariosAvailable, ai.prompt.userHeader) are fetched lazily. If sv.json is missing a key that en.json has (or vice-versa), the throw only surfaces for the affected locale at call time — and the noUsageScenariosAvailable path only fires when taxonomy.scenarios is empty, which may not be exercised in tests.

Consider adding a unit test (or a single startup validator) that iterates a declared list of required prompt paths across both 'en' and 'sv', so any drift fails CI rather than a specific user's request.

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

In `@lib/ai/requirement-prompt.ts` around lines 243 - 253,
DEFAULT_INSTRUCTION_EN/DEFAULT_INSTRUCTION_SV call getPromptMessage at module
load for only one key, but other required prompt keys (e.g.
ai.prompt.system.intro, ai.prompt.system.taxonomyIntro,
ai.prompt.system.headings.*, ai.prompt.system.outputRules,
ai.prompt.noUsageScenariosAvailable, ai.prompt.userHeader) are fetched lazily
which can hide missing keys until runtime; add a startup validator or unit test
that lists all required prompt paths and iterates locales 'en' and 'sv', calling
getPromptMessage for each path and failing fast (throw/assert) when any key is
missing so key parity between locales is enforced in CI rather than at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/ai/requirement-prompt.ts`:
- Around line 243-253: DEFAULT_INSTRUCTION_EN/DEFAULT_INSTRUCTION_SV call
getPromptMessage at module load for only one key, but other required prompt keys
(e.g. ai.prompt.system.intro, ai.prompt.system.taxonomyIntro,
ai.prompt.system.headings.*, ai.prompt.system.outputRules,
ai.prompt.noUsageScenariosAvailable, ai.prompt.userHeader) are fetched lazily
which can hide missing keys until runtime; add a startup validator or unit test
that lists all required prompt paths and iterates locales 'en' and 'sv', calling
getPromptMessage for each path and failing fast (throw/assert) when any key is
missing so key parity between locales is enforced in CI rather than at runtime.

In `@lib/auth/audit.ts`:
- Around line 107-129: The duck-typing in normalizeRequest that checks typeof
(input as Request).headers?.get === 'function' can misidentify non-Request
objects; replace that check with a proper runtime Request check using instanceof
Request (i.e., treat input as a Request only if input instanceof Request) so the
Request-specific logic (new URL(req.url).pathname, req.method, headers.get(...))
runs only for real Request instances and otherwise return input as
SecurityEventRequest; update references to req and the returned
SecurityEventRequest accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 45b8f9fa-b4c3-443c-8a61-a2a8246de3b3

📥 Commits

Reviewing files that changed from the base of the PR and between bee5145 and 39de2fc.

📒 Files selected for processing (4)
  • docs/auth-how-it-works.md
  • lib/ai/requirement-prompt.ts
  • lib/auth/audit.ts
  • tests/unit/auth-audit.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/auth-audit.test.ts
  • docs/auth-how-it-works.md

@johlju

johlju commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@johlju

johlju commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@johlju

johlju commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/auth/audit.ts`:
- Around line 107-129: normalizeRequest currently returns a SecurityEventRequest
input unchanged, which can leak query strings; update normalizeRequest to
sanitize input.path by stripping query and fragment (e.g., replace path with
input.path.split(/[?#]/,1)[0] ?? ''), then return a new SecurityEventRequest
object preserving other fields like method, userAgent, and requestId (use the
same field names as in normalizeRequest) so pre-shaped requests get the same
query-stripping behavior as native Request objects.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: deebb3ce-4cd2-4bf8-b1da-13635c6d9143

📥 Commits

Reviewing files that changed from the base of the PR and between 39de2fc and 886ace8.

📒 Files selected for processing (3)
  • lib/auth/audit.ts
  • tests/unit/auth-audit.test.ts
  • tests/unit/requirement-prompt.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/auth-audit.test.ts
  • tests/unit/requirement-prompt.test.ts

Comment thread lib/auth/audit.ts
@johlju

johlju commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@johlju

johlju commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/unit/auth-audit.test.ts (2)

219-239: Avoid coupling to V8's exact circular-reference wording.

The assertion on Line 237 pins the test to V8's current phrasing "Converting circular structure to JSON". Node has reworded this message in the past (e.g., to include the offending property path), and Vitest environments can run under different runtimes via aliases. A short, stable substring (or just asserting the 4-arg shape with a non-empty string) keeps the intent without the brittleness. Also, the circular as Record<string, never> cast on Line 229 is semantically wrong — Record<string, never> means “object with no values” — and should be Record<string, unknown> to match the public API.

♻️ Proposed tightening
-        // Force a circular reference into detail to provoke JSON.stringify.
-        detail: circular as Record<string, never>,
+        // Force a circular reference into detail to provoke JSON.stringify.
+        detail: circular as Record<string, unknown>,
       }),
     ).not.toThrow()
     expect(errorSpy).toHaveBeenCalledTimes(1)
-    expect(errorSpy.mock.calls[0]).toEqual([
-      '[security-audit] failed to record event',
-      'auth.token.rejected',
-      'mcp',
-      expect.stringContaining('Converting circular structure to JSON'),
-    ])
+    expect(errorSpy.mock.calls[0]).toEqual([
+      '[security-audit] failed to record event',
+      'auth.token.rejected',
+      'mcp',
+      expect.stringMatching(/circular/i),
+    ])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/auth-audit.test.ts` around lines 219 - 239, The test pins to V8's
exact error wording and uses an incorrect cast; change the circular test so the
circular object is typed as Record<string, unknown> (not Record<string, never>)
and relax the assertion on errorSpy.mock.calls[0] to check the 4-arg shape with
the last argument being a non-empty string (or contains a small stable substring
like "circular") instead of asserting the full V8 message; update references to
recordSecurityEvent and errorSpy in the assertions accordingly so the test
validates that recordSecurityEvent does not throw and that errorSpy was called
once with the event name, actor source, and a non-empty error string.

63-73: Tighten the ISO-8601 assertion (optional nit).

new Date(ev.ts).toISOString() only rejects unparseable strings; any parseable date (including non-ISO like "April 22 2026") would pass, so this doesn't verify the contract that ts is ISO-8601 UTC. A regex or round-trip equality gives more signal for what the comment says is being tested.

♻️ Suggested strengthening
-    const ev = emittedEvents()[0]
-    expect(typeof ev.ts).toBe('string')
-    expect(() => new Date(ev.ts as string).toISOString()).not.toThrow()
+    const ev = emittedEvents()[0]
+    expect(typeof ev.ts).toBe('string')
+    expect(ev.ts).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/)
+    // Round-trip to guarantee canonical UTC form
+    expect(new Date(ev.ts as string).toISOString()).toBe(ev.ts)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/auth-audit.test.ts` around lines 63 - 73, The test currently only
checks that ev.ts parses as a Date; tighten it by asserting the timestamp is in
ISO-8601 UTC format — either assert new Date(ev.ts as string).toISOString() ===
ev.ts as string (round-trip equality) or match ev.ts against an ISO-8601 UTC
regex; update the assertion in the test that uses recordSecurityEvent and
emittedEvents() (referenced symbols: recordSecurityEvent, emittedEvents, ev.ts)
accordingly so the test verifies ISO-8601 Z-formatted timestamps rather than any
parseable date string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/unit/auth-audit.test.ts`:
- Around line 219-239: The test pins to V8's exact error wording and uses an
incorrect cast; change the circular test so the circular object is typed as
Record<string, unknown> (not Record<string, never>) and relax the assertion on
errorSpy.mock.calls[0] to check the 4-arg shape with the last argument being a
non-empty string (or contains a small stable substring like "circular") instead
of asserting the full V8 message; update references to recordSecurityEvent and
errorSpy in the assertions accordingly so the test validates that
recordSecurityEvent does not throw and that errorSpy was called once with the
event name, actor source, and a non-empty error string.
- Around line 63-73: The test currently only checks that ev.ts parses as a Date;
tighten it by asserting the timestamp is in ISO-8601 UTC format — either assert
new Date(ev.ts as string).toISOString() === ev.ts as string (round-trip
equality) or match ev.ts against an ISO-8601 UTC regex; update the assertion in
the test that uses recordSecurityEvent and emittedEvents() (referenced symbols:
recordSecurityEvent, emittedEvents, ev.ts) accordingly so the test verifies
ISO-8601 Z-formatted timestamps rather than any parseable date string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0da7cff6-c145-4aeb-910a-0bc573071452

📥 Commits

Reviewing files that changed from the base of the PR and between 39de2fc and ae78961.

📒 Files selected for processing (4)
  • docs/auth-how-it-works.md
  • lib/auth/audit.ts
  • tests/unit/auth-audit.test.ts
  • tests/unit/requirement-prompt.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/auth-how-it-works.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/auth/audit.ts
  • tests/unit/requirement-prompt.test.ts

@johlju

johlju commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@johlju

johlju commented Apr 24, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/unit/auth-audit.test.ts (2)

156-206: Consider asserting the breadcrumb ts matches the primary event ts.

recordRedactionBreadcrumb is invoked with normalized.ts (snippet 1, line ~205), so every breadcrumb should share the primary event's timestamp. The current assertions cover auditEvent, actorSource, breadcrumb, and channel but not ts, leaving a small gap around the per-event correlation guarantee.

🧪 Optional assertion
     expect(emittedRedactionBreadcrumbs()[0]).toMatchObject({
       auditEvent: 'auth.login.failed',
       actorSource: 'oidc',
       breadcrumb: 'detail-key-redacted',
       channel: 'security-audit',
+      ts: ev.ts,
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/auth-audit.test.ts` around lines 156 - 206, The test omits
asserting that each redaction breadcrumb uses the same timestamp as the primary
audit event; update the test after calling recordSecurityEvent(...) to capture
the primary event (ev = emittedEvents()[0]) and then assert that each breadcrumb
from emittedRedactionBreadcrumbs() has its ts equal to ev.ts (or at least assert
emittedRedactionBreadcrumbs()[0].ts === ev.ts), since recordRedactionBreadcrumb
is called with normalized.ts; reference emittedEvents(),
emittedRedactionBreadcrumbs(), recordRedactionBreadcrumb, normalized.ts and
recordSecurityEvent when making the assertion.

220-244: Minor: assertion of errorCall length relies on implementation detail.

expect(errorCall).toHaveLength(4) locks in the exact positional shape of the console.error call (message, event, actorSource, err-message). This is fine as a regression guard, but if the audit module later appends context (e.g., a request id or correlation token), this test will fail even though the behavior is still correct. Consider a looser assertion (e.g., toHaveLength(expect.any(Number)) is not possible; instead use expect(errorCall?.length).toBeGreaterThanOrEqual(4)) if you want the test to remain resilient to added diagnostic fields. Optional — current strictness is defensible.

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

In `@tests/unit/auth-audit.test.ts` around lines 220 - 244, Update the brittle
assertion that checks the exact number of args passed to console.error by
relaxing the length check on the captured call; instead of asserting
expect(errorCall).toHaveLength(4) use a numeric-range assertion against
errorCall.length (e.g., expect(errorCall?.length).toBeGreaterThanOrEqual(4)) so
the test still verifies the required first 4 fields while allowing additional
diagnostic fields to be appended; locate the assertion around the variable
errorCall derived from errorSpy.mock.calls[0] in the test case "does not throw
when JSON.stringify throws (circular ref)" and replace the strict length check
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/unit/auth-audit.test.ts`:
- Around line 156-206: The test omits asserting that each redaction breadcrumb
uses the same timestamp as the primary audit event; update the test after
calling recordSecurityEvent(...) to capture the primary event (ev =
emittedEvents()[0]) and then assert that each breadcrumb from
emittedRedactionBreadcrumbs() has its ts equal to ev.ts (or at least assert
emittedRedactionBreadcrumbs()[0].ts === ev.ts), since recordRedactionBreadcrumb
is called with normalized.ts; reference emittedEvents(),
emittedRedactionBreadcrumbs(), recordRedactionBreadcrumb, normalized.ts and
recordSecurityEvent when making the assertion.
- Around line 220-244: Update the brittle assertion that checks the exact number
of args passed to console.error by relaxing the length check on the captured
call; instead of asserting expect(errorCall).toHaveLength(4) use a numeric-range
assertion against errorCall.length (e.g.,
expect(errorCall?.length).toBeGreaterThanOrEqual(4)) so the test still verifies
the required first 4 fields while allowing additional diagnostic fields to be
appended; locate the assertion around the variable errorCall derived from
errorSpy.mock.calls[0] in the test case "does not throw when JSON.stringify
throws (circular ref)" and replace the strict length check accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a011b943-4ed4-48d4-89f3-f0d0437bc5a6

📥 Commits

Reviewing files that changed from the base of the PR and between ae78961 and 7e85fa0.

📒 Files selected for processing (1)
  • tests/unit/auth-audit.test.ts

@johlju
johlju merged commit fda1bf4 into main Apr 24, 2026
11 checks passed
@johlju
johlju deleted the main-with-sqlserver-and-auth branch April 24, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: MCP Server Authentication And Authorization Plan

1 participant