kroxt 1.1.2 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +57 -16
  2. package/dist/adapter.cjs +17 -0
  3. package/dist/adapter.cjs.map +7 -0
  4. package/{dist-lib → dist}/adapter.d.ts +1 -0
  5. package/dist/adapter.d.ts.map +1 -0
  6. package/dist/adapter.js +1 -0
  7. package/dist/adapter.js.map +7 -0
  8. package/dist/core.cjs +113 -0
  9. package/dist/core.cjs.map +7 -0
  10. package/{dist-lib → dist}/core.d.ts +12 -2
  11. package/dist/core.d.ts.map +1 -0
  12. package/dist/core.js +78 -0
  13. package/dist/core.js.map +7 -0
  14. package/dist/index.cjs +42 -0
  15. package/dist/index.cjs.map +7 -0
  16. package/{dist-lib → dist}/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +12 -0
  19. package/dist/index.js.map +7 -0
  20. package/dist/memoryAdapter.cjs +55 -0
  21. package/dist/memoryAdapter.cjs.map +7 -0
  22. package/{dist-lib → dist}/memoryAdapter.d.ts +1 -0
  23. package/dist/memoryAdapter.d.ts.map +1 -0
  24. package/dist/memoryAdapter.js +31 -0
  25. package/dist/memoryAdapter.js.map +7 -0
  26. package/dist/providers.cjs +50 -0
  27. package/dist/providers.cjs.map +7 -0
  28. package/{dist-lib → dist}/providers.d.ts +1 -0
  29. package/dist/providers.d.ts.map +1 -0
  30. package/dist/providers.js +25 -0
  31. package/dist/providers.js.map +7 -0
  32. package/dist/security.cjs +55 -0
  33. package/dist/security.cjs.map +7 -0
  34. package/{dist-lib → dist}/security.d.ts +1 -0
  35. package/dist/security.d.ts.map +1 -0
  36. package/dist/security.js +20 -0
  37. package/dist/security.js.map +7 -0
  38. package/package.json +27 -16
  39. package/dist-lib/adapter.js +0 -1
  40. package/dist-lib/core.js +0 -98
  41. package/dist-lib/index.js +0 -4
  42. package/dist-lib/memoryAdapter.js +0 -34
  43. package/dist-lib/providers.js +0 -16
  44. package/dist-lib/security.js +0 -29
package/README.md CHANGED
@@ -6,12 +6,13 @@ A framework-agnostic, modular authentication engine for modern TypeScript applic
6
6
 
7
7
  - 🔐 **Secure Hashing**: Powered by `argon2` for industry-standard password security.
8
8
  - 🎟️ **Dual-Token Sessions**: Native support for Access and Refresh tokens via `jose`.
9
+ - 🧩 **JWT Customization**: Fully extensible payload with support for custom user fields and `sub` override.
9
10
  - 🌍 **OAuth Ready**: Built-in support for GitHub and Google OAuth via `arctic`.
10
11
  - 🧩 **Database Agnostic**: Use Mongoose, Prisma, Drizzle, or any store via the `AuthAdapter` pattern.
11
12
  - 🌶️ **Password Peppering**: Server-side pepper support for enhanced hash protection.
12
13
  - 🛡️ **Timing Attack Protection**: Built-in safeguards against side-channel analysis during login.
13
14
  - ✅ **Zod Schema Support**: Perfectly preserves and types your user metadata.
14
- - 🚀 **ESM First**: Native support for NodeNext module resolution.
15
+ - 🌍 **Dual ESM/CJS Support**: Native support for both modern ESM (`import`) and CommonJS (`require`).
15
16
 
16
17
  ## Installation
17
18
 
@@ -25,35 +26,60 @@ npm install kroxt
25
26
 
26
27
  This guide walks you through setting up Kroxt from scratch in your application.
27
28
 
28
- ### Step 1: The Adapter Pattern
29
+ ### Step 1: Define your User
29
30
 
30
- Kroxt doesn't care which database you use. You just need to implement the `AuthAdapter` interface.
31
+ First, define what a User looks like in your system. Kroxt allows any additional fields (like `role`, `schoolId`, etc.) which you can later sign into your JWTs.
31
32
 
32
- In this example, we use a simple user structure: `name`, `email`, and `password`.
33
- > [!NOTE]
34
- > Kroxt's adapter can accept **any** additional fields your application requires (e.g., `role`, `avatar`, `preferences`) with no limits.
33
+ ```typescript
34
+ export interface MyUser {
35
+ id: string;
36
+ email: string;
37
+ passwordHash: string;
38
+ role: 'admin' | 'user';
39
+ schoolId: string; // Custom field for enterprise/multi-tenant apps
40
+ oauthProvider?: string; // Support for OAuth (e.g., 'github')
41
+ oauthId?: string; // Unique ID from the provider
42
+ name: string;
43
+ }
44
+ ```
45
+
46
+ ### Step 2: The Adapter Pattern
47
+
48
+ Kroxt doesn't care which database you use. You just need to implement the `AuthAdapter` interface using your model. Here is a complete example using Mongoose:
35
49
 
