create-ampless 1.0.0-alpha.94 → 1.0.0-alpha.95

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.
@@ -88,7 +88,7 @@ ampless プラグインは `AmplessPlugin` オブジェクトを返す TypeScrip
88
88
  Lambda (`hooks`) でやる
89
89
  - **admin ルート / server ルート / コンテンツフィールドの追加** —
90
90
  Phase 6b 予約
91
- - **secret の読み書き**。`secretSettings` capability Phase 6a 予約。
91
+ - **Admin routes / server routes / content fields.** Phase 6b 予約。
92
92
  `settings.public` に credential を置かないこと
93
93
 
94
94
  ---
@@ -702,6 +702,19 @@ stored 値 (validated)
702
702
 
703
703
  ---
704
704
 
705
+ ## 9a. Secret settings: `ctx.secret<T>(key)` (Phase 6a)
706
+
707
+ Secret settings を使うと、trusted プラグインが認証情報 (Webhook 署名 secret・SMTP パスワード・外部 API トークン等) を admin UI 経由で保存・ローテーションできます。**公開サイトやブラウザ側コードに値が流れることはありません**。
708
+
709
+ 詳しいリファレンスは [`packages/ampless/docs/plugin-author-guide.md`](../../packages/ampless/docs/plugin-author-guide.md) §9a を参照してください。概要:
710
+
711
+ - `settings.secret` は `trust_level: 'trusted'` + `'secretSettings'` capability 必須。
712
+ - フィールド型は `PluginSecretField` = `Omit<PluginTextField, 'default'> | Omit<PluginTextareaField, 'default'>`。`default` は型レベルで禁止。fallback は closure-private 変数で保持。
713
+ - hook 内で `await ctx.secret<string>('signingSecret')` で読む。per-invocation キャッシュ済み。
714
+ - admin UI が「保存済み」表示 (`••••••••`) + Replace + Clear を提供。値は取得・表示されない。
715
+
716
+ ---
717
+
705
718
  ## 10. ウォークスルー: GA4 を Phase 1 から Phase 2 に移行する
706
719
 
707
720
  Phase 1 の GA4 プラグインは measurement ID を constructor 引数で受けていました。Phase 2 では後方互換のためにその引数を残しつつ、値は `ctx.setting()` 経由で読みます。
@@ -93,6 +93,7 @@ surfaces:
93
93
  | `ogImage` | `/og/[slug]` route | request-time, in public Lambda | Existing |
94
94
  | `hooks` | trust_level-matched processor Lambda | async, on SQS event | Existing |
95
95
  | `settings.public` | `/admin/plugins` form | declarative manifest | 2 |
96
+ | `settings.secret` | `/admin/plugins` secret section | declarative manifest, trusted Lambda only | 6a |
96
97
 
97
98
  A few surfaces don't exist yet — they're reserved for later phases
98
99
  and aren't shaped by `definePlugin` today:
@@ -111,8 +112,6 @@ and aren't shaped by `definePlugin` today:
111
112
  work belongs there.
112
113
  - **Admin routes / server routes / content fields.** Reserved for
113
114
  Phase 6b.
114
- - **Secrets.** The `secretSettings` capability is reserved for
115
- Phase 6a; nothing in `settings.public` should be a credential.
116
115
 
117
116
  ---
118
117
 
@@ -906,6 +905,108 @@ plugin's own `instanceId`.
906
905
 
907
906
  ---
908
907
 
