@ampless/backend 0.2.0-alpha.9 → 1.0.0-alpha.19
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/dist/auth/post-confirmation.js +2 -0
- package/dist/auth/user-admin.js +2 -0
- package/dist/chunk-BYXBJQAS.js +0 -0
- package/dist/events/dispatcher.js +4 -5
- package/dist/events/processor-trusted.d.ts +5 -5
- package/dist/events/processor-trusted.js +17 -22
- package/dist/events/processor-untrusted.js +4 -4
- package/dist/functions/api-key-renewer.js +2 -0
- package/dist/functions/mcp-handler.d.ts +16 -8
- package/dist/functions/mcp-handler.js +1133 -11
- package/dist/index.d.ts +47 -1
- package/dist/index.js +52 -32
- package/package.json +8 -2
package/dist/index.d.ts
CHANGED
|
@@ -189,6 +189,52 @@ declare function amplessSchemaModels(a: any, opts?: AmplessSchemaModelsOpts): {
|
|
|
189
189
|
* when there are no custom models.
|
|
190
190
|
*/
|
|
191
191
|
declare function extendAmplessSchema(a: any, custom?: Record<string, any>, opts?: AmplessSchemaModelsOpts): any;
|
|
192
|
+
interface AmplessSchemaAuthorizationOpts {
|
|
193
|
+
/**
|
|
194
|
+
* Optional Amplify `defineFunction` ref for the MCP HTTP handler.
|
|
195
|
+
* When supplied, the schema gains `allow.resource(fn).to(['query',
|
|
196
|
+
* 'mutate'])` so the Lambda can sign AppSync requests with SigV4
|
|
197
|
+
* under its own IAM role (no Cognito identity / shared API key).
|
|
198
|
+
*
|
|
199
|
+
* Resource auth is currently only honoured at schema scope by
|
|
200
|
+
* `@aws-amplify/data-schema` (model-level `.authorization` callbacks
|
|
201
|
+
* destructure `resource` out of their `allow` parameter), so this
|
|
202
|
+
* grant applies broadly — every model the Lambda calls is reachable.
|
|
203
|
+
* That's wider than strictly necessary; the MCP tools' GraphQL
|
|
204
|
+
* operations narrow the effective surface to Post / PostTag in
|
|
205
|
+
* Phase 4 and Media in Phase 5.
|
|
206
|
+
*
|
|
207
|
+
* Typed as `unknown` for the same reason `userAdminFunction` /
|
|
208
|
+
* `mcpHandlerFunction` are in other helpers — `defineFunction`'s
|
|
209
|
+
* return type carries internal pnpm paths that don't survive
|
|
210
|
+
* declaration emit.
|
|
211
|
+
*/
|
|
212
|
+
mcpHandlerFunction?: unknown;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Schema-level authorization rules for ampless. Pass the result into
|
|
216
|
+
* `a.schema({...}).authorization((allow) => [...])` in the user's
|
|
217
|
+
* `amplify/data/resource.ts`. When no Lambda function refs are
|
|
218
|
+
* supplied the function returns `[]`, so the schema stays unaffected.
|
|
219
|
+
*
|
|
220
|
+
* Return type is `any[]` (matching the rest of this module's
|
|
221
|
+
* intentional looseness around `@aws-amplify/data-schema`'s heavily
|
|
222
|
+
* generic builder types) so callers don't have to wrestle the
|
|
223
|
+
* generic `SchemaAuthorization<…>` parameters that change between
|
|
224
|
+
* minor versions. `amplify/data/resource.ts` strict-type-checks fine
|
|
225
|
+
* downstream because the schema itself still resolves through
|
|
226
|
+
* `ClientSchema<typeof schema>` correctly.
|
|
227
|
+
*
|
|
228
|
+
* Usage:
|
|
229
|
+
*
|
|
230
|
+
* const schema = a.schema({
|
|
231
|
+
* ...amplessSchemaModels(a, { resolverPaths, userAdminFunction }),
|
|
232
|
+
* ...customSchemaModels(a),
|
|
233
|
+
* }).authorization((allow) => amplessSchemaAuthorization(allow, {
|
|
234
|
+
* mcpHandlerFunction: mcpHandler,
|
|
235
|
+
* }))
|
|
236
|
+
*/
|
|
237
|
+
declare function amplessSchemaAuthorization(allow: any, opts?: AmplessSchemaAuthorizationOpts): any[];
|
|
192
238
|
/**
|
|
193
239
|
* Standard authorization modes for ampless. `userPool` is the default
|
|
194
240
|
* (admin/editor access); the API key serves the public read endpoints
|
|
@@ -199,4 +245,4 @@ declare function extendAmplessSchema(a: any, custom?: Record<string, any>, opts?
|
|
|
199
245
|
*/
|
|
200
246
|
declare const defaultAuthorizationModes: Parameters<typeof defineData>[0]['authorizationModes'];
|
|
201
247
|
|
|
202
|
-
export { type AmplessAuthConfigOpts, type AmplessBackend, type AmplessResolverPaths, type AmplessSchemaModelsOpts, DEFAULT_RESOLVER_PATHS, type DefineAmplessBackendOpts, amplessAuthConfig, amplessSchemaModels, amplessStorageConfig, defaultAuthorizationModes, defineAmplessBackend, extendAmplessSchema };
|
|
248
|
+
export { type AmplessAuthConfigOpts, type AmplessBackend, type AmplessResolverPaths, type AmplessSchemaAuthorizationOpts, type AmplessSchemaModelsOpts, DEFAULT_RESOLVER_PATHS, type DefineAmplessBackendOpts, amplessAuthConfig, amplessSchemaAuthorization, amplessSchemaModels, amplessStorageConfig, defaultAuthorizationModes, defineAmplessBackend, extendAmplessSchema };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import "./chunk-BYXBJQAS.js";
|
|
2
|
+
|
|
1
3
|
// src/backend.ts
|
|
2
4
|
import { defineBackend } from "@aws-amplify/backend";
|
|
3
5
|
import { Effect, PolicyStatement, AnyPrincipal } from "aws-cdk-lib/aws-iam";
|
|
@@ -176,11 +178,40 @@ function defineAmplessBackend(opts) {
|
|
|
176
178
|
})
|
|
177
179
|
);
|
|
178
180
|
mcpHandlerFn.addEnvironment("AMPLESS_KV_TABLE", kvTable.tableName);
|
|
181
|
+
mcpHandlerFn.addEnvironment(
|
|
182
|
+
"AMPLESS_APPSYNC_URL",
|
|
183
|
+
backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl
|
|
184
|
+
);
|
|
185
|
+
mcpHandlerFn.addToRolePolicy(
|
|
186
|
+
new PolicyStatement({
|
|
187
|
+
effect: Effect.ALLOW,
|
|
188
|
+
actions: ["s3:PutObject"],
|
|
189
|
+
resources: [`${backend.storage.resources.bucket.bucketArn}/public/media/*`]
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
mcpHandlerFn.addToRolePolicy(
|
|
193
|
+
new PolicyStatement({
|
|
194
|
+
effect: Effect.ALLOW,
|
|
195
|
+
actions: ["s3:PutObject", "s3:DeleteObject"],
|
|
196
|
+
resources: [`${backend.storage.resources.bucket.bucketArn}/public/static/*`]
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
mcpHandlerFn.addToRolePolicy(
|
|
200
|
+
new PolicyStatement({
|
|
201
|
+
effect: Effect.ALLOW,
|
|
202
|
+
actions: ["s3:ListBucket"],
|
|
203
|
+
resources: [backend.storage.resources.bucket.bucketArn],
|
|
204
|
+
conditions: {
|
|
205
|
+
StringLike: { "s3:prefix": ["public/static/*"] }
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
mcpHandlerFn.addEnvironment("AMPLESS_BUCKET_NAME", backend.storage.resources.bucket.bucketName);
|
|
179
210
|
const mcpFunctionUrl = mcpHandlerFn.addFunctionUrl({
|
|
180
211
|
authType: FunctionUrlAuthType.NONE,
|
|
181
212
|
cors: {
|
|
182
213
|
allowedOrigins: ["*"],
|
|
183
|
-
allowedMethods: [HttpMethod.POST
|
|
214
|
+
allowedMethods: [HttpMethod.POST],
|
|
184
215
|
allowedHeaders: ["*"],
|
|
185
216
|
maxAge: Duration.hours(1)
|
|
186
217
|
}
|
|
@@ -239,7 +270,6 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
239
270
|
};
|
|
240
271
|
return {
|
|
241
272
|
Post: a.model({
|
|
242
|
-
siteId: a.string().required(),
|
|
243
273
|
postId: a.id().required(),
|
|
244
274
|
slug: a.string().required(),
|
|
245
275
|
title: a.string().required(),
|
|
@@ -256,25 +286,14 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
256
286
|
// chrome). The runtime's post dispatcher redirects to the
|
|
257
287
|
// raw route handler when this is true.
|
|
258
288
|
// Other keys are passed through unchanged for plugin / app use.
|
|
259
|
-
metadata: a.json()
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// hit DynamoDB with a pure PK Query, no filter pass.
|
|
264
|
-
// siteIdStatus = `${siteId}#${status}`
|
|
265
|
-
// → bySiteIdStatus partitions per site×status, sorted by publishedAt
|
|
266
|
-
// siteIdSlug = `${siteId}#${slug}`
|
|
267
|
-
// → bySiteIdSlug locates a single post by slug in O(1)
|
|
268
|
-
siteIdStatus: a.string(),
|
|
269
|
-
siteIdSlug: a.string()
|
|
270
|
-
}).identifier(["siteId", "postId"]).secondaryIndexes((index) => [
|
|
271
|
-
index("siteIdStatus").sortKeys(["publishedAt"]).name("bySiteIdStatus"),
|
|
272
|
-
index("siteIdSlug").name("bySiteIdSlug")
|
|
289
|
+
metadata: a.json()
|
|
290
|
+
}).identifier(["postId"]).secondaryIndexes((index) => [
|
|
291
|
+
index("status").sortKeys(["publishedAt"]).name("byStatus"),
|
|
292
|
+
index("slug").name("bySlug")
|
|
273
293
|
]).authorization((allow) => [
|
|
274
294
|
allow.groups(["ampless-admin", "ampless-editor"])
|
|
275
295
|
]),
|
|
276
296
|
Page: a.model({
|
|
277
|
-
siteId: a.string().required(),
|
|
278
297
|
pageId: a.id().required(),
|
|
279
298
|
slug: a.string().required(),
|
|
280
299
|
title: a.string().required(),
|
|
@@ -282,26 +301,24 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
282
301
|
body: a.json(),
|
|
283
302
|
status: a.enum(["draft", "published"]),
|
|
284
303
|
publishedAt: a.datetime()
|
|
285
|
-
}).identifier(["
|
|
304
|
+
}).identifier(["pageId"]).authorization((allow) => [
|
|
286
305
|
allow.groups(["ampless-admin", "ampless-editor"])
|
|
287
306
|
]),
|
|
288
307
|
Media: a.model({
|
|
289
|
-
siteId: a.string().required(),
|
|
290
308
|
mediaId: a.id().required(),
|
|
291
309
|
src: a.string().required(),
|
|
292
310
|
mimeType: a.string().required(),
|
|
293
311
|
size: a.integer(),
|
|
294
312
|
delivery: a.string()
|
|
295
|
-
}).identifier(["
|
|
313
|
+
}).identifier(["mediaId"]).authorization((allow) => [
|
|
296
314
|
allow.groups(["ampless-admin", "ampless-editor"])
|
|
297
315
|
]),
|
|
298
316
|
Taxonomy: a.model({
|
|
299
|
-
siteId: a.string().required(),
|
|
300
317
|
termId: a.id().required(),
|
|
301
318
|
type: a.enum(["category", "tag"]),
|
|
302
319
|
name: a.string().required(),
|
|
303
320
|
slug: a.string().required()
|
|
304
|
-
}).identifier(["
|
|
321
|
+
}).identifier(["termId"]).authorization((allow) => [
|
|
305
322
|
allow.groups(["ampless-admin", "ampless-editor"])
|
|
306
323
|
]),
|
|
307
324
|
// Denormalized index for "posts by tag" queries. Each Post tag becomes
|
|
@@ -310,13 +327,11 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
310
327
|
// sort key. Maintained by the admin client whenever a post is created,
|
|
311
328
|
// updated, or deleted; never edited by end users.
|
|
312
329
|
//
|
|
313
|
-
// PK:
|
|
330
|
+
// PK: tag e.g. "tech"
|
|
314
331
|
// SK: publishedAtPostId e.g. "2026-04-27T13:57:05.679Z#post-001"
|
|
315
332
|
PostTag: a.model({
|
|
316
|
-
siteIdTag: a.string().required(),
|
|
317
|
-
publishedAtPostId: a.string().required(),
|
|
318
|
-
siteId: a.string().required(),
|
|
319
333
|
tag: a.string().required(),
|
|
334
|
+
publishedAtPostId: a.string().required(),
|
|
320
335
|
postId: a.id().required(),
|
|
321
336
|
publishedAt: a.datetime().required(),
|
|
322
337
|
slug: a.string().required(),
|
|
@@ -324,11 +339,11 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
324
339
|
excerpt: a.string(),
|
|
325
340
|
// Full tag list of the post (for chip rendering on tag pages).
|
|
326
341
|
tags: a.string().array()
|
|
327
|
-
}).identifier(["
|
|
342
|
+
}).identifier(["tag", "publishedAtPostId"]).authorization((allow) => [
|
|
328
343
|
allow.groups(["ampless-admin", "ampless-editor"])
|
|
329
344
|
]),
|
|
330
345
|
// Generic key/value store. Two roles in one table:
|
|
331
|
-
// - Site settings: PK = `siteconfig
|
|
346
|
+
// - Site settings: PK = `siteconfig`, SK = dotted key
|
|
332
347
|
// (`site.name`, `media.imageDisplay`, ...). No TTL → persistent.
|
|
333
348
|
// - Caches / plugin state: PK = whatever namespace the caller picks
|
|
334
349
|
// (`cache:{ns}`, `pluginstate:{name}:...`). TTL set → DynamoDB
|
|
@@ -350,7 +365,6 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
350
365
|
// Custom return type for public post reads. Decoupling from `Post` lets
|
|
351
366
|
// AppSync skip the model-level (admin-only) auth check on fields.
|
|
352
367
|
PublicPost: a.customType({
|
|
353
|
-
siteId: a.string().required(),
|
|
354
368
|
postId: a.id().required(),
|
|
355
369
|
slug: a.string().required(),
|
|
356
370
|
title: a.string().required(),
|
|
@@ -377,7 +391,6 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
377
391
|
// only thing guests can see is `status === 'published'` rows projected
|
|
378
392
|
// onto `PublicPost`.
|
|
379
393
|
listPublishedPosts: a.query().arguments({
|
|
380
|
-
siteId: a.string(),
|
|
381
394
|
// Both ISO 8601 strings; query SK condition is pushed into DynamoDB
|
|
382
395
|
// so only the matching publishedAt range is read.
|
|
383
396
|
from: a.datetime(),
|
|
@@ -395,7 +408,7 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
395
408
|
allow.publicApiKey(),
|
|
396
409
|
allow.groups(["ampless-admin", "ampless-editor"])
|
|
397
410
|
]),
|
|
398
|
-
getPublishedPost: a.query().arguments({
|
|
411
|
+
getPublishedPost: a.query().arguments({ slug: a.string().required() }).returns(a.ref("PublicPost")).handler(
|
|
399
412
|
a.handler.custom({
|
|
400
413
|
dataSource: a.ref("Post"),
|
|
401
414
|
entry: resolverPaths.getPublishedPost
|
|
@@ -407,7 +420,6 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
407
420
|
// Tag pages: newest-first only. Archive (date range) within a tag is not
|
|
408
421
|
// a common UX pattern; if it's ever needed, add `from`/`to` args here.
|
|
409
422
|
listPostsByTag: a.query().arguments({
|
|
410
|
-
siteId: a.string(),
|
|
411
423
|
tag: a.string().required(),
|
|
412
424
|
limit: a.integer(),
|
|
413
425
|
nextToken: a.string()
|
|
@@ -448,6 +460,13 @@ function extendAmplessSchema(a, custom, opts) {
|
|
|
448
460
|
...custom ?? {}
|
|
449
461
|
});
|
|
450
462
|
}
|
|
463
|
+
function amplessSchemaAuthorization(allow, opts = {}) {
|
|
464
|
+
const rules = [];
|
|
465
|
+
if (opts.mcpHandlerFunction) {
|
|
466
|
+
rules.push(allow.resource(opts.mcpHandlerFunction).to(["query", "mutate"]));
|
|
467
|
+
}
|
|
468
|
+
return rules;
|
|
469
|
+
}
|
|
451
470
|
var defaultAuthorizationModes = {
|
|
452
471
|
defaultAuthorizationMode: "userPool",
|
|
453
472
|
apiKeyAuthorizationMode: { expiresInDays: 365 }
|
|
@@ -455,6 +474,7 @@ var defaultAuthorizationModes = {
|
|
|
455
474
|
export {
|
|
456
475
|
DEFAULT_RESOLVER_PATHS,
|
|
457
476
|
amplessAuthConfig,
|
|
477
|
+
amplessSchemaAuthorization,
|
|
458
478
|
amplessSchemaModels,
|
|
459
479
|
amplessStorageConfig,
|
|
460
480
|
defaultAuthorizationModes,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/backend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.19",
|
|
4
4
|
"description": "Amplify Gen 2 backend factories for ampless: auth, data, storage, event processors, API key renewer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -53,14 +53,20 @@
|
|
|
53
53
|
"homepage": "https://github.com/heavymoons/ampless/tree/main/packages/backend#readme",
|
|
54
54
|
"bugs": "https://github.com/heavymoons/ampless/issues",
|
|
55
55
|
"dependencies": {
|
|
56
|
+
"@aws-crypto/sha256-js": "^5.2.0",
|
|
56
57
|
"@aws-sdk/client-appsync": "^3.717.0",
|
|
57
58
|
"@aws-sdk/client-cognito-identity-provider": "^3.717.0",
|
|
58
59
|
"@aws-sdk/client-dynamodb": "^3.717.0",
|
|
59
60
|
"@aws-sdk/client-s3": "^3.1048.0",
|
|
60
61
|
"@aws-sdk/client-sqs": "^3.717.0",
|
|
62
|
+
"@aws-sdk/credential-provider-node": "^3.717.0",
|
|
61
63
|
"@aws-sdk/lib-dynamodb": "^3.717.0",
|
|
62
64
|
"@aws-sdk/util-dynamodb": "^3.717.0",
|
|
63
|
-
"
|
|
65
|
+
"@smithy/protocol-http": "^5.3.0",
|
|
66
|
+
"@smithy/signature-v4": "^5.4.0",
|
|
67
|
+
"fflate": "^0.8.2",
|
|
68
|
+
"ampless": "1.0.0-alpha.9",
|
|
69
|
+
"@ampless/mcp-server": "1.0.0-alpha.10"
|
|
64
70
|
},
|
|
65
71
|
"peerDependencies": {
|
|
66
72
|
"@aws-amplify/backend": "^1",
|