36
50
  ```typescript
37
- import type { AuthAdapter, User } from "kroxt/adapter";
51
+ import type { AuthAdapter } from "kroxt/adapter";
52
+ import { User } from "./models/user.model.js"; // Your Mongoose model
53
+ import type { MyUser } from "./types.js";
38
54
 
39
- export const myAdapter: AuthAdapter = {
55
+ export const myAdapter: AuthAdapter<MyUser> = {
40
56
  createUser: async (data) => {
41
- // Save to your DB: { name, email, passwordHash, ...anyOtherFields }
42
- // return the created user including its unique id
57
+ const user = await User.create(data);
58
+ const obj = user.toObject();
59
+ return { ...obj, id: obj._id.toString() };
43
60
  },
44
61
  findUserByEmail: async (email) => {
45
- // Find user by email in your DB
62
+ const user = await User.findOne({ email });
63
+ if (!user) return null;
64
+ const obj = user.toObject();
65
+ return { ...obj, id: obj._id.toString() };
46
66
  },
47
67
  findUserById: async (id) => {
48
- // Find user by ID in your DB
68
+ const user = await User.findById(id);
69
+ if (!user) return null;
70
+ const obj = user.toObject();
71
+ return { ...obj, id: obj._id.toString() };
49
72
  },
50
- linkOAuthAccount: async (user, provider, providerId) => {
51
- // Link an OAuth provider to an existing user
73
+ linkOAuthAccount: async (userId, provider, providerId) => {
74
+ await User.findByIdAndUpdate(userId, {
75
+ oauthProvider: provider,
76
+ oauthId: providerId
77
+ });
52
78
  }
53
79
  };
54
80
  ```
55
81
 
56
- ### Step 2: Initialize the Auth Engine
82
+ ### Step 3: Initialize the Auth Engine
57
83
 
58
84
  Configure Kroxt with your adapter and security settings.
59
85
 
@@ -68,11 +94,26 @@ export const auth = createAuth({
68
94
  session: {
69
95
  expires: "15m", // Access token duration
70
96
  refreshExpires: "7d" // Refresh token duration
97
+ },
98
+ jwt: {
99
+ /**
100
+ * Optional: Fully customize the JWT payload or add extra fields.
101
+ */
102
+ payload: (user, type) => {
103
+ // Only add extra details to 'access' tokens to keep 'refresh' tokens light.
104
+ if (type === "access") {
105
+ return {
106
+ schoolId: user.schoolId, // Add custom user detail
107
+ role: user.role, // Explicitly include role
108
+ };
109
+ }
110
+ return {}; // Refresh tokens stay minimal
111
+ }
71
112
  }
72
113
  });
