kroxt 1.2.0 β†’ 1.2.2

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/README.md CHANGED
@@ -1,177 +1,183 @@
1
- # kroxt πŸ”
2
-
3
- A framework-agnostic, modular authentication engine for modern TypeScript applications. Built for security, extensibility, and pure developer joy.
4
-
5
- [![npm version](https://img.shields.io/npm/v/kroxt.svg)](https://www.npmjs.com/package/kroxt)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
-
8
- ## πŸš€ Why Kroxt?
9
-
10
- Authentication is often either too complex (Passport, Auth.js) or too restrictive. Kroxt is the **"Headless" Auth Engine** that gives you the best of both worlds:
11
-
12
- - πŸ—οΈ **Database Agnostic**: Native adapters for **Prisma**, **Drizzle**, and **Mongoose**.
13
- - πŸ› οΈ **Modular**: Use only what you need. No forced session managers or UI components.
14
- - πŸ” **Security First**: Argon2 hashing, dual-token rotation, and timing-attack protection built-in.
15
- - 🧩 **TypeScript Native**: Perfectly preserves your user schemas and metadata.
16
-
17
- ---
18
-
19
- ## πŸ—ΊοΈ How it Works
20
-
21
- Kroxt sits between your database and your controller logic. It handles the "heavy lifting" (hashing, JWT signing, token rotation) while you maintain full control over your API.
22
-
23
- ```mermaid
24
- graph LR
25
- A[Client] -- "Credentials" --> B[Express/Fastify]
26
- B -- "auth.signup()" --> C[Kroxt Engine]
27
- C -- "Save User" --> D[(Database Adapter)]
28
- D -- "Success" --> C
29
- C -- "Tokens + User" --> B
30
- B -- "Set Cookies/JSON" --> A
31
-
32
- subgraph Adapters
33
- D
34
- E[Mongoose]
35
- F[Drizzle]
36
- G[Prisma]
37
- end
38
- ```
39
-
40
- ---
41
-
42
- ## 🏁 Quick Start (5 Minutes)
43
-
44
- ### 1. Installation
45
-
46
- ```bash
47
- npm install kroxt
48
- ```
49
-
50
- ### 2. Choose Your Adapter
51
-
52
- Kroxt provides official adapters for the most popular ORMs.
53
-
54
- #### **Option A: Drizzle (SQLite/PG/MySQL)**
55
- ```typescript
56
- import { createDrizzleAdapter } from "kroxt/adapters/drizzle";
57
- import { db } from "./db";
58
- import { users } from "./schema";
59
- import { eq } from "drizzle-orm";
60
-
61
- export const adapter = createDrizzleAdapter(db, users, eq);
62
- ```
63
-
64
- #### **Option B: Prisma**
65
- ```typescript
66
- import { createPrismaAdapter } from "kroxt/adapters/prisma";
67
- import { prisma } from "./db";
68
-
69
- export const adapter = createPrismaAdapter(prisma.user);
70
- ```
71
-
72
- #### **Option C: Mongoose**
73
- ```typescript
74
- import { createMongoAdapter } from "kroxt/adapters/mongoose";
75
- import { User } from "./models/user.model";
76
-
77
- export const adapter = createMongoAdapter(User);
78
- ```
79
-
80
- ### 3. Initialize the Engine
81
-
82
- ```typescript
83
- import { createAuth } from "kroxt/core";
84
- import { adapter } from "./auth-adapter";
85
-
86
- export const auth = createAuth({
87
- adapter,
88
- secret: process.env.JWT_SECRET,
89
- session: {
90
- expires: "15m",
91
- refreshExpires: "7d"
92
- }
93
- });
94
- ```
95
-
96
- ---
97
-
98
- ## πŸ›‘οΈ Core Authentication Flows
99
-
100
- ### **Registration**
101
- ```typescript
102
- const { user, accessToken, refreshToken } = await auth.signup({
103
- email,
104
- name,
105
- role: 'user'
106
- }, password);
107
- ```
108
-
109
- ### **Login**
110
- ```typescript
111
- const { user, accessToken, refreshToken } = await auth.loginWithPassword(email, password);
112
- ```
113
-
114
- ### **Token Refresh**
115
- ```typescript
116
- const { accessToken } = await auth.refresh(refreshToken);
117
- ```
118
-
119
- ---
120
-
121
- ## 🐣 Beginner Corner: What is "Headless"?
122
-
123
- If you're new to backend development, "Headless" means Kroxt **doesn't provide a UI** (no login buttons or pre-made forms). Instead, it provides the **engine** (the logic).
124
-
125
- **Why is this good?**
126
- It means you can build your own login screen in React, Vue, or even a Mobile App, and Kroxt will handle the security part on the server exactly the same way every time.
127
-
128
- ---
129
-
130
- ## πŸ› οΈ Advanced: Custom JWT Payloads
131
-
132
- Want to share a user's `role` or `plan` with the frontend via the JWT? Use the `payload` hook:
133
-
134
- ```typescript
135
- export const auth = createAuth({
136
- adapter,
137
- jwt: {
138
- payload: (user, type) => {
139
- if (type === "access") {
140
- return {
141
- role: user.role,
142
- tier: user.subscriptionTier
143
- };
144
- }
145
- return {}; // Keep refresh tokens small
146
- }
147
- }
148
- });
149
- ```
150
-
151
- ---
152
-
153
- ## πŸ›‘ Common v1.2.0 Troubleshooting
154
-
155
- ### "Cannot find module '.prisma/client/default.js'" (ESM on Windows)
156
- If you're using Prisma with ESM (`"type": "module"`) on Windows, you may need a robust import in your `src/db/index.ts`:
157
-
158
- ```typescript
159
- import { createRequire } from "module";
160
- const require = createRequire(import.meta.url);
161
- const { PrismaClient } = require("@prisma/client");
162
- ```
163
-
164
- ### Prisma "Unknown argument `name`"
165
- Prisma is strict about schemas. If you're sending extra fields in `auth.signup()`, ensure your `schema.prisma` includes them (marked as `?` optional if needed).
166
-
167
- ---
168
-
169
- ## πŸ”— Reference Project
170
-
171
- Complete working implementations:
172
- - [Express + Drizzle + SQLite](https://github.com/adepoju-oluwatobi/kroxt-example)
173
- - [Express + Mongoose](https://github.com/adepoju-oluwatobi/kroxt-example-cjs)
174
-
175
- ## πŸ“„ License
176
-
177
- MIT Β© [Adepoju Oluwatobi](https://github.com/adepoju-oluwatobi)
1
+ # kroxt πŸ”
2
+
3
+ A framework-agnostic, modular authentication engine for modern TypeScript applications. Built for security, extensibility, and pure developer joy.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/kroxt.svg)](https://www.npmjs.com/package/kroxt)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ [![Security Policy](https://img.shields.io/badge/Security-Policy-brightgreen.svg)](./SECURITY.md)
8
+
9
+ > [!IMPORTANT]
10
+ > **What's New in v1.2.2**: Hardened CSRF protection (Regex + Hex comparisons), official Security Policy (`SECURITY.md`), standalone security examples, and full TypeScript support for all framework samples. [Read the full Changelog](./CHANGELOG.md).
11
+
12
+ ## πŸš€ Why Kroxt?
13
+
14
+ Authentication is often either too complex (Passport, Auth.js) or too restrictive. Kroxt is the **"Headless" Auth Engine** that gives you the best of both worlds:
15
+
16
+ - πŸ—οΈ **Database Agnostic**: Native adapters for **Prisma**, **Drizzle**, and **Mongoose**.
17
+ - πŸ› οΈ **Modular**: Use only what you need. No forced session managers or UI components.
18
+ - πŸ” **Security First**: Argon2 hashing, dual-token rotation, and timing-attack protection built-in.
19
+ - 🧩 **TypeScript Native**: Perfectly preserves your user schemas and metadata.
20
+
21
+ ---
22
+
23
+ ## πŸ—ΊοΈ How it Works
24
+
25
+ Kroxt sits between your database and your controller logic. It handles the "heavy lifting" (hashing, JWT signing, token rotation) while you maintain full control over your API.
26
+
27
+ ```mermaid
28
+ graph LR
29
+ A[Client] -- "Credentials" --> B[Express/Fastify]
30
+ B -- "auth.signup()" --> C[Kroxt Engine]
31
+ C -- "Save User" --> D[(Database Adapter)]
32
+ D -- "Success" --> C
33
+ C -- "Tokens + User" --> B
34
+ B -- "Set Cookies/JSON" --> A
35
+
36
+ subgraph Adapters
37
+ D
38
+ E[Mongoose]
39
+ F[Drizzle]
40
+ G[Prisma]
41
+ end
42
+ ```
43
+
44
+ ---
45
+
46
+ ## 🏁 Quick Start (5 Minutes)
47
+
48
+ ### 1. Installation
49
+
50
+ ```bash
51
+ npm install kroxt
52
+ ```
53
+
54
+ ### 2. Choose Your Adapter
55
+
56
+ Kroxt provides official adapters for the most popular ORMs.
57
+
58
+ #### **Option A: Drizzle (SQLite/PG/MySQL)**
59
+ ```typescript
60
+ import { createDrizzleAdapter } from "kroxt/adapters/drizzle";
61
+ import { db } from "./db";
62
+ import { users } from "./schema";
63
+ import { eq } from "drizzle-orm";
64
+
65
+ export const adapter = createDrizzleAdapter(db, users, eq);
66
+ ```
67
+
68
+ #### **Option B: Prisma**
69
+ ```typescript
70
+ import { createPrismaAdapter } from "kroxt/adapters/prisma";
71
+ import { prisma } from "./db";
72
+
73
+ export const adapter = createPrismaAdapter(prisma.user);
74
+ ```
75
+
76
+ #### **Option C: Mongoose**
77
+ ```typescript
78
+ import { createMongoAdapter } from "kroxt/adapters/mongoose";
79
+ import { User } from "./models/user.model";
80
+
81
+ export const adapter = createMongoAdapter(User);
82
+ ```
83
+
84
+ ### 3. Initialize the Engine
85
+
86
+ ```typescript
87
+ import { createAuth } from "kroxt/core";
88
+ import { adapter } from "./auth-adapter";
89
+
90
+ export const auth = createAuth({
91
+ adapter,
92
+ secret: process.env.JWT_SECRET,
93
+ session: {
94
+ expires: "15m",
95
+ refreshExpires: "7d"
96
+ }
97
+ });
98
+ ```
99
+
100
+ ---
101
+
102
+ ## πŸ›‘οΈ Core Authentication Flows
103
+
104
+ ### **Registration**
105
+ ```typescript
106
+ const { user, accessToken, refreshToken } = await auth.signup({
107
+ email,
108
+ name,
109
+ role: 'user'
110
+ }, password);
111
+ ```
112
+
113
+ ### **Login**
114
+ ```typescript
115
+ const { user, accessToken, refreshToken } = await auth.loginWithPassword(email, password);
116
+ ```
117
+
118
+ ### **Token Refresh**
119
+ ```typescript
120
+ const { accessToken } = await auth.refresh(refreshToken);
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 🐣 Beginner Corner: What is "Headless"?
126
+
127
+ If you're new to backend development, "Headless" means Kroxt **doesn't provide a UI** (no login buttons or pre-made forms). Instead, it provides the **engine** (the logic).
128
+
129
+ **Why is this good?**
130
+ It means you can build your own login screen in React, Vue, or even a Mobile App, and Kroxt will handle the security part on the server exactly the same way every time.
131
+
132
+ ---
133
+
134
+ ## πŸ› οΈ Advanced: Custom JWT Payloads
135
+
136
+ Want to share a user's `role` or `plan` with the frontend via the JWT? Use the `payload` hook:
137
+
138
+ ```typescript
139
+ export const auth = createAuth({
140
+ adapter,
141
+ jwt: {
142
+ payload: (user, type) => {
143
+ if (type === "access") {
144
+ return {
145
+ role: user.role,
146
+ tier: user.subscriptionTier
147
+ };
148
+ }
149
+ return {}; // Keep refresh tokens small
150
+ }
151
+ }
152
+ });
153
+ ```
154
+
155
+ ---
156
+
157
+ ## πŸ›‘ Common v1.2.0 Troubleshooting
158
+
159
+ ### "Cannot find module '.prisma/client/default.js'" (ESM on Windows)
160
+ If you're using Prisma with ESM (`"type": "module"`) on Windows, you may need a robust import in your `src/db/index.ts`:
161
+
162
+ ```typescript
163
+ import { createRequire } from "module";
164
+ const require = createRequire(import.meta.url);
165
+ const { PrismaClient } = require("@prisma/client");
166
+ ```
167
+
168
+ ### Prisma "Unknown argument `name`"
169
+ Prisma is strict about schemas. If you're sending extra fields in `auth.signup()`, ensure your `schema.prisma` includes them (marked as `?` optional if needed).
170
+
171
+ ---
172
+
173
+ ## πŸ”— Reference Projects
174
+
175
+ Complete working implementations for all frameworks:
176
+ - [Kroxt Examples (All Frameworks)](https://github.com/adepoju-oluwatobi/kroxt-examples)
177
+ - [Hono + Drizzle + SQLite](https://github.com/adepoju-oluwatobi/kroxt-examples/tree/main/hono/kroxt-hono-drizzle)
178
+ - [Express + Prisma + DB](https://github.com/adepoju-oluwatobi/kroxt-examples/tree/main/express/kroxt-express-prisma)
179
+ - [Fastify + Mongoose](https://github.com/adepoju-oluwatobi/kroxt-examples/tree/main/fastify/kroxt-fastify-mongo)
180
+
181
+ ## πŸ“„ License
182
+
183
+ MIT Β© [Adepoju Oluwatobi](https://github.com/adepoju-oluwatobi)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/drizzle.ts"],
4
- "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\n\n/**\n * Creates a Drizzle ORM adapter.\n * \n * Works with any Drizzle-supported database (PostgreSQL, MySQL, SQLite)\n * by using the standard drizzle-orm `db` instance and table definition.\n * \n * @param db - The Drizzle database instance.\n * @param table - The Drizzle table representing users.\n * @param eq - The Drizzle `eq` operator (imported from `drizzle-orm`).\n * @returns An AuthAdapter compliant object.\n */\nexport function createDrizzleAdapter<TUser extends User = User>(\n db: any,\n table: any,\n eq: any\n): AuthAdapter<TUser> {\n return {\n async createUser(data: any) {\n const dataToSave = { id: data.id || globalThis.crypto.randomUUID(), ...data };\n const results = await db.insert(table).values(dataToSave).returning();\n return results[0] as TUser;\n },\n\n async findUserByEmail(email: string) {\n const results = await db.select().from(table).where(eq(table.email, email)).limit(1);\n return (results[0] || null) as TUser | null;\n },\n\n async findUserById(id: string) {\n const results = await db.select().from(table).where(eq(table.id, id)).limit(1);\n return (results[0] || null) as TUser | null;\n },\n\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\n await db.update(table)\n .set({\n oauthProvider: provider,\n oauthId: providerId,\n })\n .where(eq(table.id, userId));\n },\n };\n}\n"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\r\n\r\n/**\r\n * Creates a Drizzle ORM adapter.\r\n * \r\n * Works with any Drizzle-supported database (PostgreSQL, MySQL, SQLite)\r\n * by using the standard drizzle-orm `db` instance and table definition.\r\n * \r\n * @param db - The Drizzle database instance.\r\n * @param table - The Drizzle table representing users.\r\n * @param eq - The Drizzle `eq` operator (imported from `drizzle-orm`).\r\n * @returns An AuthAdapter compliant object.\r\n */\r\nexport function createDrizzleAdapter<TUser extends User = User>(\r\n db: any,\r\n table: any,\r\n eq: any\r\n): AuthAdapter<TUser> {\r\n return {\r\n async createUser(data: any) {\r\n const dataToSave = { id: data.id || globalThis.crypto.randomUUID(), ...data };\r\n const results = await db.insert(table).values(dataToSave).returning();\r\n return results[0] as TUser;\r\n },\r\n\r\n async findUserByEmail(email: string) {\r\n const results = await db.select().from(table).where(eq(table.email, email)).limit(1);\r\n return (results[0] || null) as TUser | null;\r\n },\r\n\r\n async findUserById(id: string) {\r\n const results = await db.select().from(table).where(eq(table.id, id)).limit(1);\r\n return (results[0] || null) as TUser | null;\r\n },\r\n\r\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\r\n await db.update(table)\r\n .set({\r\n oauthProvider: provider,\r\n oauthId: providerId,\r\n })\r\n .where(eq(table.id, userId));\r\n },\r\n };\r\n}\r\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAaO,SAAS,qBACd,IACA,OACA,IACoB;AACpB,SAAO;AAAA,IACL,MAAM,WAAW,MAAW;AAC1B,YAAM,aAAa,EAAE,IAAI,KAAK,MAAM,WAAW,OAAO,WAAW,GAAG,GAAG,KAAK;AAC5E,YAAM,UAAU,MAAM,GAAG,OAAO,KAAK,EAAE,OAAO,UAAU,EAAE,UAAU;AACpE,aAAO,QAAQ,CAAC;AAAA,IAClB;AAAA,IAEA,MAAM,gBAAgB,OAAe;AACnC,YAAM,UAAU,MAAM,GAAG,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,GAAG,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,CAAC;AACnF,aAAQ,QAAQ,CAAC,KAAK;AAAA,IACxB;AAAA,IAEA,MAAM,aAAa,IAAY;AAC7B,YAAM,UAAU,MAAM,GAAG,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC;AAC7E,aAAQ,QAAQ,CAAC,KAAK;AAAA,IACxB;AAAA,IAEA,MAAM,iBAAiB,QAAgB,UAAkB,YAAoB;AAC3E,YAAM,GAAG,OAAO,KAAK,EAClB,IAAI;AAAA,QACH,eAAe;AAAA,QACf,SAAS;AAAA,MACX,CAAC,EACA,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC;AAAA,IAC/B;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/drizzle.ts"],
4
- "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\n\n/**\n * Creates a Drizzle ORM adapter.\n * \n * Works with any Drizzle-supported database (PostgreSQL, MySQL, SQLite)\n * by using the standard drizzle-orm `db` instance and table definition.\n * \n * @param db - The Drizzle database instance.\n * @param table - The Drizzle table representing users.\n * @param eq - The Drizzle `eq` operator (imported from `drizzle-orm`).\n * @returns An AuthAdapter compliant object.\n */\nexport function createDrizzleAdapter<TUser extends User = User>(\n db: any,\n table: any,\n eq: any\n): AuthAdapter<TUser> {\n return {\n async createUser(data: any) {\n const dataToSave = { id: data.id || globalThis.crypto.randomUUID(), ...data };\n const results = await db.insert(table).values(dataToSave).returning();\n return results[0] as TUser;\n },\n\n async findUserByEmail(email: string) {\n const results = await db.select().from(table).where(eq(table.email, email)).limit(1);\n return (results[0] || null) as TUser | null;\n },\n\n async findUserById(id: string) {\n const results = await db.select().from(table).where(eq(table.id, id)).limit(1);\n return (results[0] || null) as TUser | null;\n },\n\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\n await db.update(table)\n .set({\n oauthProvider: provider,\n oauthId: providerId,\n })\n .where(eq(table.id, userId));\n },\n };\n}\n"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\r\n\r\n/**\r\n * Creates a Drizzle ORM adapter.\r\n * \r\n * Works with any Drizzle-supported database (PostgreSQL, MySQL, SQLite)\r\n * by using the standard drizzle-orm `db` instance and table definition.\r\n * \r\n * @param db - The Drizzle database instance.\r\n * @param table - The Drizzle table representing users.\r\n * @param eq - The Drizzle `eq` operator (imported from `drizzle-orm`).\r\n * @returns An AuthAdapter compliant object.\r\n */\r\nexport function createDrizzleAdapter<TUser extends User = User>(\r\n db: any,\r\n table: any,\r\n eq: any\r\n): AuthAdapter<TUser> {\r\n return {\r\n async createUser(data: any) {\r\n const dataToSave = { id: data.id || globalThis.crypto.randomUUID(), ...data };\r\n const results = await db.insert(table).values(dataToSave).returning();\r\n return results[0] as TUser;\r\n },\r\n\r\n async findUserByEmail(email: string) {\r\n const results = await db.select().from(table).where(eq(table.email, email)).limit(1);\r\n return (results[0] || null) as TUser | null;\r\n },\r\n\r\n async findUserById(id: string) {\r\n const results = await db.select().from(table).where(eq(table.id, id)).limit(1);\r\n return (results[0] || null) as TUser | null;\r\n },\r\n\r\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\r\n await db.update(table)\r\n .set({\r\n oauthProvider: provider,\r\n oauthId: providerId,\r\n })\r\n .where(eq(table.id, userId));\r\n },\r\n };\r\n}\r\n"],
5
5
  "mappings": "AAaO,SAAS,qBACd,IACA,OACA,IACoB;AACpB,SAAO;AAAA,IACL,MAAM,WAAW,MAAW;AAC1B,YAAM,aAAa,EAAE,IAAI,KAAK,MAAM,WAAW,OAAO,WAAW,GAAG,GAAG,KAAK;AAC5E,YAAM,UAAU,MAAM,GAAG,OAAO,KAAK,EAAE,OAAO,UAAU,EAAE,UAAU;AACpE,aAAO,QAAQ,CAAC;AAAA,IAClB;AAAA,IAEA,MAAM,gBAAgB,OAAe;AACnC,YAAM,UAAU,MAAM,GAAG,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,GAAG,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,CAAC;AACnF,aAAQ,QAAQ,CAAC,KAAK;AAAA,IACxB;AAAA,IAEA,MAAM,aAAa,IAAY;AAC7B,YAAM,UAAU,MAAM,GAAG,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC;AAC7E,aAAQ,QAAQ,CAAC,KAAK;AAAA,IACxB;AAAA,IAEA,MAAM,iBAAiB,QAAgB,UAAkB,YAAoB;AAC3E,YAAM,GAAG,OAAO,KAAK,EAClB,IAAI;AAAA,QACH,eAAe;AAAA,QACf,SAAS;AAAA,MACX,CAAC,EACA,MAAM,GAAG,MAAM,IAAI,MAAM,CAAC;AAAA,IAC/B;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/index.ts"],
4
- "sourcesContent": ["export interface BaseUser {\n id: string;\n email: string;\n passwordHash?: string;\n role?: string;\n}\n\n// Allows any extended fields natively (like nin, bvn, maritalStatus, etc.)\nexport type User<TExtended = Record<string, any>> = BaseUser & TExtended;\n\nexport interface AuthAdapter<TUser = User> {\n createUser: (data: any) => Promise<TUser>;\n findUserByEmail: (email: string) => Promise<TUser | null>;\n findUserById: (id: string) => Promise<TUser | null>;\n linkOAuthAccount: (userId: string, provider: string, providerId: string) => Promise<void>;\n}\n"],
4
+ "sourcesContent": ["export interface BaseUser {\r\n id: string;\r\n email: string;\r\n passwordHash?: string;\r\n role?: string;\r\n}\r\n\r\n// Allows any extended fields natively (like nin, bvn, maritalStatus, etc.)\r\nexport type User<TExtended = Record<string, any>> = BaseUser & TExtended;\r\n\r\nexport interface AuthAdapter<TUser = User> {\r\n createUser: (data: any) => Promise<TUser>;\r\n findUserByEmail: (email: string) => Promise<TUser | null>;\r\n findUserById: (id: string) => Promise<TUser | null>;\r\n linkOAuthAccount: (userId: string, provider: string, providerId: string) => Promise<void>;\r\n}\r\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/memory.ts"],
4
- "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\n\n/**\n * Creates an in-memory database adapter for the auth engine.\n * This is useful for testing, prototyping, or when you don't need persistent storage.\n * All data is kept in memory and is lost when the server restarts.\n */\nexport function createMemoryAdapter<TUser extends User = User>(): AuthAdapter<TUser> {\n const users = new Map<string, TUser>();\n const accounts = new Map<string, { userId: string; provider: string; providerId: string }>();\n\n return {\n createUser: async (data: any) => {\n // Auto-generate ID if not provided\n const id = data.id || Date.now().toString();\n const newUser = { ...data, id } as TUser;\n\n // Store using email as the primary lookup key\n users.set(newUser.email, newUser);\n return newUser;\n },\n\n findUserByEmail: async (email: string) => {\n return users.get(email) || null;\n },\n\n findUserById: async (id: string) => {\n for (const user of users.values()) {\n if (user.id === id) {\n return user;\n }\n }\n return null;\n },\n\n linkOAuthAccount: async (userId: string, provider: string, providerId: string) => {\n const accountId = `${provider}_${providerId}`;\n accounts.set(accountId, { userId, provider, providerId });\n }\n };\n}\n"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\r\n\r\n/**\r\n * Creates an in-memory database adapter for the auth engine.\r\n * This is useful for testing, prototyping, or when you don't need persistent storage.\r\n * All data is kept in memory and is lost when the server restarts.\r\n */\r\nexport function createMemoryAdapter<TUser extends User = User>(): AuthAdapter<TUser> {\r\n const users = new Map<string, TUser>();\r\n const accounts = new Map<string, { userId: string; provider: string; providerId: string }>();\r\n\r\n return {\r\n createUser: async (data: any) => {\r\n // Auto-generate ID if not provided\r\n const id = data.id || Date.now().toString();\r\n const newUser = { ...data, id } as TUser;\r\n\r\n // Store using email as the primary lookup key\r\n users.set(newUser.email, newUser);\r\n return newUser;\r\n },\r\n\r\n findUserByEmail: async (email: string) => {\r\n return users.get(email) || null;\r\n },\r\n\r\n findUserById: async (id: string) => {\r\n for (const user of users.values()) {\r\n if (user.id === id) {\r\n return user;\r\n }\r\n }\r\n return null;\r\n },\r\n\r\n linkOAuthAccount: async (userId: string, provider: string, providerId: string) => {\r\n const accountId = `${provider}_${providerId}`;\r\n accounts.set(accountId, { userId, provider, providerId });\r\n }\r\n };\r\n}\r\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOO,SAAS,sBAAqE;AACjF,QAAM,QAAQ,oBAAI,IAAmB;AACrC,QAAM,WAAW,oBAAI,IAAsE;AAE3F,SAAO;AAAA,IACH,YAAY,OAAO,SAAc;AAE7B,YAAM,KAAK,KAAK,MAAM,KAAK,IAAI,EAAE,SAAS;AAC1C,YAAM,UAAU,EAAE,GAAG,MAAM,GAAG;AAG9B,YAAM,IAAI,QAAQ,OAAO,OAAO;AAChC,aAAO;AAAA,IACX;AAAA,IAEA,iBAAiB,OAAO,UAAkB;AACtC,aAAO,MAAM,IAAI,KAAK,KAAK;AAAA,IAC/B;AAAA,IAEA,cAAc,OAAO,OAAe;AAChC,iBAAW,QAAQ,MAAM,OAAO,GAAG;AAC/B,YAAI,KAAK,OAAO,IAAI;AAChB,iBAAO;AAAA,QACX;AAAA,MACJ;AACA,aAAO;AAAA,IACX;AAAA,IAEA,kBAAkB,OAAO,QAAgB,UAAkB,eAAuB;AAC9E,YAAM,YAAY,GAAG,QAAQ,IAAI,UAAU;AAC3C,eAAS,IAAI,WAAW,EAAE,QAAQ,UAAU,WAAW,CAAC;AAAA,IAC5D;AAAA,EACJ;AACJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/memory.ts"],
4
- "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\n\n/**\n * Creates an in-memory database adapter for the auth engine.\n * This is useful for testing, prototyping, or when you don't need persistent storage.\n * All data is kept in memory and is lost when the server restarts.\n */\nexport function createMemoryAdapter<TUser extends User = User>(): AuthAdapter<TUser> {\n const users = new Map<string, TUser>();\n const accounts = new Map<string, { userId: string; provider: string; providerId: string }>();\n\n return {\n createUser: async (data: any) => {\n // Auto-generate ID if not provided\n const id = data.id || Date.now().toString();\n const newUser = { ...data, id } as TUser;\n\n // Store using email as the primary lookup key\n users.set(newUser.email, newUser);\n return newUser;\n },\n\n findUserByEmail: async (email: string) => {\n return users.get(email) || null;\n },\n\n findUserById: async (id: string) => {\n for (const user of users.values()) {\n if (user.id === id) {\n return user;\n }\n }\n return null;\n },\n\n linkOAuthAccount: async (userId: string, provider: string, providerId: string) => {\n const accountId = `${provider}_${providerId}`;\n accounts.set(accountId, { userId, provider, providerId });\n }\n };\n}\n"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\r\n\r\n/**\r\n * Creates an in-memory database adapter for the auth engine.\r\n * This is useful for testing, prototyping, or when you don't need persistent storage.\r\n * All data is kept in memory and is lost when the server restarts.\r\n */\r\nexport function createMemoryAdapter<TUser extends User = User>(): AuthAdapter<TUser> {\r\n const users = new Map<string, TUser>();\r\n const accounts = new Map<string, { userId: string; provider: string; providerId: string }>();\r\n\r\n return {\r\n createUser: async (data: any) => {\r\n // Auto-generate ID if not provided\r\n const id = data.id || Date.now().toString();\r\n const newUser = { ...data, id } as TUser;\r\n\r\n // Store using email as the primary lookup key\r\n users.set(newUser.email, newUser);\r\n return newUser;\r\n },\r\n\r\n findUserByEmail: async (email: string) => {\r\n return users.get(email) || null;\r\n },\r\n\r\n findUserById: async (id: string) => {\r\n for (const user of users.values()) {\r\n if (user.id === id) {\r\n return user;\r\n }\r\n }\r\n return null;\r\n },\r\n\r\n linkOAuthAccount: async (userId: string, provider: string, providerId: string) => {\r\n const accountId = `${provider}_${providerId}`;\r\n accounts.set(accountId, { userId, provider, providerId });\r\n }\r\n };\r\n}\r\n"],
5
5
  "mappings": "AAOO,SAAS,sBAAqE;AACjF,QAAM,QAAQ,oBAAI,IAAmB;AACrC,QAAM,WAAW,oBAAI,IAAsE;AAE3F,SAAO;AAAA,IACH,YAAY,OAAO,SAAc;AAE7B,YAAM,KAAK,KAAK,MAAM,KAAK,IAAI,EAAE,SAAS;AAC1C,YAAM,UAAU,EAAE,GAAG,MAAM,GAAG;AAG9B,YAAM,IAAI,QAAQ,OAAO,OAAO;AAChC,aAAO;AAAA,IACX;AAAA,IAEA,iBAAiB,OAAO,UAAkB;AACtC,aAAO,MAAM,IAAI,KAAK,KAAK;AAAA,IAC/B;AAAA,IAEA,cAAc,OAAO,OAAe;AAChC,iBAAW,QAAQ,MAAM,OAAO,GAAG;AAC/B,YAAI,KAAK,OAAO,IAAI;AAChB,iBAAO;AAAA,QACX;AAAA,MACJ;AACA,aAAO;AAAA,IACX;AAAA,IAEA,kBAAkB,OAAO,QAAgB,UAAkB,eAAuB;AAC9E,YAAM,YAAY,GAAG,QAAQ,IAAI,UAAU;AAC3C,eAAS,IAAI,WAAW,EAAE,QAAQ,UAAU,WAAW,CAAC;AAAA,IAC5D;AAAA,EACJ;AACJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/mongoose.ts"],
4
- "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\n\n/**\n * Creates a MongoDB adapter using a Mongoose model.\n * \n * @param model - A Mongoose model instance (e.g., User model).\n * @returns An AuthAdapter compliant object.\n */\nexport function createMongoAdapter<TUser extends User = User>(model: any): AuthAdapter<TUser> {\n return {\n async createUser(data: any) {\n const user = await model.create(data);\n const obj = user.toObject();\n return { ...obj, id: obj._id.toString() } as TUser;\n },\n\n async findUserByEmail(email: string) {\n const user = await model.findOne({ email });\n if (!user) return null;\n const obj = user.toObject();\n return { ...obj, id: obj._id.toString() } as TUser;\n },\n\n async findUserById(id: string) {\n const user = await model.findById(id);\n if (!user) return null;\n const obj = user.toObject();\n return { ...obj, id: obj._id.toString() } as TUser;\n },\n\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\n await model.findByIdAndUpdate(userId, {\n oauthProvider: provider,\n oauthId: providerId,\n });\n },\n };\n}\n"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\r\n\r\n/**\r\n * Creates a MongoDB adapter using a Mongoose model.\r\n * \r\n * @param model - A Mongoose model instance (e.g., User model).\r\n * @returns An AuthAdapter compliant object.\r\n */\r\nexport function createMongoAdapter<TUser extends User = User>(model: any): AuthAdapter<TUser> {\r\n return {\r\n async createUser(data: any) {\r\n const user = await model.create(data);\r\n const obj = user.toObject();\r\n return { ...obj, id: obj._id.toString() } as TUser;\r\n },\r\n\r\n async findUserByEmail(email: string) {\r\n const user = await model.findOne({ email });\r\n if (!user) return null;\r\n const obj = user.toObject();\r\n return { ...obj, id: obj._id.toString() } as TUser;\r\n },\r\n\r\n async findUserById(id: string) {\r\n const user = await model.findById(id);\r\n if (!user) return null;\r\n const obj = user.toObject();\r\n return { ...obj, id: obj._id.toString() } as TUser;\r\n },\r\n\r\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\r\n await model.findByIdAndUpdate(userId, {\r\n oauthProvider: provider,\r\n oauthId: providerId,\r\n });\r\n },\r\n };\r\n}\r\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQO,SAAS,mBAA8C,OAAgC;AAC5F,SAAO;AAAA,IACL,MAAM,WAAW,MAAW;AAC1B,YAAM,OAAO,MAAM,MAAM,OAAO,IAAI;AACpC,YAAM,MAAM,KAAK,SAAS;AAC1B,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,IAAI,SAAS,EAAE;AAAA,IAC1C;AAAA,IAEA,MAAM,gBAAgB,OAAe;AACnC,YAAM,OAAO,MAAM,MAAM,QAAQ,EAAE,MAAM,CAAC;AAC1C,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,MAAM,KAAK,SAAS;AAC1B,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,IAAI,SAAS,EAAE;AAAA,IAC1C;AAAA,IAEA,MAAM,aAAa,IAAY;AAC7B,YAAM,OAAO,MAAM,MAAM,SAAS,EAAE;AACpC,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,MAAM,KAAK,SAAS;AAC1B,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,IAAI,SAAS,EAAE;AAAA,IAC1C;AAAA,IAEA,MAAM,iBAAiB,QAAgB,UAAkB,YAAoB;AAC3E,YAAM,MAAM,kBAAkB,QAAQ;AAAA,QACpC,eAAe;AAAA,QACf,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/mongoose.ts"],
4
- "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\n\n/**\n * Creates a MongoDB adapter using a Mongoose model.\n * \n * @param model - A Mongoose model instance (e.g., User model).\n * @returns An AuthAdapter compliant object.\n */\nexport function createMongoAdapter<TUser extends User = User>(model: any): AuthAdapter<TUser> {\n return {\n async createUser(data: any) {\n const user = await model.create(data);\n const obj = user.toObject();\n return { ...obj, id: obj._id.toString() } as TUser;\n },\n\n async findUserByEmail(email: string) {\n const user = await model.findOne({ email });\n if (!user) return null;\n const obj = user.toObject();\n return { ...obj, id: obj._id.toString() } as TUser;\n },\n\n async findUserById(id: string) {\n const user = await model.findById(id);\n if (!user) return null;\n const obj = user.toObject();\n return { ...obj, id: obj._id.toString() } as TUser;\n },\n\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\n await model.findByIdAndUpdate(userId, {\n oauthProvider: provider,\n oauthId: providerId,\n });\n },\n };\n}\n"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\r\n\r\n/**\r\n * Creates a MongoDB adapter using a Mongoose model.\r\n * \r\n * @param model - A Mongoose model instance (e.g., User model).\r\n * @returns An AuthAdapter compliant object.\r\n */\r\nexport function createMongoAdapter<TUser extends User = User>(model: any): AuthAdapter<TUser> {\r\n return {\r\n async createUser(data: any) {\r\n const user = await model.create(data);\r\n const obj = user.toObject();\r\n return { ...obj, id: obj._id.toString() } as TUser;\r\n },\r\n\r\n async findUserByEmail(email: string) {\r\n const user = await model.findOne({ email });\r\n if (!user) return null;\r\n const obj = user.toObject();\r\n return { ...obj, id: obj._id.toString() } as TUser;\r\n },\r\n\r\n async findUserById(id: string) {\r\n const user = await model.findById(id);\r\n if (!user) return null;\r\n const obj = user.toObject();\r\n return { ...obj, id: obj._id.toString() } as TUser;\r\n },\r\n\r\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\r\n await model.findByIdAndUpdate(userId, {\r\n oauthProvider: provider,\r\n oauthId: providerId,\r\n });\r\n },\r\n };\r\n}\r\n"],
5
5
  "mappings": "AAQO,SAAS,mBAA8C,OAAgC;AAC5F,SAAO;AAAA,IACL,MAAM,WAAW,MAAW;AAC1B,YAAM,OAAO,MAAM,MAAM,OAAO,IAAI;AACpC,YAAM,MAAM,KAAK,SAAS;AAC1B,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,IAAI,SAAS,EAAE;AAAA,IAC1C;AAAA,IAEA,MAAM,gBAAgB,OAAe;AACnC,YAAM,OAAO,MAAM,MAAM,QAAQ,EAAE,MAAM,CAAC;AAC1C,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,MAAM,KAAK,SAAS;AAC1B,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,IAAI,SAAS,EAAE;AAAA,IAC1C;AAAA,IAEA,MAAM,aAAa,IAAY;AAC7B,YAAM,OAAO,MAAM,MAAM,SAAS,EAAE;AACpC,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,MAAM,KAAK,SAAS;AAC1B,aAAO,EAAE,GAAG,KAAK,IAAI,IAAI,IAAI,SAAS,EAAE;AAAA,IAC1C;AAAA,IAEA,MAAM,iBAAiB,QAAgB,UAAkB,YAAoB;AAC3E,YAAM,MAAM,kBAAkB,QAAQ;AAAA,QACpC,eAAe;AAAA,QACf,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/prisma.ts"],
4
- "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\n\n/**\n * Creates a Prisma adapter using a Prisma delegate (e.g., prisma.user).\n * \n * Works with any Prisma-supported database by using the standard\n * Prisma delegate operations (findUnique, create, update).\n * \n * @param model - A Prisma delegate instance (e.g., prisma.user).\n * @returns An AuthAdapter compliant object.\n */\nexport function createPrismaAdapter<TUser extends User = User>(model: any): AuthAdapter<TUser> {\n return {\n async createUser(data: any) {\n const dataToSave = { id: data.id || globalThis.crypto.randomUUID(), ...data };\n const user = await model.create({ data: dataToSave });\n return user as TUser;\n },\n\n async findUserByEmail(email: string) {\n const user = await model.findUnique({\n where: { email },\n });\n return user as TUser | null;\n },\n\n async findUserById(id: string) {\n const user = await model.findUnique({\n where: { id },\n });\n return user as TUser | null;\n },\n\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\n await model.update({\n where: { id: userId },\n data: {\n oauthProvider: provider,\n oauthId: providerId,\n },\n });\n },\n };\n}\n"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\r\n\r\n/**\r\n * Creates a Prisma adapter using a Prisma delegate (e.g., prisma.user).\r\n * \r\n * Works with any Prisma-supported database by using the standard\r\n * Prisma delegate operations (findUnique, create, update).\r\n * \r\n * @param model - A Prisma delegate instance (e.g., prisma.user).\r\n * @returns An AuthAdapter compliant object.\r\n */\r\nexport function createPrismaAdapter<TUser extends User = User>(model: any): AuthAdapter<TUser> {\r\n return {\r\n async createUser(data: any) {\r\n const dataToSave = { id: data.id || globalThis.crypto.randomUUID(), ...data };\r\n const user = await model.create({ data: dataToSave });\r\n return user as TUser;\r\n },\r\n\r\n async findUserByEmail(email: string) {\r\n const user = await model.findUnique({\r\n where: { email },\r\n });\r\n return user as TUser | null;\r\n },\r\n\r\n async findUserById(id: string) {\r\n const user = await model.findUnique({\r\n where: { id },\r\n });\r\n return user as TUser | null;\r\n },\r\n\r\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\r\n await model.update({\r\n where: { id: userId },\r\n data: {\r\n oauthProvider: provider,\r\n oauthId: providerId,\r\n },\r\n });\r\n },\r\n };\r\n}\r\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAWO,SAAS,oBAA+C,OAAgC;AAC7F,SAAO;AAAA,IACL,MAAM,WAAW,MAAW;AAC1B,YAAM,aAAa,EAAE,IAAI,KAAK,MAAM,WAAW,OAAO,WAAW,GAAG,GAAG,KAAK;AAC5E,YAAM,OAAO,MAAM,MAAM,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,gBAAgB,OAAe;AACnC,YAAM,OAAO,MAAM,MAAM,WAAW;AAAA,QAClC,OAAO,EAAE,MAAM;AAAA,MACjB,CAAC;AACD,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,aAAa,IAAY;AAC7B,YAAM,OAAO,MAAM,MAAM,WAAW;AAAA,QAClC,OAAO,EAAE,GAAG;AAAA,MACd,CAAC;AACD,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,iBAAiB,QAAgB,UAAkB,YAAoB;AAC3E,YAAM,MAAM,OAAO;AAAA,QACjB,OAAO,EAAE,IAAI,OAAO;AAAA,QACpB,MAAM;AAAA,UACJ,eAAe;AAAA,UACf,SAAS;AAAA,QACX;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/adapters/prisma.ts"],
4
- "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\n\n/**\n * Creates a Prisma adapter using a Prisma delegate (e.g., prisma.user).\n * \n * Works with any Prisma-supported database by using the standard\n * Prisma delegate operations (findUnique, create, update).\n * \n * @param model - A Prisma delegate instance (e.g., prisma.user).\n * @returns An AuthAdapter compliant object.\n */\nexport function createPrismaAdapter<TUser extends User = User>(model: any): AuthAdapter<TUser> {\n return {\n async createUser(data: any) {\n const dataToSave = { id: data.id || globalThis.crypto.randomUUID(), ...data };\n const user = await model.create({ data: dataToSave });\n return user as TUser;\n },\n\n async findUserByEmail(email: string) {\n const user = await model.findUnique({\n where: { email },\n });\n return user as TUser | null;\n },\n\n async findUserById(id: string) {\n const user = await model.findUnique({\n where: { id },\n });\n return user as TUser | null;\n },\n\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\n await model.update({\n where: { id: userId },\n data: {\n oauthProvider: provider,\n oauthId: providerId,\n },\n });\n },\n };\n}\n"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./index.js\";\r\n\r\n/**\r\n * Creates a Prisma adapter using a Prisma delegate (e.g., prisma.user).\r\n * \r\n * Works with any Prisma-supported database by using the standard\r\n * Prisma delegate operations (findUnique, create, update).\r\n * \r\n * @param model - A Prisma delegate instance (e.g., prisma.user).\r\n * @returns An AuthAdapter compliant object.\r\n */\r\nexport function createPrismaAdapter<TUser extends User = User>(model: any): AuthAdapter<TUser> {\r\n return {\r\n async createUser(data: any) {\r\n const dataToSave = { id: data.id || globalThis.crypto.randomUUID(), ...data };\r\n const user = await model.create({ data: dataToSave });\r\n return user as TUser;\r\n },\r\n\r\n async findUserByEmail(email: string) {\r\n const user = await model.findUnique({\r\n where: { email },\r\n });\r\n return user as TUser | null;\r\n },\r\n\r\n async findUserById(id: string) {\r\n const user = await model.findUnique({\r\n where: { id },\r\n });\r\n return user as TUser | null;\r\n },\r\n\r\n async linkOAuthAccount(userId: string, provider: string, providerId: string) {\r\n await model.update({\r\n where: { id: userId },\r\n data: {\r\n oauthProvider: provider,\r\n oauthId: providerId,\r\n },\r\n });\r\n },\r\n };\r\n}\r\n"],
5
5
  "mappings": "AAWO,SAAS,oBAA+C,OAAgC;AAC7F,SAAO;AAAA,IACL,MAAM,WAAW,MAAW;AAC1B,YAAM,aAAa,EAAE,IAAI,KAAK,MAAM,WAAW,OAAO,WAAW,GAAG,GAAG,KAAK;AAC5E,YAAM,OAAO,MAAM,MAAM,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,gBAAgB,OAAe;AACnC,YAAM,OAAO,MAAM,MAAM,WAAW;AAAA,QAClC,OAAO,EAAE,MAAM;AAAA,MACjB,CAAC;AACD,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,aAAa,IAAY;AAC7B,YAAM,OAAO,MAAM,MAAM,WAAW;AAAA,QAClC,OAAO,EAAE,GAAG;AAAA,MACd,CAAC;AACD,aAAO;AAAA,IACT;AAAA,IAEA,MAAM,iBAAiB,QAAgB,UAAkB,YAAoB;AAC3E,YAAM,MAAM,OAAO;AAAA,QACjB,OAAO,EAAE,IAAI,OAAO;AAAA,QACpB,MAAM;AAAA,UACJ,eAAe;AAAA,UACf,SAAS;AAAA,QACX;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/core/index.ts"],
4
- "sourcesContent": ["import * as argon2 from \"argon2\";\nimport { SignJWT, jwtVerify } from \"jose\";\nimport crypto from \"crypto\";\nimport type { AuthAdapter, User } from \"../adapters/index.js\";\nimport type { Provider } from \"../providers/index.js\";\n\nexport interface CreateAuthOptions {\n adapter: AuthAdapter<any>;\n secret: string | Uint8Array;\n pepper?: string;\n session?: {\n expires?: string | number; // For access tokens\n refreshExpires?: string | number; // For refresh tokens\n };\n providers?: Provider[];\n jwt?: {\n /**\n * A callback to add custom fields to the JWT payload.\n * It receives the user object and the token type ('access' or 'refresh').\n * Return an object containing the fields to be merged into the payload.\n * You can also override default fields like 'sub'.\n */\n payload?: (user: User<any>, type: \"access\" | \"refresh\") => Record<string, any>;\n };\n}\n\nexport function createAuth(options: CreateAuthOptions) {\n const { adapter, secret, pepper, session, providers } = options;\n const encodedSecret = typeof secret === \"string\" ? new TextEncoder().encode(secret) : secret;\n const expiration = session?.expires || \"1h\"; // Default access token to 1h\n const refreshExpiration = session?.refreshExpires || \"7d\";\n\n /**\n * Generates a stateless JWT for a user session\n */\n async function generateToken(user: User<any>, type: \"access\" | \"refresh\" = \"access\") {\n let payload: Record<string, any> = { sub: user.id, role: user.role, type };\n\n if (options.jwt?.payload) {\n const customPayload = options.jwt.payload(user, type);\n payload = { ...payload, ...customPayload };\n }\n\n return new SignJWT(payload)\n .setProtectedHeader({ alg: \"HS256\" })\n .setIssuedAt()\n .setExpirationTime(type === \"access\" ? expiration : refreshExpiration)\n .sign(encodedSecret);\n }\n\n /**\n * Verifies a JWT and returns the payload.\n * Optionally checks for a specific token type (access/refresh).\n */\n async function verifyToken(token: string, expectedType: \"access\" | \"refresh\" = \"access\") {\n try {\n const { payload } = await jwtVerify(token, encodedSecret);\n if (payload.type !== expectedType) return null;\n return payload;\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Refreshes an access token using a valid refresh token.\n */\n async function refresh(refreshToken: string) {\n const payload = await verifyToken(refreshToken, \"refresh\");\n if (!payload || !payload.sub) {\n throw new Error(\"Invalid or expired refresh token\");\n }\n\n const user = await adapter.findUserById(payload.sub as string);\n if (!user) {\n throw new Error(\"User not found\");\n }\n\n const accessToken = await generateToken(user, \"access\");\n return { accessToken };\n }\n\n /**\n * Signup with a new user payload.\n * Incorporates server-side pepper for password hashing if provided.\n */\n async function signup(userData: Omit<User<any>, \"id\">, password?: string) {\n let dataToSave = { ...userData };\n\n if (password) {\n const passwordWithPepper = pepper ? `${password}${pepper}` : password;\n dataToSave.passwordHash = await argon2.hash(passwordWithPepper);\n }\n\n const newUser = await adapter.createUser(dataToSave);\n const accessToken = await generateToken(newUser, \"access\");\n const refreshToken = await generateToken(newUser, \"refresh\");\n\n return { user: newUser, accessToken, refreshToken };\n }\n\n /**\n * Standard Email/Password Login.\n * Includes timing attack protection and password peppering.\n */\n async function loginWithPassword(email: string, password: string) {\n const user = await adapter.findUserByEmail(email);\n\n // Timing attack protection: Always verify a hash, even if user doesn't exist.\n // We use a dummy hash to keep execution time consistent.\n const dummyHash = \"$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RytpInY7i6C9M5l0D4n8Q+7j/J+i\";\n const targetHash = user?.passwordHash || dummyHash;\n const passwordWithPepper = pepper ? `${password}${pepper}` : password;\n\n const isValid = await argon2.verify(targetHash, passwordWithPepper);\n\n if (!user || !user.passwordHash || !isValid) {\n throw new Error(\"Invalid credentials\");\n }\n\n const accessToken = await generateToken(user, \"access\");\n const refreshToken = await generateToken(user, \"refresh\");\n\n return { user, accessToken, refreshToken };\n }\n\n return {\n signup,\n loginWithPassword,\n refresh,\n verifyToken,\n generateToken,\n _providers: providers\n };\n}\n\n/**\n * Utility to generate a high-entropy cryptographically secure secret.\n * Useful for initializing the 'secret' option in createAuth.\n */\nexport function generateSecret(length: number = 32): Uint8Array {\n return crypto.getRandomValues(new Uint8Array(length));\n}\n"],
4
+ "sourcesContent": ["import * as argon2 from \"argon2\";\r\nimport { SignJWT, jwtVerify } from \"jose\";\r\nimport crypto from \"crypto\";\r\nimport type { AuthAdapter, User } from \"../adapters/index.js\";\r\nimport type { Provider } from \"../providers/index.js\";\r\n\r\nexport interface CreateAuthOptions {\r\n adapter: AuthAdapter<any>;\r\n secret: string | Uint8Array;\r\n pepper?: string;\r\n session?: {\r\n expires?: string | number; // For access tokens\r\n refreshExpires?: string | number; // For refresh tokens\r\n };\r\n providers?: Provider[];\r\n jwt?: {\r\n /**\r\n * A callback to add custom fields to the JWT payload.\r\n * It receives the user object and the token type ('access' or 'refresh').\r\n * Return an object containing the fields to be merged into the payload.\r\n * You can also override default fields like 'sub'.\r\n */\r\n payload?: (user: User<any>, type: \"access\" | \"refresh\") => Record<string, any>;\r\n };\r\n}\r\n\r\nexport function createAuth(options: CreateAuthOptions) {\r\n const { adapter, secret, pepper, session, providers } = options;\r\n const encodedSecret = typeof secret === \"string\" ? new TextEncoder().encode(secret) : secret;\r\n const expiration = session?.expires || \"1h\"; // Default access token to 1h\r\n const refreshExpiration = session?.refreshExpires || \"7d\";\r\n\r\n /**\r\n * Generates a stateless JWT for a user session\r\n */\r\n async function generateToken(user: User<any>, type: \"access\" | \"refresh\" = \"access\") {\r\n let payload: Record<string, any> = { sub: user.id, role: user.role, type };\r\n\r\n if (options.jwt?.payload) {\r\n const customPayload = options.jwt.payload(user, type);\r\n payload = { ...payload, ...customPayload };\r\n }\r\n\r\n return new SignJWT(payload)\r\n .setProtectedHeader({ alg: \"HS256\" })\r\n .setIssuedAt()\r\n .setExpirationTime(type === \"access\" ? expiration : refreshExpiration)\r\n .sign(encodedSecret);\r\n }\r\n\r\n /**\r\n * Verifies a JWT and returns the payload.\r\n * Optionally checks for a specific token type (access/refresh).\r\n */\r\n async function verifyToken(token: string, expectedType: \"access\" | \"refresh\" = \"access\") {\r\n try {\r\n const { payload } = await jwtVerify(token, encodedSecret);\r\n if (payload.type !== expectedType) return null;\r\n return payload;\r\n } catch (e) {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Refreshes an access token using a valid refresh token.\r\n */\r\n async function refresh(refreshToken: string) {\r\n const payload = await verifyToken(refreshToken, \"refresh\");\r\n if (!payload || !payload.sub) {\r\n throw new Error(\"Invalid or expired refresh token\");\r\n }\r\n\r\n const user = await adapter.findUserById(payload.sub as string);\r\n if (!user) {\r\n throw new Error(\"User not found\");\r\n }\r\n\r\n const accessToken = await generateToken(user, \"access\");\r\n return { accessToken };\r\n }\r\n\r\n /**\r\n * Signup with a new user payload.\r\n * Incorporates server-side pepper for password hashing if provided.\r\n */\r\n async function signup(userData: Omit<User<any>, \"id\">, password?: string) {\r\n let dataToSave = { ...userData };\r\n\r\n if (password) {\r\n const passwordWithPepper = pepper ? `${password}${pepper}` : password;\r\n dataToSave.passwordHash = await argon2.hash(passwordWithPepper);\r\n }\r\n\r\n const newUser = await adapter.createUser(dataToSave);\r\n const accessToken = await generateToken(newUser, \"access\");\r\n const refreshToken = await generateToken(newUser, \"refresh\");\r\n\r\n return { user: newUser, accessToken, refreshToken };\r\n }\r\n\r\n /**\r\n * Standard Email/Password Login.\r\n * Includes timing attack protection and password peppering.\r\n */\r\n async function loginWithPassword(email: string, password: string) {\r\n const user = await adapter.findUserByEmail(email);\r\n\r\n // Timing attack protection: Always verify a hash, even if user doesn't exist.\r\n // We use a dummy hash to keep execution time consistent.\r\n const dummyHash = \"$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RytpInY7i6C9M5l0D4n8Q+7j/J+i\";\r\n const targetHash = user?.passwordHash || dummyHash;\r\n const passwordWithPepper = pepper ? `${password}${pepper}` : password;\r\n\r\n const isValid = await argon2.verify(targetHash, passwordWithPepper);\r\n\r\n if (!user || !user.passwordHash || !isValid) {\r\n throw new Error(\"Invalid credentials\");\r\n }\r\n\r\n const accessToken = await generateToken(user, \"access\");\r\n const refreshToken = await generateToken(user, \"refresh\");\r\n\r\n return { user, accessToken, refreshToken };\r\n }\r\n\r\n return {\r\n signup,\r\n loginWithPassword,\r\n refresh,\r\n verifyToken,\r\n generateToken,\r\n _providers: providers\r\n };\r\n}\r\n\r\n/**\r\n * Utility to generate a high-entropy cryptographically secure secret.\r\n * Useful for initializing the 'secret' option in createAuth.\r\n */\r\nexport function generateSecret(length: number = 32): Uint8Array {\r\n return crypto.getRandomValues(new Uint8Array(length));\r\n}\r\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAAwB;AACxB,kBAAmC;AACnC,oBAAmB;AAwBZ,SAAS,WAAW,SAA4B;AACnD,QAAM,EAAE,SAAS,QAAQ,QAAQ,SAAS,UAAU,IAAI;AACxD,QAAM,gBAAgB,OAAO,WAAW,WAAW,IAAI,YAAY,EAAE,OAAO,MAAM,IAAI;AACtF,QAAM,aAAa,SAAS,WAAW;AACvC,QAAM,oBAAoB,SAAS,kBAAkB;AAKrD,iBAAe,cAAc,MAAiB,OAA6B,UAAU;AACjF,QAAI,UAA+B,EAAE,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,KAAK;AAEzE,QAAI,QAAQ,KAAK,SAAS;AACtB,YAAM,gBAAgB,QAAQ,IAAI,QAAQ,MAAM,IAAI;AACpD,gBAAU,EAAE,GAAG,SAAS,GAAG,cAAc;AAAA,IAC7C;AAEA,WAAO,IAAI,oBAAQ,OAAO,EACrB,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,WAAW,aAAa,iBAAiB,EACpE,KAAK,aAAa;AAAA,EAC3B;AAMA,iBAAe,YAAY,OAAe,eAAqC,UAAU;AACrF,QAAI;AACA,YAAM,EAAE,QAAQ,IAAI,UAAM,uBAAU,OAAO,aAAa;AACxD,UAAI,QAAQ,SAAS,aAAc,QAAO;AAC1C,aAAO;AAAA,IACX,SAAS,GAAG;AACR,aAAO;AAAA,IACX;AAAA,EACJ;AAKA,iBAAe,QAAQ,cAAsB;AACzC,UAAM,UAAU,MAAM,YAAY,cAAc,SAAS;AACzD,QAAI,CAAC,WAAW,CAAC,QAAQ,KAAK;AAC1B,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACtD;AAEA,UAAM,OAAO,MAAM,QAAQ,aAAa,QAAQ,GAAa;AAC7D,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,gBAAgB;AAAA,IACpC;AAEA,UAAM,cAAc,MAAM,cAAc,MAAM,QAAQ;AACtD,WAAO,EAAE,YAAY;AAAA,EACzB;AAMA,iBAAe,OAAO,UAAiC,UAAmB;AACtE,QAAI,aAAa,EAAE,GAAG,SAAS;AAE/B,QAAI,UAAU;AACV,YAAM,qBAAqB,SAAS,GAAG,QAAQ,GAAG,MAAM,KAAK;AAC7D,iBAAW,eAAe,MAAM,OAAO,KAAK,kBAAkB;AAAA,IAClE;AAEA,UAAM,UAAU,MAAM,QAAQ,WAAW,UAAU;AACnD,UAAM,cAAc,MAAM,cAAc,SAAS,QAAQ;AACzD,UAAM,eAAe,MAAM,cAAc,SAAS,SAAS;AAE3D,WAAO,EAAE,MAAM,SAAS,aAAa,aAAa;AAAA,EACtD;AAMA,iBAAe,kBAAkB,OAAe,UAAkB;AAC9D,UAAM,OAAO,MAAM,QAAQ,gBAAgB,KAAK;AAIhD,UAAM,YAAY;AAClB,UAAM,aAAa,MAAM,gBAAgB;AACzC,UAAM,qBAAqB,SAAS,GAAG,QAAQ,GAAG,MAAM,KAAK;AAE7D,UAAM,UAAU,MAAM,OAAO,OAAO,YAAY,kBAAkB;AAElE,QAAI,CAAC,QAAQ,CAAC,KAAK,gBAAgB,CAAC,SAAS;AACzC,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACzC;AAEA,UAAM,cAAc,MAAM,cAAc,MAAM,QAAQ;AACtD,UAAM,eAAe,MAAM,cAAc,MAAM,SAAS;AAExD,WAAO,EAAE,MAAM,aAAa,aAAa;AAAA,EAC7C;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,EAChB;AACJ;AAMO,SAAS,eAAe,SAAiB,IAAgB;AAC5D,SAAO,cAAAA,QAAO,gBAAgB,IAAI,WAAW,MAAM,CAAC;AACxD;",
6
6
  "names": ["crypto"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/core/index.ts"],
4
- "sourcesContent": ["import * as argon2 from \"argon2\";\nimport { SignJWT, jwtVerify } from \"jose\";\nimport crypto from \"crypto\";\nimport type { AuthAdapter, User } from \"../adapters/index.js\";\nimport type { Provider } from \"../providers/index.js\";\n\nexport interface CreateAuthOptions {\n adapter: AuthAdapter<any>;\n secret: string | Uint8Array;\n pepper?: string;\n session?: {\n expires?: string | number; // For access tokens\n refreshExpires?: string | number; // For refresh tokens\n };\n providers?: Provider[];\n jwt?: {\n /**\n * A callback to add custom fields to the JWT payload.\n * It receives the user object and the token type ('access' or 'refresh').\n * Return an object containing the fields to be merged into the payload.\n * You can also override default fields like 'sub'.\n */\n payload?: (user: User<any>, type: \"access\" | \"refresh\") => Record<string, any>;\n };\n}\n\nexport function createAuth(options: CreateAuthOptions) {\n const { adapter, secret, pepper, session, providers } = options;\n const encodedSecret = typeof secret === \"string\" ? new TextEncoder().encode(secret) : secret;\n const expiration = session?.expires || \"1h\"; // Default access token to 1h\n const refreshExpiration = session?.refreshExpires || \"7d\";\n\n /**\n * Generates a stateless JWT for a user session\n */\n async function generateToken(user: User<any>, type: \"access\" | \"refresh\" = \"access\") {\n let payload: Record<string, any> = { sub: user.id, role: user.role, type };\n\n if (options.jwt?.payload) {\n const customPayload = options.jwt.payload(user, type);\n payload = { ...payload, ...customPayload };\n }\n\n return new SignJWT(payload)\n .setProtectedHeader({ alg: \"HS256\" })\n .setIssuedAt()\n .setExpirationTime(type === \"access\" ? expiration : refreshExpiration)\n .sign(encodedSecret);\n }\n\n /**\n * Verifies a JWT and returns the payload.\n * Optionally checks for a specific token type (access/refresh).\n */\n async function verifyToken(token: string, expectedType: \"access\" | \"refresh\" = \"access\") {\n try {\n const { payload } = await jwtVerify(token, encodedSecret);\n if (payload.type !== expectedType) return null;\n return payload;\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Refreshes an access token using a valid refresh token.\n */\n async function refresh(refreshToken: string) {\n const payload = await verifyToken(refreshToken, \"refresh\");\n if (!payload || !payload.sub) {\n throw new Error(\"Invalid or expired refresh token\");\n }\n\n const user = await adapter.findUserById(payload.sub as string);\n if (!user) {\n throw new Error(\"User not found\");\n }\n\n const accessToken = await generateToken(user, \"access\");\n return { accessToken };\n }\n\n /**\n * Signup with a new user payload.\n * Incorporates server-side pepper for password hashing if provided.\n */\n async function signup(userData: Omit<User<any>, \"id\">, password?: string) {\n let dataToSave = { ...userData };\n\n if (password) {\n const passwordWithPepper = pepper ? `${password}${pepper}` : password;\n dataToSave.passwordHash = await argon2.hash(passwordWithPepper);\n }\n\n const newUser = await adapter.createUser(dataToSave);\n const accessToken = await generateToken(newUser, \"access\");\n const refreshToken = await generateToken(newUser, \"refresh\");\n\n return { user: newUser, accessToken, refreshToken };\n }\n\n /**\n * Standard Email/Password Login.\n * Includes timing attack protection and password peppering.\n */\n async function loginWithPassword(email: string, password: string) {\n const user = await adapter.findUserByEmail(email);\n\n // Timing attack protection: Always verify a hash, even if user doesn't exist.\n // We use a dummy hash to keep execution time consistent.\n const dummyHash = \"$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RytpInY7i6C9M5l0D4n8Q+7j/J+i\";\n const targetHash = user?.passwordHash || dummyHash;\n const passwordWithPepper = pepper ? `${password}${pepper}` : password;\n\n const isValid = await argon2.verify(targetHash, passwordWithPepper);\n\n if (!user || !user.passwordHash || !isValid) {\n throw new Error(\"Invalid credentials\");\n }\n\n const accessToken = await generateToken(user, \"access\");\n const refreshToken = await generateToken(user, \"refresh\");\n\n return { user, accessToken, refreshToken };\n }\n\n return {\n signup,\n loginWithPassword,\n refresh,\n verifyToken,\n generateToken,\n _providers: providers\n };\n}\n\n/**\n * Utility to generate a high-entropy cryptographically secure secret.\n * Useful for initializing the 'secret' option in createAuth.\n */\nexport function generateSecret(length: number = 32): Uint8Array {\n return crypto.getRandomValues(new Uint8Array(length));\n}\n"],
4
+ "sourcesContent": ["import * as argon2 from \"argon2\";\r\nimport { SignJWT, jwtVerify } from \"jose\";\r\nimport crypto from \"crypto\";\r\nimport type { AuthAdapter, User } from \"../adapters/index.js\";\r\nimport type { Provider } from \"../providers/index.js\";\r\n\r\nexport interface CreateAuthOptions {\r\n adapter: AuthAdapter<any>;\r\n secret: string | Uint8Array;\r\n pepper?: string;\r\n session?: {\r\n expires?: string | number; // For access tokens\r\n refreshExpires?: string | number; // For refresh tokens\r\n };\r\n providers?: Provider[];\r\n jwt?: {\r\n /**\r\n * A callback to add custom fields to the JWT payload.\r\n * It receives the user object and the token type ('access' or 'refresh').\r\n * Return an object containing the fields to be merged into the payload.\r\n * You can also override default fields like 'sub'.\r\n */\r\n payload?: (user: User<any>, type: \"access\" | \"refresh\") => Record<string, any>;\r\n };\r\n}\r\n\r\nexport function createAuth(options: CreateAuthOptions) {\r\n const { adapter, secret, pepper, session, providers } = options;\r\n const encodedSecret = typeof secret === \"string\" ? new TextEncoder().encode(secret) : secret;\r\n const expiration = session?.expires || \"1h\"; // Default access token to 1h\r\n const refreshExpiration = session?.refreshExpires || \"7d\";\r\n\r\n /**\r\n * Generates a stateless JWT for a user session\r\n */\r\n async function generateToken(user: User<any>, type: \"access\" | \"refresh\" = \"access\") {\r\n let payload: Record<string, any> = { sub: user.id, role: user.role, type };\r\n\r\n if (options.jwt?.payload) {\r\n const customPayload = options.jwt.payload(user, type);\r\n payload = { ...payload, ...customPayload };\r\n }\r\n\r\n return new SignJWT(payload)\r\n .setProtectedHeader({ alg: \"HS256\" })\r\n .setIssuedAt()\r\n .setExpirationTime(type === \"access\" ? expiration : refreshExpiration)\r\n .sign(encodedSecret);\r\n }\r\n\r\n /**\r\n * Verifies a JWT and returns the payload.\r\n * Optionally checks for a specific token type (access/refresh).\r\n */\r\n async function verifyToken(token: string, expectedType: \"access\" | \"refresh\" = \"access\") {\r\n try {\r\n const { payload } = await jwtVerify(token, encodedSecret);\r\n if (payload.type !== expectedType) return null;\r\n return payload;\r\n } catch (e) {\r\n return null;\r\n }\r\n }\r\n\r\n /**\r\n * Refreshes an access token using a valid refresh token.\r\n */\r\n async function refresh(refreshToken: string) {\r\n const payload = await verifyToken(refreshToken, \"refresh\");\r\n if (!payload || !payload.sub) {\r\n throw new Error(\"Invalid or expired refresh token\");\r\n }\r\n\r\n const user = await adapter.findUserById(payload.sub as string);\r\n if (!user) {\r\n throw new Error(\"User not found\");\r\n }\r\n\r\n const accessToken = await generateToken(user, \"access\");\r\n return { accessToken };\r\n }\r\n\r\n /**\r\n * Signup with a new user payload.\r\n * Incorporates server-side pepper for password hashing if provided.\r\n */\r\n async function signup(userData: Omit<User<any>, \"id\">, password?: string) {\r\n let dataToSave = { ...userData };\r\n\r\n if (password) {\r\n const passwordWithPepper = pepper ? `${password}${pepper}` : password;\r\n dataToSave.passwordHash = await argon2.hash(passwordWithPepper);\r\n }\r\n\r\n const newUser = await adapter.createUser(dataToSave);\r\n const accessToken = await generateToken(newUser, \"access\");\r\n const refreshToken = await generateToken(newUser, \"refresh\");\r\n\r\n return { user: newUser, accessToken, refreshToken };\r\n }\r\n\r\n /**\r\n * Standard Email/Password Login.\r\n * Includes timing attack protection and password peppering.\r\n */\r\n async function loginWithPassword(email: string, password: string) {\r\n const user = await adapter.findUserByEmail(email);\r\n\r\n // Timing attack protection: Always verify a hash, even if user doesn't exist.\r\n // We use a dummy hash to keep execution time consistent.\r\n const dummyHash = \"$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RytpInY7i6C9M5l0D4n8Q+7j/J+i\";\r\n const targetHash = user?.passwordHash || dummyHash;\r\n const passwordWithPepper = pepper ? `${password}${pepper}` : password;\r\n\r\n const isValid = await argon2.verify(targetHash, passwordWithPepper);\r\n\r\n if (!user || !user.passwordHash || !isValid) {\r\n throw new Error(\"Invalid credentials\");\r\n }\r\n\r\n const accessToken = await generateToken(user, \"access\");\r\n const refreshToken = await generateToken(user, \"refresh\");\r\n\r\n return { user, accessToken, refreshToken };\r\n }\r\n\r\n return {\r\n signup,\r\n loginWithPassword,\r\n refresh,\r\n verifyToken,\r\n generateToken,\r\n _providers: providers\r\n };\r\n}\r\n\r\n/**\r\n * Utility to generate a high-entropy cryptographically secure secret.\r\n * Useful for initializing the 'secret' option in createAuth.\r\n */\r\nexport function generateSecret(length: number = 32): Uint8Array {\r\n return crypto.getRandomValues(new Uint8Array(length));\r\n}\r\n"],
5
5
  "mappings": "AAAA,YAAY,YAAY;AACxB,SAAS,SAAS,iBAAiB;AACnC,OAAO,YAAY;AAwBZ,SAAS,WAAW,SAA4B;AACnD,QAAM,EAAE,SAAS,QAAQ,QAAQ,SAAS,UAAU,IAAI;AACxD,QAAM,gBAAgB,OAAO,WAAW,WAAW,IAAI,YAAY,EAAE,OAAO,MAAM,IAAI;AACtF,QAAM,aAAa,SAAS,WAAW;AACvC,QAAM,oBAAoB,SAAS,kBAAkB;AAKrD,iBAAe,cAAc,MAAiB,OAA6B,UAAU;AACjF,QAAI,UAA+B,EAAE,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,KAAK;AAEzE,QAAI,QAAQ,KAAK,SAAS;AACtB,YAAM,gBAAgB,QAAQ,IAAI,QAAQ,MAAM,IAAI;AACpD,gBAAU,EAAE,GAAG,SAAS,GAAG,cAAc;AAAA,IAC7C;AAEA,WAAO,IAAI,QAAQ,OAAO,EACrB,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,WAAW,aAAa,iBAAiB,EACpE,KAAK,aAAa;AAAA,EAC3B;AAMA,iBAAe,YAAY,OAAe,eAAqC,UAAU;AACrF,QAAI;AACA,YAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,aAAa;AACxD,UAAI,QAAQ,SAAS,aAAc,QAAO;AAC1C,aAAO;AAAA,IACX,SAAS,GAAG;AACR,aAAO;AAAA,IACX;AAAA,EACJ;AAKA,iBAAe,QAAQ,cAAsB;AACzC,UAAM,UAAU,MAAM,YAAY,cAAc,SAAS;AACzD,QAAI,CAAC,WAAW,CAAC,QAAQ,KAAK;AAC1B,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACtD;AAEA,UAAM,OAAO,MAAM,QAAQ,aAAa,QAAQ,GAAa;AAC7D,QAAI,CAAC,MAAM;AACP,YAAM,IAAI,MAAM,gBAAgB;AAAA,IACpC;AAEA,UAAM,cAAc,MAAM,cAAc,MAAM,QAAQ;AACtD,WAAO,EAAE,YAAY;AAAA,EACzB;AAMA,iBAAe,OAAO,UAAiC,UAAmB;AACtE,QAAI,aAAa,EAAE,GAAG,SAAS;AAE/B,QAAI,UAAU;AACV,YAAM,qBAAqB,SAAS,GAAG,QAAQ,GAAG,MAAM,KAAK;AAC7D,iBAAW,eAAe,MAAM,OAAO,KAAK,kBAAkB;AAAA,IAClE;AAEA,UAAM,UAAU,MAAM,QAAQ,WAAW,UAAU;AACnD,UAAM,cAAc,MAAM,cAAc,SAAS,QAAQ;AACzD,UAAM,eAAe,MAAM,cAAc,SAAS,SAAS;AAE3D,WAAO,EAAE,MAAM,SAAS,aAAa,aAAa;AAAA,EACtD;AAMA,iBAAe,kBAAkB,OAAe,UAAkB;AAC9D,UAAM,OAAO,MAAM,QAAQ,gBAAgB,KAAK;AAIhD,UAAM,YAAY;AAClB,UAAM,aAAa,MAAM,gBAAgB;AACzC,UAAM,qBAAqB,SAAS,GAAG,QAAQ,GAAG,MAAM,KAAK;AAE7D,UAAM,UAAU,MAAM,OAAO,OAAO,YAAY,kBAAkB;AAElE,QAAI,CAAC,QAAQ,CAAC,KAAK,gBAAgB,CAAC,SAAS;AACzC,YAAM,IAAI,MAAM,qBAAqB;AAAA,IACzC;AAEA,UAAM,cAAc,MAAM,cAAc,MAAM,QAAQ;AACtD,UAAM,eAAe,MAAM,cAAc,MAAM,SAAS;AAExD,WAAO,EAAE,MAAM,aAAa,aAAa;AAAA,EAC7C;AAEA,SAAO;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,EAChB;AACJ;AAMO,SAAS,eAAe,SAAiB,IAAgB;AAC5D,SAAO,OAAO,gBAAgB,IAAI,WAAW,MAAM,CAAC;AACxD;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/auth/index.ts"],
4
- "sourcesContent": ["export type { AuthAdapter, User, BaseUser } from \"./adapters/index.js\";\nexport { GitHub, Google } from \"./providers/index.js\";\nexport type { Provider, ProviderConfig } from \"./providers/index.js\";\nexport { createAuth, generateSecret } from \"./core/index.js\";\nexport type { CreateAuthOptions } from \"./core/index.js\";\nexport { createMemoryAdapter } from \"./adapters/memory.js\";\nexport { createMongoAdapter } from \"./adapters/mongoose.js\";\nexport * from \"./security/index.js\";\n"],
4
+ "sourcesContent": ["export type { AuthAdapter, User, BaseUser } from \"./adapters/index.js\";\r\nexport { GitHub, Google } from \"./providers/index.js\";\r\nexport type { Provider, ProviderConfig } from \"./providers/index.js\";\r\nexport { createAuth, generateSecret } from \"./core/index.js\";\r\nexport type { CreateAuthOptions } from \"./core/index.js\";\r\nexport { createMemoryAdapter } from \"./adapters/memory.js\";\r\nexport { createMongoAdapter } from \"./adapters/mongoose.js\";\r\nexport * from \"./security/index.js\";\r\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,uBAA+B;AAE/B,kBAA2C;AAE3C,oBAAoC;AACpC,sBAAmC;AACnC,0BAAc,gCAPd;",
6
6
  "names": []
7
7
  }
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/auth/index.ts"],
4
- "sourcesContent": ["export type { AuthAdapter, User, BaseUser } from \"./adapters/index.js\";\nexport { GitHub, Google } from \"./providers/index.js\";\nexport type { Provider, ProviderConfig } from \"./providers/index.js\";\nexport { createAuth, generateSecret } from \"./core/index.js\";\nexport type { CreateAuthOptions } from \"./core/index.js\";\nexport { createMemoryAdapter } from \"./adapters/memory.js\";\nexport { createMongoAdapter } from \"./adapters/mongoose.js\";\nexport * from \"./security/index.js\";\n"],
4
+ "sourcesContent": ["export type { AuthAdapter, User, BaseUser } from \"./adapters/index.js\";\r\nexport { GitHub, Google } from \"./providers/index.js\";\r\nexport type { Provider, ProviderConfig } from \"./providers/index.js\";\r\nexport { createAuth, generateSecret } from \"./core/index.js\";\r\nexport type { CreateAuthOptions } from \"./core/index.js\";\r\nexport { createMemoryAdapter } from \"./adapters/memory.js\";\r\nexport { createMongoAdapter } from \"./adapters/mongoose.js\";\r\nexport * from \"./security/index.js\";\r\n"],
5
5
  "mappings": "AACA,SAAS,QAAQ,cAAc;AAE/B,SAAS,YAAY,sBAAsB;AAE3C,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AACnC,cAAc;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/providers/index.ts"],
4
- "sourcesContent": ["import { GitHub as ArcticGitHub, Google as ArcticGoogle } from \"arctic\";\n\nexport interface ProviderConfig {\n clientId: string;\n clientSecret: string;\n redirectURI?: string;\n}\n\nexport interface Provider {\n id: string;\n handler: any; // `arctic` provider instance\n}\n\nexport function GitHub(config: ProviderConfig): Provider {\n return {\n id: \"github\",\n handler: new ArcticGitHub(config.clientId, config.clientSecret, null),\n };\n}\n\nexport function Google(config: ProviderConfig): Provider {\n if (!config.redirectURI) {\n throw new Error(\"redirectURI is required for Google OAuth provider\");\n }\n return {\n id: \"google\",\n handler: new ArcticGoogle(\n config.clientId,\n config.clientSecret,\n config.redirectURI\n ),\n };\n}\n"],
4
+ "sourcesContent": ["import { GitHub as ArcticGitHub, Google as ArcticGoogle } from \"arctic\";\r\n\r\nexport interface ProviderConfig {\r\n clientId: string;\r\n clientSecret: string;\r\n redirectURI?: string;\r\n}\r\n\r\nexport interface Provider {\r\n id: string;\r\n handler: any; // `arctic` provider instance\r\n}\r\n\r\nexport function GitHub(config: ProviderConfig): Provider {\r\n return {\r\n id: \"github\",\r\n handler: new ArcticGitHub(config.clientId, config.clientSecret, null),\r\n };\r\n}\r\n\r\nexport function Google(config: ProviderConfig): Provider {\r\n if (!config.redirectURI) {\r\n throw new Error(\"redirectURI is required for Google OAuth provider\");\r\n }\r\n return {\r\n id: \"google\",\r\n handler: new ArcticGoogle(\r\n config.clientId,\r\n config.clientSecret,\r\n config.redirectURI\r\n ),\r\n };\r\n}\r\n"],
5
5
  "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAA+D;AAaxD,SAAS,OAAO,QAAkC;AACrD,SAAO;AAAA,IACH,IAAI;AAAA,IACJ,SAAS,IAAI,cAAAA,OAAa,OAAO,UAAU,OAAO,cAAc,IAAI;AAAA,EACxE;AACJ;AAEO,SAAS,OAAO,QAAkC;AACrD,MAAI,CAAC,OAAO,aAAa;AACrB,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACvE;AACA,SAAO;AAAA,IACH,IAAI;AAAA,IACJ,SAAS,IAAI,cAAAC;AAAA,MACT,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACX;AAAA,EACJ;AACJ;",
6
6
  "names": ["ArcticGitHub", "ArcticGoogle"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/providers/index.ts"],
4
- "sourcesContent": ["import { GitHub as ArcticGitHub, Google as ArcticGoogle } from \"arctic\";\n\nexport interface ProviderConfig {\n clientId: string;\n clientSecret: string;\n redirectURI?: string;\n}\n\nexport interface Provider {\n id: string;\n handler: any; // `arctic` provider instance\n}\n\nexport function GitHub(config: ProviderConfig): Provider {\n return {\n id: \"github\",\n handler: new ArcticGitHub(config.clientId, config.clientSecret, null),\n };\n}\n\nexport function Google(config: ProviderConfig): Provider {\n if (!config.redirectURI) {\n throw new Error(\"redirectURI is required for Google OAuth provider\");\n }\n return {\n id: \"google\",\n handler: new ArcticGoogle(\n config.clientId,\n config.clientSecret,\n config.redirectURI\n ),\n };\n}\n"],
4
+ "sourcesContent": ["import { GitHub as ArcticGitHub, Google as ArcticGoogle } from \"arctic\";\r\n\r\nexport interface ProviderConfig {\r\n clientId: string;\r\n clientSecret: string;\r\n redirectURI?: string;\r\n}\r\n\r\nexport interface Provider {\r\n id: string;\r\n handler: any; // `arctic` provider instance\r\n}\r\n\r\nexport function GitHub(config: ProviderConfig): Provider {\r\n return {\r\n id: \"github\",\r\n handler: new ArcticGitHub(config.clientId, config.clientSecret, null),\r\n };\r\n}\r\n\r\nexport function Google(config: ProviderConfig): Provider {\r\n if (!config.redirectURI) {\r\n throw new Error(\"redirectURI is required for Google OAuth provider\");\r\n }\r\n return {\r\n id: \"google\",\r\n handler: new ArcticGoogle(\r\n config.clientId,\r\n config.clientSecret,\r\n config.redirectURI\r\n ),\r\n };\r\n}\r\n"],
5
5
  "mappings": "AAAA,SAAS,UAAU,cAAc,UAAU,oBAAoB;AAaxD,SAAS,OAAO,QAAkC;AACrD,SAAO;AAAA,IACH,IAAI;AAAA,IACJ,SAAS,IAAI,aAAa,OAAO,UAAU,OAAO,cAAc,IAAI;AAAA,EACxE;AACJ;AAEO,SAAS,OAAO,QAAkC;AACrD,MAAI,CAAC,OAAO,aAAa;AACrB,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACvE;AACA,SAAO;AAAA,IACH,IAAI;AAAA,IACJ,SAAS,IAAI;AAAA,MACT,OAAO;AAAA,MACP,OAAO;AAAA,MACP,OAAO;AAAA,IACX;AAAA,EACJ;AACJ;",
6
6
  "names": []
7
7
  }
@@ -38,14 +38,12 @@ function generateCsrfToken() {
38
38
  }
39
39
  function verifyCsrf(tokenInRequest, tokenInCookie) {
40
40
  if (!tokenInRequest || !tokenInCookie) return false;
41
- try {
42
- return import_crypto.default.timingSafeEqual(
43
- Buffer.from(tokenInRequest),
44
- Buffer.from(tokenInCookie)
45
- );
46
- } catch {
47
- return false;
48
- }
41
+ const isValid = /^[a-f0-9]{64}$/i.test(tokenInRequest) && /^[a-f0-9]{64}$/i.test(tokenInCookie);
42
+ if (!isValid) return false;
43
+ const bufA = Buffer.from(tokenInRequest, "hex");
44
+ const bufB = Buffer.from(tokenInCookie, "hex");
45
+ if (bufA.length !== bufB.length) return false;
46
+ return import_crypto.default.timingSafeEqual(bufA, bufB);
49
47
  }
50
48
  // Annotate the CommonJS export names for ESM import in node:
51
49
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/security/index.ts"],
4
- "sourcesContent": ["import crypto from \"crypto\";\n\n/**\n * Generates a stateless CSRF token using the double-submit cookie pattern.\n * This is recommended for Express/Kroxt setups using cookies for sessions.\n */\nexport function generateCsrfToken(): string {\n return crypto.randomBytes(32).toString(\"hex\");\n}\n\n/**\n * Simple middleware-ready check for CSRF tokens.\n * Matches a token from the request body/headers against a cookie.\n */\nexport function verifyCsrf(tokenInRequest: string, tokenInCookie: string): boolean {\n if (!tokenInRequest || !tokenInCookie) return false;\n\n // Constant time comparison\n try {\n return crypto.timingSafeEqual(\n Buffer.from(tokenInRequest),\n Buffer.from(tokenInCookie)\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Security Recommendations for Kroxt:\n * 1. Always set cookies with: httpOnly: true, secure: true, sameSite: 'strict'\n * 2. Use a 'pepper' in createAuth to protect hashes.\n * 3. Implement rate limiting on /login and /register endpoints.\n */\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAmB;AAMZ,SAAS,oBAA4B;AACxC,SAAO,cAAAA,QAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAChD;AAMO,SAAS,WAAW,gBAAwB,eAAgC;AAC/E,MAAI,CAAC,kBAAkB,CAAC,cAAe,QAAO;AAG9C,MAAI;AACA,WAAO,cAAAA,QAAO;AAAA,MACV,OAAO,KAAK,cAAc;AAAA,MAC1B,OAAO,KAAK,aAAa;AAAA,IAC7B;AAAA,EACJ,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;",
4
+ "sourcesContent": ["import crypto from \"crypto\";\r\n\r\n/**\r\n * Generates a stateless CSRF token using the double-submit cookie pattern.\r\n * This is recommended for Express/Kroxt setups using cookies for sessions.\r\n */\r\nexport function generateCsrfToken(): string {\r\n return crypto.randomBytes(32).toString(\"hex\");\r\n}\r\n\r\n/**\r\n * Simple middleware-ready check for CSRF tokens.\r\n * Matches a token from the request body/headers against a cookie.\r\n */\r\nexport function verifyCsrf(tokenInRequest: string, tokenInCookie: string): boolean {\r\n if (!tokenInRequest || !tokenInCookie) return false;\r\n\r\n const isValid =\r\n /^[a-f0-9]{64}$/i.test(tokenInRequest) &&\r\n /^[a-f0-9]{64}$/i.test(tokenInCookie);\r\n\r\n if (!isValid) return false;\r\n\r\n const bufA = Buffer.from(tokenInRequest, \"hex\");\r\n const bufB = Buffer.from(tokenInCookie, \"hex\");\r\n\r\n if (bufA.length !== bufB.length) return false;\r\n\r\n return crypto.timingSafeEqual(bufA, bufB);\r\n}\r\n\r\n/**\r\n * Security Recommendations for Kroxt:\r\n * 1. Always set cookies with: httpOnly: true, secure: true, sameSite: 'strict'\r\n * 2. Use a 'pepper' in createAuth to protect hashes.\r\n * 3. Implement rate limiting on /login and /register endpoints.\r\n */\r\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAmB;AAMZ,SAAS,oBAA4B;AACxC,SAAO,cAAAA,QAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAChD;AAMO,SAAS,WAAW,gBAAwB,eAAgC;AAC/E,MAAI,CAAC,kBAAkB,CAAC,cAAe,QAAO;AAE9C,QAAM,UACF,kBAAkB,KAAK,cAAc,KACrC,kBAAkB,KAAK,aAAa;AAExC,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,OAAO,OAAO,KAAK,gBAAgB,KAAK;AAC9C,QAAM,OAAO,OAAO,KAAK,eAAe,KAAK;AAE7C,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AAExC,SAAO,cAAAA,QAAO,gBAAgB,MAAM,IAAI;AAC5C;",
6
6
  "names": ["crypto"]
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/security/index.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAYjF;AAED;;;;;GAKG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/security/index.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAejF;AAED;;;;;GAKG"}
@@ -4,14 +4,12 @@ function generateCsrfToken() {
4
4
  }
5
5
  function verifyCsrf(tokenInRequest, tokenInCookie) {
6
6
  if (!tokenInRequest || !tokenInCookie) return false;
7
- try {
8
- return crypto.timingSafeEqual(
9
- Buffer.from(tokenInRequest),
10
- Buffer.from(tokenInCookie)
11
- );
12
- } catch {
13
- return false;
14
- }
7
+ const isValid = /^[a-f0-9]{64}$/i.test(tokenInRequest) && /^[a-f0-9]{64}$/i.test(tokenInCookie);
8
+ if (!isValid) return false;
9
+ const bufA = Buffer.from(tokenInRequest, "hex");
10
+ const bufB = Buffer.from(tokenInCookie, "hex");
11
+ if (bufA.length !== bufB.length) return false;
12
+ return crypto.timingSafeEqual(bufA, bufB);
15
13
  }
16
14
  export {
17
15
  generateCsrfToken,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/auth/security/index.ts"],
4
- "sourcesContent": ["import crypto from \"crypto\";\n\n/**\n * Generates a stateless CSRF token using the double-submit cookie pattern.\n * This is recommended for Express/Kroxt setups using cookies for sessions.\n */\nexport function generateCsrfToken(): string {\n return crypto.randomBytes(32).toString(\"hex\");\n}\n\n/**\n * Simple middleware-ready check for CSRF tokens.\n * Matches a token from the request body/headers against a cookie.\n */\nexport function verifyCsrf(tokenInRequest: string, tokenInCookie: string): boolean {\n if (!tokenInRequest || !tokenInCookie) return false;\n\n // Constant time comparison\n try {\n return crypto.timingSafeEqual(\n Buffer.from(tokenInRequest),\n Buffer.from(tokenInCookie)\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Security Recommendations for Kroxt:\n * 1. Always set cookies with: httpOnly: true, secure: true, sameSite: 'strict'\n * 2. Use a 'pepper' in createAuth to protect hashes.\n * 3. Implement rate limiting on /login and /register endpoints.\n */\n"],
5
- "mappings": "AAAA,OAAO,YAAY;AAMZ,SAAS,oBAA4B;AACxC,SAAO,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAChD;AAMO,SAAS,WAAW,gBAAwB,eAAgC;AAC/E,MAAI,CAAC,kBAAkB,CAAC,cAAe,QAAO;AAG9C,MAAI;AACA,WAAO,OAAO;AAAA,MACV,OAAO,KAAK,cAAc;AAAA,MAC1B,OAAO,KAAK,aAAa;AAAA,IAC7B;AAAA,EACJ,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;",
4
+ "sourcesContent": ["import crypto from \"crypto\";\r\n\r\n/**\r\n * Generates a stateless CSRF token using the double-submit cookie pattern.\r\n * This is recommended for Express/Kroxt setups using cookies for sessions.\r\n */\r\nexport function generateCsrfToken(): string {\r\n return crypto.randomBytes(32).toString(\"hex\");\r\n}\r\n\r\n/**\r\n * Simple middleware-ready check for CSRF tokens.\r\n * Matches a token from the request body/headers against a cookie.\r\n */\r\nexport function verifyCsrf(tokenInRequest: string, tokenInCookie: string): boolean {\r\n if (!tokenInRequest || !tokenInCookie) return false;\r\n\r\n const isValid =\r\n /^[a-f0-9]{64}$/i.test(tokenInRequest) &&\r\n /^[a-f0-9]{64}$/i.test(tokenInCookie);\r\n\r\n if (!isValid) return false;\r\n\r\n const bufA = Buffer.from(tokenInRequest, \"hex\");\r\n const bufB = Buffer.from(tokenInCookie, \"hex\");\r\n\r\n if (bufA.length !== bufB.length) return false;\r\n\r\n return crypto.timingSafeEqual(bufA, bufB);\r\n}\r\n\r\n/**\r\n * Security Recommendations for Kroxt:\r\n * 1. Always set cookies with: httpOnly: true, secure: true, sameSite: 'strict'\r\n * 2. Use a 'pepper' in createAuth to protect hashes.\r\n * 3. Implement rate limiting on /login and /register endpoints.\r\n */\r\n"],
5
+ "mappings": "AAAA,OAAO,YAAY;AAMZ,SAAS,oBAA4B;AACxC,SAAO,OAAO,YAAY,EAAE,EAAE,SAAS,KAAK;AAChD;AAMO,SAAS,WAAW,gBAAwB,eAAgC;AAC/E,MAAI,CAAC,kBAAkB,CAAC,cAAe,QAAO;AAE9C,QAAM,UACF,kBAAkB,KAAK,cAAc,KACrC,kBAAkB,KAAK,aAAa;AAExC,MAAI,CAAC,QAAS,QAAO;AAErB,QAAM,OAAO,OAAO,KAAK,gBAAgB,KAAK;AAC9C,QAAM,OAAO,OAAO,KAAK,eAAe,KAAK;AAE7C,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AAExC,SAAO,OAAO,gBAAgB,MAAM,IAAI;AAC5C;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kroxt",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "keywords": [
5
5
  "auth",
6
6
  "authentication",