@aotter/mantle 0.1.2-alpha.5 → 0.1.2-alpha.6

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.
@@ -177,8 +177,10 @@ const auth = createAuth({
177
177
  {
178
178
  kind: "social",
179
179
  provider: "github",
180
- clientId: env.GITHUB_CLIENT_ID,
181
- clientSecret: env.GITHUB_CLIENT_SECRET,
180
+ options: {
181
+ clientId: env.GITHUB_CLIENT_ID,
182
+ clientSecret: env.GITHUB_CLIENT_SECRET,
183
+ },
182
184
  },
183
185
  ],
184
186
  bootstrapOwner: env.ADMIN_GITHUB_LOGIN
@@ -734,3 +734,24 @@ one-way hashes. This is a fixed security boundary, not adopter configuration;
734
734
  Deploying this change invalidates any unconsumed codes and links created by an
735
735
  older deployment. Users request a new code or link; no database migration is
736
736
  needed.
737
+
738
+ ## 2026-09-18 amendment — native Better Auth method options
739
+
740
+ Issue #924 supersedes only the preceding amendment's “not adopter
741
+ configuration” restriction. Hashed OTP and magic-link storage remains the
742
+ Mantle default, while an explicit official Better Auth `storeOTP` or
743
+ `storeToken` option may override it, including custom hashers/encryption.
744
+
745
+ `createAuth` remains the Worker lifecycle and Mantle integration boundary, but
746
+ does not copy Better Auth's method option unions. Social methods use the
747
+ provider-specific official `SocialProviders` option keyed by `provider`,
748
+ including async factories. Generic OAuth uses `GenericOAuthConfig`; email OTP
749
+ and magic link use their official option types minus the sender callback that
750
+ Mantle owns. GitHub's bootstrap mapper composes with an adopter mapper instead
751
+ of replacing it. Admin method metadata contains only kind, provider id and
752
+ display label, never options or secrets.
753
+
754
+ Applications that need to own an entire callback may pass official plugin
755
+ instances through `plugins`. Those plugins do not synthesize Admin metadata,
756
+ and duplicate plugin ids fail at construction. There is no
757
+ `Partial<BetterAuthOptions>` deep merge and no silent plugin replacement.
@@ -15,7 +15,7 @@ Records of *why* mantle ended up shaped this way. The numbering preserves POC AD
15
15
  | [0011](0011-adapter-port-spec.md) | Adapter port spec. Required runtime ports plus optional feature ports. | Accepted (new) |
16
16
  | [0012](0012-views-as-public-rest.md) | Views auto-expose matching REST and `query_view_*` MCP reads on their declared `public` or `staff` surface. Schemas never get a public REST endpoint. | Accepted + amended |
17
17
  | [0013](0013-agent-provisioned-consumer-projects.md) | Historical agent-provisioned consumer projects path. Superseded for first launch by landing provision bundles. | Superseded |
18
- | [0014](0014-auth-better-auth-and-multi-tenant-mcp.md) | The Cloudflare adapter owns one curated Better Auth 1.7 identity/OAuth/MCP authority with CIMD discovery. Verified callers are normalized into runtime context; mutable staff role and target authorization are re-evaluated per call. | Accepted + amended |
18
+ | [0014](0014-auth-better-auth-and-multi-tenant-mcp.md) | The Cloudflare adapter owns one Better Auth 1.7 identity/OAuth/MCP authority with native method options and CIMD discovery. Verified callers are normalized into runtime context; mutable staff role and target authorization are re-evaluated per call. | Accepted + amended |
19
19
  | [0016](0016-site-semantic-layer.md) | Site semantic layer: `AGENTS.md` (cross-tool entry) + `.mantle/launch-state.json` (deterministic install context). The older `mantle/site.md` letter surface is suspended from first-run scaffolds. | Accepted (slimmed) |
20
20
  | [0017](0017-media-multi-variant-agent-side-optimization.md) | Multi-variant media assets with agent-side optimization and asset-id entry references. | Accepted |
21
21
  | [0018](0018-core-starters-repository-boundary.md) | Core produces published SDK artifacts; the separate starters repository validates them as an external consumer. Revisit after release-contract simplification. | Superseded by 0021 |
@@ -23,11 +23,11 @@ A free Mantle site can run every login method the SDK exposes through
23
23
  `createAuth()`:
24
24
 
25
25
  - social OAuth providers supported by Better Auth;
26
- - generic provider-specific options through the `social.extras` shape;
26
+ - provider-specific options through Better Auth's native `socialProviders` types;
27
27
  - email OTP;
28
28
  - magic link;
29
- - passkey or additional Better Auth method support when the SDK adds a
30
- curated first-class field for it;
29
+ - passkey or another Better Auth method through the raw `plugins` path when the
30
+ application owns its callback and UI;
31
31
  - first-party same-domain or same-parent-domain SSO when the site owner
32
32
  controls every participating subdomain.
33
33
 
@@ -143,9 +143,9 @@ the exact public API and four consumer examples.
143
143
 
144
144
  ## SDK Surface Rule
145
145
 
146
- Mantle should expose Better Auth knobs only as curated first-class
147
- fields when there is a real Mantle use case. Do not add a generic
148
- `betterAuthOptions` or `advanced` passthrough.
146
+ Mantle owns only integration fields with a real Mantle use case. Method
147
+ configuration uses Better Auth's official provider/plugin option types; do not
148
+ copy those unions or add a generic `Partial<BetterAuthOptions>` deep merge.
149
149
 
150
150
  The current first-party SSO use case justifies these optional fields on
151
151
  `CreateAuthConfig`:
@@ -154,14 +154,13 @@ The current first-party SSO use case justifies these optional fields on
154
154
  - `crossSubDomainCookies`
155
155
  - `cookiePrefix`
156
156
 
157
- The cross-site API use case additionally justifies these curated fields and
158
- facades:
157
+ The cross-site API use case additionally justifies these fields and facades:
159
158
 
160
- - generic OAuth method `resource`
159
+ - native generic OAuth `authorizationUrlParams`, `tokenUrlParams`, and
160
+ `refreshTokenParams` for RFC 8707 `resource`
161
161
  - OAuth provider `resources` and the curated `mcpResource`
162
162
  - `Auth.getProviderAccessToken(request, providerId)`
163
163
  - `Auth.verifyOAuthAccessToken(tokenOrRequest, { audience, scopes })`
164
164
 
165
- Generic OAuth providers use Better Auth 1.7's standard social sign-in and
166
- `/api/auth/callback/:id` path. These are not a raw Better Auth options
167
- passthrough.
165
+ Generic OAuth providers use Better Auth 1.7's official `GenericOAuthConfig`,
166
+ standard social sign-in and `/api/auth/callback/:id` path.
@@ -3,7 +3,7 @@ description: Configure MANTLE_AUTH_MODE, secrets, the first owner and staff role
3
3
  ---
4
4
  # Authentication
5
5
 
6
- Conventional Auth is chosen by one variable, `MANTLE_AUTH_MODE`, and fails closed when its configuration is incomplete. This page covers the two modes, the secrets each needs, first-owner bootstrap, roles, the routes that require a session, and the curated Better Auth surface.
6
+ Conventional Auth is chosen by one variable, `MANTLE_AUTH_MODE`, and fails closed when its configuration is incomplete. This page covers the two modes, the secrets each needs, first-owner bootstrap, roles, the routes that require a session, and the Better Auth integration surface.
7
7
 
8
8
  ## Mode matrix
9
9
 
@@ -71,9 +71,59 @@ The staff role is re-read from D1 on every protected REST and MCP call; a revoke
71
71
 
72
72
  MCP tokens are session-bound: signing out of Admin ends MCP access. See [MCP and agents](../concepts/mcp-and-agents.md).
73
73
 
74
- ## Curated Better Auth surface
74
+ ## Better Auth configuration
75
75
 
76
- `createAuth()` exposes curated fields, not a Better Auth passthrough: `database`, `baseURL`, `secret`, `methods`, `bootstrapOwner`, `oauthProvider`, `rateLimit`, and for first-party SSO `trustedOrigins`, `cookiePrefix` and `crossSubDomainCookies`. There is no `betterAuthOptions` or `advanced` escape hatch; a new Better Auth knob appears only when a Mantle use case justifies a first-class field.
76
+ `createAuth()` owns the Worker lifecycle, Admin metadata, sender integration,
77
+ bootstrap rules, roles and MCP invariants. Method-specific configuration stays
78
+ native to Better Auth under `options`, so provider updates and type inference do
79
+ not need a matching Mantle DSL update. There is deliberately no
80
+ `Partial<BetterAuthOptions>` deep merge.
81
+
82
+ ```ts
83
+ methods: [
84
+ {
85
+ kind: "social",
86
+ provider: "google",
87
+ options: { clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, accessType: "offline" },
88
+ },
89
+ {
90
+ kind: "social",
91
+ provider: "apple",
92
+ options: async () => ({ clientId: env.APPLE_CLIENT_ID, clientSecret: await loadAppleSecret(env) }),
93
+ },
94
+ {
95
+ kind: "email-otp",
96
+ sender,
97
+ options: { otpLength: 8 },
98
+ },
99
+ ]
100
+ ```
101
+
102
+ Email OTP and magic-link storage defaults to `hashed`. Explicit official
103
+ overrides remain available, including custom hashing/encryption:
104
+
105
+ ```ts
106
+ { kind: "email-otp", sender, options: {
107
+ storeOTP: { encrypt: encryptOtp, decrypt: decryptOtp },
108
+ } }
109
+ { kind: "magic-link", sender, options: {
110
+ storeToken: { type: "custom-hasher", hash: hashMagicToken },
111
+ } }
112
+ ```
113
+
114
+ For complete callback ownership, register an official plugin instance directly.
115
+ Raw plugins do not add a button to `Auth.methods`; the application owns that UI.
116
+ Duplicate plugin ids fail at construction rather than silently replacing one:
117
+
118
+ ```ts
119
+ createAuth({
120
+ database: env.DB,
121
+ baseURL: env.PUBLIC_ORIGIN,
122
+ secret: env.BETTER_AUTH_SECRET,
123
+ methods: [],
124
+ plugins: [emailOTP({ sendVerificationOTP, storeOTP: "encrypted" })],
125
+ });
126
+ ```
77
127
 
78
128
  When several first-party apps share one parent domain that the same party controls, configure shared cookies explicitly. `cookiePrefix` is required whenever more than one Better Auth app writes cookies under that domain; `trustedOrigins` is the auth-flow trust list, not a CORS policy.
79
129
 
@@ -116,11 +166,15 @@ const clientAuth = createAuth({
116
166
  // database, baseURL, secret, other methods...
117
167
  methods: [{
118
168
  kind: "oauth",
119
- providerId: "mantle-platform",
120
- clientId: env.PLATFORM_CLIENT_ID,
121
- discoveryUrl: "https://platform.example.com/api/auth/.well-known/openid-configuration",
122
- scopes: ["openid", "offline_access", "accounts:read"],
123
- resource: "https://api.example.com",
169
+ options: {
170
+ providerId: "mantle-platform",
171
+ clientId: env.PLATFORM_CLIENT_ID,
172
+ discoveryUrl: "https://platform.example.com/api/auth/.well-known/openid-configuration",
173
+ scopes: ["openid", "offline_access", "accounts:read"],
174
+ authorizationUrlParams: { resource: "https://api.example.com" },
175
+ tokenUrlParams: { resource: "https://api.example.com" },
176
+ refreshTokenParams: { resource: "https://api.example.com" },
177
+ },
124
178
  }],
125
179
  });
126
180
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.1.2-alpha.5",
3
+ "version": "0.1.2-alpha.6",
4
4
  "description": "Embeddable Mantle Core umbrella with Spec and Runtime; Web, Admin, Bun, Vercel, Cloudflare, and Admin UI are optional peer packages.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://mantle.tools/",
@@ -79,8 +79,8 @@
79
79
  "README.md"
80
80
  ],
