auth

package
v1.0.54 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 38 Imported by: 0

Documentation

Overview

agent_code_detect.go resolves the agent_code — which agent HOST is driving dws (claudecode / qoder / cursor / vscode / openclaw / hermes / ...). It fills the x-dingtalk-dws-agent-code header for per-channel statistics.

SEPARATE axis from DWS_CHANNEL / x-dws-channel (a distribution channel code); the two are never conflated here.

Design contract — ACCURACY OVER COVERAGE, but maximize accurate coverage:

  • Prefer generalizable, host-declared signals so one rule covers a whole family (VSCODE_BRAND covers every VS Code fork, present and future).
  • Every per-host signature below is OBSERVED on a real host (live process env via `ps eww`, or the app bundle Info.plist), not guessed.
  • Anything unidentified stays empty — never guess or synthesize a PAT key.
  • Deliberately NOT used: TERM_PROGRAM (reports the terminal, e.g. iTerm, not the agent host) and fuzzy parent-process name matching.

identity.go manages agent instance identification for tracking.

Identity has two granularities, both injected into MCP HTTP headers for gateway-side statistics:

  • machineId: a stable per-install UUID v4 (persists across upgrades, regenerates on reinstall). Non-PII.
  • agentId: a per-(machine × agentCode) id derived deterministically from machineId + agent_code, so one machine running multiple agent hosts (e.g. claudecode + cursor) yields a distinct, idempotent agentId per agent_code. Computed client-side — no gateway round-trip required.

The agent_code itself is resolved by DetectAgentCode (agent_code_detect.go).

Index

Constants

View Source
const (
	EnvClientID     = "DWS_CLIENT_ID"
	EnvClientSecret = "DWS_CLIENT_SECRET"
)

Env var names used by the env fallback channel. Must be set as a pair — any single-variable configuration is rejected so users cannot accidentally "set the env half-way" and silently fall back to keychain.

View Source
const (
	// AgentCodeEnv is the primary per-spawn environment variable the host
	// injects to declare "this process is driven by a third-party Agent host,
	// render authorization UI yourselves".
	AgentCodeEnv = "DINGTALK_DWS_AGENTCODE"

	// AgentCodeEnvCompat is a compatibility alias for hosts that shipped the
	// reversed prefix before AgentCodeEnv became the public spelling.
	AgentCodeEnvCompat = "DWS_DINGTALK_AGENTCODE"
)
View Source
const (
	StatusPending   = "PENDING"
	StatusApproved  = "APPROVED"
	StatusRejected  = "REJECTED"
	StatusExpired   = "EXPIRED"
	StatusCancelled = "CANCELLED"
)

Device flow authorization status constants. Shared across device_flow.go and pat_auth_retry.go to avoid maintaining string literals in multiple places.

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"

	// Terminal API base URL for developer settings page.
	DefaultTerminalBaseURL = "https://open-dev.dingtalk.com"
	// DevicePollPath is the device flow polling path (used with MCP base URL).
	DevicePollPath = "/cli/oauth/device/poll"

	// DeveloperSettingsPath is the path to the organization developer settings page.
	DeveloperSettingsPath = "/fe/old#/developerSettings"

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

	// MCP API endpoints for CLI authorization management.
	DefaultMCPBaseURL    = config.DefaultMCPBaseURL
	CLIAuthEnabledPath   = "/cli/cliAuthEnabled"
	SuperAdminPath       = "/cli/superAdmin"
	SendCliAuthApplyPath = "/cli/sendCliAuthApply"
	ClientIDPath         = "/cli/clientId"

	// MCP OAuth endpoints (used when clientId is fetched from MCP).
	MCPOAuthTokenPath   = "/oauth2/getToken"
	MCPRefreshTokenPath = "/oauth2/refreshToken"
	MCPRevokeTokenPath  = "/oauth2/revokeToken"

	// AppAccessTokenURL is the unified app-level access token endpoint.
	// POST with {"appKey":"X","appSecret":"X"} → {"accessToken":"...","expireIn":7200}
	AppAccessTokenURL = "https://api.dingtalk.com/v1.0/oauth2/accessToken"
)
View Source
const (
	ProfileStatusActive      = "active"
	ProfileStatusExpired     = "expired"
	ProfileStatusRevoked     = "revoked"
	ProfileStatusUnavailable = "unavailable"
)
View Source
const AgentCodeCustom = "custom"

AgentCodeCustom is the literal code a host may explicitly declare for a custom integration. It is not used as an implicit fallback.

Variables

View Source
var (
	// ErrAppConfigMissing — no app config file on disk AND no env-var
	// credentials present. Prompt the user to either `dws config init` or
	// set DWS_CLIENT_ID + DWS_CLIENT_SECRET.
	ErrAppConfigMissing = errors.New("app config missing: run `dws config init` or set DWS_CLIENT_ID/DWS_CLIENT_SECRET env vars")
	// ErrClientIDEmpty — neither env nor config supplies a non-empty ClientID.
	ErrClientIDEmpty = errors.New("ClientID is empty")
	// ErrClientSecretEmpty — there's a ClientID but ClientSecret resolved to "".
	ErrClientSecretEmpty = errors.New("ClientSecret is empty")
	// ErrSecretResolve — the secret-resolution backend (keychain) failed
	// unrecoverably. Typically headless Linux without gnome-keyring, locked
	// macOS keychain, or CI sandboxes. Suggest the env-var fallback.
	ErrSecretResolve = errors.New("ClientSecret resolution failed (keychain unavailable?); try DWS_CLIENT_ID/DWS_CLIENT_SECRET env vars")
)