908
+ ## 9a. Secret settings: `ctx.secret<T>(key)` (Phase 6a)
909
+
910
+ Secret settings let trusted plugins store and rotate credentials
911
+ (webhook signing secrets, SMTP passwords, external API tokens)
912
+ through the admin UI **without exposing them to the public site or
913
+ browser-side code**.
914
+
915
+ ### Why a separate API from `settings.public`?
916
+
917
+ Values in `settings.public` are designed to flow to the public
918
+ runtime: they're mirrored to `public/site-settings.json` and read
919
+ by `ctx.setting()` inside sync render surfaces. That flow is
920
+ intentional for analytics measurement IDs, consent category names,
921
+ etc. — but completely wrong for a webhook signing secret.
922
+
923
+ `settings.secret` has a structurally different storage model:
924
+
925
+ - Stored in the `PluginSecret` DynamoDB model (separate from KvStore).
926
+ - Admin/editor groups can **write and delete** but have **no read
927
+ authorization** — the AppSync schema does not generate
928
+ `getPluginSecret` or `listPluginSecrets` queries for those groups.
929
+ - Only the trusted-processor Lambda IAM role can read, via DDB
930
+ `GetItem` directly.
931
+ - Never queried by the site-settings mirror path.
932
+ - Never passed to any public-render surface
933
+ (`publicHead`, `publicBodyEnd`, `publicBodyForPost`,
934
+ `publicHtmlForPost`) — those surfaces only see `ctx.setting()`.
935
+
936
+ ### Requirements
937
+
938
+ `settings.secret` requires:
939
+
940
+ 1. `trust_level: 'trusted'` — untrusted Lambdas have no DDB read
941
+ access to the PluginSecret table. `definePlugin()` throws if you
942
+ declare `settings.secret` with any other trust level.
943
+ 2. `'secretSettings'` in `capabilities` — required so admin UI and
944
+ future allow-lists can gate the capability. Omitting it produces
945
+ a console warning (same pattern as `'schema'` vs `publicBodyForPost`).
946
+
947
+ ### Declaring secret fields
948
+
949
+ ```ts
950
+ import { definePlugin } from 'ampless'
951
+
952
+ export default function webhookPlugin(opts?: { signingSecret?: string }) {
953
+ // Keep any constructor-provided secret as a closure-private fallback.
954
+ // It is NEVER exposed in the manifest or the stored descriptor.
955
+ const constructorSecret = opts?.signingSecret
956
+
957
+ return definePlugin({
958
+ name: 'webhook',
959
+ apiVersion: 1,
960
+ trust_level: 'trusted',
961
+ capabilities: ['eventHooks', 'secretSettings'],
962
+ settings: {
963
+ secret: [
964
+ {
965
+ type: 'text',
966
+ key: 'signingSecret',
967
+ label: { en: 'Webhook signing secret', ja: 'Webhook 署名 secret' },
968
+ maxLength: 256,
969
+ required: false,
970
+ // NO `default` — secret fields forbid it at the type level.
971
+ // Use a closure-private fallback instead (see below).
972
+ },
973
+ ],
974
+ },
975
+ hooks: {
976
+ async 'content.published'(event, ctx) {
977
+ const storedSecret = await ctx.secret<string>('signingSecret')
978
+ const secret = storedSecret ?? constructorSecret
979
+ if (!secret) return
980
+ // ... use secret to sign and POST
981
+ },
982
+ },
983
+ })
984
+ }
985
+ ```
986
+
987
+ ### Important: no `default` on secret fields
988
+
989
+ The type `PluginSecretField` is defined as `Omit<PluginTextField,
990
+ 'default'> | Omit<PluginTextareaField, 'default'>` — the `default`
991
+ property is **removed at the type level**. Use a closure-private
992
+ fallback variable instead; never put credentials in the manifest.
993
+
994
+ ### Reading secrets: `ctx.secret<T>(key)`
995
+
996
+ `ctx.secret<T>(key)` is only available in trusted hook handlers.
997
+ Returns `undefined` when no value has been saved by admin. Results
998
+ are per-invocation cached; cache keys are namespaced by
999
+ `${instanceId ?? name}:${fieldKey}`.
1000
+
1001
+ ### Admin UI
1002
+
1003
+ When `settings.secret` is declared, the admin plugin settings page
1004
+ renders a **Secret settings** section below the public fields.
1005
+ Admins see `••••••••` for stored values and can Replace or Clear
1006
+ without deploying. Rotation takes effect within ~5–10 seconds.
1007
+
1008
+ ---
1009
+
909
1010
  ## 10. Walk-through: migrating GA4 from Phase 1 to Phase 2
910
1011
 
911
1012
  The Phase 1 GA4 plugin took the measurement ID through a
@@ -25,21 +25,21 @@
25
25
  "@tiptap/pm": "^3.23.6",
26
26
  "@tiptap/react": "^3.23.6",
27
27
  "@tiptap/starter-kit": "^3.23.6",
28
- "@ampless/plugin-analytics-ga4": "^0.2.0-alpha.12",
29
- "@ampless/plugin-cookie-consent": "^0.1.0-alpha.3",
30
- "@ampless/plugin-gtm": "^0.2.0-alpha.11",
31
- "@ampless/plugin-og-image": "^0.2.0-alpha.28",
32
- "@ampless/plugin-plausible": "^0.2.0-alpha.11",
33
- "@ampless/plugin-reading-time": "^0.1.0-alpha.1",
34
- "@ampless/plugin-rss": "^0.2.0-alpha.28",
35
- "@ampless/plugin-schema-jsonld": "^0.1.1-alpha.7",
36
- "@ampless/plugin-seo": "^0.2.0-alpha.28",
37
- "@ampless/plugin-webhook": "^0.2.0-alpha.28",
38
- "@ampless/admin": "^1.0.0-alpha.57",
39
- "@ampless/backend": "^1.0.0-alpha.45",
40
- "@ampless/runtime": "^1.0.0-alpha.36",
28
+ "@ampless/plugin-analytics-ga4": "^0.2.0-alpha.13",
29
+ "@ampless/plugin-cookie-consent": "^0.1.0-alpha.4",
30
+ "@ampless/plugin-gtm": "^0.2.0-alpha.12",
31
+ "@ampless/plugin-og-image": "^0.2.0-alpha.29",
32
+ "@ampless/plugin-plausible": "^0.2.0-alpha.12",
33
+ "@ampless/plugin-reading-time": "^0.1.0-alpha.2",
34
+ "@ampless/plugin-rss": "^0.2.0-alpha.29",
35
+ "@ampless/plugin-schema-jsonld": "^0.1.1-alpha.8",
36
+ "@ampless/plugin-seo": "^0.2.0-alpha.29",
37
+ "@ampless/plugin-webhook": "^0.2.0-alpha.29",
38
+ "@ampless/admin": "^1.0.0-alpha.58",
39
+ "@ampless/backend": "^1.0.0-alpha.46",
40
+ "@ampless/runtime": "^1.0.0-alpha.37",
41
41
  "@digital-go-jp/tailwind-theme-plugin": "^0.3.4",
42
- "ampless": "^1.0.0-alpha.28",
42
+ "ampless": "^1.0.0-alpha.29",
43
43
  "aws-amplify": "^6.17.0",
44
44
  "class-variance-authority": "^0.7.1",
45
45
  "clsx": "^2.1.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ampless",
3
- "version": "1.0.0-alpha.94",
3
+ "version": "1.0.0-alpha.95",
4
4
  "description": "Create a new ampless project",
5
5
  "license": "MIT",
6
6
  "type": "module",