81
81
  "dependencies": {
82
- "@aotter/mantle-runtime": "0.1.2-alpha.5",
83
- "@aotter/mantle-spec": "0.1.2-alpha.5"
82
+ "@aotter/mantle-runtime": "0.1.2-alpha.6",
83
+ "@aotter/mantle-spec": "0.1.2-alpha.6"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "aws4fetch": "^1.0.20",
@@ -88,12 +88,12 @@
88
88
  "hono": "^4.12.0",
89
89
  "@libsql/client": "^0.17.4",
90
90
  "zod": "^4.5.0",
91
- "@aotter/mantle-admin-ui": "0.1.2-alpha.5",
92
- "@aotter/mantle-admin": "0.1.2-alpha.5",
93
- "@aotter/mantle-vercel": "0.1.2-alpha.5",
94
- "@aotter/mantle-web": "0.1.2-alpha.5",
95
- "@aotter/mantle-bun": "0.1.2-alpha.5",
96
- "@aotter/mantle-cloudflare": "0.1.2-alpha.5"
91
+ "@aotter/mantle-admin": "0.1.2-alpha.6",
92
+ "@aotter/mantle-bun": "0.1.2-alpha.6",
93
+ "@aotter/mantle-admin-ui": "0.1.2-alpha.6",
94
+ "@aotter/mantle-vercel": "0.1.2-alpha.6",
95
+ "@aotter/mantle-cloudflare": "0.1.2-alpha.6",
96
+ "@aotter/mantle-web": "0.1.2-alpha.6"
97
97
  },
