@kamod-ch/otok-supabase 1.1.0

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.
Files changed (100) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +248 -0
  4. package/dist/admin/create-admin-client.d.ts +4 -0
  5. package/dist/admin/create-admin-client.d.ts.map +1 -0
  6. package/dist/admin/create-admin-client.js +20 -0
  7. package/dist/admin/create-admin-client.js.map +1 -0
  8. package/dist/admin/index.d.ts +2 -0
  9. package/dist/admin/index.d.ts.map +1 -0
  10. package/dist/admin/index.js +2 -0
  11. package/dist/admin/index.js.map +1 -0
  12. package/dist/auth/actions.d.ts +13 -0
  13. package/dist/auth/actions.d.ts.map +1 -0
  14. package/dist/auth/actions.js +98 -0
  15. package/dist/auth/actions.js.map +1 -0
  16. package/dist/auth/index.d.ts +4 -0
  17. package/dist/auth/index.d.ts.map +1 -0
  18. package/dist/auth/index.js +4 -0
  19. package/dist/auth/index.js.map +1 -0
  20. package/dist/auth/middleware.d.ts +7 -0
  21. package/dist/auth/middleware.d.ts.map +1 -0
  22. package/dist/auth/middleware.js +70 -0
  23. package/dist/auth/middleware.js.map +1 -0
  24. package/dist/auth/redirects.d.ts +9 -0
  25. package/dist/auth/redirects.d.ts.map +1 -0
  26. package/dist/auth/redirects.js +45 -0
  27. package/dist/auth/redirects.js.map +1 -0
  28. package/dist/auth/routes.d.ts +10 -0
  29. package/dist/auth/routes.d.ts.map +1 -0
  30. package/dist/auth/routes.js +111 -0
  31. package/dist/auth/routes.js.map +1 -0
  32. package/dist/browser/create-browser-client.d.ts +6 -0
  33. package/dist/browser/create-browser-client.d.ts.map +1 -0
  34. package/dist/browser/create-browser-client.js +27 -0
  35. package/dist/browser/create-browser-client.js.map +1 -0
  36. package/dist/browser/index.d.ts +2 -0
  37. package/dist/browser/index.d.ts.map +1 -0
  38. package/dist/browser/index.js +2 -0
  39. package/dist/browser/index.js.map +1 -0
  40. package/dist/config.d.ts +19 -0
  41. package/dist/config.d.ts.map +1 -0
  42. package/dist/config.js +77 -0
  43. package/dist/config.js.map +1 -0
  44. package/dist/context.d.ts +6 -0
  45. package/dist/context.d.ts.map +1 -0
  46. package/dist/context.js +15 -0
  47. package/dist/context.js.map +1 -0
  48. package/dist/errors.d.ts +27 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +77 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/index.d.ts +10 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +7 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/loader.d.ts +23 -0
  57. package/dist/loader.d.ts.map +1 -0
  58. package/dist/loader.js +27 -0
  59. package/dist/loader.js.map +1 -0
  60. package/dist/plugin.d.ts +6 -0
  61. package/dist/plugin.d.ts.map +1 -0
  62. package/dist/plugin.js +65 -0
  63. package/dist/plugin.js.map +1 -0
  64. package/dist/realtime/index.d.ts +8 -0
  65. package/dist/realtime/index.d.ts.map +1 -0
  66. package/dist/realtime/index.js +8 -0
  67. package/dist/realtime/index.js.map +1 -0
  68. package/dist/registry.d.ts +7 -0
  69. package/dist/registry.d.ts.map +1 -0
  70. package/dist/registry.js +18 -0
  71. package/dist/registry.js.map +1 -0
  72. package/dist/server/cookies.d.ts +20 -0
  73. package/dist/server/cookies.d.ts.map +1 -0
  74. package/dist/server/cookies.js +87 -0
  75. package/dist/server/cookies.js.map +1 -0
  76. package/dist/server/create-server-client.d.ts +6 -0
  77. package/dist/server/create-server-client.d.ts.map +1 -0
  78. package/dist/server/create-server-client.js +20 -0
  79. package/dist/server/create-server-client.js.map +1 -0
  80. package/dist/server/index.d.ts +3 -0
  81. package/dist/server/index.d.ts.map +1 -0
  82. package/dist/server/index.js +3 -0
  83. package/dist/server/index.js.map +1 -0
  84. package/dist/server/middleware.d.ts +6 -0
  85. package/dist/server/middleware.d.ts.map +1 -0
  86. package/dist/server/middleware.js +14 -0
  87. package/dist/server/middleware.js.map +1 -0
  88. package/dist/storage/index.d.ts +8 -0
  89. package/dist/storage/index.d.ts.map +1 -0
  90. package/dist/storage/index.js +8 -0
  91. package/dist/storage/index.js.map +1 -0
  92. package/dist/test/fixtures.d.ts +141 -0
  93. package/dist/test/fixtures.d.ts.map +1 -0
  94. package/dist/test/fixtures.js +30 -0
  95. package/dist/test/fixtures.js.map +1 -0
  96. package/dist/types.d.ts +63 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +2 -0
  99. package/dist/types.js.map +1 -0
  100. package/package.json +92 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # @kamod-ch/otok-supabase
