Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions internal/config/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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"`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
}
Expand Down
41 changes: 40 additions & 1 deletion internal/config/oauth_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package config

import "testing"
import (
"encoding/json"
"testing"
)

func TestConfirmationCodeFromID(t *testing.T) {
tests := []struct {
Expand Down Expand Up @@ -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)
}
}
Loading