mirror of
https://github.com/langgenius/dify.git
synced 2026-06-17 06:21:07 +08:00
131 lines
3.9 KiB
TypeScript
131 lines
3.9 KiB
TypeScript
import { AsyncEntry } from '@napi-rs/keyring'
|
|
import { BaseError } from '@/errors/base'
|
|
import { ErrorCode } from '@/errors/codes'
|
|
import { YamlStore } from './store'
|
|
|
|
/**
|
|
* Credential store keyed by an opaque (host, email) pair.
|
|
*/
|
|
export type TokenStore = {
|
|
read: (host: string, email: string) => Promise<string>
|
|
write: (host: string, email: string, bearer: string) => Promise<void>
|
|
remove: (host: string, email: string) => Promise<void>
|
|
}
|
|
|
|
const DOC_VERSION = 1
|
|
|
|
export type TokenDoc = {
|
|
version?: number
|
|
tokens?: Record<string, Record<string, string>>
|
|
}
|
|
|
|
export class FileTokenStore implements TokenStore {
|
|
private readonly store: YamlStore
|
|
|
|
constructor(filePath: string) {
|
|
this.store = new YamlStore(filePath)
|
|
}
|
|
|
|
async read(host: string, email: string): Promise<string> {
|
|
const doc = await this.store.getTyped<TokenDoc>()
|
|
if (doc === null)
|
|
return ''
|
|
// missing version = legacy pre-v1 format (same data shape); future unknown versions are rejected
|
|
if (doc.version !== undefined && doc.version !== DOC_VERSION)
|
|
return ''
|
|
return doc.tokens?.[host]?.[email] ?? ''
|
|
}
|
|
|
|
async write(host: string, email: string, bearer: string): Promise<void> {
|
|
const doc = await this.load()
|
|
const hostMap = doc.tokens[host] ?? {}
|
|
hostMap[email] = bearer
|
|
doc.tokens[host] = hostMap
|
|
await this.store.setTyped(doc)
|
|
}
|
|
|
|
async remove(host: string, email: string): Promise<void> {
|
|
const doc = await this.store.getTyped<TokenDoc>()
|
|
if (doc === null)
|
|
return
|
|
if (doc.version !== undefined && doc.version !== DOC_VERSION)
|
|
return
|
|
const tokens = doc.tokens ?? {}
|
|
const hostMap = tokens[host]
|
|
if (hostMap === undefined || !(email in hostMap))
|
|
return
|
|
delete hostMap[email]
|
|
if (Object.keys(hostMap).length === 0)
|
|
delete tokens[host]
|
|
await this.store.setTyped({ version: DOC_VERSION, tokens })
|
|
}
|
|
|
|
private async load(): Promise<{ version: number, tokens: Record<string, Record<string, string>> }> {
|
|
const doc = await this.store.getTyped<TokenDoc>()
|
|
if (doc === null)
|
|
return { version: DOC_VERSION, tokens: {} }
|
|
if (doc.version !== undefined && doc.version !== DOC_VERSION)
|
|
return { version: DOC_VERSION, tokens: {} }
|
|
return { version: DOC_VERSION, tokens: (doc.tokens ?? {}) as Record<string, Record<string, string>> }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* One OS-keyring entry per (host, email).
|
|
*/
|
|
export class KeychainTokenStore implements TokenStore {
|
|
private readonly service: string
|
|
|
|
constructor(service: string) {
|
|
this.service = service
|
|
}
|
|
|
|
async read(host: string, email: string): Promise<string> {
|
|
let raw: string | null | undefined
|
|
try {
|
|
raw = await new AsyncEntry(this.service, entryName(host, email)).getPassword()
|
|
}
|
|
catch (err) {
|
|
throw keyringUnavailableError(err)
|
|
}
|
|
if (raw === null || raw === undefined || raw === '')
|
|
return ''
|
|
try {
|
|
const parsed: unknown = JSON.parse(raw)
|
|
return typeof parsed === 'string' ? parsed : ''
|
|
}
|
|
catch {
|
|
return ''
|
|
}
|
|
}
|
|
|
|
async write(host: string, email: string, bearer: string): Promise<void> {
|
|
try {
|
|
await new AsyncEntry(this.service, entryName(host, email)).setPassword(JSON.stringify(bearer))
|
|
}
|
|
catch (err) {
|
|
throw keyringUnavailableError(err)
|
|
}
|
|
}
|
|
|
|
async remove(host: string, email: string): Promise<void> {
|
|
try {
|
|
await new AsyncEntry(this.service, entryName(host, email)).deletePassword()
|
|
}
|
|
catch { /* missing entry is fine */ }
|
|
}
|
|
}
|
|
|
|
function entryName(host: string, email: string): string {
|
|
return `tokens.${host}.${email}`
|
|
}
|
|
|
|
function keyringUnavailableError(cause: unknown): BaseError {
|
|
return new BaseError({
|
|
code: ErrorCode.KeyringUnavailable,
|
|
message: 'OS keychain is unreachable',
|
|
hint: 'credentials are stored in the system keychain but it could not be accessed; unlock the keychain (or the login session) and retry',
|
|
cause,
|
|
})
|
|
}
|