Strict resolver error sentinels. Use errors.Is to distinguish failure modes; see plan §8 strict resolver decision (4 classes).

View Source
var ErrTokenDataNotFound = errors.New("token data not found")

ErrTokenDataNotFound means the requested keychain slot does not exist.

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.

View Source
var ValidSecretSources = map[string]bool{
	"file": true, "keychain": true,
}

ValidSecretSources is the set of recognized SecretRef sources.

Functions

func AgentCodeEnvPresent added in v1.0.37

func AgentCodeEnvPresent() bool

func AgentCodeFromEnv added in v1.0.37

func AgentCodeFromEnv() (string, string)

AgentCodeFromEnv returns the effective host agent code and the env name that supplied it.

Keep the public env surface intentionally single-spelled. The reversed DWS_DINGTALK_AGENTCODE draft name is not consumed, so host-owned PAT mode, gateway identity headers, and `pat chmod --agentCode` fallback all agree on the same stable signal: DINGTALK_DWS_AGENTCODE.

func ClientID

func ClientID() string

ClientID returns the OAuth client ID with priority: 1. Runtime override (CLI flag --client-id) 2. Persisted app config (from previous login) 3. Environment variable (DWS_CLIENT_ID) 4. Default hardcoded value (if not a placeholder) Returns empty string if no valid client ID is available. Note: MCP server fetch (priority 4 in the full flow) is handled in OAuthProvider.Login()

func ClientSecret

func ClientSecret() string

ClientSecret returns the OAuth client secret with priority: 1. Runtime override (CLI flag --client-secret) 2. Persisted app config (from previous login, stored in keychain) 3. Environment variable (DWS_CLIENT_SECRET) 4. Default hardcoded value

func DeleteAllTokenData added in v1.0.45

func DeleteAllTokenData(configDir string) error

DeleteAllTokenData removes all profile-scoped and legacy token data.

func DeleteAppConfig added in v1.0.4

func DeleteAppConfig(configDir string) error

DeleteAppConfig removes the app configuration and associated keychain secrets.

func DeleteAppTokenData added in v1.0.18

func DeleteAppTokenData(clientID string) error

DeleteAppTokenData removes AppTokenData from keychain for the given clientID.

func DeleteClientSecret added in v1.0.6

func DeleteClientSecret(clientID string) error

DeleteClientSecret removes the stored client secret for a specific client ID.

func DeleteSecureData

func DeleteSecureData(configDir string) error

DeleteSecureData removes .data file from configDir.

func DeleteTokenData

func DeleteTokenData(configDir string) error

DeleteTokenData removes token data. Edition hooks and the default keychain path are both serialized with refresh through the auth dual lock.

func DeleteTokenDataForProfile added in v1.0.45

func DeleteTokenDataForProfile(configDir, profile string) error

DeleteTokenDataForProfile removes one profile's token data. Empty selector removes the current/default profile, falling back to legacy single-slot auth.

func DeleteTokenDataKeychain

func DeleteTokenDataKeychain() error

DeleteTokenDataKeychain removes TokenData from the platform keychain.

func DeleteTokenDataKeychainForCorpID added in v1.0.45

func DeleteTokenDataKeychainForCorpID(corpID string) error

DeleteTokenDataKeychainForCorpID removes TokenData from a corp-scoped keychain slot.

func DeleteTokenDataKeychainForIdentity added in v1.0.54

func DeleteTokenDataKeychainForIdentity(corpID, userID string) error

DeleteTokenDataKeychainForIdentity removes one identity-scoped token.

func DeleteTokenMarker added in v1.0.9

func DeleteTokenMarker(configDir string) error

DeleteTokenMarker removes the token.json marker file.

func DetectAgentCode added in v1.0.38

func DetectAgentCode() (code string, signal string)

DetectAgentCode resolves the agent_code via a confidence ladder and returns the normalized code plus the signal that decided it:

T0  explicit host declaration   (DINGTALK_DWS_AGENTCODE — dedicated field)
T1  verified per-agent env signature (CLI/daemon agents)
T2  VSCODE_BRAND value           (every VS Code fork declares its brand)
T3  macOS app bundle id          (known agent bundles only)
T4  unresolved -> empty          (never guess)

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 EnsureProfilesMigration added in v1.0.45

func EnsureProfilesMigration(configDir string) error

EnsureProfilesMigration initializes profiles.json from the legacy auth-token slot when needed. EnsureProfilesMigration migrates a legacy single-slot token into the profiles registry. It acquires the lock; call ensureProfilesMigrationLocked from contexts that already hold it (refresh / read paths).

func EnvHalfSet added in v1.0.52

func EnvHalfSet() bool

EnvHalfSet reports whether exactly one of (DWS_CLIENT_ID, DWS_CLIENT_SECRET) is set. Used by CLI preflight to emit a clear stderr warning of the form:

WARN: DWS_CLIENT_ID is set but DWS_CLIENT_SECRET is not — env fallback
      disabled; using keychain/app config. Set both or unset both to
      avoid this warning.

The strict resolver itself does NOT log; logging is the caller's job.

func ExportPortableAuthBundle added in v1.0.33

func ExportPortableAuthBundle(configDir string, w io.Writer) error

ExportPortableAuthBundle writes a portable auth bundle as tar.gz. It copies the encrypted keychain files plus the small config files needed to refresh tokens in another Linux sandbox.

func FetchAppToken added in v1.0.18

func FetchAppToken(ctx context.Context, appKey, appSecret string) (token string, expiresIn int64, err error)

FetchAppToken obtains an app-level access token from the unified endpoint:

POST https://api.dingtalk.com/v1.0/oauth2/accessToken
Body: {"appKey":"X","appSecret":"X"}
Response: {"accessToken":"xxx","expireIn":7200}

The same token works for both api.dingtalk.com and oapi.dingtalk.com.

func FetchClientIDFromMCP added in v1.0.5

func FetchClientIDFromMCP(ctx context.Context) (string, error)

FetchClientIDFromMCP fetches the CLI client ID from MCP server. This is used when no client ID is provided via flags, config, or env vars. It retries up to mcpRequestMaxRetries times on transient errors.

func GetAppConfigPath added in v1.0.4

func GetAppConfigPath(configDir string) string

GetAppConfigPath returns the path to the app config file for the currently-active edition. The filename is partitioned by edition so that two dws binaries from different editions sharing the same configDir (typically ~/.dws or DWS_CONFIG_DIR) cannot read or overwrite each other's credentials. Open-source stays on "app.json" for backwards compatibility; sibling editions land on "app-<edition>.json".

func GetDeveloperSettingsURL added in v1.0.11

func GetDeveloperSettingsURL() string

GetDeveloperSettingsURL returns the full URL to the organization developer settings page, derived from the terminal base URL.

func GetMCPBaseURL added in v1.0.5

func GetMCPBaseURL() string

