@learncard/learn-cloud-service 2.5.34 → 2.5.35
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/.env.example +13 -0
- package/CHANGELOG.md +19 -0
- package/dist/accesslayer/share-content/delete.d.ts +5 -0
- package/dist/accesslayer/share-content/delete.d.ts.map +1 -0
- package/dist/accesslayer/share-content/index.d.ts +8 -0
- package/dist/accesslayer/share-content/index.d.ts.map +1 -0
- package/dist/accesslayer/share-content/initialize.d.ts +5 -0
- package/dist/accesslayer/share-content/initialize.d.ts.map +1 -0
- package/dist/accesslayer/share-content/internal.d.ts +12 -0
- package/dist/accesslayer/share-content/internal.d.ts.map +1 -0
- package/dist/accesslayer/share-content/put.d.ts +5 -0
- package/dist/accesslayer/share-content/put.d.ts.map +1 -0
- package/dist/accesslayer/share-content/read.d.ts +7 -0
- package/dist/accesslayer/share-content/read.d.ts.map +1 -0
- package/dist/accesslayer/share-content/types.d.ts +85 -0
- package/dist/accesslayer/share-content/types.d.ts.map +1 -0
- package/dist/config/environment.d.ts +12 -0
- package/dist/config/environment.d.ts.map +1 -1
- package/dist/helpers/share-content-auth/adapters.d.ts +12 -0
- package/dist/helpers/share-content-auth/adapters.d.ts.map +1 -0
- package/dist/helpers/share-content-auth/canonical.d.ts +15 -0
- package/dist/helpers/share-content-auth/canonical.d.ts.map +1 -0
- package/dist/helpers/share-content-auth/claims.d.ts +19 -0
- package/dist/helpers/share-content-auth/claims.d.ts.map +1 -0
- package/dist/helpers/share-content-auth/config.d.ts +12 -0
- package/dist/helpers/share-content-auth/config.d.ts.map +1 -0
- package/dist/helpers/share-content-auth/index.d.ts +8 -0
- package/dist/helpers/share-content-auth/index.d.ts.map +1 -0
- package/dist/helpers/share-content-auth/token.d.ts +28 -0
- package/dist/helpers/share-content-auth/token.d.ts.map +1 -0
- package/dist/helpers/share-content-auth/types.d.ts +70 -0
- package/dist/helpers/share-content-auth/types.d.ts.map +1 -0
- package/dist/helpers/share-content-auth/verifier.d.ts +9 -0
- package/dist/helpers/share-content-auth/verifier.d.ts.map +1 -0
- package/dist/index.js +2 -2
- package/dist/models/ShareContent.d.ts +160 -0
- package/dist/models/ShareContent.d.ts.map +1 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/share-content/config.d.ts +28 -0
- package/dist/share-content/config.d.ts.map +1 -0
- package/dist/share-content/index.d.ts +6 -0
- package/dist/share-content/index.d.ts.map +1 -0
- package/dist/share-content/plugin.d.ts +5 -0
- package/dist/share-content/plugin.d.ts.map +1 -0
- package/dist/share-content/production.d.ts +15 -0
- package/dist/share-content/production.d.ts.map +1 -0
- package/dist/share-content/runtime.d.ts +23 -0
- package/dist/share-content/runtime.d.ts.map +1 -0
- package/dist/share-content/types.d.ts +23 -0
- package/dist/share-content/types.d.ts.map +1 -0
- package/lambda.ts +61 -0
- package/package.json +14 -14
- package/serverless.yml +25 -0
- package/src/accesslayer/share-content/delete.ts +124 -0
- package/src/accesslayer/share-content/index.ts +70 -0
- package/src/accesslayer/share-content/initialize.ts +24 -0
- package/src/accesslayer/share-content/internal.ts +89 -0
- package/src/accesslayer/share-content/put.ts +93 -0
- package/src/accesslayer/share-content/read.ts +124 -0
- package/src/accesslayer/share-content/types.ts +155 -0
- package/src/config/environment.ts +33 -0
- package/src/docker-entry.ts +19 -0
- package/src/helpers/share-content-auth/adapters.ts +48 -0
- package/src/helpers/share-content-auth/canonical.ts +91 -0
- package/src/helpers/share-content-auth/claims.ts +164 -0
- package/src/helpers/share-content-auth/config.ts +160 -0
- package/src/helpers/share-content-auth/index.ts +59 -0
- package/src/helpers/share-content-auth/token.ts +148 -0
- package/src/helpers/share-content-auth/types.ts +195 -0
- package/src/helpers/share-content-auth/verifier.ts +220 -0
- package/src/models/ShareContent.ts +198 -0
- package/src/models/index.ts +1 -0
- package/src/share-content/config.ts +199 -0
- package/src/share-content/index.ts +5 -0
- package/src/share-content/plugin.ts +317 -0
- package/src/share-content/production.ts +107 -0
- package/src/share-content/runtime.ts +90 -0
- package/src/share-content/types.ts +59 -0
- package/test/share-content-auth.spec.ts +1161 -0
- package/test/share-content-config.spec.ts +252 -0
- package/test/share-content-didkit.spec.ts +411 -0
- package/test/share-content-endpoint.spec.ts +825 -0
- package/test/share-content-redis-setup.ts +18 -0
- package/test/share-content.spec.ts +681 -0
- package/vitest.config.ts +1 -0
- package/vitest.share-content-config-unit.config.ts +8 -0
- package/vitest.share-content-endpoint.config.ts +21 -0
- package/vitest.share-content.config.ts +19 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type ShareEnvelope, type ShareOwnerRecovery } from '@learncard/types';
|
|
3
|
+
export declare const SHARE_CONTENT_COLLECTION = "share_content";
|
|
4
|
+
export declare const ShareContentNamespaceValidator: z.ZodString;
|
|
5
|
+
export type ShareContentNamespace = z.infer<typeof ShareContentNamespaceValidator>;
|
|
6
|
+
export declare const ShareContentOwnerProfileIdValidator: z.ZodString;
|
|
7
|
+
export type ShareContentOwnerProfileId = z.infer<typeof ShareContentOwnerProfileIdValidator>;
|
|
8
|
+
export declare const ShareContentObjectIdValidator: z.ZodString;
|
|
9
|
+
export type ShareContentObjectId = z.infer<typeof ShareContentObjectIdValidator>;
|
|
10
|
+
export declare const ShareContentOperationIdValidator: z.ZodString;
|
|
11
|
+
export type ShareContentOperationId = z.infer<typeof ShareContentOperationIdValidator>;
|
|
12
|
+
export declare const ShareContentVersionValidator: z.ZodNumber & z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>;
|
|
13
|
+
export type ShareContentContentVersion = z.infer<typeof ShareContentVersionValidator>;
|
|
14
|
+
export declare const ShareContentHashValidator: z.ZodString;
|
|
15
|
+
export type ShareContentHash = z.infer<typeof ShareContentHashValidator>;
|
|
16
|
+
export declare const ShareContentOwnerRecoveryValidator: z.ZodObject<{
|
|
17
|
+
protected: z.ZodString;
|
|
18
|
+
iv: z.ZodString;
|
|
19
|
+
ciphertext: z.ZodString;
|
|
20
|
+
tag: z.ZodString;
|
|
21
|
+
aad: z.ZodOptional<z.ZodString>;
|
|
22
|
+
recipients: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
23
|
+
header: z.ZodObject<{
|
|
24
|
+
alg: z.ZodString;
|
|
25
|
+
iv: z.ZodString;
|
|
26
|
+
tag: z.ZodString;
|
|
27
|
+
epk: z.ZodOptional<z.ZodObject<{
|
|
28
|
+
kty: z.ZodOptional<z.ZodString>;
|
|
29
|
+
crv: z.ZodOptional<z.ZodString>;
|
|
30
|
+
x: z.ZodOptional<z.ZodString>;
|
|
31
|
+
y: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
32
|
+
n: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
33
|
+
d: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
34
|
+
}, z.core.$strip>>;
|
|
35
|
+
kid: z.ZodOptional<z.ZodString>;
|
|
36
|
+
apv: z.ZodOptional<z.ZodString>;
|
|
37
|
+
apu: z.ZodOptional<z.ZodString>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
encrypted_key: z.ZodString;
|
|
40
|
+
}, z.core.$strip>>>;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
export type ShareContentOwnerRecovery = z.infer<typeof ShareContentOwnerRecoveryValidator>;
|
|
43
|
+
export declare const ShareContentObjectBindingValidator: z.ZodObject<{
|
|
44
|
+
namespace: z.ZodString;
|
|
45
|
+
ownerProfileId: z.ZodString;
|
|
46
|
+
shareId: z.ZodString;
|
|
47
|
+
contentVersion: z.ZodNumber & z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>;
|
|
48
|
+
objectId: z.ZodString;
|
|
49
|
+
operationId: z.ZodString;
|
|
50
|
+
}, z.core.$strict>;
|
|
51
|
+
export type ShareContentObjectBinding = z.infer<typeof ShareContentObjectBindingValidator>;
|
|
52
|
+
export declare const ShareContentPutInputValidator: z.ZodObject<{
|
|
53
|
+
namespace: z.ZodString;
|
|
54
|
+
ownerProfileId: z.ZodString;
|
|
55
|
+
shareId: z.ZodString;
|
|
56
|
+
contentVersion: z.ZodNumber & z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>;
|
|
57
|
+
objectId: z.ZodString;
|
|
58
|
+
operationId: z.ZodString;
|
|
59
|
+
envelope: z.ZodObject<{
|
|
60
|
+
v: z.ZodLiteral<1>;
|
|
61
|
+
alg: z.ZodLiteral<"A256GCM">;
|
|
62
|
+
iv: z.ZodString;
|
|
63
|
+
ct: z.ZodString;
|
|
64
|
+
}, z.core.$strict>;
|
|
65
|
+
ownerEncryptedRecovery: z.ZodObject<{
|
|
66
|
+
protected: z.ZodString;
|
|
67
|
+
iv: z.ZodString;
|
|
68
|
+
ciphertext: z.ZodString;
|
|
69
|
+
tag: z.ZodString;
|
|
70
|
+
aad: z.ZodOptional<z.ZodString>;
|
|
71
|
+
recipients: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
72
|
+
header: z.ZodObject<{
|
|
73
|
+
alg: z.ZodString;
|
|
74
|
+
iv: z.ZodString;
|
|
75
|
+
tag: z.ZodString;
|
|
76
|
+
epk: z.ZodOptional<z.ZodObject<{
|
|
77
|
+
kty: z.ZodOptional<z.ZodString>;
|
|
78
|
+
crv: z.ZodOptional<z.ZodString>;
|
|
79
|
+
x: z.ZodOptional<z.ZodString>;
|
|
80
|
+
y: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
81
|
+
n: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
82
|
+
d: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
83
|
+
}, z.core.$strip>>;
|
|
84
|
+
kid: z.ZodOptional<z.ZodString>;
|
|
85
|
+
apv: z.ZodOptional<z.ZodString>;
|
|
86
|
+
apu: z.ZodOptional<z.ZodString>;
|
|
87
|
+
}, z.core.$strip>;
|
|
88
|
+
encrypted_key: z.ZodString;
|
|
89
|
+
}, z.core.$strip>>>;
|
|
90
|
+
}, z.core.$strict>;
|
|
91
|
+
}, z.core.$strict>;
|
|
92
|
+
export type ShareContentPutInput = z.infer<typeof ShareContentPutInputValidator>;
|
|
93
|
+
export declare const computeShareContentHash: (input: ShareContentPutInput) => string;
|
|
94
|
+
export declare const computeSharePayloadHash: (input: Pick<ShareContentPutInput, "envelope" | "ownerEncryptedRecovery">) => string;
|
|
95
|
+
export declare const measureShareContentPayload: (input: Pick<ShareContentPutInput, "envelope" | "ownerEncryptedRecovery">) => {
|
|
96
|
+
ciphertextBytes: number;
|
|
97
|
+
recoveryBytes: number;
|
|
98
|
+
};
|
|
99
|
+
export declare const MongoShareContentActiveValidator: z.ZodObject<{
|
|
100
|
+
namespace: z.ZodString;
|
|
101
|
+
ownerProfileId: z.ZodString;
|
|
102
|
+
shareId: z.ZodString;
|
|
103
|
+
contentVersion: z.ZodNumber & z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>;
|
|
104
|
+
objectId: z.ZodString;
|
|
105
|
+
operationId: z.ZodString;
|
|
106
|
+
kind: z.ZodLiteral<"active">;
|
|
107
|
+
contentHash: z.ZodString;
|
|
108
|
+
payloadHash: z.ZodString;
|
|
109
|
+
envelope: z.ZodObject<{
|
|
110
|
+
v: z.ZodLiteral<1>;
|
|
111
|
+
alg: z.ZodLiteral<"A256GCM">;
|
|
112
|
+
iv: z.ZodString;
|
|
113
|
+
ct: z.ZodString;
|
|
114
|
+
}, z.core.$strict>;
|
|
115
|
+
ownerEncryptedRecovery: z.ZodObject<{
|
|
116
|
+
protected: z.ZodString;
|
|
117
|
+
iv: z.ZodString;
|
|
118
|
+
ciphertext: z.ZodString;
|
|
119
|
+
tag: z.ZodString;
|
|
120
|
+
aad: z.ZodOptional<z.ZodString>;
|
|
121
|
+
recipients: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
122
|
+
header: z.ZodObject<{
|
|
123
|
+
alg: z.ZodString;
|
|
124
|
+
iv: z.ZodString;
|
|
125
|
+
tag: z.ZodString;
|
|
126
|
+
epk: z.ZodOptional<z.ZodObject<{
|
|
127
|
+
kty: z.ZodOptional<z.ZodString>;
|
|
128
|
+
crv: z.ZodOptional<z.ZodString>;
|
|
129
|
+
x: z.ZodOptional<z.ZodString>;
|
|
130
|
+
y: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
131
|
+
n: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
132
|
+
d: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
133
|
+
}, z.core.$strip>>;
|
|
134
|
+
kid: z.ZodOptional<z.ZodString>;
|
|
135
|
+
apv: z.ZodOptional<z.ZodString>;
|
|
136
|
+
apu: z.ZodOptional<z.ZodString>;
|
|
137
|
+
}, z.core.$strip>;
|
|
138
|
+
encrypted_key: z.ZodString;
|
|
139
|
+
}, z.core.$strip>>>;
|
|
140
|
+
}, z.core.$strict>;
|
|
141
|
+
ciphertextBytes: z.ZodNumber;
|
|
142
|
+
recoveryBytes: z.ZodNumber;
|
|
143
|
+
createdAt: z.ZodDate;
|
|
144
|
+
}, z.core.$strict>;
|
|
145
|
+
export type MongoShareContentActive = z.infer<typeof MongoShareContentActiveValidator>;
|
|
146
|
+
export declare const MongoShareContentTombstoneValidator: z.ZodObject<{
|
|
147
|
+
namespace: z.ZodString;
|
|
148
|
+
ownerProfileId: z.ZodString;
|
|
149
|
+
shareId: z.ZodString;
|
|
150
|
+
contentVersion: z.ZodNumber & z.ZodType<number, number, z.core.$ZodTypeInternals<number, number>>;
|
|
151
|
+
objectId: z.ZodString;
|
|
152
|
+
operationId: z.ZodString;
|
|
153
|
+
kind: z.ZodLiteral<"tombstone">;
|
|
154
|
+
contentHash: z.ZodOptional<z.ZodString>;
|
|
155
|
+
deletedAt: z.ZodDate;
|
|
156
|
+
}, z.core.$strict>;
|
|
157
|
+
export type MongoShareContentTombstone = z.infer<typeof MongoShareContentTombstoneValidator>;
|
|
158
|
+
export type MongoShareContentDocument = MongoShareContentActive | MongoShareContentTombstone;
|
|
159
|
+
export type { ShareEnvelope, ShareOwnerRecovery };
|
|
160
|
+
//# sourceMappingURL=ShareContent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShareContent.d.ts","sourceRoot":"","sources":["../../src/models/ShareContent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAQH,KAAK,aAAa,EAClB,KAAK,kBAAkB,EAE1B,MAAM,kBAAkB,CAAC;AAkC1B,eAAO,MAAM,wBAAwB,kBAAkB,CAAC;AAWxD,eAAO,MAAM,8BAA8B,aAAgC,CAAC;AAC5E,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAGnF,eAAO,MAAM,mCAAmC,aAAqC,CAAC;AACtF,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAC;AAG7F,eAAO,MAAM,6BAA6B,aAA+B,CAAC;AAC1E,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAGjF,eAAO,MAAM,gCAAgC,aAAkC,CAAC;AAChF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAC;AAGvF,eAAO,MAAM,4BAA4B,mFAEmD,CAAC;AAC7F,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAGtF,eAAO,MAAM,yBAAyB,aAEyD,CAAC;AAChG,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAQzE,eAAO,MAAM,kCAAkC;;;;;;;;;;;;;;;;;;;;;;;;;kBAG9C,CAAC;AACF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAM3F,eAAO,MAAM,kCAAkC;;;;;;;kBASlC,CAAC;AACd,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAM3F,eAAO,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAG/B,CAAC;AACZ,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,6BAA6B,CAAC,CAAC;AAQjF,eAAO,MAAM,uBAAuB,GAAI,OAAO,oBAAoB,KAAG,MAUhE,CAAC;AAGP,eAAO,MAAM,uBAAuB,GAChC,OAAO,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,wBAAwB,CAAC,KACzE,MAIG,CAAC;AAMP,eAAO,MAAM,0BAA0B,GACnC,OAAO,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,wBAAwB,CAAC,KACzE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAWlD,CAAC;AAGF,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBASlC,CAAC;AACZ,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gCAAgC,CAAC,CAAC;AAOvF,eAAO,MAAM,mCAAmC;;;;;;;;;;kBAIrC,CAAC;AACZ,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mCAAmC,CAAC,CAAC;AAG7F,MAAM,MAAM,yBAAyB,GAAG,uBAAuB,GAAG,0BAA0B,CAAC;AAG7F,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/models/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/models/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/models/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,QAAQ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ShareContentTrustConfig } from '../helpers/share-content-auth';
|
|
2
|
+
import { type ShareContentPluginOptions } from './types';
|
|
3
|
+
export type ShareContentNamespacePolicy = {
|
|
4
|
+
isAllowed: (serviceDid: string, namespace: string) => boolean;
|
|
5
|
+
allowedNamespacesFor: (serviceDid: string) => readonly string[];
|
|
6
|
+
};
|
|
7
|
+
export type ResolvedShareContentConfig = {
|
|
8
|
+
status: 'disabled';
|
|
9
|
+
} | {
|
|
10
|
+
status: 'invalid';
|
|
11
|
+
errors: readonly string[];
|
|
12
|
+
} | {
|
|
13
|
+
status: 'enabled';
|
|
14
|
+
trustConfig: ShareContentTrustConfig;
|
|
15
|
+
namespacePolicy: ShareContentNamespacePolicy;
|
|
16
|
+
};
|
|
17
|
+
export declare const parseShareContentNamespaceBindings: (value: unknown) => Map<string, readonly string[]> | null;
|
|
18
|
+
export declare const resolveShareContentConfig: (raw: unknown) => ResolvedShareContentConfig;
|
|
19
|
+
export declare const getShareContentRawConfig: (environment: {
|
|
20
|
+
SHARE_CONTENT_AUDIENCE?: string;
|
|
21
|
+
SHARE_CONTENT_SERVICE_DIDS?: string;
|
|
22
|
+
SHARE_CONTENT_VERIFICATION_METHODS?: string;
|
|
23
|
+
SHARE_CONTENT_NAMESPACE_BINDINGS?: string;
|
|
24
|
+
}) => Record<string, unknown>;
|
|
25
|
+
export declare const toShareContentPluginOptions: (config: Extract<ResolvedShareContentConfig, {
|
|
26
|
+
status: "enabled";
|
|
27
|
+
}>, dependencies: Pick<ShareContentPluginOptions, "verifier" | "repository">) => ShareContentPluginOptions;
|
|
28
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/share-content/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,uBAAuB,EAC/B,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAmC,KAAK,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAY1F,MAAM,MAAM,2BAA2B,GAAG;IAEtC,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAE9D,oBAAoB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,CAAC;CACnE,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAChC;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAChD;IACI,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,uBAAuB,CAAC;IACrC,eAAe,EAAE,2BAA2B,CAAC;CAChD,CAAC;AAwBR,eAAO,MAAM,kCAAkC,GAC3C,OAAO,OAAO,KACf,GAAG,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,GAAG,IA0BnC,CAAC;AAwBF,eAAO,MAAM,yBAAyB,GAAI,KAAK,OAAO,KAAG,0BAgExD,CAAC;AAMF,eAAO,MAAM,wBAAwB,GAAI,aAAa;IAClD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAC5C,gCAAgC,CAAC,EAAE,MAAM,CAAC;CAC7C,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAKxB,CAAC;AAGH,eAAO,MAAM,2BAA2B,GACpC,QAAQ,OAAO,CAAC,0BAA0B,EAAE;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,CAAC,EAClE,cAAc,IAAI,CAAC,yBAAyB,EAAE,UAAU,GAAG,YAAY,CAAC,KACzE,yBAID,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/share-content/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
+
import { type ShareContentPluginOptions } from './types';
|
|
3
|
+
export declare const shareContentFastifyPlugin: FastifyPluginAsync<ShareContentPluginOptions>;
|
|
4
|
+
export default shareContentFastifyPlugin;
|
|
5
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/share-content/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,kBAAkB,EAAgC,MAAM,SAAS,CAAC;AAejG,OAAO,EAGH,KAAK,yBAAyB,EAEjC,MAAM,SAAS,CAAC;AAgPjB,eAAO,MAAM,yBAAyB,EAAE,kBAAkB,CAAC,yBAAyB,CAsDnF,CAAC;AAEF,eAAe,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ShareContentRepository } from '../accesslayer/share-content';
|
|
2
|
+
import { type ReplayStore } from '../helpers/share-content-auth';
|
|
3
|
+
import { type ShareContentRuntime } from './runtime';
|
|
4
|
+
export type ShareContentReplayStoreHandle = {
|
|
5
|
+
store: ReplayStore;
|
|
6
|
+
close: () => Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
export declare const createShareContentRedisReplayStore: (options: {
|
|
9
|
+
host: string;
|
|
10
|
+
port: number;
|
|
11
|
+
}) => ShareContentReplayStoreHandle;
|
|
12
|
+
export declare const createMongoShareContentRepository: () => Promise<ShareContentRepository>;
|
|
13
|
+
export declare const createShareContentRuntimeFromEnvironment: () => Promise<ShareContentRuntime>;
|
|
14
|
+
export declare const assertShareContentEnvironmentConfiguration: () => void;
|
|
15
|
+
//# sourceMappingURL=production.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"production.d.ts","sourceRoot":"","sources":["../../src/share-content/production.ts"],"names":[],"mappings":"AAGA,OAAO,EAGH,KAAK,sBAAsB,EAC9B,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAgC,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAI7F,OAAO,EAA4B,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAY/E,MAAM,MAAM,6BAA6B,GAAG;IACxC,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,eAAO,MAAM,kCAAkC,GAAI,SAAS;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAChB,KAAG,6BAkCH,CAAC;AAEF,eAAO,MAAM,iCAAiC,QAAa,OAAO,CAAC,sBAAsB,CAIxF,CAAC;AAcF,eAAO,MAAM,wCAAwC,QAAa,OAAO,CAAC,mBAAmB,CAKvF,CAAC;AAOP,eAAO,MAAM,0CAA0C,QAAO,IAQ7D,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
import type { ShareContentRepository } from '../accesslayer/share-content';
|
|
3
|
+
import { type LearnCardLike, type ReplayStore } from '../helpers/share-content-auth';
|
|
4
|
+
import { type ShareContentPluginOptions } from './types';
|
|
5
|
+
export type ShareContentRuntimeDependencies = {
|
|
6
|
+
getLearnCard: () => Promise<LearnCardLike>;
|
|
7
|
+
createReplayStore: () => {
|
|
8
|
+
store: ReplayStore;
|
|
9
|
+
close: () => Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
getRepository: () => Promise<ShareContentRepository>;
|
|
12
|
+
};
|
|
13
|
+
export type EnabledShareContentRuntime = {
|
|
14
|
+
enabled: true;
|
|
15
|
+
pluginOptions: ShareContentPluginOptions;
|
|
16
|
+
close: () => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
export type ShareContentRuntime = {
|
|
19
|
+
enabled: false;
|
|
20
|
+
} | EnabledShareContentRuntime;
|
|
21
|
+
export declare const buildShareContentRuntime: (rawConfig: unknown, dependencies: ShareContentRuntimeDependencies) => Promise<ShareContentRuntime>;
|
|
22
|
+
export declare const registerShareContentRoutes: (fastify: FastifyInstance, runtime: EnabledShareContentRuntime) => Promise<void>;
|
|
23
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/share-content/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAGH,KAAK,aAAa,EAClB,KAAK,WAAW,EACnB,MAAM,6BAA6B,CAAC;AAIrC,OAAO,EAA+B,KAAK,yBAAyB,EAAE,MAAM,SAAS,CAAC;AAatF,MAAM,MAAM,+BAA+B,GAAG;IAE1C,YAAY,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAC;IAE3C,iBAAiB,EAAE,MAAM;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAE5E,aAAa,EAAE,MAAM,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACrC,OAAO,EAAE,IAAI,CAAC;IACd,aAAa,EAAE,yBAAyB,CAAC;IAEzC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,GAAG,0BAA0B,CAAC;AAElF,eAAO,MAAM,wBAAwB,GACjC,WAAW,OAAO,EAClB,cAAc,+BAA+B,KAC9C,OAAO,CAAC,mBAAmB,CAgC7B,CAAC;AAGF,eAAO,MAAM,0BAA0B,GACnC,SAAS,eAAe,EACxB,SAAS,0BAA0B,KACpC,OAAO,CAAC,IAAI,CAKd,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ShareContentRepository } from '../accesslayer/share-content';
|
|
2
|
+
import type { ReplayStore, ShareContentAuthorizationVerifier } from '../helpers/share-content-auth';
|
|
3
|
+
import type { ShareContentNamespacePolicy } from './config';
|
|
4
|
+
export declare const SHARE_CONTENT_PLUGIN_PREFIX = "/internal/share-content";
|
|
5
|
+
export type ShareContentRouteOperation = 'put' | 'get' | 'stat' | 'delete' | 'readRecovery';
|
|
6
|
+
export declare const SHARE_CONTENT_MAX_REQUEST_BYTES: number;
|
|
7
|
+
export declare const SHARE_CONTENT_ERROR_CODES: readonly ["UNAUTHORIZED", "INVALID_INPUT", "PAYLOAD_TOO_LARGE", "NOT_FOUND", "CONFLICT", "UNAVAILABLE"];
|
|
8
|
+
export type ShareContentErrorCode = (typeof SHARE_CONTENT_ERROR_CODES)[number];
|
|
9
|
+
export type ShareContentErrorBody = {
|
|
10
|
+
ok: false;
|
|
11
|
+
error: ShareContentErrorCode;
|
|
12
|
+
};
|
|
13
|
+
export type ShareContentPluginOptions = {
|
|
14
|
+
verifier: ShareContentAuthorizationVerifier;
|
|
15
|
+
repository: ShareContentRepository;
|
|
16
|
+
namespacePolicy: ShareContentNamespacePolicy;
|
|
17
|
+
maxRequestBytes?: number;
|
|
18
|
+
};
|
|
19
|
+
export type ShareContentReplayStoreFactory = () => {
|
|
20
|
+
store: ReplayStore;
|
|
21
|
+
close: () => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/share-content/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,iCAAiC,EAAE,MAAM,6BAA6B,CAAC;AAElG,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,UAAU,CAAC;AAa5D,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAGrE,MAAM,MAAM,0BAA0B,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,cAAc,CAAC;AAG5F,eAAO,MAAM,+BAA+B,QAAc,CAAC;AAM3D,eAAO,MAAM,yBAAyB,yGAO5B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/E,MAAM,MAAM,qBAAqB,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,qBAAqB,CAAA;CAAE,CAAC;AAOhF,MAAM,MAAM,yBAAyB,GAAG;IACpC,QAAQ,EAAE,iCAAiC,CAAC;IAC5C,UAAU,EAAE,sBAAsB,CAAC;IACnC,eAAe,EAAE,2BAA2B,CAAC;IAE7C,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAGF,MAAM,MAAM,8BAA8B,GAAG,MAAM;IAC/C,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC"}
|
package/lambda.ts
CHANGED
|
@@ -2,6 +2,7 @@ import serverlessHttp from 'serverless-http';
|
|
|
2
2
|
import type { Context, APIGatewayProxyResultV2, APIGatewayProxyEventV2 } from 'aws-lambda';
|
|
3
3
|
import { awsLambdaRequestHandler } from '@trpc/server/adapters/aws-lambda';
|
|
4
4
|
import * as Sentry from '@sentry/serverless';
|
|
5
|
+
import Fastify, { type FastifyInstance } from 'fastify';
|
|
5
6
|
|
|
6
7
|
import app from './src/openapi';
|
|
7
8
|
import { appRouter, createContext } from './src/app';
|
|
@@ -13,6 +14,11 @@ import {
|
|
|
13
14
|
} from './src/helpers/sentry.helpers';
|
|
14
15
|
import { environment } from './src/config/environment';
|
|
15
16
|
import { toServerlessApplication } from './src/helpers/serverlessApplication';
|
|
17
|
+
import {
|
|
18
|
+
assertShareContentEnvironmentConfiguration,
|
|
19
|
+
createShareContentRuntimeFromEnvironment,
|
|
20
|
+
registerShareContentRoutes,
|
|
21
|
+
} from './src/share-content';
|
|
16
22
|
|
|
17
23
|
Sentry.AWSLambda.init({
|
|
18
24
|
dsn: environment.SENTRY_DSN,
|
|
@@ -28,6 +34,61 @@ Sentry.AWSLambda.init({
|
|
|
28
34
|
],
|
|
29
35
|
});
|
|
30
36
|
|
|
37
|
+
// Fail the cold start on an enabled-but-invalid LC-2187 share-content
|
|
38
|
+
// configuration; a disabled configuration is a no-op.
|
|
39
|
+
assertShareContentEnvironmentConfiguration();
|
|
40
|
+
|
|
41
|
+
let shareContentProxyPromise: Promise<ReturnType<typeof serverlessHttp> | null> | null = null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lazy per-container boot for the LC-2187 service routes. Resolving the runtime
|
|
45
|
+
* awaits repository index initialization and opens Redis only when enabled, so a
|
|
46
|
+
* disabled deployment installs nothing and returns 404 for every internal path.
|
|
47
|
+
*/
|
|
48
|
+
const getShareContentProxy = async (): Promise<ReturnType<typeof serverlessHttp> | null> => {
|
|
49
|
+
if (!shareContentProxyPromise) {
|
|
50
|
+
shareContentProxyPromise = (async () => {
|
|
51
|
+
const runtime = await createShareContentRuntimeFromEnvironment();
|
|
52
|
+
|
|
53
|
+
if (!runtime.enabled) return null;
|
|
54
|
+
|
|
55
|
+
const shareContentApp: FastifyInstance = Fastify({ maxParamLength: 5000 });
|
|
56
|
+
await registerShareContentRoutes(shareContentApp, runtime);
|
|
57
|
+
await shareContentApp.ready();
|
|
58
|
+
|
|
59
|
+
return serverlessHttp(toServerlessApplication(shareContentApp.server));
|
|
60
|
+
})();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return shareContentProxyPromise;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const shareContentHandler = async (
|
|
67
|
+
event: APIGatewayProxyEventV2,
|
|
68
|
+
context: Context
|
|
69
|
+
): Promise<APIGatewayProxyResultV2> => {
|
|
70
|
+
let proxy: ReturnType<typeof serverlessHttp> | null;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
proxy = await getShareContentProxy();
|
|
74
|
+
} catch {
|
|
75
|
+
// Never return a raw boot error. Allow a later invocation to retry.
|
|
76
|
+
shareContentProxyPromise = null;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
statusCode: 503,
|
|
80
|
+
headers: { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' },
|
|
81
|
+
body: JSON.stringify({ ok: false, error: 'UNAVAILABLE' }),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!proxy) {
|
|
86
|
+
return { statusCode: 404, headers: { 'Cache-Control': 'no-store' }, body: '' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return proxy(event, context);
|
|
90
|
+
};
|
|
91
|
+
|
|
31
92
|
export const swaggerUiHandler = serverlessHttp(toServerlessApplication(app), {
|
|
32
93
|
basePath: '/docs',
|
|
33
94
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/learn-cloud-service",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.35",
|
|
4
4
|
"description": "",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.12 <25"
|
|
@@ -68,19 +68,19 @@
|
|
|
68
68
|
"@fastify/cors": "^11.2.0",
|
|
69
69
|
"@fastify/rate-limit": "^10.2.0",
|
|
70
70
|
"@fastify/static": "^10.1.1",
|
|
71
|
-
"@learncard/core": "9.4.
|
|
72
|
-
"@learncard/crypto-plugin": "1.1.
|
|
73
|
-
"@learncard/did-web-plugin": "1.1.
|
|
74
|
-
"@learncard/didkey-plugin": "1.1.
|
|
75
|
-
"@learncard/didkit-plugin": "^1.10.
|
|
76
|
-
"@learncard/didkit-plugin-node": "0.3.
|
|
77
|
-
"@learncard/encryption-plugin": "1.1.
|
|
78
|
-
"@learncard/expiration-plugin": "1.2.
|
|
79
|
-
"@learncard/helpers": "1.6.
|
|
80
|
-
"@learncard/learn-card-plugin": "1.2.
|
|
81
|
-
"@learncard/types": "5.
|
|
82
|
-
"@learncard/vc-plugin": "1.6.
|
|
83
|
-
"@learncard/vc-templates-plugin": "1.1.
|
|
71
|
+
"@learncard/core": "9.4.37",
|
|
72
|
+
"@learncard/crypto-plugin": "1.1.37",
|
|
73
|
+
"@learncard/did-web-plugin": "1.1.37",
|
|
74
|
+
"@learncard/didkey-plugin": "1.1.37",
|
|
75
|
+
"@learncard/didkit-plugin": "^1.10.2",
|
|
76
|
+
"@learncard/didkit-plugin-node": "0.3.2",
|
|
77
|
+
"@learncard/encryption-plugin": "1.1.37",
|
|
78
|
+
"@learncard/expiration-plugin": "1.2.37",
|
|
79
|
+
"@learncard/helpers": "1.6.1",
|
|
80
|
+
"@learncard/learn-card-plugin": "1.2.37",
|
|
81
|
+
"@learncard/types": "5.22.0",
|
|
82
|
+
"@learncard/vc-plugin": "1.6.3",
|
|
83
|
+
"@learncard/vc-templates-plugin": "1.1.37",
|
|
84
84
|
"@sentry/serverless": "7.61.0",
|
|
85
85
|
"@trpc/server": "11.8.0",
|
|
86
86
|
"@types/lodash": "^4.14.191",
|
package/serverless.yml
CHANGED
|
@@ -140,6 +140,13 @@ provider:
|
|
|
140
140
|
'Fn::GetAtt': [ElasticCacheCluster, RedisEndpoint.Address]
|
|
141
141
|
REDIS_PORT:
|
|
142
142
|
'Fn::GetAtt': [ElasticCacheCluster, RedisEndpoint.Port]
|
|
143
|
+
# LC-2187 service-only share-content routes. Require an explicit
|
|
144
|
+
# audience, service DID/key-method allowlists and
|
|
145
|
+
# per-service namespace bindings. No secret material is passed here.
|
|
146
|
+
SHARE_CONTENT_AUDIENCE: ${env:SHARE_CONTENT_AUDIENCE, ''}
|
|
147
|
+
SHARE_CONTENT_SERVICE_DIDS: ${env:SHARE_CONTENT_SERVICE_DIDS, ''}
|
|
148
|
+
SHARE_CONTENT_VERIFICATION_METHODS: ${env:SHARE_CONTENT_VERIFICATION_METHODS, ''}
|
|
149
|
+
SHARE_CONTENT_NAMESPACE_BINDINGS: ${env:SHARE_CONTENT_NAMESPACE_BINDINGS, ''}
|
|
143
150
|
tracing:
|
|
144
151
|
apiGateway: true
|
|
145
152
|
lambda: true
|
|
@@ -170,6 +177,24 @@ functions:
|
|
|
170
177
|
events:
|
|
171
178
|
- httpApi: ANY /api/{trpc+}
|
|
172
179
|
|
|
180
|
+
# LC-2187 service-only share-content transport. The handler returns 404
|
|
181
|
+
# for every path until configured with a
|
|
182
|
+
# complete configuration. Events are the exact reviewed operations; there is
|
|
183
|
+
# no wildcard/wildcard enumeration surface.
|
|
184
|
+
shareContent:
|
|
185
|
+
handler: lambda.shareContentHandler
|
|
186
|
+
vpc:
|
|
187
|
+
securityGroupIds:
|
|
188
|
+
- 'Fn::GetAtt': [ServerlessSecurityGroup, GroupId]
|
|
189
|
+
subnetIds:
|
|
190
|
+
- Ref: PrivateSubnetA
|
|
191
|
+
events:
|
|
192
|
+
- httpApi: POST /internal/share-content/put
|
|
193
|
+
- httpApi: POST /internal/share-content/get
|
|
194
|
+
- httpApi: POST /internal/share-content/stat
|
|
195
|
+
- httpApi: POST /internal/share-content/delete
|
|
196
|
+
- httpApi: POST /internal/share-content/readRecovery
|
|
197
|
+
|
|
173
198
|
didWeb:
|
|
174
199
|
handler: didWebLambda.didWebHandler
|
|
175
200
|
vpc:
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Collection } from 'mongodb';
|
|
2
|
+
|
|
3
|
+
import type { MongoShareContentDocument } from '@models';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MAX_SHARE_CONTENT_ATTEMPTS,
|
|
7
|
+
bindingMatchesDocument,
|
|
8
|
+
parseShareContentBinding,
|
|
9
|
+
shareContentBindingFilter,
|
|
10
|
+
shareContentIdentityFilter,
|
|
11
|
+
toTombstoneSummary,
|
|
12
|
+
} from './internal';
|
|
13
|
+
import type { ShareContentRepositoryResult, ShareContentTombstoneSummary } from './types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Atomically tombstone the exact object.
|
|
17
|
+
*
|
|
18
|
+
* The active -> tombstone transition is a single atomic `findOneAndUpdate` that
|
|
19
|
+
* also removes the ciphertext/recovery fields, so once it returns the payload is
|
|
20
|
+
* gone. If the identity does not exist yet, a tombstone is inserted with an
|
|
21
|
+
* atomic `$setOnInsert` upsert (delete-before-put), so a late concurrent put can
|
|
22
|
+
* never resurrect the identity. The loop only re-runs when a racing writer
|
|
23
|
+
* changed the document between the two atomic steps; active documents are
|
|
24
|
+
* otherwise immutable and tombstones are permanent, so it always settles.
|
|
25
|
+
*
|
|
26
|
+
* A binding mismatch (including another owner's object at the same identity)
|
|
27
|
+
* never modifies the stored document and returns `BINDING_MISMATCH`.
|
|
28
|
+
*/
|
|
29
|
+
export const deleteShareContent = async (
|
|
30
|
+
collection: Collection<MongoShareContentDocument>,
|
|
31
|
+
rawBinding: unknown
|
|
32
|
+
): Promise<ShareContentRepositoryResult<ShareContentTombstoneSummary>> => {
|
|
33
|
+
const binding = parseShareContentBinding(rawBinding);
|
|
34
|
+
|
|
35
|
+
if (binding === null) return { ok: false, error: 'INVALID_INPUT' };
|
|
36
|
+
|
|
37
|
+
const exactFilter = shareContentBindingFilter(binding);
|
|
38
|
+
const identityFilter = shareContentIdentityFilter(binding);
|
|
39
|
+
|
|
40
|
+
for (let attempt = 0; attempt < MAX_SHARE_CONTENT_ATTEMPTS; attempt += 1) {
|
|
41
|
+
// 1. Atomic active -> tombstone, dropping the payload in the same update.
|
|
42
|
+
const deletedAt = new Date();
|
|
43
|
+
|
|
44
|
+
const replaced = await collection.findOneAndUpdate(
|
|
45
|
+
{ ...exactFilter, kind: 'active' },
|
|
46
|
+
{
|
|
47
|
+
$set: { kind: 'tombstone', deletedAt },
|
|
48
|
+
$unset: {
|
|
49
|
+
payloadHash: '',
|
|
50
|
+
envelope: '',
|
|
51
|
+
ownerEncryptedRecovery: '',
|
|
52
|
+
ciphertextBytes: '',
|
|
53
|
+
recoveryBytes: '',
|
|
54
|
+
createdAt: '',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{ returnDocument: 'after' }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (replaced !== null) {
|
|
61
|
+
if (replaced.kind === 'tombstone') {
|
|
62
|
+
return { ok: true, value: toTombstoneSummary(replaced) };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Unreachable under the `kind: 'active'` filter; a racing writer
|
|
66
|
+
// changed the document. Re-classify rather than trust the shape.
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 2. Delete-before-put: create a permanent tombstone only if absent.
|
|
71
|
+
const inserted = await collection.updateOne(
|
|
72
|
+
identityFilter,
|
|
73
|
+
{
|
|
74
|
+
$setOnInsert: {
|
|
75
|
+
kind: 'tombstone',
|
|
76
|
+
namespace: binding.namespace,
|
|
77
|
+
ownerProfileId: binding.ownerProfileId,
|
|
78
|
+
shareId: binding.shareId,
|
|
79
|
+
contentVersion: binding.contentVersion,
|
|
80
|
+
objectId: binding.objectId,
|
|
81
|
+
operationId: binding.operationId,
|
|
82
|
+
deletedAt,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{ upsert: true }
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (inserted.upsertedCount === 1) {
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
value: {
|
|
92
|
+
kind: 'tombstone',
|
|
93
|
+
namespace: binding.namespace,
|
|
94
|
+
ownerProfileId: binding.ownerProfileId,
|
|
95
|
+
shareId: binding.shareId,
|
|
96
|
+
contentVersion: binding.contentVersion,
|
|
97
|
+
objectId: binding.objectId,
|
|
98
|
+
operationId: binding.operationId,
|
|
99
|
+
deletedAt,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 3. The identity exists but was not replaced: classify and fail closed.
|
|
105
|
+
const existing = await collection.findOne(identityFilter);
|
|
106
|
+
|
|
107
|
+
if (existing === null) continue;
|
|
108
|
+
|
|
109
|
+
if (existing.kind === 'tombstone') {
|
|
110
|
+
return bindingMatchesDocument(existing, binding)
|
|
111
|
+
? { ok: true, value: toTombstoneSummary(existing) }
|
|
112
|
+
: { ok: false, error: 'BINDING_MISMATCH' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!bindingMatchesDocument(existing, binding)) {
|
|
116
|
+
return { ok: false, error: 'BINDING_MISMATCH' };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Exact active document: a concurrent writer slipped in between the two
|
|
120
|
+
// atomic steps; retry the replacement.
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { ok: false, error: 'CONFLICT' };
|
|
124
|
+
};
|