swift-auth 1.2.0 β 1.3.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 +335 -130
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,108 +1,119 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# β‘ Swift Auth
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-

|
|
7
|
-

|
|
5
|
+
**Type-safe, zero-hassle authentication for Next.js**
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
[](https://www.npmjs.com/package/swift-auth)
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://nodejs.org)
|
|
10
|
+
[](https://www.typescriptlang.org)
|
|
11
|
+
[](https://nextjs.org)
|
|
10
12
|
|
|
11
|
-
-
|
|
12
|
-
- [Installation](#installation)
|
|
13
|
-
- [Prerequisites](#prerequisites)
|
|
14
|
-
- [Quick Start](#quick-start)
|
|
15
|
-
- [Configuration](#configuration)
|
|
16
|
-
- [Core Usage](#core-usage)
|
|
17
|
-
- [Password Security](#password-security)
|
|
18
|
-
- [API Reference](#api-reference)
|
|
19
|
-
- [Why Swift Auth?](#why-swift-auth)
|
|
20
|
-
- [Troubleshooting](#troubleshooting)
|
|
21
|
-
- [License](#license)
|
|
13
|
+
_Lightweight β’ Secure β’ Type-Safe β’ Production-Ready_
|
|
22
14
|
|
|
23
|
-
|
|
15
|
+
[Quick Start](#-quick-start) β’ [API Reference](#-api-reference) β’ [Examples](#-full-login-example) β’ [Troubleshooting](#-troubleshooting)
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
π **Secure**: Built-in password hashing with scrypt and timing-safe comparison
|
|
27
|
-
πͺ **Cookie Handling**: Automatic HTTP-only cookie management
|
|
28
|
-
β‘ **Next.js 15+**: Fully compatible with the asynchronous `cookies()` API
|
|
29
|
-
π¦ **Minimal**: Only includes what you needβcontrol exactly what's stored in Redis
|
|
30
|
-
π **Production Ready**: Automatic secure flag handling for dev/production environments
|
|
17
|
+
</div>
|
|
31
18
|
|
|
32
|
-
|
|
19
|
+
---
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
## π Why Swift Auth?
|
|
22
|
+
|
|
23
|
+
Stop wrestling with authentication boilerplate. Swift Auth handles **session management**, **password security**, and **cookie handling**βso you can focus on building amazing features.
|
|
24
|
+
|
|
25
|
+
Built for **Next.js 15/16** with **Upstash Redis**, optimized for type safety and security.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## β¨ Key Features
|
|
30
|
+
|
|
31
|
+
| Feature | Description |
|
|
32
|
+
| ------------------------ | ---------------------------------------------------- |
|
|
33
|
+
| π **Type-Safe** | Full TypeScript support with generic user types |
|
|
34
|
+
| π‘οΈ **Secure by Default** | Password hashing via scrypt + timing-safe comparison |
|
|
35
|
+
| πͺ **Smart Cookies** | Automatic HTTP-only cookies (dev & production) |
|
|
36
|
+
| β‘ **Next.js 15+** | Works seamlessly with async `cookies()` API |
|
|
37
|
+
| π¦ **Minimal Footprint** | You control what's stored in Redis |
|
|
38
|
+
| π **Zero Crypto Deps** | No external dependencies for hashing |
|
|
39
|
+
| β
**Production-Ready** | Battle-tested session management |
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## π¦ Installation
|
|
44
|
+
|
|
45
|
+
Install Swift Auth using your favorite package manager:
|
|
39
46
|
|
|
40
47
|
```bash
|
|
41
|
-
|
|
48
|
+
npm install swift-auth
|
|
42
49
|
```
|
|
43
50
|
|
|
44
|
-
Or with pnpm
|
|
51
|
+
**Or with yarn/pnpm:**
|
|
45
52
|
|
|
46
53
|
```bash
|
|
54
|
+
yarn add swift-auth
|
|
55
|
+
# or
|
|
47
56
|
pnpm add swift-auth
|
|
48
57
|
```
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
- Node.js 18+
|
|
53
|
-
- Next.js 15 or 16
|
|
54
|
-
- [Upstash Redis](https://upstash.com) account with connection credentials
|
|
59
|
+
---
|
|
55
60
|
|
|
56
|
-
## Quick Start
|
|
61
|
+
## π― Quick Start (5 minutes)
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
### Step 1οΈβ£ Prerequisites
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
REDIS_URL=https://<your-redis-url>
|
|
62
|
-
REDIS_TOKEN=<your-redis-token>
|
|
63
|
-
```
|
|
65
|
+
Make sure you have:
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
- **Node.js** 18+
|
|
68
|
+
- **Next.js** 15 or 16
|
|
69
|
+
- **PostgreSQL** (via Prisma)
|
|
70
|
+
- **Upstash Redis** account
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
import { createAuth } from "swift-auth";
|
|
72
|
+
### Step 2οΈβ£ Environment Variables
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
id: string;
|
|
72
|
-
name: string;
|
|
73
|
-
email: string;
|
|
74
|
-
created_at: Date;
|
|
75
|
-
};
|
|
74
|
+
Create a `.env.local` file:
|
|
76
75
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
ttl: 60 * 60 * 24 * 7, // 7 Days
|
|
83
|
-
sessionFields: ["id", "name", "email", "created_at"],
|
|
84
|
-
});
|
|
76
|
+
```env
|
|
77
|
+
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
|
|
78
|
+
REDIS_URL=https://your-instance.upstash.io
|
|
79
|
+
REDIS_TOKEN=your_auth_token
|
|
85
80
|
```
|
|
86
81
|
|
|
87
|
-
|
|
82
|
+
### Step 3οΈβ£ Database Setup
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
"use server";
|
|
84
|
+
Configure your Prisma schema with the `user` model:
|
|
91
85
|
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
```prisma
|
|
87
|
+
// prisma/schema.prisma
|
|
94
88
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
generator client {
|
|
90
|
+
provider = "prisma-client"
|
|
91
|
+
output = "../lib/generated/prisma"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
datasource db {
|
|
95
|
+
provider = "postgresql"
|
|
96
|
+
url = env("DATABASE_URL")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
model user {
|
|
100
|
+
id String @id @default(uuid())
|
|
101
|
+
name String
|
|
102
|
+
email String @unique
|
|
103
|
+
password String
|
|
104
|
+
salt String // β οΈ MUST be STRING
|
|
105
|
+
created_at DateTime @default(now())
|
|
106
|
+
updated_at DateTime @updatedAt
|
|
98
107
|
}
|
|
99
108
|
```
|
|
100
109
|
|
|
101
|
-
|
|
110
|
+
> β οΈ **Critical**: Salt must be stored as a `String`, not Buffer or Bytes.
|
|
102
111
|
|
|
103
|
-
|
|
112
|
+
### Step 4οΈβ£ Create Auth Instance
|
|
104
113
|
|
|
105
114
|
```typescript
|
|
115
|
+
// lib/auth.ts
|
|
116
|
+
|
|
106
117
|
import { createAuth } from "swift-auth";
|
|
107
118
|
|
|
108
119
|
export type User = {
|
|
@@ -110,7 +121,6 @@ export type User = {
|
|
|
110
121
|
name: string;
|
|
111
122
|
email: string;
|
|
112
123
|
created_at: Date;
|
|
113
|
-
// ... any other fields
|
|
114
124
|
};
|
|
115
125
|
|
|
116
126
|
export const auth = createAuth<User>({
|
|
@@ -118,25 +128,28 @@ export const auth = createAuth<User>({
|
|
|
118
128
|
url: process.env.REDIS_URL!,
|
|
119
129
|
token: process.env.REDIS_TOKEN!,
|
|
120
130
|
},
|
|
121
|
-
ttl: 60 * 60 * 24 * 7, // 7
|
|
122
|
-
sessionFields: ["id", "name", "email", "created_at"],
|
|
131
|
+
ttl: 60 * 60 * 24 * 7, // 7 days
|
|
132
|
+
sessionFields: ["id", "name", "email", "created_at"],
|
|
123
133
|
});
|
|
124
134
|
```
|
|
125
135
|
|
|
126
136
|
**Configuration Options:**
|
|
127
137
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
| Option | Type | Description |
|
|
139
|
+
| --------------- | ---------- | ------------------------- |
|
|
140
|
+
| `redis.url` | `string` | Upstash Redis URL |
|
|
141
|
+
| `redis.token` | `string` | Upstash Redis token |
|
|
142
|
+
| `ttl` | `number` | Session TTL in seconds |
|
|
143
|
+
| `sessionFields` | `string[]` | Fields persisted in Redis |
|
|
132
144
|
|
|
133
|
-
|
|
145
|
+
---
|
|
134
146
|
|
|
135
|
-
|
|
147
|
+
## π Core Usage
|
|
136
148
|
|
|
137
|
-
|
|
149
|
+
### Create Session (Login)
|
|
138
150
|
|
|
139
151
|
```typescript
|
|
152
|
+
// app/actions/auth.ts
|
|
140
153
|
"use server";
|
|
141
154
|
|
|
142
155
|
import { auth } from "@/lib/auth";
|
|
@@ -150,9 +163,8 @@ export async function signIn(user: User) {
|
|
|
150
163
|
|
|
151
164
|
### Get Current User
|
|
152
165
|
|
|
153
|
-
Retrieve the session data in any Server Component or Layout.
|
|
154
|
-
|
|
155
166
|
```typescript
|
|
167
|
+
// app/dashboard/page.tsx
|
|
156
168
|
import { auth } from "@/lib/auth";
|
|
157
169
|
import { cookies } from "next/headers";
|
|
158
170
|
|
|
@@ -160,29 +172,28 @@ export default async function Dashboard() {
|
|
|
160
172
|
const user = await auth.getCurrentUser(await cookies());
|
|
161
173
|
|
|
162
174
|
if (!user) return <div>Please log in</div>;
|
|
163
|
-
return <div>Welcome back, {user.name}
|
|
175
|
+
return <div>Welcome back, {user.name}! π</div>;
|
|
164
176
|
}
|
|
165
177
|
```
|
|
166
178
|
|
|
167
|
-
### Update
|
|
168
|
-
|
|
169
|
-
Modify session data without creating a new session ID:
|
|
179
|
+
### Update Session
|
|
170
180
|
|
|
171
181
|
```typescript
|
|
182
|
+
// app/actions/auth.ts
|
|
172
183
|
"use server";
|
|
173
184
|
|
|
174
185
|
import { auth } from "@/lib/auth";
|
|
175
186
|
import { cookies } from "next/headers";
|
|
176
187
|
|
|
177
188
|
export async function updateProfile(user: User) {
|
|
178
|
-
|
|
179
|
-
await auth.updateUserSession(user, cookieStore);
|
|
189
|
+
await auth.updateUserSession(user, await cookies());
|
|
180
190
|
}
|
|
181
191
|
```
|
|
182
192
|
|
|
183
|
-
###
|
|
193
|
+
### Logout
|
|
184
194
|
|
|
185
195
|
```typescript
|
|
196
|
+
// app/actions/auth.ts
|
|
186
197
|
"use server";
|
|
187
198
|
|
|
188
199
|
import { auth } from "@/lib/auth";
|
|
@@ -193,69 +204,263 @@ export async function signOut() {
|
|
|
193
204
|
}
|
|
194
205
|
```
|
|
195
206
|
|
|
196
|
-
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## π Password Security
|
|
210
|
+
|
|
211
|
+
Swift Auth provides built-in password hashing and verification with scrypt.
|
|
197
212
|
|
|
198
|
-
|
|
213
|
+
### Register User
|
|
199
214
|
|
|
200
215
|
```typescript
|
|
201
|
-
// 1. Registering a user
|
|
202
216
|
const salt = auth.generateSalt();
|
|
203
|
-
const hashedPassword = await auth.hashPassword("
|
|
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
|
+
```
|
|
204
229
|
|
|
205
|
-
|
|
230
|
+
### Verify Password During Login
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
206
233
|
const isValid = await auth.comparePassword({
|
|
207
|
-
password: "
|
|
208
|
-
salt
|
|
209
|
-
hashedPassword
|
|
234
|
+
password: "user-password",
|
|
235
|
+
salt,
|
|
236
|
+
hashedPassword,
|
|
210
237
|
});
|
|
211
238
|
|
|
212
|
-
if (isValid) {
|
|
213
|
-
|
|
214
|
-
} else {
|
|
215
|
-
// Password is incorrect
|
|
239
|
+
if (!isValid) {
|
|
240
|
+
return { success: false, message: "Invalid password" };
|
|
216
241
|
}
|
|
217
242
|
```
|
|
218
243
|
|
|
219
|
-
|
|
244
|
+
---
|
|
220
245
|
|
|
221
|
-
|
|
222
|
-
| -------------------------------------- | ------------------------------------------------------- |
|
|
223
|
-
| `getCurrentUser(cookieStore)` | Returns the session data from Redis based on the cookie |
|
|
224
|
-
| `createUserSession(user, cookieStore)` | Creates a new session and sets the browser cookie |
|
|
225
|
-
| `updateUserSession(user, cookieStore)` | Updates the Redis data without changing the Session ID |
|
|
226
|
-
| `removeUserFromSession(cookieStore)` | Deletes the Redis key and clears the browser cookie |
|
|
227
|
-
| `hashPassword(password, salt)` | Hashes a string using scrypt |
|
|
228
|
-
| `comparePassword(options)` | Prevents timing attacks while verifying passwords |
|
|
229
|
-
| `generateSalt()` | Generates a cryptographic salt for password hashing |
|
|
246
|
+
## π Full Login Example
|
|
230
247
|
|
|
231
|
-
|
|
248
|
+
Complete login flow with Prisma + Zod validation:
|
|
232
249
|
|
|
233
|
-
|
|
234
|
-
- **BigInt & Date Handling**: Built-in serialization for complex JavaScript objects that standard JSON fails to handle
|
|
235
|
-
- **Automatic Secure Cookies**: Intelligently sets `secure: true` in production and `secure: false` for localhost development
|
|
236
|
-
- **Minimized Payload**: By defining `sessionFields`, you control exactly what data lives in your Redis RAM, keeping costs low and performance high
|
|
237
|
-
- **Type-Safe**: Full TypeScript support with generics for your user type
|
|
238
|
-
- **Zero Dependencies**: Uses only Node.js built-ins for cryptography
|
|
250
|
+
### Validation Schema
|
|
239
251
|
|
|
240
|
-
|
|
252
|
+
```typescript
|
|
253
|
+
// lib/validation.ts
|
|
254
|
+
import { z } from "zod";
|
|
241
255
|
|
|
242
|
-
|
|
256
|
+
export const signInSchema = z
|
|
257
|
+
.object({
|
|
258
|
+
email: z.string().email("Invalid email"),
|
|
259
|
+
password: z.string().min(8, "Password too short"),
|
|
260
|
+
})
|
|
261
|
+
.strict();
|
|
243
262
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
- Ensure your Node.js version is 18 or higher
|
|
263
|
+
export type SignInInput = z.infer<typeof signInSchema>;
|
|
264
|
+
```
|
|
247
265
|
|
|
248
|
-
###
|
|
266
|
+
### Server Action
|
|
249
267
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
268
|
+
```typescript
|
|
269
|
+
// app/actions/auth.ts
|
|
270
|
+
"use server";
|
|
253
271
|
|
|
254
|
-
|
|
272
|
+
import { auth } from "@/lib/auth";
|
|
273
|
+
import { prisma } from "@/lib/prisma";
|
|
274
|
+
import { signInSchema } from "@/lib/validation";
|
|
275
|
+
import { cookies } from "next/headers";
|
|
276
|
+
|
|
277
|
+
export async function signIn(formData: unknown) {
|
|
278
|
+
try {
|
|
279
|
+
// 1. Validate input
|
|
280
|
+
const parsed = signInSchema.safeParse(formData);
|
|
281
|
+
if (!parsed.success) {
|
|
282
|
+
return {
|
|
283
|
+
success: false,
|
|
284
|
+
message: "Validation error",
|
|
285
|
+
errors: parsed.error.flatten(),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const { email, password } = parsed.data;
|
|
290
|
+
|
|
291
|
+
// 2. Find user in database
|
|
292
|
+
const user = await prisma.user.findUnique({
|
|
293
|
+
where: { email },
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
if (!user) {
|
|
297
|
+
return { success: false, message: "Account not found" };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 3. Verify password (timing-safe)
|
|
301
|
+
const isCorrectPassword = await auth.comparePassword({
|
|
302
|
+
hashedPassword: user.password,
|
|
303
|
+
password,
|
|
304
|
+
salt: user.salt, // must be string
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
if (!isCorrectPassword) {
|
|
308
|
+
return { success: false, message: "Invalid password" };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// 4. Create session
|
|
312
|
+
await auth.createUserSession(
|
|
313
|
+
{
|
|
314
|
+
id: user.id,
|
|
315
|
+
name: user.name,
|
|
316
|
+
email: user.email,
|
|
317
|
+
created_at: user.created_at,
|
|
318
|
+
},
|
|
319
|
+
await cookies()
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
success: true,
|
|
324
|
+
message: "Logged in successfully",
|
|
325
|
+
userId: user.id,
|
|
326
|
+
};
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error("Auth Error:", error);
|
|
329
|
+
return { success: false, message: "Internal server error" };
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## π― API Reference
|
|
337
|
+
|
|
338
|
+
### Session Management
|
|
339
|
+
|
|
340
|
+
| Method | Parameters | Returns | Description |
|
|
341
|
+
| ------------------------- | --------------------- | --------------- | ------------------------------------ |
|
|
342
|
+
| `getCurrentUser()` | `cookieStore` | `User \| null` | Get the currently authenticated user |
|
|
343
|
+
| `createUserSession()` | `user`, `cookieStore` | `Promise<void>` | Create a new session |
|
|
344
|
+
| `updateUserSession()` | `user`, `cookieStore` | `Promise<void>` | Update existing session |
|
|
345
|
+
| `removeUserFromSession()` | `cookieStore` | `Promise<void>` | Logout user |
|
|
346
|
+
|
|
347
|
+
### Password Management
|
|
348
|
+
|
|
349
|
+
| Method | Parameters | Returns | Description |
|
|
350
|
+
| ------------------- | ------------------------------------ | ------------------ | -------------------------------------- |
|
|
351
|
+
| `generateSalt()` | - | `string` | Generate cryptographically secure salt |
|
|
352
|
+
| `hashPassword()` | `password`, `salt` | `Promise<string>` | Hash password with scrypt |
|
|
353
|
+
| `comparePassword()` | `{ password, salt, hashedPassword }` | `Promise<boolean>` | Timing-safe password comparison |
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## π Best Practices
|
|
358
|
+
|
|
359
|
+
β
**Do:**
|
|
360
|
+
|
|
361
|
+
- Store salt as a **STRING** in your database
|
|
362
|
+
- Use environment variables for Redis credentials
|
|
363
|
+
- Call `signOut()` before navigating to login page
|
|
364
|
+
- Update session after profile changes
|
|
365
|
+
- Use TypeScript for type safety
|
|
366
|
+
|
|
367
|
+
β **Don't:**
|
|
368
|
+
|
|
369
|
+
- Store salt as Buffer or Bytes
|
|
370
|
+
- Hardcode Redis credentials
|
|
371
|
+
- Compare passwords with `===`
|
|
372
|
+
- Expose session data to client components
|
|
373
|
+
- Use TTL shorter than 1 hour for user experience
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## π Troubleshooting
|
|
378
|
+
|
|
379
|
+
### Redis Connection Failed
|
|
380
|
+
|
|
381
|
+
**Problem**: `Error: Connection refused`
|
|
382
|
+
|
|
383
|
+
**Solution:**
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
# Verify your Upstash instance is active
|
|
387
|
+
# Check REDIS_URL and REDIS_TOKEN in .env.local
|
|
388
|
+
echo $REDIS_URL
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Session Not Found / Cookie Missing
|
|
392
|
+
|
|
393
|
+
**Problem**: User is logged out unexpectedly
|
|
394
|
+
|
|
395
|
+
**Solution:**
|
|
396
|
+
|
|
397
|
+
- Check if TTL has expired
|
|
398
|
+
- Verify `sessionFields` includes all required user data
|
|
399
|
+
- Ensure cookie store is being awaited properly
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// β
Correct
|
|
403
|
+
const cookieStore = await cookies();
|
|
404
|
+
|
|
405
|
+
// β Wrong
|
|
406
|
+
const cookieStore = cookies();
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Type Errors with User Type
|
|
255
410
|
|
|
256
|
-
|
|
257
|
-
- Import the `User` type in components: `import type { User } from "@/lib/auth"`
|
|
411
|
+
**Problem**: TypeScript complains about User type mismatch
|
|
258
412
|
|
|
259
|
-
|
|
413
|
+
**Solution**: Always export and reuse your User type:
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
// lib/auth.ts
|
|
417
|
+
export type User = {
|
|
418
|
+
/* ... */
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// app/actions/auth.ts
|
|
422
|
+
import type { User } from "@/lib/auth";
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Password Comparison Always Fails
|
|
426
|
+
|
|
427
|
+
**Problem**: `comparePassword()` returns false for valid password
|
|
428
|
+
|
|
429
|
+
**Solution**: Ensure salt is retrieved as a STRING:
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// β
Correct
|
|
433
|
+
const user = await prisma.user.findUnique({ where: { id } });
|
|
434
|
+
const isValid = await auth.comparePassword({
|
|
435
|
+
password,
|
|
436
|
+
salt: user.salt, // string β
|
|
437
|
+
hashedPassword: user.password,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// β Wrong
|
|
441
|
+
salt: user.salt as any; // don't cast!
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
---
|
|
445
|
+
|
|
446
|
+
## π More Resources
|
|
447
|
+
|
|
448
|
+
- [Next.js Cookies API](https://nextjs.org/docs/app/api-reference/functions/cookies)
|
|
449
|
+
- [Upstash Redis Docs](https://upstash.com/docs)
|
|
450
|
+
- [Prisma Documentation](https://www.prisma.io/docs)
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## π License
|
|
260
455
|
|
|
261
456
|
MIT Β© Taimoor Safdar
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
<div align="center">
|
|
461
|
+
|
|
462
|
+
**Built with β€οΈ for the Next.js community**
|
|
463
|
+
|
|
464
|
+
[β¬ back to top](#-swift-auth)
|
|
465
|
+
|
|
466
|
+
</div>
|