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
|
-
- **
|
|
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.
|
|
29
|
-
"@ampless/plugin-cookie-consent": "^0.1.0-alpha.
|
|
30
|
-
"@ampless/plugin-gtm": "^0.2.0-alpha.
|
|
31
|
-
"@ampless/plugin-og-image": "^0.2.0-alpha.
|
|
32
|
-
"@ampless/plugin-plausible": "^0.2.0-alpha.
|
|
33
|
-
"@ampless/plugin-reading-time": "^0.1.0-alpha.
|
|
34
|
-
"@ampless/plugin-rss": "^0.2.0-alpha.
|
|
35
|
-
"@ampless/plugin-schema-jsonld": "^0.1.1-alpha.
|
|
36
|
-
"@ampless/plugin-seo": "^0.2.0-alpha.
|
|
37
|
-
"@ampless/plugin-webhook": "^0.2.0-alpha.
|
|
38
|
-
"@ampless/admin": "^1.0.0-alpha.
|
|
39
|
-
"@ampless/backend": "^1.0.0-alpha.
|
|
40
|
-
"@ampless/runtime": "^1.0.0-alpha.
|
|
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.
|
|
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",
|