← Public packages
@kentcdodds/audible
Personal Audible library app — browser re-auth, library, wishlist, NAS archive stub.
src/persist-auth.ts
168 lines · 4.7 KB · TypeScript/**
* Persist audible-cli auth JSON in packageStorage for ADP signing.
* secretSet remains optional/best-effort for host allowlist naming only —
* opaque secret mounts are not readable for JSON.parse after Sept 2026.
* Never returns secret values.
*/
import { kody } from 'kody:runtime'
import {
AUDIBLE_AUTH_SECRET,
audibleAuthSetupUrl,
authPublicSummary,
fromAuthFile,
writeAuthToPackageStorage,
} from './auth.ts'
import { AUTH_RELATED_HOSTS } from './locales.ts'
import { loadAuthSecretScope, rememberAuthSecretScope } from './auth-session.ts'
import type { AudibleAuth } from './types.ts'
export type PersistAuthResult = {
ok: true
secretName: string
scope: 'package' | 'user' | 'packageStorage'
authStatus: ReturnType<typeof authPublicSummary>
setupUrl: string
hostsToApprove: string[]
note: string
secretSetOk: boolean
} | {
ok: false
secretName: string
setupUrl: string
hostsToApprove: string[]
reason: string
}
function errorMessage(error: unknown): string {
if (error instanceof Error) return error.message
return String(error)
}
function authToJson(auth: AudibleAuth): string {
return JSON.stringify(
{
adp_token: auth.adp_token,
device_private_key: auth.device_private_key,
access_token: auth.access_token,
refresh_token: auth.refresh_token,
expires: auth.expires,
website_cookies: auth.website_cookies,
store_authentication_cookie: auth.store_authentication_cookie,
device_info: auth.device_info,
customer_info: auth.customer_info,
locale_code: auth.locale,
locale: auth.locale,
},
null,
2,
)
}
/**
* Write auth for ADP signing to packageStorage first. Optionally best-effort
* secretSet (package then user) for host allowlist naming — do not rely on
* reading the secret back.
*/
export async function persistAudibleAuth(
authJson: string,
): Promise<PersistAuthResult> {
const auth = fromAuthFile(authJson)
const summary = authPublicSummary(auth)
const setupUrl = audibleAuthSetupUrl()
const hosts = AUTH_RELATED_HOSTS
const description =
'Audible device auth JSON from browser OpenID+PKCE registration (audible-cli compatible). Opaque; ADP signing uses packageStorage audible-auth-v1.'
try {
await writeAuthToPackageStorage(authJson)
} catch (error) {
return {
ok: false,
secretName: AUDIBLE_AUTH_SECRET,
setupUrl,
hostsToApprove: hosts,
reason: `packageStorage write failed for audible-auth-v1: ${errorMessage(error)}`,
}
}
const attempts: Array<'package' | 'user'> = ['package', 'user']
const errors: string[] = []
let secretScope: 'package' | 'user' | null = null
for (const scope of attempts) {
try {
await kody.secretSet({
name: AUDIBLE_AUTH_SECRET,
value: authJson,
description,
scope,
})
await rememberAuthSecretScope(scope)
secretScope = scope
break
} catch (error) {
errors.push(`${scope}: ${errorMessage(error)}`)
}
}
return {
ok: true,
secretName: AUDIBLE_AUTH_SECRET,
scope: secretScope ?? 'packageStorage',
authStatus: summary,
setupUrl,
hostsToApprove: hosts,
secretSetOk: secretScope != null,
note:
secretScope != null
? `Saved ADP signing auth in packageStorage (audible-auth-v1) and best-effort secretSet as ${secretScope}-scoped audibleAuth (opaque; not re-read for signing).`
: `Saved ADP signing auth in packageStorage (audible-auth-v1). secretSet optional and denied (${errors.join(' | ')}); Connect/signing still works from package storage.`,
}
}
/** Re-persist updated access_token / expires after refresh (packageStorage first). */
export async function persistRefreshedAuth(auth: AudibleAuth): Promise<{
persisted: boolean
scope: 'package' | 'user' | 'packageStorage' | null
reason?: string
}> {
const authJson = authToJson(auth)
try {
await writeAuthToPackageStorage(authJson)
} catch (error) {
return {
persisted: false,
scope: null,
reason: `packageStorage write failed while refreshing: ${errorMessage(error)}; in-memory token still updated for this run.`,
}
}
const remembered = await loadAuthSecretScope()
const order: Array<'package' | 'user'> = remembered
? [remembered, remembered === 'package' ? 'user' : 'package']
: ['package', 'user']
for (const scope of order) {
try {
await kody.secretSet({
name: AUDIBLE_AUTH_SECRET,
value: authJson,
description:
'Audible device auth JSON (access token refreshed). Opaque; ADP signing uses packageStorage.',
scope,
})
await rememberAuthSecretScope(scope)
return { persisted: true, scope }
} catch (error) {
void error
}
}
return {
persisted: true,
scope: 'packageStorage',
reason:
'packageStorage updated; secretSet denied while refreshing (optional). In-memory + storage token updated for this run.',
}
}