@ampless/backend 1.0.0-alpha.48 → 1.0.0-alpha.50
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.
|
@@ -192,7 +192,7 @@ function createProcessorTrustedHandler(opts) {
|
|
|
192
192
|
const result = await rawDdb.send(
|
|
193
193
|
new GetItemCommand({
|
|
194
194
|
TableName: PLUGIN_SECRET_TABLE,
|
|
195
|
-
Key: marshall({
|
|
195
|
+
Key: marshall({ sk })
|
|
196
196
|
})
|
|
197
197
|
);
|
|
198
198
|
if (!result.Item) {
|
|
@@ -74,13 +74,13 @@ async function handleSet(args) {
|
|
|
74
74
|
await ddb.send(
|
|
75
75
|
new PutItemCommand({
|
|
76
76
|
TableName: secretTable,
|
|
77
|
-
Item: marshall({
|
|
77
|
+
Item: marshall({ sk, value: ciphertext })
|
|
78
78
|
})
|
|
79
79
|
);
|
|
80
80
|
await ddb.send(
|
|
81
81
|
new PutItemCommand({
|
|
82
82
|
TableName: indicatorTable,
|
|
83
|
-
Item: marshall({
|
|
83
|
+
Item: marshall({ sk, lastSetAt: now })
|
|
84
84
|
})
|
|
85
85
|
);
|
|
86
86
|
return "ok";
|
|
@@ -99,13 +99,13 @@ async function handleClear(args) {
|
|
|
99
99
|
await ddb.send(
|
|
100
100
|
new DeleteItemCommand({
|
|
101
101
|
TableName: secretTable,
|
|
102
|
-
Key: marshall({
|
|
102
|
+
Key: marshall({ sk })
|
|
103
103
|
})
|
|
104
104
|
);
|
|
105
105
|
await ddb.send(
|
|
106
106
|
new DeleteItemCommand({
|
|
107
107
|
TableName: indicatorTable,
|
|
108
|
-
Key: marshall({
|
|
108
|
+
Key: marshall({ sk })
|
|
109
109
|
})
|
|
110
110
|
);
|
|
111
111
|
return "ok";
|
package/dist/index.js
CHANGED
|
@@ -434,33 +434,40 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
434
434
|
// never flows back to the browser.
|
|
435
435
|
//
|
|
436
436
|
// Authorization design:
|
|
437
|
-
// - admin / editor: NO
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
//
|
|
441
|
-
//
|
|
442
|
-
// -
|
|
443
|
-
// `
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
//
|
|
437
|
+
// - admin / editor: NO AppSync access. The model uses a
|
|
438
|
+
// placeholder group (`__ampless_internal__`) that no Cognito
|
|
439
|
+
// user belongs to. Amplify Gen 2 requires every model to have
|
|
440
|
+
// at least one auth rule, and this is the minimum-viable one
|
|
441
|
+
// that synthesizes cleanly while denying all AppSync access.
|
|
442
|
+
// - plugin-secret-handler Lambda: full DDB access via the
|
|
443
|
+
// `grantReadWriteData` IAM grant in `backend.ts`. It calls
|
|
444
|
+
// the DDB SDK directly (NOT through AppSync), so the model's
|
|
445
|
+
// @auth rule is irrelevant to its actual write path.
|
|
446
|
+
// - trusted-processor Lambda: read-only DDB access via the
|
|
447
|
+
// `grantReadData` IAM grant in `backend.ts`. Same pattern —
|
|
448
|
+
// bypasses AppSync entirely.
|
|
449
|
+
// - Lambda `allow.resource(...)` is unavailable at the model
|
|
450
|
+
// level in `@aws-amplify/data-schema` (the `resource` helper
|
|
451
|
+
// is destructured out of `allow` before model-level callbacks
|
|
452
|
+
// run; see line 141 in this file). Schema-level resource auth
|
|
453
|
+
// would grant the Lambda AppSync access to ALL models — too
|
|
454
|
+
// broad for our needs. The DDB-SDK direct path keeps the
|
|
455
|
+
// access scoped to the table the Lambda needs.
|
|
451
456
|
//
|
|
452
457
|
// Storage key convention:
|
|
453
|
-
// siteId = 'default' (single-site architecture)
|
|
454
458
|
// sk = `plugins.${instanceId ?? name}.${fieldKey}`
|
|
455
459
|
//
|
|
460
|
+
// Single-site architecture: no `siteId` partition column. The same
|
|
461
|
+
// single-table convention as KvStore / Post / Page / Media after
|
|
462
|
+
// the `remove-siteid-from-schema` migration.
|
|
463
|
+
//
|
|
456
464
|
// DynamoDB auto-encrypts at rest (AWS-managed KMS key). Secrets
|
|
457
465
|
// never flow to the S3 site-settings mirror because the trusted
|
|
458
466
|
// processor only queries KvStore (pk='siteconfig') for that path —
|
|
459
467
|
// PluginSecret is a structurally separate table.
|
|
460
468
|
PluginSecret: a.model({
|
|
461
|
-
//
|
|
462
|
-
|
|
463
|
-
// Composite sort key: `plugins.<instanceId>.<fieldKey>`
|
|
469
|
+
// Sort key (also the partition key — single-id identifier):
|
|
470
|
+
// `plugins.<instanceId>.<fieldKey>`
|
|
464
471
|
sk: a.string().required(),
|
|
465
472
|
// The secret value stored as AES-256-GCM ciphertext (base64).
|
|
466
473
|
// Format: base64( IV[12] || ciphertext || authTag[16] ).
|
|
@@ -475,17 +482,12 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
475
482
|
// / deploy artifact access, NOT defeated for a malicious
|
|
476
483
|
// trusted plugin co-located in the same Lambda.
|
|
477
484
|
value: a.string().required()
|
|
478
|
-
}).identifier(["
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
} else {
|
|
485
|
-
rules.push(allow.groups(["ampless-admin"]));
|
|
486
|
-
}
|
|
487
|
-
return rules;
|
|
488
|
-
}),
|
|
485
|
+
}).identifier(["sk"]).authorization((allow) => [
|
|
486
|
+
// Deny-all-via-AppSync sentinel. See the comment above this
|
|
487
|
+
// model for why this is a non-existent group name and how the
|
|
488
|
+
// real Lambda access path works (DDB SDK + IAM grants).
|
|
489
|
+
allow.groups(["__ampless_internal__"])
|
|
490
|
+
]),
|
|
489
491
|
// Existence-only indicator for PluginSecret rows.
|
|
490
492
|
//
|
|
491
493
|
// Admin/editor Cognito users cannot read from PluginSecret at all
|
|
@@ -501,29 +503,22 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
501
503
|
// row is at most stale-indicator-but-no-secret, which degrades
|
|
502
504
|
// gracefully as a false "stored" indicator in the UI).
|
|
503
505
|
PluginSecretIndicator: a.model({
|
|
504
|
-
siteId: a.string().required(),
|
|
505
506
|
// Same sort-key format as PluginSecret:
|
|
506
507
|
// `plugins.${instanceId ?? name}.${fieldKey}`
|
|
507
508
|
sk: a.string().required(),
|
|
508
509
|
// ISO 8601 datetime string — set by plugin-secret-handler on
|
|
509
510
|
// every write. Admin UI may show this as "last updated" hint.
|
|
510
511
|
lastSetAt: a.datetime().required()
|
|
511
|
-
}).identifier(["
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
]
|
|
520
|
-
|
|
521
|
-
rules.push(
|
|
522
|
-
allow.resource(opts.pluginSecretHandlerFunction).to(["read", "create", "update", "delete"])
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
return rules;
|
|
526
|
-
}),
|
|
512
|
+
}).identifier(["sk"]).authorization((allow) => [
|
|
513
|
+
// Admin/editor have AppSync read for `hasPluginSecret` and
|
|
514
|
+
// direct write+delete as a recovery path. The Lambda is the
|
|
515
|
+
// normal write path (it calls the DDB SDK directly with IAM,
|
|
516
|
+
// not through AppSync — same pattern as PluginSecret above),
|
|
517
|
+
// but allowing admin write here lets a clear-retry after a
|
|
518
|
+
// partial dual-write failure heal the inconsistency from the
|
|
519
|
+
// admin side without redeploying.
|
|
520
|
+
allow.groups(["ampless-admin", "ampless-editor"])
|
|
521
|
+
]),
|
|
527
522
|
// Custom return type for public post reads. Decoupling from `Post` lets
|
|
528
523
|
// AppSync skip the model-level (admin-only) auth check on fields.
|
|
529
524
|
//
|
package/package.json
CHANGED