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.
- package/README.md +57 -16
- package/dist/adapter.cjs +17 -0
- package/dist/adapter.cjs.map +7 -0
- package/{dist-lib → dist}/adapter.d.ts +1 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +1 -0
- package/dist/adapter.js.map +7 -0
- package/dist/core.cjs +113 -0
- package/dist/core.cjs.map +7 -0
- package/{dist-lib → dist}/core.d.ts +12 -2
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +78 -0
- package/dist/core.js.map +7 -0
- package/dist/index.cjs +42 -0
- package/dist/index.cjs.map +7 -0
- package/{dist-lib → dist}/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +7 -0
- package/dist/memoryAdapter.cjs +55 -0
- package/dist/memoryAdapter.cjs.map +7 -0
- package/{dist-lib → dist}/memoryAdapter.d.ts +1 -0
- package/dist/memoryAdapter.d.ts.map +1 -0
- package/dist/memoryAdapter.js +31 -0
- package/dist/memoryAdapter.js.map +7 -0
- package/dist/providers.cjs +50 -0
- package/dist/providers.cjs.map +7 -0
- package/{dist-lib → dist}/providers.d.ts +1 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +25 -0
- package/dist/providers.js.map +7 -0
- package/dist/security.cjs +55 -0
- package/dist/security.cjs.map +7 -0
- package/{dist-lib → dist}/security.d.ts +1 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +20 -0
- package/dist/security.js.map +7 -0
- package/package.json +27 -16
- package/dist-lib/adapter.js +0 -1
- package/dist-lib/core.js +0 -98
- package/dist-lib/index.js +0 -4
- package/dist-lib/memoryAdapter.js +0 -34
- package/dist-lib/providers.js +0 -16
- 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
|
-
-
|
|
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:
|
|
29
|
+
### Step 1: Define your User
|
|
29
30
|
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
51
|
-
|
|
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
|
|
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
|
|
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
|
|
package/dist/adapter.cjs
ADDED
|
@@ -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
|
+
}
|
|
@@ -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"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=adapter.js.map
|
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
|
package/dist/core.js.map
ADDED
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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"}
|
package/dist/security.js
ADDED
|
@@ -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.
|
|
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
|
|
8
|
-
"module": "./dist
|
|
9
|
-
"types": "./dist
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
-
"types": "./dist
|
|
13
|
-
"import": "./dist
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
14
15
|
},
|
|
15
16
|
"./adapter": {
|
|
16
|
-
"types": "./dist
|
|
17
|
-
"import": "./dist
|
|
17
|
+
"types": "./dist/adapter.d.ts",
|
|
18
|
+
"import": "./dist/adapter.js",
|
|
19
|
+
"require": "./dist/adapter.cjs"
|
|
18
20
|
},
|
|
19
21
|
"./core": {
|
|
20
|
-
"types": "./dist
|
|
21
|
-
"import": "./dist
|
|
22
|
+
"types": "./dist/core.d.ts",
|
|
23
|
+
"import": "./dist/core.js",
|
|
24
|
+
"require": "./dist/core.cjs"
|
|
22
25
|
},
|
|
23
26
|
"./providers": {
|
|
24
|
-
"types": "./dist
|
|
25
|
-
"import": "./dist
|
|
27
|
+
"types": "./dist/providers.d.ts",
|
|
28
|
+
"import": "./dist/providers.js",
|
|
29
|
+
"require": "./dist/providers.cjs"
|
|
26
30
|
},
|
|
27
31
|
"./memoryAdapter": {
|
|
28
|
-
"types": "./dist
|
|
29
|
-
"import": "./dist
|
|
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
|
|
38
|
+
"dist",
|
|
34
39
|
"README.md"
|
|
35
40
|
],
|
|
36
41
|
"scripts": {
|
|
37
|
-
"build": "
|
|
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
|
}
|
package/dist-lib/adapter.js
DELETED
|
@@ -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,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
|
-
}
|
package/dist-lib/providers.js
DELETED
|
@@ -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
|
-
}
|
package/dist-lib/security.js
DELETED
|
@@ -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
|
-
*/
|