feature: the key of a credential store may be a path of keys, so stores of several kinds share one object of config.json.
Release / release (push) Failing after 16m30s
Release / release (push) Failing after 16m30s
This commit is contained in:
1 parent
7959c90012
commit
97c46ff0d8
6 files changed
+71
-12
No files matched your search
+28
-8
@@ -8,7 +8,8 @@ import { decrypt, encrypt, encryptionKey } from './secrets.ts'
|
||||
|
||||
/**
|
||||
* A credential store: the list under key of config.json at configPath, every entry of the kind the application gives,
|
||||
* with the names reserved for its own credentials. With missingAsEmpty a missing config.json reads as {}, and the
|
||||
* with the names reserved for its own credentials. key is a key of config.json or a path of keys separated by dots,
|
||||
* such as connections.pg, for a list in nested objects. With missingAsEmpty a missing config.json reads as {}, and the
|
||||
* first write creates it.
|
||||
*/
|
||||
export type CredentialStore = {
|
||||
@@ -114,9 +115,30 @@ export function credentialProblems(entries: unknown, store: Pick<CredentialStore
|
||||
return problems
|
||||
}
|
||||
|
||||
const isObject = (value: unknown): value is Record<string, unknown> => value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
|
||||
/** The value of data at key, a key or a path of keys separated by dots; undefined when it is not there. */
|
||||
function valueAt(data: Record<string, unknown>, key: string): unknown {
|
||||
return key.split('.').reduce<unknown>((value, part) => (isObject(value) ? value[part] : undefined), data)
|
||||
}
|
||||
|
||||
/**
|
||||
* data with value at key, a key or a path of keys separated by dots, after the other keys of its object; the objects
|
||||
* of the path that are missing, or are not objects, are created, and their other keys kept.
|
||||
*/
|
||||
function withValue(data: Record<string, unknown>, key: string, value: unknown): Record<string, unknown> {
|
||||
const [first, ...rest] = key.split('.')
|
||||
if (rest.length > 0) {
|
||||
return { ...data, [first]: withValue(isObject(data[first]) ? data[first] : {}, rest.join('.'), value) }
|
||||
}
|
||||
const others = { ...data }
|
||||
delete others[first]
|
||||
return { ...others, [first]: value }
|
||||
}
|
||||
|
||||
/** The entries of the store's key in config.json; a value that is not a list reads as none. */
|
||||
function entriesOf(data: Record<string, unknown>, store: CredentialStore): Record<string, unknown>[] {
|
||||
const entries = data[store.key]
|
||||
const entries = valueAt(data, store.key)
|
||||
return Array.isArray(entries) ? entries.filter((entry) => entry !== null && typeof entry === 'object') : []
|
||||
}
|
||||
|
||||
@@ -185,7 +207,7 @@ function toEntries(profiles: CredentialProfile[], data: Record<string, unknown>,
|
||||
*/
|
||||
export function credentialValues(store: CredentialStore, name: string): CredentialValues {
|
||||
const { data } = read(store)
|
||||
const [problem] = Object.entries(credentialProblems(data[store.key], store))
|
||||
const [problem] = Object.entries(credentialProblems(valueAt(data, store.key), store))
|
||||
if (problem !== undefined) {
|
||||
throw new HttpError(500, `${problem[0]} in ${store.configPath} ${problem[1]}.`)
|
||||
}
|
||||
@@ -234,8 +256,8 @@ export function registerCredentialRoutes(app: FastifyInstance, store: Credential
|
||||
function problemsOf(value: CredentialProfile[], data: Record<string, unknown>): Record<string, string> {
|
||||
const problems: Record<string, string> = {}
|
||||
for (const [path, message] of Object.entries(credentialProblems(toEntries(value, data, store, () => 'new'), store))) {
|
||||
const [, index, field] = path.split('.')
|
||||
problems[field === undefined ? index ?? store.key : `${index}.${field}`] = `${field ?? 'The credential'} ${message}.`
|
||||
const [index, field] = path.slice(store.key.length + 1).split('.')
|
||||
problems[field === undefined ? index || store.key : `${index}.${field}`] = `${field ?? 'The credential'} ${message}.`
|
||||
}
|
||||
if (key() === null) {
|
||||
value.forEach((profile, index) => {
|
||||
@@ -279,11 +301,9 @@ export function registerCredentialRoutes(app: FastifyInstance, store: Credential
|
||||
throw new HttpError(422, 'The credentials are not valid.', problems)
|
||||
}
|
||||
const sealing = key()!
|
||||
const others = { ...file.data }
|
||||
delete others[store.key]
|
||||
const entries = toEntries(request.body.value, file.data, store, (secret) => encrypt(secret, sealing))
|
||||
try {
|
||||
writeFileSync(store.configPath, `${JSON.stringify({ ...others, [store.key]: entries }, null, 2)}\n`)
|
||||
writeFileSync(store.configPath, `${JSON.stringify(withValue(file.data, store.key, entries), null, 2)}\n`)
|
||||
} catch (error) {
|
||||
throw new HttpError(500, `${store.configPath} cannot be written: ${(error as Error).message}`)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user