auth

package
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 29, 2026 License: Apache-2.0 Imports: 24 Imported by: 0

Documentation

Overview

identity.go manages agent instance identification for tracking.

Each agent installation gets a unique agentId (UUID v4) that persists across version upgrades but regenerates on reinstall. This identity is transparently injected into MCP HTTP headers for gateway-side data collection.

Index

Constants

View Source
const (
	// AuthorizeURL is the DingTalk OAuth authorization page.
	AuthorizeURL = "https://login.dingtalk.com/oauth2/auth"

	// UserAccessTokenURL exchanges an authorization code for user tokens.
	UserAccessTokenURL = "https://api.dingtalk.com/v1.0/oauth2/userAccessToken"

	// UserInfoURL fetches the authenticated user's profile.
	UserInfoURL = "https://api.dingtalk.com/v1.0/contact/users/me"

	// DefaultClientID is the CLI's built-in OAuth client ID (DingTalk AppKey).
	// TODO: Replace <YOUR_CLIENT_ID> with your actual DingTalk AppKey before building.
	DefaultClientID = "<YOUR_CLIENT_ID>"

	// DefaultClientSecret is the CLI's built-in OAuth client secret (DingTalk AppSecret).
	// TODO: Replace <YOUR_CLIENT_SECRET> with your actual DingTalk AppSecret before building.
	DefaultClientSecret = "<YOUR_CLIENT_SECRET>"

	// CallbackPath is the localhost callback endpoint for OAuth redirect.
	CallbackPath = "/callback"

	// DefaultScopes are the OAuth scopes requested by the CLI.
	DefaultScopes = "openid corpid"

	// DefaultDeviceBaseURL is the login server base URL for device flow.
	DefaultDeviceBaseURL = "https://login.dingtalk.com"

	// DeviceCodePath requests a device_code and user_code.
	DeviceCodePath = "/oauth2/device/code.json"

	// DeviceTokenPath polls for authorization completion.
	DeviceTokenPath = "/oauth2/device/token.json"

	// DeviceGrantType is the grant_type value defined by RFC 8628.
	DeviceGrantType = "urn:ietf:params:oauth:grant-type:device_code"

	LogoutURL         = "https://login.dingtalk.com/oauth2/logout"
	LogoutContinueURL = "https://login.dingtalk.com"
)

Variables

View Source
var ErrTokenDecryption = errors.New("token decryption failed")

ErrTokenDecryption indicates that token decryption failed, typically due to a device mismatch or corrupted data file. Callers can check this with errors.Is to distinguish decryption failures from other I/O or parsing errors.

Functions

func ClientID

func ClientID() string

ClientID returns the OAuth client ID with priority: 1. Runtime override (CLI flag --client-id) 2. Environment variable (DWS_CLIENT_ID) 3. Default hardcoded value

func ClientSecret

func ClientSecret() string

ClientSecret returns the OAuth client secret with priority: 1. Runtime override (CLI flag --client-secret) 2. Environment variable (DWS_CLIENT_SECRET) 3. Default hardcoded value

func DeleteSecureData

func DeleteSecureData(configDir string) error

DeleteSecureData removes .data file from configDir.

func DeleteTokenData

func DeleteTokenData(configDir string) error

DeleteTokenData removes token data from both keychain and legacy storage.

func DeleteTokenDataKeychain

func DeleteTokenDataKeychain() error

DeleteTokenDataKeychain removes TokenData from the platform keychain.

func EnsureMigration

func EnsureMigration(configDir string, logger *slog.Logger)

EnsureMigration performs one-time migration from legacy .data to keychain. This should be called early in the auth flow (e.g., during GetAccessToken). The migration is idempotent and thread-safe.

func IsMigrationDone

func IsMigrationDone() bool

IsMigrationDone returns true if migration has been attempted.

func RevokeTokenRemote

func RevokeTokenRemote(ctx context.Context) error