98
98
  "peerDependenciesMeta": {
99
99
  "@aotter/mantle-admin": {
@@ -136,12 +136,12 @@
136
136
  "typescript": "^6.0.3",
137
137
  "vitest": "^4.1.11",
138
138
  "zod": "^4.5.4",
139
- "@aotter/mantle-admin-ui": "0.1.2-alpha.5",
140
- "@aotter/mantle-bun": "0.1.2-alpha.5",
141
- "@aotter/mantle-admin": "0.1.2-alpha.5",
142
- "@aotter/mantle-cloudflare": "0.1.2-alpha.5",
143
- "@aotter/mantle-vercel": "0.1.2-alpha.5",
144
- "@aotter/mantle-web": "0.1.2-alpha.5"
139
+ "@aotter/mantle-admin": "0.1.2-alpha.6",
140
+ "@aotter/mantle-admin-ui": "0.1.2-alpha.6",
141
+ "@aotter/mantle-bun": "0.1.2-alpha.6",
142
+ "@aotter/mantle-cloudflare": "0.1.2-alpha.6",
143
+ "@aotter/mantle-vercel": "0.1.2-alpha.6",
144
+ "@aotter/mantle-web": "0.1.2-alpha.6"
145
145
  },
146
146
  "engines": {
147
147
  "node": ">=22"