@auths-dev/sdk 0.0.1 → 0.1.0
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/Cargo.toml +45 -0
- package/README.md +163 -4
- package/__test__/client.spec.ts +78 -0
- package/__test__/exports.spec.ts +57 -0
- package/__test__/integration.spec.ts +407 -0
- package/__test__/policy.spec.ts +202 -0
- package/__test__/verify.spec.ts +88 -0
- package/build.rs +5 -0
- package/index.d.ts +259 -0
- package/index.js +622 -1
- package/lib/artifacts.ts +124 -0
- package/lib/attestations.ts +126 -0
- package/lib/audit.ts +189 -0
- package/lib/client.ts +293 -0
- package/lib/commits.ts +70 -0
- package/lib/devices.ts +178 -0
- package/lib/errors.ts +306 -0
- package/lib/identity.ts +280 -0
- package/lib/index.ts +125 -0
- package/lib/native.ts +255 -0
- package/lib/org.ts +235 -0
- package/lib/pairing.ts +271 -0
- package/lib/policy.ts +669 -0
- package/lib/signing.ts +204 -0
- package/lib/trust.ts +152 -0
- package/lib/types.ts +179 -0
- package/lib/verify.ts +241 -0
- package/lib/witness.ts +91 -0
- package/npm/darwin-arm64/README.md +3 -0
- package/npm/darwin-arm64/package.json +23 -0
- package/npm/linux-arm64-gnu/README.md +3 -0
- package/npm/linux-arm64-gnu/package.json +26 -0
- package/npm/linux-x64-gnu/README.md +3 -0
- package/npm/linux-x64-gnu/package.json +26 -0
- package/npm/win32-arm64-msvc/README.md +3 -0
- package/npm/win32-arm64-msvc/package.json +23 -0
- package/npm/win32-x64-msvc/README.md +3 -0
- package/npm/win32-x64-msvc/package.json +23 -0
- package/package.json +51 -16
- package/src/artifact.rs +217 -0
- package/src/attestation_query.rs +104 -0
- package/src/audit.rs +128 -0
- package/src/commit_sign.rs +63 -0
- package/src/device.rs +212 -0
- package/src/diagnostics.rs +106 -0
- package/src/error.rs +5 -0
- package/src/helpers.rs +60 -0
- package/src/identity.rs +467 -0
- package/src/lib.rs +26 -0
- package/src/org.rs +430 -0
- package/src/pairing.rs +454 -0
- package/src/policy.rs +147 -0
- package/src/sign.rs +215 -0
- package/src/trust.rs +189 -0
- package/src/types.rs +205 -0
- package/src/verify.rs +447 -0
- package/src/witness.rs +138 -0
- package/tsconfig.json +19 -0
- package/typedoc.json +18 -0
- package/vitest.config.ts +12 -0
package/lib/policy.ts
ADDED
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
import native from './native'
|
|
2
|
+
import { mapNativeError, AuthsError } from './errors'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Authorization outcome from a policy evaluation.
|
|
6
|
+
*
|
|
7
|
+
* Values match the Rust `Outcome` enum in `auths-policy/src/decision.rs`.
|
|
8
|
+
*/
|
|
9
|
+
export const Outcome = {
|
|
10
|
+
Allow: 'Allow',
|
|
11
|
+
Deny: 'Deny',
|
|
12
|
+
Indeterminate: 'Indeterminate',
|
|
13
|
+
RequiresApproval: 'RequiresApproval',
|
|
14
|
+
MissingCredential: 'MissingCredential',
|
|
15
|
+
} as const
|
|
16
|
+
export type Outcome = (typeof Outcome)[keyof typeof Outcome]
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Machine-readable reason code for stable logging and alerting.
|
|
20
|
+
*
|
|
21
|
+
* Values match the Rust `ReasonCode` enum in `auths-policy/src/decision.rs`.
|
|
22
|
+
*/
|
|
23
|
+
export const ReasonCode = {
|
|
24
|
+
Unconditional: 'Unconditional',
|
|
25
|
+
AllChecksPassed: 'AllChecksPassed',
|
|
26
|
+
CapabilityPresent: 'CapabilityPresent',
|
|
27
|
+
CapabilityMissing: 'CapabilityMissing',
|
|
28
|
+
IssuerMatch: 'IssuerMatch',
|
|
29
|
+
IssuerMismatch: 'IssuerMismatch',
|
|
30
|
+
Revoked: 'Revoked',
|
|
31
|
+
Expired: 'Expired',
|
|
32
|
+
InsufficientTtl: 'InsufficientTtl',
|
|
33
|
+
IssuedTooLongAgo: 'IssuedTooLongAgo',
|
|
34
|
+
RoleMismatch: 'RoleMismatch',
|
|
35
|
+
ScopeMismatch: 'ScopeMismatch',
|
|
36
|
+
ChainTooDeep: 'ChainTooDeep',
|
|
37
|
+
DelegationMismatch: 'DelegationMismatch',
|
|
38
|
+
AttrMismatch: 'AttrMismatch',
|
|
39
|
+
MissingField: 'MissingField',
|
|
40
|
+
RecursionExceeded: 'RecursionExceeded',
|
|
41
|
+
ShortCircuit: 'ShortCircuit',
|
|
42
|
+
CombinatorResult: 'CombinatorResult',
|
|
43
|
+
WorkloadMismatch: 'WorkloadMismatch',
|
|
44
|
+
WitnessQuorumNotMet: 'WitnessQuorumNotMet',
|
|
45
|
+
SignerTypeMatch: 'SignerTypeMatch',
|
|
46
|
+
SignerTypeMismatch: 'SignerTypeMismatch',
|
|
47
|
+
ApprovalRequired: 'ApprovalRequired',
|
|
48
|
+
ApprovalGranted: 'ApprovalGranted',
|
|
49
|
+
ApprovalExpired: 'ApprovalExpired',
|
|
50
|
+
ApprovalAlreadyUsed: 'ApprovalAlreadyUsed',
|
|
51
|
+
ApprovalRequestMismatch: 'ApprovalRequestMismatch',
|
|
52
|
+
} as const
|
|
53
|
+
export type ReasonCode = (typeof ReasonCode)[keyof typeof ReasonCode]
|
|
54
|
+
|
|
55
|
+
/** Result of evaluating a policy against a context. */
|
|
56
|
+
export interface PolicyDecision {
|
|
57
|
+
/** Raw outcome string (`'allow'` or `'deny'`). */
|
|
58
|
+
outcome: string
|
|
59
|
+
/** Machine-readable reason code. */
|
|
60
|
+
reason: string
|
|
61
|
+
/** Human-readable explanation of the decision. */
|
|
62
|
+
message: string
|
|
63
|
+
/** Convenience: `true` when `outcome === 'allow'`. */
|
|
64
|
+
allowed: boolean
|
|
65
|
+
/** Convenience: `true` when `outcome === 'deny'`. */
|
|
66
|
+
denied: boolean
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A commit verification result (from Git commit verification). */
|
|
70
|
+
export interface CommitResultLike {
|
|
71
|
+
/** Git commit SHA. */
|
|
72
|
+
commitSha: string
|
|
73
|
+
/** Whether the commit signature is valid. */
|
|
74
|
+
isValid: boolean
|
|
75
|
+
/** Hex-encoded public key of the signer, if identified. */
|
|
76
|
+
signer?: string | null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build an EvalContext options object from a commit verification result.
|
|
81
|
+
*
|
|
82
|
+
* Extracts the signer hex from the commit result and converts it to a
|
|
83
|
+
* `did:key:` DID for use as the `subject` field.
|
|
84
|
+
*
|
|
85
|
+
* @param commitResult - A commit verification result with a `signer` hex field.
|
|
86
|
+
* @param issuer - The issuer DID (`did:keri:...`).
|
|
87
|
+
* @param capabilities - Optional capability list to include.
|
|
88
|
+
* @returns An EvalContextOpts suitable for `evaluatePolicy()`.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const ctx = evalContextFromCommitResult(cr, org.orgDid, ['sign_commit'])
|
|
93
|
+
* const decision = evaluatePolicy(compiled, ctx)
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export function evalContextFromCommitResult(
|
|
97
|
+
commitResult: CommitResultLike,
|
|
98
|
+
issuer: string,
|
|
99
|
+
capabilities?: string[],
|
|
100
|
+
): EvalContextOpts {
|
|
101
|
+
const subject = commitResult.signer
|
|
102
|
+
? `did:key:z${commitResult.signer}`
|
|
103
|
+
: 'unknown'
|
|
104
|
+
const ctx: EvalContextOpts = { issuer, subject }
|
|
105
|
+
if (capabilities) ctx.capabilities = capabilities
|
|
106
|
+
return ctx
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Context for policy evaluation.
|
|
111
|
+
*
|
|
112
|
+
* **DID format requirements:**
|
|
113
|
+
* - `issuer`: Must be a valid DID. Typically `did:keri:E...` for identity DIDs
|
|
114
|
+
* (organizations, individuals) or `did:key:z...` for device DIDs.
|
|
115
|
+
* - `subject`: Same format rules as `issuer`. For device attestations, this is
|
|
116
|
+
* usually a `did:key:z...` device DID.
|
|
117
|
+
*
|
|
118
|
+
* Both `issuer` and `subject` are parsed into `CanonicalDid` values by the
|
|
119
|
+
* Rust policy engine. The engine accepts both `did:keri:` and `did:key:` formats.
|
|
120
|
+
* Invalid DID strings will cause evaluation to fail with a parse error.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```typescript
|
|
124
|
+
* const ctx: EvalContextOpts = {
|
|
125
|
+
* issuer: 'did:keri:EOrg123', // organization identity
|
|
126
|
+
* subject: 'did:key:z6MkDevice', // device key
|
|
127
|
+
* capabilities: ['sign_commit'],
|
|
128
|
+
* }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export interface EvalContextOpts {
|
|
132
|
+
/**
|
|
133
|
+
* DID of the attestation issuer.
|
|
134
|
+
*
|
|
135
|
+
* Must be a valid `did:keri:` or `did:key:` DID string.
|
|
136
|
+
* Typically the organization or identity that issued the attestation.
|
|
137
|
+
*/
|
|
138
|
+
issuer: string
|
|
139
|
+
/**
|
|
140
|
+
* DID of the attestation subject.
|
|
141
|
+
*
|
|
142
|
+
* Must be a valid `did:keri:` or `did:key:` DID string.
|
|
143
|
+
* For device attestations, this is the device's `did:key:z...` DID.
|
|
144
|
+
*/
|
|
145
|
+
subject: string
|
|
146
|
+
/** Capabilities held by the subject. */
|
|
147
|
+
capabilities?: string[]
|
|
148
|
+
/** Role of the subject (e.g. `'admin'`, `'member'`). */
|
|
149
|
+
role?: string
|
|
150
|
+
/** Whether the attestation has been revoked. */
|
|
151
|
+
revoked?: boolean
|
|
152
|
+
/** Expiration timestamp (RFC 3339). */
|
|
153
|
+
expiresAt?: string
|
|
154
|
+
/** Repository scope (e.g. `'org/repo'`). */
|
|
155
|
+
repo?: string
|
|
156
|
+
/** Deployment environment (e.g. `'production'`). */
|
|
157
|
+
environment?: string
|
|
158
|
+
/** Signer type constraint. */
|
|
159
|
+
signerType?: 'human' | 'agent' | 'workload'
|
|
160
|
+
/** DID of the delegating identity. */
|
|
161
|
+
delegatedBy?: string
|
|
162
|
+
/** Depth of the attestation chain. */
|
|
163
|
+
chainDepth?: number
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
type Predicate = Record<string, unknown>
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Fluent builder for composing authorization policies.
|
|
170
|
+
*
|
|
171
|
+
* Policies are built by chaining predicates, then compiled and evaluated
|
|
172
|
+
* against an attestation context to produce an allow/deny decision.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```typescript
|
|
176
|
+
* import { PolicyBuilder } from '@auths-dev/sdk'
|
|
177
|
+
*
|
|
178
|
+
* // Quick standard policy
|
|
179
|
+
* const policy = PolicyBuilder.standard('sign_commit')
|
|
180
|
+
* const decision = policy.evaluate({
|
|
181
|
+
* issuer: 'did:keri:EOrg',
|
|
182
|
+
* subject: 'did:key:zDevice',
|
|
183
|
+
* capabilities: ['sign_commit'],
|
|
184
|
+
* })
|
|
185
|
+
* console.log(decision.allowed) // true
|
|
186
|
+
*
|
|
187
|
+
* // Complex composed policy
|
|
188
|
+
* const ciPolicy = new PolicyBuilder()
|
|
189
|
+
* .notRevoked()
|
|
190
|
+
* .notExpired()
|
|
191
|
+
* .requireCapability('sign')
|
|
192
|
+
* .requireAgent()
|
|
193
|
+
* .requireRepo('org/repo')
|
|
194
|
+
* .build()
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export class PolicyBuilder {
|
|
198
|
+
private predicates: Predicate[] = []
|
|
199
|
+
|
|
200
|
+
/** All available predicate method names. */
|
|
201
|
+
static readonly AVAILABLE_PREDICATES: string[] = [
|
|
202
|
+
'notRevoked',
|
|
203
|
+
'notExpired',
|
|
204
|
+
'expiresAfter',
|
|
205
|
+
'issuedWithin',
|
|
206
|
+
'requireCapability',
|
|
207
|
+
'requireAllCapabilities',
|
|
208
|
+
'requireAnyCapability',
|
|
209
|
+
'requireIssuer',
|
|
210
|
+
'requireIssuerIn',
|
|
211
|
+
'requireSubject',
|
|
212
|
+
'requireDelegatedBy',
|
|
213
|
+
'requireAgent',
|
|
214
|
+
'requireHuman',
|
|
215
|
+
'requireWorkload',
|
|
216
|
+
'requireRepo',
|
|
217
|
+
'requireRepoIn',
|
|
218
|
+
'requireEnv',
|
|
219
|
+
'requireEnvIn',
|
|
220
|
+
'refMatches',
|
|
221
|
+
'pathAllowed',
|
|
222
|
+
'maxChainDepth',
|
|
223
|
+
'attrEquals',
|
|
224
|
+
'attrIn',
|
|
225
|
+
'workloadIssuerIs',
|
|
226
|
+
'workloadClaimEquals',
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
/** Built-in preset policy names. */
|
|
230
|
+
static readonly AVAILABLE_PRESETS: string[] = ['standard']
|
|
231
|
+
|
|
232
|
+
/** Returns the list of available predicate method names. */
|
|
233
|
+
static availablePredicates(): string[] {
|
|
234
|
+
return [...PolicyBuilder.AVAILABLE_PREDICATES]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Returns the list of available preset policy names. */
|
|
238
|
+
static availablePresets(): string[] {
|
|
239
|
+
return [...PolicyBuilder.AVAILABLE_PRESETS]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Creates a standard policy requiring not-revoked, not-expired, and a capability.
|
|
244
|
+
*
|
|
245
|
+
* @param capability - Required capability string.
|
|
246
|
+
* @returns A new builder with the standard predicates.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const policy = PolicyBuilder.standard('sign_commit')
|
|
251
|
+
* ```
|
|
252
|
+
*/
|
|
253
|
+
static standard(capability: string): PolicyBuilder {
|
|
254
|
+
return new PolicyBuilder()
|
|
255
|
+
.notRevoked()
|
|
256
|
+
.notExpired()
|
|
257
|
+
.requireCapability(capability)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Reconstructs a PolicyBuilder from a JSON policy expression.
|
|
262
|
+
*
|
|
263
|
+
* @param jsonStr - JSON string from `toJson()` or config files.
|
|
264
|
+
* @returns A new builder with the parsed predicates.
|
|
265
|
+
*/
|
|
266
|
+
static fromJson(jsonStr: string): PolicyBuilder {
|
|
267
|
+
const expr = JSON.parse(jsonStr) as Record<string, unknown>
|
|
268
|
+
const result = new PolicyBuilder()
|
|
269
|
+
if (expr.op === 'And' && Array.isArray(expr.args)) {
|
|
270
|
+
result.predicates = expr.args as Predicate[]
|
|
271
|
+
} else {
|
|
272
|
+
result.predicates = [expr as Predicate]
|
|
273
|
+
}
|
|
274
|
+
return result
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Creates a policy that passes if any of the given policies pass.
|
|
279
|
+
*
|
|
280
|
+
* @param builders - Policies to OR together.
|
|
281
|
+
* @returns A new builder combining the policies.
|
|
282
|
+
*/
|
|
283
|
+
static anyOf(...builders: PolicyBuilder[]): PolicyBuilder {
|
|
284
|
+
const result = new PolicyBuilder()
|
|
285
|
+
const orArgs = builders.map(b => ({ op: 'And', args: b.predicates }))
|
|
286
|
+
result.predicates = [{ op: 'Or', args: orArgs }]
|
|
287
|
+
return result
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Requires the attestation to not be revoked. */
|
|
291
|
+
notRevoked(): PolicyBuilder {
|
|
292
|
+
this.predicates.push({ op: 'NotRevoked' })
|
|
293
|
+
return this
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Requires the attestation to not be expired. */
|
|
297
|
+
notExpired(): PolicyBuilder {
|
|
298
|
+
this.predicates.push({ op: 'NotExpired' })
|
|
299
|
+
return this
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Requires the attestation to expire after the given number of seconds from now.
|
|
304
|
+
*
|
|
305
|
+
* @param seconds - Minimum remaining lifetime in seconds.
|
|
306
|
+
*/
|
|
307
|
+
expiresAfter(seconds: number): PolicyBuilder {
|
|
308
|
+
this.predicates.push({ op: 'ExpiresAfter', args: seconds })
|
|
309
|
+
return this
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Requires the attestation to have been issued within the given time window.
|
|
314
|
+
*
|
|
315
|
+
* @param seconds - Maximum age in seconds.
|
|
316
|
+
*/
|
|
317
|
+
issuedWithin(seconds: number): PolicyBuilder {
|
|
318
|
+
this.predicates.push({ op: 'IssuedWithin', args: seconds })
|
|
319
|
+
return this
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Requires the subject to hold a specific capability.
|
|
324
|
+
*
|
|
325
|
+
* @param cap - Capability string (e.g. `'sign'`, `'sign_commit'`).
|
|
326
|
+
*/
|
|
327
|
+
requireCapability(cap: string): PolicyBuilder {
|
|
328
|
+
this.predicates.push({ op: 'HasCapability', args: cap })
|
|
329
|
+
return this
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Requires the subject to hold all of the given capabilities.
|
|
334
|
+
*
|
|
335
|
+
* @param caps - Array of required capability strings.
|
|
336
|
+
*/
|
|
337
|
+
requireAllCapabilities(caps: string[]): PolicyBuilder {
|
|
338
|
+
for (const cap of caps) {
|
|
339
|
+
this.requireCapability(cap)
|
|
340
|
+
}
|
|
341
|
+
return this
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Requires the subject to hold at least one of the given capabilities.
|
|
346
|
+
*
|
|
347
|
+
* @param caps - Array of acceptable capability strings.
|
|
348
|
+
*/
|
|
349
|
+
requireAnyCapability(caps: string[]): PolicyBuilder {
|
|
350
|
+
const orArgs = caps.map(c => ({ op: 'HasCapability', args: c }))
|
|
351
|
+
this.predicates.push({ op: 'Or', args: orArgs })
|
|
352
|
+
return this
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Requires the issuer to match a specific DID.
|
|
357
|
+
*
|
|
358
|
+
* @param did - Required issuer DID.
|
|
359
|
+
*/
|
|
360
|
+
requireIssuer(did: string): PolicyBuilder {
|
|
361
|
+
this.predicates.push({ op: 'IssuerIs', args: did })
|
|
362
|
+
return this
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Requires the issuer to be one of the given DIDs.
|
|
367
|
+
*
|
|
368
|
+
* @param dids - Acceptable issuer DIDs.
|
|
369
|
+
*/
|
|
370
|
+
requireIssuerIn(dids: string[]): PolicyBuilder {
|
|
371
|
+
const orArgs = dids.map(d => ({ op: 'IssuerIs', args: d }))
|
|
372
|
+
this.predicates.push({ op: 'Or', args: orArgs })
|
|
373
|
+
return this
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Requires the subject to match a specific DID.
|
|
378
|
+
*
|
|
379
|
+
* @param did - Required subject DID.
|
|
380
|
+
*/
|
|
381
|
+
requireSubject(did: string): PolicyBuilder {
|
|
382
|
+
this.predicates.push({ op: 'SubjectIs', args: did })
|
|
383
|
+
return this
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Requires the attestation to have been delegated by a specific identity.
|
|
388
|
+
*
|
|
389
|
+
* @param did - DID of the required delegator.
|
|
390
|
+
*/
|
|
391
|
+
requireDelegatedBy(did: string): PolicyBuilder {
|
|
392
|
+
this.predicates.push({ op: 'DelegatedBy', args: did })
|
|
393
|
+
return this
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Requires the signer to be an agent. */
|
|
397
|
+
requireAgent(): PolicyBuilder {
|
|
398
|
+
this.predicates.push({ op: 'IsAgent' })
|
|
399
|
+
return this
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Requires the signer to be a human. */
|
|
403
|
+
requireHuman(): PolicyBuilder {
|
|
404
|
+
this.predicates.push({ op: 'IsHuman' })
|
|
405
|
+
return this
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Requires the signer to be a workload identity. */
|
|
409
|
+
requireWorkload(): PolicyBuilder {
|
|
410
|
+
this.predicates.push({ op: 'IsWorkload' })
|
|
411
|
+
return this
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Requires the operation to target a specific repository.
|
|
416
|
+
*
|
|
417
|
+
* @param repo - Repository identifier (e.g. `'org/repo'`).
|
|
418
|
+
*/
|
|
419
|
+
requireRepo(repo: string): PolicyBuilder {
|
|
420
|
+
this.predicates.push({ op: 'RepoIs', args: repo })
|
|
421
|
+
return this
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Requires the operation to target one of the given repositories.
|
|
426
|
+
*
|
|
427
|
+
* @param repos - Acceptable repository identifiers.
|
|
428
|
+
*/
|
|
429
|
+
requireRepoIn(repos: string[]): PolicyBuilder {
|
|
430
|
+
const orArgs = repos.map(r => ({ op: 'RepoIs', args: r }))
|
|
431
|
+
this.predicates.push({ op: 'Or', args: orArgs })
|
|
432
|
+
return this
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Requires a specific deployment environment.
|
|
437
|
+
*
|
|
438
|
+
* @param env - Environment name (e.g. `'production'`).
|
|
439
|
+
*/
|
|
440
|
+
requireEnv(env: string): PolicyBuilder {
|
|
441
|
+
this.predicates.push({ op: 'EnvIs', args: env })
|
|
442
|
+
return this
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Requires one of the given deployment environments.
|
|
447
|
+
*
|
|
448
|
+
* @param envs - Acceptable environment names.
|
|
449
|
+
*/
|
|
450
|
+
requireEnvIn(envs: string[]): PolicyBuilder {
|
|
451
|
+
const orArgs = envs.map(e => ({ op: 'EnvIs', args: e }))
|
|
452
|
+
this.predicates.push({ op: 'Or', args: orArgs })
|
|
453
|
+
return this
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Requires the Git ref to match a pattern.
|
|
458
|
+
*
|
|
459
|
+
* @param pattern - Ref pattern (e.g. `'refs/heads/main'`).
|
|
460
|
+
*/
|
|
461
|
+
refMatches(pattern: string): PolicyBuilder {
|
|
462
|
+
this.predicates.push({ op: 'RefMatches', args: pattern })
|
|
463
|
+
return this
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Restricts allowed file paths.
|
|
468
|
+
*
|
|
469
|
+
* @param patterns - Glob patterns for allowed paths.
|
|
470
|
+
*/
|
|
471
|
+
pathAllowed(patterns: string[]): PolicyBuilder {
|
|
472
|
+
this.predicates.push({ op: 'PathAllowed', args: patterns })
|
|
473
|
+
return this
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Limits the maximum attestation chain depth.
|
|
478
|
+
*
|
|
479
|
+
* @param depth - Maximum allowed chain depth.
|
|
480
|
+
*/
|
|
481
|
+
maxChainDepth(depth: number): PolicyBuilder {
|
|
482
|
+
this.predicates.push({ op: 'MaxChainDepth', args: depth })
|
|
483
|
+
return this
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Requires an attestation attribute to equal a specific value.
|
|
488
|
+
*
|
|
489
|
+
* @param key - Attribute key.
|
|
490
|
+
* @param value - Required attribute value.
|
|
491
|
+
*/
|
|
492
|
+
attrEquals(key: string, value: string): PolicyBuilder {
|
|
493
|
+
this.predicates.push({ op: 'AttrEquals', args: { key, value } })
|
|
494
|
+
return this
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Requires an attestation attribute to be one of the given values.
|
|
499
|
+
*
|
|
500
|
+
* @param key - Attribute key.
|
|
501
|
+
* @param values - Acceptable attribute values.
|
|
502
|
+
*/
|
|
503
|
+
attrIn(key: string, values: string[]): PolicyBuilder {
|
|
504
|
+
this.predicates.push({ op: 'AttrIn', args: { key, values } })
|
|
505
|
+
return this
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Requires the workload attestation issuer to match a specific DID.
|
|
510
|
+
*
|
|
511
|
+
* @param did - Required workload issuer DID.
|
|
512
|
+
*/
|
|
513
|
+
workloadIssuerIs(did: string): PolicyBuilder {
|
|
514
|
+
this.predicates.push({ op: 'WorkloadIssuerIs', args: did })
|
|
515
|
+
return this
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Requires a workload attestation claim to equal a specific value.
|
|
520
|
+
*
|
|
521
|
+
* @param key - Claim key.
|
|
522
|
+
* @param value - Required claim value.
|
|
523
|
+
*/
|
|
524
|
+
workloadClaimEquals(key: string, value: string): PolicyBuilder {
|
|
525
|
+
this.predicates.push({ op: 'WorkloadClaimEquals', args: { key, value } })
|
|
526
|
+
return this
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Combines this policy with another using OR logic.
|
|
531
|
+
*
|
|
532
|
+
* @param other - The other policy builder.
|
|
533
|
+
* @returns A new builder that passes if either policy passes.
|
|
534
|
+
*/
|
|
535
|
+
orPolicy(other: PolicyBuilder): PolicyBuilder {
|
|
536
|
+
return PolicyBuilder.anyOf(this, other)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Negates this policy — passes when the original would deny, and vice versa.
|
|
541
|
+
*
|
|
542
|
+
* @returns A new negated builder.
|
|
543
|
+
*/
|
|
544
|
+
negate(): PolicyBuilder {
|
|
545
|
+
const result = new PolicyBuilder()
|
|
546
|
+
result.predicates = [{ op: 'Not', args: { op: 'And', args: this.predicates } }]
|
|
547
|
+
return result
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Serializes the policy to JSON without compiling.
|
|
552
|
+
*
|
|
553
|
+
* @returns JSON string representation of the policy expression.
|
|
554
|
+
* @throws Error if the policy has no predicates.
|
|
555
|
+
*/
|
|
556
|
+
toJson(): string {
|
|
557
|
+
if (this.predicates.length === 0) {
|
|
558
|
+
throw new Error('Cannot export an empty policy.')
|
|
559
|
+
}
|
|
560
|
+
const expr = { op: 'And', args: this.predicates }
|
|
561
|
+
return JSON.stringify(expr)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Compiles the policy for evaluation using the native policy engine.
|
|
566
|
+
*
|
|
567
|
+
* @returns Compiled policy JSON string.
|
|
568
|
+
* @throws {@link AuthsError} if compilation fails.
|
|
569
|
+
* @throws Error if the policy has no predicates.
|
|
570
|
+
*/
|
|
571
|
+
build(): string {
|
|
572
|
+
if (this.predicates.length === 0) {
|
|
573
|
+
throw new Error(
|
|
574
|
+
'Cannot build an empty policy. Add at least one predicate, ' +
|
|
575
|
+
'or use PolicyBuilder.standard("capability") for the common case.'
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
const json = this.toJson()
|
|
579
|
+
try {
|
|
580
|
+
return native.compilePolicy(json)
|
|
581
|
+
} catch (err) {
|
|
582
|
+
throw mapNativeError(err, AuthsError)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Builds and evaluates the policy against a context in one step.
|
|
588
|
+
*
|
|
589
|
+
* @param context - The evaluation context.
|
|
590
|
+
* @returns The policy decision.
|
|
591
|
+
* @throws {@link AuthsError} if compilation or evaluation fails.
|
|
592
|
+
*
|
|
593
|
+
* @example
|
|
594
|
+
* ```typescript
|
|
595
|
+
* const decision = PolicyBuilder.standard('sign').evaluate({
|
|
596
|
+
* issuer: 'did:keri:EOrg',
|
|
597
|
+
* subject: 'did:key:zDevice',
|
|
598
|
+
* capabilities: ['sign'],
|
|
599
|
+
* })
|
|
600
|
+
* console.log(decision.allowed) // true
|
|
601
|
+
* ```
|
|
602
|
+
*/
|
|
603
|
+
evaluate(context: EvalContextOpts): PolicyDecision {
|
|
604
|
+
const compiledJson = this.build()
|
|
605
|
+
return evaluatePolicy(compiledJson, context)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Compiles a raw policy JSON string for use with {@link evaluatePolicy}.
|
|
611
|
+
*
|
|
612
|
+
* @param policyJson - JSON string of the policy expression.
|
|
613
|
+
* @returns Compiled policy JSON.
|
|
614
|
+
* @throws {@link AuthsError} if the policy is invalid.
|
|
615
|
+
*/
|
|
616
|
+
export function compilePolicy(policyJson: string): string {
|
|
617
|
+
try {
|
|
618
|
+
return native.compilePolicy(policyJson)
|
|
619
|
+
} catch (err) {
|
|
620
|
+
throw mapNativeError(err, AuthsError)
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Evaluates a compiled policy against an attestation context.
|
|
626
|
+
*
|
|
627
|
+
* @param compiledPolicyJson - Compiled policy from {@link compilePolicy} or {@link PolicyBuilder.build}.
|
|
628
|
+
* @param context - The evaluation context.
|
|
629
|
+
* @returns The policy decision with `allowed`/`denied` convenience booleans.
|
|
630
|
+
* @throws {@link AuthsError} if evaluation fails.
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* ```typescript
|
|
634
|
+
* import { compilePolicy, evaluatePolicy } from '@auths-dev/sdk'
|
|
635
|
+
*
|
|
636
|
+
* const compiled = compilePolicy(policyJson)
|
|
637
|
+
* const decision = evaluatePolicy(compiled, {
|
|
638
|
+
* issuer: 'did:keri:EOrg',
|
|
639
|
+
* subject: 'did:key:zDevice',
|
|
640
|
+
* })
|
|
641
|
+
* ```
|
|
642
|
+
*/
|
|
643
|
+
export function evaluatePolicy(compiledPolicyJson: string, context: EvalContextOpts): PolicyDecision {
|
|
644
|
+
try {
|
|
645
|
+
const result = native.evaluatePolicy(
|
|
646
|
+
compiledPolicyJson,
|
|
647
|
+
context.issuer,
|
|
648
|
+
context.subject,
|
|
649
|
+
context.capabilities ?? null,
|
|
650
|
+
context.role ?? null,
|
|
651
|
+
context.revoked ?? null,
|
|
652
|
+
context.expiresAt ?? null,
|
|
653
|
+
context.repo ?? null,
|
|
654
|
+
context.environment ?? null,
|
|
655
|
+
context.signerType ?? null,
|
|
656
|
+
context.delegatedBy ?? null,
|
|
657
|
+
context.chainDepth ?? null,
|
|
658
|
+
)
|
|
659
|
+
return {
|
|
660
|
+
outcome: result.outcome,
|
|
661
|
+
reason: result.reason,
|
|
662
|
+
message: result.message,
|
|
663
|
+
allowed: result.outcome === 'allow',
|
|
664
|
+
denied: result.outcome === 'deny',
|
|
665
|
+
}
|
|
666
|
+
} catch (err) {
|
|
667
|
+
throw mapNativeError(err, AuthsError)
|
|
668
|
+
}
|
|
669
|
+
}
|