GetMCPBaseURL returns the MCP base URL with priority: 1. ~/.dws/mcp_url file content (for pre-release environment) 2. Default value (https://mcp.dingtalk.com)

func GetRefreshTokenURL added in v1.0.5

func GetRefreshTokenURL() string

GetRefreshTokenURL returns the appropriate token refresh URL. Uses MCP endpoint when clientID is from MCP, otherwise uses direct DingTalk API.

func GetRevokeTokenURL added in v1.0.5

func GetRevokeTokenURL() string

GetRevokeTokenURL returns the token revocation URL (MCP only). Returns empty string if not using MCP mode.

func GetTerminalBaseURL added in v1.0.11

func GetTerminalBaseURL() string

GetTerminalBaseURL returns the terminal base URL with priority: 1. ~/.dws/terminal_url file content (for pre-release environment) 2. Default value (https://open-dev.dingtalk.com)

func GetUserAccessTokenURL added in v1.0.5

func GetUserAccessTokenURL() string

GetUserAccessTokenURL returns the appropriate token exchange URL. Uses MCP endpoint when clientID is from MCP, otherwise uses direct DingTalk API.

func HasAppConfig added in v1.0.4

func HasAppConfig(configDir string) bool

HasAppConfig returns true if an app configuration file exists.

func HasValidClientSecret added in v1.0.5

func HasValidClientSecret() bool

HasValidClientSecret returns true if a valid client secret is available. A valid secret is one that is not a placeholder (e.g., <YOUR_CLIENT_SECRET>).

func HostOwnsPATFlow added in v1.0.18

func HostOwnsPATFlow() bool

HostOwnsPATFlow reports whether the current process is running under a third-party Agent host that will render the PAT authorization card itself. The trigger is DINGTALK_DWS_AGENTCODE being non-empty. The CLI deliberately does not consult any other signal (DINGTALK_AGENT / DWS_CHANNEL / the wire claw-type header) for this decision so that server-side routing tags and the host-owned UI contract remain independent concerns.

func IsClientIDFromMCP added in v1.0.5

func IsClientIDFromMCP() bool

IsClientIDFromMCP returns true if the current clientID was fetched from MCP server.

func IsMigrationDone

func IsMigrationDone() bool

IsMigrationDone returns true if migration has been attempted.

func LoadClientSecret added in v1.0.6

func LoadClientSecret(clientID string) string

LoadClientSecret retrieves the stored client secret for a specific client ID. Returns empty string if not found.

func MarkAccessTokenStale added in v1.0.16

func MarkAccessTokenStale(configDir string) error

MarkAccessTokenStale loads the persisted TokenData, sets ExpiresAt to a past instant (preserving access_token and refresh_token), and writes it back. The next OAuthProvider.GetAccessToken call will see IsAccessTokenValid() == false and proceed to lockedRefresh, exchanging the refresh_token for a fresh access_token.

Use this only when the server has rejected the current access_token but the local expiry has not yet elapsed (zombie token scenario). It does not delete any token material and is safe to call concurrently — actual refresh is serialized by lockedRefresh's dual-layer locking.

Returns the original load error when there is no usable token on disk; a nil error when there is no access_token to invalidate (no-op).

func MarkProfileStatus added in v1.0.45

func MarkProfileStatus(configDir, selector, status string) error

MarkProfileStatus updates a selected profile status if it exists.

func MigrateKeychainToFileDEK added in v1.0.52

func MigrateKeychainToFileDEK(configDir string, dryRun bool) (int, error)

MigrateKeychainToFileDEK serializes migration with profile/token updates so refresh and login cannot rewrite an entry while its DEK backend is changing.

func ParseDeviceFlowStatus added in v1.0.11

func ParseDeviceFlowStatus(rawStatus string, success bool) string

ParseDeviceFlowStatus normalizes a raw status string from the device flow poll response into a canonical status constant. When the server returns an empty status with success=false, it falls back to StatusExpired (server error / flow not found).

func ParseIdentitySelector added in v1.0.54

func ParseIdentitySelector(selector string) (corpID, userID string, ok bool)

ParseIdentitySelector splits corpId:userId selectors.

func PortableAuthSourceReady added in v1.0.33

func PortableAuthSourceReady() bool

PortableAuthSourceReady reports whether encrypted auth token exists for export.

func PortableAuthTargetPopulated added in v1.0.33

func PortableAuthTargetPopulated(configDir string) bool

PortableAuthTargetPopulated reports whether local auth files would be overwritten by a portable import.

func PortableExportSupportError added in v1.0.54

func PortableExportSupportError() error

PortableExportSupportError explains why the current credential backend cannot produce a portable auth bundle. A nil error means export is supported.

func PortableExportSupported added in v1.0.33

func PortableExportSupported() bool

PortableExportSupported reports whether the current credential backend can produce a bundle that includes the file-based DEK required for import elsewhere.

func PortableImportSupportError added in v1.0.54

func PortableImportSupportError() error

PortableImportSupportError explains why the current credential backend cannot consume a portable auth bundle. A nil error means import is supported.

func ProfileSelector added in v1.0.54

func ProfileSelector(profile Profile) string

ProfileSelector returns the exact identity selector for a profile when its userId is known, otherwise it returns the historical corpId selector.

func ProfilesPath added in v1.0.45

func ProfilesPath(configDir string) string

ProfilesPath returns the profile metadata path for a config dir.

func ReadTokenMarkerRevision added in v1.0.54

func ReadTokenMarkerRevision(configDir string) (revision string, present bool, err error)

ReadTokenMarkerRevision returns the current credential publication revision. Existing markers without a revision remain readable, but callers must avoid caching them because they cannot prove that the credential is unchanged.

func RemoveSecretStore added in v1.0.4

func RemoveSecretStore(input SecretInput)

RemoveSecretStore cleans up keychain entries when an app is removed. Errors are intentionally ignored — cleanup is best-effort.

func ResolveAppCredentials added in v1.0.4

func ResolveAppCredentials(configDir string) (clientID, clientSecret string)

ResolveAppCredentials resolves the client ID and secret from the app config. Results are cached to avoid repeated keychain access. Returns empty strings if the config doesn't exist or resolution fails.

func ResolveSecret added in v1.0.4

func ResolveSecret(input SecretInput) (string, error)

ResolveSecret resolves a SecretInput to a plain string. SecretRef objects are resolved by source (file / keychain).

func RevokeTokenRemote

func RevokeTokenRemote(ctx context.Context) error

RevokeTokenRemote calls the appropriate logout/revoke endpoint to invalidate the access token. Uses MCP revoke endpoint when clientID is from MCP, otherwise uses DingTalk logout. 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 RevokeTokenRemoteForData added in v1.0.54

func RevokeTokenRemoteForData(ctx context.Context, tokenData *TokenData) error

RevokeTokenRemoteForData revokes the supplied account token using the credential source and client ID persisted with that exact identity.

func RuntimeProfile added in v1.0.45

func RuntimeProfile() string

RuntimeProfile returns the process-local one-shot profile override.

func SaveAppConfig added in v1.0.4

func SaveAppConfig(configDir string, config *AppConfig) error

SaveAppConfig saves the app configuration to disk. If the client secret is a plain string, it will be stored in keychain and the config file will contain a reference to it.

func SaveAppTokenData added in v1.0.18

func SaveAppTokenData(data *AppTokenData) error

SaveAppTokenData persists AppTokenData to keychain, keyed by clientID.

func SaveClientSecret added in v1.0.6

func SaveClientSecret(clientID, clientSecret string) error

SaveClientSecret stores the client secret for a specific client ID. This is called during login to snapshot the credentials used.

func SaveProfiles added in v1.0.45

func SaveProfiles(configDir string, cfg *ProfilesConfig) error

SaveProfiles writes profiles.json atomically.

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 persists TokenData under the auth dual lock. When an edition hook (SaveToken) is registered, the locked write delegates to that hook; otherwise it falls back to the default keychain-based storage.

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 SaveTokenDataKeychainForCorpID added in v1.0.45

func SaveTokenDataKeychainForCorpID(corpID string, data *TokenData) error

SaveTokenDataKeychainForCorpID saves TokenData to a corp-scoped keychain slot.

func SaveTokenDataKeychainForIdentity added in v1.0.54

func SaveTokenDataKeychainForIdentity(corpID, userID string, data *TokenData) error

SaveTokenDataKeychainForIdentity saves TokenData to an identity-scoped slot.

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 SetClientIDFromMCP added in v1.0.5

func SetClientIDFromMCP(id string)

SetClientIDFromMCP sets the clientID fetched from MCP server and marks it as MCP-sourced.

func SetClientSecret

func SetClientSecret(secret string)

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

func SetRuntimeProfile added in v1.0.45

func SetRuntimeProfile(profile string)

SetRuntimeProfile sets a process-local one-shot profile override.

func SyncLegacyTokenMirror added in v1.0.45

func SyncLegacyTokenMirror(configDir string) error

SyncLegacyTokenMirror mirrors the current profile token into legacy auth-token.

func TokenAccountForCorpID added in v1.0.45

func TokenAccountForCorpID(corpID string) string

TokenAccountForCorpID returns the keychain account used for a corp-bound token.

func TokenAccountForIdentity added in v1.0.54

func TokenAccountForIdentity(corpID, userID string) string

TokenAccountForIdentity returns the stable keychain account used for one DingTalk identity. The hash avoids collisions caused by delimiter escaping or keychain/file-name restrictions.

func TokenDataExistsKeychain

func TokenDataExistsKeychain() bool

TokenDataExistsKeychain checks if token data exists in keychain.

func TokenDataExistsKeychainForCorpID added in v1.0.45

func TokenDataExistsKeychainForCorpID(corpID string) bool

TokenDataExistsKeychainForCorpID checks if a corp-scoped token exists.

func TokenDataExistsKeychainForIdentity added in v1.0.54

func TokenDataExistsKeychainForIdentity(corpID, userID string) bool

TokenDataExistsKeychainForIdentity checks if an identity-scoped token exists.

func TokenProfileSelector added in v1.0.54

func TokenProfileSelector(data *TokenData) string

TokenProfileSelector returns the exact identity selector for token data when its userId is known, otherwise it returns the historical corpId selector.

func UpsertProfileFromToken added in v1.0.45

func UpsertProfileFromToken(configDir string, data *TokenData) error

UpsertProfileFromToken updates profiles.json after a successful login or refresh.

func UpsertProfileFromTokenWithCurrent added in v1.0.45

func UpsertProfileFromTokenWithCurrent(configDir string, data *TokenData, makeCurrent bool) error

UpsertProfileFromTokenWithCurrent updates profiles.json and optionally makes the token's corp the persistent current profile.

func WriteManualTokenMarker added in v1.0.54

func WriteManualTokenMarker(configDir string) error

WriteManualTokenMarker marks the legacy global keychain slot as an explicit `auth login --token` credential. The additive field keeps older hosts, which only inspect token.json presence and mtime, fully compatible.

func WriteTokenMarker added in v1.0.9

func WriteTokenMarker(configDir string) error

WriteTokenMarker writes a token.json marker containing only an updated_at timestamp. The host application uses this file's presence and mtime to decide whether it needs to trigger a new auth exchange.

Types

type AgentEntry added in v1.0.38

type AgentEntry struct {
	AgentID   string `json:"agentId"`
	FirstSeen string `json:"firstSeen,omitempty"`
	Detect    string `json:"detect,omitempty"` // signal that decided the agent_code
}

AgentEntry records the derived agentId for a single agent_code on this machine.

type AppConfig added in v1.0.4

type AppConfig struct {
	ClientID     string      `json:"clientId"`
	ClientSecret SecretInput `json:"clientSecret"`
	CreatedAt    time.Time   `json:"createdAt"`
	UpdatedAt    time.Time   `json:"updatedAt,omitempty"`
}

AppConfig represents the application credentials configuration. This is stored in the edition-specific app config file, with the client secret securely stored in keychain when present.

func GetCachedAppConfig added in v1.0.4

func GetCachedAppConfig(configDir string) *AppConfig

GetCachedAppConfig returns the cached app configuration. It loads from disk on first call and caches the result. Returns nil if no configuration exists or loading fails.

func LoadAppConfig added in v1.0.4

func LoadAppConfig(configDir string) (*AppConfig, error)

LoadAppConfig loads the app configuration from disk. Returns nil, nil if the config file does not exist.

func ReloadAppConfig added in v1.0.4

func ReloadAppConfig(configDir string) (*AppConfig, error)

ReloadAppConfig forces a reload of the app configuration from disk. This should be called after SaveAppConfig to ensure the cache is updated.

type AppTokenData added in v1.0.18

type AppTokenData struct {
	AccessToken string    `json:"access_token,omitempty"`
	ExpiresAt   time.Time `json:"expires_at,omitempty"`

	// Associated app credentials
	ClientID  string    `json:"client_id"`
	UpdatedAt time.Time `json:"updated_at"`
}

AppTokenData stores the app-level access token obtained from the unified POST /v1.0/oauth2/accessToken endpoint. It works for both new-style (api.dingtalk.com) and legacy (oapi.dingtalk.com) APIs — the auth method (header vs query param) is chosen by the caller based on the target host.

func LoadAppTokenData added in v1.0.18

func LoadAppTokenData(clientID string) (*AppTokenData, error)

LoadAppTokenData loads AppTokenData from keychain for the given clientID. Returns nil, nil if no data exists.

func (*AppTokenData) IsTokenValid added in v1.0.18

func (d *AppTokenData) IsTokenValid() bool

IsTokenValid returns true if the access token has not expired.

type AppTokenProvider added in v1.0.18

type AppTokenProvider struct {
	ConfigDir  string
	AppKey     string
	AppSecret  string
	HTTPClient *http.Client // injectable for testing; nil uses default
}

AppTokenProvider manages app-level token acquisition, caching and auto-refresh.

func (*AppTokenProvider) GetToken added in v1.0.18

func (p *AppTokenProvider) GetToken(ctx context.Context) (string, error)

GetToken returns a valid app-level access token. Tokens are cached in keychain and auto-refreshed when expired (with 5-min buffer).

type CLIAuthResult added in v1.0.11

type CLIAuthResult struct {
	CLIAuthEnabled       bool     `json:"cliAuthEnabled"`
	UserScope            string   `json:"userScope,omitempty"`            // "all" | "specified" | "forbidden"
	AllowedUsers         []string `json:"allowedUsers,omitempty"`         // staffId list when userScope="specified"
	ChannelScope         string   `json:"channelScope,omitempty"`         // "all" | "specified"
	AllowedChannels      []string `json:"allowedChannels,omitempty"`      // channelCode list when channelScope="specified"
	ChannelConfigEnabled bool     `json:"channelConfigEnabled,omitempty"` // whether org has any channel restriction configured
}

CLIAuthResult holds the business data returned by /cli/cliAuthEnabled. The server computes cliAuthEnabled by considering the org switch, userScope, and channelScope together; the CLI uses it as-is.

type CLIAuthStatus added in v1.0.5

type CLIAuthStatus struct {
	Success   bool           `json:"success"`
	ErrorCode string         `json:"errorCode,omitempty"`
	ErrorMsg  string         `json:"errorMsg,omitempty"`
	Result    *CLIAuthResult `json:"result"`
}

CLIAuthStatus represents the response from /cli/cliAuthEnabled API.

type ClientIDResponse added in v1.0.5

type ClientIDResponse struct {
	Success   bool   `json:"success"`
	ErrorCode string `json:"errorCode,omitempty"`
	ErrorMsg  string `json:"errorMsg,omitempty"`
	Result    string `json:"result"`
}

ClientIDResponse represents the response from /cli/clientId API.

type CredentialSource added in v1.0.52

type CredentialSource string

CredentialSource identifies where a particular credential field (ClientID or ClientSecret) was loaded from. It is exposed in `dws event status` and the HelloAck IPC frame so users can verify which credential channel is actually in use — important because env vars, keychain, and config file can all coexist and silently override each other (see plan §1 决策 "凭证来源拆字段").

const (
	CredentialSourceUnknown     CredentialSource = "unknown"
	CredentialSourceEnv         CredentialSource = "env"
	CredentialSourceAppConfig   CredentialSource = "app_config"   // value pulled from app config (plain or SecretRef metadata)
	CredentialSourceKeychain    CredentialSource = "keychain"     // SecretRef resolved through OS keychain
	CredentialSourcePlainConfig CredentialSource = "plain_config" // SecretInput stored as plaintext in config file (insecure but supported)
)

func ResolveAppCredentialsStrict added in v1.0.52

func ResolveAppCredentialsStrict(configDir string) (
	clientID, secret string,
	clientIDSource, secretSource CredentialSource,
	err error,
)

ResolveAppCredentialsStrict is the credentials channel used by the event subsystem (and by future commands that need fine-grained failure reporting). It distinguishes 4 failure classes and reports the source of each successfully-resolved field separately.

Resolution order:

  1. Env var override: if BOTH DWS_CLIENT_ID and DWS_CLIENT_SECRET are set non-empty, use them as a pair and skip keychain/config entirely. Single-variable configuration is detected and reported via the EnvHalfSet flag in the warning channel (callers MAY log a warning).
  2. App config from disk: - ClientID from cfg.ClientID - ClientSecret from ResolveSecret(cfg.ClientSecret): - SecretInput.IsPlain() → CredentialSourcePlainConfig - SecretRef → CredentialSourceKeychain (or whatever Ref.Source says)

Empty returns: clientID and secret may be empty when err is non-nil; callers must NOT use them in that case.

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"`
	FlowID                  string `json:"flowId"`
}

type DeviceFlowProvider

type DeviceFlowProvider struct {
	Output io.Writer

	NoBrowser        bool
	IdentityEnricher func(context.Context, *TokenData) error
	// 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)

func (*DeviceFlowProvider) SetScope added in v1.0.11

func (p *DeviceFlowProvider) SetScope(scope string)

SetScope overrides the OAuth scope for the device flow.

func (*DeviceFlowProvider) SetTerminalBaseURL added in v1.0.11

func (p *DeviceFlowProvider) SetTerminalBaseURL(baseURL string)

SetTerminalBaseURL sets the terminal API base URL for device flow polling.

type DevicePollData added in v1.0.11

type DevicePollData struct {
	Status   string `json:"status"`
	AuthCode string `json:"authCode,omitempty"`
	FlowID   string `json:"flowId,omitempty"`
}

type DevicePollResponse added in v1.0.11

type DevicePollResponse struct {
	Success bool           `json:"success"`
	Code    string         `json:"code,omitempty"`
	Message string         `json:"message,omitempty"`
	Data    DevicePollData `json:"data"`
	// Result is an alternate envelope some service versions return instead of
	// (or alongside) Data. Always read poll fields via EffectiveData() rather
	// than touching Data/Result directly.
	Result DevicePollData `json:"result"`
}

DevicePollResponse represents the response from the terminal API poll endpoint.

func (DevicePollResponse) EffectiveData added in v1.0.18

func (r DevicePollResponse) EffectiveData() DevicePollData

EffectiveData normalizes terminal poll responses that may carry payload fields under either `data` or `result`.

Semantics are envelope-level rather than field-level: when Data includes a non-empty status, treat Data as the authoritative payload and return it unchanged; otherwise fall back to Result. This avoids mixing fields from two disagreeing envelopes into a Frankenstein result.

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 HTTPStatusError added in v1.0.54

type HTTPStatusError struct {
	StatusCode int
	// contains filtered or unexported fields
}

HTTPStatusError preserves an OAuth endpoint status for structured retry decisions without copying an untrusted response body into logs.

func (*HTTPStatusError) Error added in v1.0.54

func (e *HTTPStatusError) Error() string

type Identity

type Identity struct {
	Version   int                    `json:"version,omitempty"`
	AgentID   string                 `json:"agentId"`             // v1 install UUID; == MachineID on v2 installs
	MachineID string                 `json:"machineId,omitempty"` // stable per-install machine seed
	Source    string                 `json:"source"`              // data source, default "dws"
	Agents    map[string]*AgentEntry `json:"agents,omitempty"`    // agent_code -> derived agentId
}

Identity holds the agent instance identification fields.

AgentID is retained for backward compatibility with v1 readers: on a fresh install it is written equal to MachineID, and a v1 file's agentId is migrated into MachineID on load.

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. v1 files are migrated in-memory (machineId backfilled from agentId).

func (*Identity) Headers

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

Headers returns the identity as static HTTP header key-value pairs. x-dws-agent-id carries the stable machine-level id (== v1 install UUID), kept continuous across versions. The per-(machine × agent_code) instance id is a SEPARATE header (x-dws-agent-instance-id) injected by the caller via ResolveAgentID — it does not override x-dws-agent-id.

func (*Identity) ResolveAgentID added in v1.0.38

func (id *Identity) ResolveAgentID(configDir, agentCode, signal string) string

ResolveAgentID returns the per-(machine × agentCode) agentId, deriving and persisting it on first sight of an agentCode. Idempotent: the same machine and agentCode always yields the same id, which is what makes cumulative per-agent_code statistics possible. An empty agentCode has no per-agent identity and returns empty.

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

	NoBrowser    bool
	TargetCorpID string
	// IdentityEnricher resolves userId/userName/corpName while the freshly
	// exchanged access token is still only in memory.
	IdentityEnricher func(context.Context, *TokenData) error
	// 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) CheckCLIAuthEnabled added in v1.0.5

func (p *OAuthProvider) CheckCLIAuthEnabled(ctx context.Context, accessToken string) (*CLIAuthStatus, error)

CheckCLIAuthEnabled checks if CLI authorization is enabled for the current corp. It retries up to mcpRequestMaxRetries times on transient errors to avoid false negatives caused by momentary network issues.

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) ForceRefreshRejectedToken added in v1.0.54

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

ForceRefreshRejectedToken refreshes rejectedAccessToken only while it is still the credential stored for the active profile. The compare and refresh run under the same process + file lock used by ordinary expiry refresh, so a late rejection cannot invalidate or refresh over a token another caller has already rotated.

When the stored token no longer matches, the newer token is returned without calling the refresh endpoint. Refresh failures leave the stored credential in place; login/logout remain the only owners of credential deletion.

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) GetTokenSnapshot added in v1.0.54