RevokeTokenRemote calls the DingTalk logout endpoint to invalidate the access token. This should be called before deleting local token data. The function is best-effort: errors are returned but callers may choose to ignore them.

func SaveSecureTokenData

func SaveSecureTokenData(configDir string, data *TokenData) error

SaveSecureTokenData encrypts and saves TokenData to .data file. The data is encrypted using AES-256-GCM with a key derived from the device MAC address. Uses atomic write (write .tmp then rename) to prevent corruption.

Concurrency: callers that involve token refresh MUST hold the business-level file lock (via acquireTokenLock) to prevent two processes from refreshing simultaneously. See OAuthProvider.lockedRefresh().

func SaveTokenData

func SaveTokenData(configDir string, data *TokenData) error

SaveTokenData saves TokenData to the platform keychain. Uses the new keychain-based storage with random master key for better security.

func SaveTokenDataKeychain

func SaveTokenDataKeychain(data *TokenData) error

SaveTokenDataKeychain saves TokenData to the platform keychain. This is the new secure storage method using random master key.

func SecureDataExists

func SecureDataExists(configDir string) bool

SecureDataExists checks if the secure .data file exists in the given directory.

func SetClientID

func SetClientID(id string)

SetClientID allows runtime override of the client ID (e.g., from CLI flags).

func SetClientSecret

func SetClientSecret(secret string)

SetClientSecret allows runtime override of the client secret (e.g., from CLI flags).

func TokenDataExistsKeychain

func TokenDataExistsKeychain() bool

TokenDataExistsKeychain checks if token data exists in keychain.

Types

type DeviceAuthResponse

type DeviceAuthResponse struct {
	DeviceCode              string `json:"deviceCode"`
	UserCode                string `json:"userCode"`
	VerificationURI         string `json:"verificationUri"`
	VerificationURIComplete string `json:"verificationUriComplete"`
	ExpiresIn               int    `json:"expiresIn"`
	Interval                int    `json:"interval"`
}

type DeviceFlowProvider

type DeviceFlowProvider struct {
	Output io.Writer
	// contains filtered or unexported fields
}

func NewDeviceFlowProvider

func NewDeviceFlowProvider(configDir string, logger *slog.Logger) *DeviceFlowProvider

func (*DeviceFlowProvider) Login

func (p *DeviceFlowProvider) Login(ctx context.Context) (*TokenData, error)

func (*DeviceFlowProvider) SetBaseURL

func (p *DeviceFlowProvider) SetBaseURL(baseURL string)

type DeviceTokenResponse

type DeviceTokenResponse struct {
	AuthCode    string `json:"authCode"`
	RedirectURL string `json:"redirectUrl"`
	Error       string `json:"error"`
}

type DualLock

type DualLock struct {
	Waited bool // true if we waited for another goroutine/process
	// contains filtered or unexported fields
}

DualLock holds both process-level and file-level locks.

func AcquireDualLock

func AcquireDualLock(ctx context.Context, configDir string) (*DualLock, error)

AcquireDualLock acquires both process-level and file-level locks. This provides comprehensive protection against: 1. Multiple goroutines in the same process (sync.Map) 2. Multiple CLI processes (file lock)

The caller MUST call Release() when done.

func (*DualLock) Release

func (d *DualLock) Release()

Release releases both locks in reverse order.

type Identity

type Identity struct {
	AgentID string `json:"agentId"` // UUID v4, generated at install time
	Source  string `json:"source"`  // data source, default "dws"
}

Identity holds the agent instance identification fields.

func EnsureExists

func EnsureExists(configDir string) *Identity

EnsureExists loads existing identity or creates a new one if not present.

func Load

func Load(configDir string) *Identity

Load reads the identity from <configDir>/identity.json. Returns nil if the file does not exist or cannot be parsed.

func (*Identity) Headers

func (id *Identity) Headers() map[string]string

Headers returns the identity as HTTP header key-value pairs.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

func NewManager

func NewManager(configDir string, logger *slog.Logger) *Manager

