Skip to content

Conversation

@colaone1
Copy link

@colaone1 colaone1 commented Sep 14, 2025

What type of PR is this?

(REQUIRED)

  • bug
  • cleanup
  • documentation
  • feature

What this PR does / why we need it:

(REQUIRED)

Which issue(s) this PR fixes:

(REQUIRED)

Special notes for your reviewer:

(fill-in or delete this section)

Testing

(fill-in or delete this section)

Summary by CodeRabbit

  • New Features

    • Added Single Sign-On (SSO) via OpenID Connect (OIDC): auth URL endpoint, secure state handling, OAuth login flow with callback, automatic user provisioning, and passwordless SSO accounts.
    • UI: "Login with SSO" button on sign-in and client-side flow to initiate/complete OIDC login.
  • Chores

    • New OIDC configuration options and API methods.
    • Database migrations and dependency updates to support OAuth identities.

kloenk and others added 17 commits August 12, 2024 00:20
# Conflicts:
#	backend/go.mod
#	backend/go.sum
#	frontend/pages/index.vue
#	frontend/pages/profile.vue
#	frontend/plugins/i18n.ts
# Conflicts:
#	backend/go.mod
#	backend/go.sum
#	backend/internal/data/ent/client.go
#	backend/internal/data/migrations/migrations/atlas.sum
#	backend/internal/sys/config/conf.go
#	frontend/pages/index.vue
- Add OAuth database schema with provider and user relationships
- Implement OAuth service with user creation and token validation
- Add OIDC provider with proper authentication flow
- Integrate OAuth provider into login endpoint
- Add frontend OAuth callback page
- Add OIDC configuration options
- Complete frontend OAuth authentication methods

Closes sysadminsmedia#6
- Implement complete OIDC authentication flow
- Add secure state parameter validation with CSRF protection
- Support for any OIDC-compliant provider (Keycloak, Azure AD, Okta, etc.)
- Dynamic UI that shows OIDC button only when configured
- Comprehensive error handling and user info endpoint fallback
- Database schema for OAuth relationships with proper migrations
- API endpoints for authorization URL generation
- Frontend integration with proper callback handling
- Security best practices with HttpOnly cookies and state validation
- Production-ready implementation with full documentation

Closes sysadminsmedia#6
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds end-to-end OIDC/OAuth support: config, provider, routes, handler, request extractors, OAuth service and repos, DB migrations; frontend API, composables, callback page, and login button. Introduces an endpoint to obtain an OIDC auth URL and validates state via cookie.

Changes

Cohort / File(s) Summary
Backend: Auth handler
backend/app/api/handlers/v1/v1_ctrl_auth.go
Adds OIDCAuthURLResponse, HandleOIDCAuthURL handler, and random state generator; sets oidc_state cookie and returns an auth URL.
Backend: Providers & extractors
backend/app/api/providers/oauth.go, backend/app/api/providers/extractors.go
New OAuthProvider (OIDC discovery, oauth2.Config, GetAuthURL, Authenticate) and getOAuthForm extractor supporting form/json, validating state cookie and code.
Backend: Routes & wiring
backend/app/api/routes.go
Loads OIDC provider when configured; appends to provider list; mounts GET /users/oidc/auth-url and wires providers into login route.
Backend: Services (OAuth)
backend/internal/core/services/service_oauth.go, backend/internal/core/services/all.go
Adds OAuthService and DTOs (OAuthConfig, OAuthValidate, etc.), implements code exchange, ID token verification, UserInfo fallback, user provisioning; adds OAuth to AllServices.
Backend: User defaults refactor
backend/internal/core/services/service_user.go, backend/internal/core/services/service_user_defaults.go
Consolidates default labels/locations creation into createDefaultLabels.
Backend: Repositories
backend/internal/data/repo/repo_oauth.go, .../repo_group.go, .../repo_users.go, .../repos_all.go
Adds OAuthRepository (Create, GetUserFromToken), GroupByName, conditional password set in user create, and registers OAuth repo in AllRepos.
Backend: Migrations
backend/internal/data/migrations/postgres/20250503221935_oidc.sql, .../sqlite3/20250503221935_oidc.sql
Makes users.password nullable; adds oauths table (provider, sub, user_oauth) and indices; SQLite migration rewrites users table and indexes.
Backend: Config & deps
backend/internal/sys/config/conf.go, backend/go.mod
Adds OIDC config options (client ID/secret, redirect URI, provider URL); adds go-oidc and oauth2 dependencies.
Frontend: Public API & types
frontend/lib/api/public.ts, frontend/lib/api/types/data-contracts.ts
Adds loginOauth(provider, iss, code, state) and getOIDCAuthURL(); introduces OAuthForm type.
Frontend: Auth context
frontend/composables/use-auth-context.ts
Adds loginOauth method; sets session and attachment cookies on success.
Frontend: Pages/UI & i18n
frontend/pages/auth/[id]/callback.vue, frontend/pages/index.vue, frontend/locales/en.json
New callback page to consume code/state and call loginOauth; index fetches auth URL and shows “Login with SSO” button; adds i18n string.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant FE as Frontend
  participant API as Backend API
  participant OIDC as OIDC Provider
  participant Svc as OAuthService

  U->>FE: Open app / click "Login with SSO"
  FE->>API: GET /users/oidc/auth-url
  API->>API: generate state, set `oidc_state` cookie
  API-->>FE: 200 { authUrl }
  FE->>OIDC: Redirect to authUrl (includes state)
  OIDC-->>FE: Redirect to /auth/oidc/callback?code=...&state=...
  FE->>API: POST /users/login?provider=oidc { iss, code, state }
  API->>API: validate state cookie
  API->>Svc: Login(config, {iss, code, state})
  Svc->>OIDC: Exchange code -> tokens
  alt id_token available
    Svc->>OIDC: Verify ID token
    Svc->>Svc: Find or Create user by issuer/sub
  else
    Svc->>OIDC: GET UserInfo
    Svc->>Svc: Find or Create user by provider/sub
  end
  Svc-->>API: UserAuthTokenDetail
  API-->>FE: 200 { session token, attachment token, expiresAt }
  FE->>U: Redirect into app (authenticated)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Security recommendations

  • Ensure oidc_state cookie is Secure, HttpOnly, SameSite=Lax/Strict and has a short TTL; clear/rotate it after validation.
  • Enforce exact redirect URI matching against provider-registered URIs; avoid open redirect risks.
  • Consider adding/verifying a nonce when relying on ID tokens to prevent replay.
  • Limit accepted content types and enforce max body size in getOAuthForm.
  • Validate iss strictly (do not trust arbitrary client-supplied issuer without config).
  • Never log tokens, codes, or full user claims; redact sensitive fields in logs.
  • Enforce TLS for OIDC provider URLs in production; reject plain HTTP.
  • Rate-limit /users/login and token exchange operations to mitigate abuse.
  • Review migration making passwords nullable — ensure legacy auth paths handle nil passwords safely and prevent accidental account lockouts.
  • Ensure provisioning checks email verification or other trust signals before auto-provisioning high-privilege access.