73
114
  ```
74
115
 
75
- ### Step 3: Implement Controllers & Routes
116
+ ### Step 4: Implement Controllers & Routes
76
117
 
77
118
  Use the engine in your application logic. Examples below use an Express-like structure.
78
119
 
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __copyProps = (to, from, except, desc) => {
7
+ if (from && typeof from === "object" || typeof from === "function") {
8
+ for (let key of __getOwnPropNames(from))
9
+ if (!__hasOwnProp.call(to, key) && key !== except)
10
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
11
+ }
12
+ return to;
13
+ };
14
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
15
+ var adapter_exports = {};
16
+ module.exports = __toCommonJS(adapter_exports);
17
+ //# sourceMappingURL=adapter.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/adapter.ts"],
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
+ "mappings": ";;;;;;;;;;;;;;AAAA;AAAA;",
6
+ "names": []
7
+ }
@@ -11,3 +11,4 @@ export interface AuthAdapter<TUser = User> {
11
11
  findUserById: (id: string) => Promise<TUser | null>;
12
12
  linkOAuthAccount: (userId: string, provider: string, providerId: string) => Promise<void>;
13
13
  }
14
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/auth/adapter.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAGD,MAAM,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,QAAQ,GAAG,SAAS,CAAC;AAEzE,MAAM,WAAW,WAAW,CAAC,KAAK,GAAG,IAAI;IACvC,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;IAC1C,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IAC1D,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACpD,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3F"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
package/dist/core.cjs ADDED
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
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
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var core_exports = {};
30
+ __export(core_exports, {
31
+ createAuth: () => createAuth,
32
+ generateSecret: () => generateSecret
33
+ });
34
+ module.exports = __toCommonJS(core_exports);
35
+ var argon2 = __toESM(require("argon2"), 1);
36
+ var import_jose = require("jose");
37
+ var import_crypto = __toESM(require("crypto"), 1);
38
+ function createAuth(options) {
39
+ const { adapter, secret, pepper, session, providers } = options;
40
+ const encodedSecret = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
41
+ const expiration = session?.expires || "1h";
42
+ const refreshExpiration = session?.refreshExpires || "7d";
43
+ async function generateToken(user, type = "access") {
44
+ let payload = { sub: user.id, role: user.role, type };
45
+ if (options.jwt?.payload) {
46
+ const customPayload = options.jwt.payload(user, type);
47
+ payload = { ...payload, ...customPayload };
48
+ }
49
+ return new import_jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(type === "access" ? expiration : refreshExpiration).sign(encodedSecret);
50
+ }
51
+ async function verifyToken(token, expectedType = "access") {
52
+ try {
53
+ const { payload } = await (0, import_jose.jwtVerify)(token, encodedSecret);
54
+ if (payload.type !== expectedType) return null;
55
+ return payload;
56
+ } catch (e) {
57
+ return null;
58
+ }
59
+ }
60
+ async function refresh(refreshToken) {
61
+ const payload = await verifyToken(refreshToken, "refresh");
62
+ if (!payload || !payload.sub) {
63
+ throw new Error("Invalid or expired refresh token");
64
+ }
65
+ const user = await adapter.findUserById(payload.sub);
66
+ if (!user) {
67
+ throw new Error("User not found");
68
+ }
69
+ const accessToken = await generateToken(user, "access");
70
+ return { accessToken };
71
+ }
72
+ async function signup(userData, password) {
73
+ let dataToSave = { ...userData };
74
+ if (password) {
75
+ const passwordWithPepper = pepper ? `${password}${pepper}` : password;
76
+ dataToSave.passwordHash = await argon2.hash(passwordWithPepper);
77
+ }
78
+ const newUser = await adapter.createUser(dataToSave);
79
+ const accessToken = await generateToken(newUser, "access");
80
+ const refreshToken = await generateToken(newUser, "refresh");
81
+ return { user: newUser, accessToken, refreshToken };
82
+ }
83
+ async function loginWithPassword(email, password) {
84
+ const user = await adapter.findUserByEmail(email);
85
+ const dummyHash = "$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RytpInY7i6C9M5l0D4n8Q+7j/J+i";
86
+ const targetHash = user?.passwordHash || dummyHash;
87
+ const passwordWithPepper = pepper ? `${password}${pepper}` : password;
88
+ const isValid = await argon2.verify(targetHash, passwordWithPepper);
89
+ if (!user || !user.passwordHash || !isValid) {
90
+ throw new Error("Invalid credentials");
91
+ }
92
+ const accessToken = await generateToken(user, "access");
93
+ const refreshToken = await generateToken(user, "refresh");
94
+ return { user, accessToken, refreshToken };
95
+ }
96
+ return {
97
+ signup,
98
+ loginWithPassword,
99
+ refresh,
100
+ verifyToken,
101
+ generateToken,
102
+ _providers: providers
103
+ };
104
+ }
105
+ function generateSecret(length = 32) {
106
+ return import_crypto.default.getRandomValues(new Uint8Array(length));
107
+ }
108
+ // Annotate the CommonJS export names for ESM import in node:
109
+ 0 && (module.exports = {
110
+ createAuth,
111
+ generateSecret
112
+ });
113
+ //# sourceMappingURL=core.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/core.ts"],
4
+ "sourcesContent": ["import * as argon2 from \"argon2\";\r\nimport { SignJWT, jwtVerify } from \"jose\";\r\nimport crypto from \"crypto\";\r\nimport type { AuthAdapter, User } from \"./adapter.js\";\r\nimport type { Provider } from \"./providers.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
+ "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
+ "names": ["crypto"]
7
+ }
@@ -9,6 +9,15 @@ export interface CreateAuthOptions {
9
9
  refreshExpires?: string | number;
10
10
  };
11
11
  providers?: Provider[];
12
+ jwt?: {
13
+ /**
14
+ * A callback to add custom fields to the JWT payload.
15
+ * It receives the user object and the token type ('access' or 'refresh').
16
+ * Return an object containing the fields to be merged into the payload.
17
+ * You can also override default fields like 'sub'.
18
+ */
19
+ payload?: (user: User<any>, type: "access" | "refresh") => Record<string, any>;
20
+ };
12
21
  }
13
22
  export declare function createAuth(options: CreateAuthOptions): {
14
23
  signup: (userData: Omit<User<any>, "id">, password?: string) => Promise<{
@@ -24,12 +33,13 @@ export declare function createAuth(options: CreateAuthOptions): {
24
33
  refresh: (refreshToken: string) => Promise<{
25
34
  accessToken: string;
26
35
  }>;
27
- verifyToken: (token: string, expectedType?: "access" | "refresh") => Promise<import("jose").JWTPayload>;
36
+ verifyToken: (token: string, expectedType?: "access" | "refresh") => Promise<import("jose").JWTPayload | null>;
28
37
  generateToken: (user: User<any>, type?: "access" | "refresh") => Promise<string>;
29
- _providers: Provider[];
38
+ _providers: Provider[] | undefined;
30
39
  };
31
40
  /**
32
41
  * Utility to generate a high-entropy cryptographically secure secret.
33
42
  * Useful for initializing the 'secret' option in createAuth.
34
43
  */
35
44
  export declare function generateSecret(length?: number): Uint8Array;
45
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/auth/core.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,iBAAiB;IAC9B,OAAO,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KACpC,CAAC;IACF,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,GAAG,CAAC,EAAE;QACF;;;;;WAKG;QACH,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,QAAQ,GAAG,SAAS,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;KAClF,CAAC;CACL;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB;uBA4DjB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,aAAa,MAAM;;;;;+BAmBhC,MAAM,YAAY,MAAM;;;;;4BAtC3B,MAAM;;;yBAbT,MAAM,iBAAgB,QAAQ,GAAG,SAAS;0BAnBzC,IAAI,CAAC,GAAG,CAAC,SAAQ,QAAQ,GAAG,SAAS;;EAmG3E;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,MAAW,GAAG,UAAU,CAE9D"}
package/dist/core.js ADDED
@@ -0,0 +1,78 @@
1
+ import * as argon2 from "argon2";
2
+ import { SignJWT, jwtVerify } from "jose";
3
+ import crypto from "crypto";
4
+ function createAuth(options) {
5
+ const { adapter, secret, pepper, session, providers } = options;
6
+ const encodedSecret = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
7
+ const expiration = session?.expires || "1h";
8
+ const refreshExpiration = session?.refreshExpires || "7d";
9
+ async function generateToken(user, type = "access") {
10
+ let payload = { sub: user.id, role: user.role, type };
11
+ if (options.jwt?.payload) {
12
+ const customPayload = options.jwt.payload(user, type);
13
+ payload = { ...payload, ...customPayload };
14
+ }
15
+ return new SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().setExpirationTime(type === "access" ? expiration : refreshExpiration).sign(encodedSecret);
16
+ }
17
+ async function verifyToken(token, expectedType = "access") {
18
+ try {
19
+ const { payload } = await jwtVerify(token, encodedSecret);
20
+ if (payload.type !== expectedType) return null;
21
+ return payload;
22
+ } catch (e) {
23
+ return null;
24
+ }
25
+ }
26
+ async function refresh(refreshToken) {
27
+ const payload = await verifyToken(refreshToken, "refresh");
28
+ if (!payload || !payload.sub) {
29
+ throw new Error("Invalid or expired refresh token");
30
+ }
31
+ const user = await adapter.findUserById(payload.sub);
32
+ if (!user) {
33
+ throw new Error("User not found");
34
+ }
35
+ const accessToken = await generateToken(user, "access");
36
+ return { accessToken };
37
+ }
38
+ async function signup(userData, password) {
39
+ let dataToSave = { ...userData };
40
+ if (password) {
41
+ const passwordWithPepper = pepper ? `${password}${pepper}` : password;
42
+ dataToSave.passwordHash = await argon2.hash(passwordWithPepper);
43
+ }
44
+ const newUser = await adapter.createUser(dataToSave);
45
+ const accessToken = await generateToken(newUser, "access");
46
+ const refreshToken = await generateToken(newUser, "refresh");
47
+ return { user: newUser, accessToken, refreshToken };
48
+ }
49
+ async function loginWithPassword(email, password) {
50
+ const user = await adapter.findUserByEmail(email);
51
+ const dummyHash = "$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RytpInY7i6C9M5l0D4n8Q+7j/J+i";
52
+ const targetHash = user?.passwordHash || dummyHash;
53
+ const passwordWithPepper = pepper ? `${password}${pepper}` : password;
54
+ const isValid = await argon2.verify(targetHash, passwordWithPepper);
55
+ if (!user || !user.passwordHash || !isValid) {
56
+ throw new Error("Invalid credentials");
57
+ }
58
+ const accessToken = await generateToken(user, "access");
59
+ const refreshToken = await generateToken(user, "refresh");
60
+ return { user, accessToken, refreshToken };
61
+ }
62
+ return {
63
+ signup,
64
+ loginWithPassword,
65
+ refresh,
66
+ verifyToken,
67
+ generateToken,
68
+ _providers: providers
69
+ };
70
+ }
71
+ function generateSecret(length = 32) {
72
+ return crypto.getRandomValues(new Uint8Array(length));
73
+ }
74
+ export {
75
+ createAuth,
76
+ generateSecret
77
+ };
78
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/core.ts"],
4
+ "sourcesContent": ["import * as argon2 from \"argon2\";\r\nimport { SignJWT, jwtVerify } from \"jose\";\r\nimport crypto from \"crypto\";\r\nimport type { AuthAdapter, User } from \"./adapter.js\";\r\nimport type { Provider } from \"./providers.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
+ "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
+ "names": []
7
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+ var index_exports = {};
21
+ __export(index_exports, {
22
+ GitHub: () => import_providers.GitHub,
23
+ Google: () => import_providers.Google,
24
+ createAuth: () => import_core.createAuth,
25
+ createMemoryAdapter: () => import_memoryAdapter.createMemoryAdapter,
26
+ generateSecret: () => import_core.generateSecret
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+ var import_providers = require("./providers.js");
30
+ var import_core = require("./core.js");
31
+ var import_memoryAdapter = require("./memoryAdapter.js");
32
+ __reExport(index_exports, require("./security.js"), module.exports);
33
+ // Annotate the CommonJS export names for ESM import in node:
34
+ 0 && (module.exports = {
35
+ GitHub,
36
+ Google,
37
+ createAuth,
38
+ createMemoryAdapter,
39
+ generateSecret,
40
+ ...require("./security.js")
41
+ });
42
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/index.ts"],
4
+ "sourcesContent": ["export type { AuthAdapter, User, BaseUser } from \"./adapter.js\";\r\nexport { GitHub, Google } from \"./providers.js\";\r\nexport type { Provider, ProviderConfig } from \"./providers.js\";\r\nexport { createAuth, generateSecret } from \"./core.js\";\r\nexport type { CreateAuthOptions } from \"./core.js\";\r\nexport { createMemoryAdapter } from \"./memoryAdapter.js\";\r\nexport * from \"./security.js\";\r\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,uBAA+B;AAE/B,kBAA2C;AAE3C,2BAAoC;AACpC,0BAAc,0BANd;",
6
+ "names": []
7
+ }
@@ -5,3 +5,4 @@ export { createAuth, generateSecret } from "./core.js";
5
5
  export type { CreateAuthOptions } from "./core.js";
6
6
  export { createMemoryAdapter } from "./memoryAdapter.js";
7
7
  export * from "./security.js";
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACvD,YAAY,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,cAAc,eAAe,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { GitHub, Google } from "./providers.js";
2
+ import { createAuth, generateSecret } from "./core.js";
3
+ import { createMemoryAdapter } from "./memoryAdapter.js";
4
+ export * from "./security.js";
5
+ export {
6
+ GitHub,
7
+ Google,
8
+ createAuth,
9
+ createMemoryAdapter,
10
+ generateSecret
11
+ };
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/index.ts"],
4
+ "sourcesContent": ["export type { AuthAdapter, User, BaseUser } from \"./adapter.js\";\r\nexport { GitHub, Google } from \"./providers.js\";\r\nexport type { Provider, ProviderConfig } from \"./providers.js\";\r\nexport { createAuth, generateSecret } from \"./core.js\";\r\nexport type { CreateAuthOptions } from \"./core.js\";\r\nexport { createMemoryAdapter } from \"./memoryAdapter.js\";\r\nexport * from \"./security.js\";\r\n"],
5
+ "mappings": "AACA,SAAS,QAAQ,cAAc;AAE/B,SAAS,YAAY,sBAAsB;AAE3C,SAAS,2BAA2B;AACpC,cAAc;",
6
+ "names": []
7
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var memoryAdapter_exports = {};
20
+ __export(memoryAdapter_exports, {
21
+ createMemoryAdapter: () => createMemoryAdapter
22
+ });
23
+ module.exports = __toCommonJS(memoryAdapter_exports);
24
+ function createMemoryAdapter() {
25
+ const users = /* @__PURE__ */ new Map();
26
+ const accounts = /* @__PURE__ */ new Map();
27
+ return {
28
+ createUser: async (data) => {
29
+ const id = data.id || Date.now().toString();
30
+ const newUser = { ...data, id };
31
+ users.set(newUser.email, newUser);
32
+ return newUser;
33
+ },
34
+ findUserByEmail: async (email) => {
35
+ return users.get(email) || null;
36
+ },
37
+ findUserById: async (id) => {
38
+ for (const user of users.values()) {
39
+ if (user.id === id) {
40
+ return user;
41
+ }
42
+ }
43
+ return null;
44
+ },
45
+ linkOAuthAccount: async (userId, provider, providerId) => {
46
+ const accountId = `${provider}_${providerId}`;
47
+ accounts.set(accountId, { userId, provider, providerId });
48
+ }
49
+ };
50
+ }
51
+ // Annotate the CommonJS export names for ESM import in node:
52
+ 0 && (module.exports = {
53
+ createMemoryAdapter
54
+ });
55
+ //# sourceMappingURL=memoryAdapter.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/memoryAdapter.ts"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./adapter.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
+ "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
+ "names": []
7
+ }
@@ -5,3 +5,4 @@ import type { AuthAdapter, User } from "./adapter.js";
5
5
  * All data is kept in memory and is lost when the server restarts.
6
6
  */
7
7
  export declare function createMemoryAdapter<TUser extends User = User>(): AuthAdapter<TUser>;
8
+ //# sourceMappingURL=memoryAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memoryAdapter.d.ts","sourceRoot":"","sources":["../src/auth/memoryAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEtD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,SAAS,IAAI,GAAG,IAAI,KAAK,WAAW,CAAC,KAAK,CAAC,CAiCnF"}
@@ -0,0 +1,31 @@
1
+ function createMemoryAdapter() {
2
+ const users = /* @__PURE__ */ new Map();
3
+ const accounts = /* @__PURE__ */ new Map();
4
+ return {
5
+ createUser: async (data) => {
6
+ const id = data.id || Date.now().toString();
7
+ const newUser = { ...data, id };
8
+ users.set(newUser.email, newUser);
9
+ return newUser;
10
+ },
11
+ findUserByEmail: async (email) => {
12
+ return users.get(email) || null;
13
+ },
14
+ findUserById: async (id) => {
15
+ for (const user of users.values()) {
16
+ if (user.id === id) {
17
+ return user;
18
+ }
19
+ }
20
+ return null;
21
+ },
22
+ linkOAuthAccount: async (userId, provider, providerId) => {
23
+ const accountId = `${provider}_${providerId}`;
24
+ accounts.set(accountId, { userId, provider, providerId });
25
+ }
26
+ };
27
+ }
28
+ export {
29
+ createMemoryAdapter
30
+ };
31
+ //# sourceMappingURL=memoryAdapter.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/memoryAdapter.ts"],
4
+ "sourcesContent": ["import type { AuthAdapter, User } from \"./adapter.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
+ "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
+ "names": []
7
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var providers_exports = {};
20
+ __export(providers_exports, {
21
+ GitHub: () => GitHub,
22
+ Google: () => Google
23
+ });
24
+ module.exports = __toCommonJS(providers_exports);
25
+ var import_arctic = require("arctic");
26
+ function GitHub(config) {
27
+ return {
28
+ id: "github",
29
+ handler: new import_arctic.GitHub(config.clientId, config.clientSecret, null)
30
+ };
31
+ }
32
+ function Google(config) {
33
+ if (!config.redirectURI) {
34
+ throw new Error("redirectURI is required for Google OAuth provider");
35
+ }
36
+ return {
37
+ id: "google",
38
+ handler: new import_arctic.Google(
39
+ config.clientId,
40
+ config.clientSecret,
41
+ config.redirectURI
42
+ )
43
+ };
44
+ }
45
+ // Annotate the CommonJS export names for ESM import in node:
46
+ 0 && (module.exports = {
47
+ GitHub,
48
+ Google
49
+ });
50
+ //# sourceMappingURL=providers.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/providers.ts"],
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
+ "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
+ "names": ["ArcticGitHub", "ArcticGoogle"]
7
+ }
@@ -9,3 +9,4 @@ export interface Provider {
9
9
  }
10
10
  export declare function GitHub(config: ProviderConfig): Provider;
11
11
  export declare function Google(config: ProviderConfig): Provider;
12
+ //# sourceMappingURL=providers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/auth/providers.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,GAAG,CAAC;CAChB;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,QAAQ,CAKvD;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,QAAQ,CAYvD"}
@@ -0,0 +1,25 @@
1
+ import { GitHub as ArcticGitHub, Google as ArcticGoogle } from "arctic";
2
+ function GitHub(config) {
3
+ return {
4
+ id: "github",
5
+ handler: new ArcticGitHub(config.clientId, config.clientSecret, null)
6
+ };
7
+ }
8
+ function Google(config) {
9
+ if (!config.redirectURI) {
10
+ throw new Error("redirectURI is required for Google OAuth provider");
11
+ }
12
+ return {
13
+ id: "google",
14
+ handler: new ArcticGoogle(
15
+ config.clientId,
16
+ config.clientSecret,
17
+ config.redirectURI
18
+ )
19
+ };
20
+ }
21
+ export {
22
+ GitHub,
23
+ Google
24
+ };
25
+ //# sourceMappingURL=providers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/providers.ts"],
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
+ "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
+ "names": []
7
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
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
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var security_exports = {};
30
+ __export(security_exports, {
31
+ generateCsrfToken: () => generateCsrfToken,
32
+ verifyCsrf: () => verifyCsrf
33
+ });
34
+ module.exports = __toCommonJS(security_exports);
35
+ var import_crypto = __toESM(require("crypto"), 1);
36
+ function generateCsrfToken() {
37
+ return import_crypto.default.randomBytes(32).toString("hex");
38
+ }
39
+ function verifyCsrf(tokenInRequest, tokenInCookie) {
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
+ }
49
+ }
50
+ // Annotate the CommonJS export names for ESM import in node:
51
+ 0 && (module.exports = {
52
+ generateCsrfToken,
53
+ verifyCsrf
54
+ });
55
+ //# sourceMappingURL=security.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/security.ts"],
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 // Constant time comparison\r\n try {\r\n return crypto.timingSafeEqual(\r\n Buffer.from(tokenInRequest),\r\n Buffer.from(tokenInCookie)\r\n );\r\n } catch {\r\n return false;\r\n }\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;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;",
6
+ "names": ["crypto"]
7
+ }
@@ -14,3 +14,4 @@ export declare function verifyCsrf(tokenInRequest: string, tokenInCookie: string
14
14
  * 2. Use a 'pepper' in createAuth to protect hashes.
15
15
  * 3. Implement rate limiting on /login and /register endpoints.
16
16
  */
17
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/auth/security.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"}
@@ -0,0 +1,20 @@
1
+ import crypto from "crypto";
2
+ function generateCsrfToken() {
3
+ return crypto.randomBytes(32).toString("hex");
4
+ }
5
+ function verifyCsrf(tokenInRequest, tokenInCookie) {
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
+ }
15
+ }
16
+ export {
17
+ generateCsrfToken,
18
+ verifyCsrf
19
+ };
20
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth/security.ts"],
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 // Constant time comparison\r\n try {\r\n return crypto.timingSafeEqual(\r\n Buffer.from(tokenInRequest),\r\n Buffer.from(tokenInCookie)\r\n );\r\n } catch {\r\n return false;\r\n }\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;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;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,45 +1,56 @@
1
1
  {
2
2
  "name": "kroxt",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "license": "MIT",
5
5
  "description": "A framework-agnostic modular auth engine",
6
6
  "type": "module",
7
- "main": "./dist-lib/index.js",
8
- "module": "./dist-lib/index.js",
9
- "types": "./dist-lib/index.d.ts",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
- "types": "./dist-lib/index.d.ts",
13
- "import": "./dist-lib/index.js"
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
14
15
  },
15
16
  "./adapter": {
16
- "types": "./dist-lib/adapter.d.ts",
17
- "import": "./dist-lib/adapter.js"
17
+ "types": "./dist/adapter.d.ts",
18
+ "import": "./dist/adapter.js",
19
+ "require": "./dist/adapter.cjs"
18
20
  },
19
21
  "./core": {
20
- "types": "./dist-lib/core.d.ts",
21
- "import": "./dist-lib/core.js"
22
+ "types": "./dist/core.d.ts",
23
+ "import": "./dist/core.js",
24
+ "require": "./dist/core.cjs"
22
25
  },
23
26
  "./providers": {
24
- "types": "./dist-lib/providers.d.ts",
25
- "import": "./dist-lib/providers.js"
27
+ "types": "./dist/providers.d.ts",
28
+ "import": "./dist/providers.js",
29
+ "require": "./dist/providers.cjs"
26
30
  },
27
31
  "./memoryAdapter": {
28
- "types": "./dist-lib/memoryAdapter.d.ts",
29
- "import": "./dist-lib/memoryAdapter.js"
32
+ "types": "./dist/memoryAdapter.d.ts",
33
+ "import": "./dist/memoryAdapter.js",
34
+ "require": "./dist/memoryAdapter.cjs"
30
35
  }
31
36
  },
32
37
  "files": [
33
- "dist-lib",
38
+ "dist",
34
39
  "README.md"
35
40
  ],
36
41
  "scripts": {
37
- "build": "npx tsc src/auth/index.ts --outDir dist-lib --module nodenext --declaration"
42
+ "build": "node build.js"
38
43
  },
39
44
  "dependencies": {
40
45
  "arctic": "^3.7.0",
41
46
  "argon2": "^0.44.0",
42
47
  "jose": "^6.2.1",
43
48
  "zod": "^3.23.8"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^25.5.0",
52
+ "esbuild": "^0.27.4",
53
+ "tsup": "^8.5.1",
54
+ "typescript": "^5.9.3"
44
55
  }
45
56
  }
@@ -1 +0,0 @@
1
- export {};
package/dist-lib/core.js DELETED
@@ -1,98 +0,0 @@
1
- import * as argon2 from "argon2";
2
- import { SignJWT, jwtVerify } from "jose";
3
- import crypto from "crypto";
4
- export function createAuth(options) {
5
- const { adapter, secret, pepper, session, providers } = options;
6
- const encodedSecret = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
7
- const expiration = session?.expires || "1h"; // Default access token to 1h
8
- const refreshExpiration = session?.refreshExpires || "7d";
9
- /**
10
- * Generates a stateless JWT for a user session
11
- */
12
- async function generateToken(user, type = "access") {
13
- return new SignJWT({ sub: user.id, role: user.role, type })
14
- .setProtectedHeader({ alg: "HS256" })
15
- .setIssuedAt()
16
- .setExpirationTime(type === "access" ? expiration : refreshExpiration)
17
- .sign(encodedSecret);
18
- }
19
- /**
20
- * Verifies a JWT and returns the payload.
21
- * Optionally checks for a specific token type (access/refresh).
22
- */
23
- async function verifyToken(token, expectedType = "access") {
24
- try {
25
- const { payload } = await jwtVerify(token, encodedSecret);
26
- if (payload.type !== expectedType)
27
- return null;
28
- return payload;
29
- }
30
- catch (e) {
31
- return null;
32
- }
33
- }
34
- /**
35
- * Refreshes an access token using a valid refresh token.
36
- */
37
- async function refresh(refreshToken) {
38
- const payload = await verifyToken(refreshToken, "refresh");
39
- if (!payload || !payload.sub) {
40
- throw new Error("Invalid or expired refresh token");
41
- }
42
- const user = await adapter.findUserById(payload.sub);
43
- if (!user) {
44
- throw new Error("User not found");
45
- }
46
- const accessToken = await generateToken(user, "access");
47
- return { accessToken };
48
- }
49
- /**
50
- * Signup with a new user payload.
51
- * Incorporates server-side pepper for password hashing if provided.
52
- */
53
- async function signup(userData, password) {
54
- let dataToSave = { ...userData };
55
- if (password) {
56
- const passwordWithPepper = pepper ? `${password}${pepper}` : password;
57
- dataToSave.passwordHash = await argon2.hash(passwordWithPepper);
58
- }
59
- const newUser = await adapter.createUser(dataToSave);
60
- const accessToken = await generateToken(newUser, "access");
61
- const refreshToken = await generateToken(newUser, "refresh");
62
- return { user: newUser, accessToken, refreshToken };
63
- }
64
- /**
65
- * Standard Email/Password Login.
66
- * Includes timing attack protection and password peppering.
67
- */
68
- async function loginWithPassword(email, password) {
69
- const user = await adapter.findUserByEmail(email);
70
- // Timing attack protection: Always verify a hash, even if user doesn't exist.
71
- // We use a dummy hash to keep execution time consistent.
72
- const dummyHash = "$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$RytpInY7i6C9M5l0D4n8Q+7j/J+i";
73
- const targetHash = user?.passwordHash || dummyHash;
74
- const passwordWithPepper = pepper ? `${password}${pepper}` : password;
75
- const isValid = await argon2.verify(targetHash, passwordWithPepper);
76
- if (!user || !user.passwordHash || !isValid) {
77
- throw new Error("Invalid credentials");
78
- }
79
- const accessToken = await generateToken(user, "access");
80
- const refreshToken = await generateToken(user, "refresh");
81
- return { user, accessToken, refreshToken };
82
- }
83
- return {
84
- signup,
85
- loginWithPassword,
86
- refresh,
87
- verifyToken,
88
- generateToken,
89
- _providers: providers
90
- };
91
- }
92
- /**
93
- * Utility to generate a high-entropy cryptographically secure secret.
94
- * Useful for initializing the 'secret' option in createAuth.
95
- */
96
- export function generateSecret(length = 32) {
97
- return crypto.getRandomValues(new Uint8Array(length));
98
- }
package/dist-lib/index.js DELETED
@@ -1,4 +0,0 @@
1
- export { GitHub, Google } from "./providers.js";
2
- export { createAuth, generateSecret } from "./core.js";
3
- export { createMemoryAdapter } from "./memoryAdapter.js";
4
- export * from "./security.js";
@@ -1,34 +0,0 @@
1
- /**
2
- * Creates an in-memory database adapter for the auth engine.
3
- * This is useful for testing, prototyping, or when you don't need persistent storage.
4
- * All data is kept in memory and is lost when the server restarts.
5
- */
6
- export function createMemoryAdapter() {
7
- const users = new Map();
8
- const accounts = new Map();
9
- return {
10
- createUser: async (data) => {
11
- // Auto-generate ID if not provided
12
- const id = data.id || Date.now().toString();
13
- const newUser = { ...data, id };
14
- // Store using email as the primary lookup key
15
- users.set(newUser.email, newUser);
16
- return newUser;
17
- },
18
- findUserByEmail: async (email) => {
19
- return users.get(email) || null;
20
- },
21
- findUserById: async (id) => {
22
- for (const user of users.values()) {
23
- if (user.id === id) {
24
- return user;
25
- }
26
- }
27
- return null;
28
- },
29
- linkOAuthAccount: async (userId, provider, providerId) => {
30
- const accountId = `${provider}_${providerId}`;
31
- accounts.set(accountId, { userId, provider, providerId });
32
- }
33
- };
34
- }
@@ -1,16 +0,0 @@
1
- import { GitHub as ArcticGitHub, Google as ArcticGoogle } from "arctic";
2
- export function GitHub(config) {
3
- return {
4
- id: "github",
5
- handler: new ArcticGitHub(config.clientId, config.clientSecret, null),
6
- };
7
- }
8
- export function Google(config) {
9
- if (!config.redirectURI) {
10
- throw new Error("redirectURI is required for Google OAuth provider");
11
- }
12
- return {
13
- id: "google",
14
- handler: new ArcticGoogle(config.clientId, config.clientSecret, config.redirectURI),
15
- };
16
- }
@@ -1,29 +0,0 @@
1
- import crypto from "crypto";
2
- /**
3
- * Generates a stateless CSRF token using the double-submit cookie pattern.
4
- * This is recommended for Express/Kroxt setups using cookies for sessions.
5
- */
6
- export function generateCsrfToken() {
7
- return crypto.randomBytes(32).toString("hex");
8
- }
9
- /**
10
- * Simple middleware-ready check for CSRF tokens.
11
- * Matches a token from the request body/headers against a cookie.
12
- */
13
- export function verifyCsrf(tokenInRequest, tokenInCookie) {
14
- if (!tokenInRequest || !tokenInCookie)
15
- return false;
16
- // Constant time comparison
17
- try {
18
- return crypto.timingSafeEqual(Buffer.from(tokenInRequest), Buffer.from(tokenInCookie));
19
- }
20
- catch {
21
- return false;
22
- }
23
- }
24
- /**
25
- * Security Recommendations for Kroxt:
26
- * 1. Always set cookies with: httpOnly: true, secure: true, sameSite: 'strict'
27
- * 2. Use a 'pepper' in createAuth to protect hashes.
28
- * 3. Implement rate limiting on /login and /register endpoints.
29
- */