2
+
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8380ef2: Add `@kamod-ch/otok-supabase` with SSR cookie auth, Hono middleware, plugin factory, loader helpers, and auth routes. Register the extension in the Otok registry and add the `with-supabase` example.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [8380ef2]
12
+ - @kamod-ch/otok@0.6.1
13
+
14
+ ## 1.0.0
15
+
16
+ Initial release: SSR cookie auth, PKCE, Hono/Otok middleware, typed clients, auth routes, admin entry point, Otok plugin factory, loader helpers, storage/realtime entry points, and registry integration.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kamod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # @kamod-ch/otok-supabase
2
+
3
+ Production-ready Supabase integration for [Otok](https://github.com/kamod-ch/otok) apps: SSR cookie sessions, PKCE, typed Hono middleware, auth routes, and optional route actions.
4
+
5
+ > **Naming:** This repository publishes extensions as `@kamod-ch/otok-*`. The requested `@otok/supabase` name maps to **`@kamod-ch/otok-supabase`**.
6
+
7
+ ## 1. Purpose and boundaries
8
+
9
+ This package solves Otok-specific integration concerns only:
10
+
11
+ - Request/response cookie wiring for `@supabase/ssr`
12
+ - Per-request SSR clients
13
+ - Typed Hono context (`c.var.supabase`)
14
+ - Auth middleware (`getClaims` / `getUser`)
15
+ - Callback, email confirmation, and sign-out routes
16
+ - Safe redirects and Otok-compatible error mapping
17
+
18
+ It does **not** wrap Supabase queries, Realtime, Storage, or billing. Use `@supabase/supabase-js` directly through the provided clients.
19
+
20
+ ## 2. Installation
21
+
22
+ ```bash
23
+ pnpm add @kamod-ch/otok-supabase @supabase/supabase-js
24
+ ```
25
+
26
+ Peer dependencies: `otok`, `hono`, `@supabase/supabase-js`.
27
+
28
+ ## 3. Prepare your Supabase project
29
+
30
+ 1. Create a project at [supabase.com](https://supabase.com).
31
+ 2. Enable Email auth (password and/or magic link).
32
+ 3. Add site URL and redirect URLs for `/auth/callback` and `/auth/confirm`.
33
+ 4. Apply RLS policies for your tables (see example migration in `examples/with-supabase`).
34
+
35
+ ## 4. Environment variables
36
+
37
+ Read env in your app and pass values explicitly:
38
+
39
+ ```env
40
+ SUPABASE_URL=https://your-project.supabase.co
41
+ SUPABASE_PUBLISHABLE_KEY=your-publishable-key
42
+ SUPABASE_SERVICE_ROLE_KEY=server-only-service-role-key
43
+ ```
44
+
45
+ Never expose the service role key to the browser.
46
+
47
+ ## 5. Server middleware
48
+
49
+ ### Otok plugin (recommended)
50
+
51
+ ```typescript
52
+ import { defineConfig } from "@kamod-ch/otok";
53
+ import supabase from "@kamod-ch/otok-supabase";
54
+
55
+ export default defineConfig({
56
+ plugins: [
57
+ supabase<Database>({
58
+ url: env.SUPABASE_URL,
59
+ publishableKey: env.SUPABASE_PUBLISHABLE_KEY,
60
+ cookieOptions: { path: "/", sameSite: "lax", secure: true },
61
+ authRoutes: {
62
+ successRedirect: "/dashboard",
63
+ errorRedirect: "/login",
64
+ redirectAllowlist: ["/", "/dashboard"],
65
+ },
66
+ }),
67
+ ],
68
+ });
69
+ ```
70
+
71
+ The plugin mounts SSR middleware and auth routes via `configureApp`. For loaders, use `@kamod-ch/otok-supabase/loader`:
72
+
73
+ ```typescript
74
+ import { defineLoader } from "@kamod-ch/otok-supabase/loader";
75
+
76
+ export const loader = defineLoader<Database>(async ({ supabase }) => {
77
+ const { data } = await supabase.from("projects").select("*");
78
+ return { projects: data ?? [] };
79
+ });
80
+ ```
81
+
82
+ ### Manual Hono middleware
83
+
84
+ ```typescript
85
+ import { supabase } from "@kamod-ch/otok-supabase/server";
86
+ import type { Database } from "./database.types";
87
+
88
+ const app = new Hono();
89
+
90
+ app.use(
91
+ "*",
92
+ supabase<Database>({
93
+ url: env.SUPABASE_URL,
94
+ publishableKey: env.SUPABASE_PUBLISHABLE_KEY,
95
+ cookieOptions: { path: "/", sameSite: "lax", secure: true },
96
+ }),
97
+ );
98
+
99
+ app.get("/projects", async (c) => {
100
+ const { data, error } = await c.var.supabase.from("projects").select("*");
101
+ if (error) throw error;
102
+ return c.json(data);
103
+ });
104
+ ```
105
+
106
+ In Otok apps, mount the same middleware in `createOtokApp({ configure })` — see `examples/with-supabase`.
107
+
108
+ ## 6. Browser client
109
+
110
+ ```typescript
111
+ import { createOtokSupabaseBrowserClient } from "@kamod-ch/otok-supabase/browser";
112
+
113
+ const supabase = createOtokSupabaseBrowserClient<Database>({
114
+ url: import.meta.env.SUPABASE_URL,
115
+ publishableKey: import.meta.env.SUPABASE_PUBLISHABLE_KEY,
116
+ });
117
+ ```
118
+
119
+ Call only in the browser (not at module import time on the server).
120
+
121
+ ## 7. Protect routes
122
+
123
+ ### Fast JWT check — `getClaims()`
124
+
125
+ ```typescript
126
+ import { requireSupabaseAuth } from "@kamod-ch/otok-supabase/auth";
127
+
128
+ app.use("/dashboard/*", requireSupabaseAuth({ redirectTo: "/login" }));
129
+ app.get("/dashboard", (c) => c.json({ sub: c.var.supabaseClaims.sub }));
130
+ ```
131
+
132
+ ### Auth-server user — `getUser()`
133
+
134
+ ```typescript
135
+ import { requireSupabaseUser } from "@kamod-ch/otok-supabase/auth";
136
+
137
+ app.use("/account/*", requireSupabaseUser({ response: "json" }));
138
+ ```
139
+
140
+ ## 8. Callback routes
141
+
142
+ ```typescript
143
+ import { createSupabaseAuthRoutes } from "@kamod-ch/otok-supabase/auth";
144
+
145
+ createSupabaseAuthRoutes({
146
+ successRedirect: "/dashboard",
147
+ errorRedirect: "/login",
148
+ redirectAllowlist: ["/", "/dashboard", "/login"],
149
+ }).mount(app);
150
+ ```
151
+
152
+ Routes: `GET /auth/callback`, `GET /auth/confirm`, `POST /auth/signout`.
153
+
154
+ ## 9. Magic link
155
+
156
+ Use `sendMagicLinkAction()` in a route action or call `supabase.auth.signInWithOtp()` from your own form handler. Configure `emailRedirectTo` to your callback/confirm URLs in Supabase.
157
+
158
+ ## 10. Generate database types
159
+
160
+ ```bash
161
+ supabase gen types typescript \
162
+ --project-id "$SUPABASE_PROJECT_ID" \
163
+ --schema public \
164
+ > src/database.types.ts
165
+ ```
166
+
167
+ ## 11. Row Level Security
168
+
169
+ Always enable RLS on user-facing tables. The example migration in `examples/with-supabase/supabase/migrations/` demonstrates profiles, projects, and project_members policies.
170
+
171
+ ## 12. Admin client
172
+
173
+ ```typescript
174
+ import { createOtokSupabaseAdminClient } from "@kamod-ch/otok-supabase/admin";
175
+
176
+ const admin = createOtokSupabaseAdminClient<Database>({
177
+ url: env.SUPABASE_URL,
178
+ serviceRoleKey: env.SUPABASE_SERVICE_ROLE_KEY,
179
+ });
180
+ ```
181
+
182
+ **Server only.** Bypasses RLS — never import from the root or browser entry points.
183
+
184
+ ## 13. Node deployment
185
+
186
+ Pass env vars to your Node/Bun adapter as with any Otok app. Middleware uses standard Web APIs (`Headers`, `Request`, `Response`) — no Node-only cookie APIs.
187
+
188
+ ## 14. Cloudflare Workers
189
+
190
+ Pass `SUPABASE_URL` and `SUPABASE_PUBLISHABLE_KEY` from Worker bindings into `supabase(config)` in your fetch handler or Otok Cloudflare adapter `configure` hook.
191
+
192
+ ## 15. Bun
193
+
194
+ Same as Node — explicit config object, no `process.env` inside the library.
195
+
196
+ ## 16. Troubleshooting
197
+
198
+ | Symptom | Check |
199
+ |---------|--------|
200
+ | Random logouts | Ensure `supabase()` middleware runs on every request with `getAll` + `setAll` |
201
+ | Redirect loops | Verify `redirectAllowlist` includes post-login targets |
202
+ | 401 on API routes | Set `Accept: application/json` or use `response: "json"` |
203
+ | CSRF on sign-out | Include `_csrf` field matching `otok_csrf` cookie |
204
+
205
+ ## 17. Security checklist
206
+
207
+ - [ ] Publishable key only in browser/SSR client config
208
+ - [ ] Service role key only in server/admin code
209
+ - [ ] RLS enabled on all public tables
210
+ - [ ] Redirect allowlist configured
211
+ - [ ] HTTPS + `secure` cookies in production
212
+ - [ ] Sign-out uses POST + CSRF
213
+
214
+ ## 18. `getClaims` vs `getUser` vs `getSession`
215
+
216
+ | Method | Use for |
217
+ |--------|---------|
218
+ | `getClaims()` | Fast route guards — local JWT verification (JWKS for asymmetric keys) |
219
+ | `getUser()` | Sensitive operations needing Auth-server confirmation |
220
+ | `getSession()` | Cookie refresh inside middleware only — **not** authorization |
221
+
222
+ ## 19. Working with `@kamod-ch/otok-oauth`
223
+
224
+ Use OAuth for social login and Supabase for database/auth as separate layers, or map OAuth users into Supabase via admin API. Do not mix session cookies without a clear bridge strategy.
225
+
226
+ ## 20. `@supabase/ssr` caveats
227
+
228
+ - Always implement both `getAll` and `setAll` when response headers can be set
229
+ - Apply cache headers from `setAll` to prevent CDN session leakage
230
+ - Cookie API is encapsulated in this package — pin versions and run tests when upgrading `@supabase/ssr`
231
+
232
+ ## API exports
233
+
234
+ | Entry | Exports |
235
+ |-------|---------|
236
+ | `.` | Config validation, errors, types, server middleware |
237
+ | `./server` | `createOtokSupabaseServerClient`, cookie helpers |
238
+ | `./browser` | `createOtokSupabaseBrowserClient` |
239
+ | `./auth` | Middleware, routes, actions, redirects |
240
+ | `./admin` | `createOtokSupabaseAdminClient` |
241
+ | `./storage` | `getSupabaseStorage` — Storage entry point |
242
+ | `./plugin` | Plugin factory (same as root default export) |
243
+ | `./registry` | Runtime registry helpers |
244
+ | `./loader` | `defineLoader` / `defineAction` with `supabase` injection |
245
+
246
+ ## License
247
+
248
+ MIT
@@ -0,0 +1,4 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import { type SupabaseAdminConfig } from "../config.js";
3
+ export declare function createOtokSupabaseAdminClient<Database>(config: SupabaseAdminConfig): SupabaseClient<Database>;
4
+ //# sourceMappingURL=create-admin-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-admin-client.d.ts","sourceRoot":"","sources":["../../src/admin/create-admin-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAA+B,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAWrF,wBAAgB,6BAA6B,CAAC,QAAQ,EACpD,MAAM,EAAE,mBAAmB,GAC1B,cAAc,CAAC,QAAQ,CAAC,CAW1B"}
@@ -0,0 +1,20 @@
1
+ import { createClient } from "@supabase/supabase-js";
2
+ import { validateSupabaseAdminConfig } from "../config.js";
3
+ import { SupabaseConfigurationError } from "../errors.js";
4
+ function assertServerRuntime() {
5
+ if (typeof window !== "undefined") {
6
+ throw new SupabaseConfigurationError("createOtokSupabaseAdminClient() is server-only and must not run in the browser.");
7
+ }
8
+ }
9
+ export function createOtokSupabaseAdminClient(config) {
10
+ assertServerRuntime();
11
+ const validated = validateSupabaseAdminConfig(config);
12
+ return createClient(validated.url, validated.serviceRoleKey, {
13
+ auth: {
14
+ autoRefreshToken: false,
15
+ persistSession: false,
16
+ detectSessionInUrl: false,
17
+ },
18
+ });
19
+ }
20
+ //# sourceMappingURL=create-admin-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-admin-client.js","sourceRoot":"","sources":["../../src/admin/create-admin-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,OAAO,EAAE,2BAA2B,EAA4B,MAAM,cAAc,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAE1D,SAAS,mBAAmB;IAC1B,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QAClC,MAAM,IAAI,0BAA0B,CAClC,iFAAiF,CAClF,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,MAA2B;IAE3B,mBAAmB,EAAE,CAAC;IACtB,MAAM,SAAS,GAAG,2BAA2B,CAAC,MAAM,CAAC,CAAC;IAEtD,OAAO,YAAY,CAAW,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,cAAc,EAAE;QACrE,IAAI,EAAE;YACJ,gBAAgB,EAAE,KAAK;YACvB,cAAc,EAAE,KAAK;YACrB,kBAAkB,EAAE,KAAK;SAC1B;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createOtokSupabaseAdminClient } from "./create-admin-client.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { createOtokSupabaseAdminClient } from "./create-admin-client.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,6BAA6B,EAAE,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,13 @@
1
+ import type { OtokActionContext } from "@kamod-ch/otok/server";
2
+ import type { SupabaseActionResult } from "../types.js";
3
+ export interface SupabasePasswordActionOptions {
4
+ successRedirect?: string;
5
+ redirectAllowlist?: readonly string[];
6
+ }
7
+ export declare function signInWithPasswordAction(options?: SupabasePasswordActionOptions): (ctx: OtokActionContext) => Promise<SupabaseActionResult>;
8
+ export declare function signUpAction(options?: SupabasePasswordActionOptions): (ctx: OtokActionContext) => Promise<SupabaseActionResult>;
9
+ export declare function sendMagicLinkAction(options?: SupabasePasswordActionOptions): (ctx: OtokActionContext) => Promise<SupabaseActionResult>;
10
+ export declare function requestPasswordResetAction(options?: SupabasePasswordActionOptions): (ctx: OtokActionContext) => Promise<SupabaseActionResult>;
11
+ export declare function updatePasswordAction(options?: SupabasePasswordActionOptions): (ctx: OtokActionContext) => Promise<SupabaseActionResult>;
12
+ export declare function signOutAction(options?: SupabasePasswordActionOptions): (ctx: OtokActionContext) => Promise<SupabaseActionResult>;
13
+ //# sourceMappingURL=actions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/auth/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAK/D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA0BxD,MAAM,WAAW,6BAA6B;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,wBAAgB,wBAAwB,CAAC,OAAO,GAAE,6BAAkC,IACpE,KAAK,iBAAiB,KAAG,OAAO,CAAC,oBAAoB,CAAC,CAerE;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,6BAAkC,IACxD,KAAK,iBAAiB,KAAG,OAAO,CAAC,oBAAoB,CAAC,CAerE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,6BAAkC,IAC/D,KAAK,iBAAiB,KAAG,OAAO,CAAC,oBAAoB,CAAC,CAkBrE;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,6BAAkC,IACtE,KAAK,iBAAiB,KAAG,OAAO,CAAC,oBAAoB,CAAC,CAerE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,6BAAkC,IAChE,KAAK,iBAAiB,KAAG,OAAO,CAAC,oBAAoB,CAAC,CAcrE;AAED,wBAAgB,aAAa,CAAC,OAAO,GAAE,6BAAkC,IACzD,KAAK,iBAAiB,KAAG,OAAO,CAAC,oBAAoB,CAAC,CAarE"}
@@ -0,0 +1,98 @@
1
+ import { fail, redirect, validationError } from "@kamod-ch/otok/server";
2
+ import { isDataRequest } from "@kamod-ch/otok/shared";
3
+ import { supabaseFromHono } from "../context.js";
4
+ import { mapSupabaseError } from "../errors.js";
5
+ import { resolveAuthRedirect } from "./redirects.js";
6
+ function requireField(formData, name) {
7
+ const value = String(formData?.get(name) ?? "").trim();
8
+ if (!value) {
9
+ validationError({ fieldErrors: { [name]: [`${name} is required.`] } });
10
+ }
11
+ return value;
12
+ }
13
+ function actionRedirectOrJson(c, target, data) {
14
+ if (isDataRequest(c.request)) {
15
+ return { ok: true, data, redirect: target };
16
+ }
17
+ redirect(target, 303);
18
+ }
19
+ function handleAuthActionError(error, fallback) {
20
+ const failure = mapSupabaseError(error, fallback);
21
+ if (failure.fieldErrors) {
22
+ validationError({ message: failure.message, fieldErrors: failure.fieldErrors }, failure.status === 422 ? 422 : 400);
23
+ }
24
+ fail(failure.status, failure);
25
+ }
26
+ export function signInWithPasswordAction(options = {}) {
27
+ return async (ctx) => {
28
+ const email = requireField(ctx.formData, "email");
29
+ const password = requireField(ctx.formData, "password");
30
+ const supabase = supabaseFromHono(ctx.hono);
31
+ const { data, error } = await supabase.auth.signInWithPassword({ email, password });
32
+ if (error)
33
+ handleAuthActionError(error, "Sign in failed.");
34
+ const target = resolveAuthRedirect(String(ctx.formData?.get("returnTo") ?? ""), options.successRedirect ?? "/dashboard", options.redirectAllowlist);
35
+ return actionRedirectOrJson(ctx, target, { user: data.user });
36
+ };
37
+ }
38
+ export function signUpAction(options = {}) {
39
+ return async (ctx) => {
40
+ const email = requireField(ctx.formData, "email");
41
+ const password = requireField(ctx.formData, "password");
42
+ const supabase = supabaseFromHono(ctx.hono);
43
+ const { data, error } = await supabase.auth.signUp({ email, password });
44
+ if (error)
45
+ handleAuthActionError(error, "Sign up failed.");
46
+ const target = resolveAuthRedirect(String(ctx.formData?.get("returnTo") ?? ""), options.successRedirect ?? "/dashboard", options.redirectAllowlist);
47
+ return actionRedirectOrJson(ctx, target, { user: data.user });
48
+ };
49
+ }
50
+ export function sendMagicLinkAction(options = {}) {
51
+ return async (ctx) => {
52
+ const email = requireField(ctx.formData, "email");
53
+ const supabase = supabaseFromHono(ctx.hono);
54
+ const emailRedirectTo = String(ctx.formData?.get("emailRedirectTo") ?? "").trim() || undefined;
55
+ const { error } = await supabase.auth.signInWithOtp({
56
+ email,
57
+ options: emailRedirectTo ? { emailRedirectTo } : undefined,
58
+ });
59
+ if (error)
60
+ handleAuthActionError(error, "Magic link request failed.");
61
+ const target = resolveAuthRedirect(String(ctx.formData?.get("returnTo") ?? ""), options.successRedirect ?? "/login?checkEmail=1", options.redirectAllowlist);
62
+ return actionRedirectOrJson(ctx, target, { sent: true });
63
+ };
64
+ }
65
+ export function requestPasswordResetAction(options = {}) {
66
+ return async (ctx) => {
67
+ const email = requireField(ctx.formData, "email");
68
+ const supabase = supabaseFromHono(ctx.hono);
69
+ const redirectTo = String(ctx.formData?.get("redirectTo") ?? "").trim() || undefined;
70
+ const { error } = await supabase.auth.resetPasswordForEmail(email, redirectTo ? { redirectTo } : undefined);
71
+ if (error)
72
+ handleAuthActionError(error, "Password reset request failed.");
73
+ const target = resolveAuthRedirect(String(ctx.formData?.get("returnTo") ?? ""), options.successRedirect ?? "/login?resetSent=1", options.redirectAllowlist);
74
+ return actionRedirectOrJson(ctx, target, { sent: true });
75
+ };
76
+ }
77
+ export function updatePasswordAction(options = {}) {
78
+ return async (ctx) => {
79
+ const password = requireField(ctx.formData, "password");
80
+ const supabase = supabaseFromHono(ctx.hono);
81
+ const { data, error } = await supabase.auth.updateUser({ password });
82
+ if (error)
83
+ handleAuthActionError(error, "Password update failed.");
84
+ const target = resolveAuthRedirect(String(ctx.formData?.get("returnTo") ?? ""), options.successRedirect ?? "/dashboard", options.redirectAllowlist);
85
+ return actionRedirectOrJson(ctx, target, { user: data.user });
86
+ };
87
+ }
88
+ export function signOutAction(options = {}) {
89
+ return async (ctx) => {
90
+ const supabase = supabaseFromHono(ctx.hono);
91
+ const { error } = await supabase.auth.signOut();
92
+ if (error)
93
+ handleAuthActionError(error, "Sign out failed.");
94
+ const target = resolveAuthRedirect(String(ctx.formData?.get("returnTo") ?? ""), options.successRedirect ?? "/login", options.redirectAllowlist);
95
+ return actionRedirectOrJson(ctx, target, { signedOut: true });
96
+ };
97
+ }
98
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../src/auth/actions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { requireSupabaseAuth, requireSupabaseUser, requireSupabaseAuthAndUser, safeRedirectPath, resolveAuthRedirect, isApiLikeRequest, prefersHtmlResponse, } from "./middleware.js";
2
+ export { createSupabaseAuthRoutes, type SupabaseAuthRoutes } from "./routes.js";
3
+ export { signInWithPasswordAction, signUpAction, sendMagicLinkAction, requestPasswordResetAction, updatePasswordAction, signOutAction, } from "./actions.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,wBAAwB,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EACL,wBAAwB,EACxB,YAAY,EACZ,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,aAAa,GACd,MAAM,cAAc,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { requireSupabaseAuth, requireSupabaseUser, requireSupabaseAuthAndUser, safeRedirectPath, resolveAuthRedirect, isApiLikeRequest, prefersHtmlResponse, } from "./middleware.js";
2
+ export { createSupabaseAuthRoutes } from "./routes.js";
3
+ export { signInWithPasswordAction, signUpAction, sendMagicLinkAction, requestPasswordResetAction, updatePasswordAction, signOutAction, } from "./actions.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,0BAA0B,EAC1B,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,wBAAwB,EAA2B,MAAM,aAAa,CAAC;AAChF,OAAO,EACL,wBAAwB,EACxB,YAAY,EACZ,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,EACpB,aAAa,GACd,MAAM,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+ import type { RequireSupabaseAuthOptions, RequireSupabaseUserOptions, SupabaseAuthClaimsEnv, SupabaseAuthFullEnv, SupabaseAuthUserEnv } from "../types.js";
3
+ export declare function requireSupabaseAuth<Database = unknown>(options?: RequireSupabaseAuthOptions): MiddlewareHandler<SupabaseAuthClaimsEnv<Database>>;
4
+ export declare function requireSupabaseUser<Database = unknown>(options?: RequireSupabaseUserOptions): MiddlewareHandler<SupabaseAuthUserEnv<Database>>;
5
+ export declare function requireSupabaseAuthAndUser<Database = unknown>(options?: RequireSupabaseUserOptions): MiddlewareHandler<SupabaseAuthFullEnv<Database>>;
6
+ export { safeRedirectPath, resolveAuthRedirect, isApiLikeRequest, prefersHtmlResponse } from "./redirects.js";
7
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAEvD,OAAO,KAAK,EACV,0BAA0B,EAC1B,0BAA0B,EAC1B,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AA0BrB,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,OAAO,EACpD,OAAO,GAAE,0BAA+B,GACvC,iBAAiB,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC,CAYpD;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,OAAO,EACpD,OAAO,GAAE,0BAA+B,GACvC,iBAAiB,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAoBlD;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,GAAG,OAAO,EAC3D,OAAO,GAAE,0BAA+B,GACvC,iBAAiB,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAoBlD;AAED,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { createMiddleware } from "hono/factory";
2
+ import { isDataRequest } from "@kamod-ch/otok/shared";
3
+ import { mapSupabaseError } from "../errors.js";
4
+ import { isApiLikeRequest, prefersHtmlResponse, resolveAuthRedirect, safeRedirectPath } from "./redirects.js";
5
+ function resolveRedirectTarget(c, options) {
6
+ const requested = c.req.query("returnTo") ?? c.req.query("next");
7
+ const fallback = options.redirectTo ?? "/login";
8
+ return resolveAuthRedirect(requested, fallback, options.redirectAllowlist);
9
+ }
10
+ function handleUnauthenticated(c, options) {
11
+ const request = c.req.raw;
12
+ const responseMode = options.response ?? (prefersHtmlResponse(request) ? "redirect" : "json");
13
+ const redirectTarget = resolveRedirectTarget(c, options);
14
+ if (responseMode === "redirect" && !isApiLikeRequest(request) && !isDataRequest(request)) {
15
+ const location = safeRedirectPath(redirectTarget, options.redirectAllowlist ?? ["/"]) ?? "/login";
16
+ return c.redirect(location, 303);
17
+ }
18
+ return c.json({ status: 401, message: "Authentication required." }, 401);
19
+ }
20
+ export function requireSupabaseAuth(options = {}) {
21
+ return createMiddleware(async (c, next) => {
22
+ const supabase = c.var.supabase;
23
+ const { data, error } = await supabase.auth.getClaims();
24
+ if (error || !data?.claims) {
25
+ return handleUnauthenticated(c, options);
26
+ }
27
+ c.set("supabaseClaims", data.claims);
28
+ await next();
29
+ });
30
+ }
31
+ export function requireSupabaseUser(options = {}) {
32
+ return createMiddleware(async (c, next) => {
33
+ const supabase = c.var.supabase;
34
+ const { data, error } = await supabase.auth.getUser();
35
+ if (error) {
36
+ const failure = mapSupabaseError(error, "Authentication required.");
37
+ if (failure.status === 401) {
38
+ return handleUnauthenticated(c, options);
39
+ }
40
+ return c.json(failure, failure.status);
41
+ }
42
+ if (!data.user) {
43
+ return handleUnauthenticated(c, options);
44
+ }
45
+ c.set("supabaseUser", data.user);
46
+ await next();
47
+ });
48
+ }
49
+ export function requireSupabaseAuthAndUser(options = {}) {
50
+ return createMiddleware(async (c, next) => {
51
+ const supabase = c.var.supabase;
52
+ const claimsResult = await supabase.auth.getClaims();
53
+ if (claimsResult.error || !claimsResult.data?.claims) {
54
+ return handleUnauthenticated(c, options);
55
+ }
56
+ c.set("supabaseClaims", claimsResult.data.claims);
57
+ const userResult = await supabase.auth.getUser();
58
+ if (userResult.error) {
59
+ const failure = mapSupabaseError(userResult.error, "Authentication required.");
60
+ return c.json(failure, failure.status);
61
+ }
62
+ if (!userResult.data.user) {
63
+ return handleUnauthenticated(c, options);
64
+ }
65
+ c.set("supabaseUser", userResult.data.user);
66
+ await next();
67
+ });
68
+ }
69
+ export { safeRedirectPath, resolveAuthRedirect, isApiLikeRequest, prefersHtmlResponse } from "./redirects.js";
70
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Validates a post-auth redirect target against an allowlist.
3
+ * Only same-origin relative paths are accepted; `//` and external URLs are rejected.
4
+ */
5
+ export declare function safeRedirectPath(raw: string | null | undefined, allowlist?: readonly string[]): string | null;
6
+ export declare function resolveAuthRedirect(requested: string | null | undefined, fallback: string, allowlist?: readonly string[]): string;
7
+ export declare function isApiLikeRequest(request: Request): boolean;
8
+ export declare function prefersHtmlResponse(request: Request): boolean;
9
+ //# sourceMappingURL=redirects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirects.d.ts","sourceRoot":"","sources":["../../src/auth/redirects.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,SAAS,GAAE,SAAS,MAAM,EAAsB,GAC/C,MAAM,GAAG,IAAI,CAWf;AAED,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,QAAQ,EAAE,MAAM,EAChB,SAAS,GAAE,SAAS,MAAM,EAAsB,GAC/C,MAAM,CASR;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAO1D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAG7D"}
@@ -0,0 +1,45 @@
1
+ import { SupabaseAuthError } from "../errors.js";
2
+ const DEFAULT_ALLOWLIST = ["/"];
3
+ /**
4
+ * Validates a post-auth redirect target against an allowlist.
5
+ * Only same-origin relative paths are accepted; `//` and external URLs are rejected.
6
+ */
7
+ export function safeRedirectPath(raw, allowlist = DEFAULT_ALLOWLIST) {
8
+ if (!raw)
9
+ return null;
10
+ if (!raw.startsWith("/") || raw.startsWith("//"))
11
+ return null;
12
+ const allowed = allowlist.length > 0 ? allowlist : DEFAULT_ALLOWLIST;
13
+ const matches = allowed.some((prefix) => {
14
+ if (prefix === "/")
15
+ return true;
16
+ return raw === prefix || raw.startsWith(`${prefix}/`);
17
+ });
18
+ return matches ? raw : null;
19
+ }
20
+ export function resolveAuthRedirect(requested, fallback, allowlist = DEFAULT_ALLOWLIST) {
21
+ const safe = safeRedirectPath(requested, allowlist);
22
+ if (!safe) {
23
+ if (requested) {
24
+ throw new SupabaseAuthError("Unsafe redirect target.", 400);
25
+ }
26
+ return fallback;
27
+ }
28
+ return safe;
29
+ }
30
+ export function isApiLikeRequest(request) {
31
+ const accept = request.headers.get("accept") ?? "";
32
+ if (accept.includes("application/json"))
33
+ return true;
34
+ if (accept.includes("application/vnd.otok+json"))
35
+ return true;
36
+ if (request.headers.get("x-otok-data") === "1")
37
+ return true;
38
+ const requestedWith = request.headers.get("x-requested-with");
39
+ return requestedWith?.toLowerCase() === "xmlhttprequest";
40
+ }
41
+ export function prefersHtmlResponse(request) {
42
+ const accept = request.headers.get("accept") ?? "";
43
+ return accept.includes("text/html") || (!isApiLikeRequest(request) && !accept.includes("application/json"));
44
+ }
45
+ //# sourceMappingURL=redirects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirects.js","sourceRoot":"","sources":["../../src/auth/redirects.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,iBAAiB,GAAG,CAAC,GAAG,CAAU,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,GAA8B,EAC9B,YAA+B,iBAAiB;IAEhD,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9D,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC;IACrE,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACtC,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAChC,OAAO,GAAG,KAAK,MAAM,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,SAAoC,EACpC,QAAgB,EAChB,YAA+B,iBAAiB;IAEhD,MAAM,IAAI,GAAG,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACpD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,IAAI,iBAAiB,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAgB;IAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,IAAI,MAAM,CAAC,QAAQ,CAAC,2BAA2B,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9D,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAC5D,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC9D,OAAO,aAAa,EAAE,WAAW,EAAE,KAAK,gBAAgB,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,OAAgB;IAClD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACnD,OAAO,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAC9G,CAAC"}