@serve.zone/interfaces 21.1.0 → 23.0.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/changelog.md +23 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/appstore/types.d.ts +31 -16
- package/dist_ts/appstore/types.js +208 -11
- package/dist_ts/data/coremail.d.ts +83 -9
- package/dist_ts/data/coremail.js +67 -1
- package/dist_ts/data/coremail.runtime.d.ts +27 -1
- package/dist_ts/data/coremail.runtime.js +800 -36
- package/dist_ts/data/deploymentoperation.js +6 -20
- package/dist_ts/data/deploymentpreflight.d.ts +2 -6
- package/dist_ts/data/deploymentpreflight.js +1 -1
- package/dist_ts/data/hostedapp.d.ts +40 -14
- package/dist_ts/data/hostedapp.js +90 -1
- package/dist_ts/data/immutableimage.d.ts +30 -1
- package/dist_ts/data/immutableimage.js +103 -1
- package/dist_ts/data/index.d.ts +0 -2
- package/dist_ts/data/index.js +1 -3
- package/dist_ts/data/secret.d.ts +186 -13
- package/dist_ts/data/secret.js +543 -45
- package/dist_ts/data/service.d.ts +0 -39
- package/dist_ts/data/service.js +1 -77
- package/dist_ts/data/settings.d.ts +62 -43
- package/dist_ts/data/settings.js +34 -2
- package/dist_ts/platform/storage.d.ts +13 -14
- package/dist_ts/platform/storage.js +1 -1
- package/dist_ts/platform/storagemigration.d.ts +8 -8
- package/dist_ts/platform/storagemigration.golden.d.ts +2 -2
- package/dist_ts/platform/storagemigration.golden.js +19 -88
- package/dist_ts/platform/storagemigration.js +46 -58
- package/dist_ts/platform/types.d.ts +5 -19
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/requests/coremail.d.ts +12 -5
- package/dist_ts/requests/hostedapp.d.ts +12 -9
- package/dist_ts/requests/hostedapp.js +38 -1
- package/dist_ts/requests/index.d.ts +1 -3
- package/dist_ts/requests/index.js +2 -4
- package/dist_ts/requests/platform.d.ts +0 -1
- package/dist_ts/requests/secret.d.ts +23 -17
- package/dist_ts/requests/secret.js +161 -2
- package/dist_ts/requests/service.d.ts +0 -13
- package/dist_ts/requests/settings.d.ts +3 -3
- package/dist_ts/runtime.d.ts +78 -26
- package/dist_ts/runtime.js +219 -19
- package/package.json +2 -1
- package/readme.md +163 -38
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/appstore/types.ts +251 -25
- package/ts/data/coremail.runtime.ts +1212 -38
- package/ts/data/coremail.ts +120 -8
- package/ts/data/deploymentoperation.ts +7 -31
- package/ts/data/deploymentpreflight.ts +2 -5
- package/ts/data/hostedapp.ts +144 -18
- package/ts/data/immutableimage.ts +150 -1
- package/ts/data/index.ts +0 -2
- package/ts/data/secret.ts +775 -53
- package/ts/data/service.ts +0 -118
- package/ts/data/settings.ts +70 -80
- package/ts/platform/storage.ts +15 -16
- package/ts/platform/storagemigration.golden.ts +18 -95
- package/ts/platform/storagemigration.ts +57 -87
- package/ts/platform/types.ts +6 -19
- package/ts/plugins.ts +2 -0
- package/ts/requests/coremail.ts +13 -4
- package/ts/requests/hostedapp.ts +50 -9
- package/ts/requests/index.ts +0 -4
- package/ts/requests/platform.ts +0 -1
- package/ts/requests/secret.ts +204 -9
- package/ts/requests/service.ts +0 -16
- package/ts/requests/settings.ts +3 -3
- package/ts/runtime.ts +314 -41
- package/dist_ts/data/secretbundle.d.ts +0 -57
- package/dist_ts/data/secretbundle.js +0 -2
- package/dist_ts/data/secretgroup.d.ts +0 -52
- package/dist_ts/data/secretgroup.js +0 -2
- package/dist_ts/requests/secretbundle.d.ts +0 -75
- package/dist_ts/requests/secretbundle.js +0 -4
- package/dist_ts/requests/secretgroup.d.ts +0 -52
- package/dist_ts/requests/secretgroup.js +0 -4
- package/ts/data/secretbundle.ts +0 -65
- package/ts/data/secretgroup.ts +0 -58
- package/ts/requests/secretbundle.ts +0 -103
- package/ts/requests/secretgroup.ts +0 -74
package/ts/data/coremail.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
export type TCoreMailSha256 = `sha256:${string}`;
|
|
11
11
|
|
|
12
12
|
export type TCoreMailCapability = 'outbound' | 'inbound';
|
|
13
|
-
export type TCoreMailBindingState = 'active' | 'disabled';
|
|
13
|
+
export type TCoreMailBindingState = 'active' | 'draining' | 'disabled';
|
|
14
14
|
export type TCoreMailCredentialState = 'current' | 'retiring';
|
|
15
15
|
export type TCoreMailCredentialVerifierFormat = 'argon2id-v1';
|
|
16
16
|
export type TCoreMailPartKind = 'text' | 'html' | 'attachment';
|
|
@@ -30,6 +30,16 @@ export type TCoreMailInboundDeliveryState = 'pending' | 'fetching' | 'fetched' |
|
|
|
30
30
|
export type TCoreMailInboundAckOutcome = 'processed' | 'discarded';
|
|
31
31
|
export type TCoreMailRecipientAction = 'accept' | 'reject' | 'defer';
|
|
32
32
|
export type TCoreMailReconciliationState = 'applying' | 'ready' | 'failed';
|
|
33
|
+
export type TCoreMailWorkloadOperation =
|
|
34
|
+
| 'coreMailPrepareOutboundSubmission'
|
|
35
|
+
| 'coreMailPrepareOutboundPartUpload'
|
|
36
|
+
| 'coreMailCompleteOutboundPartUpload'
|
|
37
|
+
| 'coreMailFinalizeOutboundSubmission'
|
|
38
|
+
| 'coreMailGetOutboundSubmission'
|
|
39
|
+
| 'coreMailListInboundDeliveries'
|
|
40
|
+
| 'coreMailPrepareInboundFetch'
|
|
41
|
+
| 'coreMailCompleteInboundFetch'
|
|
42
|
+
| 'coreMailAcknowledgeInboundDelivery';
|
|
33
43
|
|
|
34
44
|
export type TCoreMailErrorCode =
|
|
35
45
|
| 'AUTHENTICATION_FAILED'
|
|
@@ -43,6 +53,7 @@ export type TCoreMailErrorCode =
|
|
|
43
53
|
| 'INVALID_RECIPIENT'
|
|
44
54
|
| 'INVALID_HEADER'
|
|
45
55
|
| 'PAYLOAD_LIMIT_EXCEEDED'
|
|
56
|
+
| 'QUOTA_EXCEEDED'
|
|
46
57
|
| 'IDEMPOTENCY_CONFLICT'
|
|
47
58
|
| 'STATE_CONFLICT'
|
|
48
59
|
| 'TRANSFER_GRANT_EXPIRED'
|
|
@@ -52,6 +63,9 @@ export type TCoreMailErrorCode =
|
|
|
52
63
|
| 'TRANSFER_ENCODING_UNSUPPORTED'
|
|
53
64
|
| 'OBJECT_INTEGRITY_CONFLICT'
|
|
54
65
|
| 'DELIVERY_NOT_FETCHED'
|
|
66
|
+
| 'DELIVERY_DEFERRED'
|
|
67
|
+
| 'DELIVERY_FAILED'
|
|
68
|
+
| 'DELIVERY_DEAD_LETTERED'
|
|
55
69
|
| 'GATEWAY_UNAVAILABLE'
|
|
56
70
|
| 'RECONCILIATION_FENCE_MISMATCH';
|
|
57
71
|
|
|
@@ -111,6 +125,7 @@ export interface ICoreMailTransferGrant {
|
|
|
111
125
|
sha256: TCoreMailSha256;
|
|
112
126
|
lengthBytes: number;
|
|
113
127
|
contentType: string;
|
|
128
|
+
issuedAt: number;
|
|
114
129
|
expiresAt: number;
|
|
115
130
|
}
|
|
116
131
|
|
|
@@ -138,8 +153,10 @@ export interface ICoreMailSubmission {
|
|
|
138
153
|
transportMessageId?: string;
|
|
139
154
|
attempts: number;
|
|
140
155
|
nextAttemptAt?: number;
|
|
156
|
+
error?: ICoreMailErrorData;
|
|
141
157
|
acceptedAt?: number;
|
|
142
158
|
deliveredAt?: number;
|
|
159
|
+
terminalAt?: number;
|
|
143
160
|
createdAt: number;
|
|
144
161
|
updatedAt: number;
|
|
145
162
|
}
|
|
@@ -158,6 +175,13 @@ export interface ICoreMailInboundDelivery {
|
|
|
158
175
|
subject?: string;
|
|
159
176
|
receivedAt: number;
|
|
160
177
|
updatedAt: number;
|
|
178
|
+
acknowledgedAt?: number;
|
|
179
|
+
acknowledgedOutcome?: TCoreMailInboundAckOutcome;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface ICoreMailInboundDeliveryPage {
|
|
183
|
+
deliveries: ICoreMailInboundDelivery[];
|
|
184
|
+
nextCursor?: string;
|
|
161
185
|
}
|
|
162
186
|
|
|
163
187
|
export interface ICoreMailBindingCredentialVerifier {
|
|
@@ -172,7 +196,7 @@ export interface ICoreMailBindingCredentialVerifier {
|
|
|
172
196
|
}
|
|
173
197
|
|
|
174
198
|
export interface ICoreMailBindingDesiredState {
|
|
175
|
-
schemaVersion:
|
|
199
|
+
schemaVersion: 2;
|
|
176
200
|
bindingId: string;
|
|
177
201
|
serviceId: string;
|
|
178
202
|
tenantId: string;
|
|
@@ -183,15 +207,17 @@ export interface ICoreMailBindingDesiredState {
|
|
|
183
207
|
allowedSenders: string[];
|
|
184
208
|
inboundRecipients: string[];
|
|
185
209
|
defaultSender?: string;
|
|
186
|
-
limits
|
|
187
|
-
messagesPerMinute
|
|
188
|
-
messagesPerDay
|
|
189
|
-
maxPendingInbound
|
|
210
|
+
limits: {
|
|
211
|
+
messagesPerMinute: number;
|
|
212
|
+
messagesPerDay: number;
|
|
213
|
+
maxPendingInbound: number;
|
|
190
214
|
};
|
|
191
215
|
}
|
|
192
216
|
|
|
193
217
|
export interface ICoreMailGatewayDesiredState {
|
|
194
218
|
endpointUrl: string;
|
|
219
|
+
/** Canonical CoreMail HTTPS origin authorized for path-only transfer grants. */
|
|
220
|
+
coreMailTransferOrigin: string;
|
|
195
221
|
credentialId: string;
|
|
196
222
|
credentialVersion: number;
|
|
197
223
|
/** Key in CoreMail's resolved runtime secret material, never a plaintext value. */
|
|
@@ -220,11 +246,21 @@ export interface ICoreMailGatewayPeerDesiredState {
|
|
|
220
246
|
credentials: ICoreMailBindingCredentialVerifier[];
|
|
221
247
|
}
|
|
222
248
|
|
|
249
|
+
export interface ICoreMailRuntimeKeyReference {
|
|
250
|
+
keyId: string;
|
|
251
|
+
version: number;
|
|
252
|
+
state: TCoreMailCredentialState;
|
|
253
|
+
/** Key in CoreMail's resolved runtime secret material, never a plaintext value. */
|
|
254
|
+
secretKey: string;
|
|
255
|
+
acceptUntil?: number;
|
|
256
|
+
}
|
|
257
|
+
|
|
223
258
|
export interface ICoreMailDesiredState {
|
|
224
|
-
schemaVersion:
|
|
259
|
+
schemaVersion: 2;
|
|
225
260
|
configEpoch: number;
|
|
226
261
|
bindings: ICoreMailBindingDesiredState[];
|
|
227
262
|
gateway: ICoreMailGatewayDesiredState;
|
|
263
|
+
cursorKeys: ICoreMailRuntimeKeyReference[];
|
|
228
264
|
}
|
|
229
265
|
|
|
230
266
|
export interface ICoreMailReplicaIdentity {
|
|
@@ -239,7 +275,9 @@ export interface ICoreMailBindingReconciliationStatus {
|
|
|
239
275
|
bindingId: string;
|
|
240
276
|
revision: number;
|
|
241
277
|
state: TCoreMailBindingState;
|
|
242
|
-
|
|
278
|
+
pendingInboundCount: number;
|
|
279
|
+
activeSessionsByCredential: Array<{
|
|
280
|
+
credentialId: string;
|
|
243
281
|
version: number;
|
|
244
282
|
count: number;
|
|
245
283
|
}>;
|
|
@@ -281,7 +319,10 @@ export interface ICoreMailGatewayOutboundStatus {
|
|
|
281
319
|
>;
|
|
282
320
|
attempts: number;
|
|
283
321
|
nextAttemptAt?: number;
|
|
322
|
+
error?: ICoreMailErrorData;
|
|
284
323
|
smtpCode?: number;
|
|
324
|
+
deliveredAt?: number;
|
|
325
|
+
terminalAt?: number;
|
|
285
326
|
updatedAt: number;
|
|
286
327
|
}
|
|
287
328
|
|
|
@@ -295,12 +336,83 @@ export const coreMailLimits = Object.freeze({
|
|
|
295
336
|
aggregateAttachmentBytes: 17 * 1024 * 1024,
|
|
296
337
|
mimeFramingBytes: 512 * 1024,
|
|
297
338
|
serializedMimeBytes: 30 * 1024 * 1024,
|
|
339
|
+
recipientCount: 100,
|
|
340
|
+
inboundPageSize: 100,
|
|
341
|
+
inboundPageBytes: 56 * 1024,
|
|
342
|
+
cursorBytes: 1_024,
|
|
343
|
+
cursorMinimumDecodedBytes: 32,
|
|
344
|
+
routingHandleTtlMs: 5 * 60 * 1000,
|
|
298
345
|
transferGrantTtlMs: 5 * 60 * 1000,
|
|
299
346
|
transferHeaderTimeoutMs: 10 * 1000,
|
|
300
347
|
transferIdleTimeoutMs: 15 * 1000,
|
|
301
348
|
transferOverallTimeoutMs: 2 * 60 * 1000,
|
|
302
349
|
} as const);
|
|
303
350
|
|
|
351
|
+
export const coreMailTransferTokenPolicy = Object.freeze({
|
|
352
|
+
format: 'base64url-256' as const,
|
|
353
|
+
decodedBytes: 32,
|
|
354
|
+
encodedCharacters: 43,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
export const coreMailWorkloadOperationPolicy = Object.freeze({
|
|
358
|
+
active: Object.freeze({
|
|
359
|
+
outbound: Object.freeze([
|
|
360
|
+
'coreMailPrepareOutboundSubmission',
|
|
361
|
+
'coreMailPrepareOutboundPartUpload',
|
|
362
|
+
'coreMailCompleteOutboundPartUpload',
|
|
363
|
+
'coreMailFinalizeOutboundSubmission',
|
|
364
|
+
'coreMailGetOutboundSubmission',
|
|
365
|
+
] satisfies TCoreMailWorkloadOperation[]),
|
|
366
|
+
inbound: Object.freeze([
|
|
367
|
+
'coreMailListInboundDeliveries',
|
|
368
|
+
'coreMailPrepareInboundFetch',
|
|
369
|
+
'coreMailCompleteInboundFetch',
|
|
370
|
+
'coreMailAcknowledgeInboundDelivery',
|
|
371
|
+
] satisfies TCoreMailWorkloadOperation[]),
|
|
372
|
+
}),
|
|
373
|
+
draining: Object.freeze({
|
|
374
|
+
outbound: Object.freeze([
|
|
375
|
+
'coreMailGetOutboundSubmission',
|
|
376
|
+
] satisfies TCoreMailWorkloadOperation[]),
|
|
377
|
+
inbound: Object.freeze([
|
|
378
|
+
'coreMailListInboundDeliveries',
|
|
379
|
+
'coreMailPrepareInboundFetch',
|
|
380
|
+
'coreMailCompleteInboundFetch',
|
|
381
|
+
'coreMailAcknowledgeInboundDelivery',
|
|
382
|
+
] satisfies TCoreMailWorkloadOperation[]),
|
|
383
|
+
}),
|
|
384
|
+
disabled: Object.freeze({
|
|
385
|
+
outbound: Object.freeze([] satisfies TCoreMailWorkloadOperation[]),
|
|
386
|
+
inbound: Object.freeze([] satisfies TCoreMailWorkloadOperation[]),
|
|
387
|
+
}),
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
export const coreMailRetentionPolicy = Object.freeze({
|
|
391
|
+
terminalOutboundMs: 30 * 24 * 60 * 60 * 1000,
|
|
392
|
+
acknowledgedInboundMs: 30 * 24 * 60 * 60 * 1000,
|
|
393
|
+
idempotencyReceiptMs: 30 * 24 * 60 * 60 * 1000,
|
|
394
|
+
expiredCapabilityMs: 24 * 60 * 60 * 1000,
|
|
395
|
+
pendingInboundAgePurge: false as const,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
export const coreMailQuotaPolicy = Object.freeze({
|
|
399
|
+
windows: 'fixed-utc-minute-and-day' as const,
|
|
400
|
+
outboundUnit: 'first-durable-idempotency-insert' as const,
|
|
401
|
+
outboundReplayConsumesUnit: false as const,
|
|
402
|
+
pendingInboundStates: ['pending', 'fetching', 'fetched'] as const,
|
|
403
|
+
inboundAcknowledgementReleasesUnit: true as const,
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
export const coreMailTransferProtocol = Object.freeze({
|
|
407
|
+
pathPrefix: '/transfers/',
|
|
408
|
+
authorizationScheme: 'Bearer',
|
|
409
|
+
putSuccessStatus: 204,
|
|
410
|
+
getSuccessStatus: 200,
|
|
411
|
+
requireContentLength: true,
|
|
412
|
+
requireContentType: true,
|
|
413
|
+
integritySource: 'grant-metadata-and-completion' as const,
|
|
414
|
+
});
|
|
415
|
+
|
|
304
416
|
export const coreMailCredentialVerifierPolicy = Object.freeze({
|
|
305
417
|
format: 'argon2id-v1' as const,
|
|
306
418
|
version: 19,
|
|
@@ -3,7 +3,7 @@ import type { IGatewayRouteDnsResult } from './gateway.js';
|
|
|
3
3
|
import type { IImageRolloutStatus, IImmutableImageDeploymentPlan, TSha256Digest } from './immutableimage.js';
|
|
4
4
|
import { normalizeImmutableReleaseTag, normalizeSha256Digest } from './immutableimage.js';
|
|
5
5
|
import type { IRegistryTarget } from './registry.js';
|
|
6
|
-
import { normalizeServiceAbsolutePath, validateServiceContainerArgs
|
|
6
|
+
import { normalizeServiceAbsolutePath, validateServiceContainerArgs } from './service.js';
|
|
7
7
|
import type { TServiceTargetPortRef } from './serviceports.js';
|
|
8
8
|
|
|
9
9
|
export type TDeploymentOperationMode = 'existing-service' | 'greenfield';
|
|
@@ -162,10 +162,12 @@ export const validateDeploymentProposedConfiguration = (
|
|
|
162
162
|
|| configurationArg.projectName.length > 200) {
|
|
163
163
|
errors.push('projectName must be bounded and non-empty');
|
|
164
164
|
}
|
|
165
|
-
if (!validateStringArray(configurationArg.targetPlatforms,
|
|
166
|
-
|
|
167
|
-
)))
|
|
168
|
-
|
|
165
|
+
if (!validateStringArray(configurationArg.targetPlatforms, 2, (platformArg) => (
|
|
166
|
+
platformArg === 'linux/amd64' || platformArg === 'linux/arm64'
|
|
167
|
+
)) || (configurationArg.targetPlatforms as string[]).length === 0
|
|
168
|
+
|| JSON.stringify(configurationArg.targetPlatforms)
|
|
169
|
+
!== JSON.stringify([...(configurationArg.targetPlatforms as string[])].sort())) {
|
|
170
|
+
errors.push('targetPlatforms must be a sorted non-empty supported platform set');
|
|
169
171
|
}
|
|
170
172
|
if (!validateStringArray(configurationArg.publicDomains, 32, (domainArg) => (
|
|
171
173
|
canonicalizeDeploymentHostname(domainArg) === domainArg
|
|
@@ -177,33 +179,7 @@ export const validateDeploymentProposedConfiguration = (
|
|
|
177
179
|
))) {
|
|
178
180
|
errors.push('environmentVariableNames must be unique canonical names');
|
|
179
181
|
}
|
|
180
|
-
if (!validateStringArray(configurationArg.secretReferenceIds, 64, (idArg) => (
|
|
181
|
-
idArg.length <= 253 && /^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(idArg)
|
|
182
|
-
))) {
|
|
183
|
-
errors.push('secretReferenceIds must be unique canonical identifiers');
|
|
184
|
-
}
|
|
185
|
-
const volumeMountPaths = Array.isArray(configurationArg.volumeMounts)
|
|
186
|
-
? configurationArg.volumeMounts.flatMap((mountArg) => (
|
|
187
|
-
isRecord(mountArg) && typeof mountArg.mountPath === 'string' ? [mountArg.mountPath] : []
|
|
188
|
-
))
|
|
189
|
-
: [];
|
|
190
182
|
errors.push(...validateServiceContainerArgs(configurationArg.containerArgs));
|
|
191
|
-
errors.push(...validateServiceSecretFiles(configurationArg.secretFiles, volumeMountPaths));
|
|
192
|
-
if (Array.isArray(configurationArg.secretFiles)
|
|
193
|
-
&& Array.isArray(configurationArg.environmentVariableNames)) {
|
|
194
|
-
const publicEnvironmentNames = new Set(
|
|
195
|
-
configurationArg.environmentVariableNames.filter((nameArg): nameArg is string => (
|
|
196
|
-
typeof nameArg === 'string'
|
|
197
|
-
)),
|
|
198
|
-
);
|
|
199
|
-
if (configurationArg.secretFiles.some((fileArg) => (
|
|
200
|
-
isRecord(fileArg)
|
|
201
|
-
&& typeof fileArg.sourceKey === 'string'
|
|
202
|
-
&& publicEnvironmentNames.has(fileArg.sourceKey)
|
|
203
|
-
))) {
|
|
204
|
-
errors.push('secretFiles source keys must not be declared as environment variables');
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
183
|
if (!Array.isArray(configurationArg.containerPorts)
|
|
208
184
|
|| configurationArg.containerPorts.length > 64
|
|
209
185
|
|| configurationArg.containerPorts.some((portArg) => !Number.isSafeInteger(portArg)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
TImmutableContainerPlatform,
|
|
2
3
|
TOciManifestMediaType,
|
|
3
4
|
TSha256Digest,
|
|
4
5
|
} from './immutableimage.js';
|
|
5
6
|
import { normalizeImmutableReleaseTag, normalizeSha256Digest } from './immutableimage.js';
|
|
6
7
|
import type { TServiceDeploymentCapability } from './user.js';
|
|
7
|
-
import type { IServiceSecretFile } from './service.js';
|
|
8
8
|
import type {
|
|
9
9
|
IDeploymentRouteRequest,
|
|
10
10
|
IServiceDeploymentOperation,
|
|
@@ -145,14 +145,11 @@ export interface IDeploymentPreflightProposedConfiguration {
|
|
|
145
145
|
schemaVersion: 1;
|
|
146
146
|
declarationComplete: boolean;
|
|
147
147
|
projectName: string;
|
|
148
|
-
targetPlatforms:
|
|
148
|
+
targetPlatforms: TImmutableContainerPlatform[];
|
|
149
149
|
publicDomains: string[];
|
|
150
150
|
environmentVariableNames: string[];
|
|
151
|
-
secretReferenceIds: string[];
|
|
152
151
|
/** Exact OCI/Docker argument vector appended after the image entrypoint. */
|
|
153
152
|
containerArgs?: string[];
|
|
154
|
-
/** Secret-bundle keys that must be delivered only as Docker secret files. */
|
|
155
|
-
secretFiles?: IServiceSecretFile[];
|
|
156
153
|
containerPorts: number[];
|
|
157
154
|
volumeMounts: IDeploymentPreflightProposedVolumeMount[];
|
|
158
155
|
requiredCapabilities: Array<'database' | 'objectstorage' | 'pushnotification'>;
|
package/ts/data/hostedapp.ts
CHANGED
|
@@ -2,6 +2,58 @@ export type THostedAppHostType = 'onebox' | 'cloudly' | string;
|
|
|
2
2
|
|
|
3
3
|
export const hostedAppOidcRoleClaim = 'roles' as const;
|
|
4
4
|
export const hostedAppOidcAppInstanceIdClaim = 'servezone_app_instance_id' as const;
|
|
5
|
+
export const hostedAppMachineServiceIdClaim = 'servezone_service_id' as const;
|
|
6
|
+
export const hostedAppMachineAudience = 'serve.zone/hosted-app-lifecycle' as const;
|
|
7
|
+
|
|
8
|
+
export interface IHostedAppMachineClaims {
|
|
9
|
+
sub: string;
|
|
10
|
+
aud: typeof hostedAppMachineAudience;
|
|
11
|
+
iat: number;
|
|
12
|
+
exp: number;
|
|
13
|
+
[hostedAppOidcAppInstanceIdClaim]: string;
|
|
14
|
+
[hostedAppMachineServiceIdClaim]: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const createHostedAppMachineSubject = (appInstanceIdArg: string): string => (
|
|
18
|
+
`urn:serve.zone:hosted-app:${appInstanceIdArg}`
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export const validateHostedAppMachineClaims = (
|
|
22
|
+
claimsArg: unknown,
|
|
23
|
+
expectedArg: {
|
|
24
|
+
appInstanceId: string;
|
|
25
|
+
serviceId: string;
|
|
26
|
+
nowEpochSeconds?: number;
|
|
27
|
+
},
|
|
28
|
+
): string[] => {
|
|
29
|
+
if (!claimsArg || typeof claimsArg !== 'object' || Array.isArray(claimsArg)) {
|
|
30
|
+
return ['hosted app machine claims must be an object'];
|
|
31
|
+
}
|
|
32
|
+
const claims = claimsArg as Record<string, unknown>;
|
|
33
|
+
const errors: string[] = [];
|
|
34
|
+
if (claims.sub !== createHostedAppMachineSubject(expectedArg.appInstanceId)) {
|
|
35
|
+
errors.push('hosted app machine subject does not match');
|
|
36
|
+
}
|
|
37
|
+
if (claims.aud !== hostedAppMachineAudience) {
|
|
38
|
+
errors.push('hosted app machine audience does not match');
|
|
39
|
+
}
|
|
40
|
+
if (claims[hostedAppOidcAppInstanceIdClaim] !== expectedArg.appInstanceId) {
|
|
41
|
+
errors.push('hosted app machine app instance does not match');
|
|
42
|
+
}
|
|
43
|
+
if (claims[hostedAppMachineServiceIdClaim] !== expectedArg.serviceId) {
|
|
44
|
+
errors.push('hosted app machine service does not match');
|
|
45
|
+
}
|
|
46
|
+
const now = expectedArg.nowEpochSeconds ?? Math.floor(Date.now() / 1000);
|
|
47
|
+
if (!Number.isSafeInteger(claims.iat) || (claims.iat as number) < 0
|
|
48
|
+
|| (claims.iat as number) > now) {
|
|
49
|
+
errors.push('hosted app machine issued-at time is malformed');
|
|
50
|
+
}
|
|
51
|
+
if (!Number.isSafeInteger(claims.exp) || (claims.exp as number) <= now
|
|
52
|
+
|| (Number.isSafeInteger(claims.iat) && (claims.exp as number) <= (claims.iat as number))) {
|
|
53
|
+
errors.push('hosted app machine expiry is malformed or expired');
|
|
54
|
+
}
|
|
55
|
+
return errors;
|
|
56
|
+
};
|
|
5
57
|
|
|
6
58
|
export interface IHostedAppRoleDefinition {
|
|
7
59
|
/** Stable role identifier emitted in the hosted app's audience-scoped token. */
|
|
@@ -276,7 +328,7 @@ export type THostedAppRuntimeStatus =
|
|
|
276
328
|
| 'stopped'
|
|
277
329
|
| 'unknown';
|
|
278
330
|
|
|
279
|
-
export type THostedAppBootstrapActionType = '
|
|
331
|
+
export type THostedAppBootstrapActionType = 'setupRoute' | 'message';
|
|
280
332
|
|
|
281
333
|
export type THostedAppBootstrapActionStatus =
|
|
282
334
|
| 'requested'
|
|
@@ -293,21 +345,12 @@ export type THostedAppUpgradeStatus =
|
|
|
293
345
|
| 'success'
|
|
294
346
|
| 'failed';
|
|
295
347
|
|
|
296
|
-
|
|
297
|
-
appInstanceId: string;
|
|
298
|
-
appControlToken: string;
|
|
299
|
-
hostType?: THostedAppHostType;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
export interface IHostedAppBootstrapAction {
|
|
348
|
+
interface IHostedAppBootstrapActionBase {
|
|
303
349
|
id: string;
|
|
304
|
-
|
|
350
|
+
/** Server-managed CAS revision incremented on every action transition. */
|
|
351
|
+
revision: number;
|
|
305
352
|
status: THostedAppBootstrapActionStatus;
|
|
306
353
|
label: string;
|
|
307
|
-
url?: string;
|
|
308
|
-
username?: string;
|
|
309
|
-
password?: string;
|
|
310
|
-
message?: string;
|
|
311
354
|
expiresAt?: number;
|
|
312
355
|
createdAt: number;
|
|
313
356
|
updatedAt: number;
|
|
@@ -315,6 +358,18 @@ export interface IHostedAppBootstrapAction {
|
|
|
315
358
|
dismissedAt?: number;
|
|
316
359
|
}
|
|
317
360
|
|
|
361
|
+
export type IHostedAppBootstrapAction =
|
|
362
|
+
| (IHostedAppBootstrapActionBase & {
|
|
363
|
+
type: 'setupRoute';
|
|
364
|
+
route: string;
|
|
365
|
+
message?: never;
|
|
366
|
+
})
|
|
367
|
+
| (IHostedAppBootstrapActionBase & {
|
|
368
|
+
type: 'message';
|
|
369
|
+
route?: never;
|
|
370
|
+
message: string;
|
|
371
|
+
});
|
|
372
|
+
|
|
318
373
|
export interface IHostedAppUpgradeState {
|
|
319
374
|
status: THostedAppUpgradeStatus;
|
|
320
375
|
appTemplateId?: string;
|
|
@@ -347,9 +402,80 @@ export interface IHostedAppLifecycleState extends IHostedAppLifecycleReport {
|
|
|
347
402
|
upgradeState?: IHostedAppUpgradeState;
|
|
348
403
|
}
|
|
349
404
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
405
|
+
type THostedAppBootstrapActionServerFields =
|
|
406
|
+
| 'id'
|
|
407
|
+
| 'revision'
|
|
408
|
+
| 'status'
|
|
409
|
+
| 'createdAt'
|
|
410
|
+
| 'updatedAt'
|
|
411
|
+
| 'completedAt'
|
|
412
|
+
| 'dismissedAt';
|
|
413
|
+
|
|
414
|
+
export type IHostedAppBootstrapActionRequest =
|
|
415
|
+
| (Omit<
|
|
416
|
+
Extract<IHostedAppBootstrapAction, { type: 'setupRoute' }>,
|
|
417
|
+
THostedAppBootstrapActionServerFields
|
|
418
|
+
> & { id?: string })
|
|
419
|
+
| (Omit<
|
|
420
|
+
Extract<IHostedAppBootstrapAction, { type: 'message' }>,
|
|
421
|
+
THostedAppBootstrapActionServerFields
|
|
422
|
+
> & { id?: string });
|
|
423
|
+
|
|
424
|
+
export const normalizeHostedAppSetupRoute = (routeArg: unknown): string | undefined => {
|
|
425
|
+
if (typeof routeArg !== 'string'
|
|
426
|
+
|| !routeArg.startsWith('/')
|
|
427
|
+
|| routeArg.startsWith('//')
|
|
428
|
+
|| /[\\?#\s]/.test(routeArg)
|
|
429
|
+
|| /%5c/i.test(routeArg)) {
|
|
430
|
+
return undefined;
|
|
431
|
+
}
|
|
432
|
+
const parsed = new URL(routeArg, 'https://app.invalid');
|
|
433
|
+
return parsed.pathname === routeArg ? routeArg : undefined;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
export const validateHostedAppBootstrapActionRequest = (
|
|
437
|
+
actionArg: unknown,
|
|
438
|
+
): string[] => {
|
|
439
|
+
if (!actionArg || typeof actionArg !== 'object' || Array.isArray(actionArg)) {
|
|
440
|
+
return ['hosted app bootstrap action must be an object'];
|
|
441
|
+
}
|
|
442
|
+
const action = actionArg as Record<string, unknown>;
|
|
443
|
+
const errors: string[] = [];
|
|
444
|
+
const allowedKeys = action.type === 'setupRoute'
|
|
445
|
+
? new Set(['id', 'type', 'label', 'route', 'expiresAt'])
|
|
446
|
+
: action.type === 'message'
|
|
447
|
+
? new Set(['id', 'type', 'label', 'message', 'expiresAt'])
|
|
448
|
+
: new Set(['id', 'type', 'label', 'expiresAt']);
|
|
449
|
+
if (Object.keys(action).some((keyArg) => !allowedKeys.has(keyArg))) {
|
|
450
|
+
errors.push('hosted app bootstrap action contains fields outside its schema');
|
|
451
|
+
}
|
|
452
|
+
if (action.id !== undefined
|
|
453
|
+
&& (typeof action.id !== 'string'
|
|
454
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9:._-]{0,199}$/.test(action.id))) {
|
|
455
|
+
errors.push('hosted app bootstrap action id must be canonical');
|
|
456
|
+
}
|
|
457
|
+
if (action.expiresAt !== undefined
|
|
458
|
+
&& (!Number.isSafeInteger(action.expiresAt) || (action.expiresAt as number) < 1)) {
|
|
459
|
+
errors.push('hosted app bootstrap action expiresAt must be a positive integer');
|
|
460
|
+
}
|
|
461
|
+
if (action.type !== 'setupRoute' && action.type !== 'message') {
|
|
462
|
+
errors.push('hosted app bootstrap action type is unsupported');
|
|
463
|
+
}
|
|
464
|
+
if (typeof action.label !== 'string' || !action.label.trim() || action.label.length > 200) {
|
|
465
|
+
errors.push('hosted app bootstrap action label must be bounded');
|
|
466
|
+
}
|
|
467
|
+
if (action.type === 'setupRoute') {
|
|
468
|
+
if (normalizeHostedAppSetupRoute(action.route) !== action.route) {
|
|
469
|
+
errors.push('hosted app setup route must be a canonical same-origin absolute path');
|
|
470
|
+
}
|
|
471
|
+
if ('message' in action) errors.push('hosted app setup route forbids message');
|
|
472
|
+
}
|
|
473
|
+
if (action.type === 'message') {
|
|
474
|
+
if (typeof action.message !== 'string' || !action.message.trim()
|
|
475
|
+
|| action.message.length > 4000) {
|
|
476
|
+
errors.push('hosted app bootstrap message must be bounded');
|
|
477
|
+
}
|
|
478
|
+
if ('route' in action) errors.push('hosted app message forbids route');
|
|
479
|
+
}
|
|
480
|
+
return errors;
|
|
355
481
|
};
|