@solvapay/auth 1.2.0 → 1.3.0-preview-3fd9d2205ea1113de9bbfb34ba7217629ccf40f0

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.
@@ -0,0 +1,134 @@
1
+ // src/verify-token.ts
2
+ import { createRemoteJWKSet, decodeJwt, decodeProtectedHeader, jwtVerify } from "jose";
3
+ var ASYMMETRIC_ALGORITHMS = ["ES256", "ES384", "ES512", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "EdDSA"];
4
+ var TOKEN_NOT_VERIFIABLE = "No way to verify the authentication token. Set SOLVAPAY_JWT_SECRET (HS256 tokens), SOLVAPAY_JWKS_URL (ES256/RS256 tokens; derived from SUPABASE_URL on Supabase), or SOLVAPAY_AUTH_TRUST_GATEWAY=true if a gateway verifies every token before this handler.";
5
+ function readEnv(name) {
6
+ const proc = globalThis.process;
7
+ const deno = globalThis.Deno;
8
+ const value = proc?.env?.[name] ?? deno?.env?.get(name);
9
+ return value && value.length > 0 ? value : void 0;
10
+ }
11
+ function supabaseJwksUrl() {
12
+ const base = readEnv("SUPABASE_URL");
13
+ return base ? `${base.replace(/\/+$/, "")}/auth/v1/.well-known/jwks.json` : void 0;
14
+ }
15
+ var jwksSets = /* @__PURE__ */ new Map();
16
+ function jwksFor(url) {
17
+ let set = jwksSets.get(url);
18
+ if (!set) {
19
+ set = createRemoteJWKSet(new URL(url));
20
+ jwksSets.set(url, set);
21
+ }
22
+ return set;
23
+ }
24
+ async function verifyBearerToken(token, options = {}) {
25
+ const strict = readEnv("SOLVAPAY_AUTH_STRICT") === "true";
26
+ const trustGateway = options.trustGateway ?? readEnv("SOLVAPAY_AUTH_TRUST_GATEWAY") === "true";
27
+ if (strict && trustGateway) {
28
+ return {
29
+ ok: false,
30
+ reason: "SOLVAPAY_AUTH_STRICT=true forbids trusting a gateway, but gateway trust is on. Turn one of them off."
31
+ };
32
+ }
33
+ let alg;
34
+ try {
35
+ alg = decodeProtectedHeader(token).alg;
36
+ } catch {
37
+ return { ok: false, reason: "Malformed authentication token" };
38
+ }
39
+ if (alg === "HS256") {
40
+ const secret = options.jwtSecret || readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
41
+ if (secret) {
42
+ try {
43
+ const { payload: payload2 } = await jwtVerify(token, new TextEncoder().encode(secret), {
44
+ algorithms: ["HS256"]
45
+ });
46
+ return { ok: true, payload: payload2, verifiedBy: "secret" };
47
+ } catch {
48
+ return { ok: false, reason: "Invalid or expired authentication token" };
49
+ }
50
+ }
51
+ } else if (typeof alg === "string" && ASYMMETRIC_ALGORITHMS.includes(alg)) {
52
+ const jwksUrl = options.jwksUrl || readEnv("SOLVAPAY_JWKS_URL") || supabaseJwksUrl();
53
+ if (jwksUrl) {
54
+ try {
55
+ const { payload: payload2 } = await jwtVerify(token, jwksFor(jwksUrl), {
56
+ algorithms: ASYMMETRIC_ALGORITHMS
57
+ });
58
+ return { ok: true, payload: payload2, verifiedBy: "jwks" };
59
+ } catch {
60
+ return { ok: false, reason: "Invalid or expired authentication token" };
61
+ }
62
+ }
63
+ } else {
64
+ return { ok: false, reason: `Unsupported authentication token algorithm: ${String(alg)}` };
65
+ }
66
+ if (!trustGateway) {
67
+ return { ok: false, reason: TOKEN_NOT_VERIFIABLE };
68
+ }
69
+ let payload;
70
+ try {
71
+ payload = decodeJwt(token);
72
+ } catch {
73
+ return { ok: false, reason: "Malformed authentication token" };
74
+ }
75
+ if (typeof payload.exp === "number" && payload.exp * 1e3 <= Date.now()) {
76
+ return { ok: false, reason: "Invalid or expired authentication token" };
77
+ }
78
+ return { ok: true, payload, verifiedBy: "gateway" };
79
+ }
80
+
81
+ // src/next-utils.ts
82
+ function extractBearerToken(request) {
83
+ const authHeader = request.headers.get("authorization");
84
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
85
+ const token = authHeader.slice(7).trim();
86
+ return token.length > 0 ? token : null;
87
+ }
88
+ async function resolveJwtPayload(token, jwtSecret) {
89
+ const verified = await verifyBearerToken(token, { jwtSecret });
90
+ return verified.ok ? verified.payload : null;
91
+ }
92
+ function getUserIdFromRequest(request, options) {
93
+ const headerName = options?.headerName || "x-user-id";
94
+ return request.headers.get(headerName);
95
+ }
96
+ async function getUserEmailFromRequest(request, options) {
97
+ const token = extractBearerToken(request);
98
+ if (!token) return null;
99
+ const payload = await resolveJwtPayload(token, options?.jwtSecret);
100
+ if (!payload) return null;
101
+ return typeof payload.email === "string" ? payload.email : null;
102
+ }
103
+ async function getUserNameFromRequest(request, options) {
104
+ const token = extractBearerToken(request);
105
+ if (!token) return null;
106
+ const payload = await resolveJwtPayload(token, options?.jwtSecret);
107
+ if (!payload) return null;
108
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
109
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
110
+ const claimName = typeof payload.name === "string" ? payload.name : null;
111
+ return metadataFullName || metadataName || claimName || null;
112
+ }
113
+ function requireUserId(request, options) {
114
+ const headerName = options?.headerName || "x-user-id";
115
+ const errorMessage = options?.errorMessage || "Unauthorized";
116
+ const errorDetails = options?.errorDetails || "User ID not found. Ensure middleware is configured.";
117
+ const userId = request.headers.get(headerName);
118
+ if (!userId) {
119
+ return new Response(JSON.stringify({ error: errorMessage, details: errorDetails }), {
120
+ status: 401,
121
+ headers: { "Content-Type": "application/json" }
122
+ });
123
+ }
124
+ return userId;
125
+ }
126
+
127
+ export {
128
+ TOKEN_NOT_VERIFIABLE,
129
+ verifyBearerToken,
130
+ getUserIdFromRequest,
131
+ getUserEmailFromRequest,
132
+ getUserNameFromRequest,
133
+ requireUserId
134
+ };
package/dist/index.cjs CHANGED
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/index.ts
@@ -35,10 +25,12 @@ __export(index_exports, {
35
25
  SOLVAPAY_USER_ID_HEADER: () => SOLVAPAY_USER_ID_HEADER,
36
26
  SolvapayAuthAdapter: () => SolvapayAuthAdapter,
37
27
  SolvapayOAuthClient: () => SolvapayOAuthClient,
28
+ TOKEN_NOT_VERIFIABLE: () => TOKEN_NOT_VERIFIABLE,
38
29
  getUserEmailFromRequest: () => getUserEmailFromRequest,
39
30
  getUserIdFromRequest: () => getUserIdFromRequest,
40
31
  getUserNameFromRequest: () => getUserNameFromRequest,
41
- requireUserId: () => requireUserId
32
+ requireUserId: () => requireUserId,
33
+ verifyBearerToken: () => verifyBearerToken
42
34
  });
