kroxt 1.0.0
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 +82 -0
- package/dist-lib/adapter.d.ts +13 -0
- package/dist-lib/adapter.js +1 -0
- package/dist-lib/core.d.ts +23 -0
- package/dist-lib/core.js +67 -0
- package/dist-lib/index.d.ts +6 -0
- package/dist-lib/index.js +3 -0
- package/dist-lib/memoryAdapter.d.ts +7 -0
- package/dist-lib/memoryAdapter.js +34 -0
- package/dist-lib/providers.d.ts +11 -0
- package/dist-lib/providers.js +16 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# kroxt
|
|
2
|
+
|
|
3
|
+
A framework-agnostic, modular authentication engine for modern TypeScript applications. Built for security, extensibility, and ease of use.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Secure Hashing**: Powered by `argon2` for industry-standard password security.
|
|
8
|
+
- 🎟️ **Stateless Sessions**: Managed via `jose` with high-performance JWT signing and verification.
|
|
9
|
+
- 🌍 **OAuth Ready**: Built-in support for GitHub and Google OAuth via `arctic`.
|
|
10
|
+
- 🧩 **Database Agnostic**: Use Mongoose, Prisma, Drizzle, or even in-memory stores via the `AuthAdapter` pattern.
|
|
11
|
+
- ✅ **Zod Schema Support**: Perfectly preserves and types your extended user metadata.
|
|
12
|
+
- 🚀 **ESM First**: Native support for NodeNext module resolution.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install kroxt
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Initialize the Auth Engine
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { createAuth } from "kroxt";
|
|
26
|
+
import { myDatabaseAdapter } from "./myAdapter.js";
|
|
27
|
+
|
|
28
|
+
const auth = createAuth({
|
|
29
|
+
adapter: myDatabaseAdapter,
|
|
30
|
+
secret: process.env.AUTH_SECRET,
|
|
31
|
+
session: {
|
|
32
|
+
expires: "7d" // jose compatible duration
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Sign Up a User
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const { user, token } = await auth.signup({
|
|
41
|
+
email: "user@example.com",
|
|
42
|
+
firstName: "Tobi",
|
|
43
|
+
role: "tenant",
|
|
44
|
+
// ...any other extended fields supported by your adapter
|
|
45
|
+
}, "strong-password-123");
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Log In
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const { user, token } = await auth.loginWithPassword("user@example.com", "password");
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 4. Verify a Session
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const payload = await auth.verifyToken(token);
|
|
58
|
+
// auth.verifyToken returns the signed payload { sub: string, role: string, ... }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## The Adapter Pattern
|
|
62
|
+
|
|
63
|
+
Gatekeeper doesn't care which DB you use. You just need to implement the `AuthAdapter` interface:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import type { AuthAdapter, User } from "kroxt/adapter";
|
|
67
|
+
|
|
68
|
+
export const myAdapter: AuthAdapter = {
|
|
69
|
+
createUser: async (data) => { /* logic */ },
|
|
70
|
+
findUserByEmail: async (email) => { /* logic */ },
|
|
71
|
+
findUserById: async (id) => { /* logic */ },
|
|
72
|
+
linkOAuthAccount: async (user, provider, provId) => { /* logic */ }
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Reference Project
|
|
77
|
+
|
|
78
|
+
Check out the `test-project` folder for a complete **Express + MongoDB** implementation using this library.
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
ISC
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface BaseUser {
|
|
2
|
+
id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
passwordHash?: string;
|
|
5
|
+
role?: string;
|
|
6
|
+
}
|
|
7
|
+
export type User<TExtended = Record<string, any>> = BaseUser & TExtended;
|
|
8
|
+
export interface AuthAdapter<TUser = User> {
|
|
9
|
+
createUser: (data: any) => Promise<TUser>;
|
|
10
|
+
findUserByEmail: (email: string) => Promise<TUser | null>;
|
|
11
|
+
findUserById: (id: string) => Promise<TUser | null>;
|
|
12
|
+
linkOAuthAccount: (userId: string, provider: string, providerId: string) => Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AuthAdapter, User } from "./adapter.js";
|
|
2
|
+
import type { Provider } from "./providers.js";
|
|
3
|
+
export interface CreateAuthOptions {
|
|
4
|
+
adapter: AuthAdapter<any>;
|
|
5
|
+
secret: string;
|
|
6
|
+
session?: {
|
|
7
|
+
expires?: string | number;
|
|
8
|
+
};
|
|
9
|
+
providers?: Provider[];
|
|
10
|
+
}
|
|
11
|
+
export declare function createAuth(options: CreateAuthOptions): {
|
|
12
|
+
signup: (userData: Omit<User<any>, "id">, password?: string) => Promise<{
|
|
13
|
+
user: any;
|
|
14
|
+
token: string;
|
|
15
|
+
}>;
|
|
16
|
+
loginWithPassword: (email: string, password: string) => Promise<{
|
|
17
|
+
user: any;
|
|
18
|
+
token: string;
|
|
19
|
+
}>;
|
|
20
|
+
verifyToken: (token: string) => Promise<import("jose").JWTPayload>;
|
|
21
|
+
generateToken: (user: User<any>) => Promise<string>;
|
|
22
|
+
_providers: Provider[];
|
|
23
|
+
};
|
package/dist-lib/core.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as argon2 from "argon2";
|
|
2
|
+
import { SignJWT, jwtVerify } from "jose";
|
|
3
|
+
export function createAuth(options) {
|
|
4
|
+
const { adapter, secret, session, providers } = options;
|
|
5
|
+
const encodedSecret = new TextEncoder().encode(secret);
|
|
6
|
+
const expiration = session?.expires || "7d";
|
|
7
|
+
/**
|
|
8
|
+
* Generates a stateless JWT for a user session
|
|
9
|
+
*/
|
|
10
|
+
async function generateToken(user) {
|
|
11
|
+
return new SignJWT({ sub: user.id, role: user.role })
|
|
12
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
13
|
+
.setIssuedAt()
|
|
14
|
+
.setExpirationTime(expiration)
|
|
15
|
+
.sign(encodedSecret);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Verifies a JWT and returns the payload
|
|
19
|
+
*/
|
|
20
|
+
async function verifyToken(token) {
|
|
21
|
+
try {
|
|
22
|
+
const { payload } = await jwtVerify(token, encodedSecret);
|
|
23
|
+
return payload;
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Signup with a new user payload (handles extended schemas out-of-the-box).
|
|
31
|
+
* Generates a password hash if password is provided.
|
|
32
|
+
*/
|
|
33
|
+
async function signup(userData, password) {
|
|
34
|
+
let dataToSave = { ...userData };
|
|
35
|
+
if (password) {
|
|
36
|
+
dataToSave.passwordHash = await argon2.hash(password);
|
|
37
|
+
}
|
|
38
|
+
const newUser = await adapter.createUser(dataToSave);
|
|
39
|
+
const token = await generateToken(newUser);
|
|
40
|
+
return { user: newUser, token };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Standard Email/Password Login
|
|
44
|
+
*/
|
|
45
|
+
async function loginWithPassword(email, password) {
|
|
46
|
+
const user = await adapter.findUserByEmail(email);
|
|
47
|
+
if (!user) {
|
|
48
|
+
throw new Error("Invalid credentials");
|
|
49
|
+
}
|
|
50
|
+
if (!user.passwordHash) {
|
|
51
|
+
throw new Error("User does not have a password setup. Did they use OAuth?");
|
|
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 };
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
signup,
|
|
62
|
+
loginWithPassword,
|
|
63
|
+
verifyToken,
|
|
64
|
+
generateToken,
|
|
65
|
+
_providers: providers // Exposing to internal router mechanisms if needed
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { AuthAdapter, User, BaseUser } from "./adapter.js";
|
|
2
|
+
export { GitHub, Google } from "./providers.js";
|
|
3
|
+
export type { Provider, ProviderConfig } from "./providers.js";
|
|
4
|
+
export { createAuth } from "./core.js";
|
|
5
|
+
export type { CreateAuthOptions } from "./core.js";
|
|
6
|
+
export { createMemoryAdapter } from "./memoryAdapter.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AuthAdapter, User } from "./adapter.js";
|
|
2
|
+
/**
|
|
3
|
+
* Creates an in-memory database adapter for the auth engine.
|
|
4
|
+
* This is useful for testing, prototyping, or when you don't need persistent storage.
|
|
5
|
+
* All data is kept in memory and is lost when the server restarts.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createMemoryAdapter<TUser extends User = User>(): AuthAdapter<TUser>;
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ProviderConfig {
|
|
2
|
+
clientId: string;
|
|
3
|
+
clientSecret: string;
|
|
4
|
+
redirectURI?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Provider {
|
|
7
|
+
id: string;
|
|
8
|
+
handler: any;
|
|
9
|
+
}
|
|
10
|
+
export declare function GitHub(config: ProviderConfig): Provider;
|
|
11
|
+
export declare function Google(config: ProviderConfig): Provider;
|
|
@@ -0,0 +1,16 @@
|
|
|
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/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kroxt",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A framework-agnostic modular auth engine",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist-lib/index.js",
|
|
7
|
+
"module": "./dist-lib/index.js",
|
|
8
|
+
"types": "./dist-lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist-lib/index.js",
|
|
12
|
+
"types": "./dist-lib/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./adapter": {
|
|
15
|
+
"import": "./dist-lib/adapter.js",
|
|
16
|
+
"types": "./dist-lib/adapter.d.ts"
|
|
17
|
+
},
|
|
18
|
+
"./core": {
|
|
19
|
+
"import": "./dist-lib/core.js",
|
|
20
|
+
"types": "./dist-lib/core.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./providers": {
|
|
23
|
+
"import": "./dist-lib/providers.js",
|
|
24
|
+
"types": "./dist-lib/providers.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./memoryAdapter": {
|
|
27
|
+
"import": "./dist-lib/memoryAdapter.js",
|
|
28
|
+
"types": "./dist-lib/memoryAdapter.d.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist-lib",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "npx tsc src/auth/index.ts --outDir dist-lib --module nodenext --declaration"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"arctic": "^3.7.0",
|
|
40
|
+
"argon2": "^0.44.0",
|
|
41
|
+
"jose": "^6.2.1",
|
|
42
|
+
"zod": "^3.23.8"
|
|
43
|
+
}
|
|
44
|
+
}
|