func (*Manager) DeleteToken

func (m *Manager) DeleteToken() error

func (*Manager) GetMCPURL

func (m *Manager) GetMCPURL() (string, error)

func (*Manager) GetToken

func (m *Manager) GetToken() (string, string, error)

func (*Manager) IsAuthenticated

func (m *Manager) IsAuthenticated() bool

func (*Manager) SaveMCPURL

func (m *Manager) SaveMCPURL(url string) error

func (*Manager) SaveToken

func (m *Manager) SaveToken(token string) error

func (*Manager) Status

func (m *Manager) Status() (authenticated bool, source string, maskedToken string)

type OAuthProvider

type OAuthProvider struct {
	Output io.Writer
	// contains filtered or unexported fields
}

OAuthProvider handles the DingTalk OAuth 2.0 authorization code flow.

func NewOAuthProvider

func NewOAuthProvider(configDir string, logger *slog.Logger) *OAuthProvider

NewOAuthProvider creates a new OAuth provider.

func (*OAuthProvider) ExchangeAuthCode

func (p *OAuthProvider) ExchangeAuthCode(ctx context.Context, authCode, uid string) (*TokenData, error)

ExchangeAuthCode takes an AuthCode and an optional UserID provided by an external host, exchanges it for tokens, and persists them.

func (*OAuthProvider) GetAccessToken

func (p *OAuthProvider) GetAccessToken(ctx context.Context) (string, error)

GetAccessToken returns a valid access token, auto-refreshing if needed. Uses a file lock with double-check pattern to prevent concurrent refresh from multiple CLI processes.

func (*OAuthProvider) Login

func (p *OAuthProvider) Login(ctx context.Context, force bool) (*TokenData, error)

Login performs authentication with smart degradation: 1. If force=false, try silent token refresh first (refresh_token) 2. If all silent methods fail (or force=true), fall back to browser OAuth flow

func (*OAuthProvider) Logout

func (p *OAuthProvider) Logout() error

Logout clears all stored credentials.

func (*OAuthProvider) Status

func (p *OAuthProvider) Status() (*TokenData, error)

Status returns the current auth status.

type TokenData

type TokenData struct {
	AccessToken    string    `json:"access_token"`
	RefreshToken   string    `json:"refresh_token"`
	PersistentCode string    `json:"persistent_code"`
	ExpiresAt      time.Time `json:"expires_at"`
	RefreshExpAt   time.Time `json:"refresh_expires_at"`
	CorpID         string    `json:"corp_id"`
	UserID         string    `json:"user_id,omitempty"`
	UserName       string    `json:"user_name,omitempty"`
	CorpName       string    `json:"corp_name,omitempty"`
	UpdatedAt      string    `json:"updated_at,omitempty"`
	Source         string    `json:"source,omitempty"`
}

TokenData holds the OAuth token set persisted to disk.

func LoadSecureTokenData

func LoadSecureTokenData(configDir string) (*TokenData, error)

LoadSecureTokenData decrypts and loads TokenData from .data file. Reads are safe without locking because SaveSecureTokenData uses atomic rename.

func LoadTokenData

func LoadTokenData(configDir string) (*TokenData, error)

LoadTokenData reads TokenData from the platform keychain. On first call, it attempts to migrate legacy .data file if present.

func LoadTokenDataKeychain

func LoadTokenDataKeychain() (*TokenData, error)

LoadTokenDataKeychain loads TokenData from the platform keychain.

func (*TokenData) HasPersistentCode

func (t *TokenData) HasPersistentCode() bool

HasPersistentCode returns true if a persistent code is available.

func (*TokenData) IsAccessTokenValid

func (t *TokenData) IsAccessTokenValid() bool

IsAccessTokenValid returns true if the access token has not expired.

func (*TokenData) IsRefreshTokenValid

func (t *TokenData) IsRefreshTokenValid() bool

IsRefreshTokenValid returns true if the refresh token has not expired.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL