Files
Hermes-ui/packages/server/src/services/credentials.ts
T

60 lines
1.8 KiB
TypeScript

import { readFile, writeFile, mkdir, unlink } from 'fs/promises'
import { existsSync } from 'fs'
import { join } from 'path'
import { scryptSync, randomBytes } from 'node:crypto'
import { config } from '../config'
const APP_HOME = config.appHome
const CREDENTIALS_FILE = join(APP_HOME, '.credentials')
export interface Credentials {
username: string
password_hash: string
salt: string
created_at: number
}
const SCRYPT_OPTIONS = { N: 16384, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }
function hashPassword(password: string, salt: string): string {
return scryptSync(password, salt, 64, SCRYPT_OPTIONS).toString('hex')
}
export async function getCredentials(): Promise<Credentials | null> {
try {
const data = await readFile(CREDENTIALS_FILE, 'utf-8')
return JSON.parse(data)
} catch {
return null
}
}
export async function setCredentials(username: string, password: string): Promise<Credentials> {
const salt = randomBytes(16).toString('hex')
const password_hash = hashPassword(password, salt)
const cred: Credentials = { username, password_hash, salt, created_at: Date.now() }
await mkdir(APP_HOME, { recursive: true })
await writeFile(CREDENTIALS_FILE, JSON.stringify(cred, null, 2), { mode: 0o600 })
return cred
}
export async function deleteCredentials(): Promise<void> {
try {
await unlink(CREDENTIALS_FILE)
} catch {
// File may not exist
}
}
export async function verifyCredentials(username: string, password: string): Promise<boolean> {
const cred = await getCredentials()
if (!cred) return false
if (cred.username !== username) return false
const computed = hashPassword(password, cred.salt)
return computed === cred.password_hash
}
export function credentialsFileExists(): boolean {
return existsSync(CREDENTIALS_FILE)
}