43
35
  module.exports = __toCommonJS(index_exports);
44
36
 
@@ -180,68 +172,96 @@ var SolvapayOAuthClient = class {
180
172
  }
181
173
  };
182
174
 
183
- // src/next-utils.ts
184
- function base64UrlDecode(input) {
185
- const padded = input.replace(/-/g, "+").replace(/_/g, "/");
186
- const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
187
- const base64 = padded + padding;
188
- if (typeof atob === "function") {
189
- const binary = atob(base64);
190
- let result = "";
191
- for (let i = 0; i < binary.length; i++) {
192
- result += String.fromCharCode(binary.charCodeAt(i));
175
+ // src/verify-token.ts
176
+ var import_jose = require("jose");
177
+ var ASYMMETRIC_ALGORITHMS = ["ES256", "ES384", "ES512", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "EdDSA"];
178
+ var TOKEN_NOT_VERIFIABLE = "No way to verify the authentication token. Set SOLVAPAY_JWT_SECRET (HS256 tokens), SOLVAPAY_JWKS_URL (ES256/RS256 tokens; derived from SUPABASE_URL on Supabase), or SOLVAPAY_AUTH_TRUST_GATEWAY=true if a gateway verifies every token before this handler.";
179
+ function readEnv(name) {
180
+ const proc = globalThis.process;
181
+ const deno = globalThis.Deno;
182
+ const value = proc?.env?.[name] ?? deno?.env?.get(name);
183
+ return value && value.length > 0 ? value : void 0;
184
+ }
185
+ function supabaseJwksUrl() {
186
+ const base = readEnv("SUPABASE_URL");
187
+ return base ? `${base.replace(/\/+$/, "")}/auth/v1/.well-known/jwks.json` : void 0;
188
+ }
189
+ var jwksSets = /* @__PURE__ */ new Map();
190
+ function jwksFor(url) {
191
+ let set = jwksSets.get(url);
192
+ if (!set) {
193
+ set = (0, import_jose.createRemoteJWKSet)(new URL(url));
194
+ jwksSets.set(url, set);
195
+ }
196
+ return set;
197
+ }
198
+ async function verifyBearerToken(token, options = {}) {
199
+ const strict = readEnv("SOLVAPAY_AUTH_STRICT") === "true";
200
+ const trustGateway = options.trustGateway ?? readEnv("SOLVAPAY_AUTH_TRUST_GATEWAY") === "true";
201
+ if (strict && trustGateway) {
202
+ return {
203
+ ok: false,
204
+ reason: "SOLVAPAY_AUTH_STRICT=true forbids trusting a gateway, but gateway trust is on. Turn one of them off."
205
+ };
206
+ }
207
+ let alg;
208
+ try {
209
+ alg = (0, import_jose.decodeProtectedHeader)(token).alg;
210
+ } catch {
211
+ return { ok: false, reason: "Malformed authentication token" };
212
+ }
213
+ if (alg === "HS256") {
214
+ const secret = options.jwtSecret || readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
215
+ if (secret) {
216
+ try {
217
+ const { payload: payload2 } = await (0, import_jose.jwtVerify)(token, new TextEncoder().encode(secret), {
218
+ algorithms: ["HS256"]
219
+ });
220
+ return { ok: true, payload: payload2, verifiedBy: "secret" };
221
+ } catch {
222
+ return { ok: false, reason: "Invalid or expired authentication token" };
223
+ }
193
224
  }
194
- try {
195
- return decodeURIComponent(escape(result));
196
- } catch {
197
- return result;
225
+ } else if (typeof alg === "string" && ASYMMETRIC_ALGORITHMS.includes(alg)) {
226
+ const jwksUrl = options.jwksUrl || readEnv("SOLVAPAY_JWKS_URL") || supabaseJwksUrl();
227
+ if (jwksUrl) {
228
+ try {
229
+ const { payload: payload2 } = await (0, import_jose.jwtVerify)(token, jwksFor(jwksUrl), {
230
+ algorithms: ASYMMETRIC_ALGORITHMS
231
+ });
232
+ return { ok: true, payload: payload2, verifiedBy: "jwks" };
233
+ } catch {
234
+ return { ok: false, reason: "Invalid or expired authentication token" };
235
+ }
198
236
  }
237
+ } else {
238
+ return { ok: false, reason: `Unsupported authentication token algorithm: ${String(alg)}` };
199
239
  }
200
- const BufferCtor = globalThis.Buffer;
201
- if (BufferCtor) {
202
- return BufferCtor.from(base64, "base64").toString("utf-8");
240
+ if (!trustGateway) {
241
+ return { ok: false, reason: TOKEN_NOT_VERIFIABLE };
203
242
  }
204
- throw new Error("No base64 decoder available in this runtime");
205
- }
206
- function decodeJwtUnverified(token) {
207
- const parts = token.split(".");
208
- if (parts.length !== 3) return null;
243
+ let payload;
209
244
  try {
210
- const json = base64UrlDecode(parts[1]);
211
- const payload = JSON.parse(json);
212
- if (typeof payload !== "object" || payload === null) return null;
213
- return payload;
245
+ payload = (0, import_jose.decodeJwt)(token);
214
246
  } catch {
215
- return null;
247
+ return { ok: false, reason: "Malformed authentication token" };
216
248
  }
249
+ if (typeof payload.exp === "number" && payload.exp * 1e3 <= Date.now()) {
250
+ return { ok: false, reason: "Invalid or expired authentication token" };
251
+ }
252
+ return { ok: true, payload, verifiedBy: "gateway" };
217
253
  }
254
+
255
+ // src/next-utils.ts
218
256
  function extractBearerToken(request) {
219
257
  const authHeader = request.headers.get("authorization");
220
258
  if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
221
259
  const token = authHeader.slice(7).trim();
222
260
  return token.length > 0 ? token : null;
223
261
  }
224
- function readEnv(name) {
225
- const proc = globalThis.process;
226
- return proc?.env?.[name];
227
- }
228
- function isStrictMode() {
229
- return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
230
- }
231
262
  async function resolveJwtPayload(token, jwtSecret) {
232
- const secret = jwtSecret || readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
233
- if (secret) {
234
- try {
235
- const { jwtVerify } = await import("jose");
236
- const key = new TextEncoder().encode(secret);
237
- const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
238
- return payload;
239
- } catch {
240
- return null;
241
- }
242
- }
243
- if (isStrictMode()) return null;
244
- return decodeJwtUnverified(token);
263
+ const verified = await verifyBearerToken(token, { jwtSecret });
264
+ return verified.ok ? verified.payload : null;
245
265
  }
246
266
  function getUserIdFromRequest(request, options) {
247
267
  const headerName = options?.headerName || "x-user-id";
@@ -284,8 +304,10 @@ function requireUserId(request, options) {
284
304
  SOLVAPAY_USER_ID_HEADER,
285
305
  SolvapayAuthAdapter,
286
306
  SolvapayOAuthClient,
307
+ TOKEN_NOT_VERIFIABLE,
287
308
  getUserEmailFromRequest,
288
309
  getUserIdFromRequest,
289
310
  getUserNameFromRequest,
290
- requireUserId
311
+ requireUserId,
312
+ verifyBearerToken
291
313
  });
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { A as AuthAdapter, R as RequestLike } from './adapter-B2LYWIJl.cjs';
2
2
  export { a as AuthRequestHandleResult, S as ServerIdentity } from './adapter-B2LYWIJl.cjs';
3
3
  export { MockAuthAdapter, MockAuthAdapter as MockAuthAdapterType } from './mock.cjs';
4
+ import { JWTPayload } from 'jose';
4
5
  export { getUserEmailFromRequest, getUserIdFromRequest, getUserNameFromRequest, requireUserId } from './next-utils.cjs';
5
6
 
6
7
  /**
@@ -78,4 +79,45 @@ declare class SolvapayOAuthClient {
78
79
  getUserInfo(accessToken: string, userInfoUrl?: string): Promise<Record<string, unknown>>;
79
80
  }
80
81
 
81
- export { AuthAdapter, RequestLike, SOLVAPAY_AUTHORIZATION_HEADER, SOLVAPAY_USER_ID_HEADER, SolvapayAuthAdapter, type SolvapayAuthAdapterConfig, SolvapayOAuthClient, type SolvapayOAuthConfig, type TokenResponse };
82
+ /**
83
+ * Verifies a bearer token before a route trusts who it names.
84
+ *
85
+ * Every SolvaPay route helper that reads the user from `Authorization: Bearer`
86
+ * goes through `verifyBearerToken`. A token is trusted only when one of these
87
+ * says so, chosen by the token's `alg`:
88
+ *
89
+ * - **HS256** with a shared secret: `jwtSecret`, else `SOLVAPAY_JWT_SECRET`,
90
+ * else `SUPABASE_JWT_SECRET`.
91
+ * - **ES256 / RS256 and other asymmetric algorithms** against a JWKS:
92
+ * `jwksUrl`, else `SOLVAPAY_JWKS_URL`, else, on Supabase,
93
+ * `${SUPABASE_URL}/auth/v1/.well-known/jwks.json`.
94
+ * - **A gateway that already verified it**, only when you say so:
95
+ * `trustGateway`, or `SOLVAPAY_AUTH_TRUST_GATEWAY=true` (for example
96
+ * Supabase Edge Functions with `verify_jwt = true`). The token is then read
97
+ * without checking its signature, so turn this on only when nothing can
98
+ * reach the handler without passing that gateway.
99
+ *
100
+ * With none of these, the token is refused: there is no unverified default.
101
+ * `SOLVAPAY_AUTH_STRICT=true` forbids gateway trust outright.
102
+ */
103
+
104
+ type TokenVerification = {
105
+ ok: true;
106
+ payload: JWTPayload;
107
+ verifiedBy: 'secret' | 'jwks' | 'gateway';
108
+ } | {
109
+ ok: false;
110
+ reason: string;
111
+ };
112
+ interface VerifyBearerTokenOptions {
113
+ /** HS256 secret; defaults to SOLVAPAY_JWT_SECRET, then SUPABASE_JWT_SECRET. */
114
+ jwtSecret?: string;
115
+ /** JWKS URL for ES256/RS256 tokens; defaults to SOLVAPAY_JWKS_URL, then Supabase's from SUPABASE_URL. */
116
+ jwksUrl?: string;
117
+ /** Trust a gateway that verified the token already; defaults to SOLVAPAY_AUTH_TRUST_GATEWAY=true. */
118
+ trustGateway?: boolean;
119
+ }
120
+ declare const TOKEN_NOT_VERIFIABLE: string;
121
+ declare function verifyBearerToken(token: string, options?: VerifyBearerTokenOptions): Promise<TokenVerification>;
122
+
123
+ export { AuthAdapter, RequestLike, SOLVAPAY_AUTHORIZATION_HEADER, SOLVAPAY_USER_ID_HEADER, SolvapayAuthAdapter, type SolvapayAuthAdapterConfig, SolvapayOAuthClient, type SolvapayOAuthConfig, TOKEN_NOT_VERIFIABLE, type TokenResponse, type TokenVerification, type VerifyBearerTokenOptions, verifyBearerToken };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { A as AuthAdapter, R as RequestLike } from './adapter-B2LYWIJl.js';
2
2
  export { a as AuthRequestHandleResult, S as ServerIdentity } from './adapter-B2LYWIJl.js';
3
3
  export { MockAuthAdapter, MockAuthAdapter as MockAuthAdapterType } from './mock.js';
4
+ import { JWTPayload } from 'jose';
4
5
  export { getUserEmailFromRequest, getUserIdFromRequest, getUserNameFromRequest, requireUserId } from './next-utils.js';
5
6
 
6
7
  /**
@@ -78,4 +79,45 @@ declare class SolvapayOAuthClient {
78
79
  getUserInfo(accessToken: string, userInfoUrl?: string): Promise<Record<string, unknown>>;
79
80
  }
80
81
 
81
- export { AuthAdapter, RequestLike, SOLVAPAY_AUTHORIZATION_HEADER, SOLVAPAY_USER_ID_HEADER, SolvapayAuthAdapter, type SolvapayAuthAdapterConfig, SolvapayOAuthClient, type SolvapayOAuthConfig, type TokenResponse };
82
+ /**
83
+ * Verifies a bearer token before a route trusts who it names.
84
+ *
85
+ * Every SolvaPay route helper that reads the user from `Authorization: Bearer`
86
+ * goes through `verifyBearerToken`. A token is trusted only when one of these
87
+ * says so, chosen by the token's `alg`:
88
+ *
89
+ * - **HS256** with a shared secret: `jwtSecret`, else `SOLVAPAY_JWT_SECRET`,
90
+ * else `SUPABASE_JWT_SECRET`.
91
+ * - **ES256 / RS256 and other asymmetric algorithms** against a JWKS:
92
+ * `jwksUrl`, else `SOLVAPAY_JWKS_URL`, else, on Supabase,
93
+ * `${SUPABASE_URL}/auth/v1/.well-known/jwks.json`.
94
+ * - **A gateway that already verified it**, only when you say so:
95
+ * `trustGateway`, or `SOLVAPAY_AUTH_TRUST_GATEWAY=true` (for example
96
+ * Supabase Edge Functions with `verify_jwt = true`). The token is then read
97
+ * without checking its signature, so turn this on only when nothing can
98
+ * reach the handler without passing that gateway.
99
+ *
100
+ * With none of these, the token is refused: there is no unverified default.
101
+ * `SOLVAPAY_AUTH_STRICT=true` forbids gateway trust outright.
102
+ */
103
+
104
+ type TokenVerification = {
105
+ ok: true;
106
+ payload: JWTPayload;
107
+ verifiedBy: 'secret' | 'jwks' | 'gateway';
108
+ } | {
109
+ ok: false;
110
+ reason: string;
111
+ };
112
+ interface VerifyBearerTokenOptions {
113
+ /** HS256 secret; defaults to SOLVAPAY_JWT_SECRET, then SUPABASE_JWT_SECRET. */
114
+ jwtSecret?: string;
115
+ /** JWKS URL for ES256/RS256 tokens; defaults to SOLVAPAY_JWKS_URL, then Supabase's from SUPABASE_URL. */
116
+ jwksUrl?: string;
117
+ /** Trust a gateway that verified the token already; defaults to SOLVAPAY_AUTH_TRUST_GATEWAY=true. */
118
+ trustGateway?: boolean;
119
+ }
120
+ declare const TOKEN_NOT_VERIFIABLE: string;
121
+ declare function verifyBearerToken(token: string, options?: VerifyBearerTokenOptions): Promise<TokenVerification>;
122
+
123
+ export { AuthAdapter, RequestLike, SOLVAPAY_AUTHORIZATION_HEADER, SOLVAPAY_USER_ID_HEADER, SolvapayAuthAdapter, type SolvapayAuthAdapterConfig, SolvapayOAuthClient, type SolvapayOAuthConfig, TOKEN_NOT_VERIFIABLE, type TokenResponse, type TokenVerification, type VerifyBearerTokenOptions, verifyBearerToken };
package/dist/index.js CHANGED
@@ -2,11 +2,13 @@ import {
2
2
  MockAuthAdapter
3
3
  } from "./chunk-OETEFHW6.js";
4
4
  import {
5
+ TOKEN_NOT_VERIFIABLE,
5
6
  getUserEmailFromRequest,
6
7
  getUserIdFromRequest,
7
8
  getUserNameFromRequest,
8
- requireUserId
9
- } from "./chunk-ESD73BSC.js";
9
+ requireUserId,
10
+ verifyBearerToken
11
+ } from "./chunk-WVD7RQ6U.js";
10
12
 
11
13
  // src/constants.ts
12
14
  var SOLVAPAY_USER_ID_HEADER = "x-user-id";
@@ -136,8 +138,10 @@ export {
136
138
  SOLVAPAY_USER_ID_HEADER,
137
139
  SolvapayAuthAdapter,
138
140
  SolvapayOAuthClient,
141
+ TOKEN_NOT_VERIFIABLE,
139
142
  getUserEmailFromRequest,
140
143
  getUserIdFromRequest,
141
144
  getUserNameFromRequest,
142
- requireUserId
145
+ requireUserId,
146
+ verifyBearerToken
143
147
  };
@@ -1,9 +1,7 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
6
  var __export = (target, all) => {
9
7
  for (var name in all)
@@ -17,14 +15,6 @@ var __copyProps = (to, from, except, desc) => {
17
15
  }
18
16
  return to;
19
17
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
19
 
30
20
  // src/next-utils.ts
@@ -36,67 +26,97 @@ __export(next_utils_exports, {
36
26
  requireUserId: () => requireUserId
37
27
  });
38
28
  module.exports = __toCommonJS(next_utils_exports);
39
- function base64UrlDecode(input) {
40
- const padded = input.replace(/-/g, "+").replace(/_/g, "/");
41
- const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
42
- const base64 = padded + padding;
43
- if (typeof atob === "function") {
44
- const binary = atob(base64);
45
- let result = "";
46
- for (let i = 0; i < binary.length; i++) {
47
- result += String.fromCharCode(binary.charCodeAt(i));
29
+
30
+ // src/verify-token.ts
31
+ var import_jose = require("jose");
32
+ var ASYMMETRIC_ALGORITHMS = ["ES256", "ES384", "ES512", "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "EdDSA"];
33
+ var TOKEN_NOT_VERIFIABLE = "No way to verify the authentication token. Set SOLVAPAY_JWT_SECRET (HS256 tokens), SOLVAPAY_JWKS_URL (ES256/RS256 tokens; derived from SUPABASE_URL on Supabase), or SOLVAPAY_AUTH_TRUST_GATEWAY=true if a gateway verifies every token before this handler.";
34
+ function readEnv(name) {
35
+ const proc = globalThis.process;
36
+ const deno = globalThis.Deno;
37
+ const value = proc?.env?.[name] ?? deno?.env?.get(name);
38
+ return value && value.length > 0 ? value : void 0;
39
+ }
40
+ function supabaseJwksUrl() {
41
+ const base = readEnv("SUPABASE_URL");
42
+ return base ? `${base.replace(/\/+$/, "")}/auth/v1/.well-known/jwks.json` : void 0;
43
+ }
44
+ var jwksSets = /* @__PURE__ */ new Map();
45
+ function jwksFor(url) {
46
+ let set = jwksSets.get(url);
47
+ if (!set) {
48
+ set = (0, import_jose.createRemoteJWKSet)(new URL(url));
49
+ jwksSets.set(url, set);
50
+ }
51
+ return set;
52
+ }
53
+ async function verifyBearerToken(token, options = {}) {
54
+ const strict = readEnv("SOLVAPAY_AUTH_STRICT") === "true";
55
+ const trustGateway = options.trustGateway ?? readEnv("SOLVAPAY_AUTH_TRUST_GATEWAY") === "true";
56
+ if (strict && trustGateway) {
57
+ return {
58
+ ok: false,
59
+ reason: "SOLVAPAY_AUTH_STRICT=true forbids trusting a gateway, but gateway trust is on. Turn one of them off."
60
+ };
61
+ }
62
+ let alg;
63
+ try {
64
+ alg = (0, import_jose.decodeProtectedHeader)(token).alg;
65
+ } catch {
66
+ return { ok: false, reason: "Malformed authentication token" };
67
+ }
68
+ if (alg === "HS256") {
69
+ const secret = options.jwtSecret || readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
70
+ if (secret) {
71
+ try {
72
+ const { payload: payload2 } = await (0, import_jose.jwtVerify)(token, new TextEncoder().encode(secret), {
73
+ algorithms: ["HS256"]
74
+ });
75
+ return { ok: true, payload: payload2, verifiedBy: "secret" };
76
+ } catch {
77
+ return { ok: false, reason: "Invalid or expired authentication token" };
78
+ }
48
79
  }
49
- try {
50
- return decodeURIComponent(escape(result));
51
- } catch {
52
- return result;
80
+ } else if (typeof alg === "string" && ASYMMETRIC_ALGORITHMS.includes(alg)) {
81
+ const jwksUrl = options.jwksUrl || readEnv("SOLVAPAY_JWKS_URL") || supabaseJwksUrl();
82
+ if (jwksUrl) {
83
+ try {
84
+ const { payload: payload2 } = await (0, import_jose.jwtVerify)(token, jwksFor(jwksUrl), {
85
+ algorithms: ASYMMETRIC_ALGORITHMS
86
+ });
87
+ return { ok: true, payload: payload2, verifiedBy: "jwks" };
88
+ } catch {
89
+ return { ok: false, reason: "Invalid or expired authentication token" };
90
+ }
53
91
  }
92
+ } else {
93
+ return { ok: false, reason: `Unsupported authentication token algorithm: ${String(alg)}` };
54
94
  }
55
- const BufferCtor = globalThis.Buffer;
56
- if (BufferCtor) {
57
- return BufferCtor.from(base64, "base64").toString("utf-8");
95
+ if (!trustGateway) {
96
+ return { ok: false, reason: TOKEN_NOT_VERIFIABLE };
58
97
  }
59
- throw new Error("No base64 decoder available in this runtime");
60
- }
61
- function decodeJwtUnverified(token) {
62
- const parts = token.split(".");
63
- if (parts.length !== 3) return null;
98
+ let payload;
64
99
  try {
65
- const json = base64UrlDecode(parts[1]);
66
- const payload = JSON.parse(json);
67
- if (typeof payload !== "object" || payload === null) return null;
68
- return payload;
100
+ payload = (0, import_jose.decodeJwt)(token);
69
101
  } catch {
70
- return null;
102
+ return { ok: false, reason: "Malformed authentication token" };
103
+ }
104
+ if (typeof payload.exp === "number" && payload.exp * 1e3 <= Date.now()) {
105
+ return { ok: false, reason: "Invalid or expired authentication token" };
71
106
  }
107
+ return { ok: true, payload, verifiedBy: "gateway" };
72
108
  }
109
+
110
+ // src/next-utils.ts
73
111
  function extractBearerToken(request) {
74
112
  const authHeader = request.headers.get("authorization");
75
113
  if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
76
114
  const token = authHeader.slice(7).trim();
77
115
  return token.length > 0 ? token : null;
78
116
  }
79
- function readEnv(name) {
80
- const proc = globalThis.process;
81
- return proc?.env?.[name];
82
- }
83
- function isStrictMode() {
84
- return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
85
- }
86
117
  async function resolveJwtPayload(token, jwtSecret) {
87
- const secret = jwtSecret || readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
88
- if (secret) {
89
- try {
90
- const { jwtVerify } = await import("jose");
91
- const key = new TextEncoder().encode(secret);
92
- const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
93
- return payload;
94
- } catch {
95
- return null;
96
- }
97
- }
98
- if (isStrictMode()) return null;
99
- return decodeJwtUnverified(token);
118
+ const verified = await verifyBearerToken(token, { jwtSecret });
119
+ return verified.ok ? verified.payload : null;
100
120
  }
101
121
  function getUserIdFromRequest(request, options) {
102
122
  const headerName = options?.headerName || "x-user-id";
@@ -46,13 +46,12 @@ declare function getUserIdFromRequest(request: Request, options?: {
46
46
  /**
47
47
  * Extract user email from a Bearer JWT in the Authorization header.
48
48
  *
49
- * When `SOLVAPAY_JWT_SECRET` or `SUPABASE_JWT_SECRET` is set (or the
50
- * equivalent `options.jwtSecret`), the token is verified via HS256.
51
- * Otherwise, the payload is decoded without verification — safe when the
52
- * platform gateway has already validated the token (e.g. Supabase Edge
53
- * Functions with `verify_jwt = true`, which is the default). Opt into
54
- * strict mode with `SOLVAPAY_AUTH_STRICT=true` to disable the
55
- * unverified-decode fallback.
49
+ * The token is verified first, as `verifyBearerToken` does: HS256 with
50
+ * `options.jwtSecret` / `SOLVAPAY_JWT_SECRET` / `SUPABASE_JWT_SECRET`,
51
+ * ES256/RS256 against `SOLVAPAY_JWKS_URL` (or Supabase's JWKS from
52
+ * `SUPABASE_URL`), or a gateway you trust with
53
+ * `SOLVAPAY_AUTH_TRUST_GATEWAY=true`. A token none of these verifies gives
54
+ * `null`.
56
55
  *
57
56
  * @param request - Request object (works with NextRequest from next/server)
58
57
  * @param options - Configuration options
@@ -46,13 +46,12 @@ declare function getUserIdFromRequest(request: Request, options?: {
46
46
  /**
47
47
  * Extract user email from a Bearer JWT in the Authorization header.
48
48
  *
49
- * When `SOLVAPAY_JWT_SECRET` or `SUPABASE_JWT_SECRET` is set (or the
50
- * equivalent `options.jwtSecret`), the token is verified via HS256.
51
- * Otherwise, the payload is decoded without verification — safe when the
52
- * platform gateway has already validated the token (e.g. Supabase Edge
53
- * Functions with `verify_jwt = true`, which is the default). Opt into
54
- * strict mode with `SOLVAPAY_AUTH_STRICT=true` to disable the
55
- * unverified-decode fallback.
49
+ * The token is verified first, as `verifyBearerToken` does: HS256 with
50
+ * `options.jwtSecret` / `SOLVAPAY_JWT_SECRET` / `SUPABASE_JWT_SECRET`,
51
+ * ES256/RS256 against `SOLVAPAY_JWKS_URL` (or Supabase's JWKS from
52
+ * `SUPABASE_URL`), or a gateway you trust with
53
+ * `SOLVAPAY_AUTH_TRUST_GATEWAY=true`. A token none of these verifies gives
54
+ * `null`.
56
55
  *
57
56
  * @param request - Request object (works with NextRequest from next/server)
58
57
  * @param options - Configuration options
@@ -3,7 +3,7 @@ import {
3
3
  getUserIdFromRequest,
4
4
  getUserNameFromRequest,
5
5
  requireUserId
6
- } from "./chunk-ESD73BSC.js";
6
+ } from "./chunk-WVD7RQ6U.js";
7
7
  export {
8
8
  getUserEmailFromRequest,
9
9
  getUserIdFromRequest,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solvapay/auth",
3
- "version": "1.2.0",
3
+ "version": "1.3.0-preview-3fd9d2205ea1113de9bbfb34ba7217629ccf40f0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -1,104 +0,0 @@
1
- // src/next-utils.ts
2
- function base64UrlDecode(input) {
3
- const padded = input.replace(/-/g, "+").replace(/_/g, "/");
4
- const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
5
- const base64 = padded + padding;
6
- if (typeof atob === "function") {
7
- const binary = atob(base64);
8
- let result = "";
9
- for (let i = 0; i < binary.length; i++) {
10
- result += String.fromCharCode(binary.charCodeAt(i));
11
- }
12
- try {
13
- return decodeURIComponent(escape(result));
14
- } catch {
15
- return result;
16
- }
17
- }
18
- const BufferCtor = globalThis.Buffer;
19
- if (BufferCtor) {
20
- return BufferCtor.from(base64, "base64").toString("utf-8");
21
- }
22
- throw new Error("No base64 decoder available in this runtime");
23
- }
24
- function decodeJwtUnverified(token) {
25
- const parts = token.split(".");
26
- if (parts.length !== 3) return null;
27
- try {
28
- const json = base64UrlDecode(parts[1]);
29
- const payload = JSON.parse(json);
30
- if (typeof payload !== "object" || payload === null) return null;
31
- return payload;
32
- } catch {
33
- return null;
34
- }
35
- }
36
- function extractBearerToken(request) {
37
- const authHeader = request.headers.get("authorization");
38
- if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
39
- const token = authHeader.slice(7).trim();
40
- return token.length > 0 ? token : null;
41
- }
42
- function readEnv(name) {
43
- const proc = globalThis.process;
44
- return proc?.env?.[name];
45
- }
46
- function isStrictMode() {
47
- return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
48
- }
49
- async function resolveJwtPayload(token, jwtSecret) {
50
- const secret = jwtSecret || readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
51
- if (secret) {
52
- try {
53
- const { jwtVerify } = await import("jose");
54
- const key = new TextEncoder().encode(secret);
55
- const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
56
- return payload;
57
- } catch {
58
- return null;
59
- }
60
- }
61
- if (isStrictMode()) return null;
62
- return decodeJwtUnverified(token);
63
- }
64
- function getUserIdFromRequest(request, options) {
65
- const headerName = options?.headerName || "x-user-id";
66
- return request.headers.get(headerName);
67
- }
68
- async function getUserEmailFromRequest(request, options) {
69
- const token = extractBearerToken(request);
70
- if (!token) return null;
71
- const payload = await resolveJwtPayload(token, options?.jwtSecret);
72
- if (!payload) return null;
73
- return typeof payload.email === "string" ? payload.email : null;
74
- }
75
- async function getUserNameFromRequest(request, options) {
76
- const token = extractBearerToken(request);
77
- if (!token) return null;
78
- const payload = await resolveJwtPayload(token, options?.jwtSecret);
79
- if (!payload) return null;
80
- const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
81
- const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
82
- const claimName = typeof payload.name === "string" ? payload.name : null;
83
- return metadataFullName || metadataName || claimName || null;
84
- }
85
- function requireUserId(request, options) {
86
- const headerName = options?.headerName || "x-user-id";
87
- const errorMessage = options?.errorMessage || "Unauthorized";
88
- const errorDetails = options?.errorDetails || "User ID not found. Ensure middleware is configured.";
89
- const userId = request.headers.get(headerName);
90
- if (!userId) {
91
- return new Response(JSON.stringify({ error: errorMessage, details: errorDetails }), {
92
- status: 401,
93
- headers: { "Content-Type": "application/json" }
94
- });
95
- }
96
- return userId;
97
- }
98
-
99
- export {
100
- getUserIdFromRequest,
101
- getUserEmailFromRequest,
102
- getUserNameFromRequest,
103
- requireUserId
104
- };