@ampless/backend 0.2.0-alpha.1 → 0.2.0-alpha.11
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/README.ja.md +130 -0
- package/README.md +3 -0
- package/dist/auth/post-confirmation.js +2 -0
- package/dist/auth/user-admin.d.ts +49 -0
- package/dist/auth/user-admin.js +107 -0
- package/dist/chunk-BYXBJQAS.js +0 -0
- package/dist/events/dispatcher.js +2 -0
- package/dist/events/processor-trusted.d.ts +6 -2
- package/dist/events/processor-trusted.js +2 -0
- package/dist/events/processor-untrusted.d.ts +6 -2
- package/dist/events/processor-untrusted.js +2 -0
- package/dist/functions/api-key-renewer.js +2 -0
- package/dist/functions/mcp-handler.d.ts +41 -0
- package/dist/functions/mcp-handler.js +754 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +88 -8
- package/package.json +15 -2
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ interface DefineAmplessBackendOpts {
|
|
|
13
13
|
processorTrusted: FunctionResource;
|
|
14
14
|
processorUntrusted: FunctionResource;
|
|
15
15
|
apiKeyRenewer: FunctionResource;
|
|
16
|
+
userAdmin: FunctionResource;
|
|
17
|
+
mcpHandler: FunctionResource;
|
|
16
18
|
}
|
|
17
19
|
type AmplessBackend = any;
|
|
18
20
|
/**
|
|
@@ -136,6 +138,33 @@ interface AmplessSchemaModelsOpts {
|
|
|
136
138
|
* they must point to actual `.js` files inside the user's project.
|
|
137
139
|
*/
|
|
138
140
|
resolverPaths?: Partial<AmplessResolverPaths>;
|
|
141
|
+
/**
|
|
142
|
+
* Optional Amplify `defineFunction` ref backing the user-admin
|
|
143
|
+
* AppSync ops (`listAdminUsers` query, `setAdminUserRole` mutation).
|
|
144
|
+
* When supplied, the corresponding `AdminUser` customType + the two
|
|
145
|
+
* ops are added to the schema, both gated to `ampless-admin`.
|
|
146
|
+
*
|
|
147
|
+
* Typed as `unknown` because `defineFunction`'s return type carries
|
|
148
|
+
* internal pnpm paths that don't survive declaration emit — same
|
|
149
|
+
* pattern as `AmplessAuthConfigOpts.postConfirmation`.
|
|
150
|
+
*/
|
|
151
|
+
userAdminFunction?: unknown;
|
|
152
|
+
/**
|
|
153
|
+
* Optional Amplify `defineFunction` ref backing the MCP HTTP Lambda.
|
|
154
|
+
* When supplied, the Post and PostTag models gain an `allow.resource(...)`
|
|
155
|
+
* authorization clause granting the Lambda `query` + `mutate` access
|
|
156
|
+
* via AppSync IAM auth. The existing group-based clauses stay intact —
|
|
157
|
+
* this just adds a second principal so the Lambda can dispatch tool
|
|
158
|
+
* calls under its own scoped role, with no Cognito identity or
|
|
159
|
+
* shared API key involved.
|
|
160
|
+
*
|
|
161
|
+
* Phase 4 covers Post + PostTag only (the read/write surface for the
|
|
162
|
+
* post CRUD tools). Media gets the same treatment in Phase 5 once
|
|
163
|
+
* the presigned-PUT upload flow lands.
|
|
164
|
+
*
|
|
165
|
+
* Typed as `unknown` for the same reason as `userAdminFunction`.
|
|
166
|
+
*/
|
|
167
|
+
mcpHandlerFunction?: unknown;
|
|
139
168
|
}
|
|
140
169
|
/**
|
|
141
170
|
* The Ampless model + custom query definitions, returned as a plain
|
|
@@ -153,6 +182,9 @@ interface AmplessSchemaModelsOpts {
|
|
|
153
182
|
* extra models.
|
|
154
183
|
*/
|
|
155
184
|
declare function amplessSchemaModels(a: any, opts?: AmplessSchemaModelsOpts): {
|
|
185
|
+
AdminUser?: any;
|
|
186
|
+
listAdminUsers?: any;
|
|
187
|
+
setAdminUserRole?: any;
|
|
156
188
|
Post: any;
|
|
157
189
|
Page: any;
|
|
158
190
|
Media: any;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
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";
|
|
4
6
|
import { Duration, Stack } from "aws-cdk-lib";
|
|
5
7
|
import { Queue } from "aws-cdk-lib/aws-sqs";
|
|
6
|
-
import { StartingPosition } from "aws-cdk-lib/aws-lambda";
|
|
8
|
+
import { FunctionUrlAuthType, HttpMethod, StartingPosition } from "aws-cdk-lib/aws-lambda";
|
|
7
9
|
import { DynamoEventSource, SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
|
8
10
|
import { Rule, Schedule } from "aws-cdk-lib/aws-events";
|
|
9
11
|
import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
|
|
@@ -16,7 +18,9 @@ function defineAmplessBackend(opts) {
|
|
|
16
18
|
eventDispatcher: opts.eventDispatcher,
|
|
17
19
|
processorTrusted: opts.processorTrusted,
|
|
18
20
|
processorUntrusted: opts.processorUntrusted,
|
|
19
|
-
apiKeyRenewer: opts.apiKeyRenewer
|
|
21
|
+
apiKeyRenewer: opts.apiKeyRenewer,
|
|
22
|
+
userAdmin: opts.userAdmin,
|
|
23
|
+
mcpHandler: opts.mcpHandler
|
|
20
24
|
});
|
|
21
25
|
const cfnBucket = backend.storage.resources.cfnResources.cfnBucket;
|
|
22
26
|
cfnBucket.publicAccessBlockConfiguration = {
|
|
@@ -61,6 +65,22 @@ function defineAmplessBackend(opts) {
|
|
|
61
65
|
resources: ["arn:aws:cognito-idp:*:*:userpool/*"]
|
|
62
66
|
})
|
|
63
67
|
);
|
|
68
|
+
backend.userAdmin.resources.lambda.addToRolePolicy(
|
|
69
|
+
new PolicyStatement({
|
|
70
|
+
effect: Effect.ALLOW,
|
|
71
|
+
actions: [
|
|
72
|
+
"cognito-idp:ListUsers",
|
|
73
|
+
"cognito-idp:AdminListGroupsForUser",
|
|
74
|
+
"cognito-idp:AdminAddUserToGroup",
|
|
75
|
+
"cognito-idp:AdminRemoveUserFromGroup"
|
|
76
|
+
],
|
|
77
|
+
resources: ["arn:aws:cognito-idp:*:*:userpool/*"]
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
backend.userAdmin.resources.lambda.addEnvironment(
|
|
81
|
+
"AMPLESS_USER_POOL_ID",
|
|
82
|
+
backend.auth.resources.userPool.userPoolId
|
|
83
|
+
);
|
|
64
84
|
const postTable = backend.data.resources.tables["Post"];
|
|
65
85
|
const cfnPostTable = backend.data.resources.cfnResources.amplifyDynamoDbTables["Post"];
|
|
66
86
|
cfnPostTable.streamSpecification = { streamViewType: "NEW_AND_OLD_IMAGES" };
|
|
@@ -149,6 +169,35 @@ function defineAmplessBackend(opts) {
|
|
|
149
169
|
schedule: Schedule.cron({ minute: "0", hour: "3", day: "1", month: "*", year: "*" }),
|
|
150
170
|
targets: [new LambdaFunction(apiKeyRenewerFn)]
|
|
151
171
|
});
|
|
172
|
+
const mcpHandlerFn = backend.mcpHandler.resources.lambda;
|
|
173
|
+
mcpHandlerFn.addToRolePolicy(
|
|
174
|
+
new PolicyStatement({
|
|
175
|
+
effect: Effect.ALLOW,
|
|
176
|
+
actions: ["dynamodb:GetItem"],
|
|
177
|
+
resources: [kvTable.tableArn]
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
mcpHandlerFn.addEnvironment("AMPLESS_KV_TABLE", kvTable.tableName);
|
|
181
|
+
mcpHandlerFn.addEnvironment(
|
|
182
|
+
"AMPLESS_APPSYNC_URL",
|
|
183
|
+
backend.data.resources.cfnResources.cfnGraphqlApi.attrGraphQlUrl
|
|
184
|
+
);
|
|
185
|
+
const mcpFunctionUrl = mcpHandlerFn.addFunctionUrl({
|
|
186
|
+
authType: FunctionUrlAuthType.NONE,
|
|
187
|
+
cors: {
|
|
188
|
+
allowedOrigins: ["*"],
|
|
189
|
+
allowedMethods: [HttpMethod.POST],
|
|
190
|
+
allowedHeaders: ["*"],
|
|
191
|
+
maxAge: Duration.hours(1)
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
backend.addOutput({
|
|
195
|
+
custom: {
|
|
196
|
+
mcp: {
|
|
197
|
+
endpoint: mcpFunctionUrl.url
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
152
201
|
return backend;
|
|
153
202
|
}
|
|
154
203
|
|
|
@@ -201,11 +250,19 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
201
250
|
slug: a.string().required(),
|
|
202
251
|
title: a.string().required(),
|
|
203
252
|
excerpt: a.string(),
|
|
204
|
-
format: a.enum(["tiptap", "markdown", "html"]),
|
|
253
|
+
format: a.enum(["tiptap", "markdown", "html", "static"]),
|
|
205
254
|
body: a.json(),
|
|
206
255
|
status: a.enum(["draft", "published"]),
|
|
207
256
|
publishedAt: a.datetime(),
|
|
208
257
|
tags: a.string().array(),
|
|
258
|
+
// Free-form per-post metadata (JSON). Reserved well-known keys
|
|
259
|
+
// are documented on `PostMetadata` in `ampless/src/types.ts`.
|
|
260
|
+
// Currently:
|
|
261
|
+
// no_layout: boolean — serve the post as bare HTML (no theme
|
|
262
|
+
// chrome). The runtime's post dispatcher redirects to the
|
|
263
|
+
// raw route handler when this is true.
|
|
264
|
+
// Other keys are passed through unchanged for plugin / app use.
|
|
265
|
+
metadata: a.json(),
|
|
209
266
|
// Denormalized GSI keys — set by every write path (admin client,
|
|
210
267
|
// MCP tools). Same pattern as siteIdStatus: composing the
|
|
211
268
|
// partition key as a single string lets each public-read query
|
|
@@ -220,14 +277,15 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
220
277
|
index("siteIdStatus").sortKeys(["publishedAt"]).name("bySiteIdStatus"),
|
|
221
278
|
index("siteIdSlug").name("bySiteIdSlug")
|
|
222
279
|
]).authorization((allow) => [
|
|
223
|
-
allow.groups(["ampless-admin", "ampless-editor"])
|
|
280
|
+
allow.groups(["ampless-admin", "ampless-editor"]),
|
|
281
|
+
...opts.mcpHandlerFunction ? [allow.resource(opts.mcpHandlerFunction).to(["query", "mutate"])] : []
|
|
224
282
|
]),
|
|
225
283
|
Page: a.model({
|
|
226
284
|
siteId: a.string().required(),
|
|
227
285
|
pageId: a.id().required(),
|
|
228
286
|
slug: a.string().required(),
|
|
229
287
|
title: a.string().required(),
|
|
230
|
-
format: a.enum(["tiptap", "markdown", "html"]),
|
|
288
|
+
format: a.enum(["tiptap", "markdown", "html", "static"]),
|
|
231
289
|
body: a.json(),
|
|
232
290
|
status: a.enum(["draft", "published"]),
|
|
233
291
|
publishedAt: a.datetime()
|
|
@@ -274,7 +332,8 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
274
332
|
// Full tag list of the post (for chip rendering on tag pages).
|
|
275
333
|
tags: a.string().array()
|
|
276
334
|
}).identifier(["siteIdTag", "publishedAtPostId"]).authorization((allow) => [
|
|
277
|
-
allow.groups(["ampless-admin", "ampless-editor"])
|
|
335
|
+
allow.groups(["ampless-admin", "ampless-editor"]),
|
|
336
|
+
...opts.mcpHandlerFunction ? [allow.resource(opts.mcpHandlerFunction).to(["query", "mutate"])] : []
|
|
278
337
|
]),
|
|
279
338
|
// Generic key/value store. Two roles in one table:
|
|
280
339
|
// - Site settings: PK = `siteconfig:{siteId}`, SK = dotted key
|
|
@@ -308,7 +367,8 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
308
367
|
body: a.json(),
|
|
309
368
|
status: a.string(),
|
|
310
369
|
publishedAt: a.datetime(),
|
|
311
|
-
tags: a.string().array()
|
|
370
|
+
tags: a.string().array(),
|
|
371
|
+
metadata: a.json()
|
|
312
372
|
}),
|
|
313
373
|
// Paginated wrapper for list responses.
|
|
314
374
|
PublicPostConnection: a.customType({
|
|
@@ -367,7 +427,27 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
367
427
|
).authorization((allow) => [
|
|
368
428
|
allow.publicApiKey(),
|
|
369
429
|
allow.groups(["ampless-admin", "ampless-editor"])
|
|
370
|
-
])
|
|
430
|
+
]),
|
|
431
|
+
// User management ops, only wired when the caller supplies a
|
|
432
|
+
// Lambda function ref. Conditionally spread because
|
|
433
|
+
// `a.handler.function(undefined)` is not a valid call — projects
|
|
434
|
+
// that haven't opted into the user-admin Lambda must not see
|
|
435
|
+
// these schema entries.
|
|
436
|
+
...opts.userAdminFunction ? {
|
|
437
|
+
AdminUser: a.customType({
|
|
438
|
+
userId: a.string().required(),
|
|
439
|
+
email: a.string().required(),
|
|
440
|
+
// 'admin' | 'editor' | 'none' — stored as string because
|
|
441
|
+
// a.enum() in customType doesn't round-trip cleanly across
|
|
442
|
+
// AppSync's typegen + the admin client cast pattern.
|
|
443
|
+
role: a.string().required()
|
|
444
|
+
}),
|
|
445
|
+
listAdminUsers: a.query().returns(a.ref("AdminUser").array()).handler(a.handler.function(opts.userAdminFunction)).authorization((allow) => [allow.groups(["ampless-admin"])]),
|
|
446
|
+
setAdminUserRole: a.mutation().arguments({
|
|
447
|
+
userId: a.string().required(),
|
|
448
|
+
role: a.string().required()
|
|
449
|
+
}).returns(a.ref("AdminUser")).handler(a.handler.function(opts.userAdminFunction)).authorization((allow) => [allow.groups(["ampless-admin"])])
|
|
450
|
+
} : {}
|
|
371
451
|
};
|
|
372
452
|
}
|
|
373
453
|
function extendAmplessSchema(a, custom, opts) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ampless/backend",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.11",
|
|
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",
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"import": "./dist/auth/post-confirmation.js",
|
|
14
14
|
"types": "./dist/auth/post-confirmation.d.ts"
|
|
15
15
|
},
|
|
16
|
+
"./auth/user-admin": {
|
|
17
|
+
"import": "./dist/auth/user-admin.js",
|
|
18
|
+
"types": "./dist/auth/user-admin.d.ts"
|
|
19
|
+
},
|
|
16
20
|
"./events/dispatcher": {
|
|
17
21
|
"import": "./dist/events/dispatcher.js",
|
|
18
22
|
"types": "./dist/events/dispatcher.d.ts"
|
|
@@ -28,6 +32,10 @@
|
|
|
28
32
|
"./functions/api-key-renewer": {
|
|
29
33
|
"import": "./dist/functions/api-key-renewer.js",
|
|
30
34
|
"types": "./dist/functions/api-key-renewer.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./functions/mcp-handler": {
|
|
37
|
+
"import": "./dist/functions/mcp-handler.js",
|
|
38
|
+
"types": "./dist/functions/mcp-handler.d.ts"
|
|
31
39
|
}
|
|
32
40
|
},
|
|
33
41
|
"files": [
|
|
@@ -45,14 +53,19 @@
|
|
|
45
53
|
"homepage": "https://github.com/heavymoons/ampless/tree/main/packages/backend#readme",
|
|
46
54
|
"bugs": "https://github.com/heavymoons/ampless/issues",
|
|
47
55
|
"dependencies": {
|
|
56
|
+
"@aws-crypto/sha256-js": "^5.2.0",
|
|
48
57
|
"@aws-sdk/client-appsync": "^3.717.0",
|
|
49
58
|
"@aws-sdk/client-cognito-identity-provider": "^3.717.0",
|
|
50
59
|
"@aws-sdk/client-dynamodb": "^3.717.0",
|
|
51
60
|
"@aws-sdk/client-s3": "^3.1048.0",
|
|
52
61
|
"@aws-sdk/client-sqs": "^3.717.0",
|
|
62
|
+
"@aws-sdk/credential-provider-node": "^3.717.0",
|
|
53
63
|
"@aws-sdk/lib-dynamodb": "^3.717.0",
|
|
54
64
|
"@aws-sdk/util-dynamodb": "^3.717.0",
|
|
55
|
-
"
|
|
65
|
+
"@smithy/protocol-http": "^5.3.0",
|
|
66
|
+
"@smithy/signature-v4": "^5.4.0",
|
|
67
|
+
"ampless": "0.2.0-alpha.6",
|
|
68
|
+
"@ampless/mcp-server": "0.2.0-alpha.6"
|
|
56
69
|
},
|
|
57
70
|
"peerDependencies": {
|
|
58
71
|
"@aws-amplify/backend": "^1",
|