第十五章:凭证与授权 本章目标 :帮助你理解 dsh 的凭证接缝(Credential Seam)、环境变量 → .env → Settings 的优先级链、OAuth/授权流的人类交互、以及 API Key 的安全存储与轮换。阅读本章后,你应该能回答"凭证是如何被管理和使用的"以及"如何添加新的凭证类型"。
15.1 凭证架构 15.1.1 凭证接缝 15.1.2 凭证类型
type CredentialType =
| 'api-key'
| 'oauth-token'
| 'certificate'
| 'password'
| 'ssh-key'
15.2 凭证服务 15.2.1 CredentialsService
class CredentialsService {
async get ( name: string ) : Promise < Credential | null > {
for ( const provider of this . providers) {
const credential = await provider. get ( name)
if ( credential) return credential
}
return null
}
async set ( name: string , credential: Credential) : Promise < void > {
await this . defaultProvider. set ( name, credential)
}
async delete ( name: string ) : Promise < void > {
await this . defaultProvider. delete ( name)
}
async list ( ) : Promise < CredentialEntry[ ] > {
const entries: CredentialEntry[ ] = [ ]
for ( const provider of this . providers) {
const providerEntries = await provider. list ( )
entries. push ( ... providerEntries)
}
return entries
}
}
15.2.2 Credential
interface Credential {
name: string
type: CredentialType
value: string
metadata? : Record< string , unknown >
expiresAt? : Date
}
15.3 凭证提供者 15.3.1 环境变量提供者
class EnvCredentialProvider implements CredentialProvider {
async get ( name: string ) : Promise < Credential | null > {
const envName = name. replace ( / - / g , '_' ) . toUpperCase ( )
const value = process. env[ envName]
if ( ! value) return null
return {
name,
type: 'api-key' ,
value
}
}
async list ( ) : Promise < CredentialEntry[ ] > {
return Object. entries ( process. env)
. filter ( ( [ key] ) => key. startsWith ( 'DSH_' ) )
. map ( ( [ key, value] ) => ( {
name: key. replace ( / ^DSH_ / , '' ) . toLowerCase ( ) . replace ( / _ / g , '-' ) ,
type: 'api-key' ,
provider: 'env'
} ) )
}
}
15.3.2 .env 文件提供者
class DotEnvCredentialProvider implements CredentialProvider {
constructor ( private envPath: string ) { }
async get ( name: string ) : Promise < Credential | null > {
const env = await this . loadEnvFile ( )
const value = env[ name]
if ( ! value) return null
return {
name,
type: 'api-key' ,
value
}
}
private async loadEnvFile ( ) : Promise < Record< string , string >> {
const content = await readFile ( this . envPath, 'utf-8' )
const env: Record< string , string > = { }
for ( const line of content. split ( '\n' ) ) {
const trimmed = line. trim ( )
if ( ! trimmed || trimmed. startsWith ( '#' ) ) continue
const [ key, ... valueParts] = trimmed. split ( '=' )
env[ key. trim ( ) ] = valueParts. join ( '=' ) . trim ( )
}
return env
}
}
15.3.3 Settings 提供者
class SettingsCredentialProvider implements CredentialProvider {
constructor ( private ctx: Context) { }
async get ( name: string ) : Promise < Credential | null > {
const settings = await this . ctx. settings. get ( 'credentials' )
const value = settings?. [ name]
if ( ! value) return null
return {
name,
type: 'api-key' ,
value
}
}
async set ( name: string , credential: Credential) : Promise < void > {
const settings = await this . ctx. settings. get ( 'credentials' ) ?? { }
settings[ name] = credential. value
await this . ctx. settings. set ( 'credentials' , settings)
}
}
15.4 凭证解析优先级 15.4.1 优先级链 15.4.2 实现
class CredentialResolver {
async resolve ( name: string , options? : ResolveOptions) : Promise < Credential | null > {
let credential = await this . envProvider. get ( name)
if ( credential) return credential
credential = await this . dotEnvProvider. get ( name)
if ( credential) return credential
credential = await this . settingsProvider. get ( name)
if ( credential) return credential
if ( options?. askUser) {
const value = await this . askUserForCredential ( name)
if ( value) {
credential = { name, type: 'api-key' , value }
await this . settingsProvider. set ( name, credential)
return credential
}
}
return null
}
}
15.5 OAuth 授权流 15.5.1 概念对于需要 OAuth 的服务,dsh 支持人类交互式的授权流。
15.5.2 AuthorizationService
class AuthorizationService {
registerFlow ( flow: AuthorizationFlow) : Disposable {
this . flows. set ( flow. provider, flow)
return ( ) => {
this . flows. delete ( flow. provider)
}
}
async requestAuthorization ( provider: string ) : Promise < Credential> {
const flow = this . flows. get ( provider)
if ( ! flow) throw new Error ( ` No authorization flow for provider: ${ provider} ` )
const authUrl = await flow. generateAuthorizationUrl ( )
await this . showAuthorizationUrl ( authUrl)
const code = await this . waitForCallback ( flow. callbackUrl)
const token = await flow. exchangeCode ( code)
return token
}
}
15.6 API Key 安全 15.6.1 安全存储
class SecureCredentialStorage {
async store ( credential: Credential) : Promise < void > {
const encrypted = await this . encrypt ( credential. value)
await this . secureStore. set ( credential. name, {
... credential,
value: encrypted
} )
}
async retrieve ( name: string ) : Promise < Credential | null > {
const stored = await this . secureStore. get ( name)
if ( ! stored) return null
const decrypted = await this . decrypt ( stored. value)
return {
... stored,
value: decrypted
}
}
}
15.6.2 密钥轮换
class CredentialRotation {
async needsRotation ( credential: Credential) : Promise < boolean > {
if ( ! credential. expiresAt) return false
const daysUntilExpiry = ( credential. expiresAt. getTime ( ) - Date. now ( ) ) / ( 1000 * 60 * 60 * 24 )
return daysUntilExpiry < 7
}
async rotate ( name: string ) : Promise < Credential> {
const current = await this . credentials. get ( name)
if ( ! current) throw new Error ( ` Credential not found: ${ name} ` )
const newCredential = await this . generateNewCredential ( current)
await this . credentials. set ( name, newCredential)
return newCredential
}
}
15.7 配置 15.7.1 凭证配置
- id : credentials
config :
providers :
- 'env'
- 'dot-env'
- 'settings'
dotEnvPath : '${DSH_HOME}/.env'
secureStorage :
enabled : true
type : 'keychain'
15.8 小结
概念 一句话解释 Credential Seam 凭证管理的能力接缝 Env Provider 环境变量凭证提供者 DotEnv Provider .env 文件凭证提供者 Settings Provider 用户设置凭证提供者 Authorization Flow OAuth 授权流 密钥轮换 自动轮换即将过期的凭证
下一步 :第十六章:Webhook 与外部事件 ——深入理解验证外部事件的签名机制和可信规则。