You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
4.0 KiB
Go
143 lines
4.0 KiB
Go
// Package zitadel provides Zitadel authentication client for the KnowFoolery application.
|
|
package zitadel
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// Config holds the configuration for the Zitadel client.
|
|
type Config struct {
|
|
BaseURL string
|
|
ProjectID string
|
|
AdminToken string
|
|
Timeout time.Duration
|
|
}
|
|
|
|
// DefaultConfig returns a default configuration.
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
Timeout: 10 * time.Second,
|
|
}
|
|
}
|
|
|
|
// Client provides access to Zitadel authentication services.
|
|
type Client struct {
|
|
config Config
|
|
httpClient *http.Client
|
|
jwksCache *JWKSCache
|
|
}
|
|
|
|
// JWKSCache caches the JSON Web Key Set for token validation.
|
|
type JWKSCache struct {
|
|
//mu sync.RWMutex
|
|
keys map[string]interface{}
|
|
//expiry time.Time
|
|
duration time.Duration
|
|
}
|
|
|
|
// NewJWKSCache creates a new JWKS cache.
|
|
func NewJWKSCache(cacheDuration time.Duration) *JWKSCache {
|
|
return &JWKSCache{
|
|
keys: make(map[string]interface{}),
|
|
duration: cacheDuration,
|
|
}
|
|
}
|
|
|
|
// NewClient creates a new Zitadel client.
|
|
func NewClient(config Config) *Client {
|
|
return &Client{
|
|
config: config,
|
|
httpClient: &http.Client{
|
|
Timeout: config.Timeout,
|
|
},
|
|
jwksCache: NewJWKSCache(5 * time.Minute),
|
|
}
|
|
}
|
|
|
|
// AuthClaims represents the claims extracted from a validated JWT.
|
|
type AuthClaims struct {
|
|
Subject string `json:"sub"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
Roles []string `json:"urn:zitadel:iam:org:project:roles"`
|
|
Audience []string `json:"aud"`
|
|
Issuer string `json:"iss"`
|
|
IssuedAt int64 `json:"iat"`
|
|
ExpiresAt int64 `json:"exp"`
|
|
MFAVerified bool `json:"amr"`
|
|
}
|
|
|
|
// TokenResponse represents a token response from Zitadel.
|
|
type TokenResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
}
|
|
|
|
// UserInfo represents user information from Zitadel.
|
|
type UserInfo struct {
|
|
ID string `json:"sub"`
|
|
Email string `json:"email"`
|
|
Name string `json:"name"`
|
|
Verified bool `json:"email_verified"`
|
|
Roles []string `json:"roles"`
|
|
}
|
|
|
|
// ValidateToken validates a JWT token and returns the claims.
|
|
// This is a placeholder implementation that should be replaced with actual JWT validation.
|
|
func (c *Client) ValidateToken(ctx context.Context, token string) (*AuthClaims, error) {
|
|
// TODO: Implement actual JWT validation with JWKS
|
|
// This is a placeholder that should:
|
|
// 1. Fetch JWKS from Zitadel
|
|
// 2. Parse and validate the JWT
|
|
// 3. Verify signature, expiration, issuer, audience
|
|
// 4. Return the claims
|
|
return nil, fmt.Errorf("not implemented: token validation requires JWKS integration")
|
|
}
|
|
|
|
// RefreshToken refreshes an access token using a refresh token.
|
|
func (c *Client) RefreshToken(ctx context.Context, refreshToken string) (*TokenResponse, error) {
|
|
// TODO: Implement token refresh
|
|
return nil, fmt.Errorf("not implemented: token refresh")
|
|
}
|
|
|
|
// GetUserInfo retrieves user information using an access token.
|
|
func (c *Client) GetUserInfo(ctx context.Context, accessToken string) (*UserInfo, error) {
|
|
url := fmt.Sprintf("%s/oidc/v1/userinfo", c.config.BaseURL)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
|
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get user info: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("failed to get user info: status %d", resp.StatusCode)
|
|
}
|
|
|
|
var userInfo UserInfo
|
|
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
|
|
return nil, fmt.Errorf("failed to decode user info: %w", err)
|
|
}
|
|
|
|
return &userInfo, nil
|
|
}
|
|
|
|
// RevokeToken revokes a token.
|
|
func (c *Client) RevokeToken(ctx context.Context, token string) error {
|
|
// TODO: Implement token revocation
|
|
return fmt.Errorf("not implemented: token revocation")
|
|
}
|