func (p *OAuthProvider) GetTokenSnapshot(ctx context.Context) (*TokenData, error)

GetTokenSnapshot returns a valid token together with its expiry metadata. Storage and refresh failures retain their original cause; only a confirmed missing credential is reported as ErrTokenDataNotFound.

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 PortableImportReport added in v1.0.33

type PortableImportReport struct {
	BundleOS   string
	OSMismatch bool
}

PortableImportReport summarizes bundle metadata consumed during import.

func ImportPortableAuthBundle added in v1.0.33

func ImportPortableAuthBundle(configDir string, r io.Reader) (PortableImportReport, error)

ImportPortableAuthBundle extracts a tar.gz auth bundle into the current config and keychain locations.

type Profile added in v1.0.45

type Profile struct {
	Name              string   `json:"name"`
	CorpID            string   `json:"corpId"`
	CorpName          string   `json:"corpName,omitempty"`
	UserID            string   `json:"userId,omitempty"`
	UserName          string   `json:"userName,omitempty"`
	ClientID          string   `json:"clientId,omitempty"`
	Status            string   `json:"status,omitempty"`
	AuthorizedDomains []string `json:"authorizedDomains,omitempty"`
	ExpiresAt         string   `json:"expiresAt,omitempty"`
	RefreshExpAt      string   `json:"refreshExpAt,omitempty"`
	LastLoginAt       string   `json:"lastLoginAt,omitempty"`
	LastUsedAt        string   `json:"lastUsedAt,omitempty"`
	UpdatedAt         string   `json:"updatedAt,omitempty"`
}

