@banata-auth/nextjs 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Banata Auth Contributors
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,48 @@
1
+ # @banata-auth/nextjs
2
+
3
+ Next.js integration for Banata Auth -- route handler proxy, middleware, and server-side utilities.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @banata-auth/nextjs
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ### Route handler
14
+
15
+ ```ts
16
+ // app/api/auth/[...all]/route.ts
17
+ import { createRouteHandler } from "@banata-auth/nextjs";
18
+
19
+ const handler = createRouteHandler({
20
+ convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
21
+ });
22
+
23
+ export const { GET, POST } = handler;
24
+ ```
25
+
26
+ ### Server-side auth
27
+
28
+ ```ts
29
+ import { createBanataAuthServer } from "@banata-auth/nextjs/server";
30
+
31
+ const { handler, isAuthenticated, getToken } = createBanataAuthServer({
32
+ convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
33
+ convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
34
+ });
35
+
36
+ // In a Server Component or API route
37
+ const authenticated = await isAuthenticated();
38
+ ```
39
+
40
+ ## Features
41
+
42
+ - Secure proxy route handler with header allowlisting
43
+ - Server-side session and auth token retrieval
44
+ - Middleware helpers for auth-gated routes
45
+
46
+ ## License
47
+
48
+ MIT
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Bot protection utilities for Next.js applications using Banata Auth.
3
+ *
4
+ * Provides helpers to integrate bot detection services (Vercel BotID,
5
+ * Cloudflare Turnstile, hCaptcha, reCAPTCHA) with the Banata Auth
6
+ * route handler.
7
+ *
8
+ * These utilities wrap the route handler's POST method to automatically
9
+ * verify requests against protected auth paths before forwarding to
10
+ * the auth backend.
11
+ *
12
+ * Credentials can be supplied directly or fetched from the Banata Auth
13
+ * config API (configured via the Radar page in the dashboard).
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * // app/api/auth/[...all]/route.ts
18
+ * import { createRouteHandler } from "@banata-auth/nextjs";
19
+ * import { withBotProtection, createBotIdVerifier } from "@banata-auth/nextjs/bot-protection";
20
+ *
21
+ * const handler = createRouteHandler({
22
+ * convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
23
+ * });
24
+ *
25
+ * export const GET = handler.GET;
26
+ * export const POST = withBotProtection(handler.POST, {
27
+ * protectedPaths: ["/api/auth/sign-in", "/api/auth/sign-up"],
28
+ * verify: async () => {
29
+ * const { checkBotId } = await import("botid/server");
30
+ * const result = await checkBotId();
31
+ * return { isBot: result.isBot };
32
+ * },
33
+ * });
34
+ * ```
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * // Using credentials from the Banata Auth config API
39
+ * import { createConfigAwareVerifier } from "@banata-auth/nextjs/bot-protection";
40
+ *
41
+ * const verify = createConfigAwareVerifier({
42
+ * configApiUrl: process.env.NEXT_PUBLIC_APP_URL + "/api/auth",
43
+ * });
44
+ *
45
+ * export const POST = withBotProtection(handler.POST, { verify });
46
+ * ```
47
+ */
48
+ /**
49
+ * Result of a bot verification check.
50
+ */
51
+ interface BotCheckResult {
52
+ /** Whether the request is from a bot. */
53
+ isBot: boolean;
54
+ /** Optional reason string for logging. */
55
+ reason?: string;
56
+ }
57
+ /**
58
+ * Function that verifies whether a request is from a bot.
59
+ * Implement with your provider of choice.
60
+ */
61
+ type BotCheckFn = (request: Request) => Promise<BotCheckResult>;
62
+ /**
63
+ * Configuration for the route-handler-level bot protection wrapper.
64
+ */
65
+ interface BotProtectionConfig {
66
+ /**
67
+ * The verification function.
68
+ * Called for every request matching a protected path.
69
+ */
70
+ verify: BotCheckFn;
71
+ /**
72
+ * Path prefixes to protect. Requests to other paths pass through.
73
+ * @default ["/api/auth/sign-in", "/api/auth/sign-up", "/api/auth/forget-password", "/api/auth/reset-password"]
74
+ */
75
+ protectedPaths?: string[];
76
+ /**
77
+ * Whether to allow requests through if verification fails (service unavailable).
78
+ * @default true
79
+ */
80
+ failOpen?: boolean;
81
+ /**
82
+ * Error message returned when a bot is detected.
83
+ * @default "Bot detected. Access denied."
84
+ */
85
+ blockedMessage?: string;
86
+ }
87
+ /**
88
+ * Bot provider credentials as stored in the Radar configuration.
89
+ */
90
+ interface BotProviderCredentials {
91
+ apiKey?: string;
92
+ siteKey?: string;
93
+ secretKey?: string;
94
+ }
95
+ /**
96
+ * Radar configuration shape returned by the config API.
97
+ */
98
+ interface RadarConfigResponse {
99
+ enabled: boolean;
100
+ botDetection: boolean;
101
+ botProvider: string | null;
102
+ botProviderCredentials: BotProviderCredentials;
103
+ }
104
+ /**
105
+ * Options for creating a config-aware bot verifier.
106
+ */
107
+ interface ConfigAwareVerifierOptions {
108
+ /**
109
+ * Base URL of the Banata Auth API (e.g., "http://localhost:3000/api/auth").
110
+ * The radar config endpoint will be called at `${configApiUrl}/banata/config/radar/get`.
111
+ */
112
+ configApiUrl: string;
113
+ /**
114
+ * How long to cache the radar config (in milliseconds).
115
+ * @default 60000 (1 minute)
116
+ */
117
+ cacheTtlMs?: number;
118
+ /**
119
+ * Cookie header to forward for authentication.
120
+ * If not provided, the verifier will try to read from the request headers.
121
+ */
122
+ cookieHeader?: string;
123
+ /**
124
+ * Whether to allow requests through if config fetch or verification fails.
125
+ * @default true
126
+ */
127
+ failOpen?: boolean;
128
+ }
129
+ /**
130
+ * Wrap a route handler function with bot protection.
131
+ *
132
+ * This is the recommended way to add bot protection in Next.js
133
+ * route handlers. It checks requests against protected paths and
134
+ * verifies them before forwarding to the auth handler.
135
+ *
136
+ * @param handler - The original route handler function (e.g., `handler.POST`)
137
+ * @param config - Bot protection configuration
138
+ * @returns A wrapped handler that verifies bots before forwarding
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * import { createRouteHandler } from "@banata-auth/nextjs";
143
+ * import { withBotProtection } from "@banata-auth/nextjs/bot-protection";
144
+ * import { checkBotId } from "botid/server";
145
+ *
146
+ * const handler = createRouteHandler({ convexSiteUrl: "..." });
147
+ *
148
+ * export const GET = handler.GET;
149
+ * export const POST = withBotProtection(handler.POST, {
150
+ * verify: async () => {
151
+ * const result = await checkBotId();
152
+ * return { isBot: result.isBot };
153
+ * },
154
+ * });
155
+ * ```
156
+ */
157
+ declare function withBotProtection<THandler extends (...args: any[]) => Promise<Response>>(handler: THandler, config: BotProtectionConfig): THandler;
158
+ /**
159
+ * Create a BotID verification function for use with `withBotProtection`
160
+ * or the `banataProtection` plugin.
161
+ *
162
+ * This is a convenience factory that wraps Vercel BotID's `checkBotId()`
163
+ * function in the format expected by the protection system.
164
+ *
165
+ * @param checkBotIdFn - The `checkBotId` function from `botid/server`
166
+ * @returns A verification function compatible with `BotCheckFn` / `BotVerifyFn`
167
+ *
168
+ * @example
169
+ * ```ts
170
+ * import { createBotIdVerifier } from "@banata-auth/nextjs/bot-protection";
171
+ * import { checkBotId } from "botid/server";
172
+ *
173
+ * const verify = createBotIdVerifier(checkBotId);
174
+ *
175
+ * export const POST = withBotProtection(handler.POST, { verify });
176
+ * ```
177
+ */
178
+ declare function createBotIdVerifier(checkBotIdFn: () => Promise<{
179
+ isBot: boolean;
180
+ }>): BotCheckFn;
181
+ /**
182
+ * Create a bot verifier that reads provider credentials from the
183
+ * Banata Auth config API (Radar configuration).
184
+ *
185
+ * This allows dashboard admins to configure bot protection credentials
186
+ * through the UI (Radar > Configuration > Bot Detection Provider),
187
+ * and the verifier will automatically pick up the latest credentials.
188
+ *
189
+ * The config is cached in-memory for performance (default: 1 minute TTL).
190
+ *
191
+ * @param options - Config-aware verifier options
192
+ * @returns A verification function that uses the configured bot provider
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * import { createConfigAwareVerifier, withBotProtection } from "@banata-auth/nextjs/bot-protection";
197
+ *
198
+ * const verify = createConfigAwareVerifier({
199
+ * configApiUrl: process.env.NEXT_PUBLIC_APP_URL + "/api/auth",
200
+ * });
201
+ *
202
+ * export const POST = withBotProtection(handler.POST, { verify });
203
+ * ```
204
+ */
205
+ declare function createConfigAwareVerifier(options: ConfigAwareVerifierOptions): BotCheckFn;
206
+
207
+ export { type BotCheckFn, type BotCheckResult, type BotProtectionConfig, type BotProviderCredentials, type ConfigAwareVerifierOptions, type RadarConfigResponse, createBotIdVerifier, createConfigAwareVerifier, withBotProtection };
@@ -0,0 +1,286 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // ../../node_modules/botid/dist/server/index.mjs
12
+ var server_exports = {};
13
+ __export(server_exports, {
14
+ checkBotId: () => z
15
+ });
16
+ async function m(e) {
17
+ let o = l();
18
+ if (o.headers || process.env.NODE_ENV !== "development") return { ...o.headers || {}, url: o.url || "" };
19
+ let t = await T();
20
+ if (t) return x(t);
21
+ if (e) {
22
+ let n = {};
23
+ for (let [i, r] of Object.entries(e)) typeof r == "string" ? n[i.toLowerCase()] = r : Array.isArray(r) && (n[i.toLowerCase()] = r.join(", "));
24
+ return n;
25
+ }
26
+ return {};
27
+ }
28
+ async function T() {
29
+ try {
30
+ let { headers: e } = await import('next/headers');
31
+ return e();
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ function x(e) {
37
+ let o = {};
38
+ return e.forEach((t, n) => {
39
+ o[n] = t;
40
+ }), o;
41
+ }
42
+ async function g() {
43
+ return C();
44
+ }
45
+ function C() {
46
+ let e = l().headers?.["x-vercel-oidc-token"] ?? process.env.VERCEL_OIDC_TOKEN;
47
+ if (!e) throw new Error("The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?");
48
+ return e;
49
+ }
50
+ var k, h, B, l, y, H, w, z;
51
+ var init_server = __esm({
52
+ "../../node_modules/botid/dist/server/index.mjs"() {
53
+ k = "https://api.vercel.com/bot-protection";
54
+ h = () => process.env.OVERRIDE_BOTID_SERVER_URL ? process.env.OVERRIDE_BOTID_SERVER_URL : k;
55
+ B = Symbol.for("@vercel/request-context");
56
+ l = () => globalThis[B]?.get?.() ?? {};
57
+ y = (e) => {
58
+ let o = e.cookie ?? e.Cookie ?? "";
59
+ return o ? o.split(";").map((t) => t.trim()).filter((t) => t.length > 0).map((t) => {
60
+ let [n, ...i] = t.split("="), r = i.join("=");
61
+ return { name: n.trim(), value: r.trim() };
62
+ }) : [];
63
+ };
64
+ H = (e) => {
65
+ let { authorization: o, ...t } = e, i = y(t).filter((a) => a.name.startsWith("KP_")), r = {};
66
+ for (let a of i) r[a.name] = a.value;
67
+ return Object.keys(r).length > 0 ? t.cookie = Object.entries(r).map(([a, f]) => `${a}=${f}`).join("; ") : t.cookie = void 0, t;
68
+ };
69
+ w = (e) => {
70
+ try {
71
+ return new URL(e);
72
+ } catch {
73
+ return null;
74
+ }
75
+ };
76
+ z = async ({ developmentOptions: e, advancedOptions: o } = {}) => {
77
+ if (o?.checkLevel !== void 0 && o.checkLevel !== "deepAnalysis" && o.checkLevel !== "basic") throw new Error(`Invalid checkLevel "${o.checkLevel}". Must be one of: deepAnalysis, basic`);
78
+ let t = await m(o?.headers), n = t.host || "unknown host", i = w(t.url || "")?.pathname || t["x-path"] || "<your protected endpoint>", r = t["x-method"] || "POST", a = t["x-is-human"], f = e?.isDevelopment ?? process.env.NODE_ENV !== "production";
79
+ if (a || (process.env.NODE_ENV !== "production" || process.env.VERCEL_ENV !== "production" ? console.error(`Possible misconfiguration of Vercel BotId.
80
+ Ensure that the client-side protection is properly configured for '${r} ${i}'.
81
+ Add the following item to your BotId client side protection:
82
+ {
83
+ path: '${i}',
84
+ method: '${r}',
85
+ }
86
+ More info at https://vercel.com/docs/botid/get-started#add-client-side-protection`) : console.warn(`Possible misconfiguration of Vercel BotId or malicious request to '${r} ${i}'. More info at https://vercel.com/docs/botid/get-started#add-client-side-protection`)), f && e?.bypass) return { isHuman: e.bypass === "HUMAN", isBot: e.bypass === "BAD-BOT", isVerifiedBot: e.bypass === "GOOD-BOT", bypassed: false };
87
+ if (f) return console.warn("[Dev Only] Without setting the developmentOptions.bypass value, the bot protection will return HUMAN."), { isHuman: true, isBot: false, isVerifiedBot: false, bypassed: true };
88
+ let R = h(), u = o?.vercelOidcToken ?? await g();
89
+ if (!u) throw new Error("VERCEL_OIDC_TOKEN is not set");
90
+ let d = l(), b = H(t), v = { url: d.url || "", method: r, headers: Object.entries(b), vercelOidcToken: u, forceCheckLevel: o?.checkLevel, extraAllowedHosts: o?.extraAllowedHosts }, E = `${R}/v1/is-bot?v=${3} `, s = await (await fetch(E, { method: "POST", body: JSON.stringify(v), headers: { "Content-Type": "application/json", "user-agent": `Vercel/BotId (${n})`, "x-vercel-oidc-token": u } })).json();
91
+ if (!d) throw new Error("Must be deployed on Vercel to access response headers");
92
+ if (!d.mutateResponseHeadersBeforeFlush) throw new Error("Must be deployed on Vercel to set response headers");
93
+ if (s.responseHeadersToSet?.addHeaders) for (let c of s.responseHeadersToSet.addHeaders) d.mutateResponseHeadersBeforeFlush((p) => {
94
+ p.append(c.key, c.value);
95
+ });
96
+ if (s.responseHeadersToSet?.addValuesToHeaders) for (let c of s.responseHeadersToSet.addValuesToHeaders) d.mutateResponseHeadersBeforeFlush((p) => {
97
+ p.append(c.key, c.value);
98
+ });
99
+ if (s.responseHeadersToSet?.replaceHeaders) for (let c of s.responseHeadersToSet.replaceHeaders) d.mutateResponseHeadersBeforeFlush((p) => {
100
+ p.set(c.key, c.value);
101
+ });
102
+ return { isHuman: !s.isBot, isBot: s.isBot, isVerifiedBot: s.isVerifiedBot, verifiedBotName: s.verifiedBotName, verifiedBotCategory: s.verifiedBotCategory, bypassed: s.bypassed, classificationReason: s.classificationReason, ...o?.returnResponseHeaders && { responseHeaders: s.responseHeadersToSet } };
103
+ };
104
+ }
105
+ });
106
+
107
+ // src/bot-protection.ts
108
+ var DEFAULT_PROTECTED_PATHS = [
109
+ "/api/auth/sign-in",
110
+ "/api/auth/sign-up",
111
+ "/api/auth/forget-password",
112
+ "/api/auth/reset-password"
113
+ ];
114
+ function withBotProtection(handler, config) {
115
+ const {
116
+ verify,
117
+ protectedPaths = DEFAULT_PROTECTED_PATHS,
118
+ failOpen = true,
119
+ blockedMessage = "Bot detected. Access denied."
120
+ } = config;
121
+ const wrapped = async (...args) => {
122
+ const request = args[0];
123
+ if (request?.url) {
124
+ const url = new URL(request.url);
125
+ const isProtected = protectedPaths.some((path) => url.pathname.startsWith(path));
126
+ if (isProtected) {
127
+ try {
128
+ const result = await verify(request);
129
+ if (result.isBot) {
130
+ return new Response(JSON.stringify({ error: blockedMessage }), {
131
+ status: 403,
132
+ headers: { "Content-Type": "application/json" }
133
+ });
134
+ }
135
+ } catch {
136
+ if (!failOpen) {
137
+ return new Response(
138
+ JSON.stringify({ error: "Bot verification unavailable. Please try again." }),
139
+ {
140
+ status: 503,
141
+ headers: { "Content-Type": "application/json" }
142
+ }
143
+ );
144
+ }
145
+ }
146
+ }
147
+ }
148
+ return handler(...args);
149
+ };
150
+ return wrapped;
151
+ }
152
+ function createBotIdVerifier(checkBotIdFn) {
153
+ return async (_request) => {
154
+ try {
155
+ const result = await checkBotIdFn();
156
+ return { isBot: result.isBot };
157
+ } catch {
158
+ return { isBot: false };
159
+ }
160
+ };
161
+ }
162
+ function createConfigAwareVerifier(options) {
163
+ const { configApiUrl, cacheTtlMs = 6e4, failOpen = true } = options;
164
+ let cachedConfig = null;
165
+ let cacheTimestamp = 0;
166
+ async function fetchRadarConfig(request) {
167
+ const now = Date.now();
168
+ if (cachedConfig && now - cacheTimestamp < cacheTtlMs) {
169
+ return cachedConfig;
170
+ }
171
+ try {
172
+ const cookieHeader = options.cookieHeader ?? request.headers.get("cookie") ?? "";
173
+ const response = await fetch(`${configApiUrl}/banata/config/radar/get`, {
174
+ method: "POST",
175
+ headers: {
176
+ "content-type": "application/json",
177
+ ...cookieHeader ? { cookie: cookieHeader } : {}
178
+ },
179
+ body: JSON.stringify({})
180
+ });
181
+ if (!response.ok) {
182
+ console.warn("[bot-protection] Failed to fetch radar config:", response.status);
183
+ return cachedConfig;
184
+ }
185
+ const data = await response.json();
186
+ cachedConfig = data;
187
+ cacheTimestamp = now;
188
+ return data;
189
+ } catch (error) {
190
+ console.warn(
191
+ "[bot-protection] Error fetching radar config:",
192
+ error instanceof Error ? error.message : error
193
+ );
194
+ return cachedConfig;
195
+ }
196
+ }
197
+ return async (request) => {
198
+ try {
199
+ const radarConfig = await fetchRadarConfig(request);
200
+ if (!radarConfig?.enabled || !radarConfig.botDetection) {
201
+ return { isBot: false };
202
+ }
203
+ const { botProvider, botProviderCredentials } = radarConfig;
204
+ if (!botProvider || !botProviderCredentials) {
205
+ return { isBot: false };
206
+ }
207
+ switch (botProvider) {
208
+ case "botid": {
209
+ try {
210
+ const { checkBotId } = await Promise.resolve().then(() => (init_server(), server_exports));
211
+ const result = await checkBotId();
212
+ return { isBot: result.isBot };
213
+ } catch {
214
+ return { isBot: false };
215
+ }
216
+ }
217
+ case "turnstile": {
218
+ const { secretKey } = botProviderCredentials;
219
+ if (!secretKey) return { isBot: false };
220
+ const formData = new URLSearchParams();
221
+ formData.append("secret", secretKey);
222
+ const turnstileToken = request.headers.get("cf-turnstile-response") ?? request.headers.get("x-turnstile-token");
223
+ if (!turnstileToken) {
224
+ return { isBot: false, reason: "No Turnstile token provided" };
225
+ }
226
+ formData.append("response", turnstileToken);
227
+ const verifyResponse = await fetch(
228
+ "https://challenges.cloudflare.com/turnstile/v0/siteverify",
229
+ {
230
+ method: "POST",
231
+ headers: { "content-type": "application/x-www-form-urlencoded" },
232
+ body: formData.toString()
233
+ }
234
+ );
235
+ const verifyResult = await verifyResponse.json();
236
+ return { isBot: !verifyResult.success };
237
+ }
238
+ case "recaptcha": {
239
+ const { secretKey: recaptchaSecret } = botProviderCredentials;
240
+ if (!recaptchaSecret) return { isBot: false };
241
+ const recaptchaToken = request.headers.get("x-recaptcha-token") ?? request.headers.get("g-recaptcha-response");
242
+ if (!recaptchaToken) {
243
+ return { isBot: false, reason: "No reCAPTCHA token provided" };
244
+ }
245
+ const recaptchaResponse = await fetch(
246
+ `https://www.google.com/recaptcha/api/siteverify?secret=${encodeURIComponent(recaptchaSecret)}&response=${encodeURIComponent(recaptchaToken)}`,
247
+ { method: "POST" }
248
+ );
249
+ const recaptchaResult = await recaptchaResponse.json();
250
+ const isBot = !recaptchaResult.success || (recaptchaResult.score ?? 1) < 0.5;
251
+ return { isBot };
252
+ }
253
+ case "hcaptcha": {
254
+ const { secretKey: hcaptchaSecret } = botProviderCredentials;
255
+ if (!hcaptchaSecret) return { isBot: false };
256
+ const hcaptchaToken = request.headers.get("x-hcaptcha-token") ?? request.headers.get("h-captcha-response");
257
+ if (!hcaptchaToken) {
258
+ return { isBot: false, reason: "No hCaptcha token provided" };
259
+ }
260
+ const hcaptchaResponse = await fetch("https://api.hcaptcha.com/siteverify", {
261
+ method: "POST",
262
+ headers: { "content-type": "application/x-www-form-urlencoded" },
263
+ body: `secret=${encodeURIComponent(hcaptchaSecret)}&response=${encodeURIComponent(hcaptchaToken)}`
264
+ });
265
+ const hcaptchaResult = await hcaptchaResponse.json();
266
+ return { isBot: !hcaptchaResult.success };
267
+ }
268
+ default:
269
+ return { isBot: false };
270
+ }
271
+ } catch (error) {
272
+ if (failOpen) {
273
+ console.warn(
274
+ "[bot-protection] Verification failed, allowing request (failOpen=true):",
275
+ error instanceof Error ? error.message : error
276
+ );
277
+ return { isBot: false };
278
+ }
279
+ return { isBot: true, reason: "Verification service unavailable" };
280
+ }
281
+ };
282
+ }
283
+
284
+ export { createBotIdVerifier, createConfigAwareVerifier, withBotProtection };
285
+ //# sourceMappingURL=bot-protection.js.map
286
+ //# sourceMappingURL=bot-protection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../node_modules/botid/dist/server/index.mjs","../src/bot-protection.ts"],"names":[],"mappings":"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","file":"bot-protection.js","sourcesContent":["var k=\"https://api.vercel.com/bot-protection\",h=()=>process.env.OVERRIDE_BOTID_SERVER_URL?process.env.OVERRIDE_BOTID_SERVER_URL:k;var B=Symbol.for(\"@vercel/request-context\"),l=()=>globalThis[B]?.get?.()??{};async function m(e){let o=l();if(o.headers||process.env.NODE_ENV!==\"development\")return{...o.headers||{},url:o.url||\"\"};let t=await T();if(t)return x(t);if(e){let n={};for(let[i,r]of Object.entries(e))typeof r==\"string\"?n[i.toLowerCase()]=r:Array.isArray(r)&&(n[i.toLowerCase()]=r.join(\", \"));return n}return{}}async function T(){try{let{headers:e}=await import(\"next/headers\");return e()}catch{return null}}function x(e){let o={};return e.forEach((t,n)=>{o[n]=t}),o}async function g(){return C()}function C(){let e=l().headers?.[\"x-vercel-oidc-token\"]??process.env.VERCEL_OIDC_TOKEN;if(!e)throw new Error(\"The 'x-vercel-oidc-token' header is missing from the request. Do you have the OIDC option enabled in the Vercel project settings?\");return e}var y=e=>{let o=e.cookie??e.Cookie??\"\";return o?o.split(\";\").map(t=>t.trim()).filter(t=>t.length>0).map(t=>{let[n,...i]=t.split(\"=\"),r=i.join(\"=\");return{name:n.trim(),value:r.trim()}}):[]};var H=e=>{let{authorization:o,...t}=e,i=y(t).filter(a=>a.name.startsWith(\"KP_\")),r={};for(let a of i)r[a.name]=a.value;return Object.keys(r).length>0?t.cookie=Object.entries(r).map(([a,f])=>`${a}=${f}`).join(\"; \"):t.cookie=void 0,t};var w=e=>{try{return new URL(e)}catch{return null}},z=async({developmentOptions:e,advancedOptions:o}={})=>{if(o?.checkLevel!==void 0&&o.checkLevel!==\"deepAnalysis\"&&o.checkLevel!==\"basic\")throw new Error(`Invalid checkLevel \"${o.checkLevel}\". Must be one of: deepAnalysis, basic`);let t=await m(o?.headers),n=t.host||\"unknown host\",i=w(t.url||\"\")?.pathname||t[\"x-path\"]||\"<your protected endpoint>\",r=t[\"x-method\"]||\"POST\",a=t[\"x-is-human\"],f=e?.isDevelopment??process.env.NODE_ENV!==\"production\";if(a||(process.env.NODE_ENV!==\"production\"||process.env.VERCEL_ENV!==\"production\"?console.error(`Possible misconfiguration of Vercel BotId. \nEnsure that the client-side protection is properly configured for '${r} ${i}'.\nAdd the following item to your BotId client side protection:\n{\n path: '${i}',\n method: '${r}',\n}\nMore info at https://vercel.com/docs/botid/get-started#add-client-side-protection`):console.warn(`Possible misconfiguration of Vercel BotId or malicious request to '${r} ${i}'. More info at https://vercel.com/docs/botid/get-started#add-client-side-protection`)),f&&e?.bypass)return{isHuman:e.bypass===\"HUMAN\",isBot:e.bypass===\"BAD-BOT\",isVerifiedBot:e.bypass===\"GOOD-BOT\",bypassed:!1};if(f)return console.warn(\"[Dev Only] Without setting the developmentOptions.bypass value, the bot protection will return HUMAN.\"),{isHuman:!0,isBot:!1,isVerifiedBot:!1,bypassed:!0};let R=h(),u=o?.vercelOidcToken??await g();if(!u)throw new Error(\"VERCEL_OIDC_TOKEN is not set\");let d=l(),b=H(t),v={url:d.url||\"\",method:r,headers:Object.entries(b),vercelOidcToken:u,forceCheckLevel:o?.checkLevel,extraAllowedHosts:o?.extraAllowedHosts},E=`${R}/v1/is-bot?v=${3} `,s=await(await fetch(E,{method:\"POST\",body:JSON.stringify(v),headers:{\"Content-Type\":\"application/json\",\"user-agent\":`Vercel/BotId (${n})`,\"x-vercel-oidc-token\":u}})).json();if(!d)throw new Error(\"Must be deployed on Vercel to access response headers\");if(!d.mutateResponseHeadersBeforeFlush)throw new Error(\"Must be deployed on Vercel to set response headers\");if(s.responseHeadersToSet?.addHeaders)for(let c of s.responseHeadersToSet.addHeaders)d.mutateResponseHeadersBeforeFlush(p=>{p.append(c.key,c.value)});if(s.responseHeadersToSet?.addValuesToHeaders)for(let c of s.responseHeadersToSet.addValuesToHeaders)d.mutateResponseHeadersBeforeFlush(p=>{p.append(c.key,c.value)});if(s.responseHeadersToSet?.replaceHeaders)for(let c of s.responseHeadersToSet.replaceHeaders)d.mutateResponseHeadersBeforeFlush(p=>{p.set(c.key,c.value)});return{isHuman:!s.isBot,isBot:s.isBot,isVerifiedBot:s.isVerifiedBot,verifiedBotName:s.verifiedBotName,verifiedBotCategory:s.verifiedBotCategory,bypassed:s.bypassed,classificationReason:s.classificationReason,...o?.returnResponseHeaders&&{responseHeaders:s.responseHeadersToSet}}};export{z as checkBotId};\n","/**\n * Bot protection utilities for Next.js applications using Banata Auth.\n *\n * Provides helpers to integrate bot detection services (Vercel BotID,\n * Cloudflare Turnstile, hCaptcha, reCAPTCHA) with the Banata Auth\n * route handler.\n *\n * These utilities wrap the route handler's POST method to automatically\n * verify requests against protected auth paths before forwarding to\n * the auth backend.\n *\n * Credentials can be supplied directly or fetched from the Banata Auth\n * config API (configured via the Radar page in the dashboard).\n *\n * @example\n * ```ts\n * // app/api/auth/[...all]/route.ts\n * import { createRouteHandler } from \"@banata-auth/nextjs\";\n * import { withBotProtection, createBotIdVerifier } from \"@banata-auth/nextjs/bot-protection\";\n *\n * const handler = createRouteHandler({\n * convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,\n * });\n *\n * export const GET = handler.GET;\n * export const POST = withBotProtection(handler.POST, {\n * protectedPaths: [\"/api/auth/sign-in\", \"/api/auth/sign-up\"],\n * verify: async () => {\n * const { checkBotId } = await import(\"botid/server\");\n * const result = await checkBotId();\n * return { isBot: result.isBot };\n * },\n * });\n * ```\n *\n * @example\n * ```ts\n * // Using credentials from the Banata Auth config API\n * import { createConfigAwareVerifier } from \"@banata-auth/nextjs/bot-protection\";\n *\n * const verify = createConfigAwareVerifier({\n * configApiUrl: process.env.NEXT_PUBLIC_APP_URL + \"/api/auth\",\n * });\n *\n * export const POST = withBotProtection(handler.POST, { verify });\n * ```\n */\n\n// ─── Types ──────────────────────────────────────────────────────────\n\n/**\n * Result of a bot verification check.\n */\nexport interface BotCheckResult {\n\t/** Whether the request is from a bot. */\n\tisBot: boolean;\n\t/** Optional reason string for logging. */\n\treason?: string;\n}\n\n/**\n * Function that verifies whether a request is from a bot.\n * Implement with your provider of choice.\n */\nexport type BotCheckFn = (request: Request) => Promise<BotCheckResult>;\n\n/**\n * Configuration for the route-handler-level bot protection wrapper.\n */\nexport interface BotProtectionConfig {\n\t/**\n\t * The verification function.\n\t * Called for every request matching a protected path.\n\t */\n\tverify: BotCheckFn;\n\n\t/**\n\t * Path prefixes to protect. Requests to other paths pass through.\n\t * @default [\"/api/auth/sign-in\", \"/api/auth/sign-up\", \"/api/auth/forget-password\", \"/api/auth/reset-password\"]\n\t */\n\tprotectedPaths?: string[];\n\n\t/**\n\t * Whether to allow requests through if verification fails (service unavailable).\n\t * @default true\n\t */\n\tfailOpen?: boolean;\n\n\t/**\n\t * Error message returned when a bot is detected.\n\t * @default \"Bot detected. Access denied.\"\n\t */\n\tblockedMessage?: string;\n}\n\n/**\n * Bot provider credentials as stored in the Radar configuration.\n */\nexport interface BotProviderCredentials {\n\tapiKey?: string;\n\tsiteKey?: string;\n\tsecretKey?: string;\n}\n\n/**\n * Radar configuration shape returned by the config API.\n */\nexport interface RadarConfigResponse {\n\tenabled: boolean;\n\tbotDetection: boolean;\n\tbotProvider: string | null;\n\tbotProviderCredentials: BotProviderCredentials;\n}\n\n/**\n * Options for creating a config-aware bot verifier.\n */\nexport interface ConfigAwareVerifierOptions {\n\t/**\n\t * Base URL of the Banata Auth API (e.g., \"http://localhost:3000/api/auth\").\n\t * The radar config endpoint will be called at `${configApiUrl}/banata/config/radar/get`.\n\t */\n\tconfigApiUrl: string;\n\n\t/**\n\t * How long to cache the radar config (in milliseconds).\n\t * @default 60000 (1 minute)\n\t */\n\tcacheTtlMs?: number;\n\n\t/**\n\t * Cookie header to forward for authentication.\n\t * If not provided, the verifier will try to read from the request headers.\n\t */\n\tcookieHeader?: string;\n\n\t/**\n\t * Whether to allow requests through if config fetch or verification fails.\n\t * @default true\n\t */\n\tfailOpen?: boolean;\n}\n\n// ─── Default Protected Paths ────────────────────────────────────────\n\nconst DEFAULT_PROTECTED_PATHS = [\n\t\"/api/auth/sign-in\",\n\t\"/api/auth/sign-up\",\n\t\"/api/auth/forget-password\",\n\t\"/api/auth/reset-password\",\n];\n\n// ─── withBotProtection ──────────────────────────────────────────────\n\n/**\n * Wrap a route handler function with bot protection.\n *\n * This is the recommended way to add bot protection in Next.js\n * route handlers. It checks requests against protected paths and\n * verifies them before forwarding to the auth handler.\n *\n * @param handler - The original route handler function (e.g., `handler.POST`)\n * @param config - Bot protection configuration\n * @returns A wrapped handler that verifies bots before forwarding\n *\n * @example\n * ```ts\n * import { createRouteHandler } from \"@banata-auth/nextjs\";\n * import { withBotProtection } from \"@banata-auth/nextjs/bot-protection\";\n * import { checkBotId } from \"botid/server\";\n *\n * const handler = createRouteHandler({ convexSiteUrl: \"...\" });\n *\n * export const GET = handler.GET;\n * export const POST = withBotProtection(handler.POST, {\n * verify: async () => {\n * const result = await checkBotId();\n * return { isBot: result.isBot };\n * },\n * });\n * ```\n */\nexport function withBotProtection<THandler extends (...args: any[]) => Promise<Response>>(\n\thandler: THandler,\n\tconfig: BotProtectionConfig,\n): THandler {\n\tconst {\n\t\tverify,\n\t\tprotectedPaths = DEFAULT_PROTECTED_PATHS,\n\t\tfailOpen = true,\n\t\tblockedMessage = \"Bot detected. Access denied.\",\n\t} = config;\n\n\tconst wrapped = async (...args: Parameters<THandler>): Promise<Response> => {\n\t\t// Extract the request from the first argument\n\t\tconst request = args[0] as Request;\n\n\t\tif (request?.url) {\n\t\t\tconst url = new URL(request.url);\n\t\t\tconst isProtected = protectedPaths.some((path) => url.pathname.startsWith(path));\n\n\t\t\tif (isProtected) {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await verify(request);\n\t\t\t\t\tif (result.isBot) {\n\t\t\t\t\t\treturn new Response(JSON.stringify({ error: blockedMessage }), {\n\t\t\t\t\t\t\tstatus: 403,\n\t\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\tif (!failOpen) {\n\t\t\t\t\t\treturn new Response(\n\t\t\t\t\t\t\tJSON.stringify({ error: \"Bot verification unavailable. Please try again.\" }),\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstatus: 503,\n\t\t\t\t\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\t// fail open — allow through\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn handler(...args);\n\t};\n\n\treturn wrapped as THandler;\n}\n\n/**\n * Create a BotID verification function for use with `withBotProtection`\n * or the `banataProtection` plugin.\n *\n * This is a convenience factory that wraps Vercel BotID's `checkBotId()`\n * function in the format expected by the protection system.\n *\n * @param checkBotIdFn - The `checkBotId` function from `botid/server`\n * @returns A verification function compatible with `BotCheckFn` / `BotVerifyFn`\n *\n * @example\n * ```ts\n * import { createBotIdVerifier } from \"@banata-auth/nextjs/bot-protection\";\n * import { checkBotId } from \"botid/server\";\n *\n * const verify = createBotIdVerifier(checkBotId);\n *\n * export const POST = withBotProtection(handler.POST, { verify });\n * ```\n */\nexport function createBotIdVerifier(checkBotIdFn: () => Promise<{ isBot: boolean }>): BotCheckFn {\n\treturn async (_request: Request): Promise<BotCheckResult> => {\n\t\ttry {\n\t\t\tconst result = await checkBotIdFn();\n\t\t\treturn { isBot: result.isBot };\n\t\t} catch {\n\t\t\t// BotID not configured (not on Vercel) — allow through\n\t\t\treturn { isBot: false };\n\t\t}\n\t};\n}\n\n// ─── Config-Aware Verifier ──────────────────────────────────────────\n\n/**\n * Create a bot verifier that reads provider credentials from the\n * Banata Auth config API (Radar configuration).\n *\n * This allows dashboard admins to configure bot protection credentials\n * through the UI (Radar > Configuration > Bot Detection Provider),\n * and the verifier will automatically pick up the latest credentials.\n *\n * The config is cached in-memory for performance (default: 1 minute TTL).\n *\n * @param options - Config-aware verifier options\n * @returns A verification function that uses the configured bot provider\n *\n * @example\n * ```ts\n * import { createConfigAwareVerifier, withBotProtection } from \"@banata-auth/nextjs/bot-protection\";\n *\n * const verify = createConfigAwareVerifier({\n * configApiUrl: process.env.NEXT_PUBLIC_APP_URL + \"/api/auth\",\n * });\n *\n * export const POST = withBotProtection(handler.POST, { verify });\n * ```\n */\nexport function createConfigAwareVerifier(options: ConfigAwareVerifierOptions): BotCheckFn {\n\tconst { configApiUrl, cacheTtlMs = 60_000, failOpen = true } = options;\n\n\tlet cachedConfig: RadarConfigResponse | null = null;\n\tlet cacheTimestamp = 0;\n\n\tasync function fetchRadarConfig(request: Request): Promise<RadarConfigResponse | null> {\n\t\tconst now = Date.now();\n\t\tif (cachedConfig && now - cacheTimestamp < cacheTtlMs) {\n\t\t\treturn cachedConfig;\n\t\t}\n\n\t\ttry {\n\t\t\t// Forward cookies from the incoming request for auth\n\t\t\tconst cookieHeader = options.cookieHeader ?? request.headers.get(\"cookie\") ?? \"\";\n\n\t\t\tconst response = await fetch(`${configApiUrl}/banata/config/radar/get`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\t...(cookieHeader ? { cookie: cookieHeader } : {}),\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({}),\n\t\t\t});\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconsole.warn(\"[bot-protection] Failed to fetch radar config:\", response.status);\n\t\t\t\treturn cachedConfig; // Return stale cache if available\n\t\t\t}\n\n\t\t\tconst data = (await response.json()) as RadarConfigResponse;\n\t\t\tcachedConfig = data;\n\t\t\tcacheTimestamp = now;\n\t\t\treturn data;\n\t\t} catch (error) {\n\t\t\tconsole.warn(\n\t\t\t\t\"[bot-protection] Error fetching radar config:\",\n\t\t\t\terror instanceof Error ? error.message : error,\n\t\t\t);\n\t\t\treturn cachedConfig; // Return stale cache if available\n\t\t}\n\t}\n\n\treturn async (request: Request): Promise<BotCheckResult> => {\n\t\ttry {\n\t\t\tconst radarConfig = await fetchRadarConfig(request);\n\n\t\t\t// If radar is not enabled or bot detection is off, allow through\n\t\t\tif (!radarConfig?.enabled || !radarConfig.botDetection) {\n\t\t\t\treturn { isBot: false };\n\t\t\t}\n\n\t\t\tconst { botProvider, botProviderCredentials } = radarConfig;\n\n\t\t\tif (!botProvider || !botProviderCredentials) {\n\t\t\t\treturn { isBot: false };\n\t\t\t}\n\n\t\t\t// Dispatch to the appropriate provider\n\t\t\tswitch (botProvider) {\n\t\t\t\tcase \"botid\": {\n\t\t\t\t\t// Vercel BotID — uses checkBotId() which reads from the request context\n\t\t\t\t\t// The API key is configured at the Vercel project level, not per-request\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst { checkBotId } = await import(\"botid/server\");\n\t\t\t\t\t\tconst result = await checkBotId();\n\t\t\t\t\t\treturn { isBot: result.isBot };\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// BotID not available (not on Vercel)\n\t\t\t\t\t\treturn { isBot: false };\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tcase \"turnstile\": {\n\t\t\t\t\t// Cloudflare Turnstile verification\n\t\t\t\t\tconst { secretKey } = botProviderCredentials;\n\t\t\t\t\tif (!secretKey) return { isBot: false };\n\n\t\t\t\t\tconst formData = new URLSearchParams();\n\t\t\t\t\tformData.append(\"secret\", secretKey);\n\n\t\t\t\t\t// The Turnstile token should be in the request body or header\n\t\t\t\t\tconst turnstileToken =\n\t\t\t\t\t\trequest.headers.get(\"cf-turnstile-response\") ??\n\t\t\t\t\t\trequest.headers.get(\"x-turnstile-token\");\n\t\t\t\t\tif (!turnstileToken) {\n\t\t\t\t\t\treturn { isBot: false, reason: \"No Turnstile token provided\" };\n\t\t\t\t\t}\n\t\t\t\t\tformData.append(\"response\", turnstileToken);\n\n\t\t\t\t\tconst verifyResponse = await fetch(\n\t\t\t\t\t\t\"https://challenges.cloudflare.com/turnstile/v0/siteverify\",\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\t\theaders: { \"content-type\": \"application/x-www-form-urlencoded\" },\n\t\t\t\t\t\t\tbody: formData.toString(),\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\n\t\t\t\t\tconst verifyResult = (await verifyResponse.json()) as { success: boolean };\n\t\t\t\t\treturn { isBot: !verifyResult.success };\n\t\t\t\t}\n\n\t\t\t\tcase \"recaptcha\": {\n\t\t\t\t\t// Google reCAPTCHA v3 verification\n\t\t\t\t\tconst { secretKey: recaptchaSecret } = botProviderCredentials;\n\t\t\t\t\tif (!recaptchaSecret) return { isBot: false };\n\n\t\t\t\t\tconst recaptchaToken =\n\t\t\t\t\t\trequest.headers.get(\"x-recaptcha-token\") ?? request.headers.get(\"g-recaptcha-response\");\n\t\t\t\t\tif (!recaptchaToken) {\n\t\t\t\t\t\treturn { isBot: false, reason: \"No reCAPTCHA token provided\" };\n\t\t\t\t\t}\n\n\t\t\t\t\tconst recaptchaResponse = await fetch(\n\t\t\t\t\t\t`https://www.google.com/recaptcha/api/siteverify?secret=${encodeURIComponent(recaptchaSecret)}&response=${encodeURIComponent(recaptchaToken)}`,\n\t\t\t\t\t\t{ method: \"POST\" },\n\t\t\t\t\t);\n\n\t\t\t\t\tconst recaptchaResult = (await recaptchaResponse.json()) as {\n\t\t\t\t\t\tsuccess: boolean;\n\t\t\t\t\t\tscore?: number;\n\t\t\t\t\t};\n\t\t\t\t\t// Score < 0.5 is likely a bot (reCAPTCHA v3)\n\t\t\t\t\tconst isBot = !recaptchaResult.success || (recaptchaResult.score ?? 1) < 0.5;\n\t\t\t\t\treturn { isBot };\n\t\t\t\t}\n\n\t\t\t\tcase \"hcaptcha\": {\n\t\t\t\t\t// hCaptcha verification\n\t\t\t\t\tconst { secretKey: hcaptchaSecret } = botProviderCredentials;\n\t\t\t\t\tif (!hcaptchaSecret) return { isBot: false };\n\n\t\t\t\t\tconst hcaptchaToken =\n\t\t\t\t\t\trequest.headers.get(\"x-hcaptcha-token\") ?? request.headers.get(\"h-captcha-response\");\n\t\t\t\t\tif (!hcaptchaToken) {\n\t\t\t\t\t\treturn { isBot: false, reason: \"No hCaptcha token provided\" };\n\t\t\t\t\t}\n\n\t\t\t\t\tconst hcaptchaResponse = await fetch(\"https://api.hcaptcha.com/siteverify\", {\n\t\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\t\theaders: { \"content-type\": \"application/x-www-form-urlencoded\" },\n\t\t\t\t\t\tbody: `secret=${encodeURIComponent(hcaptchaSecret)}&response=${encodeURIComponent(hcaptchaToken)}`,\n\t\t\t\t\t});\n\n\t\t\t\t\tconst hcaptchaResult = (await hcaptchaResponse.json()) as { success: boolean };\n\t\t\t\t\treturn { isBot: !hcaptchaResult.success };\n\t\t\t\t}\n\n\t\t\t\tdefault:\n\t\t\t\t\treturn { isBot: false };\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tif (failOpen) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"[bot-protection] Verification failed, allowing request (failOpen=true):\",\n\t\t\t\t\terror instanceof Error ? error.message : error,\n\t\t\t\t);\n\t\t\t\treturn { isBot: false };\n\t\t\t}\n\t\t\treturn { isBot: true, reason: \"Verification service unavailable\" };\n\t\t}\n\t};\n}\n"]}
@@ -0,0 +1 @@
1
+ export { usePreloadedAuthQuery } from '@convex-dev/better-auth/nextjs/client';
package/dist/client.js ADDED
@@ -0,0 +1,3 @@
1
+ "use client";export { usePreloadedAuthQuery } from '@convex-dev/better-auth/nextjs/client';
2
+ //# sourceMappingURL=client.js.map
3
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"client.js","sourcesContent":[]}
@@ -0,0 +1,16 @@
1
+ export { BanataAuthMiddlewareOptions, banataAuthMiddleware, banataAuthProxy } from './middleware.js';
2
+ export { getSessionCookie } from 'better-auth/cookies';
3
+ import 'next/server';
4
+
5
+ declare function createRouteHandler(options: {
6
+ /** The Convex .site URL where HTTP actions are hosted. */
7
+ convexSiteUrl: string;
8
+ }): {
9
+ GET: (request: Request) => Promise<Response>;
10
+ POST: (request: Request) => Promise<Response>;
11
+ PUT: (request: Request) => Promise<Response>;
12
+ PATCH: (request: Request) => Promise<Response>;
13
+ DELETE: (request: Request) => Promise<Response>;
14
+ };
15
+
16
+ export { createRouteHandler };
package/dist/index.js ADDED
@@ -0,0 +1,122 @@
1
+ import { NextResponse } from 'next/server';
2
+ export { getSessionCookie } from 'better-auth/cookies';
3
+
4
+ // src/middleware.ts
5
+ var AUTH_COOKIE_NAMES = [
6
+ "better-auth.session_token",
7
+ "__Secure-better-auth.session_token",
8
+ "better-auth-session_token",
9
+ "__Secure-better-auth-session_token",
10
+ "convex_jwt"
11
+ ];
12
+ function hasSessionCookie(request) {
13
+ return AUTH_COOKIE_NAMES.some((name) => {
14
+ const cookie = request.cookies.get(name);
15
+ return cookie !== void 0 && cookie.value !== "";
16
+ });
17
+ }
18
+ function createAuthProxy(options = {}) {
19
+ const { publicRoutes = [], ignoredRoutes = [], signInUrl = "/sign-in" } = options;
20
+ const compiledPublicRoutes = compileRoutes(publicRoutes);
21
+ const compiledIgnoredRoutes = compileRoutes(ignoredRoutes);
22
+ return async function proxy(request) {
23
+ const { pathname } = request.nextUrl;
24
+ const requestHeaders = new Headers(request.headers);
25
+ requestHeaders.set("x-pathname", pathname);
26
+ if (isMatchingRoute(pathname, compiledIgnoredRoutes)) {
27
+ return NextResponse.next({ request: { headers: requestHeaders } });
28
+ }
29
+ if (isMatchingRoute(pathname, compiledPublicRoutes)) {
30
+ return NextResponse.next({ request: { headers: requestHeaders } });
31
+ }
32
+ if (!hasSessionCookie(request)) {
33
+ const signInURL = new URL(signInUrl, request.url);
34
+ signInURL.searchParams.set("redirect_url", pathname);
35
+ return NextResponse.redirect(signInURL);
36
+ }
37
+ return NextResponse.next({ request: { headers: requestHeaders } });
38
+ };
39
+ }
40
+ var banataAuthProxy = createAuthProxy;
41
+ var banataAuthMiddleware = createAuthProxy;
42
+ function compileRoutes(routes) {
43
+ return routes.map((route) => {
44
+ try {
45
+ return new RegExp(`^${route}$`);
46
+ } catch {
47
+ return route;
48
+ }
49
+ });
50
+ }
51
+ function isMatchingRoute(pathname, compiledRoutes) {
52
+ return compiledRoutes.some((route) => {
53
+ if (typeof route === "string") {
54
+ return pathname === route;
55
+ }
56
+ return route.test(pathname);
57
+ });
58
+ }
59
+
60
+ // src/route-handler.ts
61
+ var FORWARDED_HEADERS = /* @__PURE__ */ new Set([
62
+ "accept",
63
+ "accept-language",
64
+ "authorization",
65
+ "content-type",
66
+ "content-length",
67
+ "cookie",
68
+ "origin",
69
+ "referer",
70
+ "user-agent",
71
+ "x-requested-with"
72
+ ]);
73
+ function createRouteHandler(options) {
74
+ const { convexSiteUrl } = options;
75
+ const siteUrl = convexSiteUrl.replace(/\/$/, "");
76
+ async function handler(request) {
77
+ const requestUrl = new URL(request.url);
78
+ const targetUrl = `${siteUrl}${requestUrl.pathname}${requestUrl.search}`;
79
+ const forwardedHeaders = new Headers();
80
+ for (const [key, value] of request.headers) {
81
+ if (FORWARDED_HEADERS.has(key.toLowerCase())) {
82
+ forwardedHeaders.set(key, value);
83
+ }
84
+ }
85
+ const newRequest = new Request(targetUrl, {
86
+ method: request.method,
87
+ headers: forwardedHeaders,
88
+ body: request.body,
89
+ // @ts-expect-error - duplex is needed for streaming bodies
90
+ duplex: "half"
91
+ });
92
+ newRequest.headers.set("host", new URL(siteUrl).host);
93
+ try {
94
+ const response = await fetch(newRequest, {
95
+ method: request.method,
96
+ redirect: "manual"
97
+ });
98
+ return new Response(response.body, {
99
+ status: response.status,
100
+ statusText: response.statusText,
101
+ headers: response.headers
102
+ });
103
+ } catch (error) {
104
+ console.error("[banata-auth] Proxy error:", error);
105
+ return new Response(JSON.stringify({ error: "Auth service unavailable" }), {
106
+ status: 502,
107
+ headers: { "Content-Type": "application/json" }
108
+ });
109
+ }
110
+ }
111
+ return {
112
+ GET: handler,
113
+ POST: handler,
114
+ PUT: handler,
115
+ PATCH: handler,
116
+ DELETE: handler
117
+ };
118
+ }
119
+
120
+ export { banataAuthMiddleware, banataAuthProxy, createRouteHandler };
121
+ //# sourceMappingURL=index.js.map
122
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts","../src/route-handler.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { type NextRequest, NextResponse } from \"next/server\";\n\n/**\n * Cookie names that indicate an active auth session.\n *\n * Better Auth uses `better-auth.session_token` (or `__Secure-better-auth.session_token`\n * in production). The Convex Better Auth integration also sets `convex_jwt`.\n * We check both to cover all deployment scenarios.\n *\n * NOTE: This only checks for cookie *presence*, not validity. The actual\n * token is validated server-side when the API routes are hit.\n */\nconst AUTH_COOKIE_NAMES = [\n\t\"better-auth.session_token\",\n\t\"__Secure-better-auth.session_token\",\n\t\"better-auth-session_token\",\n\t\"__Secure-better-auth-session_token\",\n\t\"convex_jwt\",\n];\n\n/**\n * Check whether the request carries any known auth session cookie.\n * Uses the standard `NextRequest.cookies` API rather than Better Auth's\n * `getSessionCookie()`, which requires a `Headers` object incompatible\n * with some mocking/testing scenarios.\n */\nfunction hasSessionCookie(request: NextRequest): boolean {\n\treturn AUTH_COOKIE_NAMES.some((name) => {\n\t\tconst cookie = request.cookies.get(name);\n\t\treturn cookie !== undefined && cookie.value !== \"\";\n\t});\n}\n\nexport interface BanataAuthMiddlewareOptions {\n\t/**\n\t * Routes that don't require authentication.\n\t * Supports exact strings and regex patterns.\n\t */\n\tpublicRoutes?: string[];\n\n\t/**\n\t * Routes that the middleware should ignore entirely.\n\t */\n\tignoredRoutes?: string[];\n\n\t/**\n\t * URL to redirect to when unauthenticated.\n\t * @default \"/sign-in\"\n\t */\n\tsignInUrl?: string;\n\n\t/**\n\t * URL to redirect to after successful sign-in.\n\t * @default \"/dashboard\"\n\t */\n\tafterSignInUrl?: string;\n}\n\n/**\n * Create a Next.js proxy/middleware that protects routes based on auth state.\n *\n * **Important:** This middleware checks for the *presence* of an auth session\n * cookie, not its validity. The actual token is validated server-side when\n * API routes are hit. This is the standard pattern for Next.js Edge\n * middleware, which runs before the request reaches the server and cannot\n * perform full JWT validation without a network round-trip.\n *\n * In Next.js 16+, the file convention is `proxy.ts` with a named `proxy` export.\n * For Next.js 15 and earlier, use `middleware.ts` with a default export.\n *\n * @example\n * ```ts\n * // proxy.ts (Next.js 16+)\n * import { banataAuthProxy } from \"@banata-auth/nextjs\";\n *\n * export const proxy = banataAuthProxy({\n * publicRoutes: [\"/\", \"/pricing\", \"/blog(.*)\"],\n * signInUrl: \"/sign-in\",\n * });\n *\n * export const config = {\n * matcher: [\"/((?!.*\\\\..*|_next).*)\", \"/\", \"/(api|trpc)(.*)\"],\n * };\n * ```\n *\n * @example\n * ```ts\n * // middleware.ts (Next.js 15 and earlier)\n * import { banataAuthMiddleware } from \"@banata-auth/nextjs\";\n *\n * export default banataAuthMiddleware({\n * publicRoutes: [\"/\", \"/pricing\", \"/blog(.*)\"],\n * signInUrl: \"/sign-in\",\n * });\n *\n * export const config = {\n * matcher: [\"/((?!.*\\\\..*|_next).*)\", \"/\", \"/(api|trpc)(.*)\"],\n * };\n * ```\n */\nfunction createAuthProxy(options: BanataAuthMiddlewareOptions = {}) {\n\tconst { publicRoutes = [], ignoredRoutes = [], signInUrl = \"/sign-in\" } = options;\n\n\t// Pre-compile route patterns at config time (once) to avoid\n\t// re-creating RegExp objects on every request and to catch\n\t// invalid patterns early.\n\tconst compiledPublicRoutes = compileRoutes(publicRoutes);\n\tconst compiledIgnoredRoutes = compileRoutes(ignoredRoutes);\n\n\treturn async function proxy(request: NextRequest) {\n\t\tconst { pathname } = request.nextUrl;\n\n\t\t// Forward pathname as a request header so server components\n\t\t// can read it via headers(). This is the official Next.js pattern\n\t\t// for passing data from proxy/middleware to server components.\n\t\tconst requestHeaders = new Headers(request.headers);\n\t\trequestHeaders.set(\"x-pathname\", pathname);\n\n\t\t// Check ignored routes\n\t\tif (isMatchingRoute(pathname, compiledIgnoredRoutes)) {\n\t\t\treturn NextResponse.next({ request: { headers: requestHeaders } });\n\t\t}\n\n\t\t// Check public routes\n\t\tif (isMatchingRoute(pathname, compiledPublicRoutes)) {\n\t\t\treturn NextResponse.next({ request: { headers: requestHeaders } });\n\t\t}\n\n\t\t// Check for auth session cookie presence\n\t\tif (!hasSessionCookie(request)) {\n\t\t\tconst signInURL = new URL(signInUrl, request.url);\n\t\t\tsignInURL.searchParams.set(\"redirect_url\", pathname);\n\t\t\treturn NextResponse.redirect(signInURL);\n\t\t}\n\n\t\t// Token exists - allow the request through.\n\t\treturn NextResponse.next({ request: { headers: requestHeaders } });\n\t};\n}\n\n/**\n * Create a Next.js proxy function for `proxy.ts` (Next.js 16+).\n * The returned function is named `proxy` for compatibility with the new convention.\n */\nexport const banataAuthProxy = createAuthProxy;\n\n/**\n * Backward-compatible alias for Next.js 15 and earlier `middleware.ts`.\n * @deprecated Use `banataAuthProxy` with `proxy.ts` for Next.js 16+\n */\nexport const banataAuthMiddleware = createAuthProxy;\n\n/** A pre-compiled route matcher — either a RegExp or an exact string. */\ntype CompiledRoute = RegExp | string;\n\n/**\n * Pre-compile route patterns into RegExp objects (anchored with ^ and $).\n * Invalid patterns fall back to exact string matching.\n * This is called once at config time, not per-request.\n */\nfunction compileRoutes(routes: string[]): CompiledRoute[] {\n\treturn routes.map((route) => {\n\t\ttry {\n\t\t\treturn new RegExp(`^${route}$`);\n\t\t} catch {\n\t\t\t// If the pattern is invalid regex, fall back to exact match\n\t\t\treturn route;\n\t\t}\n\t});\n}\n\nfunction isMatchingRoute(pathname: string, compiledRoutes: CompiledRoute[]): boolean {\n\treturn compiledRoutes.some((route) => {\n\t\tif (typeof route === \"string\") {\n\t\t\treturn pathname === route;\n\t\t}\n\t\treturn route.test(pathname);\n\t});\n}\n","/**\n * Create a Next.js route handler that proxies auth requests to Convex.\n *\n * This is used in `app/api/auth/[...all]/route.ts` to forward all\n * Better Auth API calls to the Convex HTTP actions endpoint.\n *\n * @example\n * ```ts\n * // app/api/auth/[...all]/route.ts\n * import { createRouteHandler } from \"@banata-auth/nextjs\";\n *\n * const handler = createRouteHandler({\n * convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,\n * });\n *\n * export const { GET, POST } = handler;\n * ```\n */\n/**\n * Headers that are safe to forward from the client to the Convex auth API.\n * All other headers are stripped to avoid leaking internal server details\n * (e.g. X-Forwarded-For, X-Real-IP, CF-* Cloudflare headers, etc.).\n */\nconst FORWARDED_HEADERS = new Set([\n\t\"accept\",\n\t\"accept-language\",\n\t\"authorization\",\n\t\"content-type\",\n\t\"content-length\",\n\t\"cookie\",\n\t\"origin\",\n\t\"referer\",\n\t\"user-agent\",\n\t\"x-requested-with\",\n]);\n\nexport function createRouteHandler(options: {\n\t/** The Convex .site URL where HTTP actions are hosted. */\n\tconvexSiteUrl: string;\n}) {\n\tconst { convexSiteUrl } = options;\n\tconst siteUrl = convexSiteUrl.replace(/\\/$/, \"\");\n\n\tasync function handler(request: Request): Promise<Response> {\n\t\tconst requestUrl = new URL(request.url);\n\t\tconst targetUrl = `${siteUrl}${requestUrl.pathname}${requestUrl.search}`;\n\n\t\t// Only forward allowed headers to the upstream auth API\n\t\tconst forwardedHeaders = new Headers();\n\t\tfor (const [key, value] of request.headers) {\n\t\t\tif (FORWARDED_HEADERS.has(key.toLowerCase())) {\n\t\t\t\tforwardedHeaders.set(key, value);\n\t\t\t}\n\t\t}\n\n\t\tconst newRequest = new Request(targetUrl, {\n\t\t\tmethod: request.method,\n\t\t\theaders: forwardedHeaders,\n\t\t\tbody: request.body,\n\t\t\t// @ts-expect-error - duplex is needed for streaming bodies\n\t\t\tduplex: \"half\",\n\t\t});\n\n\t\t// Set the host header to the Convex site\n\t\tnewRequest.headers.set(\"host\", new URL(siteUrl).host);\n\n\t\ttry {\n\t\t\tconst response = await fetch(newRequest, {\n\t\t\t\tmethod: request.method,\n\t\t\t\tredirect: \"manual\",\n\t\t\t});\n\n\t\t\t// Forward the response including cookies\n\t\t\treturn new Response(response.body, {\n\t\t\t\tstatus: response.status,\n\t\t\t\tstatusText: response.statusText,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[banata-auth] Proxy error:\", error);\n\t\t\treturn new Response(JSON.stringify({ error: \"Auth service unavailable\" }), {\n\t\t\t\tstatus: 502,\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t});\n\t\t}\n\t}\n\n\treturn {\n\t\tGET: handler,\n\t\tPOST: handler,\n\t\tPUT: handler,\n\t\tPATCH: handler,\n\t\tDELETE: handler,\n\t};\n}\n"]}
@@ -0,0 +1,78 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+
3
+ interface BanataAuthMiddlewareOptions {
4
+ /**
5
+ * Routes that don't require authentication.
6
+ * Supports exact strings and regex patterns.
7
+ */
8
+ publicRoutes?: string[];
9
+ /**
10
+ * Routes that the middleware should ignore entirely.
11
+ */
12
+ ignoredRoutes?: string[];
13
+ /**
14
+ * URL to redirect to when unauthenticated.
15
+ * @default "/sign-in"
16
+ */
17
+ signInUrl?: string;
18
+ /**
19
+ * URL to redirect to after successful sign-in.
20
+ * @default "/dashboard"
21
+ */
22
+ afterSignInUrl?: string;
23
+ }
24
+ /**
25
+ * Create a Next.js proxy/middleware that protects routes based on auth state.
26
+ *
27
+ * **Important:** This middleware checks for the *presence* of an auth session
28
+ * cookie, not its validity. The actual token is validated server-side when
29
+ * API routes are hit. This is the standard pattern for Next.js Edge
30
+ * middleware, which runs before the request reaches the server and cannot
31
+ * perform full JWT validation without a network round-trip.
32
+ *
33
+ * In Next.js 16+, the file convention is `proxy.ts` with a named `proxy` export.
34
+ * For Next.js 15 and earlier, use `middleware.ts` with a default export.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * // proxy.ts (Next.js 16+)
39
+ * import { banataAuthProxy } from "@banata-auth/nextjs";
40
+ *
41
+ * export const proxy = banataAuthProxy({
42
+ * publicRoutes: ["/", "/pricing", "/blog(.*)"],
43
+ * signInUrl: "/sign-in",
44
+ * });
45
+ *
46
+ * export const config = {
47
+ * matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
48
+ * };
49
+ * ```
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * // middleware.ts (Next.js 15 and earlier)
54
+ * import { banataAuthMiddleware } from "@banata-auth/nextjs";
55
+ *
56
+ * export default banataAuthMiddleware({
57
+ * publicRoutes: ["/", "/pricing", "/blog(.*)"],
58
+ * signInUrl: "/sign-in",
59
+ * });
60
+ *
61
+ * export const config = {
62
+ * matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
63
+ * };
64
+ * ```
65
+ */
66
+ declare function createAuthProxy(options?: BanataAuthMiddlewareOptions): (request: NextRequest) => Promise<NextResponse<unknown>>;
67
+ /**
68
+ * Create a Next.js proxy function for `proxy.ts` (Next.js 16+).
69
+ * The returned function is named `proxy` for compatibility with the new convention.
70
+ */
71
+ declare const banataAuthProxy: typeof createAuthProxy;
72
+ /**
73
+ * Backward-compatible alias for Next.js 15 and earlier `middleware.ts`.
74
+ * @deprecated Use `banataAuthProxy` with `proxy.ts` for Next.js 16+
75
+ */
76
+ declare const banataAuthMiddleware: typeof createAuthProxy;
77
+
78
+ export { type BanataAuthMiddlewareOptions, banataAuthMiddleware, banataAuthProxy };
@@ -0,0 +1,61 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ // src/middleware.ts
4
+ var AUTH_COOKIE_NAMES = [
5
+ "better-auth.session_token",
6
+ "__Secure-better-auth.session_token",
7
+ "better-auth-session_token",
8
+ "__Secure-better-auth-session_token",
9
+ "convex_jwt"
10
+ ];
11
+ function hasSessionCookie(request) {
12
+ return AUTH_COOKIE_NAMES.some((name) => {
13
+ const cookie = request.cookies.get(name);
14
+ return cookie !== void 0 && cookie.value !== "";
15
+ });
16
+ }
17
+ function createAuthProxy(options = {}) {
18
+ const { publicRoutes = [], ignoredRoutes = [], signInUrl = "/sign-in" } = options;
19
+ const compiledPublicRoutes = compileRoutes(publicRoutes);
20
+ const compiledIgnoredRoutes = compileRoutes(ignoredRoutes);
21
+ return async function proxy(request) {
22
+ const { pathname } = request.nextUrl;
23
+ const requestHeaders = new Headers(request.headers);
24
+ requestHeaders.set("x-pathname", pathname);
25
+ if (isMatchingRoute(pathname, compiledIgnoredRoutes)) {
26
+ return NextResponse.next({ request: { headers: requestHeaders } });
27
+ }
28
+ if (isMatchingRoute(pathname, compiledPublicRoutes)) {
29
+ return NextResponse.next({ request: { headers: requestHeaders } });
30
+ }
31
+ if (!hasSessionCookie(request)) {
32
+ const signInURL = new URL(signInUrl, request.url);
33
+ signInURL.searchParams.set("redirect_url", pathname);
34
+ return NextResponse.redirect(signInURL);
35
+ }
36
+ return NextResponse.next({ request: { headers: requestHeaders } });
37
+ };
38
+ }
39
+ var banataAuthProxy = createAuthProxy;
40
+ var banataAuthMiddleware = createAuthProxy;
41
+ function compileRoutes(routes) {
42
+ return routes.map((route) => {
43
+ try {
44
+ return new RegExp(`^${route}$`);
45
+ } catch {
46
+ return route;
47
+ }
48
+ });
49
+ }
50
+ function isMatchingRoute(pathname, compiledRoutes) {
51
+ return compiledRoutes.some((route) => {
52
+ if (typeof route === "string") {
53
+ return pathname === route;
54
+ }
55
+ return route.test(pathname);
56
+ });
57
+ }
58
+
59
+ export { banataAuthMiddleware, banataAuthProxy };
60
+ //# sourceMappingURL=middleware.js.map
61
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts"],"names":[],"mappings":";;;AAYA,IAAM,iBAAA,GAAoB;AAAA,EACzB,2BAAA;AAAA,EACA,oCAAA;AAAA,EACA,2BAAA;AAAA,EACA,oCAAA;AAAA,EACA;AACD,CAAA;AAQA,SAAS,iBAAiB,OAAA,EAA+B;AACxD,EAAA,OAAO,iBAAA,CAAkB,IAAA,CAAK,CAAC,IAAA,KAAS;AACvC,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,IAAI,CAAA;AACvC,IAAA,OAAO,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,KAAA,KAAU,EAAA;AAAA,EACjD,CAAC,CAAA;AACF;AAqEA,SAAS,eAAA,CAAgB,OAAA,GAAuC,EAAC,EAAG;AACnE,EAAA,MAAM,EAAE,eAAe,EAAC,EAAG,gBAAgB,EAAC,EAAG,SAAA,GAAY,UAAA,EAAW,GAAI,OAAA;AAK1E,EAAA,MAAM,oBAAA,GAAuB,cAAc,YAAY,CAAA;AACvD,EAAA,MAAM,qBAAA,GAAwB,cAAc,aAAa,CAAA;AAEzD,EAAA,OAAO,eAAe,MAAM,OAAA,EAAsB;AACjD,IAAA,MAAM,EAAE,QAAA,EAAS,GAAI,OAAA,CAAQ,OAAA;AAK7B,IAAA,MAAM,cAAA,GAAiB,IAAI,OAAA,CAAQ,OAAA,CAAQ,OAAO,CAAA;AAClD,IAAA,cAAA,CAAe,GAAA,CAAI,cAAc,QAAQ,CAAA;AAGzC,IAAA,IAAI,eAAA,CAAgB,QAAA,EAAU,qBAAqB,CAAA,EAAG;AACrD,MAAA,OAAO,YAAA,CAAa,KAAK,EAAE,OAAA,EAAS,EAAE,OAAA,EAAS,cAAA,IAAkB,CAAA;AAAA,IAClE;AAGA,IAAA,IAAI,eAAA,CAAgB,QAAA,EAAU,oBAAoB,CAAA,EAAG;AACpD,MAAA,OAAO,YAAA,CAAa,KAAK,EAAE,OAAA,EAAS,EAAE,OAAA,EAAS,cAAA,IAAkB,CAAA;AAAA,IAClE;AAGA,IAAA,IAAI,CAAC,gBAAA,CAAiB,OAAO,CAAA,EAAG;AAC/B,MAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,SAAA,EAAW,QAAQ,GAAG,CAAA;AAChD,MAAA,SAAA,CAAU,YAAA,CAAa,GAAA,CAAI,cAAA,EAAgB,QAAQ,CAAA;AACnD,MAAA,OAAO,YAAA,CAAa,SAAS,SAAS,CAAA;AAAA,IACvC;AAGA,IAAA,OAAO,YAAA,CAAa,KAAK,EAAE,OAAA,EAAS,EAAE,OAAA,EAAS,cAAA,IAAkB,CAAA;AAAA,EAClE,CAAA;AACD;AAMO,IAAM,eAAA,GAAkB;AAMxB,IAAM,oBAAA,GAAuB;AAUpC,SAAS,cAAc,MAAA,EAAmC;AACzD,EAAA,OAAO,MAAA,CAAO,GAAA,CAAI,CAAC,KAAA,KAAU;AAC5B,IAAA,IAAI;AACH,MAAA,OAAO,IAAI,MAAA,CAAO,CAAA,CAAA,EAAI,KAAK,CAAA,CAAA,CAAG,CAAA;AAAA,IAC/B,CAAA,CAAA,MAAQ;AAEP,MAAA,OAAO,KAAA;AAAA,IACR;AAAA,EACD,CAAC,CAAA;AACF;AAEA,SAAS,eAAA,CAAgB,UAAkB,cAAA,EAA0C;AACpF,EAAA,OAAO,cAAA,CAAe,IAAA,CAAK,CAAC,KAAA,KAAU;AACrC,IAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC9B,MAAA,OAAO,QAAA,KAAa,KAAA;AAAA,IACrB;AACA,IAAA,OAAO,KAAA,CAAM,KAAK,QAAQ,CAAA;AAAA,EAC3B,CAAC,CAAA;AACF","file":"middleware.js","sourcesContent":["import { type NextRequest, NextResponse } from \"next/server\";\n\n/**\n * Cookie names that indicate an active auth session.\n *\n * Better Auth uses `better-auth.session_token` (or `__Secure-better-auth.session_token`\n * in production). The Convex Better Auth integration also sets `convex_jwt`.\n * We check both to cover all deployment scenarios.\n *\n * NOTE: This only checks for cookie *presence*, not validity. The actual\n * token is validated server-side when the API routes are hit.\n */\nconst AUTH_COOKIE_NAMES = [\n\t\"better-auth.session_token\",\n\t\"__Secure-better-auth.session_token\",\n\t\"better-auth-session_token\",\n\t\"__Secure-better-auth-session_token\",\n\t\"convex_jwt\",\n];\n\n/**\n * Check whether the request carries any known auth session cookie.\n * Uses the standard `NextRequest.cookies` API rather than Better Auth's\n * `getSessionCookie()`, which requires a `Headers` object incompatible\n * with some mocking/testing scenarios.\n */\nfunction hasSessionCookie(request: NextRequest): boolean {\n\treturn AUTH_COOKIE_NAMES.some((name) => {\n\t\tconst cookie = request.cookies.get(name);\n\t\treturn cookie !== undefined && cookie.value !== \"\";\n\t});\n}\n\nexport interface BanataAuthMiddlewareOptions {\n\t/**\n\t * Routes that don't require authentication.\n\t * Supports exact strings and regex patterns.\n\t */\n\tpublicRoutes?: string[];\n\n\t/**\n\t * Routes that the middleware should ignore entirely.\n\t */\n\tignoredRoutes?: string[];\n\n\t/**\n\t * URL to redirect to when unauthenticated.\n\t * @default \"/sign-in\"\n\t */\n\tsignInUrl?: string;\n\n\t/**\n\t * URL to redirect to after successful sign-in.\n\t * @default \"/dashboard\"\n\t */\n\tafterSignInUrl?: string;\n}\n\n/**\n * Create a Next.js proxy/middleware that protects routes based on auth state.\n *\n * **Important:** This middleware checks for the *presence* of an auth session\n * cookie, not its validity. The actual token is validated server-side when\n * API routes are hit. This is the standard pattern for Next.js Edge\n * middleware, which runs before the request reaches the server and cannot\n * perform full JWT validation without a network round-trip.\n *\n * In Next.js 16+, the file convention is `proxy.ts` with a named `proxy` export.\n * For Next.js 15 and earlier, use `middleware.ts` with a default export.\n *\n * @example\n * ```ts\n * // proxy.ts (Next.js 16+)\n * import { banataAuthProxy } from \"@banata-auth/nextjs\";\n *\n * export const proxy = banataAuthProxy({\n * publicRoutes: [\"/\", \"/pricing\", \"/blog(.*)\"],\n * signInUrl: \"/sign-in\",\n * });\n *\n * export const config = {\n * matcher: [\"/((?!.*\\\\..*|_next).*)\", \"/\", \"/(api|trpc)(.*)\"],\n * };\n * ```\n *\n * @example\n * ```ts\n * // middleware.ts (Next.js 15 and earlier)\n * import { banataAuthMiddleware } from \"@banata-auth/nextjs\";\n *\n * export default banataAuthMiddleware({\n * publicRoutes: [\"/\", \"/pricing\", \"/blog(.*)\"],\n * signInUrl: \"/sign-in\",\n * });\n *\n * export const config = {\n * matcher: [\"/((?!.*\\\\..*|_next).*)\", \"/\", \"/(api|trpc)(.*)\"],\n * };\n * ```\n */\nfunction createAuthProxy(options: BanataAuthMiddlewareOptions = {}) {\n\tconst { publicRoutes = [], ignoredRoutes = [], signInUrl = \"/sign-in\" } = options;\n\n\t// Pre-compile route patterns at config time (once) to avoid\n\t// re-creating RegExp objects on every request and to catch\n\t// invalid patterns early.\n\tconst compiledPublicRoutes = compileRoutes(publicRoutes);\n\tconst compiledIgnoredRoutes = compileRoutes(ignoredRoutes);\n\n\treturn async function proxy(request: NextRequest) {\n\t\tconst { pathname } = request.nextUrl;\n\n\t\t// Forward pathname as a request header so server components\n\t\t// can read it via headers(). This is the official Next.js pattern\n\t\t// for passing data from proxy/middleware to server components.\n\t\tconst requestHeaders = new Headers(request.headers);\n\t\trequestHeaders.set(\"x-pathname\", pathname);\n\n\t\t// Check ignored routes\n\t\tif (isMatchingRoute(pathname, compiledIgnoredRoutes)) {\n\t\t\treturn NextResponse.next({ request: { headers: requestHeaders } });\n\t\t}\n\n\t\t// Check public routes\n\t\tif (isMatchingRoute(pathname, compiledPublicRoutes)) {\n\t\t\treturn NextResponse.next({ request: { headers: requestHeaders } });\n\t\t}\n\n\t\t// Check for auth session cookie presence\n\t\tif (!hasSessionCookie(request)) {\n\t\t\tconst signInURL = new URL(signInUrl, request.url);\n\t\t\tsignInURL.searchParams.set(\"redirect_url\", pathname);\n\t\t\treturn NextResponse.redirect(signInURL);\n\t\t}\n\n\t\t// Token exists - allow the request through.\n\t\treturn NextResponse.next({ request: { headers: requestHeaders } });\n\t};\n}\n\n/**\n * Create a Next.js proxy function for `proxy.ts` (Next.js 16+).\n * The returned function is named `proxy` for compatibility with the new convention.\n */\nexport const banataAuthProxy = createAuthProxy;\n\n/**\n * Backward-compatible alias for Next.js 15 and earlier `middleware.ts`.\n * @deprecated Use `banataAuthProxy` with `proxy.ts` for Next.js 16+\n */\nexport const banataAuthMiddleware = createAuthProxy;\n\n/** A pre-compiled route matcher — either a RegExp or an exact string. */\ntype CompiledRoute = RegExp | string;\n\n/**\n * Pre-compile route patterns into RegExp objects (anchored with ^ and $).\n * Invalid patterns fall back to exact string matching.\n * This is called once at config time, not per-request.\n */\nfunction compileRoutes(routes: string[]): CompiledRoute[] {\n\treturn routes.map((route) => {\n\t\ttry {\n\t\t\treturn new RegExp(`^${route}$`);\n\t\t} catch {\n\t\t\t// If the pattern is invalid regex, fall back to exact match\n\t\t\treturn route;\n\t\t}\n\t});\n}\n\nfunction isMatchingRoute(pathname: string, compiledRoutes: CompiledRoute[]): boolean {\n\treturn compiledRoutes.some((route) => {\n\t\tif (typeof route === \"string\") {\n\t\t\treturn pathname === route;\n\t\t}\n\t\treturn route.test(pathname);\n\t});\n}\n"]}
@@ -0,0 +1,80 @@
1
+ import * as convex_react from 'convex/react';
2
+ import * as convex_helpers from 'convex-helpers';
3
+ import * as convex_server from 'convex/server';
4
+
5
+ interface BanataAuthServerOptions {
6
+ /**
7
+ * Your Convex cloud URL (e.g. `https://adjective-animal-123.convex.cloud`).
8
+ * Typically `process.env.NEXT_PUBLIC_CONVEX_URL`.
9
+ */
10
+ convexUrl: string;
11
+ /**
12
+ * Your Convex site URL (e.g. `https://adjective-animal-123.convex.site`).
13
+ * Typically `process.env.NEXT_PUBLIC_CONVEX_SITE_URL`.
14
+ */
15
+ convexSiteUrl: string;
16
+ }
17
+ /**
18
+ * Create the Banata Auth server utilities for Next.js.
19
+ *
20
+ * This is the recommended way to configure server-side auth helpers.
21
+ * It wraps `@convex-dev/better-auth/nextjs` so consumers don't need
22
+ * that package as a direct dependency.
23
+ *
24
+ * Returns `handler`, `isAuthenticated`, `getToken`, `preloadAuthQuery`,
25
+ * `fetchAuthQuery`, `fetchAuthMutation`, and `fetchAuthAction`.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // lib/auth-server.ts
30
+ * import { createBanataAuthServer } from "@banata-auth/nextjs/server";
31
+ *
32
+ * export const {
33
+ * handler,
34
+ * isAuthenticated,
35
+ * getToken,
36
+ * preloadAuthQuery,
37
+ * fetchAuthQuery,
38
+ * fetchAuthMutation,
39
+ * fetchAuthAction,
40
+ * } = createBanataAuthServer({
41
+ * convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
42
+ * convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
43
+ * });
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * // app/api/auth/[...all]/route.ts
49
+ * import { handler } from "@/lib/auth-server";
50
+ * export const { GET, POST } = handler;
51
+ * ```
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // app/layout.tsx (server component)
56
+ * import { getToken, isAuthenticated } from "@/lib/auth-server";
57
+ * import { redirect } from "next/navigation";
58
+ *
59
+ * export default async function Layout({ children }) {
60
+ * if (!(await isAuthenticated())) redirect("/sign-in");
61
+ * const token = await getToken();
62
+ * return <Provider initialToken={token}>{children}</Provider>;
63
+ * }
64
+ * ```
65
+ */
66
+ declare function createBanataAuthServer(opts: BanataAuthServerOptions): {
67
+ getToken: () => Promise<string | undefined>;
68
+ handler: {
69
+ GET: (request: Request) => Promise<Response>;
70
+ POST: (request: Request) => Promise<Response>;
71
+ };
72
+ isAuthenticated: () => Promise<boolean>;
73
+ preloadAuthQuery: <Query extends convex_server.FunctionReference<"query">>(query: Query, ...args: Query["_args"] extends convex_helpers.EmptyObject ? [args?: convex_helpers.EmptyObject | undefined] : [args: Query["_args"]]) => Promise<convex_react.Preloaded<Query>>;
74
+ fetchAuthQuery: <Query extends convex_server.FunctionReference<"query">>(query: Query, ...args: Query["_args"] extends convex_helpers.EmptyObject ? [args?: convex_helpers.EmptyObject | undefined] : [args: Query["_args"]]) => Promise<convex_server.FunctionReturnType<Query>>;
75
+ fetchAuthMutation: <Mutation extends convex_server.FunctionReference<"mutation">>(mutation: Mutation, ...args: Mutation["_args"] extends convex_helpers.EmptyObject ? [args?: convex_helpers.EmptyObject | undefined] : [args: Mutation["_args"]]) => Promise<convex_server.FunctionReturnType<Mutation>>;
76
+ fetchAuthAction: <Action extends convex_server.FunctionReference<"action">>(action: Action, ...args: Action["_args"] extends convex_helpers.EmptyObject ? [args?: convex_helpers.EmptyObject | undefined] : [args: Action["_args"]]) => Promise<convex_server.FunctionReturnType<Action>>;
77
+ };
78
+ type BanataAuthServer = ReturnType<typeof createBanataAuthServer>;
79
+
80
+ export { type BanataAuthServer, type BanataAuthServerOptions, createBanataAuthServer };
package/dist/server.js ADDED
@@ -0,0 +1,13 @@
1
+ import { convexBetterAuthNextJs } from '@convex-dev/better-auth/nextjs';
2
+
3
+ // src/server.ts
4
+ function createBanataAuthServer(opts) {
5
+ return convexBetterAuthNextJs({
6
+ convexUrl: opts.convexUrl,
7
+ convexSiteUrl: opts.convexSiteUrl
8
+ });
9
+ }
10
+
11
+ export { createBanataAuthServer };
12
+ //# sourceMappingURL=server.js.map
13
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts"],"names":[],"mappings":";;;AAmEO,SAAS,uBAAuB,IAAA,EAA+B;AACrE,EAAA,OAAO,sBAAA,CAAuB;AAAA,IAC7B,WAAW,IAAA,CAAK,SAAA;AAAA,IAChB,eAAe,IAAA,CAAK;AAAA,GACpB,CAAA;AACF","file":"server.js","sourcesContent":["import { convexBetterAuthNextJs } from \"@convex-dev/better-auth/nextjs\";\n\n// ── Types ───────────────────────────────────────────────────────────\n\nexport interface BanataAuthServerOptions {\n\t/**\n\t * Your Convex cloud URL (e.g. `https://adjective-animal-123.convex.cloud`).\n\t * Typically `process.env.NEXT_PUBLIC_CONVEX_URL`.\n\t */\n\tconvexUrl: string;\n\n\t/**\n\t * Your Convex site URL (e.g. `https://adjective-animal-123.convex.site`).\n\t * Typically `process.env.NEXT_PUBLIC_CONVEX_SITE_URL`.\n\t */\n\tconvexSiteUrl: string;\n}\n\n/**\n * Create the Banata Auth server utilities for Next.js.\n *\n * This is the recommended way to configure server-side auth helpers.\n * It wraps `@convex-dev/better-auth/nextjs` so consumers don't need\n * that package as a direct dependency.\n *\n * Returns `handler`, `isAuthenticated`, `getToken`, `preloadAuthQuery`,\n * `fetchAuthQuery`, `fetchAuthMutation`, and `fetchAuthAction`.\n *\n * @example\n * ```ts\n * // lib/auth-server.ts\n * import { createBanataAuthServer } from \"@banata-auth/nextjs/server\";\n *\n * export const {\n * handler,\n * isAuthenticated,\n * getToken,\n * preloadAuthQuery,\n * fetchAuthQuery,\n * fetchAuthMutation,\n * fetchAuthAction,\n * } = createBanataAuthServer({\n * convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,\n * convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,\n * });\n * ```\n *\n * @example\n * ```ts\n * // app/api/auth/[...all]/route.ts\n * import { handler } from \"@/lib/auth-server\";\n * export const { GET, POST } = handler;\n * ```\n *\n * @example\n * ```ts\n * // app/layout.tsx (server component)\n * import { getToken, isAuthenticated } from \"@/lib/auth-server\";\n * import { redirect } from \"next/navigation\";\n *\n * export default async function Layout({ children }) {\n * if (!(await isAuthenticated())) redirect(\"/sign-in\");\n * const token = await getToken();\n * return <Provider initialToken={token}>{children}</Provider>;\n * }\n * ```\n */\nexport function createBanataAuthServer(opts: BanataAuthServerOptions) {\n\treturn convexBetterAuthNextJs({\n\t\tconvexUrl: opts.convexUrl,\n\t\tconvexSiteUrl: opts.convexSiteUrl,\n\t});\n}\n\n// Re-export the return type so consumers can type their variables if needed\nexport type BanataAuthServer = ReturnType<typeof createBanataAuthServer>;\n"]}
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "@banata-auth/nextjs",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "description": "Next.js integration for Banata Auth — route handler, middleware, and server-side utilities",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/banata-auth/banata-auth",
11
+ "directory": "packages/nextjs"
12
+ },
13
+ "homepage": "https://auth-docs.banata.dev/docs/nextjs",
14
+ "keywords": ["banata-auth", "auth", "nextjs", "middleware", "route-handler", "server"],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "sideEffects": false,
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "./server": {
26
+ "types": "./dist/server.d.ts",
27
+ "import": "./dist/server.js",
28
+ "default": "./dist/server.js"
29
+ },
30
+ "./client": {
31
+ "types": "./dist/client.d.ts",
32
+ "import": "./dist/client.js",
33
+ "default": "./dist/client.js"
34
+ },
35
+ "./middleware": {
36
+ "types": "./dist/middleware.d.ts",
37
+ "import": "./dist/middleware.js",
38
+ "default": "./dist/middleware.js"
39
+ },
40
+ "./bot-protection": {
41
+ "types": "./dist/bot-protection.d.ts",
42
+ "import": "./dist/bot-protection.js",
43
+ "default": "./dist/bot-protection.js"
44
+ }
45
+ },
46
+ "main": "./dist/index.js",
47
+ "module": "./dist/index.js",
48
+ "types": "./dist/index.d.ts",
49
+ "files": ["dist"],
50
+ "scripts": {
51
+ "build": "tsup && node ../../scripts/ensure-use-client.mjs dist/client.js",
52
+ "dev": "tsup --watch",
53
+ "typecheck": "tsc --noEmit",
54
+ "test": "vitest run --passWithNoTests",
55
+ "prepublishOnly": "bun run build",
56
+ "clean": "rm -rf dist .turbo"
57
+ },
58
+ "dependencies": {
59
+ "@banata-auth/react": "workspace:*",
60
+ "@banata-auth/shared": "workspace:*"
61
+ },
62
+ "peerDependencies": {
63
+ "@convex-dev/better-auth": ">=0.10.0",
64
+ "better-auth": ">=1.4.0",
65
+ "convex": ">=1.25.0",
66
+ "next": ">=15.0.0",
67
+ "react": "^18 || ^19"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "@convex-dev/better-auth": { "optional": true },
71
+ "better-auth": { "optional": true },
72
+ "convex": { "optional": true }
73
+ },
74
+ "devDependencies": {
75
+ "@convex-dev/better-auth": "^0.10.13",
76
+ "@types/react": "^19.0.0",
77
+ "better-auth": "^1.4.20",
78
+ "convex": "^1.25.0",
79
+ "next": "^16.0.0",
80
+ "react": "^19.0.0",
81
+ "tsup": "^8.3.0",
82
+ "typescript": "^5.7.0",
83
+ "vitest": "^3.0.0"
84
+ }
85
+ }