Skip to content

Add OAuth2/OpenID Connect authorization server#957

Open
gearnode wants to merge 2 commits intomainfrom
gearnode/oauth2-openid-server
Open

Add OAuth2/OpenID Connect authorization server#957
gearnode wants to merge 2 commits intomainfrom
gearnode/oauth2-openid-server

Conversation

@gearnode
Copy link
Copy Markdown
Contributor

@gearnode gearnode commented Mar 30, 2026

Summary

  • Implements a full OAuth2 2.0 and OpenID Connect 1.0 authorization server with authorization code flow (PKCE), refresh token rotation, device authorization grant, dynamic client registration, token introspection, and revocation
  • Adds database schema (6 tables), coredata layer, oauth2server service package, HTTP handlers with CSRF bypasses for token endpoints, OIDC discovery (/.well-known/openid-configuration), and JWKS publishing
  • Integrates into the existing IAM service lifecycle with graceful shutdown and crash propagation

Test plan

  • Verify OIDC discovery endpoint returns correct metadata at /.well-known/openid-configuration
  • Test authorization code flow with PKCE end-to-end
  • Test device authorization grant flow
  • Test refresh token rotation and replay detection
  • Test token introspection and revocation with client authentication
  • Verify JWKS endpoint serves the correct public key

Summary by cubic

Adds a first-party OAuth2.0 and OpenID Connect 1.0 authorization server, wired into IAM and the HTTP API. Supports auth code with PKCE, device code, refresh token rotation, token introspection/revocation, OIDC discovery, and JWKS publishing; also adds strict scope parsing to reject invalid scopes.

  • New Features

    • New oauth2server service with RSA-signed JWTs (configurable signing key/KID).
    • Endpoints under /connect/v1/oauth2: /token, /device, /userinfo, /introspect, /revoke, /jwks; OIDC discovery at /.well-known/openid-configuration.
    • Authorization flows: authorization code + PKCE and device authorization; refresh token rotation with replay detection.
    • Consent UI and background GC; new coredata models and DB schema for clients, codes, tokens, device codes, and consents.
    • Strict scope parsing in OAuth2Scopes.UnmarshalText validates each scope and rejects invalid values.
  • Migration

    • Run the new DB migration.
    • Configure the signing key in auth.oauth2-server (signing-key-file, optional signing-kid), then restart.
    • Verify /.well-known/openid-configuration and /connect/v1/oauth2/jwks return expected metadata/keys.

Written for commit e2dd212. Summary will update on new commits.

Implement a full OAuth2 2.0 and OpenID Connect 1.0 authorization
server with support for authorization code flow (with PKCE),
refresh token rotation, device authorization grant, dynamic
client registration, token introspection, and token revocation.

Includes database schema, coredata layer, service logic, HTTP
handlers, OIDC discovery endpoint, and JWKS publishing.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
// verifyClientSecret verifies a plaintext secret against a stored hash
// using constant-time comparison.
func verifyClientSecret(storedHash []byte, secret string) bool {
h := sha256.Sum256([]byte(secret))

Check failure

Code scanning / CodeQL

Use of a broken or weak cryptographic hashing algorithm on sensitive data High

Sensitive data (password)
is used in a hashing algorithm (SHA256) that is insecure for password hashing, since it is not a computationally expensive hash function.
}
u.RawQuery = q.Encode()

http.Redirect(w, r, u.String(), http.StatusFound)

Check warning

Code scanning / CodeQL

Open URL redirect Medium

This path to an untrusted URL redirection depends on a
user-provided value
.
This path to an untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI about 17 hours ago

General approach: ensure that any redirectURI used in redirectWithError and redirectWithCode is either (a) taken from a trusted server-side allowlist, or (b) validated so that it can only point to allowed locations (for example, matching a set of permitted origins or being a relative URL). Because we cannot change external behavior beyond security hardening and we don’t see all call sites, the safest fix is to add validation inside these helper functions so they cannot be misused from any path.

Best concrete fix in this file:

  1. Introduce a small validation helper, e.g. isSafeRedirectURI, near the redirect helpers. It will:

    • Parse the redirectURI string.
    • Optionally normalize backslashes to forward slashes (consistent with the background example) before parsing.
    • Enforce that the URI is either:
      • Exactly one of the client-registered redirect_uri values (we cannot access client in this helper without major refactoring), or
      • At least restricted to same-origin / relative paths.
        Since we do not know the deployment host, the least intrusive and safest generic policy is: only allow relative URLs (no scheme, no host). This removes open redirect risk while keeping redirects within the current application.
  2. Use that helper in redirectWithError and redirectWithCode:

    • If the redirectURI fails validation (absolute URL, has host, invalid syntax), do not redirect to it; instead fall back to a safe behavior, such as returning an OAuth2 error directly (writeOAuth2ServerError or writeOAuth2InvalidRequest) or possibly a 400. The most conservative choice given existing helpers is to call writeOAuth2ServerError(w, "invalid redirect_uri") and return, mirroring the existing error handling for url.Parse failures in redirectWithError.
  3. Additionally harden the existing url.Parse usage:

    • In redirectWithError, we already handle err != nil by sending an error response; we will add a host/scheme check after parsing.
    • In redirectWithCode, the code currently ignores the parse error (u, _ := url.Parse(redirectURI)); we should handle errors and avoid redirecting if parsing fails or the URI is unsafe.

Locations/lines to change:

  • Around lines 811–839 (definitions of redirectWithError and redirectWithCode) in pkg/server/api/connect/v1/oauth2_handler.go:
    • Add a new helper isSafeRedirectURI immediately above redirectWithError.
    • Modify redirectWithError to:
      • Normalize and validate redirectURI with isSafeRedirectURI.
      • Only call http.Redirect when validation passes.
    • Modify redirectWithCode similarly:
      • Parse with error handling.
      • Normalize and validate before use.

Imports:

  • We already import "net/url" and "strings" at the top, so no new imports are required for this approach.

Suggested changeset 1
pkg/server/api/connect/v1/oauth2_handler.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pkg/server/api/connect/v1/oauth2_handler.go b/pkg/server/api/connect/v1/oauth2_handler.go
--- a/pkg/server/api/connect/v1/oauth2_handler.go
+++ b/pkg/server/api/connect/v1/oauth2_handler.go
@@ -808,9 +808,26 @@
 	})
 }
 
-func redirectWithError(w http.ResponseWriter, r *http.Request, redirectURI, errorCode, description, state string) {
-	u, err := url.Parse(redirectURI)
+func isSafeRedirectURI(raw string) (*url.URL, bool) {
+	// Normalize backslashes to forward slashes to avoid browser quirks.
+	normalized := strings.ReplaceAll(raw, "\\", "/")
+
+	u, err := url.Parse(normalized)
 	if err != nil {
+		return nil, false
+	}
+
+	// Only allow relative redirects (no scheme/host) to avoid open redirects.
+	if u.IsAbs() || u.Hostname() != "" {
+		return nil, false
+	}
+
+	return u, true
+}
+
+func redirectWithError(w http.ResponseWriter, r *http.Request, redirectURI, errorCode, description, state string) {
+	u, ok := isSafeRedirectURI(redirectURI)
+	if !ok {
 		writeOAuth2ServerError(w, "invalid redirect_uri")
 		return
 	}
@@ -827,7 +843,12 @@
 }
 
 func redirectWithCode(w http.ResponseWriter, r *http.Request, redirectURI, code, state string) {
-	u, _ := url.Parse(redirectURI)
+	u, ok := isSafeRedirectURI(redirectURI)
+	if !ok {
+		writeOAuth2ServerError(w, "invalid redirect_uri")
+		return
+	}
+
 	q := u.Query()
 	q.Set("code", code)
 	if state != "" {
EOF
@@ -808,9 +808,26 @@
})
}

func redirectWithError(w http.ResponseWriter, r *http.Request, redirectURI, errorCode, description, state string) {
u, err := url.Parse(redirectURI)
func isSafeRedirectURI(raw string) (*url.URL, bool) {
// Normalize backslashes to forward slashes to avoid browser quirks.
normalized := strings.ReplaceAll(raw, "\\", "/")

u, err := url.Parse(normalized)
if err != nil {
return nil, false
}

// Only allow relative redirects (no scheme/host) to avoid open redirects.
if u.IsAbs() || u.Hostname() != "" {
return nil, false
}

return u, true
}

func redirectWithError(w http.ResponseWriter, r *http.Request, redirectURI, errorCode, description, state string) {
u, ok := isSafeRedirectURI(redirectURI)
if !ok {
writeOAuth2ServerError(w, "invalid redirect_uri")
return
}
@@ -827,7 +843,12 @@
}

func redirectWithCode(w http.ResponseWriter, r *http.Request, redirectURI, code, state string) {
u, _ := url.Parse(redirectURI)
u, ok := isSafeRedirectURI(redirectURI)
if !ok {
writeOAuth2ServerError(w, "invalid redirect_uri")
return
}

q := u.Query()
q.Set("code", code)
if state != "" {
Copilot is powered by AI and may make mistakes. Always verify output.
}
u.RawQuery = q.Encode()

http.Redirect(w, r, u.String(), http.StatusFound)

Check warning

Code scanning / CodeQL

Open URL redirect Medium

This path to an untrusted URL redirection depends on a
user-provided value
.
This path to an untrusted URL redirection depends on a
user-provided value
.

Copilot Autofix

AI about 17 hours ago

In general, to fix open redirect issues you must not pass raw user input to http.Redirect. Instead, either (a) map user input to a server‑side list of allowed destinations, or (b) validate the URL to ensure it is acceptable (for example, only relative paths or URLs within your own domain, or URLs that match a pre‑registered set).

In this file, the simplest fix that doesn’t change higher‑level behavior is to have redirectWithError and redirectWithCode validate redirectURI before redirecting. A small helper can parse the string, normalize backslashes to slashes (to avoid browser quirks), and enforce constraints. Since this is an OAuth2 authorization endpoint, redirecting to fully external client URLs is expected, so we cannot simply force relative URLs. However, we can at least mitigate common open‑redirect and SSRF patterns by:

  • Rejecting non‑HTTP(S) schemes (no javascript:, data:, etc.).
  • Requiring that the parsed URL either:
    • Has an empty Scheme and Host (relative URL), or
    • Has an allowed scheme (http or https) and a host that matches a whitelist.
      Because we don’t see any client registry here, we can implement a conservative generic check: only allow relative URLs and URLs whose host equals the current server’s host. That prevents arbitrary external redirects while still allowing redirects back to the same host.

Concretely:

  • Add a helper function sanitizeRedirectURI(r *http.Request, redirectURI string) (string, error) near the redirect helpers (around lines 811+). This will:
    • Replace \ with /.
    • Parse the URL.
    • If parsing fails, return error.
    • If u.Scheme == "" && u.Host == "": allow it as a relative redirect, return u.
    • Else, if u.Scheme is http or https AND strings.EqualFold(u.Hostname(), r.Hostname() or r.Host) (we only have r.Host, but we can compare .Hostname() against the host part of r.Host), allow it; otherwise, return error.
  • Update redirectWithError:
    • Call sanitizeRedirectURI. If it fails, fall back to writing an OAuth2 error to the response instead of redirecting.
    • Use the sanitized URL (u) to add the query parameters and call http.Redirect.
  • Update redirectWithCode similarly, including proper error handling and not calling http.Redirect when validation fails.

This keeps existing functional behavior for same‑host redirects and relative paths while blocking open redirects to third‑party sites derived from untrusted input.

Suggested changeset 1
pkg/server/api/connect/v1/oauth2_handler.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pkg/server/api/connect/v1/oauth2_handler.go b/pkg/server/api/connect/v1/oauth2_handler.go
--- a/pkg/server/api/connect/v1/oauth2_handler.go
+++ b/pkg/server/api/connect/v1/oauth2_handler.go
@@ -808,9 +808,42 @@
 	})
 }
 
+func sanitizeRedirectURI(r *http.Request, redirectURI string) (*url.URL, error) {
+	// Normalize backslashes to forward slashes to avoid browser quirks when parsing URLs.
+	normalized := strings.ReplaceAll(redirectURI, "\\", "/")
+
+	u, err := url.Parse(normalized)
+	if err != nil {
+		return nil, err
+	}
+
+	// Allow relative URLs (no scheme/host) such as "/path" or "path?x=1".
+	if u.Scheme == "" && u.Host == "" {
+		return u, nil
+	}
+
+	// Only allow http/https redirects and restrict to the current host to avoid open redirects.
+	if u.Scheme != "http" && u.Scheme != "https" {
+		return nil, errors.New("unsupported redirect_uri scheme")
+	}
+
+	// r.Host may include a port; extract just the hostname for comparison.
+	requestHost := r.Host
+	if h, _, splitErr := strings.Cut(requestHost, ":"); splitErr == nil {
+		requestHost = h
+	}
+
+	if !strings.EqualFold(u.Hostname(), requestHost) {
+		return nil, errors.New("redirect_uri host mismatch")
+	}
+
+	return u, nil
+}
+
 func redirectWithError(w http.ResponseWriter, r *http.Request, redirectURI, errorCode, description, state string) {
-	u, err := url.Parse(redirectURI)
+	u, err := sanitizeRedirectURI(r, redirectURI)
 	if err != nil {
+		// If the redirect URI is invalid or unsafe, send an error response instead of redirecting.
 		writeOAuth2ServerError(w, "invalid redirect_uri")
 		return
 	}
@@ -827,7 +858,13 @@
 }
 
 func redirectWithCode(w http.ResponseWriter, r *http.Request, redirectURI, code, state string) {
-	u, _ := url.Parse(redirectURI)
+	u, err := sanitizeRedirectURI(r, redirectURI)
+	if err != nil {
+		// If the redirect URI is invalid or unsafe, send an error response instead of redirecting.
+		writeOAuth2ServerError(w, "invalid redirect_uri")
+		return
+	}
+
 	q := u.Query()
 	q.Set("code", code)
 	if state != "" {
EOF
@@ -808,9 +808,42 @@
})
}

func sanitizeRedirectURI(r *http.Request, redirectURI string) (*url.URL, error) {
// Normalize backslashes to forward slashes to avoid browser quirks when parsing URLs.
normalized := strings.ReplaceAll(redirectURI, "\\", "/")

u, err := url.Parse(normalized)
if err != nil {
return nil, err
}

// Allow relative URLs (no scheme/host) such as "/path" or "path?x=1".
if u.Scheme == "" && u.Host == "" {
return u, nil
}

// Only allow http/https redirects and restrict to the current host to avoid open redirects.
if u.Scheme != "http" && u.Scheme != "https" {
return nil, errors.New("unsupported redirect_uri scheme")
}

// r.Host may include a port; extract just the hostname for comparison.
requestHost := r.Host
if h, _, splitErr := strings.Cut(requestHost, ":"); splitErr == nil {
requestHost = h
}

if !strings.EqualFold(u.Hostname(), requestHost) {
return nil, errors.New("redirect_uri host mismatch")
}

return u, nil
}

func redirectWithError(w http.ResponseWriter, r *http.Request, redirectURI, errorCode, description, state string) {
u, err := url.Parse(redirectURI)
u, err := sanitizeRedirectURI(r, redirectURI)
if err != nil {
// If the redirect URI is invalid or unsafe, send an error response instead of redirecting.
writeOAuth2ServerError(w, "invalid redirect_uri")
return
}
@@ -827,7 +858,13 @@
}

func redirectWithCode(w http.ResponseWriter, r *http.Request, redirectURI, code, state string) {
u, _ := url.Parse(redirectURI)
u, err := sanitizeRedirectURI(r, redirectURI)
if err != nil {
// If the redirect URI is invalid or unsafe, send an error response instead of redirecting.
writeOAuth2ServerError(w, "invalid redirect_uri")
return
}

q := u.Query()
q.Set("code", code)
if state != "" {
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

9 issues found across 43 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="pkg/iam/oauth2server/service.go">

<violation number="1" location="pkg/iam/oauth2server/service.go:480">
P1: Custom agent: **Avoid Logging Sensitive Information**

Remove or redact the identity_id from this log message to avoid logging sensitive user identifiers.</violation>

<violation number="2" location="pkg/iam/oauth2server/service.go:517">
P1: Refresh token rotation is not atomic: the old token is revoked in one transaction, then new tokens are created in separate connections. If `CreateRefreshToken` fails (or the process crashes) after the old token is already revoked, the client loses its session with no way to recover except re-authentication. Consider moving the new token creation into the same transaction that revokes the old token.</violation>

<violation number="3" location="pkg/iam/oauth2server/service.go:684">
P1: Race condition: status check and token issuance happen outside the transaction that locks the device code row. Two concurrent polls for an authorized device code will both pass the status check and issue duplicate token sets. Move the status/expiry checks and device code deletion (or a status transition to a "consumed" state) inside the `WithTx` block so that only one poller can issue tokens.</violation>
</file>

<file name="pkg/iam/service.go">

<violation number="1" location="pkg/iam/service.go:188">
P2: Validate OAuth2ServerSigningKey before constructing the OAuth2 server service; it is dereferenced in JWKS/ID token signing and will panic if nil.</violation>
</file>

<file name="pkg/iam/oauth2server/user_code.go">

<violation number="1" location="pkg/iam/oauth2server/user_code.go:39">
P2: GenerateUserCode returns an unformatted 8‑character code despite the comment promising XXXX-XXXX. Either format the return value or adjust the docs; as written, callers expecting the documented format will render the code incorrectly.</violation>
</file>

<file name="pkg/coredata/oauth2_scope.go">

<violation number="1" location="pkg/coredata/oauth2_scope.go:80">
P2: Validate each parsed scope in OAuth2Scopes.UnmarshalText; otherwise invalid scope strings are accepted silently.</violation>
</file>

<file name="pkg/coredata/oauth2_consent.go">

<violation number="1" location="pkg/coredata/oauth2_consent.go:53">
P1: Coredata methods here omit the Scoper/tenant_id filter required for tenant isolation. This allows cross-tenant reads/writes of OAuth2 consent records.</violation>
</file>

<file name="pkg/server/api/connect/v1/oauth2_handler.go">

<violation number="1" location="pkg/server/api/connect/v1/oauth2_handler.go:299">
P0: The `redirect_uri` from the consent form POST body is not re-validated against the client's registered redirect URIs. An attacker can modify the hidden form field to steal the authorization code via an open redirect. Load the client and validate `redirect_uri` against `client.RedirectURIs` before issuing the redirect, just as `AuthorizeHandler` does.</violation>
</file>

<file name="pkg/iam/oauth2server/gc.go">

<violation number="1" location="pkg/iam/oauth2server/gc.go:74">
P2: Validate gc.interval before calling time.NewTicker; zero/negative durations will panic at runtime.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

return
}

redirectURI := r.FormValue("redirect_uri")
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

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

P0: The redirect_uri from the consent form POST body is not re-validated against the client's registered redirect URIs. An attacker can modify the hidden form field to steal the authorization code via an open redirect. Load the client and validate redirect_uri against client.RedirectURIs before issuing the redirect, just as AuthorizeHandler does.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/server/api/connect/v1/oauth2_handler.go, line 299:

<comment>The `redirect_uri` from the consent form POST body is not re-validated against the client's registered redirect URIs. An attacker can modify the hidden form field to steal the authorization code via an open redirect. Load the client and validate `redirect_uri` against `client.RedirectURIs` before issuing the redirect, just as `AuthorizeHandler` does.</comment>

<file context>
@@ -0,0 +1,871 @@
+		return
+	}
+
+	redirectURI := r.FormValue("redirect_uri")
+	state := r.FormValue("state")
+
</file context>
Fix with Cubic

if oldToken.RevokedAt != nil {
s.logger.WarnCtx(ctx, "refresh token replay detected, revoking all tokens",
log.String("client_id", client.ID.String()),
log.String("identity_id", oldToken.IdentityID.String()),
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

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

P1: Custom agent: Avoid Logging Sensitive Information

Remove or redact the identity_id from this log message to avoid logging sensitive user identifiers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/iam/oauth2server/service.go, line 480:

<comment>Remove or redact the identity_id from this log message to avoid logging sensitive user identifiers.</comment>

<file context>
@@ -0,0 +1,1126 @@
+		if oldToken.RevokedAt != nil {
+			s.logger.WarnCtx(ctx, "refresh token replay detected, revoking all tokens",
+				log.String("client_id", client.ID.String()),
+				log.String("identity_id", oldToken.IdentityID.String()),
+			)
+
</file context>
Fix with Cubic

}

// Issue new tokens.
accessTokenValue, accessToken, err := s.CreateAccessToken(ctx, client.ID, identityID, scopes)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

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

P1: Refresh token rotation is not atomic: the old token is revoked in one transaction, then new tokens are created in separate connections. If CreateRefreshToken fails (or the process crashes) after the old token is already revoked, the client loses its session with no way to recover except re-authentication. Consider moving the new token creation into the same transaction that revokes the old token.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/iam/oauth2server/service.go, line 517:

<comment>Refresh token rotation is not atomic: the old token is revoked in one transaction, then new tokens are created in separate connections. If `CreateRefreshToken` fails (or the process crashes) after the old token is already revoked, the client loses its session with no way to recover except re-authentication. Consider moving the new token creation into the same transaction that revokes the old token.</comment>

<file context>
@@ -0,0 +1,1126 @@
+	}
+
+	// Issue new tokens.
+	accessTokenValue, accessToken, err := s.CreateAccessToken(ctx, client.ID, identityID, scopes)
+	if err != nil {
+		return nil, fmt.Errorf("cannot create access token: %w", err)
</file context>
Fix with Cubic

return nil, fmt.Errorf("%w: invalid device code", ErrInvalidGrant)
}

if time.Now().After(dc.ExpiresAt) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

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

P1: Race condition: status check and token issuance happen outside the transaction that locks the device code row. Two concurrent polls for an authorized device code will both pass the status check and issue duplicate token sets. Move the status/expiry checks and device code deletion (or a status transition to a "consumed" state) inside the WithTx block so that only one poller can issue tokens.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/iam/oauth2server/service.go, line 684:

<comment>Race condition: status check and token issuance happen outside the transaction that locks the device code row. Two concurrent polls for an authorized device code will both pass the status check and issue duplicate token sets. Move the status/expiry checks and device code deletion (or a status transition to a "consumed" state) inside the `WithTx` block so that only one poller can issue tokens.</comment>

<file context>
@@ -0,0 +1,1126 @@
+		return nil, fmt.Errorf("%w: invalid device code", ErrInvalidGrant)
+	}
+
+	if time.Now().After(dc.ExpiresAt) {
+		return nil, ErrExpiredToken
+	}
</file context>
Fix with Cubic

panic(fmt.Sprintf("unsupported order by: %s", orderBy))
}

func (c *OAuth2Consent) LoadByIdentityAndClient(
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

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

P1: Coredata methods here omit the Scoper/tenant_id filter required for tenant isolation. This allows cross-tenant reads/writes of OAuth2 consent records.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/coredata/oauth2_consent.go, line 53:

<comment>Coredata methods here omit the Scoper/tenant_id filter required for tenant isolation. This allows cross-tenant reads/writes of OAuth2 consent records.</comment>

<file context>
@@ -0,0 +1,249 @@
+	panic(fmt.Sprintf("unsupported order by: %s", orderBy))
+}
+
+func (c *OAuth2Consent) LoadByIdentityAndClient(
+	ctx context.Context,
+	conn pg.Conn,
</file context>
Fix with Cubic

svc.OAuth2ServerService = oauth2server.NewService(
pgClient,
oauth2server.Config{
SigningKey: cfg.OAuth2ServerSigningKey,
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

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

P2: Validate OAuth2ServerSigningKey before constructing the OAuth2 server service; it is dereferenced in JWKS/ID token signing and will panic if nil.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/iam/service.go, line 188:

<comment>Validate OAuth2ServerSigningKey before constructing the OAuth2 server service; it is dereferenced in JWKS/ID token signing and will panic if nil.</comment>

<file context>
@@ -177,6 +182,16 @@ func NewService(
+	svc.OAuth2ServerService = oauth2server.NewService(
+		pgClient,
+		oauth2server.Config{
+			SigningKey: cfg.OAuth2ServerSigningKey,
+			SigningKID: cfg.OAuth2ServerSigningKID,
+			BaseURL:    cfg.BaseURL.String(),
</file context>
Fix with Cubic

code[i] = userCodeAlphabet[n.Int64()]
}

return string(code), nil
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 30, 2026

Choose a reason for hiding this comment

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

P2: GenerateUserCode returns an unformatted 8‑character code despite the comment promising XXXX-XXXX. Either format the return value or adjust the docs; as written, callers expecting the documented format will render the code incorrectly.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At pkg/iam/oauth2server/user_code.go, line 39:

<comment>GenerateUserCode returns an unformatted 8‑character code despite the comment promising XXXX-XXXX. Either format the return value or adjust the docs; as written, callers expecting the documented format will render the code incorrectly.</comment>

<file context>
@@ -0,0 +1,49 @@
+		code[i] = userCodeAlphabet[n.Int64()]
+	}
+
+	return string(code), nil
+}
+
</file context>
Fix with Cubic

Each scope parsed from the space-delimited string is now validated
via OAuth2Scope.UnmarshalText, rejecting invalid scope values
instead of accepting them silently.

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant