diff --git a/internal/config/oauth.go b/internal/config/oauth.go index addbacf7..e02af2fe 100644 --- a/internal/config/oauth.go +++ b/internal/config/oauth.go @@ -56,10 +56,11 @@ type AuthRequest struct { // AuthResponse represents the response from the authentication initiation endpoint type AuthResponse struct { - AuthURL string `json:"authURL"` - ID string `json:"id"` - BaseURL string `json:"baseURL"` - TTL int64 `json:"ttl"` + AuthURL string `json:"authURL"` + ID string `json:"id"` + BaseURL string `json:"baseURL"` + PickupSecret string `json:"pickupSecret"` + TTL int64 `json:"ttl"` } func confirmationCodeFromID(id string) string { @@ -72,6 +73,17 @@ func confirmationCodeFromID(id string) string { return strings.ToUpper(suffix[:4] + "-" + suffix[4:]) } +func newOAuthTokenRequest(tokenURL, id, pickupSecret string) (*http.Request, error) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s", tokenURL, id), nil) + if err != nil { + return nil, err + } + if pickupSecret != "" { + req.Header.Set("Authorization", "Bearer "+pickupSecret) + } + return req, nil +} + // OAuthTokenResponse represents the response containing the encrypted token from OAuth flow type OAuthTokenResponse struct { ID string `json:"id"` @@ -454,7 +466,12 @@ func OAuthLogin() (TokenSet, error) { return set, fmt.Errorf("authentication timed out after 5 minutes") case <-ticker.C: // Query Auth-Lambda for token using UUID - tokenResp, err := http.Get(fmt.Sprintf("%s/%s", AuthLambdaTokenURL, authResponse.ID)) + tokenReq, err := newOAuthTokenRequest(AuthLambdaTokenURL, authResponse.ID, authResponse.PickupSecret) + if err != nil { + return set, fmt.Errorf("failed to create token polling request: %v", err) + } + + tokenResp, err := http.DefaultClient.Do(tokenReq) if err != nil { log.Debug("Error polling for token", "error", err) continue @@ -516,6 +533,12 @@ func OAuthLogin() (TokenSet, error) { log.Info("OAuth authentication successful") return set, nil } + bodyBytes, _ := io.ReadAll(tokenResp.Body) + if tokenResp.StatusCode == http.StatusUnauthorized { + tokenResp.Body.Close() + return set, fmt.Errorf("token polling unauthorized: %s", string(bodyBytes)) + } + log.Debug("Token not ready", "status", tokenResp.StatusCode, "body", string(bodyBytes)) tokenResp.Body.Close() } } diff --git a/internal/config/oauth_test.go b/internal/config/oauth_test.go index 8333e563..a4b9e687 100644 --- a/internal/config/oauth_test.go +++ b/internal/config/oauth_test.go @@ -1,6 +1,9 @@ package config -import "testing" +import ( + "encoding/json" + "testing" +) func TestConfirmationCodeFromID(t *testing.T) { tests := []struct { @@ -38,3 +41,39 @@ func TestConfirmationCodeFromID(t *testing.T) { }) } } + +func TestAuthResponseIncludesPickupSecret(t *testing.T) { + raw := []byte(`{ + "authURL": "https://example.com/auth", + "id": "12345678-90ab-cdef-1234-567890abcdef", + "baseURL": "https://example.api.identitynow.com", + "pickupSecret": "secret-value", + "ttl": 123 + }`) + + var response AuthResponse + if err := json.Unmarshal(raw, &response); err != nil { + t.Fatalf("json.Unmarshal() error = %v", err) + } + + if response.PickupSecret != "secret-value" { + t.Fatalf("PickupSecret = %q, want %q", response.PickupSecret, "secret-value") + } +} + +func TestNewOAuthTokenRequestUsesPickupSecretBearer(t *testing.T) { + req, err := newOAuthTokenRequest("https://example.com/auth/token", "session-id", "secret-value") + if err != nil { + t.Fatalf("newOAuthTokenRequest() error = %v", err) + } + + if got, want := req.Method, "GET"; got != want { + t.Fatalf("Method = %q, want %q", got, want) + } + if got, want := req.URL.String(), "https://example.com/auth/token/session-id"; got != want { + t.Fatalf("URL = %q, want %q", got, want) + } + if got, want := req.Header.Get("Authorization"), "Bearer secret-value"; got != want { + t.Fatalf("Authorization = %q, want %q", got, want) + } +}