package server import ( "encoding/json" "net/http" "strings" "time" ) // LoginClient abstracts the MCIAS login call so the handler can work // with the real client or a test fake. type LoginClient interface { Login(username, password string) (token string, expiresIn int, err error) } // tokenResponse is the JSON body returned by the token endpoint. type tokenResponse struct { Token string `json:"token"` ExpiresIn int `json:"expires_in"` IssuedAt string `json:"issued_at"` } // TokenHandler returns an http.HandlerFunc that exchanges Basic // credentials for a bearer token via the given LoginClient. // // If the password looks like a JWT (contains two dots), the handler // first tries to validate it directly via the TokenValidator. This // allows service accounts to authenticate with a pre-issued MCIAS // token as the password, following the personal-access-token pattern // used by GitHub Container Registry, GitLab, etc. If JWT validation // fails, the handler falls through to the standard username+password // login flow. func TokenHandler(loginClient LoginClient, validator TokenValidator) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { username, password, ok := r.BasicAuth() if !ok || (username == "" && password == "") { writeOCIError(w, "UNAUTHORIZED", http.StatusUnauthorized, "basic authentication required") return } // If the password looks like a JWT, try validating it directly. // This enables non-interactive auth for service accounts. if strings.Count(password, ".") == 2 { if _, err := validator.ValidateToken(password); err == nil { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(tokenResponse{ Token: password, IssuedAt: time.Now().UTC().Format(time.RFC3339), }) return } } if username == "" { writeOCIError(w, "UNAUTHORIZED", http.StatusUnauthorized, "authentication failed") return } token, expiresIn, err := loginClient.Login(username, password) if err != nil { writeOCIError(w, "UNAUTHORIZED", http.StatusUnauthorized, "authentication failed") return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(tokenResponse{ Token: token, ExpiresIn: expiresIn, IssuedAt: time.Now().UTC().Format(time.RFC3339), }) } }