-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathauthHandlers.ts
More file actions
198 lines (170 loc) · 6.67 KB
/
authHandlers.ts
File metadata and controls
198 lines (170 loc) · 6.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/**
* Mock Auth Handlers for MSW
*
* Provides in-memory mock implementations of the better-auth standard
* endpoints used by @object-ui/auth's createAuthClient. These handlers
* enable sign-up, sign-in, session, and sign-out flows in the MSW
* (browser / test) environment where no real AuthPlugin is available.
*
* NOTE: This is a mock/testing module only. Passwords are stored in
* plain text — never use this pattern in production code.
*
* Endpoints:
* POST /sign-up/email — register a new user
* POST /sign-in/email — authenticate with email + password
* GET /get-session — retrieve the current session
* POST /sign-out — clear the session
* POST /forget-password — no-op acknowledgement (better-auth convention)
* POST /reset-password — no-op acknowledgement
* POST /update-user — update the current user's profile
*/
import { http, HttpResponse } from 'msw';
import type { HttpHandler } from 'msw';
interface MockUser {
id: string;
name: string;
email: string;
emailVerified: boolean;
image?: string;
role: string;
}
interface MockSession {
token: string;
expiresAt: string;
}
/** Simple in-memory store for mock users and sessions. */
const users = new Map<string, MockUser & { password: string }>();
let currentSession: { user: MockUser; session: MockSession } | null = null;
let nextId = 1;
/** Reset all in-memory auth state. Call in test `beforeEach` for isolation. */
export function resetAuthState(): void {
users.clear();
currentSession = null;
nextId = 1;
}
function generateToken(): string {
return `mock-token-${Date.now()}-${Math.random().toString(36).slice(2)}`;
}
function generateExpiry(): string {
return new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
}
/** Return a user object without the password field. */
function toSafeUser(user: MockUser & { password: string }): MockUser {
const { password: _, ...safe } = user;
return safe;
}
/**
* Create MSW request handlers that mock the better-auth REST endpoints.
*
* @param baseUrl — The API prefix (e.g. "/api/v1/auth").
*/
export function createAuthHandlers(baseUrl: string): HttpHandler[] {
// Prefix with wildcard so handlers match full URLs in both browser
// (MSW worker, relative paths) and node (MSW server, absolute URLs).
const p = `*${baseUrl}`;
return [
// ── Sign Up ──────────────────────────────────────────────────────────
http.post(`${p}/sign-up/email`, async ({ request }) => {
const body = (await request.json()) as {
name?: string;
email?: string;
password?: string;
};
if (!body.email || !body.password) {
return HttpResponse.json(
{ message: 'Email and password are required' },
{ status: 400 },
);
}
if (users.has(body.email)) {
return HttpResponse.json(
{ message: 'User already exists' },
{ status: 409 },
);
}
const id = String(nextId++);
const user: MockUser & { password: string } = {
id,
name: body.name || body.email.split('@')[0],
email: body.email,
emailVerified: false,
role: 'user',
password: body.password,
};
users.set(body.email, user);
const session: MockSession = {
token: generateToken(),
expiresAt: generateExpiry(),
};
const safeUser = toSafeUser(user);
currentSession = { user: safeUser, session };
return HttpResponse.json({ user: safeUser, session });
}),
// ── Sign In ──────────────────────────────────────────────────────────
http.post(`${p}/sign-in/email`, async ({ request }) => {
const body = (await request.json()) as {
email?: string;
password?: string;
};
if (!body.email || !body.password) {
return HttpResponse.json(
{ message: 'Email and password are required' },
{ status: 400 },
);
}
const stored = users.get(body.email);
if (!stored || stored.password !== body.password) {
return HttpResponse.json(
{ message: 'Invalid email or password' },
{ status: 401 },
);
}
const session: MockSession = {
token: generateToken(),
expiresAt: generateExpiry(),
};
const safeUser = toSafeUser(stored);
currentSession = { user: safeUser, session };
return HttpResponse.json({ user: safeUser, session });
}),
// ── Get Session ──────────────────────────────────────────────────────
http.get(`${p}/get-session`, () => {
if (!currentSession) {
return HttpResponse.json(null, { status: 401 });
}
return HttpResponse.json(currentSession);
}),
// ── Sign Out ─────────────────────────────────────────────────────────
http.post(`${p}/sign-out`, () => {
currentSession = null;
return HttpResponse.json({ success: true });
}),
// ── Forgot Password (mock acknowledgement) ──────────────────────────
// better-auth uses "forget-password" (not "forgot-password")
http.post(`${p}/forget-password`, () => {
return HttpResponse.json({ status: true });
}),
// ── Reset Password (mock acknowledgement) ────────────────────────────
http.post(`${p}/reset-password`, () => {
return HttpResponse.json({ success: true });
}),
// ── Update User ──────────────────────────────────────────────────────
http.post(`${p}/update-user`, async ({ request }) => {
if (!currentSession) {
return HttpResponse.json(
{ message: 'Not authenticated' },
{ status: 401 },
);
}
const body = (await request.json()) as Partial<MockUser>;
const updated: MockUser = { ...currentSession.user, ...body };
currentSession = { ...currentSession, user: updated };
// Sync the stored record if we have one
const stored = users.get(updated.email);
if (stored) {
Object.assign(stored, body);
}
return HttpResponse.json({ user: updated });
}),
];
}