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
31 changes: 23 additions & 8 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,28 +82,45 @@ func randomState() (string, error) {
func discoverOAuthMetadata(server string) (map[string]any, error) {
client := &http.Client{Timeout: 15 * time.Second}

var lastErr error
for _, path := range []string{
"/.well-known/oauth-authorization-server/api/auth",
"/api/auth/.well-known/openid-configuration",
"/.well-known/oauth-authorization-server",
"/.well-known/openid-configuration",
} {
resp, err := client.Get(strings.TrimRight(server, "/") + path)
if err != nil {
lastErr = fmt.Errorf("%s: %w", path, err)
continue
}
if resp.StatusCode == 200 {
var meta map[string]any
decodeErr := json.NewDecoder(resp.Body).Decode(&meta)
_ = resp.Body.Close()
if decodeErr != nil {
lastErr = fmt.Errorf("%s: decode metadata: %w", path, decodeErr)
continue
}
return meta, nil
}
body, _ := io.ReadAll(resp.Body)
lastErr = fmt.Errorf("%s returned %d: %s", path, resp.StatusCode, strings.TrimSpace(string(body)))
_ = resp.Body.Close()
}
if lastErr != nil {
return nil, fmt.Errorf("could not discover OAuth metadata from %s: %w", server, lastErr)
}
return nil, fmt.Errorf("could not discover OAuth metadata from %s", server)
}

func oauthResource(server string, meta map[string]any) string {
if issuer := strings.TrimRight(stringFromMap(meta, "issuer"), "/"); issuer != "" {
return issuer
}
return strings.TrimRight(server, "/")
}

func registerPublicClient(endpoint string, redirectURIs, grantTypes, responseTypes []string) (map[string]any, error) {
body := map[string]any{
"client_name": "life-ustc-cli",
Expand Down Expand Up @@ -172,6 +189,7 @@ func Login(server string) (*config.Credential, error) {
if regEndpoint == "" {
return nil, fmt.Errorf("server does not advertise a registration_endpoint")
}
resource := oauthResource(server, meta)

// Start local callback server
listener, err := net.Listen("tcp", "127.0.0.1:0")
Expand Down Expand Up @@ -213,7 +231,7 @@ func Login(server string) (*config.Credential, error) {
authURL := conf.AuthCodeURL(state,
oauth2.SetAuthURLParam("code_challenge", challenge),
oauth2.SetAuthURLParam("code_challenge_method", "S256"),
oauth2.SetAuthURLParam("resource", server),
oauth2.SetAuthURLParam("resource", resource),
)

// Channel for callback result
Expand Down Expand Up @@ -265,7 +283,7 @@ func Login(server string) (*config.Credential, error) {

tok, err := conf.Exchange(ctx, result.code,
oauth2.VerifierOption(verifier),
oauth2.SetAuthURLParam("resource", server),
oauth2.SetAuthURLParam("resource", resource),
)
if err != nil {
return nil, fmt.Errorf("token exchange failed: %w", err)
Expand All @@ -282,14 +300,14 @@ func Login(server string) (*config.Credential, error) {
if err := vt.ValidateIDToken(issuer, clientID); err != nil {
return nil, err
}
return verifiedTokenToCredential(clientID, server, vt, "", oauthScope, time.Now())
return verifiedTokenToCredential(clientID, resource, vt, "", oauthScope, time.Now())
}

// RefreshToken attempts to refresh the access token.
//
// Note: oauth2.TokenSource does not expose extra refresh parameters, so the
// original OAuth 2.0 resource indicator is not sent on refresh requests. The
// stored resource (server URL) is preserved in the returned credential, while
// returned credential stores the current issuer resource from metadata, while
// ID token audience validation uses the registered client_id.
//
// The oauth2.Token Expiry is set one hour before the stored ExpiresAt. This
Expand Down Expand Up @@ -332,10 +350,7 @@ func RefreshToken(server string, cred *config.Credential) (*config.Credential, e
if issuer == "" {
issuer = server
}
resource := cred.Resource
if resource == "" {
resource = server
}
resource := oauthResource(server, meta)
if err := vt.ValidateIDToken(issuer, cred.ClientID); err != nil {
return nil, err
}
Expand Down
7 changes: 4 additions & 3 deletions internal/auth/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func LoginDeviceCode(server string) (*config.Credential, error) {
if regEndpoint == "" {
return nil, fmt.Errorf("server does not advertise a registration_endpoint")
}
resource := oauthResource(server, meta)

// Register client
clientInfo, err := registerPublicClient(
Expand All @@ -56,7 +57,7 @@ func LoginDeviceCode(server string) (*config.Credential, error) {
}

ctx := oauth2Context(context.Background(), &http.Client{Timeout: 15 * time.Second})
res, err := conf.DeviceAuth(ctx, oauth2.SetAuthURLParam("resource", server))
res, err := conf.DeviceAuth(ctx, oauth2.SetAuthURLParam("resource", resource))
if err != nil {
return nil, fmt.Errorf("device authorization request failed: %w", err)
}
Expand All @@ -81,7 +82,7 @@ func LoginDeviceCode(server string) (*config.Credential, error) {

fmt.Println("Waiting for authorization...")

tok, err := conf.DeviceAccessToken(ctx, res, oauth2.SetAuthURLParam("resource", server))
tok, err := conf.DeviceAccessToken(ctx, res, oauth2.SetAuthURLParam("resource", resource))
if err != nil {
return nil, fmt.Errorf("device authorization failed: %w", err)
}
Expand All @@ -97,5 +98,5 @@ func LoginDeviceCode(server string) (*config.Credential, error) {
if err := vt.ValidateIDToken(issuer, clientID); err != nil {
return nil, err
}
return verifiedTokenToCredential(clientID, server, vt, "", oauthScope, time.Now())
return verifiedTokenToCredential(clientID, resource, vt, "", oauthScope, time.Now())
}
2 changes: 1 addition & 1 deletion internal/auth/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (t *VerifiedToken) ValidateIDToken(issuer, audience string) error {
if audience == "" {
return errors.New("id_token audience required")
}
parsed, err := jwt.ParseSigned(t.IDToken, []jose.SignatureAlgorithm{jose.RS256, jose.ES256})
parsed, err := jwt.ParseSigned(t.IDToken, []jose.SignatureAlgorithm{jose.RS256, jose.ES256, jose.EdDSA})
if err != nil {
return fmt.Errorf("invalid id_token: %w", err)
}
Expand Down
Loading