Suggested reviewers

  • tankerkiller125
  • tonyaellie

Poem

A state is born, a cookie set,
Redirects hum, the tokens met.
From code to claim the entries flow,
New single-sign-on doors aglow.
Small bytes, big welcomes—let users go. ✨

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b565054 and 10bef23.

⛔ Files ignored due to path filters (25)
  • backend/go.sum is excluded by !**/*.sum
  • backend/internal/data/ent/client.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/ent.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/has_id.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/hook/hook.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/migrate/schema.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/mutation.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth/oauth.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth/where.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth_create.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth_delete.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth_query.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth_update.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/predicate/predicate.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/runtime.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/schema/oauth.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/schema/user.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/tx.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user/user.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user/where.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user_create.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user_query.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user_update.go is excluded by !backend/internal/data/ent/**
📒 Files selected for processing (22)
  • backend/app/api/handlers/v1/v1_ctrl_auth.go (4 hunks)
  • backend/app/api/providers/extractors.go (2 hunks)
  • backend/app/api/providers/oauth.go (1 hunks)
  • backend/app/api/routes.go (2 hunks)
  • backend/go.mod (3 hunks)
  • backend/internal/core/services/all.go (2 hunks)
  • backend/internal/core/services/service_oauth.go (1 hunks)
  • backend/internal/core/services/service_user.go (1 hunks)
  • backend/internal/core/services/service_user_defaults.go (2 hunks)
  • backend/internal/data/migrations/postgres/20250503221935_oidc.sql (1 hunks)
  • backend/internal/data/migrations/sqlite3/20250503221935_oidc.sql (1 hunks)
  • backend/internal/data/repo/repo_group.go (1 hunks)
  • backend/internal/data/repo/repo_oauth.go (1 hunks)
  • backend/internal/data/repo/repo_users.go (1 hunks)
  • backend/internal/data/repo/repos_all.go (2 hunks)
  • backend/internal/sys/config/conf.go (1 hunks)
  • frontend/composables/use-auth-context.ts (3 hunks)
  • frontend/lib/api/public.ts (2 hunks)
  • frontend/lib/api/types/data-contracts.ts (1 hunks)
  • frontend/locales/en.json (1 hunks)
  • frontend/pages/auth/[id]/callback.vue (1 hunks)
  • frontend/pages/index.vue (3 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai bot added the ⬆️ enhancement New feature or request label Sep 14, 2025
- Merge go.mod: Keep OIDC dependencies and use newer versions from main
- Resolve frontend conflict: Keep OIDC button and add test ID from main
- Remove go.sum to allow clean regeneration
- Ensure compatibility with latest main branch changes

Closes sysadminsmedia#6
- Create empty go.sum file to satisfy Dockerfile requirements
- File will be properly generated during Docker build process
- Resolves Docker build failures in CI/CD pipeline

Closes sysadminsmedia#6
@tankerkiller125
Copy link
Contributor

This is the second time you have submitted this PR with basically zero changes from the existing broken PR (and thus is still broken).

Continuing to do this will result in blocking your ability to interact with this repo.

If you wish to fix up the existing PR, and submit an actual working PR (AFTER it's actually working!) then you can do that. Opening a PR essentially just to open one is not appreciated. (Especially for Issues with large bounties)

Also of note that #996 is already addressing this feature separately, and mostly works with minor changes required.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 20

Caution

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

⚠️ Outside diff range comments (2)
backend/internal/data/migrations/postgres/20250503221935_oidc.sql (1)

4-15: Enforce unique (provider, sub) mapping.

Add a unique index/constraint to avoid duplicate identity rows.

Apply:

 );
+
+-- Ensure each provider+sub maps at most one row
+CREATE UNIQUE INDEX IF NOT EXISTS oauth_provider_sub_unique ON oauths (provider, sub);
backend/internal/core/services/service_user.go (1)

174-186: Explicitly block password login for passwordless accounts.

Given repo changes allow NULL passwords, add an explicit guard here (see suggested diff in repo_users.go comment) so password auth can’t proceed for OIDC-only users.

🧹 Nitpick comments (8)
frontend/locales/en.json (1)

174-174: Clarify SSO copy and sync other locales.

Confirm the UI uses “Login with SSO” consistently (buttons, aria-labels, and alt text). Please also add this key to other locale files or ensure fallback behavior is acceptable.

frontend/composables/use-auth-context.ts (1)

110-114: Set cookie security attributes for attachment token.

Harden the client-managed cookie by setting SameSite and Secure.

[security]
Apply this diff:

-      this._attachmentToken = useCookie(AuthContext.cookieAttachmentTokenKey, {
-        expires: expiresAt,
-      });
+      this._attachmentToken = useCookie(AuthContext.cookieAttachmentTokenKey, {
+        expires: expiresAt,
+        sameSite: "lax",
+        secure: true,
+      });
backend/app/api/routes.go (1)

91-101: Don’t silently skip OIDC route on init error; log the failure.

Currently errors are ignored. Log them to aid ops and security auditing.

[security]
Apply this diff:

-        if err == nil {
-          r.Get("/users/oidc/auth-url", chain.ToHandlerFunc(v1Ctrl.HandleOIDCAuthURL(oidcProvider)))
-        }
+        if err != nil {
+          fmt.Printf("failed to initialize OIDC provider for auth-url route: %v\n", err)
+        } else {
+          r.Get("/users/oidc/auth-url", chain.ToHandlerFunc(v1Ctrl.HandleOIDCAuthURL(oidcProvider)))
+        }
backend/app/api/providers/oauth.go (1)

60-62: Consider adding nonce support for OIDC.

For stricter replay protections, support a nonce in the auth request and validate it with the ID token. This can be optional for code flow but is a good hardening.

[security]
Example (conceptual):

nonce := /* generate, store */ 
return p.config.Config.AuthCodeURL(state, oauth2.SetAuthURLParam("nonce", nonce))
backend/internal/data/repo/repo_oauth.go (1)

