@ampless/backend 0.2.0-alpha.0 → 0.2.0-alpha.10
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 +14 -5
- package/dist/auth/user-admin.d.ts +49 -0
- package/dist/auth/user-admin.js +105 -0
- package/dist/events/processor-trusted.d.ts +6 -2
- package/dist/events/processor-untrusted.d.ts +6 -2
- package/dist/functions/mcp-handler.d.ts +33 -0
- package/dist/functions/mcp-handler.js +70 -0
- package/dist/index.d.ts +56 -12
- package/dist/index.js +86 -16
- package/package.json +11 -3
package/README.ja.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
> English: [README.md](./README.md)
|
|
2
|
+
>
|
|
3
|
+
|
|
4
|
+
# @ampless/backend
|
|
5
|
+
|
|
6
|
+
[ampless](https://github.com/heavymoons/ampless) 向け Amplify Gen 2 バックエンドファクトリー。IAM / SQS / DynamoDB ストリームの配線、認証 / データ / ストレージの定義、およびすべてのイベント処理 Lambda を `defineAmplessBackend(...)` ひとつのファクトリーにまとめます。
|
|
7
|
+
|
|
8
|
+
> **プレリリース / アルファ版。** v1.0 まではマイナーバージョンでも破壊的変更が入る可能性があります。
|
|
9
|
+
|
|
10
|
+
テンプレートから切り出すことで、スキャフォールダーを再実行せずに `npm update @ampless/backend` でアップデートできます。バックエンドのバグ修正やインフラ改善はパッケージ経由で届きます。ユーザー側の `amplify/` ツリーは、ファクトリーを組み合わせる 1〜5 行のシェルだけで済みます。
|
|
11
|
+
|
|
12
|
+
## インストール
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @ampless/backend@alpha ampless@alpha
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
ピア依存: `@aws-amplify/backend`(^1)、`aws-cdk-lib`(^2)。CLI スキャフォールダーがテンプレートの `package.json` に互換バージョンをピン留めします。
|
|
19
|
+
|
|
20
|
+
## 使い方
|
|
21
|
+
|
|
22
|
+
### `amplify/backend.ts`
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { defineAmplessBackend } from '@ampless/backend'
|
|
26
|
+
import { auth } from './auth/resource'
|
|
27
|
+
import { data } from './data/resource'
|
|
28
|
+
import { storage } from './storage/resource'
|
|
29
|
+
import { postConfirmation } from './auth/post-confirmation/resource'
|
|
30
|
+
import { eventDispatcher } from './events/dispatcher/resource'
|
|
31
|
+
import { processorTrusted } from './events/processor-trusted/resource'
|
|
32
|
+
import { processorUntrusted } from './events/processor-untrusted/resource'
|
|
33
|
+
import { apiKeyRenewer } from './functions/api-key-renewer/resource'
|
|
34
|
+
|
|
35
|
+
export default defineAmplessBackend({
|
|
36
|
+
auth, data, storage, postConfirmation,
|
|
37
|
+
eventDispatcher, processorTrusted, processorUntrusted, apiKeyRenewer,
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `amplify/auth/resource.ts`
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { defineAuth } from '@aws-amplify/backend'
|
|
45
|
+
import { amplessAuthConfig } from '@ampless/backend'
|
|
46
|
+
import { postConfirmation } from './post-confirmation/resource'
|
|
47
|
+
|
|
48
|
+
export const auth = defineAuth(amplessAuthConfig({ postConfirmation }))
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
> `defineAuth` は `amplify/auth/resource.ts` 自体に記述する必要があります。Amplify Gen 2 のインポートパス検証機能が `defineAuth` / `defineData` / `defineStorage` の呼び出し元を検査し、他のファイル(`node_modules/@ampless/backend/...` 配下のラッパーを含む)から呼び出された場合に `Amplify Auth must be defined in amplify/auth/resource.ts` というエラーを投げます。`amplessAuthConfig` は props オブジェクトを返すため、ampless のデフォルト設定を失わずにこのファイルで `defineAuth(...)` を呼び出せます。
|
|
52
|
+
|
|
53
|
+
### `amplify/data/resource.ts`
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { a, defineData, type ClientSchema } from '@aws-amplify/backend'
|
|
57
|
+
import { amplessSchemaModels, defaultAuthorizationModes } from '@ampless/backend'
|
|
58
|
+
|
|
59
|
+
const schema = a.schema({
|
|
60
|
+
...amplessSchemaModels(a),
|
|
61
|
+
// カスタムモデルをここに追加 — 組み込みモデルと同居します:
|
|
62
|
+
// MyCustomModel: a.model({ ... }).authorization((allow) => [...]),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
export type Schema = ClientSchema<typeof schema>
|
|
66
|
+
export const data = defineData({ schema, authorizationModes: defaultAuthorizationModes })
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
3 つの AppSync JS リゾルバーファイル(`list-published-posts.js`、`get-published-post.js`、`list-posts-by-tag.js`)はテンプレート内に残ります — AppSync がリゾルバーの `entry` パスを CDK synth 時に `defineData` を呼び出すファイルからの相対パスで解決するため、pnpm でシンボリックリンクされた `node_modules` パスは解決を通過できません。`amplify/data/` 以外の場所に移動する場合は、`amplessSchemaModels(a, { resolverPaths })` で新しいパスを渡してください。
|
|
70
|
+
|
|
71
|
+
### `amplify/storage/resource.ts`
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { defineStorage } from '@aws-amplify/backend'
|
|
75
|
+
import { amplessStorageConfig } from '@ampless/backend'
|
|
76
|
+
export const storage = defineStorage(amplessStorageConfig())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
> 認証と同じインポートパス制約があります — `defineStorage` はこのファイルから直接呼び出す必要があります。`amplessStorageConfig` は props オブジェクトを返します。
|
|
80
|
+
|
|
81
|
+
### Lambda 薄型シェル
|
|
82
|
+
|
|
83
|
+
`amplify/auth/`、`amplify/events/`、`amplify/functions/` 内の各ハンドラーファイルは 1〜3 行の re-export になります。Amplify の esbuild がこのパッケージへのインポートを追跡し、実際のハンドラーコードを Lambda アーティファクトにバンドルします。
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// amplify/auth/post-confirmation/handler.ts
|
|
87
|
+
export { handler } from '@ampless/backend/auth/post-confirmation'
|
|
88
|
+
|
|
89
|
+
// amplify/events/dispatcher/handler.ts
|
|
90
|
+
export { handler } from '@ampless/backend/events/dispatcher'
|
|
91
|
+
|
|
92
|
+
// amplify/events/processor-trusted/handler.ts
|
|
93
|
+
import config from '../../../cms.config'
|
|
94
|
+
import { createProcessorTrustedHandler } from '@ampless/backend/events/processor-trusted'
|
|
95
|
+
export const handler = createProcessorTrustedHandler({
|
|
96
|
+
plugins: config.plugins,
|
|
97
|
+
site: config.site,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// amplify/events/processor-untrusted/handler.ts
|
|
101
|
+
import config from '../../../cms.config'
|
|
102
|
+
import { createProcessorUntrustedHandler } from '@ampless/backend/events/processor-untrusted'
|
|
103
|
+
export const handler = createProcessorUntrustedHandler({
|
|
104
|
+
plugins: config.plugins,
|
|
105
|
+
site: config.site,
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// amplify/functions/api-key-renewer/handler.ts
|
|
109
|
+
export { handler } from '@ampless/backend/functions/api-key-renewer'
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## サブパス
|
|
113
|
+
|
|
114
|
+
- `@ampless/backend` — `defineAmplessBackend`、`amplessAuthConfig`、`amplessStorageConfig`、`amplessSchemaModels`、`extendAmplessSchema`、`defaultAuthorizationModes`
|
|
115
|
+
- `@ampless/backend/auth/post-confirmation` — Lambda ハンドラー
|
|
116
|
+
- `@ampless/backend/events/dispatcher` — Lambda ハンドラー
|
|
117
|
+
- `@ampless/backend/events/processor-trusted` — `createProcessorTrustedHandler({ plugins, site })`
|
|
118
|
+
- `@ampless/backend/events/processor-untrusted` — `createProcessorUntrustedHandler({ plugins, site })`
|
|
119
|
+
- `@ampless/backend/functions/api-key-renewer` — Lambda ハンドラー
|
|
120
|
+
|
|
121
|
+
## テンプレートに残るもの
|
|
122
|
+
|
|
123
|
+
- `amplify/data/*.js` — AppSync JS リゾルバー(ファイルパス制約のため)。
|
|
124
|
+
- すべての `resource.ts` と `handler.ts` — 薄型シェルですが、Amplify の CDK synth がユーザー側で解決する `entry: './handler.ts'` パスを保持しています。
|
|
125
|
+
- `cms.config.ts` と `themes-registry.ts` — ユーザーが所有するカスタマイズサーフェス。
|
|
126
|
+
- `templates/<theme>/` 以下のテーマコンポーネント — ユーザーが所有。
|
|
127
|
+
|
|
128
|
+
## ライセンス
|
|
129
|
+
|
|
130
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
> 日本語版: [README.ja.md](./README.ja.md)
|
|
2
|
+
>
|
|
3
|
+
|
|
1
4
|
# @ampless/backend
|
|
2
5
|
|
|
3
6
|
Amplify Gen 2 backend factories for [ampless](https://github.com/heavymoons/ampless). Bundles the IAM / SQS / DynamoDB-stream wiring, the auth / data / storage definitions, and every event-processing Lambda behind one `defineAmplessBackend(...)` factory.
|
|
@@ -38,12 +41,15 @@ export default defineAmplessBackend({
|
|
|
38
41
|
### `amplify/auth/resource.ts`
|
|
39
42
|
|
|
40
43
|
```ts
|
|
41
|
-
import {
|
|
44
|
+
import { defineAuth } from '@aws-amplify/backend'
|
|
45
|
+
import { amplessAuthConfig } from '@ampless/backend'
|
|
42
46
|
import { postConfirmation } from './post-confirmation/resource'
|
|
43
47
|
|
|
44
|
-
export const auth =
|
|
48
|
+
export const auth = defineAuth(amplessAuthConfig({ postConfirmation }))
|
|
45
49
|
```
|
|
46
50
|
|
|
51
|
+
> `defineAuth` must live in `amplify/auth/resource.ts` itself. Amplify Gen 2's import-path verifier inspects the call site of `defineAuth` / `defineData` / `defineStorage` and throws `Amplify Auth must be defined in amplify/auth/resource.ts` if it's invoked from any other file (including a `node_modules/@ampless/backend/...` wrapper). `amplessAuthConfig` returns the props object so you can call `defineAuth(...)` here without losing the ampless defaults.
|
|
52
|
+
|
|
47
53
|
### `amplify/data/resource.ts`
|
|
48
54
|
|
|
49
55
|
```ts
|
|
@@ -65,10 +71,13 @@ The three AppSync JS resolver files (`list-published-posts.js`, `get-published-p
|
|
|
65
71
|
### `amplify/storage/resource.ts`
|
|
66
72
|
|
|
67
73
|
```ts
|
|
68
|
-
import {
|
|
69
|
-
|
|
74
|
+
import { defineStorage } from '@aws-amplify/backend'
|
|
75
|
+
import { amplessStorageConfig } from '@ampless/backend'
|
|
76
|
+
export const storage = defineStorage(amplessStorageConfig())
|
|
70
77
|
```
|
|
71
78
|
|
|
79
|
+
> Same import-path verifier constraint as auth — `defineStorage` has to be called from this file directly. `amplessStorageConfig` returns the props object.
|
|
80
|
+
|
|
72
81
|
### Lambda thin shells
|
|
73
82
|
|
|
74
83
|
Every handler file in `amplify/auth/`, `amplify/events/`, and `amplify/functions/` becomes a 1–3 line re-export. Amplify's esbuild follows the import into this package and bundles the real handler code into the Lambda artifact.
|
|
@@ -102,7 +111,7 @@ export { handler } from '@ampless/backend/functions/api-key-renewer'
|
|
|
102
111
|
|
|
103
112
|
## Sub-paths
|
|
104
113
|
|
|
105
|
-
- `@ampless/backend` — `defineAmplessBackend`, `
|
|
114
|
+
- `@ampless/backend` — `defineAmplessBackend`, `amplessAuthConfig`, `amplessStorageConfig`, `amplessSchemaModels`, `extendAmplessSchema`, `defaultAuthorizationModes`
|
|
106
115
|
- `@ampless/backend/auth/post-confirmation` — Lambda handler
|
|
107
116
|
- `@ampless/backend/events/dispatcher` — Lambda handler
|
|
108
117
|
- `@ampless/backend/events/processor-trusted` — `createProcessorTrustedHandler({ plugins, site })`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Handler } from 'aws-lambda';
|
|
2
|
+
|
|
3
|
+
type AdminRole = 'admin' | 'editor' | 'none';
|
|
4
|
+
interface AdminUserDto {
|
|
5
|
+
userId: string;
|
|
6
|
+
email: string;
|
|
7
|
+
role: AdminRole;
|
|
8
|
+
}
|
|
9
|
+
interface SetArgs {
|
|
10
|
+
userId: string;
|
|
11
|
+
role: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Event shape that Amplify Gen 2 actually delivers to a Lambda data
|
|
15
|
+
* source attached via `a.handler.function()`. The CDK generated
|
|
16
|
+
* AppSync resource is a PIPELINE resolver whose Lambda-invocation
|
|
17
|
+
* function uses a VTL request mapping template that emits a flat
|
|
18
|
+
* payload:
|
|
19
|
+
*
|
|
20
|
+
* {
|
|
21
|
+
* "operation": "Invoke",
|
|
22
|
+
* "payload": {
|
|
23
|
+
* "typeName": "Query",
|
|
24
|
+
* "fieldName": "listAdminUsers",
|
|
25
|
+
* "arguments": { ... },
|
|
26
|
+
* "identity": { ... },
|
|
27
|
+
* "source": ..., "request": ..., "prev": ...
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* Notably this is NOT the canonical `AppSyncResolverEvent` shape
|
|
32
|
+
* (which has `event.info.fieldName`). The `aws-lambda` package's
|
|
33
|
+
* `AppSyncResolverHandler` type is misleading here — typing the
|
|
34
|
+
* handler with it sends `event.info.fieldName` to `undefined` and
|
|
35
|
+
* blows up at runtime with
|
|
36
|
+
* `Cannot read properties of undefined (reading 'fieldName')`.
|
|
37
|
+
*/
|
|
38
|
+
interface UserAdminEvent {
|
|
39
|
+
typeName: string;
|
|
40
|
+
fieldName: string;
|
|
41
|
+
arguments: Partial<SetArgs>;
|
|
42
|
+
identity?: unknown;
|
|
43
|
+
source?: unknown;
|
|
44
|
+
request?: unknown;
|
|
45
|
+
prev?: unknown;
|
|
46
|
+
}
|
|
47
|
+
declare const handler: Handler<UserAdminEvent, AdminUserDto | AdminUserDto[] | null>;
|
|
48
|
+
|
|
49
|
+
export { handler };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// src/auth/user-admin.ts
|
|
2
|
+
import {
|
|
3
|
+
CognitoIdentityProviderClient,
|
|
4
|
+
ListUsersCommand,
|
|
5
|
+
AdminListGroupsForUserCommand,
|
|
6
|
+
AdminAddUserToGroupCommand,
|
|
7
|
+
AdminRemoveUserFromGroupCommand
|
|
8
|
+
} from "@aws-sdk/client-cognito-identity-provider";
|
|
9
|
+
var cognito = new CognitoIdentityProviderClient({});
|
|
10
|
+
var ADMIN_GROUP = "ampless-admin";
|
|
11
|
+
var EDITOR_GROUP = "ampless-editor";
|
|
12
|
+
function requireUserPoolId() {
|
|
13
|
+
const v = process.env.AMPLESS_USER_POOL_ID;
|
|
14
|
+
if (!v) {
|
|
15
|
+
console.error("[user-admin] missing required env var AMPLESS_USER_POOL_ID");
|
|
16
|
+
throw new Error("user-admin: missing required env var AMPLESS_USER_POOL_ID");
|
|
17
|
+
}
|
|
18
|
+
return v;
|
|
19
|
+
}
|
|
20
|
+
function emailOf(user) {
|
|
21
|
+
const attr = user.Attributes?.find((a) => a.Name === "email");
|
|
22
|
+
return attr?.Value ?? "";
|
|
23
|
+
}
|
|
24
|
+
async function roleOf(userPoolId, username) {
|
|
25
|
+
const { Groups } = await cognito.send(
|
|
26
|
+
new AdminListGroupsForUserCommand({ UserPoolId: userPoolId, Username: username })
|
|
27
|
+
);
|
|
28
|
+
const names = (Groups ?? []).map((g) => g.GroupName);
|
|
29
|
+
if (names.includes(ADMIN_GROUP)) return "admin";
|
|
30
|
+
if (names.includes(EDITOR_GROUP)) return "editor";
|
|
31
|
+
return "none";
|
|
32
|
+
}
|
|
33
|
+
async function listAdminUsers(userPoolId) {
|
|
34
|
+
const { Users } = await cognito.send(
|
|
35
|
+
new ListUsersCommand({ UserPoolId: userPoolId, Limit: 60 })
|
|
36
|
+
);
|
|
37
|
+
const users = Users ?? [];
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const u of users) {
|
|
40
|
+
if (!u.Username) continue;
|
|
41
|
+
const role = await roleOf(userPoolId, u.Username);
|
|
42
|
+
out.push({ userId: u.Username, email: emailOf(u), role });
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
async function setAdminUserRole(userPoolId, userId, role) {
|
|
47
|
+
for (const group of [ADMIN_GROUP, EDITOR_GROUP]) {
|
|
48
|
+
await cognito.send(
|
|
49
|
+
new AdminRemoveUserFromGroupCommand({
|
|
50
|
+
UserPoolId: userPoolId,
|
|
51
|
+
Username: userId,
|
|
52
|
+
GroupName: group
|
|
53
|
+
})
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (role !== "none") {
|
|
57
|
+
await cognito.send(
|
|
58
|
+
new AdminAddUserToGroupCommand({
|
|
59
|
+
UserPoolId: userPoolId,
|
|
60
|
+
Username: userId,
|
|
61
|
+
GroupName: role === "admin" ? ADMIN_GROUP : EDITOR_GROUP
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const { Users } = await cognito.send(
|
|
66
|
+
new ListUsersCommand({
|
|
67
|
+
UserPoolId: userPoolId,
|
|
68
|
+
Filter: `sub = "${userId}"`,
|
|
69
|
+
Limit: 1
|
|
70
|
+
})
|
|
71
|
+
);
|
|
72
|
+
const user = Users?.[0];
|
|
73
|
+
const email = user ? emailOf(user) : "";
|
|
74
|
+
const finalRole = await roleOf(userPoolId, userId);
|
|
75
|
+
return { userId, email, role: finalRole };
|
|
76
|
+
}
|
|
77
|
+
var handler = async (event) => {
|
|
78
|
+
const userPoolId = requireUserPoolId();
|
|
79
|
+
const field = event.fieldName;
|
|
80
|
+
try {
|
|
81
|
+
if (field === "listAdminUsers") {
|
|
82
|
+
return await listAdminUsers(userPoolId);
|
|
83
|
+
}
|
|
84
|
+
if (field === "setAdminUserRole") {
|
|
85
|
+
const { userId, role } = event.arguments;
|
|
86
|
+
if (role !== "admin" && role !== "editor" && role !== "none") {
|
|
87
|
+
console.error(`[user-admin] invalid role: ${role}`);
|
|
88
|
+
throw new Error(`Invalid role: ${role}`);
|
|
89
|
+
}
|
|
90
|
+
if (!userId) {
|
|
91
|
+
console.error("[user-admin] missing userId argument");
|
|
92
|
+
throw new Error("Missing userId argument");
|
|
93
|
+
}
|
|
94
|
+
return await setAdminUserRole(userPoolId, userId, role);
|
|
95
|
+
}
|
|
96
|
+
console.error(`[user-admin] unsupported fieldName: ${field}`);
|
|
97
|
+
throw new Error(`Unsupported fieldName: ${field}`);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(`[user-admin] ${field} failed:`, err);
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
export {
|
|
104
|
+
handler
|
|
105
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SQSHandler } from 'aws-lambda';
|
|
2
|
-
import {
|
|
2
|
+
import { Config } from 'ampless';
|
|
3
3
|
|
|
4
4
|
interface CreateProcessorTrustedHandlerOpts {
|
|
5
5
|
/**
|
|
@@ -7,8 +7,12 @@ interface CreateProcessorTrustedHandlerOpts {
|
|
|
7
7
|
* trusted plugins itself so callers don't need to remember the
|
|
8
8
|
* filter, and so adding `privileged` later only touches the handler
|
|
9
9
|
* code in this package.
|
|
10
|
+
*
|
|
11
|
+
* Accepts the raw `Config['plugins']` shape (which permits string
|
|
12
|
+
* entries for future dynamic loading) — the runtime filter discards
|
|
13
|
+
* anything that isn't a plugin object.
|
|
10
14
|
*/
|
|
11
|
-
plugins?:
|
|
15
|
+
plugins?: Config['plugins'];
|
|
12
16
|
/**
|
|
13
17
|
* The `cms.config.site` block, surfaced to plugin hooks via
|
|
14
18
|
* `ctx.site`. Pass through from the thin shell — handlers must
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { SQSHandler } from 'aws-lambda';
|
|
2
|
-
import {
|
|
2
|
+
import { Config } from 'ampless';
|
|
3
3
|
|
|
4
4
|
interface CreateProcessorUntrustedHandlerOpts {
|
|
5
5
|
/**
|
|
6
6
|
* The full `cms.config.plugins` array. The handler filters down to
|
|
7
7
|
* untrusted plugins itself.
|
|
8
|
+
*
|
|
9
|
+
* Accepts the raw `Config['plugins']` shape (which permits string
|
|
10
|
+
* entries for future dynamic loading) — the runtime filter discards
|
|
11
|
+
* anything that isn't a plugin object.
|
|
8
12
|
*/
|
|
9
|
-
plugins?:
|
|
13
|
+
plugins?: Config['plugins'];
|
|
10
14
|
/**
|
|
11
15
|
* The `cms.config.site` block, surfaced to plugin hooks via
|
|
12
16
|
* `ctx.site` (read-only — untrusted plugins have no AWS-touching
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP HTTP endpoint Lambda. Phase 3: Bearer token validation only.
|
|
3
|
+
*
|
|
4
|
+
* Reads the KvStore table directly (PK = 'mcp-tokens', SK = SHA-256
|
|
5
|
+
* hash of the plaintext token) instead of going through AppSync — the
|
|
6
|
+
* Lambda IAM role only needs `dynamodb:GetItem` on that one table,
|
|
7
|
+
* which is much narrower than `appsync:GraphQL` and avoids the
|
|
8
|
+
* IAM-auth-mode complexity of letting Lambdas call AppSync as
|
|
9
|
+
* privileged identity. Phase 4 will add AppSync IAM auth when tool
|
|
10
|
+
* dispatch actually needs schema-aware access.
|
|
11
|
+
*
|
|
12
|
+
* Function URL event format: Lambda Function URLs emit API Gateway
|
|
13
|
+
* HTTP v2 events (https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads).
|
|
14
|
+
* Headers arrive lowercased.
|
|
15
|
+
*/
|
|
16
|
+
interface FunctionUrlEvent {
|
|
17
|
+
headers?: Record<string, string | undefined>;
|
|
18
|
+
body?: string;
|
|
19
|
+
isBase64Encoded?: boolean;
|
|
20
|
+
requestContext?: {
|
|
21
|
+
http?: {
|
|
22
|
+
method?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
interface FunctionUrlResult {
|
|
27
|
+
statusCode: number;
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
body: string;
|
|
30
|
+
}
|
|
31
|
+
declare const handler: (event: FunctionUrlEvent) => Promise<FunctionUrlResult>;
|
|
32
|
+
|
|
33
|
+
export { handler };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/functions/mcp-handler.ts
|
|
2
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
3
|
+
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";
|
|
4
|
+
import { createHash } from "crypto";
|
|
5
|
+
var TOKENS_PK = "mcp-tokens";
|
|
6
|
+
function requireEnv(name) {
|
|
7
|
+
const v = process.env[name];
|
|
8
|
+
if (!v) throw new Error(`[mcp-handler] missing required env var ${name}`);
|
|
9
|
+
return v;
|
|
10
|
+
}
|
|
11
|
+
var KV_TABLE = requireEnv("AMPLESS_KV_TABLE");
|
|
12
|
+
var ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
|
|
13
|
+
function hashToken(plain) {
|
|
14
|
+
return createHash("sha256").update(plain).digest("hex");
|
|
15
|
+
}
|
|
16
|
+
function jsonResponse(statusCode, body) {
|
|
17
|
+
return {
|
|
18
|
+
statusCode,
|
|
19
|
+
headers: { "content-type": "application/json" },
|
|
20
|
+
body: JSON.stringify(body)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function validateBearer(plaintext) {
|
|
24
|
+
const hash = hashToken(plaintext);
|
|
25
|
+
const res = await ddb.send(
|
|
26
|
+
new GetCommand({
|
|
27
|
+
TableName: KV_TABLE,
|
|
28
|
+
Key: { pk: TOKENS_PK, sk: hash }
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
const row = res.Item;
|
|
32
|
+
if (!row?.value) return null;
|
|
33
|
+
let meta;
|
|
34
|
+
try {
|
|
35
|
+
meta = JSON.parse(row.value);
|
|
36
|
+
} catch {
|
|
37
|
+
console.error("[mcp-handler] could not parse token row", { hash });
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (meta.revokedAt) return null;
|
|
41
|
+
if (meta.expiresAt && new Date(meta.expiresAt).getTime() <= Date.now()) return null;
|
|
42
|
+
return meta;
|
|
43
|
+
}
|
|
44
|
+
var handler = async (event) => {
|
|
45
|
+
if (event.requestContext?.http?.method === "OPTIONS") {
|
|
46
|
+
return { statusCode: 204, body: "" };
|
|
47
|
+
}
|
|
48
|
+
const authHeader = event.headers?.authorization ?? event.headers?.Authorization;
|
|
49
|
+
if (!authHeader) {
|
|
50
|
+
return jsonResponse(401, { error: "missing_authorization" });
|
|
51
|
+
}
|
|
52
|
+
const match = /^Bearer\s+(amk_[A-Za-z0-9_-]+)$/.exec(authHeader);
|
|
53
|
+
if (!match) {
|
|
54
|
+
return jsonResponse(401, { error: "invalid_authorization" });
|
|
55
|
+
}
|
|
56
|
+
const plaintext = match[1];
|
|
57
|
+
const meta = await validateBearer(plaintext);
|
|
58
|
+
if (!meta) {
|
|
59
|
+
return jsonResponse(401, { error: "invalid_token" });
|
|
60
|
+
}
|
|
61
|
+
return jsonResponse(200, {
|
|
62
|
+
ok: true,
|
|
63
|
+
tokenPrefix: meta.prefix,
|
|
64
|
+
scope: meta.scope,
|
|
65
|
+
note: "Phase 3 stub. JSON-RPC tool dispatch lands in Phase 4."
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
export {
|
|
69
|
+
handler
|
|
70
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineData } from '@aws-amplify/backend';
|
|
1
|
+
import { defineAuth, defineStorage, defineData } from '@aws-amplify/backend';
|
|
2
2
|
|
|
3
3
|
type AuthResource = any;
|
|
4
4
|
type DataResource = any;
|
|
@@ -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
|
/**
|
|
@@ -44,7 +46,7 @@ type AmplessBackend = any;
|
|
|
44
46
|
*/
|
|
45
47
|
declare function defineAmplessBackend(opts: DefineAmplessBackendOpts): AmplessBackend;
|
|
46
48
|
|
|
47
|
-
interface
|
|
49
|
+
interface AmplessAuthConfigOpts {
|
|
48
50
|
/**
|
|
49
51
|
* The `post-confirmation` Cognito Lambda trigger that promotes the
|
|
50
52
|
* first signup to `ampless-admin`. Wire your own `defineFunction`
|
|
@@ -59,17 +61,35 @@ interface DefineAmplessAuthOpts {
|
|
|
59
61
|
postConfirmation?: unknown;
|
|
60
62
|
}
|
|
61
63
|
/**
|
|
62
|
-
*
|
|
63
|
-
* three role groups and the optional post-confirmation
|
|
64
|
+
* Build the ampless Cognito configuration (User Pool + Identity Pool
|
|
65
|
+
* with the three role groups and the optional post-confirmation
|
|
66
|
+
* trigger) as a plain options object suitable for `defineAuth(...)`.
|
|
67
|
+
*
|
|
68
|
+
* Returning a config object — rather than calling `defineAuth`
|
|
69
|
+
* internally — keeps the actual `defineAuth` call inside the user's
|
|
70
|
+
* `amplify/auth/resource.ts`. Amplify Gen 2's import-path verifier
|
|
71
|
+
* (`@aws-amplify/backend-auth/lib/factory.js`) inspects the second
|
|
72
|
+
* stack frame and requires the call site to live at
|
|
73
|
+
* `amplify/auth/resource.ts`; routing through this package fails
|
|
74
|
+
* that check.
|
|
64
75
|
*
|
|
65
76
|
* Public reads use AppSync's API key (not the guest Identity Pool
|
|
66
77
|
* role) because `a.handler.custom` doesn't accept `allow.guest()`
|
|
67
78
|
* in Amplify Gen 2 — see `data/resource.ts` and the RUNBOOK.
|
|
79
|
+
*
|
|
80
|
+
* Usage:
|
|
81
|
+
*
|
|
82
|
+
* // amplify/auth/resource.ts
|
|
83
|
+
* import { defineAuth } from '@aws-amplify/backend'
|
|
84
|
+
* import { amplessAuthConfig } from '@ampless/backend'
|
|
85
|
+
* import { postConfirmation } from './post-confirmation/resource.js'
|
|
86
|
+
* export const auth = defineAuth(amplessAuthConfig({ postConfirmation }))
|
|
68
87
|
*/
|
|
69
|
-
declare function
|
|
88
|
+
declare function amplessAuthConfig(opts?: AmplessAuthConfigOpts): Parameters<typeof defineAuth>[0];
|
|
70
89
|
|
|
71
90
|
/**
|
|
72
|
-
*
|
|
91
|
+
* Build the ampless S3 bucket configuration as a plain options object
|
|
92
|
+
* suitable for `defineStorage(...)`. Access map:
|
|
73
93
|
* - `public/media/*` — guest read, admin/editor full
|
|
74
94
|
* - `public/plugins/*` — guest read, admin full
|
|
75
95
|
*
|
|
@@ -77,12 +97,22 @@ declare function defineAmplessAuth(opts?: DefineAmplessAuthOpts): any;
|
|
|
77
97
|
* `public/site-settings/*`) are applied by `defineAmplessBackend`
|
|
78
98
|
* after `defineBackend` runs.
|
|
79
99
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
100
|
+
* Returning a config object — rather than calling `defineStorage`
|
|
101
|
+
* internally — keeps the actual `defineStorage` call inside the
|
|
102
|
+
* user's `amplify/storage/resource.ts`. Amplify Gen 2's import-path
|
|
103
|
+
* verifier (`@aws-amplify/backend-storage/lib/factory.js`) inspects
|
|
104
|
+
* the second stack frame and requires the call site to live at
|
|
105
|
+
* `amplify/storage/resource.ts`; routing through this package fails
|
|
106
|
+
* that check.
|
|
107
|
+
*
|
|
108
|
+
* Usage:
|
|
109
|
+
*
|
|
110
|
+
* // amplify/storage/resource.ts
|
|
111
|
+
* import { defineStorage } from '@aws-amplify/backend'
|
|
112
|
+
* import { amplessStorageConfig } from '@ampless/backend'
|
|
113
|
+
* export const storage = defineStorage(amplessStorageConfig())
|
|
84
114
|
*/
|
|
85
|
-
declare function
|
|
115
|
+
declare function amplessStorageConfig(): Parameters<typeof defineStorage>[0];
|
|
86
116
|
|
|
87
117
|
interface AmplessResolverPaths {
|
|
88
118
|
listPublishedPosts: string;
|
|
@@ -108,6 +138,17 @@ interface AmplessSchemaModelsOpts {
|
|
|
108
138
|
* they must point to actual `.js` files inside the user's project.
|
|
109
139
|
*/
|
|
110
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;
|
|
111
152
|
}
|
|
112
153
|
/**
|
|
113
154
|
* The Ampless model + custom query definitions, returned as a plain
|
|
@@ -125,6 +166,9 @@ interface AmplessSchemaModelsOpts {
|
|
|
125
166
|
* extra models.
|
|
126
167
|
*/
|
|
127
168
|
declare function amplessSchemaModels(a: any, opts?: AmplessSchemaModelsOpts): {
|
|
169
|
+
AdminUser?: any;
|
|
170
|
+
listAdminUsers?: any;
|
|
171
|
+
setAdminUserRole?: any;
|
|
128
172
|
Post: any;
|
|
129
173
|
Page: any;
|
|
130
174
|
Media: any;
|
|
@@ -155,4 +199,4 @@ declare function extendAmplessSchema(a: any, custom?: Record<string, any>, opts?
|
|
|
155
199
|
*/
|
|
156
200
|
declare const defaultAuthorizationModes: Parameters<typeof defineData>[0]['authorizationModes'];
|
|
157
201
|
|
|
158
|
-
export { type AmplessBackend, type AmplessResolverPaths, type AmplessSchemaModelsOpts, DEFAULT_RESOLVER_PATHS, type
|
|
202
|
+
export { type AmplessAuthConfigOpts, type AmplessBackend, type AmplessResolverPaths, type AmplessSchemaModelsOpts, DEFAULT_RESOLVER_PATHS, type DefineAmplessBackendOpts, amplessAuthConfig, amplessSchemaModels, amplessStorageConfig, defaultAuthorizationModes, defineAmplessBackend, extendAmplessSchema };
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { defineBackend } from "@aws-amplify/backend";
|
|
|
3
3
|
import { Effect, PolicyStatement, AnyPrincipal } from "aws-cdk-lib/aws-iam";
|
|
4
4
|
import { Duration, Stack } from "aws-cdk-lib";
|
|
5
5
|
import { Queue } from "aws-cdk-lib/aws-sqs";
|
|
6
|
-
import { StartingPosition } from "aws-cdk-lib/aws-lambda";
|
|
6
|
+
import { FunctionUrlAuthType, HttpMethod, StartingPosition } from "aws-cdk-lib/aws-lambda";
|
|
7
7
|
import { DynamoEventSource, SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
|
|
8
8
|
import { Rule, Schedule } from "aws-cdk-lib/aws-events";
|
|
9
9
|
import { LambdaFunction } from "aws-cdk-lib/aws-events-targets";
|
|
@@ -16,7 +16,9 @@ function defineAmplessBackend(opts) {
|
|
|
16
16
|
eventDispatcher: opts.eventDispatcher,
|
|
17
17
|
processorTrusted: opts.processorTrusted,
|
|
18
18
|
processorUntrusted: opts.processorUntrusted,
|
|
19
|
-
apiKeyRenewer: opts.apiKeyRenewer
|
|
19
|
+
apiKeyRenewer: opts.apiKeyRenewer,
|
|
20
|
+
userAdmin: opts.userAdmin,
|
|
21
|
+
mcpHandler: opts.mcpHandler
|
|
20
22
|
});
|
|
21
23
|
const cfnBucket = backend.storage.resources.cfnResources.cfnBucket;
|
|
22
24
|
cfnBucket.publicAccessBlockConfiguration = {
|
|
@@ -61,6 +63,22 @@ function defineAmplessBackend(opts) {
|
|
|
61
63
|
resources: ["arn:aws:cognito-idp:*:*:userpool/*"]
|
|
62
64
|
})
|
|
63
65
|
);
|
|
66
|
+
backend.userAdmin.resources.lambda.addToRolePolicy(
|
|
67
|
+
new PolicyStatement({
|
|
68
|
+
effect: Effect.ALLOW,
|
|
69
|
+
actions: [
|
|
70
|
+
"cognito-idp:ListUsers",
|
|
71
|
+
"cognito-idp:AdminListGroupsForUser",
|
|
72
|
+
"cognito-idp:AdminAddUserToGroup",
|
|
73
|
+
"cognito-idp:AdminRemoveUserFromGroup"
|
|
74
|
+
],
|
|
75
|
+
resources: ["arn:aws:cognito-idp:*:*:userpool/*"]
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
backend.userAdmin.resources.lambda.addEnvironment(
|
|
79
|
+
"AMPLESS_USER_POOL_ID",
|
|
80
|
+
backend.auth.resources.userPool.userPoolId
|
|
81
|
+
);
|
|
64
82
|
const postTable = backend.data.resources.tables["Post"];
|
|
65
83
|
const cfnPostTable = backend.data.resources.cfnResources.amplifyDynamoDbTables["Post"];
|
|
66
84
|
cfnPostTable.streamSpecification = { streamViewType: "NEW_AND_OLD_IMAGES" };
|
|
@@ -149,13 +167,37 @@ function defineAmplessBackend(opts) {
|
|
|
149
167
|
schedule: Schedule.cron({ minute: "0", hour: "3", day: "1", month: "*", year: "*" }),
|
|
150
168
|
targets: [new LambdaFunction(apiKeyRenewerFn)]
|
|
151
169
|
});
|
|
170
|
+
const mcpHandlerFn = backend.mcpHandler.resources.lambda;
|
|
171
|
+
mcpHandlerFn.addToRolePolicy(
|
|
172
|
+
new PolicyStatement({
|
|
173
|
+
effect: Effect.ALLOW,
|
|
174
|
+
actions: ["dynamodb:GetItem"],
|
|
175
|
+
resources: [kvTable.tableArn]
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
mcpHandlerFn.addEnvironment("AMPLESS_KV_TABLE", kvTable.tableName);
|
|
179
|
+
const mcpFunctionUrl = mcpHandlerFn.addFunctionUrl({
|
|
180
|
+
authType: FunctionUrlAuthType.NONE,
|
|
181
|
+
cors: {
|
|
182
|
+
allowedOrigins: ["*"],
|
|
183
|
+
allowedMethods: [HttpMethod.POST],
|
|
184
|
+
allowedHeaders: ["*"],
|
|
185
|
+
maxAge: Duration.hours(1)
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
backend.addOutput({
|
|
189
|
+
custom: {
|
|
190
|
+
mcp: {
|
|
191
|
+
endpoint: mcpFunctionUrl.url
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
152
195
|
return backend;
|
|
153
196
|
}
|
|
154
197
|
|
|
155
198
|
// src/auth/index.ts
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return defineAuth({
|
|
199
|
+
function amplessAuthConfig(opts = {}) {
|
|
200
|
+
return {
|
|
159
201
|
loginWith: {
|
|
160
202
|
email: true
|
|
161
203
|
},
|
|
@@ -164,13 +206,12 @@ function defineAmplessAuth(opts = {}) {
|
|
|
164
206
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
165
207
|
{ postConfirmation: opts.postConfirmation }
|
|
166
208
|
) : void 0
|
|
167
|
-
}
|
|
209
|
+
};
|
|
168
210
|
}
|
|
169
211
|
|
|
170
212
|
// src/storage/index.ts
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return defineStorage({
|
|
213
|
+
function amplessStorageConfig() {
|
|
214
|
+
return {
|
|
174
215
|
name: "amplessMedia",
|
|
175
216
|
access: (allow) => ({
|
|
176
217
|
"public/media/*": [
|
|
@@ -182,7 +223,7 @@ function defineAmplessStorage() {
|
|
|
182
223
|
allow.groups(["ampless-admin"]).to(["read", "write", "delete"])
|
|
183
224
|
]
|
|
184
225
|
})
|
|
185
|
-
}
|
|
226
|
+
};
|
|
186
227
|
}
|
|
187
228
|
|
|
188
229
|
// src/data/index.ts
|
|
@@ -203,11 +244,19 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
203
244
|
slug: a.string().required(),
|
|
204
245
|
title: a.string().required(),
|
|
205
246
|
excerpt: a.string(),
|
|
206
|
-
format: a.enum(["tiptap", "markdown", "html"]),
|
|
247
|
+
format: a.enum(["tiptap", "markdown", "html", "static"]),
|
|
207
248
|
body: a.json(),
|
|
208
249
|
status: a.enum(["draft", "published"]),
|
|
209
250
|
publishedAt: a.datetime(),
|
|
210
251
|
tags: a.string().array(),
|
|
252
|
+
// Free-form per-post metadata (JSON). Reserved well-known keys
|
|
253
|
+
// are documented on `PostMetadata` in `ampless/src/types.ts`.
|
|
254
|
+
// Currently:
|
|
255
|
+
// no_layout: boolean — serve the post as bare HTML (no theme
|
|
256
|
+
// chrome). The runtime's post dispatcher redirects to the
|
|
257
|
+
// raw route handler when this is true.
|
|
258
|
+
// Other keys are passed through unchanged for plugin / app use.
|
|
259
|
+
metadata: a.json(),
|
|
211
260
|
// Denormalized GSI keys — set by every write path (admin client,
|
|
212
261
|
// MCP tools). Same pattern as siteIdStatus: composing the
|
|
213
262
|
// partition key as a single string lets each public-read query
|
|
@@ -229,7 +278,7 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
229
278
|
pageId: a.id().required(),
|
|
230
279
|
slug: a.string().required(),
|
|
231
280
|
title: a.string().required(),
|
|
232
|
-
format: a.enum(["tiptap", "markdown", "html"]),
|
|
281
|
+
format: a.enum(["tiptap", "markdown", "html", "static"]),
|
|
233
282
|
body: a.json(),
|
|
234
283
|
status: a.enum(["draft", "published"]),
|
|
235
284
|
publishedAt: a.datetime()
|
|
@@ -310,7 +359,8 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
310
359
|
body: a.json(),
|
|
311
360
|
status: a.string(),
|
|
312
361
|
publishedAt: a.datetime(),
|
|
313
|
-
tags: a.string().array()
|
|
362
|
+
tags: a.string().array(),
|
|
363
|
+
metadata: a.json()
|
|
314
364
|
}),
|
|
315
365
|
// Paginated wrapper for list responses.
|
|
316
366
|
PublicPostConnection: a.customType({
|
|
@@ -369,7 +419,27 @@ function amplessSchemaModels(a, opts = {}) {
|
|
|
369
419
|
).authorization((allow) => [
|
|
370
420
|
allow.publicApiKey(),
|
|
371
421
|
allow.groups(["ampless-admin", "ampless-editor"])
|
|
372
|
-
])
|
|
422
|
+
]),
|
|
423
|
+
// User management ops, only wired when the caller supplies a
|
|
424
|
+
// Lambda function ref. Conditionally spread because
|
|
425
|
+
// `a.handler.function(undefined)` is not a valid call — projects
|
|
426
|
+
// that haven't opted into the user-admin Lambda must not see
|
|
427
|
+
// these schema entries.
|
|
428
|
+
...opts.userAdminFunction ? {
|
|
429
|
+
AdminUser: a.customType({
|
|
430
|
+
userId: a.string().required(),
|
|
431
|
+
email: a.string().required(),
|
|
432
|
+
// 'admin' | 'editor' | 'none' — stored as string because
|
|
433
|
+
// a.enum() in customType doesn't round-trip cleanly across
|
|
434
|
+
// AppSync's typegen + the admin client cast pattern.
|
|
435
|
+
role: a.string().required()
|
|
436
|
+
}),
|
|
437
|
+
listAdminUsers: a.query().returns(a.ref("AdminUser").array()).handler(a.handler.function(opts.userAdminFunction)).authorization((allow) => [allow.groups(["ampless-admin"])]),
|
|
438
|
+
setAdminUserRole: a.mutation().arguments({
|
|
439
|
+
userId: a.string().required(),
|
|
440
|
+
role: a.string().required()
|
|
441
|
+
}).returns(a.ref("AdminUser")).handler(a.handler.function(opts.userAdminFunction)).authorization((allow) => [allow.groups(["ampless-admin"])])
|
|
442
|
+
} : {}
|
|
373
443
|
};
|
|
374
444
|
}
|
|
375
445
|
function extendAmplessSchema(a, custom, opts) {
|
|
@@ -384,10 +454,10 @@ var defaultAuthorizationModes = {
|
|
|
384
454
|
};
|
|
385
455
|
export {
|
|
386
456
|
DEFAULT_RESOLVER_PATHS,
|
|
457
|
+
amplessAuthConfig,
|
|
387
458
|
amplessSchemaModels,
|
|
459
|
+
amplessStorageConfig,
|
|
388
460
|
defaultAuthorizationModes,
|
|
389
|
-
defineAmplessAuth,
|
|
390
461
|
defineAmplessBackend,
|
|
391
|
-
defineAmplessStorage,
|
|
392
462
|
extendAmplessSchema
|
|
393
463
|
};
|
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.10",
|
|
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": [
|
|
@@ -48,11 +56,11 @@
|
|
|
48
56
|
"@aws-sdk/client-appsync": "^3.717.0",
|
|
49
57
|
"@aws-sdk/client-cognito-identity-provider": "^3.717.0",
|
|
50
58
|
"@aws-sdk/client-dynamodb": "^3.717.0",
|
|
51
|
-
"@aws-sdk/client-s3": "^3.
|
|
59
|
+
"@aws-sdk/client-s3": "^3.1048.0",
|
|
52
60
|
"@aws-sdk/client-sqs": "^3.717.0",
|
|
53
61
|
"@aws-sdk/lib-dynamodb": "^3.717.0",
|
|
54
62
|
"@aws-sdk/util-dynamodb": "^3.717.0",
|
|
55
|
-
"ampless": "0.2.0-alpha.
|
|
63
|
+
"ampless": "0.2.0-alpha.6"
|
|
56
64
|
},
|
|
57
65
|
"peerDependencies": {
|
|
58
66
|
"@aws-amplify/backend": "^1",
|