> 日本語版: [04-access-layer-mcp.ja.md](./04-access-layer-mcp.ja.md) > ## 4. Access Layer and MCP ### Design Philosophy All persistent state lives in DynamoDB + S3 behind a single AppSync GraphQL endpoint. Every client (Admin UI, MCP HTTP handler, public read traffic) reaches that endpoint with a different auth mode — there is no separate CRUD service. ``` Admin UI (Next.js) → AppSync (Cognito User Pool, admin/editor groups) →┐ MCP Lambda (HTTP) → AppSync (IAM / SigV4 via resource auth) ├→ DynamoDB / S3 Public site / themes → AppSync (apiKey, custom resolvers strip drafts) ─┘ ``` The `ampless` package itself does not carry CRUD logic. It exports types, theme/plugin contracts, format helpers, and a small `PostsProvider` interface ([`packages/ampless/src/core.ts`](../../packages/ampless/src/core.ts)) so that admin and runtime callers can be wired against either the live Amplify Data client or test fixtures. ### Authentication Standard Cognito **email + password** auth (SRP). Implemented via Amplify Auth ([`packages/backend/src/auth/index.ts`](../../packages/backend/src/auth/index.ts)) with no custom flow. ```typescript // resolved by @ampless/backend → amplify/auth/resource.ts defineAuth({ loginWith: { email: true }, groups: ['ampless-admin', 'ampless-editor', 'ampless-reader'], triggers: { postConfirmation: defineFunction({ /* promote first user to admin */ }), }, }) ``` The login UI ([`packages/admin/src/components/login-view.tsx`](../../packages/admin/src/components/login-view.tsx)) covers the standard Cognito modes: | Mode | Purpose | |---|---| | `signIn` | Email + password login (existing user) | | `signUp` | New account (admin-invited or self-signup) → confirmation code emailed | | `confirm` | Enter the emailed code to verify the address | | `forgot` | Trigger a password-reset email | | `reset` | Set a new password using the emailed code | Passwordless flows (magic link / WebAuthn) are not used. Adopting them later would be a config swap on `defineAuth`, not a redesign. #### Cognito User Groups Three groups are declared. The admin app surfaces only the first two as assignable roles — `ampless-reader` is the implicit landing state for any account not promoted to admin/editor. | Group | Description | |---|---| | `ampless-admin` | Full permissions: user management, site settings, plugin management, MCP token issuance | | `ampless-editor` | Create / edit / delete content. Treated as a trusted principal (see below) | | `ampless-reader` | Default for un-promoted accounts. No admin UI access; the public site uses an API key instead, so this group is currently a placeholder | #### Initial Setup ``` 1. Generate project with `npx create-ampless@beta` 2. Deploy with `npx ampx sandbox` (dev) or via Amplify Hosting (prod) 3. Sign up on the admin login screen → Cognito emails a confirmation code 4. Enter the code; the post-confirmation trigger checks whether the admin group is empty and, if so, promotes this user to `ampless-admin` 5. Subsequent sign-ups land in the implicit reader state and must be promoted by an admin ``` The post-confirmation trigger ([`packages/backend/src/auth/post-confirmation.ts`](../../packages/backend/src/auth/post-confirmation.ts)) only promotes the **first** confirmed user — the rest of the workflow is admin-driven. #### User Management Admin → Users in the admin UI lists Cognito users and lets admins flip a user's role between `admin` / `editor` / `none`. The page calls AppSync queries / mutations (`listAdminUsers` / `setAdminUserRole`) backed by the user-admin Lambda ([`packages/backend/src/auth/user-admin.ts`](../../packages/backend/src/auth/user-admin.ts)). The Lambda uses Cognito Admin APIs (`ListUsers`, `AdminAddUserToGroup`, `AdminRemoveUserFromGroup`) — no custom user table. | Operation | Who | Mechanism | |---|---|---| | First admin | Initial setup only | Post-confirmation Lambda trigger | | Sign up | Anyone (or invitee) | Cognito sign-up + email confirmation | | Assign / change role | admin | `setAdminUserRole` → Cognito `AdminAddUserToGroup` / `AdminRemoveUserFromGroup` | | List users | admin | `listAdminUsers` → Cognito `ListUsers` | | Self-promotion | (blocked) | The `listAdminUsers` / `setAdminUserRole` GraphQL ops require `ampless-admin` membership | #### Permission Boundary Every server-side mutation either runs under Cognito group authorization (admin/editor) or under the MCP Lambda's IAM role (see below). There is no in-browser path that bypasses AppSync's authorization. | Source | Auth mode | Effective role | |---|---|---| | Admin UI | Cognito User Pool | From `cognito:groups` (`admin` or `editor`) | | Public site / theme components | AppSync API key | Read-only, restricted to the `listPublishedPosts` / `getPublishedPost` / `listPostsByTag` custom resolvers (drafts stripped) | | MCP HTTP handler | IAM SigV4 via `allow.resource(mcpHandler)` | Equivalent to admin for the models the resource grant covers | #### Editor Trust Model (Specification) In ampless, `editor` is treated as a **trusted principal**. Following the same philosophy as WordPress's `unfiltered_html` capability, **editors can save arbitrary HTML / JavaScript as post body content** — this is a deliberate design decision. Specifically: - The `body` field of a Post is not sanitized server-side - No sanitization for any of `format: 'tiptap' | 'markdown' | 'html'` - tiptap attributes (`href`, `src`, `alt`, `title`, etc.) are also not sanitized - `