Login handles GET /api/v1/auth/oidc/login.
(w http.ResponseWriter, r *http.Request)
| 93 | |
| 94 | // Login handles GET /api/v1/auth/oidc/login. |
| 95 | func (h *OidcHandler) Login(w http.ResponseWriter, r *http.Request) { |
| 96 | h.clientMu.RLock() |
| 97 | client := h.client |
| 98 | h.clientMu.RUnlock() |
| 99 | |
| 100 | if client == nil { |
| 101 | Error(w, http.StatusBadRequest, "OIDC authentication is not configured") |
| 102 | return |
| 103 | } |
| 104 | if h.requireHTTPS(w, r) { |
| 105 | return |
| 106 | } |
| 107 | state := generateState() |
| 108 | authURL, session, err := client.AuthCodeURL(r.Context(), state) |
| 109 | if err != nil { |
| 110 | if h.log != nil { |
| 111 | h.log.Error("oidc auth url failed", "error", err) |
| 112 | } |
| 113 | Error(w, http.StatusServiceUnavailable, "Failed to reach the OIDC provider; please try again shortly") |
| 114 | return |
| 115 | } |
| 116 | ttl := time.Duration(h.cfg.OidcSessionTTL) * time.Second |
| 117 | if ttl <= 0 { |
| 118 | ttl = 600 * time.Second |
| 119 | } |
| 120 | sessionData := &store.OidcSessionData{ |
| 121 | CodeVerifier: session.CodeVerifier, |
| 122 | Nonce: session.Nonce, |
| 123 | State: state, |
| 124 | CreatedAt: time.Now().UnixMilli(), |
| 125 | } |
| 126 | if err := h.oidcStore.Store(r.Context(), state, sessionData, ttl); err != nil { |
| 127 | if h.log != nil { |
| 128 | h.log.Error("oidc store session failed", "error", err) |
| 129 | } |
| 130 | Error(w, http.StatusInternalServerError, "Failed to initiate OIDC login") |
| 131 | return |
| 132 | } |
| 133 | // Narrow Path to the OIDC routes so this cookie isn't transmitted on every |
| 134 | // request to the app (defense-in-depth; it carries no credential, only a |
| 135 | // random 32-byte state value, but least-privilege scoping is the right default). |
| 136 | http.SetCookie(w, &http.Cookie{ |
| 137 | Name: "oidc_state", |
| 138 | Value: state, |
| 139 | Path: "/api/v1/auth/oidc", |
| 140 | MaxAge: int(ttl.Seconds()), |
| 141 | HttpOnly: true, |
| 142 | Secure: h.cfg.Env == "production" && (r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https"), |
| 143 | SameSite: http.SameSiteLaxMode, |
| 144 | }) |
| 145 | http.Redirect(w, r, authURL, http.StatusFound) |
| 146 | } |
| 147 | |
| 148 | // Callback handles GET /api/v1/auth/oidc/callback. |
| 149 | func (h *OidcHandler) Callback(w http.ResponseWriter, r *http.Request) { |
nothing calls this directly
no test coverage detected