Address Gadfly review on #5: OIDC identity guard, verified-email, timeouts
Build image / build-and-push (push) Successful in 9s
Build image / build-and-push (push) Successful in 9s
Fixes from the PR #24 adversarial review (graded 23 real / 1 false positive): Security / correctness - LinkOIDC no longer overwrites a different stored identity: the UPDATE matches only when the row has no identity yet or already carries this exact one, so a second IdP asserting the same verified email can't hijack or lock out an account (returns ErrOIDCIdentityConflict). Uses a single UPDATE...RETURNING (also fixes the ignored-RowsAffected / misleading-ErrNotFound path and the round-trip). - Provisioning now requires a verified email for BOTH linking and JIT creation (was: linking only), so an unverified-email identity can't create an account — nor become the first admin on a fresh instance, nor squat an email a real user later owns. - OIDC-identity collisions surface as the dedicated ErrOIDCIdentityConflict instead of the email-specific ErrEmailTaken. Robustness - readOIDCTxCookie requires a non-empty nonce (an empty one would make the callback's nonce check pass vacuously). - Callback token exchange + verify run under a 15s context timeout so a slow IdP can't outlast the server write timeout. - ensure() performs discovery outside the mutex, so concurrent cold-start requests don't serialize behind one another's full timeout. - setOIDCTxCookie returns its marshal error; oidcLogin aborts rather than redirecting to the IdP with no tx cookie. Maintainability - redirectAuthError helper dedups the ~dozen callback redirects (and the empty-code path now logs like the rest). - Distinct login error codes (no_email / email_unverified / oidc_conflict) for the UI; writeServiceError maps the OIDC sentinels; shared test issuer const. Tests: unverified email refused for both link and JIT; identity-overwrite refused while the original identity keeps working. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01JdQpdYYsTgtkJBxbcpAszi
This commit is contained in:
@@ -223,6 +223,12 @@ func writeServiceError(c *gin.Context, err error) {
|
||||
writeAPIError(c, http.StatusForbidden, "REGISTRATION_CLOSED", "registration is closed")
|
||||
case errors.Is(err, domain.ErrLocalAuthDisabled):
|
||||
writeAPIError(c, http.StatusForbidden, "LOCAL_AUTH_DISABLED", "local authentication is disabled")
|
||||
case errors.Is(err, domain.ErrOIDCNoEmail):
|
||||
writeAPIError(c, http.StatusBadRequest, "OIDC_NO_EMAIL", "the identity provider returned no email")
|
||||
case errors.Is(err, domain.ErrOIDCEmailUnverified):
|
||||
writeAPIError(c, http.StatusForbidden, "OIDC_EMAIL_UNVERIFIED", "the identity provider's email is not verified")
|
||||
case errors.Is(err, domain.ErrOIDCIdentityConflict):
|
||||
writeAPIError(c, http.StatusConflict, "OIDC_IDENTITY_CONFLICT", "this identity conflicts with an existing account")
|
||||
case errors.Is(err, domain.ErrInvalidInput):
|
||||
writeAPIError(c, http.StatusBadRequest, "INVALID_INPUT", "invalid input")
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user