feat(desktop): remember login credentials
Desktop Client Build / Resolve Build Metadata (push) Successful in 49s
Frontend CI / Frontend (push) Successful in 5m47s
Desktop Client Build / Build Desktop Client (push) Successful in 42m28s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 2m38s
Desktop Client Build / Resolve Build Metadata (push) Successful in 49s
Frontend CI / Frontend (push) Successful in 5m47s
Desktop Client Build / Build Desktop Client (push) Successful in 42m28s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 2m38s
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { app, safeStorage } from 'electron/main'
|
||||
|
||||
export interface DesktopLoginCredentials {
|
||||
identifier: string
|
||||
password: string
|
||||
}
|
||||
|
||||
interface PersistedLoginCredentials {
|
||||
version?: number
|
||||
identifier?: unknown
|
||||
encryptedPassword?: unknown
|
||||
}
|
||||
|
||||
function credentialsPath(): string {
|
||||
return join(app.getPath('userData'), 'login-credentials.json')
|
||||
}
|
||||
|
||||
function normalizeCredentials(input: unknown): PersistedLoginCredentials {
|
||||
if (!input || typeof input !== 'object') {
|
||||
return {}
|
||||
}
|
||||
return input as PersistedLoginCredentials
|
||||
}
|
||||
|
||||
export function getSavedLoginCredentials(): DesktopLoginCredentials | null {
|
||||
try {
|
||||
if (!existsSync(credentialsPath())) {
|
||||
return null
|
||||
}
|
||||
|
||||
const persisted = normalizeCredentials(JSON.parse(readFileSync(credentialsPath(), 'utf8')))
|
||||
const identifier = typeof persisted.identifier === 'string' ? persisted.identifier : ''
|
||||
const encryptedPassword =
|
||||
typeof persisted.encryptedPassword === 'string' ? persisted.encryptedPassword : ''
|
||||
|
||||
if (!identifier || !encryptedPassword || !safeStorage.isEncryptionAvailable()) {
|
||||
return null
|
||||
}
|
||||
|
||||
const password = safeStorage.decryptString(Buffer.from(encryptedPassword, 'base64'))
|
||||
return password ? { identifier, password } : null
|
||||
} catch (error) {
|
||||
console.warn('[login-credentials] read failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function saveLoginCredentials(credentials: DesktopLoginCredentials): DesktopLoginCredentials {
|
||||
const identifier = credentials.identifier.trim()
|
||||
if (!identifier || !credentials.password) {
|
||||
clearSavedLoginCredentials()
|
||||
return { identifier: '', password: '' }
|
||||
}
|
||||
|
||||
if (!safeStorage.isEncryptionAvailable()) {
|
||||
throw new Error('login_credentials_encryption_unavailable')
|
||||
}
|
||||
|
||||
const encryptedPassword = safeStorage.encryptString(credentials.password).toString('base64')
|
||||
writeFileSync(
|
||||
credentialsPath(),
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
identifier,
|
||||
encryptedPassword,
|
||||
updatedAt: new Date().toISOString(),
|
||||
}),
|
||||
'utf8',
|
||||
)
|
||||
return { identifier, password: credentials.password }
|
||||
}
|
||||
|
||||
export function clearSavedLoginCredentials(): null {
|
||||
try {
|
||||
rmSync(credentialsPath(), { force: true })
|
||||
} catch (error) {
|
||||
console.warn('[login-credentials] clear failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
}
|
||||
return null
|
||||
}
|
||||
Reference in New Issue
Block a user