Profile is a logged-in DingTalk organization identity.

func RemoveProfile added in v1.0.45

func RemoveProfile(configDir, selector string) (*Profile, error)

RemoveProfile removes a profile from metadata and returns the removed profile.

func ResolveProfile added in v1.0.45

func ResolveProfile(configDir, selector string) (*Profile, error)

ResolveProfile returns a profile selected by name/corpId/identity or by current fallback.

func ResolveProfileDeletionScope added in v1.0.54

func ResolveProfileDeletionScope(configDir, selector string) (*Profile, bool, error)

ResolveProfileDeletionScope resolves selectors for destructive profile removal. Organization selectors intentionally resolve to the whole organization even when it has multiple accounts and no current account.

func ResolveProfileWithScope added in v1.0.54

func ResolveProfileWithScope(configDir, selector string) (*Profile, bool, error)

ResolveProfileWithScope resolves a selector and reports whether it targets one identity (compound selector or local profile name) rather than an organization as a whole.

func SetCurrentProfile added in v1.0.45

func SetCurrentProfile(configDir, selector string) (*Profile, error)

SetCurrentProfile persists the selected current profile.

func UsePreviousProfile added in v1.0.45

func UsePreviousProfile(configDir string) (*Profile, error)

UsePreviousProfile toggles currentProfile and previousProfile.

type ProfilesConfig added in v1.0.45

type ProfilesConfig struct {
	Version            int               `json:"version"`
	PrimaryProfile     string            `json:"primaryProfile,omitempty"`
	CurrentProfile     string            `json:"currentProfile,omitempty"`
	PreviousProfile    string            `json:"previousProfile,omitempty"`
	OrgCurrentProfiles map[string]string `json:"orgCurrentProfiles,omitempty"`
	Profiles           []Profile         `json:"profiles,omitempty"`
}

ProfilesConfig stores non-sensitive profile metadata. Token material stays in keychain.

func LoadProfiles added in v1.0.45

func LoadProfiles(configDir string) (*ProfilesConfig, error)

LoadProfiles reads profiles.json. A missing file returns an empty config.

type RefreshFailureClass added in v1.0.54

type RefreshFailureClass string

RefreshFailureClass separates refresh failures that may recover after a delay from failures that require new credentials or local intervention.

const (
	RefreshFailureUnknown   RefreshFailureClass = "unknown"
	RefreshFailureTransient RefreshFailureClass = "transient"
	RefreshFailureTerminal  RefreshFailureClass = "terminal"
)

func ClassifyRefreshFailure added in v1.0.54

func ClassifyRefreshFailure(err error) RefreshFailureClass