43-61: Minor naming consistency: UserID.

For Go idioms, prefer UserID over UserId in public structs. Not urgent but improves consistency with ent setters.

backend/internal/core/services/service_oauth.go (1)

115-122: Security: Improve error message to avoid information disclosure

The error message "cannot create OAuth connection" when an email already exists could be used to enumerate valid email addresses in the system.

 	} else {
-		return repo.UserOut{}, errors.New("cannot create OAuth connection")
+		return repo.UserOut{}, errors.New("authentication failed")
 	}
frontend/lib/api/public.ts (1)

1-1: Remove unused import

ParsedAuth is not used.

-import { ParsedAuth } from "ufo";
backend/app/api/providers/extractors.go (1)

80-86: State verification: good, but consider constant-time compare and one-time use

  • Optional: use subtle.ConstantTimeCompare for comparing the state cookie to reduce timing side-channel (low risk here).
  • Ensure the state cookie is cleared after successful validation in the handler.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b565054 and d67e2a0.

⛔ Files ignored due to path filters (26)
  • backend/go.sum is excluded by !**/*.sum
  • backend/internal/data/ent/client.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/ent.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/has_id.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/hook/hook.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/migrate/schema.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/mutation.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth/oauth.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth/where.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth_create.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth_delete.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth_query.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/oauth_update.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/predicate/predicate.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/runtime.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/runtime/runtime.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/schema/oauth.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/schema/user.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/tx.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user/user.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user/where.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user_create.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user_query.go is excluded by !backend/internal/data/ent/**
  • backend/internal/data/ent/user_update.go is excluded by !backend/internal/data/ent/**
📒 Files selected for processing (22)
  • backend/app/api/handlers/v1/v1_ctrl_auth.go (4 hunks)
  • backend/app/api/providers/extractors.go (2 hunks)
  • backend/app/api/providers/oauth.go (1 hunks)
  • backend/app/api/routes.go (2 hunks)
  • backend/go.mod (4 hunks)
  • backend/internal/core/services/all.go (2 hunks)
  • backend/internal/core/services/service_oauth.go (1 hunks)
  • backend/internal/core/services/service_user.go (1 hunks)
  • backend/internal/core/services/service_user_defaults.go (2 hunks)
  • backend/internal/data/migrations/postgres/20250503221935_oidc.sql (1 hunks)
  • backend/internal/data/migrations/sqlite3/20250503221935_oidc.sql (1 hunks)
  • backend/internal/data/repo/repo_group.go (1 hunks)
  • backend/internal/data/repo/repo_oauth.go (1 hunks)
  • backend/internal/data/repo/repo_users.go (1 hunks)
  • backend/internal/data/repo/repos_all.go (2 hunks)
  • backend/internal/sys/config/conf.go (1 hunks)
  • frontend/composables/use-auth-context.ts (3 hunks)
  • frontend/lib/api/public.ts (2 hunks)
  • frontend/lib/api/types/data-contracts.ts (1 hunks)
  • frontend/locales/en.json (1 hunks)
  • frontend/pages/auth/[id]/callback.vue (1 hunks)
  • frontend/pages/index.vue (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-06T18:51:05.524Z
Learnt from: mcarbonne
PR: sysadminsmedia/homebox#869
File: backend/internal/data/migrations/sqlite3/20250706190000_fix_old_timestamps.sql:0-0
Timestamp: 2025-07-06T18:51:05.524Z
Learning: Goose database migrations are automatically wrapped in transactions by the migration framework, eliminating the need for explicit BEGIN TRANSACTION and COMMIT statements in migration scripts.

Applied to files:

  • backend/internal/data/migrations/sqlite3/20250503221935_oidc.sql
🧬 Code graph analysis (11)
backend/internal/data/repo/repos_all.go (1)
backend/internal/data/repo/repo_oauth.go (2)
  • OAuth (22-25)
  • OAuthRepository (11-13)
backend/internal/core/services/all.go (3)
backend/internal/data/repo/repo_oauth.go (1)
  • OAuth (22-25)
backend/internal/core/services/service_oauth.go (1)
  • OAuthService (15-18)
backend/internal/core/services/service_user.go (1)
  • UserService (22-24)
backend/internal/core/services/service_user_defaults.go (1)
backend/internal/data/repo/repos_all.go (1)
  • AllRepos (10-21)
backend/app/api/providers/oauth.go (3)
backend/internal/core/services/service_oauth.go (2)
  • OAuthService (15-18)
  • OAuthConfig (21-25)
backend/internal/core/services/all.go (1)
  • New (37-71)
backend/internal/core/services/service_user.go (1)
  • UserAuthTokenDetail (33-37)
frontend/composables/use-auth-context.ts (1)
frontend/lib/api/public.ts (1)
  • PublicApi (12-48)
backend/app/api/handlers/v1/v1_ctrl_auth.go (3)
backend/app/api/handlers/v1/controller.go (1)
  • V1Controller (67-77)
backend/internal/sys/validate/errors.go (1)
  • NewRequestError (57-59)
backend/app/api/providers/oauth.go (1)
  • OAuthProvider (13-17)
frontend/lib/api/public.ts (3)
frontend/lib/api/types/data-contracts.ts (2)
  • OAuthForm (460-465)
  • TokenResponse (467-471)
backend/app/api/handlers/v1/v1_ctrl_auth.go (1)
  • TokenResponse (27-31)
frontend/lib/api/base/urls.ts (1)
  • route (22-36)
backend/app/api/providers/extractors.go (2)
backend/internal/core/services/service_oauth.go (1)
  • OAuthValidate (26-30)
backend/internal/sys/validate/errors.go (2)
  • NewFieldErrors (100-102)
  • FieldError (72-75)
backend/app/api/routes.go (4)
backend/app/api/handlers/v1/v1_ctrl_auth.go (1)
  • AuthProvider (70-85)
backend/app/api/providers/local.go (1)
  • NewLocalProvider (13-17)
backend/internal/sys/config/conf.go (1)
  • Options (33-43)
backend/app/api/providers/oauth.go (1)
  • NewOAuthProvider (19-45)
backend/internal/data/repo/repo_oauth.go (2)
backend/internal/core/services/contexts.go (1)
  • Context (19-30)
backend/internal/data/repo/repo_users.go (1)
  • UserOut (32-41)
backend/internal/core/services/service_oauth.go (5)
backend/internal/data/repo/repos_all.go (2)
  • AllRepos (10-21)
  • New (23-36)
backend/internal/core/services/service_user.go (3)
  • UserService (22-24)
  • UserAuthTokenDetail (33-37)
  • ErrorInvalidLogin (17-17)
backend/internal/data/repo/repo_users.go (2)
  • UserOut (32-41)
  • UserCreate (18-25)
backend/internal/data/repo/repo_oauth.go (2)
  • OAuth (22-25)
  • OAuthCreate (16-20)
backend/internal/core/services/all.go (1)
  • New (37-71)
🔇 Additional comments (19)
backend/internal/core/services/service_user.go (1)

99-101: Good consolidation of bootstrap logic.

Centralizing default label/location creation improves maintainability. Ensure createDefaultLabels is idempotent in case it’s ever called more than once for a group.

backend/go.mod (1)

9-9: New OIDC deps — run vuln scan, add upgrade policy, enforce timeouts (verification required)

Sandbox verification failed: "failed to start telemetry sidecar: os.Executable: readlink /proc/self/exe: no such file or directory" and "go: go.mod file not found" — cannot complete govulncheck here.

  • Add a security gate for auth libs (github.com/coreos/go-oidc, golang.org/x/oauth2, github.com/square/go-jose) and a documented upgrade/patch policy.
  • Ensure all outbound OIDC/OAuth calls use contexts with deadlines/timeouts.
  • Run vuln check locally from the module root and paste output:
#!/bin/bash
set -euo pipefail
cd backend || exit 1
go mod tidy -v
go mod verify
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
grep -nE 'go-oidc|oauth2|go-jose' go.mod || true

Applies to backend/go.mod lines: 9, 22, 28, 31, 36, 39, 61, 94.

backend/internal/data/repo/repo_users.go (1)

87-101: Disallow passwordless logins; verify hasher + ent schema

Create() now conditionally omits SetPassword (backend/internal/data/repo/repo_users.go ≈ lines 87–101). Add an explicit guard in Login to reject empty/NULL password hashes and confirm the hasher and ent schema behavior.

 func (svc *UserService) Login(ctx context.Context, username, password string, extendedSession bool) (UserAuthTokenDetail, error) {
   usr, err := svc.repos.Users.GetOneEmail(ctx, username)
   if err != nil {
     hasher.CheckPasswordHash("not-a-real-password", "not-a-real-password")
     return UserAuthTokenDetail{}, ErrorInvalidLogin
   }
+  if usr.PasswordHash == "" {
+    // Disallow password login for passwordless/OIDC-provisioned accounts.
+    return UserAuthTokenDetail{}, ErrorInvalidLogin
+  }
   if !hasher.CheckPasswordHash(password, usr.PasswordHash) {
     return UserAuthTokenDetail{}, ErrorInvalidLogin
   }
   return svc.createSessionToken(ctx, usr.ID, extendedSession)
 }
  • Verify hasher.CheckPasswordHash safely handles empty or NULL hashes (no NPE/panic).
  • Verify ent schema for the user password field (backend/internal/data/ent/schema) — ensure it is Optional/Nillable if you intend passwordless users; otherwise make SetPassword required.
  • Security recommendations: use constant-time hash checks, return a generic auth error to avoid user enumeration, rate-limit/login throttling and audit auth failures and OIDC provisioning.
backend/internal/core/services/service_user_defaults.go (2)

4-7: LGTM! Appropriate imports for the functionality.

The imports correctly include context for proper request handling, UUID for group identification, and logging for debugging purposes.


62-79: LGTM! Clean helper function with proper error handling.

The createDefaultLabels function correctly:

  • Uses context for proper request handling
  • Implements proper error propagation
  • Includes debug logging for operational visibility
  • Follows consistent patterns with the existing codebase
backend/app/api/routes.go (1)

88-90: LGTM: multi-provider login wiring.

Routing login through HandleAuthLogin(providerList...) enables local + OIDC cleanly.

backend/app/api/providers/oauth.go (1)

19-45: LGTM: OIDC provider discovery and config wiring look correct.

Provider discovery, scopes, and verifier setup are sound.

backend/internal/core/services/service_oauth.go (1)

197-197: Security: Prevent response body resource leak

Good use of defer resp.Body.Close() to prevent resource leaks!

backend/internal/data/repo/repos_all.go (2)

12-12: Wiring in OAuth repository looks correct

The repository is injected consistently alongside existing repos. No issues spotted.


26-26: Constructor correctly instantiates OAuthRepository — methods verified

repo_oauth.go defines GetUserFromToken (line 28) and Create (line 43); service_oauth.go calls OAuth.GetUserFromToken (lines 95, 211); token flows use AuthTokens.GetUserFromToken (service_user.go lines 121, 198).

Security: validate ID token issuer/audience/scopes, avoid logging tokens/subjects, and ensure sensitive DB fields are encrypted and access-controlled.

backend/internal/core/services/all.go (1)

58-63: Init order and DI for OAuthService are fine

Creating user := &UserService{repos} then injecting it into OAuthService is clean and avoids cyc dependency. LGTM.

frontend/lib/api/public.ts (2)

39-43: OIDC auth URL method looks good

Matches expected shape { authUrl: string }.


28-37: Don't change provider to a path param; omit null fields from the OAuth body.

Backend registers POST /v1/users/login and reads provider from the query (r.URL.Query().Get("provider")), and State/Issuer are plain strings — sending state: null will break decoding. Keep the URL as /users/login?provider=... and change the frontend to only include iss/code/state when defined (omit keys when absent). See: backend/app/api/routes.go, backend/app/api/handlers/v1/v1_ctrl_auth.go, backend/internal/core/services/service_oauth.go, backend/app/api/providers/extractors.go.

  • Actionable: keep route("/users/login", { provider }) and build body with conditional spreads (only include fields that are truthy).
  • Do not make iss optional in frontend unless the backend contract is intentionally changed and validated.

Security recommendations for this PR:

  • Ensure provider values are allow‑listed server‑side (backend already maps providers; keep it).
  • Ensure OIDC state is validated on callback and use secure, httpOnly cookies.
  • Always send these requests over HTTPS and avoid logging tokens/credentials.

Likely an incorrect or invalid review comment.

backend/app/api/providers/extractors.go (2)

88-95: Field validation is fine; ensure JSON nulls don’t reach here

If clients send state: null, JSON decode fails earlier. Frontend fix provided; alternatively, make State a *string in OAuthValidate if you want to accept JSON nulls.


5-5: Import of services is correct

Used for services.OAuthValidate. All good.

frontend/pages/index.vue (4)

115-116: Init OIDC discovery on mount is fine

Looks good.


121-121: State variable for OIDC URL is appropriate

No issues.


150-161: Graceful handling when OIDC is not configured

Good UX with debug logging. LGTM.


302-310: Conditional OIDC button rendering is correct

UI wiring looks good.

Comment on lines +203 to +206
oidcProvider, ok := provider.(*providers.OAuthProvider)
if !ok {
return validate.NewRequestError(errors.New("invalid OIDC provider"), http.StatusInternalServerError)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Reduce tight coupling to provider type for auth URL generation.

Type-asserting to *providers.OAuthProvider couples the controller to the concrete type. Prefer a narrow local interface (e.g., interface{ GetAuthURL(string) string }) and assert to that.

Apply this conceptual change:

type authURLer interface{ GetAuthURL(string) string }
p, ok := provider.(authURLer)

Then use p.GetAuthURL(state).

🤖 Prompt for AI Agents
In backend/app/api/handlers/v1/v1_ctrl_auth.go around lines 203 to 206, replace
the concrete type assertion to *providers.OAuthProvider with a narrow local
interface to decouple the controller: define a local interface type authURLer {
GetAuthURL(string) string }, assert the provider using p, ok :=
provider.(authURLer), handle the non-ok case similarly, and call
p.GetAuthURL(state) where the auth URL is needed.

Comment on lines +208 to +226
// Generate state parameter for security
state := generateRandomState()

// Store state in session/cookie for validation
http.SetCookie(w, &http.Cookie{
Name: "oidc_state",
Value: state,
MaxAge: 600, // 10 minutes
HttpOnly: true,
Secure: ctrl.cookieSecure,
Path: "/",
})

// Get the authorization URL
authURL := oidcProvider.GetAuthURL(state)

return server.JSON(w, http.StatusOK, OIDCAuthURLResponse{
AuthURL: authURL,
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle crypto RNG errors for the state generator and bubble them up.

rand.Read errors are ignored. Fail safely if entropy is unavailable.

[security]
Apply these diffs:

-    // Generate state parameter for security
-    state := generateRandomState()
+    // Generate state parameter for security
+    state, err := generateRandomState()
+    if err != nil {
+      return validate.NewRequestError(errors.New("failed to generate OIDC state"), http.StatusInternalServerError)
+    }
-func generateRandomState() string {
-  bytes := make([]byte, 16)
-  rand.Read(bytes)
-  return hex.EncodeToString(bytes)
-}
+func generateRandomState() (string, error) {
+  b := make([]byte, 16)
+  if _, err := rand.Read(b); err != nil {
+    return "", err
+  }
+  return hex.EncodeToString(b), nil
+}

Also applies to: 234-238

🤖 Prompt for AI Agents
In backend/app/api/handlers/v1/v1_ctrl_auth.go around lines 208-226 and 234-238,
the call to generateRandomState silently ignores crypto RNG errors; update
generateRandomState (and its callers here) to return an error when rand.Read
fails and propagate that error: check the error from generateRandomState in this
handler, log/return an HTTP 500 (or appropriate error response) when state
generation fails instead of proceeding, and adjust cookie-setting and auth URL
generation to only run on successful state creation.

Comment on lines +212 to +219
http.SetCookie(w, &http.Cookie{
Name: "oidc_state",
Value: state,
MaxAge: 600, // 10 minutes
HttpOnly: true,
Secure: ctrl.cookieSecure,
Path: "/",
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Set SameSite and an absolute expiry on the OIDC state cookie.

Add SameSite=Lax to mitigate CSRF and set Expires for broader client compatibility.

[security]
Apply this diff:

   http.SetCookie(w, &http.Cookie{
     Name:     "oidc_state",
     Value:    state,
-    MaxAge:   600, // 10 minutes
+    MaxAge:   600, // 10 minutes
+    Expires:  time.Now().Add(10 * time.Minute),
     HttpOnly: true,
     Secure:   ctrl.cookieSecure,
     Path:     "/",
+    SameSite: http.SameSiteLaxMode,
   })
📝 Committable suggestion

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

Suggested change
http.SetCookie(w, &http.Cookie{
Name: "oidc_state",
Value: state,
MaxAge: 600, // 10 minutes
HttpOnly: true,
Secure: ctrl.cookieSecure,
Path: "/",
})
http.SetCookie(w, &http.Cookie{
Name: "oidc_state",
Value: state,
MaxAge: 600, // 10 minutes
Expires: time.Now().Add(10 * time.Minute),
HttpOnly: true,
Secure: ctrl.cookieSecure,
Path: "/",
SameSite: http.SameSiteLaxMode,
})
🤖 Prompt for AI Agents
In backend/app/api/handlers/v1/v1_ctrl_auth.go around lines 212 to 219, the OIDC
state cookie lacks a SameSite attribute and an absolute Expires timestamp;
update the http.SetCookie call to add SameSite: http.SameSiteLaxMode to mitigate
CSRF and add Expires: time.Now().Add(10 * time.Minute) (in addition to MaxAge)
so clients that prefer absolute expiry honor the lifetime; ensure time is from
the standard time package and keep HttpOnly, Secure and Path as-is.

Comment on lines +60 to +78
switch r.Header.Get("Content-Type") {
case "application/x-www-form-urlencoded":
err := r.ParseForm()
if err != nil {
return oauthForm, errors.New("failed to parse form")
}

oauthForm.Issuer = r.PostFormValue("iss")
oauthForm.Code = r.PostFormValue("code")
oauthForm.State = r.PostFormValue("state")
case "application/json":
err := server.Decode(r, &oauthForm)
if err != nil {
log.Err(err).Msg("failed to decode OAuth form")
return oauthForm, err
}
default:
return oauthForm, errors.New("invalid content type")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Content-Type matching is too strict; handle charset params

Many clients send application/json; charset=utf-8 or application/x-www-form-urlencoded; charset=UTF-8. Use mime.ParseMediaType to avoid false negatives.

- switch r.Header.Get("Content-Type") {
- case "application/x-www-form-urlencoded":
+ ct := r.Header.Get("Content-Type")
+ mt, _, _ := mime.ParseMediaType(ct)
+ switch mt {
+ case "application/x-www-form-urlencoded":
@@
- case "application/json":
+ case "application/json":

Add import:

+ "mime"

Security: Keep refusing unknown content types. Good.

📝 Committable suggestion

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

Suggested change
switch r.Header.Get("Content-Type") {
case "application/x-www-form-urlencoded":
err := r.ParseForm()
if err != nil {
return oauthForm, errors.New("failed to parse form")
}
oauthForm.Issuer = r.PostFormValue("iss")
oauthForm.Code = r.PostFormValue("code")
oauthForm.State = r.PostFormValue("state")
case "application/json":
err := server.Decode(r, &oauthForm)
if err != nil {
log.Err(err).Msg("failed to decode OAuth form")
return oauthForm, err
}
default:
return oauthForm, errors.New("invalid content type")
}
ct := r.Header.Get("Content-Type")
mt, _, _ := mime.ParseMediaType(ct)
switch mt {
case "application/x-www-form-urlencoded":
err := r.ParseForm()
if err != nil {
return oauthForm, errors.New("failed to parse form")
}
oauthForm.Issuer = r.PostFormValue("iss")
oauthForm.Code = r.PostFormValue("code")
oauthForm.State = r.PostFormValue("state")
case "application/json":
err := server.Decode(r, &oauthForm)
if err != nil {
log.Err(err).Msg("failed to decode OAuth form")
return oauthForm, err
}
default:
return oauthForm, errors.New("invalid content type")
}
🤖 Prompt for AI Agents
In backend/app/api/providers/extractors.go around lines 60 to 78, the
Content-Type switch is too strict and fails when headers include charset params;
parse the header using mime.ParseMediaType to get the base media type (e.g.,
"application/json" or "application/x-www-form-urlencoded"), then switch on the
parsed media type instead of the raw header; add the "mime" import, keep
existing error handling and the default branch that rejects unknown content
types.

Comment on lines +72 to +86
providerList := []v1.AuthProvider{
providers.NewLocalProvider(a.services.User),
}
options := a.conf.Options
if options.OIDCProviderURL != "" {
provider, err := providers.NewOAuthProvider(context.Background(), a.services.OAuth,
options.OIDCClientID,
options.OIDCClientSecret,
options.OIDCRedirectURI,
options.OIDCProviderURL)
if err != nil {
panic(err)
}
providerList = append(providerList, provider)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Create the OIDC provider once; avoid panic or duplicate discovery.

You create an OIDC provider here (and panic on error), then create it again below for the auth-url route. Prefer constructing it once and reusing it; fail fast at startup with a clear error instead of panicking, or consistently log and disable OIDC endpoints.

[security]
Would you like a follow-up diff to hoist provider construction and reuse it in both the provider list and the auth-url route?

🤖 Prompt for AI Agents
In backend/app/api/routes.go around lines 72 to 86, the OIDC provider is being
constructed in-place (and panicking on error) and then constructed again later
for the auth-url route; instead, create the OIDC provider once up-front and
reuse the same instance in both the providerList and the auth-url handler. Move
NewOAuthProvider call to startup initialization, return a startup error (or log
and disable OIDC) instead of calling panic, assign the provider to a variable,
append it to providerList if non-nil, and pass the same reference into the
auth-url route so discovery is performed only once and error handling is
consistent.

Comment on lines +39 to +42
OIDCClientID string `yaml:"oidc_client_id"`
OIDCClientSecret string `yaml:"oidc_client_secret"`
OIDCRedirectURI string `yaml:"oidc_redirect_uri"`
OIDCProviderURL string `yaml:"oidc_provider_url"`
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Secret exposure risk: OIDCClientSecret will be printed by Config.Print(). Mask or omit.

Config.Print marshals the whole struct to JSON; without masking, secrets can leak to logs. Add json:"-" and/or conf:"mask" to OIDCClientSecret, and consider masking in Print.

Apply:

- OIDCClientID         string `yaml:"oidc_client_id"`
- OIDCClientSecret     string `yaml:"oidc_client_secret"`
+ OIDCClientID         string `yaml:"oidc_client_id"`
+ OIDCClientSecret     string `yaml:"oidc_client_secret" json:"-" conf:"mask"`
  OIDCRedirectURI      string `yaml:"oidc_redirect_uri"`
  OIDCProviderURL      string `yaml:"oidc_provider_url"`

Optionally harden Print (outside this hunk):

// Alternative: use conf.String to respect conf:"mask"
func (c *Config) Print() {
    fmt.Println(conf.String(c))
}
🤖 Prompt for AI Agents
In backend/internal/sys/config/conf.go around lines 39 to 42 the
OIDCClientSecret field is currently untagged and will be included when
Config.Print marshals the struct, risking secret exposure; add a tag to prevent
serialization (e.g. json:"-" and/or conf:"mask") on OIDCClientSecret and update
Config.Print to use conf.String(c) or otherwise respect conf:"mask" so secrets
are omitted/masked from logs. Ensure the struct tag is applied only to the
secret field and that Print no longer directly marshals the whole struct to
JSON.

@@ -31,9 +32,12 @@ export interface IAuthContext {
* Logs in the user and sets the authorization context via cookies
*/
login(api: PublicApi, email: string, password: string, stayLoggedIn: boolean): ReturnType<PublicApi["login"]>;

loginOauth(api: PublicApi, provider: string, iss: string, code: string): ReturnType<PublicApi["loginOauth"]>;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Pass and validate OAuth “state” to prevent CSRF; update API call signature.

PublicApi.loginOauth supports a state param, but the context method and interface omit it. This breaks/bypasses state validation on the server and weakens CSRF protections in the OIDC flow. Propagate state from the callback query to the backend.

Apply this diff:

-  loginOauth(api: PublicApi, provider: string, iss: string, code: string): ReturnType<PublicApi["loginOauth"]>;
+  loginOauth(api: PublicApi, provider: string, iss: string, code: string, state: string | null): ReturnType<PublicApi["loginOauth"]>;
-  async loginOauth(api: PublicApi, provider: string, iss: string, code: string) {
-    const r = await api.loginOauth(provider, iss, code);
+  async loginOauth(api: PublicApi, provider: string, iss: string, code: string, state: string | null) {
+    const r = await api.loginOauth(provider, iss, code, state);
     if (!r.error) {
       const expiresAt = new Date(r.data.expiresAt);
       this._token = useCookie(AuthContext.cookieTokenKey);
       this._attachmentToken = useCookie(AuthContext.cookieAttachmentTokenKey, {
         expires: expiresAt,
       });
       this._attachmentToken.value = r.data.attachmentToken;
     }
     return r;
   }

Run to find call sites missing the new param:

Also applies to: 105-118


🏁 Script executed:

#!/bin/bash
rg -nP -C2 '\bloginOauth\s*\(' --glob 'frontend/**'

Length of output: 1687


Pass and validate OAuth "state" to prevent CSRF — update use-auth-context to forward state

PublicApi.loginOauth already accepts a state param and callback.vue passes it, but frontend/composables/use-auth-context.ts omits the state parameter and doesn't forward it to api.loginOauth, bypassing server-side state validation.

Location: frontend/composables/use-auth-context.ts — lines ~36 and 105-118

Apply this diff:

-  loginOauth(api: PublicApi, provider: string, iss: string, code: string): ReturnType<PublicApi["loginOauth"]>;
+  loginOauth(api: PublicApi, provider: string, iss: string, code: string, state: string | null): ReturnType<PublicApi["loginOauth"]>;
-  async loginOauth(api: PublicApi, provider: string, iss: string, code: string) {
-    const r = await api.loginOauth(provider, iss, code);
+  async loginOauth(api: PublicApi, provider: string, iss: string, code: string, state: string | null) {
+    const r = await api.loginOauth(provider, iss, code, state);
     if (!r.error) {
       const expiresAt = new Date(r.data.expiresAt);
       this._token = useCookie(AuthContext.cookieTokenKey);
       this._attachmentToken = useCookie(AuthContext.cookieAttachmentTokenKey, {
         expires: expiresAt,
       });
       this._attachmentToken.value = r.data.attachmentToken;
     }
     return r;
   }

Security recommendation: ensure server-side state validation remains enforced; add unit/integration tests for OIDC callback handling of missing/invalid state and log state mismatches.

📝 Committable suggestion

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

Suggested change
loginOauth(api: PublicApi, provider: string, iss: string, code: string): ReturnType<PublicApi["loginOauth"]>;
loginOauth(api: PublicApi, provider: string, iss: string, code: string, state: string | null): ReturnType<PublicApi["loginOauth"]>;
async loginOauth(api: PublicApi, provider: string, iss: string, code: string, state: string | null) {
const r = await api.loginOauth(provider, iss, code, state);
if (!r.error) {
const expiresAt = new Date(r.data.expiresAt);
this._token = useCookie(AuthContext.cookieTokenKey);
this._attachmentToken = useCookie(AuthContext.cookieAttachmentTokenKey, {
expires: expiresAt,
});
this._attachmentToken.value = r.data.attachmentToken;
}
return r;
}

Comment on lines +460 to +466
export interface OAuthForm {
provider: string;
iss: string;
code: string;
state: string | null;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Type contract mismatch: provider shouldn’t be in OAuth body; state must not be null

Backend expects JSON with fields matching OAuthValidate (iss, code, state as strings). Including provider in the body is unused, and sending state: null will fail to decode on the Go side (json: cannot unmarshal null into Go value of type string).

  • Make state optional and omit it when not present.
  • Drop provider from the body type to reflect actual API shape.

Apply this diff to align the contract:

-export interface OAuthForm {
-  provider: string;
-  iss: string;
-  code: string;
-  state: string | null;
-}
+export interface OAuthForm {
+  iss?: string;
+  code: string;
+  state?: string;
+}

Security note: Avoid accepting/propagating arbitrary iss from clients; prefer deriving/validating server‑side against an allow‑list.

📝 Committable suggestion

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

Suggested change
export interface OAuthForm {
provider: string;
iss: string;
code: string;
state: string | null;
}
export interface OAuthForm {
iss?: string;
code: string;
state?: string;
}
🤖 Prompt for AI Agents
In frontend/lib/api/types/data-contracts.ts around lines 460 to 466, the
OAuthForm type wrongly includes a provider field and allows state to be null
which mismatches the backend OAuthValidate contract; remove the provider
property from the interface, change state to optional string (e.g. state?:
string) so it is omitted when not present, and update any callers/serializers to
stop sending provider in the request body and to omit state instead of sending
null so the backend receives strings only.

Comment on lines +18 to +41
async function login() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");
const state = urlParams.get("state");

if (!code) {
console.error("No authorization code received");
navigateTo("/");
return;
}

// For OIDC, we need to get the issuer from the provider configuration
// The issuer will be determined by the backend from the ID token
const { error } = await ctx.loginOauth(api, "oidc", "", code, state);

if (error) {
console.warn(error);
navigateTo("/");
return;
}

navigateTo(redirectTo.value || "/home");
redirectTo.value = null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Use dynamic provider from route and avoid sending null state

Hardcoding "oidc" ignores [id] route; also passing null for state will break backend JSON decoding. Use the route param and omit state when absent.

-  async function login() {
+  async function login() {
+    const route = useRoute();
+    const provider = String(route.params.id || "oidc");
     const urlParams = new URLSearchParams(window.location.search);
     const code = urlParams.get("code");
     const state = urlParams.get("state");
@@
-    const { error } = await ctx.loginOauth(api, "oidc", "", code, state);
+    const { error } = await ctx.loginOauth(api, provider, undefined, code, state || undefined);

Security: After successful validation, consider clearing the oidc_state cookie server‑side to reduce replay risk.

📝 Committable suggestion

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

Suggested change
async function login() {
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");
const state = urlParams.get("state");
if (!code) {
console.error("No authorization code received");
navigateTo("/");
return;
}
// For OIDC, we need to get the issuer from the provider configuration
// The issuer will be determined by the backend from the ID token
const { error } = await ctx.loginOauth(api, "oidc", "", code, state);
if (error) {
console.warn(error);
navigateTo("/");
return;
}
navigateTo(redirectTo.value || "/home");
redirectTo.value = null;
}
async function login() {
const route = useRoute();
const provider = String(route.params.id || "oidc");
const urlParams = new URLSearchParams(window.location.search);
const code = urlParams.get("code");
const state = urlParams.get("state");
if (!code) {
console.error("No authorization code received");
navigateTo("/");
return;
}
// For OIDC, we need to get the issuer from the provider configuration
// The issuer will be determined by the backend from the ID token
const { error } = await ctx.loginOauth(api, provider, undefined, code, state || undefined);
if (error) {
console.warn(error);
navigateTo("/");
return;
}
navigateTo(redirectTo.value || "/home");
redirectTo.value = null;
}
🤖 Prompt for AI Agents
In frontend/pages/auth/[id]/callback.vue around lines 18 to 41, replace the
hardcoded provider and null state: read the dynamic provider id from the route
param (the [id] route) and pass that provider to ctx.loginOauth instead of the
literal "oidc"; only include the state argument when it exists (do not pass
null—omit the argument or construct the payload without state) so backend JSON
decoding isn't broken; additionally, ensure the server clears the oidc_state
cookie after successful validation to reduce replay risk.

Comment on lines +140 to +149
async function loginWithOIDC() {
if (!oidcAuthURL.value) {
toast.error("OIDC authentication not available");
return;
}

// Redirect to OIDC provider
window.location.href = oidcAuthURL.value;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Preserve post-login redirect before sending user to IdP

Store current path so the callback can return users to where they started.

 async function loginWithOIDC() {
   if (!oidcAuthURL.value) {
     toast.error("OIDC authentication not available");
     return;
   }
-  // Redirect to OIDC provider
+  // Remember where to come back
+  redirectTo.value = useRoute().fullPath;
+  // Redirect to OIDC provider
   window.location.href = oidcAuthURL.value;
 }

Security: Ensure backend only returns allow‑listed absolute URLs to prevent open redirect abuse.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/pages/index.vue around lines 140–149, preserve the current path
before redirecting to the IdP so the callback can return the user to where they
started: capture window.location.pathname + window.location.search +
window.location.hash and persist it (e.g., sessionStorage/localStorage under a
clear key or embed it as a signed/hashed state parameter into the oidcAuthURL),
then perform the redirect; on callback read and validate this value and navigate
there. Also ensure the backend only issues allow-listed absolute redirect URLs
and validates any returned state to prevent open-redirect or CSRF abuse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⬆️ enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants