added password resetting and custom passwords
This commit is contained in:
parent
2b84b9c433
commit
0314add130
4 changed files with 696 additions and 209 deletions
|
@ -1,6 +1,5 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { Authentication } from '../../../scripts/pocketbase/Authentication';
|
import { Authentication } from '../../../scripts/pocketbase/Authentication';
|
||||||
import { Update } from '../../../scripts/pocketbase/Update';
|
|
||||||
import { Collections, type User } from '../../../schemas/pocketbase/schema';
|
import { Collections, type User } from '../../../schemas/pocketbase/schema';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
|
||||||
|
@ -9,8 +8,19 @@ export default function EmailRequestSettings() {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [requesting, setRequesting] = useState(false);
|
const [requesting, setRequesting] = useState(false);
|
||||||
|
const [resettingPassword, setResettingPassword] = useState(false);
|
||||||
const [isOfficer, setIsOfficer] = useState(false);
|
const [isOfficer, setIsOfficer] = useState(false);
|
||||||
const [createdEmail, setCreatedEmail] = useState<string | null>(null);
|
const [createdEmail, setCreatedEmail] = useState<string | null>(null);
|
||||||
|
const [showPasswordReset, setShowPasswordReset] = useState(false);
|
||||||
|
const [newPassword, setNewPassword] = useState('');
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [passwordError, setPasswordError] = useState('');
|
||||||
|
|
||||||
|
// For initial email creation
|
||||||
|
const [initialPassword, setInitialPassword] = useState('');
|
||||||
|
const [initialConfirmPassword, setInitialConfirmPassword] = useState('');
|
||||||
|
const [initialPasswordError, setInitialPasswordError] = useState('');
|
||||||
|
const [showEmailForm, setShowEmailForm] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadUserData = async () => {
|
const loadUserData = async () => {
|
||||||
|
@ -46,12 +56,43 @@ export default function EmailRequestSettings() {
|
||||||
loadUserData();
|
loadUserData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const toggleEmailForm = () => {
|
||||||
|
setShowEmailForm(!showEmailForm);
|
||||||
|
setInitialPassword('');
|
||||||
|
setInitialConfirmPassword('');
|
||||||
|
setInitialPasswordError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateInitialPassword = () => {
|
||||||
|
if (initialPassword.length < 8) {
|
||||||
|
setInitialPasswordError('Password must be at least 8 characters long');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialPassword !== initialConfirmPassword) {
|
||||||
|
setInitialPasswordError('Passwords do not match');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitialPasswordError('');
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
const handleRequestEmail = async () => {
|
const handleRequestEmail = async () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
|
if (initialPassword && !validateInitialPassword()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setRequesting(true);
|
setRequesting(true);
|
||||||
|
|
||||||
|
// Determine what the email will be
|
||||||
|
const emailUsername = user.email.split('@')[0].toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||||
|
const emailDomain = import.meta.env.PUBLIC_MXROUTE_EMAIL_DOMAIN || 'ieeeucsd.org';
|
||||||
|
const expectedEmail = `${emailUsername}@${emailDomain}`;
|
||||||
|
|
||||||
// Call the API to create the email account
|
// Call the API to create the email account
|
||||||
const response = await fetch('/api/create-ieee-email', {
|
const response = await fetch('/api/create-ieee-email', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -61,7 +102,8 @@ export default function EmailRequestSettings() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
email: user.email
|
email: user.email,
|
||||||
|
password: initialPassword || undefined
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -77,7 +119,8 @@ export default function EmailRequestSettings() {
|
||||||
requested_email: true
|
requested_email: true
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.success('IEEE email created successfully! Check your email for login details.');
|
toast.success('IEEE email created successfully!');
|
||||||
|
setShowEmailForm(false);
|
||||||
} else {
|
} else {
|
||||||
toast.error(result.message || 'Failed to create email. Please contact the webmaster for assistance.');
|
toast.error(result.message || 'Failed to create email. Please contact the webmaster for assistance.');
|
||||||
}
|
}
|
||||||
|
@ -89,6 +132,71 @@ export default function EmailRequestSettings() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const togglePasswordReset = () => {
|
||||||
|
setShowPasswordReset(!showPasswordReset);
|
||||||
|
setNewPassword('');
|
||||||
|
setConfirmPassword('');
|
||||||
|
setPasswordError('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const validatePassword = () => {
|
||||||
|
if (newPassword.length < 8) {
|
||||||
|
setPasswordError('Password must be at least 8 characters long');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newPassword !== confirmPassword) {
|
||||||
|
setPasswordError('Passwords do not match');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPasswordError('');
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetPassword = async () => {
|
||||||
|
if (!user || !user.requested_email) return;
|
||||||
|
|
||||||
|
if (!validatePassword()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the email address
|
||||||
|
const emailAddress = createdEmail || (user ? `${user.email.split('@')[0].toLowerCase().replace(/[^a-z0-9]/g, "")}@ieeeucsd.org` : '');
|
||||||
|
|
||||||
|
try {
|
||||||
|
setResettingPassword(true);
|
||||||
|
|
||||||
|
// Call the API to reset the password
|
||||||
|
const response = await fetch('/api/reset-email-password', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: emailAddress,
|
||||||
|
password: newPassword
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result.success) {
|
||||||
|
toast.success('Password reset successfully!');
|
||||||
|
setShowPasswordReset(false);
|
||||||
|
setNewPassword('');
|
||||||
|
setConfirmPassword('');
|
||||||
|
} else {
|
||||||
|
toast.error(result.message || 'Failed to reset password. Please contact the webmaster for assistance.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error resetting password:', error);
|
||||||
|
toast.error('Failed to reset password. Please try again later.');
|
||||||
|
} finally {
|
||||||
|
setResettingPassword(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center p-8">
|
<div className="flex justify-center items-center p-8">
|
||||||
|
@ -107,27 +215,154 @@ export default function EmailRequestSettings() {
|
||||||
|
|
||||||
if (user?.requested_email || createdEmail) {
|
if (user?.requested_email || createdEmail) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-6">
|
||||||
<div className="p-4 bg-base-200 rounded-lg">
|
<div className="p-4 bg-base-200 rounded-lg">
|
||||||
<h3 className="font-bold text-lg mb-2">
|
<h3 className="font-bold text-lg mb-2">
|
||||||
{createdEmail ? 'Your IEEE Email Address' : 'Email Request Status'}
|
{createdEmail ? 'Your IEEE Email Address' : 'Email Request Status'}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{createdEmail && (
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<p className="text-xl font-mono bg-base-100 p-2 rounded">{createdEmail}</p>
|
<p className="text-xl font-mono bg-base-100 p-2 rounded">
|
||||||
|
{createdEmail || (user ? `${user.email.split('@')[0].toLowerCase().replace(/[^a-z0-9]/g, "")}@ieeeucsd.org` : '')}
|
||||||
|
</p>
|
||||||
|
{initialPassword ? (
|
||||||
|
<p className="mt-2 text-sm">Your email has been created with the password you provided.</p>
|
||||||
|
) : (
|
||||||
<p className="mt-2 text-sm">Check your personal email for login instructions.</p>
|
<p className="mt-2 text-sm">Check your personal email for login instructions.</p>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h4 className="font-semibold mb-1">Access Your Email</h4>
|
<h4 className="font-semibold mb-1">Access Your Email</h4>
|
||||||
<ul className="list-disc list-inside space-y-1">
|
<ul className="list-disc list-inside space-y-1">
|
||||||
<li>Webmail: <a href="https://heracles.mxrouting.net:2096/" target="_blank" rel="noopener noreferrer" className="link link-primary">https://heracles.mxrouting.net:2096/</a></li>
|
<li>Webmail: <a href="https://mail.ieeeucsd.org" target="_blank" rel="noopener noreferrer" className="link link-primary">https://mail.ieeeucsd.org</a></li>
|
||||||
<li>IMAP/SMTP settings: <a href="https://mxroute.com/setup/" target="_blank" rel="noopener noreferrer" className="link link-primary">https://mxroute.com/setup/</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
{!showPasswordReset ? (
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary w-full"
|
||||||
|
onClick={togglePasswordReset}
|
||||||
|
>
|
||||||
|
Reset Email Password
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4 p-4 bg-base-100 rounded-lg">
|
||||||
|
<h4 className="font-semibold">Reset Your Email Password</h4>
|
||||||
|
|
||||||
|
<div className="alert alert-warning">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" className="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||||
|
<span>
|
||||||
|
<strong>Important:</strong> After resetting your password, you'll need to update it in any email clients, Gmail integrations, or mobile devices where you've set up this email account.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">New Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="input input-bordered"
|
||||||
|
value={newPassword}
|
||||||
|
onChange={(e) => setNewPassword(e.target.value)}
|
||||||
|
placeholder="Enter new password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Confirm Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="input input-bordered"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
placeholder="Confirm new password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{passwordError && (
|
||||||
|
<div className="text-error text-sm">{passwordError}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary flex-1"
|
||||||
|
onClick={handleResetPassword}
|
||||||
|
disabled={resettingPassword}
|
||||||
|
>
|
||||||
|
{resettingPassword ? (
|
||||||
|
<>
|
||||||
|
<span className="loading loading-spinner loading-sm"></span>
|
||||||
|
Resetting...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'Reset Password'
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-outline"
|
||||||
|
onClick={togglePasswordReset}
|
||||||
|
disabled={resettingPassword}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!showPasswordReset && (
|
||||||
|
<p className="text-xs mt-2 opacity-70">
|
||||||
|
Reset your IEEE email password to a new password of your choice.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 bg-base-200 rounded-lg">
|
||||||
|
<h3 className="font-bold text-lg mb-4">Setting Up Your IEEE Email in Gmail</h3>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="font-semibold mb-2">First Step: Set Up Sending From Your IEEE Email</h4>
|
||||||
|
<ol className="list-decimal list-inside space-y-2">
|
||||||
|
<li>Go to settings (gear icon) → Accounts and Import</li>
|
||||||
|
<li>In the section that says <span className="text-blue-600">Send mail as:</span>, select <span className="text-blue-600">Reply from the same address the message was sent to</span></li>
|
||||||
|
<li>In that same section, select <span className="text-blue-600">Add another email address</span></li>
|
||||||
|
<li>For the Name, put your actual name (e.g. Charles Nguyen) if this is your personal ieeeucsd.org or put the department name (e.g. IEEEUCSD Webmaster)</li>
|
||||||
|
<li>For the Email address, put the email that was provided for you</li>
|
||||||
|
<li>Make sure the <span className="text-blue-600">Treat as an alias</span> button is selected. Go to the next step</li>
|
||||||
|
<li>For the SMTP Server, put <span className="text-blue-600">mail.ieeeucsd.org</span></li>
|
||||||
|
<li>For the username, put in your <span className="text-blue-600">FULL ieeeucsd email address</span></li>
|
||||||
|
<li>For the password, put in the email's password</li>
|
||||||
|
<li>For the port, put in <span className="text-blue-600">587</span></li>
|
||||||
|
<li>Make sure you select <span className="text-blue-600">Secured connection with TLS</span></li>
|
||||||
|
<li>Go back to mail.ieeeucsd.org and verify the email that Google has sent you</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="font-semibold mb-2">Second Step: Set Up Receiving Your IEEE Email</h4>
|
||||||
|
<ol className="list-decimal list-inside space-y-2">
|
||||||
|
<li>Go to settings (gear icon) → Accounts and Import</li>
|
||||||
|
<li>In the section that says <span className="text-blue-600">Check mail from other accounts:</span>, select <span className="text-blue-600">Add a mail account</span></li>
|
||||||
|
<li>Put in the ieeeucsd email and hit next</li>
|
||||||
|
<li>Make sure <span className="text-blue-600">Import emails from my other account (POP3)</span> is selected, then hit next</li>
|
||||||
|
<li>For the username, put in your full ieeeucsd.org email</li>
|
||||||
|
<li>For the password, put in your ieeeucsd.org password</li>
|
||||||
|
<li>For the POP Server, put in <span className="text-blue-600">mail.ieeeucsd.org</span></li>
|
||||||
|
<li>For the Port, put in <span className="text-blue-600">995</span></li>
|
||||||
|
<li>Select <span className="text-blue-600">Leave a copy of retrieved message on the server</span></li>
|
||||||
|
<li>Select <span className="text-blue-600">Always use a secure connection (SSL) when retrieving mail</span></li>
|
||||||
|
<li>Select <span className="text-blue-600">Label incoming messages</span></li>
|
||||||
|
<li>Then hit <span className="text-blue-600">Add Account</span></li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 bg-base-200 rounded-lg">
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
If you have any questions or need help with your IEEE email, please contact <a href="mailto:webmaster@ieeeucsd.org" className="underline">webmaster@ieeeucsd.org</a>
|
If you have any questions or need help with your IEEE email, please contact <a href="mailto:webmaster@ieeeucsd.org" className="underline">webmaster@ieeeucsd.org</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -138,6 +373,8 @@ export default function EmailRequestSettings() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{!showEmailForm ? (
|
||||||
|
<>
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
As an IEEE officer, you're eligible for an official IEEE UCSD email address. This email can be used for all IEEE-related communications and provides a professional identity when representing the organization.
|
As an IEEE officer, you're eligible for an official IEEE UCSD email address. This email can be used for all IEEE-related communications and provides a professional identity when representing the organization.
|
||||||
</p>
|
</p>
|
||||||
|
@ -152,8 +389,73 @@ export default function EmailRequestSettings() {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 bg-base-200 rounded-lg">
|
||||||
|
<h3 className="font-bold text-lg mb-2">Your IEEE Email Address</h3>
|
||||||
|
<p className="text-sm mb-2">When you request an email, you'll receive:</p>
|
||||||
|
<p className="text-xl font-mono bg-base-100 p-2 rounded">
|
||||||
|
{user?.email ? `${user.email.split('@')[0].toLowerCase().replace(/[^a-z0-9]/g, "")}@ieeeucsd.org` : 'Loading...'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary w-full"
|
className="btn btn-primary w-full"
|
||||||
|
onClick={toggleEmailForm}
|
||||||
|
>
|
||||||
|
Request IEEE Email Address
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="text-xs opacity-70">
|
||||||
|
<p>By requesting an email, you agree to use it responsibly and in accordance with IEEE UCSD policies.</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="p-4 bg-base-200 rounded-lg space-y-4">
|
||||||
|
<h3 className="font-bold text-lg">Create Your IEEE Email</h3>
|
||||||
|
|
||||||
|
<div className="p-4 bg-base-100 rounded-lg">
|
||||||
|
<p className="font-semibold">Your email address will be:</p>
|
||||||
|
<p className="text-xl font-mono mt-2">
|
||||||
|
{user?.email ? `${user.email.split('@')[0].toLowerCase().replace(/[^a-z0-9]/g, "")}@ieeeucsd.org` : 'Loading...'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Choose a Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="input input-bordered"
|
||||||
|
value={initialPassword}
|
||||||
|
onChange={(e) => setInitialPassword(e.target.value)}
|
||||||
|
placeholder="Enter password (min. 8 characters)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Confirm Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
className="input input-bordered"
|
||||||
|
value={initialConfirmPassword}
|
||||||
|
onChange={(e) => setInitialConfirmPassword(e.target.value)}
|
||||||
|
placeholder="Confirm password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{initialPasswordError && (
|
||||||
|
<div className="text-error text-sm">{initialPasswordError}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<p className="text-sm opacity-70">
|
||||||
|
Leave the password fields empty if you want a secure random password to be generated and sent to your personal email.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
className="btn btn-primary flex-1"
|
||||||
onClick={handleRequestEmail}
|
onClick={handleRequestEmail}
|
||||||
disabled={requesting}
|
disabled={requesting}
|
||||||
>
|
>
|
||||||
|
@ -163,14 +465,19 @@ export default function EmailRequestSettings() {
|
||||||
Creating Email...
|
Creating Email...
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
'Create IEEE Email Address'
|
'Create IEEE Email'
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
<div className="text-xs opacity-70">
|
className="btn btn-outline"
|
||||||
<p>By requesting an email, you agree to use it responsibly and in accordance with IEEE UCSD policies.</p>
|
onClick={toggleEmailForm}
|
||||||
<p>Your email address will be based on your current email username.</p>
|
disabled={requesting}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,149 +0,0 @@
|
||||||
# LogTo Password Change Implementation
|
|
||||||
|
|
||||||
This document explains how the password change functionality works with LogTo authentication.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The password change functionality uses LogTo's Management API to update user passwords. The implementation follows the Machine-to-Machine (M2M) authentication flow as described in the LogTo documentation.
|
|
||||||
|
|
||||||
## Key Files
|
|
||||||
|
|
||||||
1. **`/src/pages/api/change-password.ts`**: The server-side API endpoint that handles password change requests
|
|
||||||
2. **`/src/components/dashboard/SettingsSection/PasswordChangeSettings.tsx`**: The React component that provides the password change UI
|
|
||||||
3. **`/src/components/dashboard/SettingsSection/AccountSecuritySettings.tsx`**: The parent component that includes the password change functionality
|
|
||||||
|
|
||||||
## How It Works
|
|
||||||
|
|
||||||
### Authentication Flow
|
|
||||||
|
|
||||||
1. The client sends a password change request with the user ID and new password
|
|
||||||
2. The server obtains an access token using the client credentials flow
|
|
||||||
3. The server uses the access token to make an authenticated request to the LogTo Management API
|
|
||||||
4. The LogTo API updates the user's password
|
|
||||||
|
|
||||||
### Implementation Details
|
|
||||||
|
|
||||||
#### 1. Client Credentials Flow
|
|
||||||
|
|
||||||
The implementation tries multiple approaches to obtain an access token using the OAuth 2.0 client credentials flow:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Attempt 1: Without resource parameter
|
|
||||||
let tokenResponse = await fetch(logtoTokenEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
grant_type: "client_credentials",
|
|
||||||
client_id: logtoAppId,
|
|
||||||
client_secret: logtoAppSecret,
|
|
||||||
scope: "all",
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Attempt 2: With Basic Auth
|
|
||||||
tokenResponse = await fetch(logtoTokenEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
Authorization: `Basic ${Buffer.from(`${logtoAppId}:${logtoAppSecret}`).toString("base64")}`,
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
grant_type: "client_credentials",
|
|
||||||
scope: "all",
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Attempt 3: With organization_id
|
|
||||||
tokenResponse = await fetch(logtoTokenEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
grant_type: "client_credentials",
|
|
||||||
client_id: logtoAppId,
|
|
||||||
client_secret: logtoAppSecret,
|
|
||||||
organization_id: "default",
|
|
||||||
scope: "all",
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Attempt 4: With audience parameter
|
|
||||||
tokenResponse = await fetch(logtoTokenEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
grant_type: "client_credentials",
|
|
||||||
client_id: logtoAppId,
|
|
||||||
client_secret: logtoAppSecret,
|
|
||||||
audience: "https://auth.ieeeucsd.org/api",
|
|
||||||
scope: "all",
|
|
||||||
}).toString(),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Key points:
|
|
||||||
|
|
||||||
- The `grant_type` must be `client_credentials`
|
|
||||||
- Multiple approaches are tried to handle different LogTo configurations
|
|
||||||
- The `scope` parameter is set to `all` to request all available permissions
|
|
||||||
|
|
||||||
#### 2. Password Update API
|
|
||||||
|
|
||||||
After obtaining an access token, the implementation calls the LogTo Management API to update the password:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const passwordEndpoint = `${logtoApiEndpoint}/api/users/${userId}/password`;
|
|
||||||
|
|
||||||
const changePasswordResponse = await fetch(passwordEndpoint, {
|
|
||||||
method: "PATCH",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${accessToken}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
password: newPassword,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Key points:
|
|
||||||
|
|
||||||
- The endpoint is `/api/users/{userId}/password`
|
|
||||||
- The HTTP method is `PATCH`
|
|
||||||
- The request body contains only the `password` field
|
|
||||||
- The `Authorization` header must include the access token
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
1. **Authentication Errors (401)**
|
|
||||||
|
|
||||||
- Check that the client ID and secret are correct
|
|
||||||
- Verify that the M2M application has the necessary permissions
|
|
||||||
|
|
||||||
2. **Permission Errors (403)**
|
|
||||||
|
|
||||||
- Ensure the M2M application has been assigned the appropriate role
|
|
||||||
- Check that the role has the necessary permissions for user management
|
|
||||||
|
|
||||||
3. **Resource Parameter Issues**
|
|
||||||
|
|
||||||
- The implementation tries multiple approaches to handle resource parameter issues
|
|
||||||
- Different LogTo configurations may require different parameters (resource, audience, etc.)
|
|
||||||
|
|
||||||
4. **User ID Issues**
|
|
||||||
- Ensure the user ID is correctly retrieved from the authentication system
|
|
||||||
- The user ID should match the LogTo user ID, not the local user ID
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [LogTo M2M Quick Start](https://docs.logto.io/quick-starts/m2m)
|
|
||||||
- [LogTo API Reference](https://openapi.logto.io/)
|
|
||||||
- [LogTo Update User Password API](https://openapi.logto.io/operation/operation-updateuserpassword)
|
|
||||||
- [LogTo Authentication](https://openapi.logto.io/authentication)
|
|
|
@ -2,9 +2,23 @@ import type { APIRoute } from "astro";
|
||||||
|
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
try {
|
try {
|
||||||
const { userId, name, email } = await request.json();
|
console.log("Email creation request received");
|
||||||
|
|
||||||
|
const requestBody = await request.json();
|
||||||
|
console.log(
|
||||||
|
"Request body:",
|
||||||
|
JSON.stringify({
|
||||||
|
userId: requestBody.userId,
|
||||||
|
name: requestBody.name,
|
||||||
|
email: requestBody.email,
|
||||||
|
passwordProvided: !!requestBody.password,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { userId, name, email, password } = requestBody;
|
||||||
|
|
||||||
if (!userId || !name || !email) {
|
if (!userId || !name || !email) {
|
||||||
|
console.log("Missing required parameters");
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -21,12 +35,15 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
|
|
||||||
// Extract username from email (everything before the @ symbol)
|
// Extract username from email (everything before the @ symbol)
|
||||||
const emailUsername = email.split("@")[0].toLowerCase();
|
const emailUsername = email.split("@")[0].toLowerCase();
|
||||||
|
console.log(`Email username extracted: ${emailUsername}`);
|
||||||
|
|
||||||
// Remove any special characters that might cause issues
|
// Remove any special characters that might cause issues
|
||||||
const cleanUsername = emailUsername.replace(/[^a-z0-9]/g, "");
|
const cleanUsername = emailUsername.replace(/[^a-z0-9]/g, "");
|
||||||
|
console.log(`Cleaned username: ${cleanUsername}`);
|
||||||
|
|
||||||
// Generate a secure random password
|
// Use provided password or generate a secure random one if not provided
|
||||||
const password = generateSecurePassword();
|
const newPassword = password || generateSecurePassword();
|
||||||
|
console.log(`Using ${password ? "user-provided" : "generated"} password`);
|
||||||
|
|
||||||
// MXRoute DirectAdmin API credentials from environment variables
|
// MXRoute DirectAdmin API credentials from environment variables
|
||||||
const loginKey = import.meta.env.MXROUTE_LOGIN_KEY;
|
const loginKey = import.meta.env.MXROUTE_LOGIN_KEY;
|
||||||
|
@ -36,12 +53,20 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
const emailOutboundLimit = import.meta.env.MXROUTE_EMAIL_OUTBOUND_LIMIT;
|
const emailOutboundLimit = import.meta.env.MXROUTE_EMAIL_OUTBOUND_LIMIT;
|
||||||
const emailDomain = import.meta.env.MXROUTE_EMAIL_DOMAIN;
|
const emailDomain = import.meta.env.MXROUTE_EMAIL_DOMAIN;
|
||||||
|
|
||||||
|
console.log(`Environment variables:
|
||||||
|
loginKey: ${loginKey ? "Set" : "Not set"}
|
||||||
|
serverLogin: ${serverLogin ? "Set" : "Not set"}
|
||||||
|
serverUrl: ${serverUrl ? "Set" : "Not set"}
|
||||||
|
emailQuota: ${emailQuota || "Not set"}
|
||||||
|
emailOutboundLimit: ${emailOutboundLimit || "Not set"}
|
||||||
|
emailDomain: ${emailDomain || "Not set"}
|
||||||
|
`);
|
||||||
|
|
||||||
if (!loginKey || !serverLogin || !serverUrl || !emailDomain) {
|
if (!loginKey || !serverLogin || !serverUrl || !emailDomain) {
|
||||||
throw new Error("Missing MXRoute configuration");
|
throw new Error("Missing MXRoute configuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
// DirectAdmin API endpoint for creating email accounts
|
// DirectAdmin API endpoint for creating email accounts
|
||||||
// According to the documentation: https://docs.directadmin.com/developer/api/legacy-api.html
|
|
||||||
let baseUrl = serverUrl;
|
let baseUrl = serverUrl;
|
||||||
|
|
||||||
// If the URL contains a specific command, extract just the base URL
|
// If the URL contains a specific command, extract just the base URL
|
||||||
|
@ -65,17 +90,22 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
formData.append("action", "create");
|
formData.append("action", "create");
|
||||||
formData.append("domain", emailDomain);
|
formData.append("domain", emailDomain);
|
||||||
formData.append("user", cleanUsername); // DirectAdmin uses 'user' for POP accounts
|
formData.append("user", cleanUsername); // DirectAdmin uses 'user' for POP accounts
|
||||||
formData.append("passwd", password);
|
formData.append("passwd", newPassword);
|
||||||
formData.append("passwd2", password);
|
formData.append("passwd2", newPassword);
|
||||||
formData.append("quota", emailQuota || "200");
|
formData.append("quota", emailQuota || "200");
|
||||||
formData.append("limit", emailOutboundLimit || "9600");
|
formData.append("limit", emailOutboundLimit || "9600");
|
||||||
|
|
||||||
// Log the form data being sent
|
// Log the form data being sent (without showing the actual password)
|
||||||
console.log("Form data:");
|
console.log("Form data:");
|
||||||
formData.forEach((value, key) => {
|
console.log(` action: create`);
|
||||||
console.log(` ${key}: ${value}`);
|
console.log(` domain: ${emailDomain}`);
|
||||||
});
|
console.log(` user: ${cleanUsername}`);
|
||||||
|
console.log(` passwd: ********`);
|
||||||
|
console.log(` passwd2: ********`);
|
||||||
|
console.log(` quota: ${emailQuota || "200"}`);
|
||||||
|
console.log(` limit: ${emailOutboundLimit || "9600"}`);
|
||||||
|
|
||||||
|
console.log("Sending request to DirectAdmin API...");
|
||||||
const response = await fetch(emailApiUrl, {
|
const response = await fetch(emailApiUrl, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -86,6 +116,7 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const responseText = await response.text();
|
const responseText = await response.text();
|
||||||
|
console.log(`DirectAdmin response status: ${response.status}`);
|
||||||
console.log(`DirectAdmin response: ${responseText}`);
|
console.log(`DirectAdmin response: ${responseText}`);
|
||||||
|
|
||||||
// DirectAdmin API returns "error=1" in the response text for errors
|
// DirectAdmin API returns "error=1" in the response text for errors
|
||||||
|
@ -126,14 +157,19 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send notification email to the user with their new email credentials
|
console.log("Email account created successfully");
|
||||||
|
|
||||||
|
// Only send notification email if we generated a random password
|
||||||
|
if (!password) {
|
||||||
|
console.log("Sending credentials email to user");
|
||||||
await sendCredentialsEmail(
|
await sendCredentialsEmail(
|
||||||
email,
|
email,
|
||||||
`${cleanUsername}@${emailDomain}`,
|
`${cleanUsername}@${emailDomain}`,
|
||||||
password,
|
newPassword,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Send notification to webmaster
|
console.log("Sending notification to webmaster");
|
||||||
await sendWebmasterNotification(
|
await sendWebmasterNotification(
|
||||||
userId,
|
userId,
|
||||||
name,
|
name,
|
||||||
|
@ -146,8 +182,9 @@ export const POST: APIRoute = async ({ request }) => {
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: {
|
||||||
ieeeEmail: `${cleanUsername}@${emailDomain}`,
|
ieeeEmail: `${cleanUsername}@${emailDomain}`,
|
||||||
message:
|
message: password
|
||||||
"Email account created successfully. Check your email for login details.",
|
? "Email account created successfully with your chosen password."
|
||||||
|
: "Email account created successfully. Check your email for login details.",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
@ -223,6 +260,36 @@ async function sendCredentialsEmail(
|
||||||
- Webmail: https://heracles.mxrouting.net:2096/
|
- Webmail: https://heracles.mxrouting.net:2096/
|
||||||
- IMAP/SMTP settings can be found at: https://mxroute.com/setup/
|
- IMAP/SMTP settings can be found at: https://mxroute.com/setup/
|
||||||
|
|
||||||
|
===== Setting Up Your IEEE Email in Gmail =====
|
||||||
|
|
||||||
|
--- First Step: Set Up Sending From Your IEEE Email ---
|
||||||
|
1. Go to settings (gear icon) → Accounts and Import
|
||||||
|
2. In the section that says "Send mail as:", select "Reply from the same address the message was sent to"
|
||||||
|
3. In that same section, select "Add another email address"
|
||||||
|
4. For the Name, put your actual name or department name (e.g. IEEEUCSD Webmaster)
|
||||||
|
5. For the Email address, put ${ieeeEmail}
|
||||||
|
6. Make sure the "Treat as an alias" button is selected. Go to the next step
|
||||||
|
7. For the SMTP Server, put mail.ieeeucsd.org
|
||||||
|
8. For the username, put in your FULL ieeeucsd email address (${ieeeEmail})
|
||||||
|
9. For the password, put in the email's password (provided above)
|
||||||
|
10. For the port, put in 587
|
||||||
|
11. Make sure you select "Secured connection with TLS"
|
||||||
|
12. Go back to mail.ieeeucsd.org and verify the email that Google has sent you
|
||||||
|
|
||||||
|
--- Second Step: Set Up Receiving Your IEEE Email ---
|
||||||
|
1. Go to settings (gear icon) → Accounts and Import
|
||||||
|
2. In the section that says "Check mail from other accounts:", select "Add a mail account"
|
||||||
|
3. Put in ${ieeeEmail} and hit next
|
||||||
|
4. Make sure "Import emails from my other account (POP3)" is selected, then hit next
|
||||||
|
5. For the username, put in ${ieeeEmail}
|
||||||
|
6. For the password, put in your password (provided above)
|
||||||
|
7. For the POP Server, put in mail.ieeeucsd.org
|
||||||
|
8. For the Port, put in 995
|
||||||
|
9. Select "Leave a copy of retrieved message on the server"
|
||||||
|
10. Select "Always use a secure connection (SSL) when retrieving mail"
|
||||||
|
11. Select "Label incoming messages"
|
||||||
|
12. Then hit "Add Account"
|
||||||
|
|
||||||
Please change your password after your first login.
|
Please change your password after your first login.
|
||||||
|
|
||||||
If you have any questions, please contact webmaster@ieeeucsd.org.
|
If you have any questions, please contact webmaster@ieeeucsd.org.
|
||||||
|
|
262
src/pages/api/reset-email-password.ts
Normal file
262
src/pages/api/reset-email-password.ts
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
import type { APIRoute } from "astro";
|
||||||
|
|
||||||
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
|
try {
|
||||||
|
console.log("Password reset request received");
|
||||||
|
|
||||||
|
const requestBody = await request.json();
|
||||||
|
console.log(
|
||||||
|
"Request body:",
|
||||||
|
JSON.stringify({
|
||||||
|
email: requestBody.email,
|
||||||
|
passwordProvided: !!requestBody.password,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { email, password } = requestBody;
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
console.log("Missing email address");
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
message: "Missing email address",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract username and domain from email
|
||||||
|
const [username, domain] = email.split("@");
|
||||||
|
console.log(`Email parsed: username=${username}, domain=${domain}`);
|
||||||
|
|
||||||
|
if (!username || !domain) {
|
||||||
|
console.log("Invalid email format");
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
message: "Invalid email format",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use provided password or generate a secure random one if not provided
|
||||||
|
const newPassword = password || generateSecurePassword();
|
||||||
|
console.log(`Using ${password ? "user-provided" : "generated"} password`);
|
||||||
|
|
||||||
|
// MXRoute DirectAdmin API credentials from environment variables
|
||||||
|
const loginKey = import.meta.env.MXROUTE_LOGIN_KEY;
|
||||||
|
const serverLogin = import.meta.env.MXROUTE_SERVER_LOGIN;
|
||||||
|
const serverUrl = import.meta.env.MXROUTE_SERVER_URL;
|
||||||
|
|
||||||
|
console.log(`Environment variables:
|
||||||
|
loginKey: ${loginKey ? "Set" : "Not set"}
|
||||||
|
serverLogin: ${serverLogin ? "Set" : "Not set"}
|
||||||
|
serverUrl: ${serverUrl ? "Set" : "Not set"}
|
||||||
|
`);
|
||||||
|
|
||||||
|
if (!loginKey || !serverLogin || !serverUrl) {
|
||||||
|
throw new Error("Missing MXRoute configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
// DirectAdmin API endpoint for changing email password
|
||||||
|
let baseUrl = serverUrl;
|
||||||
|
|
||||||
|
// If the URL contains a specific command, extract just the base URL
|
||||||
|
if (baseUrl.includes("/CMD_")) {
|
||||||
|
baseUrl = baseUrl.split("/CMD_")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there's no trailing slash
|
||||||
|
baseUrl = baseUrl.replace(/\/$/, "");
|
||||||
|
|
||||||
|
// Construct the email POP API URL
|
||||||
|
const emailApiUrl = `${baseUrl}/CMD_API_EMAIL_POP`;
|
||||||
|
|
||||||
|
console.log(`Resetting password for email: ${email}`);
|
||||||
|
console.log(`DirectAdmin API URL: ${emailApiUrl}`);
|
||||||
|
|
||||||
|
// Create the form data for password reset
|
||||||
|
const formData = new URLSearchParams();
|
||||||
|
formData.append("action", "modify");
|
||||||
|
formData.append("domain", domain);
|
||||||
|
formData.append("user", username);
|
||||||
|
formData.append("passwd", newPassword);
|
||||||
|
formData.append("passwd2", newPassword);
|
||||||
|
|
||||||
|
// Log the form data being sent (without showing the actual password)
|
||||||
|
console.log("Form data:");
|
||||||
|
console.log(` action: modify`);
|
||||||
|
console.log(` domain: ${domain}`);
|
||||||
|
console.log(` user: ${username}`);
|
||||||
|
console.log(` passwd: ********`);
|
||||||
|
console.log(` passwd2: ********`);
|
||||||
|
|
||||||
|
console.log("Sending request to DirectAdmin API...");
|
||||||
|
const response = await fetch(emailApiUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
Authorization: `Basic ${Buffer.from(`${serverLogin}:${loginKey}`).toString("base64")}`,
|
||||||
|
},
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseText = await response.text();
|
||||||
|
console.log(`DirectAdmin response status: ${response.status}`);
|
||||||
|
console.log(`DirectAdmin response: ${responseText}`);
|
||||||
|
|
||||||
|
// DirectAdmin API returns "error=1" in the response text for errors
|
||||||
|
if (responseText.includes("error=1") || !response.ok) {
|
||||||
|
console.error("Error resetting email password:", responseText);
|
||||||
|
|
||||||
|
// Parse the error details if possible
|
||||||
|
let errorMessage = "Failed to reset email password";
|
||||||
|
try {
|
||||||
|
const errorParams = new URLSearchParams(responseText);
|
||||||
|
if (errorParams.has("text")) {
|
||||||
|
errorMessage = decodeURIComponent(errorParams.get("text") || "");
|
||||||
|
}
|
||||||
|
if (errorParams.has("details")) {
|
||||||
|
const details = decodeURIComponent(errorParams.get("details") || "");
|
||||||
|
errorMessage += `: ${details.replace(/<br>/g, " ")}`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error parsing DirectAdmin error response:", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Password reset successful");
|
||||||
|
|
||||||
|
// Only send notification email if we generated a random password
|
||||||
|
if (!password) {
|
||||||
|
await sendPasswordResetEmail(email, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: password
|
||||||
|
? "Password reset successfully. Remember to update your password in any email clients or integrations."
|
||||||
|
: "Password reset successfully. Check your personal email for the new password.",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in reset-email-password:", error);
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : "An error occurred",
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate a secure random password
|
||||||
|
function generateSecurePassword(length = 16) {
|
||||||
|
const charset =
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+";
|
||||||
|
let password = "";
|
||||||
|
|
||||||
|
// Ensure at least one character from each category
|
||||||
|
password += charset.substring(0, 26).charAt(Math.floor(Math.random() * 26)); // lowercase
|
||||||
|
password += charset.substring(26, 52).charAt(Math.floor(Math.random() * 26)); // uppercase
|
||||||
|
password += charset.substring(52, 62).charAt(Math.floor(Math.random() * 10)); // number
|
||||||
|
password += charset
|
||||||
|
.substring(62)
|
||||||
|
.charAt(Math.floor(Math.random() * (charset.length - 62))); // special
|
||||||
|
|
||||||
|
// Fill the rest randomly
|
||||||
|
for (let i = 4; i < length; i++) {
|
||||||
|
password += charset.charAt(Math.floor(Math.random() * charset.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shuffle the password
|
||||||
|
return password
|
||||||
|
.split("")
|
||||||
|
.sort(() => 0.5 - Math.random())
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send email with new password
|
||||||
|
async function sendPasswordResetEmail(ieeeEmail: string, newPassword: string) {
|
||||||
|
try {
|
||||||
|
// Extract username from IEEE email
|
||||||
|
const username = ieeeEmail.split("@")[0];
|
||||||
|
|
||||||
|
// Get the user from PocketBase to find their personal email
|
||||||
|
const PocketBase = await import("pocketbase").then(
|
||||||
|
(module) => module.default,
|
||||||
|
);
|
||||||
|
const pb = new PocketBase(
|
||||||
|
import.meta.env.POCKETBASE_URL || "http://127.0.0.1:8090",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to find the user with this username
|
||||||
|
const userRecord = await pb.collection("users").getList(1, 1, {
|
||||||
|
filter: `email~"${username}@"`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine which email to send to
|
||||||
|
let recipientEmail = ieeeEmail;
|
||||||
|
if (userRecord && userRecord.items.length > 0) {
|
||||||
|
recipientEmail = userRecord.items[0].email;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In a real implementation, you would use an email service like SendGrid, Mailgun, etc.
|
||||||
|
// For now, we'll just log the email that would be sent
|
||||||
|
console.log(`
|
||||||
|
To: ${recipientEmail}
|
||||||
|
Subject: Your IEEE UCSD Email Password Has Been Reset
|
||||||
|
|
||||||
|
Hello,
|
||||||
|
|
||||||
|
Your IEEE UCSD email password has been reset:
|
||||||
|
|
||||||
|
IEEE Email address: ${ieeeEmail}
|
||||||
|
New Password: ${newPassword}
|
||||||
|
|
||||||
|
You can access your email through:
|
||||||
|
- Webmail: https://mail.ieeeucsd.org
|
||||||
|
|
||||||
|
Please consider changing this password to something you can remember after logging in.
|
||||||
|
|
||||||
|
If you did not request this password reset, please contact webmaster@ieeeucsd.org immediately.
|
||||||
|
|
||||||
|
Best regards,
|
||||||
|
IEEE UCSD Web Team
|
||||||
|
`);
|
||||||
|
|
||||||
|
// In a production environment, replace with actual email sending code
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error sending password reset email:", error);
|
||||||
|
// Still return true to not block the password reset process
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue