- Improve health check logic in CI workflows for both HTTP and gRPC readiness of the Tai service, ensuring clearer error reporting if the service fails to start. - Update the OAuth Device Flow implementation to support additional claims during device authorization, enhancing the flexibility of the authorization process. - Refactor the `AuthorizeDevice` method to accept extra claims, allowing for more detailed user context during authorization. - Introduce a new utility function to extract bearer tokens from requests, streamlining token handling across the OpenAPI service. These changes enhance the robustness of service readiness checks and improve the OAuth device authorization flow, contributing to a more reliable and flexible authentication mechanism.
118 lines
3 KiB
Go
118 lines
3 KiB
Go
package cmd
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// Credential represents the stored OAuth credential for gRPC mode.
|
|
type Credential struct {
|
|
Server string `json:"server"`
|
|
GRPCAddr string `json:"grpc_addr,omitempty"`
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
Scope string `json:"scope,omitempty"`
|
|
User string `json:"user,omitempty"`
|
|
ExpiresAt string `json:"expires_at,omitempty"`
|
|
}
|
|
|
|
// Expired returns true if the credential has an expires_at in the past.
|
|
func (c *Credential) Expired() bool {
|
|
if c.ExpiresAt == "" {
|
|
return false
|
|
}
|
|
t, err := time.Parse(time.RFC3339, c.ExpiresAt)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return time.Now().After(t)
|
|
}
|
|
|
|
func credentialPath() (string, error) {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot determine home directory: %w", err)
|
|
}
|
|
return filepath.Join(home, ".yao", "credentials"), nil
|
|
}
|
|
|
|
// LoadCredential reads and decodes ~/.yao/credentials. Returns nil if the file
|
|
// does not exist.
|
|
func LoadCredential() (*Credential, error) {
|
|
path, err := credentialPath()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
raw, err := os.ReadFile(path)
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read credentials: %w", err)
|
|
}
|
|
|
|
decoded, err := base64.StdEncoding.DecodeString(string(raw))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode credentials: %w", err)
|
|
}
|
|
|
|
var cred Credential
|
|
if err := json.Unmarshal(decoded, &cred); err != nil {
|
|
return nil, fmt.Errorf("unmarshal credentials: %w", err)
|
|
}
|
|
return &cred, nil
|
|
}
|
|
|
|
// LoadCredentialFrom reads and decodes a credential file from a custom path.
|
|
func LoadCredentialFrom(path string) (*Credential, error) {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read credentials from %s: %w", path, err)
|
|
}
|
|
decoded, err := base64.StdEncoding.DecodeString(string(raw))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode credentials: %w", err)
|
|
}
|
|
var cred Credential
|
|
if err := json.Unmarshal(decoded, &cred); err != nil {
|
|
return nil, fmt.Errorf("unmarshal credentials: %w", err)
|
|
}
|
|
return &cred, nil
|
|
}
|
|
|
|
// SaveCredential encodes and writes the credential to ~/.yao/credentials.
|
|
func SaveCredential(cred *Credential) error {
|
|
path, err := credentialPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dir := filepath.Dir(path)
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
return fmt.Errorf("create directory %s: %w", dir, err)
|
|
}
|
|
data, err := json.Marshal(cred)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal credentials: %w", err)
|
|
}
|
|
encoded := base64.StdEncoding.EncodeToString(data)
|
|
if err := os.WriteFile(path, []byte(encoded), 0600); err != nil {
|
|
return fmt.Errorf("write credentials: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveCredential deletes ~/.yao/credentials.
|
|
func RemoveCredential() error {
|
|
path, err := credentialPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("remove credentials: %w", err)
|
|
}
|
|
return nil
|
|
}
|