mirror of
https://github.com/stablyai/orca.git
synced 2026-10-24 16:03:45 +00:00
146 lines
4.1 KiB
TypeScript
146 lines
4.1 KiB
TypeScript
import { getSecretStore } from '../../shared/secret-store'
|
|
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
import {
|
|
LEGACY_WORKSPACE_ID,
|
|
ensureOrcaDir,
|
|
ensureWorkspaceTokenDir,
|
|
getWorkspaceTokenPath
|
|
} from './linear-credential-paths'
|
|
import {
|
|
clearLegacyViewerOnDisk,
|
|
forgetLegacyViewer,
|
|
resetLegacyViewerCache
|
|
} from './linear-legacy-viewer-store'
|
|
import { emptyWorkspaceFile } from './linear-workspace-record'
|
|
import {
|
|
cacheToken,
|
|
clearCredentialError,
|
|
forgetCachedToken,
|
|
getCachedToken,
|
|
getWorkspaceFile,
|
|
getWorkspaceState,
|
|
recordCredentialError,
|
|
resetCredentialCaches,
|
|
resetWorkspaceFileCacheToEmpty,
|
|
resolveWorkspaceId,
|
|
upsertWorkspace,
|
|
writeWorkspaceFile
|
|
} from './linear-workspace-registry'
|
|
import {
|
|
CredentialDecryptionError,
|
|
readStoredCredentialToken
|
|
} from '../integration-credential-file'
|
|
import type { LinearWorkspace } from '../../shared/linear/workspace-types'
|
|
|
|
function writeEncryptedToken(path: string, apiKey: string): void {
|
|
if (getSecretStore().isEncryptionAvailable()) {
|
|
const encrypted = getSecretStore().encryptString(apiKey)
|
|
writeFileSync(path, encrypted, { mode: 0o600 })
|
|
return
|
|
}
|
|
|
|
console.warn('[linear] secret encryption unavailable — storing token in plaintext')
|
|
writeFileSync(path, apiKey, { encoding: 'utf-8', mode: 0o600 })
|
|
}
|
|
|
|
export function saveWorkspaceToken(workspaceId: string, apiKey: string): void {
|
|
ensureOrcaDir()
|
|
if (workspaceId !== LEGACY_WORKSPACE_ID) {
|
|
ensureWorkspaceTokenDir()
|
|
}
|
|
const tokenPath = getWorkspaceTokenPath(workspaceId)
|
|
writeEncryptedToken(tokenPath, apiKey)
|
|
cacheToken(workspaceId, apiKey)
|
|
clearCredentialError(workspaceId)
|
|
}
|
|
|
|
// Backward-compatible export for the legacy single-workspace storage path.
|
|
export function saveToken(apiKey: string): void {
|
|
saveWorkspaceToken(LEGACY_WORKSPACE_ID, apiKey)
|
|
}
|
|
|
|
export function loadToken(options: { force?: boolean; workspaceId?: string } = {}): string | null {
|
|
const workspaceId = options.workspaceId ?? resolveWorkspaceId()
|
|
if (!workspaceId) {
|
|
return null
|
|
}
|
|
const cached = getCachedToken(workspaceId)
|
|
if (cached !== undefined) {
|
|
return cached
|
|
}
|
|
if (!options.force) {
|
|
return null
|
|
}
|
|
const tokenPath = getWorkspaceTokenPath(workspaceId)
|
|
if (!existsSync(tokenPath)) {
|
|
return null
|
|
}
|
|
try {
|
|
const raw = readFileSync(tokenPath)
|
|
const token = readStoredCredentialToken('Linear', raw)
|
|
if (token) {
|
|
cacheToken(workspaceId, token)
|
|
}
|
|
clearCredentialError(workspaceId)
|
|
return token
|
|
} catch (error) {
|
|
if (error instanceof CredentialDecryptionError) {
|
|
recordCredentialError(workspaceId, error.message)
|
|
throw error
|
|
}
|
|
return null
|
|
}
|
|
}
|
|
|
|
export function clearTokenFile(workspaceId: string): void {
|
|
forgetCachedToken(workspaceId)
|
|
try {
|
|
unlinkSync(getWorkspaceTokenPath(workspaceId))
|
|
} catch {
|
|
// File may not exist — safe to ignore.
|
|
}
|
|
}
|
|
|
|
export function clearToken(workspaceId?: string): void {
|
|
if (!workspaceId) {
|
|
const state = getWorkspaceState()
|
|
for (const workspace of state.workspaces) {
|
|
clearTokenFile(workspace.id)
|
|
}
|
|
resetCredentialCaches()
|
|
resetLegacyViewerCache()
|
|
resetWorkspaceFileCacheToEmpty()
|
|
clearLegacyViewerOnDisk()
|
|
writeWorkspaceFile(emptyWorkspaceFile())
|
|
return
|
|
}
|
|
|
|
clearTokenFile(workspaceId)
|
|
if (workspaceId === LEGACY_WORKSPACE_ID) {
|
|
resetLegacyViewerCache()
|
|
clearLegacyViewerOnDisk()
|
|
return
|
|
}
|
|
|
|
const file = getWorkspaceFile()
|
|
const workspaces = file.workspaces.filter((workspace) => workspace.id !== workspaceId)
|
|
const activeWorkspaceId =
|
|
file.activeWorkspaceId === workspaceId ? (workspaces[0]?.id ?? null) : file.activeWorkspaceId
|
|
const selectedWorkspaceId =
|
|
file.selectedWorkspaceId === workspaceId ? activeWorkspaceId : file.selectedWorkspaceId
|
|
writeWorkspaceFile({
|
|
version: 1,
|
|
activeWorkspaceId,
|
|
selectedWorkspaceId,
|
|
workspaces
|
|
})
|
|
}
|
|
|
|
export function replaceLegacyWorkspace(workspace: LinearWorkspace, token: string): void {
|
|
saveWorkspaceToken(workspace.id, token)
|
|
clearTokenFile(LEGACY_WORKSPACE_ID)
|
|
clearLegacyViewerOnDisk()
|
|
forgetLegacyViewer()
|
|
upsertWorkspace(workspace, { select: true })
|
|
}
|