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
- Variables
- func AgentCodeEnvPresent() bool
- func AgentCodeFromEnv() (string, string)
- func ClientID() string
- func ClientSecret() string
- func DeleteAllTokenData(configDir string) error
- func DeleteAppConfig(configDir string) error
- func DeleteAppTokenData(clientID string) error
- func DeleteClientSecret(clientID string) error
- func DeleteSecureData(configDir string) error
- func DeleteTokenData(configDir string) error
- func DeleteTokenDataForProfile(configDir, profile string) error
- func DeleteTokenDataKeychain() error
- func DeleteTokenDataKeychainForCorpID(corpID string) error
- func DeleteTokenDataKeychainForIdentity(corpID, userID string) error
- func DeleteTokenMarker(configDir string) error
- func DetectAgentCode() (code string, signal string)
- func EnsureMigration(configDir string, logger *slog.Logger)
- func EnsureProfilesMigration(configDir string) error
- func EnvHalfSet() bool
- func ExportPortableAuthBundle(configDir string, w io.Writer) error
- func FetchAppToken(ctx context.Context, appKey, appSecret string) (token string, expiresIn int64, err error)
- func FetchClientIDFromMCP(ctx context.Context) (string, error)
- func GetAppConfigPath(configDir string) string
- func GetDeveloperSettingsURL() string
- func GetMCPBaseURL() string
- func GetRefreshTokenURL() string
- func GetRevokeTokenURL() string
- func GetTerminalBaseURL() string
- func GetUserAccessTokenURL() string
- func HasAppConfig(configDir string) bool
- func HasValidClientSecret() bool
- func HostOwnsPATFlow() bool
- func IsClientIDFromMCP() bool
- func IsMigrationDone() bool
- func LoadClientSecret(clientID string) string
- func MarkAccessTokenStale(configDir string) error
- func MarkProfileStatus(configDir, selector, status string) error
- func MigrateKeychainToFileDEK(configDir string, dryRun bool) (int, error)
- func ParseDeviceFlowStatus(rawStatus string, success bool) string
- func ParseIdentitySelector(selector string) (corpID, userID string, ok bool)
- func PortableAuthSourceReady() bool
- func PortableAuthTargetPopulated(configDir string) bool
- func PortableExportSupportError() error
- func PortableExportSupported() bool
- func PortableImportSupportError() error
- func ProfileSelector(profile Profile) string
- func ProfilesPath(configDir string) string
- func ReadTokenMarkerRevision(configDir string) (revision string, present bool, err error)
- func RemoveSecretStore(input SecretInput)
- func ResolveAppCredentials(configDir string) (clientID, clientSecret string)
- func ResolveSecret(input SecretInput) (string, error)
- func RevokeTokenRemote(ctx context.Context) error
- func RevokeTokenRemoteForData(ctx context.Context, tokenData *TokenData) error
- func RuntimeProfile() string
- func SaveAppConfig(configDir string, config *AppConfig) error
- func SaveAppTokenData(data *AppTokenData) error
- func SaveClientSecret(clientID, clientSecret string) error
- func SaveProfiles(configDir string, cfg *ProfilesConfig) error
- func SaveSecureTokenData(configDir string, data *TokenData) error
- func SaveTokenData(configDir string, data *TokenData) error
- func SaveTokenDataKeychain(data *TokenData) error
- func SaveTokenDataKeychainForCorpID(corpID string, data *TokenData) error
- func SaveTokenDataKeychainForIdentity(corpID, userID string, data *TokenData) error
- func SecureDataExists(configDir string) bool
- func SetClientID(id string)
- func SetClientIDFromMCP(id string)
- func SetClientSecret(secret string)
- func SetRuntimeProfile(profile string)
- func SyncLegacyTokenMirror(configDir string) error
- func TokenAccountForCorpID(corpID string) string
- func TokenAccountForIdentity(corpID, userID string) string
- func TokenDataExistsKeychain() bool
- func TokenDataExistsKeychainForCorpID(corpID string) bool
- func TokenDataExistsKeychainForIdentity(corpID, userID string) bool
- func TokenProfileSelector(data *TokenData) string
- func UpsertProfileFromToken(configDir string, data *TokenData) error
- func UpsertProfileFromTokenWithCurrent(configDir string, data *TokenData, makeCurrent bool) error
- func WriteManualTokenMarker(configDir string) error
- func WriteTokenMarker(configDir string) error
- type AgentEntry
- type AppConfig
- type AppTokenData
- type AppTokenProvider
- type CLIAuthResult
- type CLIAuthStatus
- type ClientIDResponse
- type CredentialSource
- type DeviceAuthResponse
- type DeviceFlowProvider
- type DevicePollData
- type DevicePollResponse
- type DeviceTokenResponse
- type DualLock
- type HTTPStatusError
- type Identity
- type Manager
- func (m *Manager) DeleteToken() error
- func (m *Manager) GetMCPURL() (string, error)
- func (m *Manager) GetToken() (string, string, error)
- func (m *Manager) IsAuthenticated() bool
- func (m *Manager) SaveMCPURL(url string) error
- func (m *Manager) SaveToken(token string) error
- func (m *Manager) Status() (authenticated bool, source string, maskedToken string)
- type OAuthProvider
- func (p *OAuthProvider) CheckCLIAuthEnabled(ctx context.Context, accessToken string) (*CLIAuthStatus, error)
- func (p *OAuthProvider) ExchangeAuthCode(ctx context.Context, authCode, uid string) (*TokenData, error)
- func (p *OAuthProvider) ForceRefreshRejectedToken(ctx context.Context, rejectedAccessToken string) (string, error)
- func (p *OAuthProvider) GetAccessToken(ctx context.Context) (string, error)
- func (p *OAuthProvider) GetTokenSnapshot(ctx context.Context) (*TokenData, error)
- func (p *OAuthProvider) Login(ctx context.Context, force bool) (*TokenData, error)
- func (p *OAuthProvider) Logout() error
- func (p *OAuthProvider) Status() (*TokenData, error)
- type PortableImportReport
- type Profile
- func RemoveProfile(configDir, selector string) (*Profile, error)
- func ResolveProfile(configDir, selector string) (*Profile, error)
- func ResolveProfileDeletionScope(configDir, selector string) (*Profile, bool, error)
- func ResolveProfileWithScope(configDir, selector string) (*Profile, bool, error)
- func SetCurrentProfile(configDir, selector string) (*Profile, error)
- func UsePreviousProfile(configDir string) (*Profile, error)
- type ProfilesConfig
- type RefreshFailureClass
- type SecretInput
- type SecretRef
- type SendApplyResponse
- type SuperAdmin
- type SuperAdminResponse
- type TokenData
- func ExchangeCodeForToken(ctx context.Context, configDir, code string) (*TokenData, error)
- func LoadSecureTokenData(configDir string) (*TokenData, error)
- func LoadTokenData(configDir string) (*TokenData, error)
- func LoadTokenDataForProfile(configDir, profile string) (*TokenData, error)
- func LoadTokenDataKeychain() (*TokenData, error)
- func LoadTokenDataKeychainForCorpID(corpID string) (*TokenData, error)
- func LoadTokenDataKeychainForIdentity(corpID, userID string) (*TokenData, error)
- type TokenMarker
Constants ¶
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.
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" )
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.
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" )
const ( ProfileStatusActive = "active" ProfileStatusExpired = "expired" ProfileStatusRevoked = "revoked" )
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 ¶
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).
var ErrTokenDataNotFound = errors.New("token data not found")
ErrTokenDataNotFound means the requested keychain slot does not exist.
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.
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
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
DeleteAllTokenData removes all profile-scoped and legacy token data.
func DeleteAppConfig ¶ added in v1.0.4
DeleteAppConfig removes the app configuration and associated keychain secrets.
func DeleteAppTokenData ¶ added in v1.0.18
DeleteAppTokenData removes AppTokenData from keychain for the given clientID.
func DeleteClientSecret ¶ added in v1.0.6
DeleteClientSecret removes the stored client secret for a specific client ID.
func DeleteSecureData ¶
DeleteSecureData removes .data file from configDir.
func DeleteTokenData ¶
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
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
DeleteTokenDataKeychainForCorpID removes TokenData from a corp-scoped keychain slot.
func DeleteTokenDataKeychainForIdentity ¶ added in v1.0.54
DeleteTokenDataKeychainForIdentity removes one identity-scoped token.
func DeleteTokenMarker ¶ added in v1.0.9
DeleteTokenMarker removes the token.json marker file.
func DetectAgentCode ¶ added in v1.0.38
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 ¶
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
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
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
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
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
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
LoadClientSecret retrieves the stored client secret for a specific client ID. Returns empty string if not found.
func MarkAccessTokenStale ¶ added in v1.0.16
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
MarkProfileStatus updates a selected profile status if it exists.
func MigrateKeychainToFileDEK ¶ added in v1.0.52
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
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
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
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
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
ProfilesPath returns the profile metadata path for a config dir.
func ReadTokenMarkerRevision ¶ added in v1.0.54
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
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 ¶
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
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
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
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 ¶
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 ¶
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 ¶
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
SaveTokenDataKeychainForCorpID saves TokenData to a corp-scoped keychain slot.
func SaveTokenDataKeychainForIdentity ¶ added in v1.0.54
SaveTokenDataKeychainForIdentity saves TokenData to an identity-scoped slot.
func SecureDataExists ¶
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
SyncLegacyTokenMirror mirrors the current profile token into legacy auth-token.
func TokenAccountForCorpID ¶ added in v1.0.45
TokenAccountForCorpID returns the keychain account used for a corp-bound token.
func TokenAccountForIdentity ¶ added in v1.0.54
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
TokenDataExistsKeychainForCorpID checks if a corp-scoped token exists.
func TokenDataExistsKeychainForIdentity ¶ added in v1.0.54
TokenDataExistsKeychainForIdentity checks if an identity-scoped token exists.
func TokenProfileSelector ¶ added in v1.0.54
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
UpsertProfileFromToken updates profiles.json after a successful login or refresh.
func UpsertProfileFromTokenWithCurrent ¶ added in v1.0.45
UpsertProfileFromTokenWithCurrent updates profiles.json and optionally makes the token's corp the persistent current profile.
func WriteManualTokenMarker ¶ added in v1.0.54
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
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
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
LoadAppConfig loads the app configuration from disk. Returns nil, nil if the config file does not exist.
func ReloadAppConfig ¶ added in v1.0.4
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.
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:
- 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).
- 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 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 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 ¶
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.
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 ¶
EnsureExists loads existing identity or creates a new one if not present.
func Load ¶
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 ¶
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
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 (*Manager) DeleteToken ¶
func (*Manager) IsAuthenticated ¶
func (*Manager) SaveMCPURL ¶
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 ¶
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
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
RemoveProfile removes a profile from metadata and returns the removed profile.
func ResolveProfile ¶ added in v1.0.45
ResolveProfile returns a profile selected by name/corpId/identity or by current fallback.
func ResolveProfileDeletionScope ¶ added in v1.0.54
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
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
SetCurrentProfile persists the selected current profile.
func UsePreviousProfile ¶ added in v1.0.45
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
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
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 ¶
LoadSecureTokenData decrypts and loads TokenData from .data file. Reads are safe without locking because SaveSecureTokenData uses atomic rename.
func LoadTokenData ¶
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
LoadTokenDataForProfile reads TokenData for a profile selector without mutating currentProfile. Empty selector follows the default resolution chain.
func LoadTokenDataKeychain ¶
LoadTokenDataKeychain loads TokenData from the platform keychain.
func LoadTokenDataKeychainForCorpID ¶ added in v1.0.45
LoadTokenDataKeychainForCorpID loads TokenData from a corp-scoped keychain slot.
func LoadTokenDataKeychainForIdentity ¶ added in v1.0.54
LoadTokenDataKeychainForIdentity loads TokenData from an identity-scoped slot.
func (*TokenData) HasPersistentCode ¶
HasPersistentCode returns true if a persistent code is available.
func (*TokenData) IsAccessTokenValid ¶
IsAccessTokenValid returns true if the access token has not expired.
func (*TokenData) IsRefreshTokenValid ¶
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.
Source Files
¶
- agent_code_detect.go
- app_config.go
- app_config_strict.go
- app_token.go
- browser.go
- browser_unix.go
- channel.go
- device_flow.go
- device_flow_status.go
- edition_headers.go
- endpoints.go
- filelock.go
- filelock_unix.go
- force_refresh.go
- identity.go
- keychain_migration.go
- keychain_store.go
- manager.go
- oauth_helpers.go
- oauth_provider.go
- portable_store.go
- profiles.go
- refresh_failure.go
- secret.go
- secure_store.go
- token.go