nestjs-ddd-cli 3.3.1 → 3.4.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.
|
@@ -90,3 +90,33 @@ are bounded at 16 KiB under a five-second deadline; redirects are refused.
|
|
|
90
90
|
Receipts must bind the exact scope, operation and resource and expire within
|
|
91
91
|
PARC's five-second bound. Never cache/reuse them as durable permissions. A failed
|
|
92
92
|
request may already have consumed its lease; obtain fresh authority explicitly.
|
|
93
|
+
|
|
94
|
+
## Scoped grant shadow comparison
|
|
95
|
+
|
|
96
|
+
Deploy a PARC version accepting optional `shadowContext` before opting a consumer
|
|
97
|
+
into `shadowFacts`. Omit the option for unchanged wire compatibility with older
|
|
98
|
+
PARC installations. The receipt, allow/deny decision, expiry and owner commit
|
|
99
|
+
checks remain unchanged. There is no client-side grant evaluator.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const receipt = await parc.check({
|
|
103
|
+
// ...the existing lease, credential, permission, action, resource and expected scope
|
|
104
|
+
contextDigest: digestOfOwnerVerifiedOperation,
|
|
105
|
+
shadowFacts: { amountMinor: persistedOperation.amountMinor, currency: persistedOperation.currency },
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The operation digest must cover these exact facts and the resource/version being
|
|
110
|
+
authorized. Read them from persisted or independently verified owner state, never
|
|
111
|
+
copy amount, currency or policy claims from browser headers. PARC accepts facts
|
|
112
|
+
only from the admitted service owning the permission. Amounts are canonical
|
|
113
|
+
unsigned integer strings of at most 38 digits; currency is three uppercase
|
|
114
|
+
letters. Invalid optional facts produce an invalid shadow observation and do not
|
|
115
|
+
change the existing decision. No facts are included in the returned receipt.
|
|
116
|
+
|
|
117
|
+
PARC compares imported grants against legacy decisions separately from proposed
|
|
118
|
+
resource, amount and expiry restrictions. Intentional proposal differences are
|
|
119
|
+
not compatibility failures. Reports include unobserved decisions; an empty
|
|
120
|
+
window, missing observations or only denied requests cannot prove full parity.
|
|
121
|
+
Role/proposal administration stays in the governed PARC console, never this
|
|
122
|
+
consumer client. Keep legacy enforcement until the scoped rollout is proven.
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { BoundedJsonHttpClient } from '../../auth/oauth2-token-introspection/bounded-json-http.client';
|
|
2
2
|
|
|
3
|
+
export interface ParcShadowFacts {
|
|
4
|
+
/** Canonical integer string, never a floating-point amount. */
|
|
5
|
+
amountMinor: string;
|
|
6
|
+
currency: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Optional diagnostic facts. They cannot alter or replace the bound receipt. */
|
|
10
|
+
function shadowContext(facts: ParcShadowFacts, contextDigest: string) {
|
|
11
|
+
if (!facts || typeof facts.amountMinor !== 'string' || !/^(0|[1-9][0-9]{0,37})$/.test(facts.amountMinor) ||
|
|
12
|
+
typeof facts.currency !== 'string' || !/^[A-Z]{3}$/.test(facts.currency)) return null;
|
|
13
|
+
return { contractVersion: 1, contextDigest, facts: { amountMinor: facts.amountMinor, currency: facts.currency } };
|
|
14
|
+
}
|
|
15
|
+
|
|
3
16
|
export interface ParcBoundRequest {
|
|
4
17
|
/** Fresh opaque SA lease. PARC consumes it; do not introspect it first. */
|
|
5
18
|
lease: string;
|
|
@@ -10,6 +23,9 @@ export interface ParcBoundRequest {
|
|
|
10
23
|
resource: { type: string; id: string };
|
|
11
24
|
/** SHA-256 of the owner-verified operation material. */
|
|
12
25
|
contextDigest: string;
|
|
26
|
+
/** Opt-in shadow comparison only. Read from the same owner material covered by contextDigest.
|
|
27
|
+
* Enable only after the receiving PARC API supports shadowContext. */
|
|
28
|
+
shadowFacts?: ParcShadowFacts;
|
|
13
29
|
/** Persisted owner scope or an untrusted route selector; trusted only after exact receipt matching. */
|
|
14
30
|
expected: { businessId: string; clientAppId: string; subjectId?: string };
|
|
15
31
|
}
|
|
@@ -75,6 +91,10 @@ export class ParcBoundAuthorizationClient {
|
|
|
75
91
|
};
|
|
76
92
|
if (typeof request.contextDigest !== 'string' ||
|
|
77
93
|
!/^[a-f0-9]{64}$/.test(request.contextDigest)) this.invalid();
|
|
94
|
+
// Invalid optional facts are recorded as invalid shadow context without changing enforcement.
|
|
95
|
+
const payload = input.shadowFacts === undefined ? request : {
|
|
96
|
+
...request, shadowContext: shadowContext(input.shadowFacts, request.contextDigest),
|
|
97
|
+
};
|
|
78
98
|
const expected = {
|
|
79
99
|
businessId: this.id(input?.expected?.businessId),
|
|
80
100
|
clientAppId: this.id(input?.expected?.clientAppId),
|
|
@@ -92,7 +112,7 @@ export class ParcBoundAuthorizationClient {
|
|
|
92
112
|
'x-request-context-lease': lease,
|
|
93
113
|
'x-service-access-credential': credential,
|
|
94
114
|
},
|
|
95
|
-
}).postJson(['v1', 'decisions', 'check-bound'],
|
|
115
|
+
}).postJson(['v1', 'decisions', 'check-bound'], payload);
|
|
96
116
|
} catch { throw new ParcBoundAuthorizationError('unavailable'); }
|
|
97
117
|
this.requireTls();
|
|
98
118
|
const fail = (): never => { throw new ParcBoundAuthorizationError('invalid_receipt'); };
|