swift-auth 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -55
- package/dist/index.cjs +13 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -6
- package/dist/index.d.ts +2 -6
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ Built for **Next.js 15/16** with **Upstash Redis**, optimized for type safety an
|
|
|
37
37
|
| 📦 **Minimal Footprint** | You control what's stored in Redis |
|
|
38
38
|
| 🚀 **Zero Crypto Deps** | No external dependencies for hashing |
|
|
39
39
|
| ✅ **Production-Ready** | Battle-tested session management |
|
|
40
|
+
| 🔄 **Flexible Hashing** | Use built-in scrypt or bring your own encryption |
|
|
40
41
|
|
|
41
42
|
---
|
|
42
43
|
|
|
@@ -79,9 +80,13 @@ REDIS_URL=https://your-instance.upstash.io
|
|
|
79
80
|
REDIS_TOKEN=your_auth_token
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
### Step 3️⃣ Database Setup
|
|
83
|
+
### Step 3️⃣ Database Setup & Password Hashing
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
Choose your password hashing approach:
|
|
86
|
+
|
|
87
|
+
#### Option A: Use Swift Auth's Built-in Hashing (Recommended)
|
|
88
|
+
|
|
89
|
+
Configure your Prisma schema with the `user` model including salt:
|
|
85
90
|
|
|
86
91
|
```prisma
|
|
87
92
|
// prisma/schema.prisma
|
|
@@ -101,7 +106,7 @@ model user {
|
|
|
101
106
|
name String
|
|
102
107
|
email String @unique
|
|
103
108
|
password String
|
|
104
|
-
salt String //
|
|
109
|
+
salt String // Required for built-in hashing
|
|
105
110
|
created_at DateTime @default(now())
|
|
106
111
|
updated_at DateTime @updatedAt
|
|
107
112
|
}
|
|
@@ -109,6 +114,82 @@ model user {
|
|
|
109
114
|
|
|
110
115
|
> ⚠️ **Critical**: Salt must be stored as a `String`, not Buffer or Bytes.
|
|
111
116
|
|
|
117
|
+
#### Option B: Use Your Own Encryption Method
|
|
118
|
+
|
|
119
|
+
If you prefer your own password hashing logic, omit the `salt` field:
|
|
120
|
+
|
|
121
|
+
```prisma
|
|
122
|
+
model user {
|
|
123
|
+
id String @id @default(uuid())
|
|
124
|
+
name String
|
|
125
|
+
email String @unique
|
|
126
|
+
password String // Your pre-encrypted password
|
|
127
|
+
created_at DateTime @default(now())
|
|
128
|
+
updated_at DateTime @updatedAt
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🔒 Password Management
|
|
135
|
+
|
|
136
|
+
### Option A: Swift Auth Built-in Hashing
|
|
137
|
+
|
|
138
|
+
#### Generate Salt & Hash Password
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const salt = auth.generateSalt();
|
|
142
|
+
const hashedPassword = await auth.hashPassword("user-password", salt);
|
|
143
|
+
|
|
144
|
+
// Store both hashedPassword and salt as STRINGS in your database
|
|
145
|
+
await prisma.user.create({
|
|
146
|
+
data: {
|
|
147
|
+
email: "user@example.com",
|
|
148
|
+
password: hashedPassword,
|
|
149
|
+
salt: salt,
|
|
150
|
+
name: "John Doe",
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### Verify Password During Login
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const isValid = await auth.comparePassword({
|
|
159
|
+
password: "user-password",
|
|
160
|
+
salt,
|
|
161
|
+
hashedPassword,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (!isValid) {
|
|
165
|
+
return { success: false, message: "Invalid password" };
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Option B: Custom Encryption
|
|
170
|
+
|
|
171
|
+
Use your own encryption method before storing in the database:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import bcrypt from "bcrypt"; // or any other method
|
|
175
|
+
|
|
176
|
+
// During registration
|
|
177
|
+
const hashedPassword = await bcrypt.hash("user-password", 10);
|
|
178
|
+
|
|
179
|
+
await prisma.user.create({
|
|
180
|
+
data: {
|
|
181
|
+
email: "user@example.com",
|
|
182
|
+
password: hashedPassword,
|
|
183
|
+
name: "John Doe",
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// During login verification
|
|
188
|
+
const isValid = await bcrypt.compare("user-password", user.password);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
112
193
|
### Step 4️⃣ Create Auth Instance
|
|
113
194
|
|
|
114
195
|
```typescript
|
|
@@ -129,18 +210,18 @@ export const auth = createAuth<User>({
|
|
|
129
210
|
token: process.env.REDIS_TOKEN!,
|
|
130
211
|
},
|
|
131
212
|
ttl: 60 * 60 * 24 * 7, // 7 days
|
|
132
|
-
|
|
213
|
+
payload: ["id", "name", "email", "created_at"],
|
|
133
214
|
});
|
|
134
215
|
```
|
|
135
216
|
|
|
136
217
|
**Configuration Options:**
|
|
137
218
|
|
|
138
|
-
| Option
|
|
139
|
-
|
|
|
140
|
-
| `redis.url`
|
|
141
|
-
| `redis.token`
|
|
142
|
-
| `ttl`
|
|
143
|
-
| `
|
|
219
|
+
| Option | Type | Description |
|
|
220
|
+
| ------------- | ---------- | ------------------------- |
|
|
221
|
+
| `redis.url` | `string` | Upstash Redis URL |
|
|
222
|
+
| `redis.token` | `string` | Upstash Redis token |
|
|
223
|
+
| `ttl` | `number` | Session TTL in seconds |
|
|
224
|
+
| `payload` | `string[]` | Fields persisted in Redis |
|
|
144
225
|
|
|
145
226
|
---
|
|
146
227
|
|
|
@@ -206,43 +287,6 @@ export async function signOut() {
|
|
|
206
287
|
|
|
207
288
|
---
|
|
208
289
|
|
|
209
|
-
## 🔒 Password Security
|
|
210
|
-
|
|
211
|
-
Swift Auth provides built-in password hashing and verification with scrypt.
|
|
212
|
-
|
|
213
|
-
### Register User
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
const salt = auth.generateSalt();
|
|
217
|
-
const hashedPassword = await auth.hashPassword("user-password", salt);
|
|
218
|
-
|
|
219
|
-
// Store both hashedPassword and salt as STRINGS in your database
|
|
220
|
-
await prisma.user.create({
|
|
221
|
-
data: {
|
|
222
|
-
email: "user@example.com",
|
|
223
|
-
password: hashedPassword,
|
|
224
|
-
salt: salt,
|
|
225
|
-
name: "John Doe",
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### Verify Password During Login
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
const isValid = await auth.comparePassword({
|
|
234
|
-
password: "user-password",
|
|
235
|
-
salt,
|
|
236
|
-
hashedPassword,
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
if (!isValid) {
|
|
240
|
-
return { success: false, message: "Invalid password" };
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
---
|
|
245
|
-
|
|
246
290
|
## 📚 Full Login Example
|
|
247
291
|
|
|
248
292
|
Complete login flow with Prisma + Zod validation:
|
|
@@ -297,13 +341,17 @@ export async function signIn(formData: unknown) {
|
|
|
297
341
|
return { success: false, message: "Account not found" };
|
|
298
342
|
}
|
|
299
343
|
|
|
300
|
-
// 3. Verify password (
|
|
344
|
+
// 3. Verify password (choose your method)
|
|
345
|
+
// Option A: Using Swift Auth's built-in method
|
|
301
346
|
const isCorrectPassword = await auth.comparePassword({
|
|
302
347
|
hashedPassword: user.password,
|
|
303
348
|
password,
|
|
304
|
-
salt: user.salt, //
|
|
349
|
+
salt: user.salt, // if using built-in hashing
|
|
305
350
|
});
|
|
306
351
|
|
|
352
|
+
// Option B: Using custom encryption (e.g., bcrypt)
|
|
353
|
+
// const isCorrectPassword = await bcrypt.compare(password, user.password);
|
|
354
|
+
|
|
307
355
|
if (!isCorrectPassword) {
|
|
308
356
|
return { success: false, message: "Invalid password" };
|
|
309
357
|
}
|
|
@@ -344,7 +392,7 @@ export async function signIn(formData: unknown) {
|
|
|
344
392
|
| `updateUserSession()` | `user`, `cookieStore` | `Promise<void>` | Update existing session |
|
|
345
393
|
| `removeUserFromSession()` | `cookieStore` | `Promise<void>` | Logout user |
|
|
346
394
|
|
|
347
|
-
### Password Management
|
|
395
|
+
### Password Management (Optional - Built-in only)
|
|
348
396
|
|
|
349
397
|
| Method | Parameters | Returns | Description |
|
|
350
398
|
| ------------------- | ------------------------------------ | ------------------ | -------------------------------------- |
|
|
@@ -358,7 +406,8 @@ export async function signIn(formData: unknown) {
|
|
|
358
406
|
|
|
359
407
|
✅ **Do:**
|
|
360
408
|
|
|
361
|
-
-
|
|
409
|
+
- Choose a hashing method before building your database schema
|
|
410
|
+
- Store salt as a **STRING** if using Swift Auth's hashing
|
|
362
411
|
- Use environment variables for Redis credentials
|
|
363
412
|
- Call `signOut()` before navigating to login page
|
|
364
413
|
- Update session after profile changes
|
|
@@ -366,9 +415,10 @@ export async function signIn(formData: unknown) {
|
|
|
366
415
|
|
|
367
416
|
❌ **Don't:**
|
|
368
417
|
|
|
369
|
-
-
|
|
418
|
+
- Mix hashing methods (pick one and stick with it)
|
|
419
|
+
- Store salt as Buffer or Bytes (if using built-in hashing)
|
|
370
420
|
- Hardcode Redis credentials
|
|
371
|
-
- Compare passwords with `===`
|
|
421
|
+
- Compare passwords manually with `===`
|
|
372
422
|
- Expose session data to client components
|
|
373
423
|
- Use TTL shorter than 1 hour for user experience
|
|
374
424
|
|
|
@@ -395,7 +445,7 @@ echo $REDIS_URL
|
|
|
395
445
|
**Solution:**
|
|
396
446
|
|
|
397
447
|
- Check if TTL has expired
|
|
398
|
-
- Verify `
|
|
448
|
+
- Verify `payload` includes all required user data
|
|
399
449
|
- Ensure cookie store is being awaited properly
|
|
400
450
|
|
|
401
451
|
```typescript
|
|
@@ -422,7 +472,7 @@ export type User = {
|
|
|
422
472
|
import type { User } from "@/lib/auth";
|
|
423
473
|
```
|
|
424
474
|
|
|
425
|
-
### Password Comparison Always Fails
|
|
475
|
+
### Password Comparison Always Fails (Built-in Hashing)
|
|
426
476
|
|
|
427
477
|
**Problem**: `comparePassword()` returns false for valid password
|
|
428
478
|
|
|
@@ -464,3 +514,4 @@ MIT © Taimoor Safdar
|
|
|
464
514
|
[⬆ back to top](#-swift-auth)
|
|
465
515
|
|
|
466
516
|
</div>
|
|
517
|
+
|
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,19 @@ module.exports = __toCommonJS(index_exports);
|
|
|
37
37
|
// src/create-auth.ts
|
|
38
38
|
var import_crypto = __toESM(require("crypto"), 1);
|
|
39
39
|
|
|
40
|
+
// src/internal/redis-client.ts
|
|
41
|
+
var import_redis = require("@upstash/redis");
|
|
42
|
+
var createRedisClient = ({ url, token }) => {
|
|
43
|
+
if (!url || !token) {
|
|
44
|
+
throw new Error("Both REDIS URL and TOKEN are required to create Redis client");
|
|
45
|
+
}
|
|
46
|
+
const redisClient = new import_redis.Redis({
|
|
47
|
+
url,
|
|
48
|
+
token
|
|
49
|
+
});
|
|
50
|
+
return redisClient;
|
|
51
|
+
};
|
|
52
|
+
|
|
40
53
|
// src/internal/keys.ts
|
|
41
54
|
var COOKIE_SESSION_KEY = "session-id";
|
|
42
55
|
|
|
@@ -113,19 +126,6 @@ async function removeUserFromSession(auth, cookies) {
|
|
|
113
126
|
deleteSessionCookie(cookies);
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
// src/internal/redis-client.ts
|
|
117
|
-
var import_redis = require("@upstash/redis");
|
|
118
|
-
var createRedisClient = ({ url, token }) => {
|
|
119
|
-
if (!url || !token) {
|
|
120
|
-
throw new Error("Both REDIS URL and TOKEN are required to create Redis client");
|
|
121
|
-
}
|
|
122
|
-
const redisClient = new import_redis.Redis({
|
|
123
|
-
url,
|
|
124
|
-
token
|
|
125
|
-
});
|
|
126
|
-
return redisClient;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
129
|
// src/create-auth.ts
|
|
130
130
|
function createAuth(options) {
|
|
131
131
|
if (!options.payload.includes("id")) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/create-auth.ts","../src/internal/keys.ts","../src/internal/cookie.ts","../src/internal/utils.ts","../src/internal/session.ts","../src/internal/redis-client.ts"],"sourcesContent":["export { createAuth } from \"./create-auth.js\";\n","import crypto from \"crypto\";\r\nimport {\r\n getUserFromSession,\r\n createUserSession as internalCreateUserSession,\r\n removeUserFromSession as internalRemoveUserFromSession,\r\n updateUserSession as internalUpdateUserSession,\r\n} from \"./internal/session.js\";\r\nimport { AuthInstance, AuthPublic } from \"./internal/types.js\";\r\nimport { createRedisClient } from \"./internal/redis-client.js\";\r\nimport { Cookies } from \"./internal/cookie.js\";\r\n\r\ntype StrictRedisConfig = {\r\n url: string;\r\n token: string;\r\n} & {\r\n [K in Exclude<string, \"url\" | \"token\">]?: never;\r\n};\r\n\r\ntype CreateAuthOptions<UserType extends Record<string, unknown>> = {\r\n redis: StrictRedisConfig;\r\n ttl: number;\r\n payload: (keyof UserType)[];\r\n} & {\r\n [K in Exclude<string, \"redis\" | \"ttl\" | \"payload\">]?: never;\r\n};\r\n\r\n\r\n/**\r\n * Create an authentication instance\r\n */\r\nexport function createAuth<UserType extends Record<string, unknown>>(\r\n options: CreateAuthOptions<UserType>\r\n): AuthPublic<UserType> {\r\n if (!options.payload.includes(\"id\" as keyof UserType)) {\r\n throw new Error(\"payload must include `id`\");\r\n }\r\n\r\n const _redis = createRedisClient(options.redis);\r\n const _payload = options.payload;\r\n const _ttl = options.ttl;\r\n\r\n const auth: AuthInstance<UserType> = {\r\n _redis,\r\n _payload,\r\n _ttl,\r\n // -------------------\r\n // Session operations\r\n // -------------------\r\n getCurrentUser(cookies) {\r\n return getUserFromSession({ _redis, _payload, _ttl: _ttl }, cookies);\r\n },\r\n\r\n createUserSession(user, cookies) {\r\n const sessionData: Partial<UserType> = {} as any;\r\n for (const key of _payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n return internalCreateUserSession({ _redis, _payload, _ttl: _ttl }, sessionData, cookies);\r\n },\r\n\r\n updateUserSession(user, cookies: Pick<Cookies, \"get\" | \"set\">) {\r\n const sessionData: Partial<UserType> = {};\r\n for (const key of _payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n return internalUpdateUserSession({ _redis, _payload, _ttl }, sessionData, cookies);\r\n },\r\n\r\n async removeUserFromSession(cookies) {\r\n await internalRemoveUserFromSession({ _redis, _payload, _ttl: _ttl }, cookies);\r\n },\r\n\r\n // -------------------\r\n // Password helpers\r\n // -------------------\r\n hashPassword(password: string, salt: string): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n crypto.scrypt(\r\n password.normalize(),\r\n salt,\r\n 64,\r\n (err: Error | null, derivedKey: Buffer) => {\r\n if (err) return reject(err);\r\n resolve(derivedKey.toString(\"hex\").normalize());\r\n }\r\n );\r\n });\r\n },\r\n\r\n generateSalt(): string {\r\n return crypto.randomBytes(16).toString(\"hex\").normalize();\r\n },\r\n\r\n async comparePassword({\r\n password,\r\n salt,\r\n hashedPassword,\r\n }: {\r\n password: string;\r\n salt: string;\r\n hashedPassword: string;\r\n }): Promise<boolean> {\r\n const inputHashed = await auth.hashPassword(password, salt);\r\n return crypto.timingSafeEqual(\r\n Buffer.from(inputHashed, \"hex\"),\r\n Buffer.from(hashedPassword, \"hex\")\r\n );\r\n },\r\n };\r\n\r\n return auth;\r\n}\r\n","export const COOKIE_SESSION_KEY = \"session-id\"","import { COOKIE_SESSION_KEY } from \"./keys.js\";\r\n\r\nexport type Cookies = {\r\n set: (\r\n key: string,\r\n value: string,\r\n options?: {\r\n secure?: boolean;\r\n httpOnly?: boolean;\r\n sameSite?: \"strict\" | \"lax\";\r\n path: string;\r\n maxAge: number\r\n }\r\n ) => void;\r\n get: (key: string) => { name: string; value: string } | undefined;\r\n delete: (key: string) => void;\r\n};\r\n\r\nexport function getSessionId(\r\n cookies: Pick<Cookies, \"get\">\r\n): string | null {\r\n return cookies.get(COOKIE_SESSION_KEY)?.value ?? null;\r\n}\r\n\r\nexport function deleteSessionCookie(\r\n cookies: Pick<Cookies, \"delete\">\r\n) {\r\n cookies.delete(COOKIE_SESSION_KEY);\r\n}\r\n","export function stringifyBigInt<T>(obj: T): string {\r\n return JSON.stringify(obj, (_, value) =>\r\n typeof value === \"bigint\" ? value.toString() : value\r\n );\r\n}\r\n\r\nexport function generateSessionId(): string {\r\n const array = new Uint8Array(512);\r\n crypto.getRandomValues(array);\r\n\r\n return Array.from(array)\r\n .map((b) => b.toString(16).padStart(2, \"0\"))\r\n .join(\"\");\r\n}\r\n","import { Redis } from \"@upstash/redis\";\r\nimport {\r\n Cookies,\r\n deleteSessionCookie,\r\n getSessionId,\r\n} from \"./cookie.js\";\r\nimport { generateSessionId, stringifyBigInt } from \"./utils.js\";\r\nimport { COOKIE_SESSION_KEY } from \"./keys.js\";\r\n\r\nexport async function getUserFromSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n cookies: Pick<Cookies, \"get\">\r\n): Promise<Partial<UserType> | null> {\r\n const sessionId = cookies.get(COOKIE_SESSION_KEY)?.value;\r\n if (!sessionId) return null;\r\n\r\n const data = await auth._redis.get<string | object>(`session:${sessionId}`);\r\n if (!data) return null;\r\n\r\n // Only parse if it's still a string\r\n const sessionData: Partial<UserType> = typeof data === \"string\"\r\n ? JSON.parse(data)\r\n : (data as any);\r\n\r\n // Optional: pick only _payload to ensure type safety\r\n const result: Partial<UserType> = {};\r\n for (const key of auth._payload) {\r\n if (key in sessionData) result[key] = sessionData[key];\r\n }\r\n\r\n return result;\r\n}\r\n\r\nexport async function createUserSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n user: Partial<UserType>,\r\n cookies: Pick<Cookies, \"set\">\r\n) {\r\n const sessionId = generateSessionId();\r\n\r\n // Pick only session fields\r\n const sessionData: Partial<UserType> = {};\r\n for (const key of auth._payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n\r\n await auth._redis.set(\r\n `session:${sessionId}`,\r\n stringifyBigInt(sessionData),\r\n { ex: auth._ttl }\r\n );\r\n\r\n cookies.set(COOKIE_SESSION_KEY, sessionId, {\r\n secure: process.env.NODE_ENV === \"production\",\r\n httpOnly: true,\r\n sameSite: \"lax\",\r\n path: \"/\",\r\n maxAge: auth._ttl,\r\n });\r\n}\r\n\r\n\r\nexport async function updateUserSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n user: Partial<UserType>, // ✅ allow partial here\r\n cookies: Pick<Cookies, \"get\" | \"set\">\r\n): Promise<void> {\r\n const sessionId = getSessionId(cookies);\r\n if (!sessionId) return; // ✅ just return void\r\n\r\n // Pick only session fields\r\n const sessionData: Partial<UserType> = {} as any;\r\n for (const key of auth._payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n\r\n await auth._redis.set(\r\n `session:${sessionId}`,\r\n stringifyBigInt(sessionData),\r\n { ex: auth._ttl }\r\n );\r\n}\r\n\r\nexport async function removeUserFromSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n cookies: Pick<Cookies, \"get\" | \"delete\">\r\n) {\r\n const sessionId = getSessionId(cookies);\r\n if (!sessionId) return null;\r\n\r\n await auth._redis.del(`session:${sessionId}`);\r\n deleteSessionCookie(cookies);\r\n}\r\n","import { Redis } from \"@upstash/redis\";\r\n\r\nexport type CreateRedisClientOptions = {\r\n url: string;\r\n token: string;\r\n};\r\n\r\nexport const createRedisClient = ({ url, token }: CreateRedisClientOptions) => {\r\n if (!url || !token) {\r\n throw new Error(\"Both REDIS URL and TOKEN are required to create Redis client\");\r\n }\r\n\r\n const redisClient = new Redis({\r\n url,\r\n token,\r\n });\r\n\r\n return redisClient;\r\n};\r\n"],"mappings":"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","names":["crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/create-auth.ts","../src/internal/redis-client.ts","../src/internal/keys.ts","../src/internal/cookie.ts","../src/internal/utils.ts","../src/internal/session.ts"],"sourcesContent":["export { createAuth } from \"./create-auth.js\";\n","import crypto from \"crypto\";\r\nimport { Cookies } from \"./internal/cookie.js\";\r\nimport { createRedisClient } from \"./internal/redis-client.js\";\r\nimport {\r\n getUserFromSession,\r\n createUserSession as internalCreateUserSession,\r\n removeUserFromSession as internalRemoveUserFromSession,\r\n updateUserSession as internalUpdateUserSession,\r\n} from \"./internal/session.js\";\r\nimport { AuthInstance, AuthPublic } from \"./internal/types.js\";\r\n\r\ntype RedisConfig = {\r\n url: string;\r\n token: string;\r\n}\r\n\r\ntype CreateAuthOptions<UserType extends Record<string, unknown>> = {\r\n redis: RedisConfig;\r\n ttl: number;\r\n payload: (keyof UserType)[];\r\n}\r\n\r\n/**\r\n * Create an authentication instance\r\n */\r\nexport function createAuth<UserType extends Record<string, unknown>>(\r\n options: CreateAuthOptions<UserType>\r\n): AuthPublic<UserType> {\r\n if (!options.payload.includes(\"id\" as keyof UserType)) {\r\n throw new Error(\"payload must include `id`\");\r\n }\r\n\r\n const _redis = createRedisClient(options.redis);\r\n const _payload = options.payload;\r\n const _ttl = options.ttl;\r\n\r\n const auth: AuthInstance<UserType> = {\r\n _redis,\r\n _payload,\r\n _ttl,\r\n // -------------------\r\n // Session operations\r\n // -------------------\r\n getCurrentUser(cookies) {\r\n return getUserFromSession({ _redis, _payload, _ttl: _ttl }, cookies);\r\n },\r\n\r\n createUserSession(user, cookies) {\r\n const sessionData: Partial<UserType> = {} as any;\r\n for (const key of _payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n return internalCreateUserSession({ _redis, _payload, _ttl: _ttl }, sessionData, cookies);\r\n },\r\n\r\n updateUserSession(user, cookies: Pick<Cookies, \"get\" | \"set\">) {\r\n const sessionData: Partial<UserType> = {};\r\n for (const key of _payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n return internalUpdateUserSession({ _redis, _payload, _ttl }, sessionData, cookies);\r\n },\r\n\r\n async removeUserFromSession(cookies) {\r\n await internalRemoveUserFromSession({ _redis, _payload, _ttl: _ttl }, cookies);\r\n },\r\n\r\n // -------------------\r\n // Password helpers\r\n // -------------------\r\n hashPassword(password: string, salt: string): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n crypto.scrypt(\r\n password.normalize(),\r\n salt,\r\n 64,\r\n (err: Error | null, derivedKey: Buffer) => {\r\n if (err) return reject(err);\r\n resolve(derivedKey.toString(\"hex\").normalize());\r\n }\r\n );\r\n });\r\n },\r\n\r\n generateSalt(): string {\r\n return crypto.randomBytes(16).toString(\"hex\").normalize();\r\n },\r\n\r\n async comparePassword({\r\n password,\r\n salt,\r\n hashedPassword,\r\n }: {\r\n password: string;\r\n salt: string;\r\n hashedPassword: string;\r\n }): Promise<boolean> {\r\n const inputHashed = await auth.hashPassword(password, salt);\r\n return crypto.timingSafeEqual(\r\n Buffer.from(inputHashed, \"hex\"),\r\n Buffer.from(hashedPassword, \"hex\")\r\n );\r\n },\r\n };\r\n\r\n return auth;\r\n}\r\n","import { Redis } from \"@upstash/redis\";\r\n\r\nexport type CreateRedisClientOptions = {\r\n url: string;\r\n token: string;\r\n};\r\n\r\nexport const createRedisClient = ({ url, token }: CreateRedisClientOptions) => {\r\n if (!url || !token) {\r\n throw new Error(\"Both REDIS URL and TOKEN are required to create Redis client\");\r\n }\r\n\r\n const redisClient = new Redis({\r\n url,\r\n token,\r\n });\r\n\r\n return redisClient;\r\n};\r\n","export const COOKIE_SESSION_KEY = \"session-id\"","import { COOKIE_SESSION_KEY } from \"./keys.js\";\r\n\r\nexport type Cookies = {\r\n set: (\r\n key: string,\r\n value: string,\r\n options?: {\r\n secure?: boolean;\r\n httpOnly?: boolean;\r\n sameSite?: \"strict\" | \"lax\";\r\n path: string;\r\n maxAge: number\r\n }\r\n ) => void;\r\n get: (key: string) => { name: string; value: string } | undefined;\r\n delete: (key: string) => void;\r\n};\r\n\r\nexport function getSessionId(\r\n cookies: Pick<Cookies, \"get\">\r\n): string | null {\r\n return cookies.get(COOKIE_SESSION_KEY)?.value ?? null;\r\n}\r\n\r\nexport function deleteSessionCookie(\r\n cookies: Pick<Cookies, \"delete\">\r\n) {\r\n cookies.delete(COOKIE_SESSION_KEY);\r\n}\r\n","export function stringifyBigInt<T>(obj: T): string {\r\n return JSON.stringify(obj, (_, value) =>\r\n typeof value === \"bigint\" ? value.toString() : value\r\n );\r\n}\r\n\r\nexport function generateSessionId(): string {\r\n const array = new Uint8Array(512);\r\n crypto.getRandomValues(array);\r\n\r\n return Array.from(array)\r\n .map((b) => b.toString(16).padStart(2, \"0\"))\r\n .join(\"\");\r\n}\r\n","import { Redis } from \"@upstash/redis\";\r\nimport {\r\n Cookies,\r\n deleteSessionCookie,\r\n getSessionId,\r\n} from \"./cookie.js\";\r\nimport { generateSessionId, stringifyBigInt } from \"./utils.js\";\r\nimport { COOKIE_SESSION_KEY } from \"./keys.js\";\r\n\r\nexport async function getUserFromSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n cookies: Pick<Cookies, \"get\">\r\n): Promise<Partial<UserType> | null> {\r\n const sessionId = cookies.get(COOKIE_SESSION_KEY)?.value;\r\n if (!sessionId) return null;\r\n\r\n const data = await auth._redis.get<string | object>(`session:${sessionId}`);\r\n if (!data) return null;\r\n\r\n // Only parse if it's still a string\r\n const sessionData: Partial<UserType> = typeof data === \"string\"\r\n ? JSON.parse(data)\r\n : (data as any);\r\n\r\n // Optional: pick only _payload to ensure type safety\r\n const result: Partial<UserType> = {};\r\n for (const key of auth._payload) {\r\n if (key in sessionData) result[key] = sessionData[key];\r\n }\r\n\r\n return result;\r\n}\r\n\r\nexport async function createUserSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n user: Partial<UserType>,\r\n cookies: Pick<Cookies, \"set\">\r\n) {\r\n const sessionId = generateSessionId();\r\n\r\n // Pick only session fields\r\n const sessionData: Partial<UserType> = {};\r\n for (const key of auth._payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n\r\n await auth._redis.set(\r\n `session:${sessionId}`,\r\n stringifyBigInt(sessionData),\r\n { ex: auth._ttl }\r\n );\r\n\r\n cookies.set(COOKIE_SESSION_KEY, sessionId, {\r\n secure: process.env.NODE_ENV === \"production\",\r\n httpOnly: true,\r\n sameSite: \"lax\",\r\n path: \"/\",\r\n maxAge: auth._ttl,\r\n });\r\n}\r\n\r\n\r\nexport async function updateUserSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n user: Partial<UserType>, // ✅ allow partial here\r\n cookies: Pick<Cookies, \"get\" | \"set\">\r\n): Promise<void> {\r\n const sessionId = getSessionId(cookies);\r\n if (!sessionId) return; // ✅ just return void\r\n\r\n // Pick only session fields\r\n const sessionData: Partial<UserType> = {} as any;\r\n for (const key of auth._payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n\r\n await auth._redis.set(\r\n `session:${sessionId}`,\r\n stringifyBigInt(sessionData),\r\n { ex: auth._ttl }\r\n );\r\n}\r\n\r\nexport async function removeUserFromSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n cookies: Pick<Cookies, \"get\" | \"delete\">\r\n) {\r\n const sessionId = getSessionId(cookies);\r\n if (!sessionId) return null;\r\n\r\n await auth._redis.del(`session:${sessionId}`);\r\n deleteSessionCookie(cookies);\r\n}\r\n"],"mappings":"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","names":["crypto"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -27,18 +27,14 @@ type AuthPublic<UserType extends Record<string, unknown>> = {
|
|
|
27
27
|
}): Promise<boolean>;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
type
|
|
30
|
+
type RedisConfig = {
|
|
31
31
|
url: string;
|
|
32
32
|
token: string;
|
|
33
|
-
} & {
|
|
34
|
-
[K in Exclude<string, "url" | "token">]?: never;
|
|
35
33
|
};
|
|
36
34
|
type CreateAuthOptions<UserType extends Record<string, unknown>> = {
|
|
37
|
-
redis:
|
|
35
|
+
redis: RedisConfig;
|
|
38
36
|
ttl: number;
|
|
39
37
|
payload: (keyof UserType)[];
|
|
40
|
-
} & {
|
|
41
|
-
[K in Exclude<string, "redis" | "ttl" | "payload">]?: never;
|
|
42
38
|
};
|
|
43
39
|
/**
|
|
44
40
|
* Create an authentication instance
|
package/dist/index.d.ts
CHANGED
|
@@ -27,18 +27,14 @@ type AuthPublic<UserType extends Record<string, unknown>> = {
|
|
|
27
27
|
}): Promise<boolean>;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
type
|
|
30
|
+
type RedisConfig = {
|
|
31
31
|
url: string;
|
|
32
32
|
token: string;
|
|
33
|
-
} & {
|
|
34
|
-
[K in Exclude<string, "url" | "token">]?: never;
|
|
35
33
|
};
|
|
36
34
|
type CreateAuthOptions<UserType extends Record<string, unknown>> = {
|
|
37
|
-
redis:
|
|
35
|
+
redis: RedisConfig;
|
|
38
36
|
ttl: number;
|
|
39
37
|
payload: (keyof UserType)[];
|
|
40
|
-
} & {
|
|
41
|
-
[K in Exclude<string, "redis" | "ttl" | "payload">]?: never;
|
|
42
38
|
};
|
|
43
39
|
/**
|
|
44
40
|
* Create an authentication instance
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
// src/create-auth.ts
|
|
2
2
|
import crypto2 from "crypto";
|
|
3
3
|
|
|
4
|
+
// src/internal/redis-client.ts
|
|
5
|
+
import { Redis } from "@upstash/redis";
|
|
6
|
+
var createRedisClient = ({ url, token }) => {
|
|
7
|
+
if (!url || !token) {
|
|
8
|
+
throw new Error("Both REDIS URL and TOKEN are required to create Redis client");
|
|
9
|
+
}
|
|
10
|
+
const redisClient = new Redis({
|
|
11
|
+
url,
|
|
12
|
+
token
|
|
13
|
+
});
|
|
14
|
+
return redisClient;
|
|
15
|
+
};
|
|
16
|
+
|
|
4
17
|
// src/internal/keys.ts
|
|
5
18
|
var COOKIE_SESSION_KEY = "session-id";
|
|
6
19
|
|
|
@@ -77,19 +90,6 @@ async function removeUserFromSession(auth, cookies) {
|
|
|
77
90
|
deleteSessionCookie(cookies);
|
|
78
91
|
}
|
|
79
92
|
|
|
80
|
-
// src/internal/redis-client.ts
|
|
81
|
-
import { Redis } from "@upstash/redis";
|
|
82
|
-
var createRedisClient = ({ url, token }) => {
|
|
83
|
-
if (!url || !token) {
|
|
84
|
-
throw new Error("Both REDIS URL and TOKEN are required to create Redis client");
|
|
85
|
-
}
|
|
86
|
-
const redisClient = new Redis({
|
|
87
|
-
url,
|
|
88
|
-
token
|
|
89
|
-
});
|
|
90
|
-
return redisClient;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
93
|
// src/create-auth.ts
|
|
94
94
|
function createAuth(options) {
|
|
95
95
|
if (!options.payload.includes("id")) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/create-auth.ts","../src/internal/keys.ts","../src/internal/cookie.ts","../src/internal/utils.ts","../src/internal/session.ts","../src/internal/redis-client.ts"],"sourcesContent":["import crypto from \"crypto\";\r\nimport {\r\n getUserFromSession,\r\n createUserSession as internalCreateUserSession,\r\n removeUserFromSession as internalRemoveUserFromSession,\r\n updateUserSession as internalUpdateUserSession,\r\n} from \"./internal/session.js\";\r\nimport { AuthInstance, AuthPublic } from \"./internal/types.js\";\r\nimport { createRedisClient } from \"./internal/redis-client.js\";\r\nimport { Cookies } from \"./internal/cookie.js\";\r\n\r\ntype StrictRedisConfig = {\r\n url: string;\r\n token: string;\r\n} & {\r\n [K in Exclude<string, \"url\" | \"token\">]?: never;\r\n};\r\n\r\ntype CreateAuthOptions<UserType extends Record<string, unknown>> = {\r\n redis: StrictRedisConfig;\r\n ttl: number;\r\n payload: (keyof UserType)[];\r\n} & {\r\n [K in Exclude<string, \"redis\" | \"ttl\" | \"payload\">]?: never;\r\n};\r\n\r\n\r\n/**\r\n * Create an authentication instance\r\n */\r\nexport function createAuth<UserType extends Record<string, unknown>>(\r\n options: CreateAuthOptions<UserType>\r\n): AuthPublic<UserType> {\r\n if (!options.payload.includes(\"id\" as keyof UserType)) {\r\n throw new Error(\"payload must include `id`\");\r\n }\r\n\r\n const _redis = createRedisClient(options.redis);\r\n const _payload = options.payload;\r\n const _ttl = options.ttl;\r\n\r\n const auth: AuthInstance<UserType> = {\r\n _redis,\r\n _payload,\r\n _ttl,\r\n // -------------------\r\n // Session operations\r\n // -------------------\r\n getCurrentUser(cookies) {\r\n return getUserFromSession({ _redis, _payload, _ttl: _ttl }, cookies);\r\n },\r\n\r\n createUserSession(user, cookies) {\r\n const sessionData: Partial<UserType> = {} as any;\r\n for (const key of _payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n return internalCreateUserSession({ _redis, _payload, _ttl: _ttl }, sessionData, cookies);\r\n },\r\n\r\n updateUserSession(user, cookies: Pick<Cookies, \"get\" | \"set\">) {\r\n const sessionData: Partial<UserType> = {};\r\n for (const key of _payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n return internalUpdateUserSession({ _redis, _payload, _ttl }, sessionData, cookies);\r\n },\r\n\r\n async removeUserFromSession(cookies) {\r\n await internalRemoveUserFromSession({ _redis, _payload, _ttl: _ttl }, cookies);\r\n },\r\n\r\n // -------------------\r\n // Password helpers\r\n // -------------------\r\n hashPassword(password: string, salt: string): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n crypto.scrypt(\r\n password.normalize(),\r\n salt,\r\n 64,\r\n (err: Error | null, derivedKey: Buffer) => {\r\n if (err) return reject(err);\r\n resolve(derivedKey.toString(\"hex\").normalize());\r\n }\r\n );\r\n });\r\n },\r\n\r\n generateSalt(): string {\r\n return crypto.randomBytes(16).toString(\"hex\").normalize();\r\n },\r\n\r\n async comparePassword({\r\n password,\r\n salt,\r\n hashedPassword,\r\n }: {\r\n password: string;\r\n salt: string;\r\n hashedPassword: string;\r\n }): Promise<boolean> {\r\n const inputHashed = await auth.hashPassword(password, salt);\r\n return crypto.timingSafeEqual(\r\n Buffer.from(inputHashed, \"hex\"),\r\n Buffer.from(hashedPassword, \"hex\")\r\n );\r\n },\r\n };\r\n\r\n return auth;\r\n}\r\n","export const COOKIE_SESSION_KEY = \"session-id\"","import { COOKIE_SESSION_KEY } from \"./keys.js\";\r\n\r\nexport type Cookies = {\r\n set: (\r\n key: string,\r\n value: string,\r\n options?: {\r\n secure?: boolean;\r\n httpOnly?: boolean;\r\n sameSite?: \"strict\" | \"lax\";\r\n path: string;\r\n maxAge: number\r\n }\r\n ) => void;\r\n get: (key: string) => { name: string; value: string } | undefined;\r\n delete: (key: string) => void;\r\n};\r\n\r\nexport function getSessionId(\r\n cookies: Pick<Cookies, \"get\">\r\n): string | null {\r\n return cookies.get(COOKIE_SESSION_KEY)?.value ?? null;\r\n}\r\n\r\nexport function deleteSessionCookie(\r\n cookies: Pick<Cookies, \"delete\">\r\n) {\r\n cookies.delete(COOKIE_SESSION_KEY);\r\n}\r\n","export function stringifyBigInt<T>(obj: T): string {\r\n return JSON.stringify(obj, (_, value) =>\r\n typeof value === \"bigint\" ? value.toString() : value\r\n );\r\n}\r\n\r\nexport function generateSessionId(): string {\r\n const array = new Uint8Array(512);\r\n crypto.getRandomValues(array);\r\n\r\n return Array.from(array)\r\n .map((b) => b.toString(16).padStart(2, \"0\"))\r\n .join(\"\");\r\n}\r\n","import { Redis } from \"@upstash/redis\";\r\nimport {\r\n Cookies,\r\n deleteSessionCookie,\r\n getSessionId,\r\n} from \"./cookie.js\";\r\nimport { generateSessionId, stringifyBigInt } from \"./utils.js\";\r\nimport { COOKIE_SESSION_KEY } from \"./keys.js\";\r\n\r\nexport async function getUserFromSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n cookies: Pick<Cookies, \"get\">\r\n): Promise<Partial<UserType> | null> {\r\n const sessionId = cookies.get(COOKIE_SESSION_KEY)?.value;\r\n if (!sessionId) return null;\r\n\r\n const data = await auth._redis.get<string | object>(`session:${sessionId}`);\r\n if (!data) return null;\r\n\r\n // Only parse if it's still a string\r\n const sessionData: Partial<UserType> = typeof data === \"string\"\r\n ? JSON.parse(data)\r\n : (data as any);\r\n\r\n // Optional: pick only _payload to ensure type safety\r\n const result: Partial<UserType> = {};\r\n for (const key of auth._payload) {\r\n if (key in sessionData) result[key] = sessionData[key];\r\n }\r\n\r\n return result;\r\n}\r\n\r\nexport async function createUserSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n user: Partial<UserType>,\r\n cookies: Pick<Cookies, \"set\">\r\n) {\r\n const sessionId = generateSessionId();\r\n\r\n // Pick only session fields\r\n const sessionData: Partial<UserType> = {};\r\n for (const key of auth._payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n\r\n await auth._redis.set(\r\n `session:${sessionId}`,\r\n stringifyBigInt(sessionData),\r\n { ex: auth._ttl }\r\n );\r\n\r\n cookies.set(COOKIE_SESSION_KEY, sessionId, {\r\n secure: process.env.NODE_ENV === \"production\",\r\n httpOnly: true,\r\n sameSite: \"lax\",\r\n path: \"/\",\r\n maxAge: auth._ttl,\r\n });\r\n}\r\n\r\n\r\nexport async function updateUserSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n user: Partial<UserType>, // ✅ allow partial here\r\n cookies: Pick<Cookies, \"get\" | \"set\">\r\n): Promise<void> {\r\n const sessionId = getSessionId(cookies);\r\n if (!sessionId) return; // ✅ just return void\r\n\r\n // Pick only session fields\r\n const sessionData: Partial<UserType> = {} as any;\r\n for (const key of auth._payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n\r\n await auth._redis.set(\r\n `session:${sessionId}`,\r\n stringifyBigInt(sessionData),\r\n { ex: auth._ttl }\r\n );\r\n}\r\n\r\nexport async function removeUserFromSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n cookies: Pick<Cookies, \"get\" | \"delete\">\r\n) {\r\n const sessionId = getSessionId(cookies);\r\n if (!sessionId) return null;\r\n\r\n await auth._redis.del(`session:${sessionId}`);\r\n deleteSessionCookie(cookies);\r\n}\r\n","import { Redis } from \"@upstash/redis\";\r\n\r\nexport type CreateRedisClientOptions = {\r\n url: string;\r\n token: string;\r\n};\r\n\r\nexport const createRedisClient = ({ url, token }: CreateRedisClientOptions) => {\r\n if (!url || !token) {\r\n throw new Error(\"Both REDIS URL and TOKEN are required to create Redis client\");\r\n }\r\n\r\n const redisClient = new Redis({\r\n url,\r\n token,\r\n });\r\n\r\n return redisClient;\r\n};\r\n"],"mappings":"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","names":["crypto","crypto"]}
|
|
1
|
+
{"version":3,"sources":["../src/create-auth.ts","../src/internal/redis-client.ts","../src/internal/keys.ts","../src/internal/cookie.ts","../src/internal/utils.ts","../src/internal/session.ts"],"sourcesContent":["import crypto from \"crypto\";\r\nimport { Cookies } from \"./internal/cookie.js\";\r\nimport { createRedisClient } from \"./internal/redis-client.js\";\r\nimport {\r\n getUserFromSession,\r\n createUserSession as internalCreateUserSession,\r\n removeUserFromSession as internalRemoveUserFromSession,\r\n updateUserSession as internalUpdateUserSession,\r\n} from \"./internal/session.js\";\r\nimport { AuthInstance, AuthPublic } from \"./internal/types.js\";\r\n\r\ntype RedisConfig = {\r\n url: string;\r\n token: string;\r\n}\r\n\r\ntype CreateAuthOptions<UserType extends Record<string, unknown>> = {\r\n redis: RedisConfig;\r\n ttl: number;\r\n payload: (keyof UserType)[];\r\n}\r\n\r\n/**\r\n * Create an authentication instance\r\n */\r\nexport function createAuth<UserType extends Record<string, unknown>>(\r\n options: CreateAuthOptions<UserType>\r\n): AuthPublic<UserType> {\r\n if (!options.payload.includes(\"id\" as keyof UserType)) {\r\n throw new Error(\"payload must include `id`\");\r\n }\r\n\r\n const _redis = createRedisClient(options.redis);\r\n const _payload = options.payload;\r\n const _ttl = options.ttl;\r\n\r\n const auth: AuthInstance<UserType> = {\r\n _redis,\r\n _payload,\r\n _ttl,\r\n // -------------------\r\n // Session operations\r\n // -------------------\r\n getCurrentUser(cookies) {\r\n return getUserFromSession({ _redis, _payload, _ttl: _ttl }, cookies);\r\n },\r\n\r\n createUserSession(user, cookies) {\r\n const sessionData: Partial<UserType> = {} as any;\r\n for (const key of _payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n return internalCreateUserSession({ _redis, _payload, _ttl: _ttl }, sessionData, cookies);\r\n },\r\n\r\n updateUserSession(user, cookies: Pick<Cookies, \"get\" | \"set\">) {\r\n const sessionData: Partial<UserType> = {};\r\n for (const key of _payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n return internalUpdateUserSession({ _redis, _payload, _ttl }, sessionData, cookies);\r\n },\r\n\r\n async removeUserFromSession(cookies) {\r\n await internalRemoveUserFromSession({ _redis, _payload, _ttl: _ttl }, cookies);\r\n },\r\n\r\n // -------------------\r\n // Password helpers\r\n // -------------------\r\n hashPassword(password: string, salt: string): Promise<string> {\r\n return new Promise((resolve, reject) => {\r\n crypto.scrypt(\r\n password.normalize(),\r\n salt,\r\n 64,\r\n (err: Error | null, derivedKey: Buffer) => {\r\n if (err) return reject(err);\r\n resolve(derivedKey.toString(\"hex\").normalize());\r\n }\r\n );\r\n });\r\n },\r\n\r\n generateSalt(): string {\r\n return crypto.randomBytes(16).toString(\"hex\").normalize();\r\n },\r\n\r\n async comparePassword({\r\n password,\r\n salt,\r\n hashedPassword,\r\n }: {\r\n password: string;\r\n salt: string;\r\n hashedPassword: string;\r\n }): Promise<boolean> {\r\n const inputHashed = await auth.hashPassword(password, salt);\r\n return crypto.timingSafeEqual(\r\n Buffer.from(inputHashed, \"hex\"),\r\n Buffer.from(hashedPassword, \"hex\")\r\n );\r\n },\r\n };\r\n\r\n return auth;\r\n}\r\n","import { Redis } from \"@upstash/redis\";\r\n\r\nexport type CreateRedisClientOptions = {\r\n url: string;\r\n token: string;\r\n};\r\n\r\nexport const createRedisClient = ({ url, token }: CreateRedisClientOptions) => {\r\n if (!url || !token) {\r\n throw new Error(\"Both REDIS URL and TOKEN are required to create Redis client\");\r\n }\r\n\r\n const redisClient = new Redis({\r\n url,\r\n token,\r\n });\r\n\r\n return redisClient;\r\n};\r\n","export const COOKIE_SESSION_KEY = \"session-id\"","import { COOKIE_SESSION_KEY } from \"./keys.js\";\r\n\r\nexport type Cookies = {\r\n set: (\r\n key: string,\r\n value: string,\r\n options?: {\r\n secure?: boolean;\r\n httpOnly?: boolean;\r\n sameSite?: \"strict\" | \"lax\";\r\n path: string;\r\n maxAge: number\r\n }\r\n ) => void;\r\n get: (key: string) => { name: string; value: string } | undefined;\r\n delete: (key: string) => void;\r\n};\r\n\r\nexport function getSessionId(\r\n cookies: Pick<Cookies, \"get\">\r\n): string | null {\r\n return cookies.get(COOKIE_SESSION_KEY)?.value ?? null;\r\n}\r\n\r\nexport function deleteSessionCookie(\r\n cookies: Pick<Cookies, \"delete\">\r\n) {\r\n cookies.delete(COOKIE_SESSION_KEY);\r\n}\r\n","export function stringifyBigInt<T>(obj: T): string {\r\n return JSON.stringify(obj, (_, value) =>\r\n typeof value === \"bigint\" ? value.toString() : value\r\n );\r\n}\r\n\r\nexport function generateSessionId(): string {\r\n const array = new Uint8Array(512);\r\n crypto.getRandomValues(array);\r\n\r\n return Array.from(array)\r\n .map((b) => b.toString(16).padStart(2, \"0\"))\r\n .join(\"\");\r\n}\r\n","import { Redis } from \"@upstash/redis\";\r\nimport {\r\n Cookies,\r\n deleteSessionCookie,\r\n getSessionId,\r\n} from \"./cookie.js\";\r\nimport { generateSessionId, stringifyBigInt } from \"./utils.js\";\r\nimport { COOKIE_SESSION_KEY } from \"./keys.js\";\r\n\r\nexport async function getUserFromSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n cookies: Pick<Cookies, \"get\">\r\n): Promise<Partial<UserType> | null> {\r\n const sessionId = cookies.get(COOKIE_SESSION_KEY)?.value;\r\n if (!sessionId) return null;\r\n\r\n const data = await auth._redis.get<string | object>(`session:${sessionId}`);\r\n if (!data) return null;\r\n\r\n // Only parse if it's still a string\r\n const sessionData: Partial<UserType> = typeof data === \"string\"\r\n ? JSON.parse(data)\r\n : (data as any);\r\n\r\n // Optional: pick only _payload to ensure type safety\r\n const result: Partial<UserType> = {};\r\n for (const key of auth._payload) {\r\n if (key in sessionData) result[key] = sessionData[key];\r\n }\r\n\r\n return result;\r\n}\r\n\r\nexport async function createUserSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n user: Partial<UserType>,\r\n cookies: Pick<Cookies, \"set\">\r\n) {\r\n const sessionId = generateSessionId();\r\n\r\n // Pick only session fields\r\n const sessionData: Partial<UserType> = {};\r\n for (const key of auth._payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n\r\n await auth._redis.set(\r\n `session:${sessionId}`,\r\n stringifyBigInt(sessionData),\r\n { ex: auth._ttl }\r\n );\r\n\r\n cookies.set(COOKIE_SESSION_KEY, sessionId, {\r\n secure: process.env.NODE_ENV === \"production\",\r\n httpOnly: true,\r\n sameSite: \"lax\",\r\n path: \"/\",\r\n maxAge: auth._ttl,\r\n });\r\n}\r\n\r\n\r\nexport async function updateUserSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n user: Partial<UserType>, // ✅ allow partial here\r\n cookies: Pick<Cookies, \"get\" | \"set\">\r\n): Promise<void> {\r\n const sessionId = getSessionId(cookies);\r\n if (!sessionId) return; // ✅ just return void\r\n\r\n // Pick only session fields\r\n const sessionData: Partial<UserType> = {} as any;\r\n for (const key of auth._payload) {\r\n if (key in user) sessionData[key] = user[key];\r\n }\r\n\r\n await auth._redis.set(\r\n `session:${sessionId}`,\r\n stringifyBigInt(sessionData),\r\n { ex: auth._ttl }\r\n );\r\n}\r\n\r\nexport async function removeUserFromSession<UserType extends Record<string, unknown>>(\r\n auth: { _redis: Redis; _payload: (keyof UserType)[]; _ttl: number },\r\n cookies: Pick<Cookies, \"get\" | \"delete\">\r\n) {\r\n const sessionId = getSessionId(cookies);\r\n if (!sessionId) return null;\r\n\r\n await auth._redis.del(`session:${sessionId}`);\r\n deleteSessionCookie(cookies);\r\n}\r\n"],"mappings":"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","names":["crypto","crypto"]}
|