// 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") }