// Package oidc 實作 OIDC 端點:/.well-known/jwks.json,以及之後的 // Discovery、/authorize、/token 等,供 RP(Application)整合。 package oidc import ( "fmt" "log" "net/http" "gorm.io/gorm" "alterminal/internal/auth" "alterminal/internal/jwk" ) // jwksMaxAge 為 JWKS 回應的建議快取秒數。金鑰輪替流程為「先產生並 // 發佈新金鑰,舊金鑰退休前仍留在 JWKS 供已簽發的 token 驗證」,因此 // RP 快取一小時並不影響驗證:快取期間內新舊金鑰皆可取得。 const jwksMaxAge = 3600 // JWKSHandler 處理 GET /.well-known/jwks.json(RFC 7517 §5):發佈所有 // 使用中簽章金鑰的公開 JWK,供 RP 驗證 ID Token/Access Token 的 // 簽章。已退休金鑰不再發佈;無使用中金鑰時回應空的 keys 陣列。 func JWKSHandler(db *gorm.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var keys []jwk.SigningKey if err := db.Where("retired_at IS NULL").Order("created_at DESC").Find(&keys).Error; err != nil { log.Printf("jwks: %v", err) auth.WriteError(w, http.StatusInternalServerError, "內部錯誤") return } set := jwk.JWKS{Keys: make([]jwk.JWK, 0, len(keys))} for i := range keys { k, err := keys[i].PublicJWK() if err != nil { // 單一金鑰的私鑰儲存毀損時跳過該金鑰並記錄待查,不讓整個 // 端點失靈——其餘金鑰照常發佈,RP 仍可驗證其簽發的 token。 log.Printf("jwks: 金鑰 %d(kid=%s)無法轉為公開 JWK: %v", keys[i].ID, keys[i].Kid, err) continue } set.Keys = append(set.Keys, *k) } w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", jwksMaxAge)) auth.WriteJSON(w, http.StatusOK, set) } }