@kamod-ch/otok-supabase 1.0.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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +248 -0
- package/dist/admin/create-admin-client.d.ts +4 -0
- package/dist/admin/create-admin-client.d.ts.map +1 -0
- package/dist/admin/create-admin-client.js +22 -0
- package/dist/admin/create-admin-client.js.map +1 -0
- package/dist/admin/index.d.ts +2 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +2 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/auth/actions.d.ts +13 -0
- package/dist/auth/actions.d.ts.map +1 -0
- package/dist/auth/actions.js +98 -0
- package/dist/auth/actions.js.map +1 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +4 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +7 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +70 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/redirects.d.ts +9 -0
- package/dist/auth/redirects.d.ts.map +1 -0
- package/dist/auth/redirects.js +45 -0
- package/dist/auth/redirects.js.map +1 -0
- package/dist/auth/routes.d.ts +10 -0
- package/dist/auth/routes.d.ts.map +1 -0
- package/dist/auth/routes.js +111 -0
- package/dist/auth/routes.js.map +1 -0
- package/dist/browser/create-browser-client.d.ts +6 -0
- package/dist/browser/create-browser-client.d.ts.map +1 -0
- package/dist/browser/create-browser-client.js +27 -0
- package/dist/browser/create-browser-client.js.map +1 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +6 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +15 -0
- package/dist/context.js.map +1 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +74 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.d.ts +23 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +27 -0
- package/dist/loader.js.map +1 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +65 -0
- package/dist/plugin.js.map +1 -0
- package/dist/realtime/index.d.ts +8 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +8 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/registry.d.ts +7 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +18 -0
- package/dist/registry.js.map +1 -0
- package/dist/server/cookies.d.ts +20 -0
- package/dist/server/cookies.d.ts.map +1 -0
- package/dist/server/cookies.js +87 -0
- package/dist/server/cookies.js.map +1 -0
- package/dist/server/create-server-client.d.ts +6 -0
- package/dist/server/create-server-client.d.ts.map +1 -0
- package/dist/server/create-server-client.js +20 -0
- package/dist/server/create-server-client.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware.d.ts +6 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +14 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/storage/index.d.ts +8 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +8 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/test/fixtures.d.ts +141 -0
- package/dist/test/fixtures.d.ts.map +1 -0
- package/dist/test/fixtures.js +30 -0
- package/dist/test/fixtures.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +94 -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;AAE5D,OAAO,EAA+B,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAWrF,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,MAAM,EAAE,mBAAmB,GAAG,cAAc,CAAC,QAAQ,CAAC,CAY7G"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createClient } from "@supabase/supabase-js";
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
import { validateSupabaseAdminConfig } from "../config.js";
|
|
4
|
+
import { SupabaseConfigurationError } from "../errors.js";
|
|
5
|
+
function assertServerRuntime() {
|
|
6
|
+
if (typeof window !== "undefined") {
|
|
7
|
+
throw new SupabaseConfigurationError("createOtokSupabaseAdminClient() is server-only and must not run in the browser.");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function createOtokSupabaseAdminClient(config) {
|
|
11
|
+
assertServerRuntime();
|
|
12
|
+
const validated = validateSupabaseAdminConfig(config);
|
|
13
|
+
return createClient(validated.url, validated.serviceRoleKey, {
|
|
14
|
+
auth: {
|
|
15
|
+
autoRefreshToken: false,
|
|
16
|
+
persistSession: false,
|
|
17
|
+
detectSessionInUrl: false,
|
|
18
|
+
},
|
|
19
|
+
realtime: { transport: WebSocket },
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
//# 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,SAAS,MAAM,IAAI,CAAC;AAC3B,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,CAAW,MAA2B;IACjF,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;QACD,QAAQ,EAAE,EAAE,SAAS,EAAE,SAAkB,EAAE;KAC5C,CAA6B,CAAC;AACjC,CAAC"}
|
|
@@ -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 @@
|
|
|
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;AAuBrB,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
|