ClassifyRefreshFailure uses only structured transport and HTTP signals. Unknown errors, including parse, keychain and persistence failures, remain fatal so a long-running source cannot retry an error that needs user action.

type SecretInput added in v1.0.4

type SecretInput struct {
	Plain string     // non-empty for plain string values
	Ref   *SecretRef // non-nil for SecretRef values
}

SecretInput represents a secret value: either a plain string or a SecretRef object.

func PlainSecret added in v1.0.4

func PlainSecret(s string) SecretInput

PlainSecret creates a SecretInput from a plain string.

func StoreSecret added in v1.0.4

func StoreSecret(clientID string, input SecretInput) (SecretInput, error)

StoreSecret stores a plain text secret in keychain and returns a SecretRef. If the input is already a SecretRef, it is returned as-is. Returns error if keychain is unavailable.

func (SecretInput) IsPlain added in v1.0.4

func (s SecretInput) IsPlain() bool

IsPlain returns true if this is a plain text string (not a SecretRef).

func (SecretInput) IsSecretRef added in v1.0.4

func (s SecretInput) IsSecretRef() bool

IsSecretRef returns true if this is a SecretRef object.

func (SecretInput) IsZero added in v1.0.4

func (s SecretInput) IsZero() bool

IsZero returns true if the SecretInput has no value.

func (SecretInput) MarshalJSON added in v1.0.4

func (s SecretInput) MarshalJSON() ([]byte, error)

MarshalJSON serializes SecretInput: plain string → JSON string, SecretRef → JSON object.

func (*SecretInput) UnmarshalJSON added in v1.0.4

func (s *SecretInput) UnmarshalJSON(data []byte) error

UnmarshalJSON deserializes SecretInput from either a JSON string or a SecretRef object.

type SecretRef added in v1.0.4

type SecretRef struct {
	Source string `json:"source"` // "keychain" | "file"
	ID     string `json:"id"`     // keychain key or file path
}

SecretRef references a secret stored externally.

type SendApplyResponse added in v1.0.5

type SendApplyResponse struct {
	Success   bool   `json:"success"`
	ErrorCode string `json:"errorCode,omitempty"`
	ErrorMsg  string `json:"errorMsg,omitempty"`
	Result    bool   `json:"result"`
}

SendApplyResponse represents the response from /cli/sendCliAuthApply API.

func SendCliAuthApply added in v1.0.5

func SendCliAuthApply(ctx context.Context, accessToken, adminStaffID string) (*SendApplyResponse, error)

SendCliAuthApply sends a CLI auth apply request to the specified admin. It retries up to mcpRequestMaxRetries times on transient errors.

type SuperAdmin added in v1.0.5

type SuperAdmin struct {
	StaffID string `json:"staffId"`
	Name    string `json:"name"`
}

SuperAdmin represents a corp super admin.

type SuperAdminResponse added in v1.0.5

type SuperAdminResponse struct {
	Success   bool         `json:"success"`
	ErrorCode string       `json:"errorCode,omitempty"`
	ErrorMsg  string       `json:"errorMsg,omitempty"`
	Result    []SuperAdmin `json:"result"`
}

SuperAdminResponse represents the response from /cli/superAdmin API.

func GetSuperAdmins added in v1.0.5

func GetSuperAdmins(ctx context.Context, accessToken string) (*SuperAdminResponse, error)

GetSuperAdmins fetches the list of corp super admins. It retries up to mcpRequestMaxRetries times on transient errors.

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"`
	ClientID       string    `json:"client_id,omitempty"` // Associated app client ID for refresh
	UpdatedAt      string    `json:"updated_at,omitempty"`
	Source         string    `json:"source,omitempty"`
}

TokenData holds the OAuth token set persisted to disk.

func ExchangeCodeForToken added in v1.0.11

func ExchangeCodeForToken(ctx context.Context, configDir, code string) (*TokenData, error)

ExchangeCodeForToken exchanges an authorization code for token data using the currently configured client credentials. This is a convenience wrapper around OAuthProvider.exchangeCode for callers outside the auth package.

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. When an edition hook (LoadToken) is registered, it delegates entirely to the hook; otherwise it falls back to keychain with legacy .data migration.

func LoadTokenDataForProfile added in v1.0.45

func LoadTokenDataForProfile(configDir, profile string) (*TokenData, error)

LoadTokenDataForProfile reads TokenData for a profile selector without mutating currentProfile. Empty selector follows the default resolution chain.

func LoadTokenDataKeychain

func LoadTokenDataKeychain() (*TokenData, error)

LoadTokenDataKeychain loads TokenData from the platform keychain.

func LoadTokenDataKeychainForCorpID added in v1.0.45

func LoadTokenDataKeychainForCorpID(corpID string) (*TokenData, error)

LoadTokenDataKeychainForCorpID loads TokenData from a corp-scoped keychain slot.

func LoadTokenDataKeychainForIdentity added in v1.0.54

func LoadTokenDataKeychainForIdentity(corpID, userID string) (*TokenData, error)

LoadTokenDataKeychainForIdentity loads TokenData from an identity-scoped slot.

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.

type TokenMarker added in v1.0.9

type TokenMarker struct {
	UpdatedAt   string `json:"updated_at"`
	ManualToken bool   `json:"manual_token,omitempty"`
	// Revision changes on every credential publication. Runtime token caches
	// use it as a cheap cross-process invalidation signal without reading the
	// platform keychain on every request.
	Revision string `json:"revision,omitempty"`
}

TokenMarker is a lightweight file the host application reads to detect whether the CLI has a valid token without accessing the keychain.

Jump to

Keyboard shortcuts

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