mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-31 06:12:26 +00:00
136 lines
4.3 KiB
TypeScript
136 lines
4.3 KiB
TypeScript
import path from "path"
|
|
import fs from "fs/promises"
|
|
import z from "zod"
|
|
import { Global } from "../global"
|
|
|
|
export namespace McpAuth {
|
|
export const Tokens = z.object({
|
|
accessToken: z.string(),
|
|
refreshToken: z.string().optional(),
|
|
expiresAt: z.number().optional(),
|
|
scope: z.string().optional(),
|
|
})
|
|
export type Tokens = z.infer<typeof Tokens>
|
|
|
|
export const ClientInfo = z.object({
|
|
clientId: z.string(),
|
|
clientSecret: z.string().optional(),
|
|
clientIdIssuedAt: z.number().optional(),
|
|
clientSecretExpiresAt: z.number().optional(),
|
|
})
|
|
export type ClientInfo = z.infer<typeof ClientInfo>
|
|
|
|
export const Entry = z.object({
|
|
tokens: Tokens.optional(),
|
|
clientInfo: ClientInfo.optional(),
|
|
codeVerifier: z.string().optional(),
|
|
oauthState: z.string().optional(),
|
|
serverUrl: z.string().optional(), // Track the URL these credentials are for
|
|
})
|
|
export type Entry = z.infer<typeof Entry>
|
|
|
|
const filepath = path.join(Global.Path.data, "mcp-auth.json")
|
|
|
|
export async function get(mcpName: string): Promise<Entry | undefined> {
|
|
const data = await all()
|
|
return data[mcpName]
|
|
}
|
|
|
|
/**
|
|
* Get auth entry and validate it's for the correct URL.
|
|
* Returns undefined if URL has changed (credentials are invalid).
|
|
*/
|
|
export async function getForUrl(mcpName: string, serverUrl: string): Promise<Entry | undefined> {
|
|
const entry = await get(mcpName)
|
|
if (!entry) return undefined
|
|
|
|
// If no serverUrl is stored, this is from an old version - consider it invalid
|
|
if (!entry.serverUrl) return undefined
|
|
|
|
// If URL has changed, credentials are invalid
|
|
if (entry.serverUrl !== serverUrl) return undefined
|
|
|
|
return entry
|
|
}
|
|
|
|
export async function all(): Promise<Record<string, Entry>> {
|
|
const file = Bun.file(filepath)
|
|
return file.json().catch(() => ({}))
|
|
}
|
|
|
|
export async function set(mcpName: string, entry: Entry, serverUrl?: string): Promise<void> {
|
|
const file = Bun.file(filepath)
|
|
const data = await all()
|
|
// Always update serverUrl if provided
|
|
if (serverUrl) {
|
|
entry.serverUrl = serverUrl
|
|
}
|
|
await Bun.write(file, JSON.stringify({ ...data, [mcpName]: entry }, null, 2))
|
|
await fs.chmod(file.name!, 0o600)
|
|
}
|
|
|
|
export async function remove(mcpName: string): Promise<void> {
|
|
const file = Bun.file(filepath)
|
|
const data = await all()
|
|
delete data[mcpName]
|
|
await Bun.write(file, JSON.stringify(data, null, 2))
|
|
await fs.chmod(file.name!, 0o600)
|
|
}
|
|
|
|
export async function updateTokens(mcpName: string, tokens: Tokens, serverUrl?: string): Promise<void> {
|
|
const entry = (await get(mcpName)) ?? {}
|
|
entry.tokens = tokens
|
|
await set(mcpName, entry, serverUrl)
|
|
}
|
|
|
|
export async function updateClientInfo(mcpName: string, clientInfo: ClientInfo, serverUrl?: string): Promise<void> {
|
|
const entry = (await get(mcpName)) ?? {}
|
|
entry.clientInfo = clientInfo
|
|
await set(mcpName, entry, serverUrl)
|
|
}
|
|
|
|
export async function updateCodeVerifier(mcpName: string, codeVerifier: string): Promise<void> {
|
|
const entry = (await get(mcpName)) ?? {}
|
|
entry.codeVerifier = codeVerifier
|
|
await set(mcpName, entry)
|
|
}
|
|
|
|
export async function clearCodeVerifier(mcpName: string): Promise<void> {
|
|
const entry = await get(mcpName)
|
|
if (entry) {
|
|
delete entry.codeVerifier
|
|
await set(mcpName, entry)
|
|
}
|
|
}
|
|
|
|
export async function updateOAuthState(mcpName: string, oauthState: string): Promise<void> {
|
|
const entry = (await get(mcpName)) ?? {}
|
|
entry.oauthState = oauthState
|
|
await set(mcpName, entry)
|
|
}
|
|
|
|
export async function getOAuthState(mcpName: string): Promise<string | undefined> {
|
|
const entry = await get(mcpName)
|
|
return entry?.oauthState
|
|
}
|
|
|
|
export async function clearOAuthState(mcpName: string): Promise<void> {
|
|
const entry = await get(mcpName)
|
|
if (entry) {
|
|
delete entry.oauthState
|
|
await set(mcpName, entry)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if stored tokens are expired.
|
|
* Returns null if no tokens exist, false if no expiry or not expired, true if expired.
|
|
*/
|
|
export async function isTokenExpired(mcpName: string): Promise<boolean | null> {
|
|
const entry = await get(mcpName)
|
|
if (!entry?.tokens) return null
|
|
if (!entry.tokens.expiresAt) return false
|
|
return entry.tokens.expiresAt < Date.now() / 1000
|
|
}
|
|
}
|