@better-forge/vault 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/vaultUse.ts CHANGED
@@ -1,41 +1,41 @@
1
- import { tokenAllows, VAULT_USE_SCOPE } from '@better-forge/contracts'
2
-
3
- type Json = Record<string, unknown>
4
- const isObject = (value: unknown): value is Json => !!value && typeof value === 'object' && !Array.isArray(value)
5
-
6
- /** Whether anything under a `vault` key is set, whatever its shape: a reference that is malformed is still treated as one. */
7
- function holdsVault(value: unknown): boolean {
8
- if (Array.isArray(value)) return value.some(holdsVault)
9
- if (!isObject(value)) return false
10
- return Object.entries(value).some(([key, inner]) => (key === 'vault' && inner !== undefined && inner !== null && inner !== false) || holdsVault(inner))
11
- }
12
-
13
- /** The same text for the same value, whatever the order of keys. */
14
- export function canonical(value: unknown): string {
15
- if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
16
- if (isObject(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
17
- return JSON.stringify(value) ?? 'null'
18
- }
19
-
20
- /**
21
- * The check that keeps a token without the permission to use vault secrets away from them. `target` names what a
22
- * vault reference sits in, in the application's words (for example "job or channel"), for the refusal message.
23
- */
24
- export function createVaultUseGuard(options: { target: string }) {
25
- const refused =
26
- `This token does not have the "${VAULT_USE_SCOPE}" permission, which it needs to add a vault reference or to change the configuration of a ${options.target} that has one`
27
-
28
- /**
29
- * A vault secret is sent wherever the configuration that references it points, and what the target answers (a query
30
- * result, an error message) comes back to the caller. A token that may edit such a configuration but may not use vault
31
- * secrets therefore can neither add a reference nor change anything in a configuration that has one: it can edit the
32
- * rest or remove the reference. `stored` is the configuration as it is saved now, or undefined for a new one.
33
- * Returns a message, or null if fine.
34
- */
35
- function vaultUseProblem(token: { scopes: readonly string[] } | undefined, incoming: unknown, stored: unknown): string | null {
36
- if (!token || tokenAllows(token.scopes, VAULT_USE_SCOPE) || !holdsVault(incoming)) return null
37
- return stored !== undefined && canonical(incoming) === canonical(stored) ? null : refused
38
- }
39
-
40
- return { refused, vaultUseProblem }
41
- }
1
+ import { tokenAllows, VAULT_USE_SCOPE } from '@better-forge/contracts'
2
+
3
+ type Json = Record<string, unknown>
4
+ const isObject = (value: unknown): value is Json => !!value && typeof value === 'object' && !Array.isArray(value)
5
+
6
+ /** Whether anything under a `vault` key is set, whatever its shape: a reference that is malformed is still treated as one. */
7
+ function holdsVault(value: unknown): boolean {
8
+ if (Array.isArray(value)) return value.some(holdsVault)
9
+ if (!isObject(value)) return false
10
+ return Object.entries(value).some(([key, inner]) => (key === 'vault' && inner !== undefined && inner !== null && inner !== false) || holdsVault(inner))
11
+ }
12
+
13
+ /** The same text for the same value, whatever the order of keys. */
14
+ export function canonical(value: unknown): string {
15
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
16
+ if (isObject(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
17
+ return JSON.stringify(value) ?? 'null'
18
+ }
19
+
20
+ /**
21
+ * The check that keeps a token without the permission to use vault secrets away from them. `target` names what a
22
+ * vault reference sits in, in the application's words (for example "job or channel"), for the refusal message.
23
+ */
24
+ export function createVaultUseGuard(options: { target: string }) {
25
+ const refused =
26
+ `This token does not have the "${VAULT_USE_SCOPE}" permission, which it needs to add a vault reference or to change the configuration of a ${options.target} that has one`
27
+
28
+ /**
29
+ * A vault secret is sent wherever the configuration that references it points, and what the target answers (a query
30
+ * result, an error message) comes back to the caller. A token that may edit such a configuration but may not use vault
31
+ * secrets therefore can neither add a reference nor change anything in a configuration that has one: it can edit the
32
+ * rest or remove the reference. `stored` is the configuration as it is saved now, or undefined for a new one.
33
+ * Returns a message, or null if fine.
34
+ */
35
+ function vaultUseProblem(token: { scopes: readonly string[] } | undefined, incoming: unknown, stored: unknown): string | null {
36
+ if (!token || tokenAllows(token.scopes, VAULT_USE_SCOPE) || !holdsVault(incoming)) return null
37
+ return stored !== undefined && canonical(incoming) === canonical(stored) ? null : refused
38
+ }
39
+
40
+ return { refused, vaultUseProblem }
41
+ }