kroxt 1.0.1 → 1.1.1
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/dist-lib/core.d.ts +17 -5
- package/dist-lib/core.js +56 -25
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +2 -1
- package/dist-lib/security.d.ts +16 -0
- package/dist-lib/security.js +29 -0
- package/package.json +1 -1
package/dist-lib/core.d.ts
CHANGED
|
@@ -2,22 +2,34 @@ import type { AuthAdapter, User } from "./adapter.js";
|
|
|
2
2
|
import type { Provider } from "./providers.js";
|
|
3
3
|
export interface CreateAuthOptions {
|
|
4
4
|
adapter: AuthAdapter<any>;
|
|
5
|
-
secret: string;
|
|
5
|
+
secret: string | Uint8Array;
|
|
6
|
+
pepper?: string;
|
|
6
7
|
session?: {
|
|
7
8
|
expires?: string | number;
|
|
9
|
+
refreshExpires?: string | number;
|
|
8
10
|
};
|
|
9
11
|
providers?: Provider[];
|
|
10
12
|
}
|
|
11
13
|
export declare function createAuth(options: CreateAuthOptions): {
|
|
12
14
|
signup: (userData: Omit<User<any>, "id">, password?: string) => Promise<{
|
|
13
15
|
user: any;
|
|
14
|
-
|
|
16
|
+
accessToken: string;
|
|
17
|
+
refreshToken: string;
|
|
15
18
|
}>;
|
|
16
19
|
loginWithPassword: (email: string, password: string) => Promise<{
|
|
17
20
|
user: any;
|
|
18
|
-
|
|
21
|
+
accessToken: string;
|
|
22
|
+
refreshToken: string;
|
|
19
23
|
}>;
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
refresh: (refreshToken: string) => Promise<{
|
|
25
|
+
accessToken: string;
|
|
26
|
+
}>;
|
|
27
|
+
verifyToken: (token: string, expectedType?: "access" | "refresh") => Promise<import("jose").JWTPayload>;
|
|
28
|
+
generateToken: (user: User<any>, type?: "access" | "refresh") => Promise<string>;
|
|
22
29
|
_providers: Provider[];
|
|
23
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Utility to generate a high-entropy cryptographically secure secret.
|
|
33
|
+
* Useful for initializing the 'secret' option in createAuth.
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateSecret(length?: number): Uint8Array;
|
package/dist-lib/core.js
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
import * as argon2 from "argon2";
|
|
2
2
|
import { SignJWT, jwtVerify } from "jose";
|
|
3
|
+
import crypto from "crypto";
|
|
3
4
|
export function createAuth(options) {
|
|
4
|
-
const { adapter, secret, session, providers } = options;
|
|
5
|
-
const encodedSecret = new TextEncoder().encode(secret);
|
|
6
|
-
const expiration = session?.expires || "
|
|
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";
|
|
7
9
|
/**
|
|
8
10
|
* Generates a stateless JWT for a user session
|
|
9
11
|
*/
|
|
10
|
-
async function generateToken(user) {
|
|
11
|
-
return new SignJWT({ sub: user.id, role: user.role })
|
|
12
|
+
async function generateToken(user, type = "access") {
|
|
13
|
+
return new SignJWT({ sub: user.id, role: user.role, type })
|
|
12
14
|
.setProtectedHeader({ alg: "HS256" })
|
|
13
15
|
.setIssuedAt()
|
|
14
|
-
.setExpirationTime(expiration)
|
|
16
|
+
.setExpirationTime(type === "access" ? expiration : refreshExpiration)
|
|
15
17
|
.sign(encodedSecret);
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
|
-
* Verifies a JWT and returns the payload
|
|
20
|
+
* Verifies a JWT and returns the payload.
|
|
21
|
+
* Optionally checks for a specific token type (access/refresh).
|
|
19
22
|
*/
|
|
20
|
-
async function verifyToken(token) {
|
|
23
|
+
async function verifyToken(token, expectedType = "access") {
|
|
21
24
|
try {
|
|
22
25
|
const { payload } = await jwtVerify(token, encodedSecret);
|
|
26
|
+
if (payload.type !== expectedType)
|
|
27
|
+
return null;
|
|
23
28
|
return payload;
|
|
24
29
|
}
|
|
25
30
|
catch (e) {
|
|
@@ -27,41 +32,67 @@ export function createAuth(options) {
|
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
/**
|
|
30
|
-
*
|
|
31
|
-
|
|
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.
|
|
32
52
|
*/
|
|
33
53
|
async function signup(userData, password) {
|
|
34
54
|
let dataToSave = { ...userData };
|
|
35
55
|
if (password) {
|
|
36
|
-
|
|
56
|
+
const passwordWithPepper = pepper ? `${password}${pepper}` : password;
|
|
57
|
+
dataToSave.passwordHash = await argon2.hash(passwordWithPepper);
|
|
37
58
|
}
|
|
38
59
|
const newUser = await adapter.createUser(dataToSave);
|
|
39
|
-
const
|
|
40
|
-
|
|
60
|
+
const accessToken = await generateToken(newUser, "access");
|
|
61
|
+
const refreshToken = await generateToken(newUser, "refresh");
|
|
62
|
+
return { user: newUser, accessToken, refreshToken };
|
|
41
63
|
}
|
|
42
64
|
/**
|
|
43
|
-
* Standard Email/Password Login
|
|
65
|
+
* Standard Email/Password Login.
|
|
66
|
+
* Includes timing attack protection and password peppering.
|
|
44
67
|
*/
|
|
45
68
|
async function loginWithPassword(email, password) {
|
|
46
69
|
const user = await adapter.findUserByEmail(email);
|
|
47
|
-
if
|
|
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) {
|
|
48
77
|
throw new Error("Invalid credentials");
|
|
49
78
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
const isValid = await argon2.verify(user.passwordHash, password);
|
|
54
|
-
if (!isValid) {
|
|
55
|
-
throw new Error("Invalid credentials");
|
|
56
|
-
}
|
|
57
|
-
const token = await generateToken(user);
|
|
58
|
-
return { user, token };
|
|
79
|
+
const accessToken = await generateToken(user, "access");
|
|
80
|
+
const refreshToken = await generateToken(user, "refresh");
|
|
81
|
+
return { user, accessToken, refreshToken };
|
|
59
82
|
}
|
|
60
83
|
return {
|
|
61
84
|
signup,
|
|
62
85
|
loginWithPassword,
|
|
86
|
+
refresh,
|
|
63
87
|
verifyToken,
|
|
64
88
|
generateToken,
|
|
65
|
-
_providers: providers
|
|
89
|
+
_providers: providers
|
|
66
90
|
};
|
|
67
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.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type { AuthAdapter, User, BaseUser } from "./adapter.js";
|
|
2
2
|
export { GitHub, Google } from "./providers.js";
|
|
3
3
|
export type { Provider, ProviderConfig } from "./providers.js";
|
|
4
|
-
export { createAuth } from "./core.js";
|
|
4
|
+
export { createAuth, generateSecret } from "./core.js";
|
|
5
5
|
export type { CreateAuthOptions } from "./core.js";
|
|
6
6
|
export { createMemoryAdapter } from "./memoryAdapter.js";
|
|
7
|
+
export * from "./security.js";
|
package/dist-lib/index.js
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a stateless CSRF token using the double-submit cookie pattern.
|
|
3
|
+
* This is recommended for Express/Kroxt setups using cookies for sessions.
|
|
4
|
+
*/
|
|
5
|
+
export declare function generateCsrfToken(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Simple middleware-ready check for CSRF tokens.
|
|
8
|
+
* Matches a token from the request body/headers against a cookie.
|
|
9
|
+
*/
|
|
10
|
+
export declare function verifyCsrf(tokenInRequest: string, tokenInCookie: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Security Recommendations for Kroxt:
|
|
13
|
+
* 1. Always set cookies with: httpOnly: true, secure: true, sameSite: 'strict'
|
|
14
|
+
* 2. Use a 'pepper' in createAuth to protect hashes.
|
|
15
|
+
* 3. Implement rate limiting on /login and /register endpoints.
|
|
16
|
+
*/
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
*/
|