@main12/auth-login 0.1.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 +218 -0
- package/dist/auth/application/hooks/useForgotPasswordFlow.d.ts +10 -0
- package/dist/auth/application/hooks/useForgotPasswordFlow.js +45 -0
- package/dist/auth/application/hooks/useLoginFlow.d.ts +30 -0
- package/dist/auth/application/hooks/useLoginFlow.js +105 -0
- package/dist/auth/application/hooks/useSetPasswordFlow.d.ts +18 -0
- package/dist/auth/application/hooks/useSetPasswordFlow.js +59 -0
- package/dist/auth/application/hooks/useVerifyOtpFlow.d.ts +18 -0
- package/dist/auth/application/hooks/useVerifyOtpFlow.js +84 -0
- package/dist/auth/application/services/authService.d.ts +26 -0
- package/dist/auth/application/services/authService.js +90 -0
- package/dist/auth/domain/otp.d.ts +25 -0
- package/dist/auth/domain/otp.js +41 -0
- package/dist/auth/domain/passwordRules.d.ts +12 -0
- package/dist/auth/domain/passwordRules.js +34 -0
- package/dist/auth/domain/types.d.ts +35 -0
- package/dist/auth/domain/types.js +2 -0
- package/dist/components/AuthLayout.d.ts +20 -0
- package/dist/components/AuthLayout.js +49 -0
- package/dist/components/PoweredBy.d.ts +10 -0
- package/dist/components/PoweredBy.js +50 -0
- package/dist/components/email/baseTemplate.d.ts +13 -0
- package/dist/components/email/baseTemplate.js +69 -0
- package/dist/components/email/constants.d.ts +26 -0
- package/dist/components/email/constants.js +30 -0
- package/dist/components/email/index.d.ts +7 -0
- package/dist/components/email/index.js +7 -0
- package/dist/components/email/templates/otp.d.ts +16 -0
- package/dist/components/email/templates/otp.js +38 -0
- package/dist/components/email/templates/passwordChanged.d.ts +15 -0
- package/dist/components/email/templates/passwordChanged.js +33 -0
- package/dist/components/email/templates/passwordReset.d.ts +15 -0
- package/dist/components/email/templates/passwordReset.js +36 -0
- package/dist/components/email/templates/welcome.d.ts +16 -0
- package/dist/components/email/templates/welcome.js +38 -0
- package/dist/components/email/translations.d.ts +45 -0
- package/dist/components/email/translations.js +88 -0
- package/dist/components/pages/ForgotPasswordPage.d.ts +5 -0
- package/dist/components/pages/ForgotPasswordPage.js +45 -0
- package/dist/components/pages/LoginPage.d.ts +11 -0
- package/dist/components/pages/LoginPage.js +222 -0
- package/dist/components/pages/SetPasswordPage.d.ts +5 -0
- package/dist/components/pages/SetPasswordPage.js +74 -0
- package/dist/components/pages/SignupPage.d.ts +10 -0
- package/dist/components/pages/SignupPage.js +129 -0
- package/dist/components/pages/VerifyOtpPage.d.ts +5 -0
- package/dist/components/pages/VerifyOtpPage.js +87 -0
- package/dist/components/ui/index.d.ts +57 -0
- package/dist/components/ui/index.js +121 -0
- package/dist/css.d.js +0 -0
- package/dist/endpoints/authEndpoints.d.ts +22 -0
- package/dist/endpoints/authEndpoints.js +422 -0
- package/dist/exports/client.d.ts +24 -0
- package/dist/exports/client.js +22 -0
- package/dist/exports/rsc.d.ts +6 -0
- package/dist/exports/rsc.js +5 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +16 -0
- package/package.json +115 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { generateOtp, hashOtp, verifyOtp, getOtpExpiry, isOtpExpired } from '../auth/domain/otp.js';
|
|
2
|
+
import { generateWelcomeEmail, generateOtpEmail } from '../components/email/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* POST /api/auth/check-email — Check if user exists and has password
|
|
5
|
+
*/ export const checkEmailEndpoint = {
|
|
6
|
+
path: '/api/auth/check-email',
|
|
7
|
+
method: 'post',
|
|
8
|
+
handler: async (req)=>{
|
|
9
|
+
const { email } = req.json ? await req.json() : req.body || {};
|
|
10
|
+
if (!email) return Response.json({
|
|
11
|
+
error: 'Email is required'
|
|
12
|
+
}, {
|
|
13
|
+
status: 400
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
const users = await req.payload.find({
|
|
17
|
+
collection: 'users',
|
|
18
|
+
where: {
|
|
19
|
+
email: {
|
|
20
|
+
equals: email.toLowerCase().trim()
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
limit: 1
|
|
24
|
+
});
|
|
25
|
+
if (!users.docs?.length) {
|
|
26
|
+
return Response.json({
|
|
27
|
+
exists: false,
|
|
28
|
+
hasPassword: false,
|
|
29
|
+
authProvider: null
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const user = users.docs[0];
|
|
33
|
+
return Response.json({
|
|
34
|
+
exists: true,
|
|
35
|
+
hasPassword: !!user.password,
|
|
36
|
+
authProvider: user.authProvider || null
|
|
37
|
+
});
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return Response.json({
|
|
40
|
+
error: 'Failed to check email'
|
|
41
|
+
}, {
|
|
42
|
+
status: 500
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* POST /api/auth/otp/send — Generate OTP, store it, send email via Payload
|
|
49
|
+
*/ export const sendOtpEndpoint = {
|
|
50
|
+
path: '/api/auth/otp/send',
|
|
51
|
+
method: 'post',
|
|
52
|
+
handler: async (req)=>{
|
|
53
|
+
const body = req.json ? await req.json() : req.body || {};
|
|
54
|
+
const { email, purpose = 'login' } = body;
|
|
55
|
+
if (!email) return Response.json({
|
|
56
|
+
success: false,
|
|
57
|
+
message: 'Email is required'
|
|
58
|
+
}, {
|
|
59
|
+
status: 400
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
// Check user exists
|
|
63
|
+
const users = await req.payload.find({
|
|
64
|
+
collection: 'users',
|
|
65
|
+
where: {
|
|
66
|
+
email: {
|
|
67
|
+
equals: email.toLowerCase().trim()
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
limit: 1
|
|
71
|
+
});
|
|
72
|
+
if (!users.docs?.length) {
|
|
73
|
+
return Response.json({
|
|
74
|
+
success: false,
|
|
75
|
+
message: 'No account found with this email'
|
|
76
|
+
}, {
|
|
77
|
+
status: 404
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const user = users.docs[0];
|
|
81
|
+
const otp = generateOtp();
|
|
82
|
+
const hashed = hashOtp(otp);
|
|
83
|
+
const expiresAt = getOtpExpiry(10);
|
|
84
|
+
// Store OTP in a custom collection or users doc
|
|
85
|
+
// For simplicity, we'll use the users collection with an otp field
|
|
86
|
+
// In production, use a dedicated `otps` collection
|
|
87
|
+
try {
|
|
88
|
+
await req.payload.update({
|
|
89
|
+
collection: 'users',
|
|
90
|
+
id: user.id,
|
|
91
|
+
data: {
|
|
92
|
+
otpHash: hashed,
|
|
93
|
+
otpPurpose: purpose,
|
|
94
|
+
otpAttempts: 0,
|
|
95
|
+
otpExpiresAt: expiresAt.toISOString()
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
} catch {
|
|
99
|
+
// If the users collection doesn't have OTP fields, create a dedicated otps doc
|
|
100
|
+
await req.payload.create({
|
|
101
|
+
collection: 'otps',
|
|
102
|
+
data: {
|
|
103
|
+
email: email.toLowerCase().trim(),
|
|
104
|
+
hash: hashed,
|
|
105
|
+
purpose,
|
|
106
|
+
attempts: 0,
|
|
107
|
+
expiresAt: expiresAt.toISOString()
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// Send email via Payload's configured adapter
|
|
112
|
+
const emailResult = purpose === 'signup' ? generateOtpEmail({
|
|
113
|
+
userName: user.name || user.email,
|
|
114
|
+
otp,
|
|
115
|
+
purpose: 'login'
|
|
116
|
+
}) : generateOtpEmail({
|
|
117
|
+
userName: user.name || user.email,
|
|
118
|
+
otp,
|
|
119
|
+
purpose: purpose
|
|
120
|
+
});
|
|
121
|
+
await req.payload.sendEmail({
|
|
122
|
+
to: email,
|
|
123
|
+
subject: emailResult.subject,
|
|
124
|
+
html: emailResult.html
|
|
125
|
+
});
|
|
126
|
+
return Response.json({
|
|
127
|
+
success: true
|
|
128
|
+
});
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error('OTP send error:', err);
|
|
131
|
+
return Response.json({
|
|
132
|
+
success: false,
|
|
133
|
+
message: err.message || 'Failed to send OTP'
|
|
134
|
+
}, {
|
|
135
|
+
status: 500
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* POST /api/auth/otp/verify — Verify OTP and login the user
|
|
142
|
+
*/ export const verifyOtpEndpoint = {
|
|
143
|
+
path: '/api/auth/otp/verify',
|
|
144
|
+
method: 'post',
|
|
145
|
+
handler: async (req)=>{
|
|
146
|
+
const body = req.json ? await req.json() : req.body || {};
|
|
147
|
+
const { email, otp } = body;
|
|
148
|
+
if (!email || !otp) return Response.json({
|
|
149
|
+
success: false,
|
|
150
|
+
error: 'Email and OTP are required'
|
|
151
|
+
}, {
|
|
152
|
+
status: 400
|
|
153
|
+
});
|
|
154
|
+
try {
|
|
155
|
+
// Find OTP record — try users collection first
|
|
156
|
+
const users = await req.payload.find({
|
|
157
|
+
collection: 'users',
|
|
158
|
+
where: {
|
|
159
|
+
email: {
|
|
160
|
+
equals: email.toLowerCase().trim()
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
limit: 1
|
|
164
|
+
});
|
|
165
|
+
let otpHash;
|
|
166
|
+
let otpAttempts = 0;
|
|
167
|
+
let otpExpiresAt;
|
|
168
|
+
const user = users.docs?.[0];
|
|
169
|
+
if (user?.otpHash) {
|
|
170
|
+
otpHash = user.otpHash;
|
|
171
|
+
otpAttempts = user.otpAttempts || 0;
|
|
172
|
+
otpExpiresAt = user.otpExpiresAt;
|
|
173
|
+
} else {
|
|
174
|
+
// Try dedicated otps collection
|
|
175
|
+
const otpDocs = await req.payload.find({
|
|
176
|
+
collection: 'otps',
|
|
177
|
+
where: {
|
|
178
|
+
email: {
|
|
179
|
+
equals: email.toLowerCase().trim()
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
sort: '-createdAt',
|
|
183
|
+
limit: 1
|
|
184
|
+
});
|
|
185
|
+
const otpDoc = otpDocs.docs?.[0];
|
|
186
|
+
if (otpDoc) {
|
|
187
|
+
otpHash = otpDoc.hash;
|
|
188
|
+
otpAttempts = otpDoc.attempts || 0;
|
|
189
|
+
otpExpiresAt = otpDoc.expiresAt;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!otpHash) {
|
|
193
|
+
return Response.json({
|
|
194
|
+
success: false,
|
|
195
|
+
error: 'No OTP found. Please request a new code.'
|
|
196
|
+
}, {
|
|
197
|
+
status: 400
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (isOtpExpired(otpExpiresAt)) {
|
|
201
|
+
return Response.json({
|
|
202
|
+
success: false,
|
|
203
|
+
error: 'Code has expired. Please request a new one.'
|
|
204
|
+
}, {
|
|
205
|
+
status: 400
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
if (otpAttempts >= 3) {
|
|
209
|
+
return Response.json({
|
|
210
|
+
success: false,
|
|
211
|
+
error: 'Too many attempts. Please request a new code.'
|
|
212
|
+
}, {
|
|
213
|
+
status: 400
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
// Increment attempts
|
|
217
|
+
if (user?.otpHash) {
|
|
218
|
+
await req.payload.update({
|
|
219
|
+
collection: 'users',
|
|
220
|
+
id: user.id,
|
|
221
|
+
data: {
|
|
222
|
+
otpAttempts: otpAttempts + 1
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (!verifyOtp(otp, otpHash)) {
|
|
227
|
+
return Response.json({
|
|
228
|
+
success: false,
|
|
229
|
+
error: 'Invalid code. Please try again.'
|
|
230
|
+
}, {
|
|
231
|
+
status: 400
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
// OTP is valid — log the user in
|
|
235
|
+
// Payload requires password for login. We use the stored password
|
|
236
|
+
// (auto-generated during signup or previously set by user).
|
|
237
|
+
const userData = users.docs?.[0];
|
|
238
|
+
const result = await req.payload.login({
|
|
239
|
+
collection: 'users',
|
|
240
|
+
data: {
|
|
241
|
+
email: email.toLowerCase().trim(),
|
|
242
|
+
password: userData?.password || ''
|
|
243
|
+
},
|
|
244
|
+
req: req
|
|
245
|
+
});
|
|
246
|
+
// Clean up OTP
|
|
247
|
+
if (user?.otpHash) {
|
|
248
|
+
await req.payload.update({
|
|
249
|
+
collection: 'users',
|
|
250
|
+
id: user.id,
|
|
251
|
+
data: {
|
|
252
|
+
otpHash: null,
|
|
253
|
+
otpPurpose: null,
|
|
254
|
+
otpAttempts: null,
|
|
255
|
+
otpExpiresAt: null
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return Response.json({
|
|
260
|
+
success: true,
|
|
261
|
+
token: result.token,
|
|
262
|
+
isNewUser: !user?.password
|
|
263
|
+
});
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error('OTP verify error:', err);
|
|
266
|
+
return Response.json({
|
|
267
|
+
success: false,
|
|
268
|
+
error: err.message || 'Verification failed'
|
|
269
|
+
}, {
|
|
270
|
+
status: 500
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* POST /api/auth/set-password — Set/update password for authenticated user
|
|
277
|
+
*/ export const setPasswordEndpoint = {
|
|
278
|
+
path: '/api/auth/set-password',
|
|
279
|
+
method: 'post',
|
|
280
|
+
handler: async (req)=>{
|
|
281
|
+
const body = req.json ? await req.json() : req.body || {};
|
|
282
|
+
const { password, confirmPassword } = body;
|
|
283
|
+
if (!password || !confirmPassword) {
|
|
284
|
+
return Response.json({
|
|
285
|
+
success: false,
|
|
286
|
+
message: 'Password and confirmation are required'
|
|
287
|
+
}, {
|
|
288
|
+
status: 400
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (password !== confirmPassword) {
|
|
292
|
+
return Response.json({
|
|
293
|
+
success: false,
|
|
294
|
+
message: 'Passwords do not match'
|
|
295
|
+
}, {
|
|
296
|
+
status: 400
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
if (password.length < 8) {
|
|
300
|
+
return Response.json({
|
|
301
|
+
success: false,
|
|
302
|
+
message: 'Password must be at least 8 characters'
|
|
303
|
+
}, {
|
|
304
|
+
status: 400
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
try {
|
|
308
|
+
const user = req.user;
|
|
309
|
+
if (!user) {
|
|
310
|
+
return Response.json({
|
|
311
|
+
success: false,
|
|
312
|
+
message: 'Not authenticated'
|
|
313
|
+
}, {
|
|
314
|
+
status: 401
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
await req.payload.update({
|
|
318
|
+
collection: 'users',
|
|
319
|
+
id: user.id,
|
|
320
|
+
data: {
|
|
321
|
+
password,
|
|
322
|
+
confirmPassword
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
return Response.json({
|
|
326
|
+
success: true,
|
|
327
|
+
message: 'Password set successfully'
|
|
328
|
+
});
|
|
329
|
+
} catch (err) {
|
|
330
|
+
return Response.json({
|
|
331
|
+
success: false,
|
|
332
|
+
message: err.message || 'Failed to set password'
|
|
333
|
+
}, {
|
|
334
|
+
status: 500
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
/**
|
|
340
|
+
* POST /api/auth/signup — Create new user + send welcome email
|
|
341
|
+
*/ export const signupEndpoint = {
|
|
342
|
+
path: '/api/auth/signup',
|
|
343
|
+
method: 'post',
|
|
344
|
+
handler: async (req)=>{
|
|
345
|
+
const body = req.json ? await req.json() : req.body || {};
|
|
346
|
+
const { name, email } = body;
|
|
347
|
+
if (!email) {
|
|
348
|
+
return Response.json({
|
|
349
|
+
success: false,
|
|
350
|
+
message: 'Email is required'
|
|
351
|
+
}, {
|
|
352
|
+
status: 400
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
// Check if user already exists
|
|
357
|
+
const existing = await req.payload.find({
|
|
358
|
+
collection: 'users',
|
|
359
|
+
where: {
|
|
360
|
+
email: {
|
|
361
|
+
equals: email.toLowerCase().trim()
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
limit: 1
|
|
365
|
+
});
|
|
366
|
+
if (existing.docs?.length) {
|
|
367
|
+
return Response.json({
|
|
368
|
+
success: false,
|
|
369
|
+
message: 'An account with this email already exists'
|
|
370
|
+
}, {
|
|
371
|
+
status: 409
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// Auto-generate a random password (user sets their own via OTP flow)
|
|
375
|
+
const tempPassword = `tmp_${Math.random().toString(36).slice(2)}_${Date.now()}`;
|
|
376
|
+
const user = await req.payload.create({
|
|
377
|
+
collection: 'users',
|
|
378
|
+
data: {
|
|
379
|
+
email: email.toLowerCase().trim(),
|
|
380
|
+
password: tempPassword,
|
|
381
|
+
name: name?.trim() || email.split('@')[0],
|
|
382
|
+
role: 'user'
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
// Send welcome email
|
|
386
|
+
try {
|
|
387
|
+
const welcomeEmail = generateWelcomeEmail({
|
|
388
|
+
userName: name?.trim() || email.split('@')[0],
|
|
389
|
+
userEmail: email.toLowerCase().trim()
|
|
390
|
+
});
|
|
391
|
+
await req.payload.sendEmail({
|
|
392
|
+
to: email,
|
|
393
|
+
subject: welcomeEmail.subject,
|
|
394
|
+
html: welcomeEmail.html
|
|
395
|
+
});
|
|
396
|
+
} catch (emailErr) {
|
|
397
|
+
console.error('Welcome email failed:', emailErr);
|
|
398
|
+
// Don't fail signup if email fails
|
|
399
|
+
}
|
|
400
|
+
return Response.json({
|
|
401
|
+
success: true,
|
|
402
|
+
message: 'Account created',
|
|
403
|
+
userId: user.id
|
|
404
|
+
});
|
|
405
|
+
} catch (err) {
|
|
406
|
+
console.error('Signup error:', err);
|
|
407
|
+
return Response.json({
|
|
408
|
+
success: false,
|
|
409
|
+
message: err.message || 'Signup failed'
|
|
410
|
+
}, {
|
|
411
|
+
status: 500
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
export const authEndpoints = [
|
|
417
|
+
checkEmailEndpoint,
|
|
418
|
+
sendOtpEndpoint,
|
|
419
|
+
verifyOtpEndpoint,
|
|
420
|
+
setPasswordEndpoint,
|
|
421
|
+
signupEndpoint
|
|
422
|
+
];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export { useLoginFlow } from '../auth/application/hooks/useLoginFlow.js';
|
|
2
|
+
export { useVerifyOtpFlow } from '../auth/application/hooks/useVerifyOtpFlow.js';
|
|
3
|
+
export { useForgotPasswordFlow } from '../auth/application/hooks/useForgotPasswordFlow.js';
|
|
4
|
+
export { useSetPasswordFlow } from '../auth/application/hooks/useSetPasswordFlow.js';
|
|
5
|
+
export { checkEmail, sendOtp, verifyOtp, setUserPassword, signup, initiateGoogleLogin, } from '../auth/application/services/authService.js';
|
|
6
|
+
export { evaluatePasswordStrength, isPasswordValid, MIN_PASSWORD_LENGTH } from '../auth/domain/passwordRules.js';
|
|
7
|
+
export { default as LoginPage } from '../components/pages/LoginPage.js';
|
|
8
|
+
export { default as SignupPage } from '../components/pages/SignupPage.js';
|
|
9
|
+
export { default as ForgotPasswordPage } from '../components/pages/ForgotPasswordPage.js';
|
|
10
|
+
export { default as VerifyOtpPage } from '../components/pages/VerifyOtpPage.js';
|
|
11
|
+
export { default as SetPasswordPage } from '../components/pages/SetPasswordPage.js';
|
|
12
|
+
export { AuthLayout } from '../components/AuthLayout.js';
|
|
13
|
+
export { PoweredBy } from '../components/PoweredBy.js';
|
|
14
|
+
export type { LoginStep, OTPPurpose, CheckEmailResponse, SendOtpResponse, VerifyOtpResponse, SetPasswordResponse, SignupResponse, PasswordStrengthResult, } from '../auth/domain/types.js';
|
|
15
|
+
export type { UseLoginFlowOptions } from '../auth/application/hooks/useLoginFlow.js';
|
|
16
|
+
export type { UseVerifyOtpFlowOptions } from '../auth/application/hooks/useVerifyOtpFlow.js';
|
|
17
|
+
export type { UseSetPasswordFlowOptions } from '../auth/application/hooks/useSetPasswordFlow.js';
|
|
18
|
+
export type { LoginPageProps } from '../components/pages/LoginPage.js';
|
|
19
|
+
export type { SignupPageProps } from '../components/pages/SignupPage.js';
|
|
20
|
+
export type { ForgotPasswordPageProps } from '../components/pages/ForgotPasswordPage.js';
|
|
21
|
+
export type { VerifyOtpPageProps } from '../components/pages/VerifyOtpPage.js';
|
|
22
|
+
export type { SetPasswordPageProps } from '../components/pages/SetPasswordPage.js';
|
|
23
|
+
export type { AuthLayoutConfig, AuthLayoutProps } from '../components/AuthLayout.js';
|
|
24
|
+
export type { PoweredByProps } from '../components/PoweredBy.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
// ============================================================
|
|
3
|
+
// Client-side exports for @main12/auth-login/client
|
|
4
|
+
// ============================================================
|
|
5
|
+
// Auth hooks
|
|
6
|
+
export { useLoginFlow } from '../auth/application/hooks/useLoginFlow.js';
|
|
7
|
+
export { useVerifyOtpFlow } from '../auth/application/hooks/useVerifyOtpFlow.js';
|
|
8
|
+
export { useForgotPasswordFlow } from '../auth/application/hooks/useForgotPasswordFlow.js';
|
|
9
|
+
export { useSetPasswordFlow } from '../auth/application/hooks/useSetPasswordFlow.js';
|
|
10
|
+
// Auth service functions (client-side fetch wrappers)
|
|
11
|
+
export { checkEmail, sendOtp, verifyOtp, setUserPassword, signup, initiateGoogleLogin } from '../auth/application/services/authService.js';
|
|
12
|
+
// Domain utilities
|
|
13
|
+
export { evaluatePasswordStrength, isPasswordValid, MIN_PASSWORD_LENGTH } from '../auth/domain/passwordRules.js';
|
|
14
|
+
// Page components
|
|
15
|
+
export { default as LoginPage } from '../components/pages/LoginPage.js';
|
|
16
|
+
export { default as SignupPage } from '../components/pages/SignupPage.js';
|
|
17
|
+
export { default as ForgotPasswordPage } from '../components/pages/ForgotPasswordPage.js';
|
|
18
|
+
export { default as VerifyOtpPage } from '../components/pages/VerifyOtpPage.js';
|
|
19
|
+
export { default as SetPasswordPage } from '../components/pages/SetPasswordPage.js';
|
|
20
|
+
// Layout & shared components
|
|
21
|
+
export { AuthLayout } from '../components/AuthLayout.js';
|
|
22
|
+
export { PoweredBy } from '../components/PoweredBy.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { wrapInBaseTemplate, DEFAULT_COLORS, SOCIAL_ICONS, getBaseUrl, getSenderEmail, getEmailTranslations, generateOtpEmail, generateWelcomeEmail, generatePasswordResetEmail, generatePasswordChangedEmail, } from '../components/email/index.js';
|
|
2
|
+
export type { BaseTemplateOptions, } from '../components/email/baseTemplate.js';
|
|
3
|
+
export type { EmailColors, SocialLink, SocialPlatform, } from '../components/email/constants.js';
|
|
4
|
+
export type { SupportedLanguage, EmailTranslations, } from '../components/email/translations.js';
|
|
5
|
+
export type { OtpEmailParams, OtpEmailResult, WelcomeEmailParams, WelcomeEmailResult, PasswordResetEmailParams, PasswordResetEmailResult, PasswordChangedEmailParams, PasswordChangedEmailResult, } from '../components/email/index.js';
|
|
6
|
+
export type { AuthLoginPluginOptions } from '../index.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// RSC (React Server Components) exports for @main12/auth-login/rsc
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Email template system (server-only — uses Node APIs)
|
|
5
|
+
export { wrapInBaseTemplate, DEFAULT_COLORS, SOCIAL_ICONS, getBaseUrl, getSenderEmail, getEmailTranslations, generateOtpEmail, generateWelcomeEmail, generatePasswordResetEmail, generatePasswordChangedEmail } from '../components/email/index.js';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Config } from 'payload';
|
|
2
|
+
export interface AuthLoginPluginOptions {
|
|
3
|
+
/** Enable/disable the plugin */
|
|
4
|
+
enabled?: boolean;
|
|
5
|
+
/** Project name used in emails */
|
|
6
|
+
projectName?: string;
|
|
7
|
+
/** Contact email used in email footers */
|
|
8
|
+
contactEmail?: string;
|
|
9
|
+
/** Domain for links in emails */
|
|
10
|
+
domain?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const authLoginPlugin: (options?: AuthLoginPluginOptions) => (config: Config) => Config;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { authEndpoints } from './endpoints/authEndpoints.js';
|
|
2
|
+
export const authLoginPlugin = (options = {})=>(config)=>{
|
|
3
|
+
if (options.enabled === false) return config;
|
|
4
|
+
// Register auth API endpoints
|
|
5
|
+
config.endpoints = [
|
|
6
|
+
...config.endpoints || [],
|
|
7
|
+
...authEndpoints
|
|
8
|
+
];
|
|
9
|
+
// Chain onInit
|
|
10
|
+
const incomingOnInit = config.onInit;
|
|
11
|
+
config.onInit = async (payload)=>{
|
|
12
|
+
if (incomingOnInit) await incomingOnInit(payload);
|
|
13
|
+
payload.logger.info(`[auth-login] Plugin initialized for ${options.projectName || 'project'}`);
|
|
14
|
+
};
|
|
15
|
+
return config;
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@main12/auth-login",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable Payload CMS auth plugin — login, signup, OTP, forgot password, branded emails, Powered by Main12",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./src/index.ts",
|
|
10
|
+
"types": "./src/index.ts",
|
|
11
|
+
"default": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"import": "./src/exports/client.ts",
|
|
15
|
+
"types": "./src/exports/client.ts",
|
|
16
|
+
"default": "./src/exports/client.ts"
|
|
17
|
+
},
|
|
18
|
+
"./rsc": {
|
|
19
|
+
"import": "./src/exports/rsc.ts",
|
|
20
|
+
"types": "./src/exports/rsc.ts",
|
|
21
|
+
"default": "./src/exports/rsc.ts"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"main": "./src/index.ts",
|
|
25
|
+
"types": "./src/index.ts",
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "pnpm copyfiles && pnpm build:types && pnpm build:swc",
|
|
31
|
+
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
32
|
+
"build:types": "tsc --outDir dist",
|
|
33
|
+
"clean": "rimraf {dist,*.tsbuildinfo}",
|
|
34
|
+
"copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/",
|
|
35
|
+
"dev": "next dev dev --turbo",
|
|
36
|
+
"dev:generate-importmap": "pnpm dev:payload generate:importmap",
|
|
37
|
+
"dev:generate-types": "pnpm dev:payload generate:types",
|
|
38
|
+
"dev:payload": "cross-env PAYLOAD_CONFIG_PATH=./dev/payload.config.ts payload",
|
|
39
|
+
"lint": "eslint",
|
|
40
|
+
"lint:fix": "eslint ./src --fix",
|
|
41
|
+
"test": "vitest"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
45
|
+
"@payloadcms/db-sqlite": "3.82.1",
|
|
46
|
+
"@payloadcms/next": "3.82.1",
|
|
47
|
+
"@payloadcms/richtext-lexical": "3.82.1",
|
|
48
|
+
"@payloadcms/ui": "3.82.1",
|
|
49
|
+
"@swc/cli": "0.6.0",
|
|
50
|
+
"@tailwindcss/postcss": "^4.3.3",
|
|
51
|
+
"@types/node": "24.12.3",
|
|
52
|
+
"@types/react": "19.2.14",
|
|
53
|
+
"@types/react-dom": "19.2.3",
|
|
54
|
+
"copyfiles": "2.4.1",
|
|
55
|
+
"cross-env": "10.1.0",
|
|
56
|
+
"eslint": "^9.23.0",
|
|
57
|
+
"next": "16.2.7",
|
|
58
|
+
"payload": "3.82.1",
|
|
59
|
+
"react": "19.2.6",
|
|
60
|
+
"react-dom": "19.2.6",
|
|
61
|
+
"rimraf": "3.0.2",
|
|
62
|
+
"sharp": "0.34.2",
|
|
63
|
+
"tailwindcss": "^4.3.3",
|
|
64
|
+
"typescript": "6.0.3",
|
|
65
|
+
"vitest": "4.1.6"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"next": "^16.0.0",
|
|
69
|
+
"payload": "^3.82.0",
|
|
70
|
+
"react": "^19.0.0"
|
|
71
|
+
},
|
|
72
|
+
"peerDependenciesMeta": {
|
|
73
|
+
"@heroui/react": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"framer-motion": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"next-intl": {
|
|
80
|
+
"optional": true
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"engines": {
|
|
84
|
+
"node": ">=18.20.2"
|
|
85
|
+
},
|
|
86
|
+
"publishConfig": {
|
|
87
|
+
"exports": {
|
|
88
|
+
".": {
|
|
89
|
+
"import": "./dist/index.js",
|
|
90
|
+
"types": "./dist/index.d.ts",
|
|
91
|
+
"default": "./dist/index.js"
|
|
92
|
+
},
|
|
93
|
+
"./client": {
|
|
94
|
+
"import": "./dist/exports/client.js",
|
|
95
|
+
"types": "./dist/exports/client.d.ts",
|
|
96
|
+
"default": "./dist/exports/client.js"
|
|
97
|
+
},
|
|
98
|
+
"./rsc": {
|
|
99
|
+
"import": "./dist/exports/rsc.js",
|
|
100
|
+
"types": "./dist/exports/rsc.d.ts",
|
|
101
|
+
"default": "./dist/exports/rsc.js"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"main": "./dist/index.js",
|
|
105
|
+
"types": "./dist/index.d.ts"
|
|
106
|
+
},
|
|
107
|
+
"pnpm": {
|
|
108
|
+
"onlyBuiltDependencies": [
|
|
109
|
+
"@swc/core",
|
|
110
|
+
"sharp",
|
|
111
|
+
"esbuild"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"registry": "https://registry.npmjs.org/"
|
|
115
|
+
}
|