diff --git a/functions/src/controllers/auth_controller.ts b/functions/src/controllers/auth_controller.ts
index 8ba6210..dfb8a77 100644
--- a/functions/src/controllers/auth_controller.ts
+++ b/functions/src/controllers/auth_controller.ts
@@ -37,6 +37,152 @@ const validateEmailAndPassword = (
return true;
};
+// Configure Nodemailer to use Mailtrap's SMTP
+const transporter = nodemailer.createTransport({
+ host: "live.smtp.mailtrap.io",
+ port: 587,
+ auth: {
+ user: process.env.MAILTRAP_USER,
+ pass: process.env.MAILTRAP_PASS,
+ },
+});
+
+interface MailOptions {
+ from: string | { name: string; address: string };
+ to: string;
+ subject: string;
+ html: string;
+ text: string;
+}
+
+const createPasswordResetMailOptions = (
+ email: string,
+ link: string
+): MailOptions => ({
+ from: {
+ name: "Garuda Hacks",
+ address: "no-reply@garudahacks.com",
+ },
+ to: email,
+ subject: "Reset your Garuda Hacks password",
+ html: `
+
+
+
+
+
+ Reset Your Password
+
+
+
+
+
+
Reset Your Password
+
You requested a password reset. Click the button below to choose a new password:
+
Reset Password
+
+ If you didn't request this, you can safely ignore this email. Your password will remain unchanged.
+
+
+ This link will expire in 1 hour for security reasons.
+
+
+
+
+
+ `,
+ text: `Reset Your Password
+
+You requested a password reset. Click the link below to choose a new password:
+
+${link}
+
+If you didn't request this, you can safely ignore this email. Your password will remain unchanged.
+
+This link will expire in 1 hour for security reasons.
+
+© ${new Date().getFullYear()} Garuda Hacks. All rights reserved.`,
+});
+
+const sendPasswordResetEmail = async (
+ email: string,
+ link: string
+): Promise => {
+ const mailOptions = createPasswordResetMailOptions(email, link);
+ await transporter.sendMail(mailOptions);
+ functions.logger.info("Password reset email sent successfully to:", email);
+};
+
+const createVerificationMailOptions = (
+ email: string,
+ link: string
+): MailOptions => ({
+ from: {
+ name: "Garuda Hacks",
+ address: "no-reply@garudahacks.com",
+ },
+ to: email,
+ subject: "Verify your Garuda Hacks account",
+ html: `
+
+
+
+
+
+ Verify Your Account
+
+
+
+
+
+
Welcome to Garuda Hacks!
+
Thank you for registering. Please verify your email address by clicking the button below:
+
Verify Email
+
+ If you didn't create an account with us, you can safely ignore this email.
+
+
+ This verification link will expire in 24 hours.
+
+
+
+
+
+ `,
+ text: `Welcome to Garuda Hacks!
+
+Thank you for registering. Please verify your email address by clicking the link below:
+
+${link}
+
+If you didn't create an account with us, you can safely ignore this email.
+
+This verification link will expire in 24 hours.
+
+© ${new Date().getFullYear()} Garuda Hacks. All rights reserved.`,
+});
+
+const sendVerificationEmail = async (
+ email: string,
+ link: string
+): Promise => {
+ const mailOptions = createVerificationMailOptions(email, link);
+ await transporter.sendMail(mailOptions);
+ functions.logger.info("Verification email sent successfully to:", email);
+};
+
/**
* Logs in user
*/
@@ -143,6 +289,12 @@ export const register = async (req: Request, res: Response): Promise => {
role: "User",
});
+ // Generate email verification link
+ const verificationLink = await auth.generateEmailVerificationLink(email);
+
+ // Send verification email
+ await sendVerificationEmail(email, verificationLink);
+
const customToken = await auth.createCustomToken(user.uid);
const url = isEmulator
@@ -204,7 +356,8 @@ export const register = async (req: Request, res: Response): Promise => {
res.status(201).json(
convertResponseToSnakeCase({
status: 201,
- message: "Registration successful",
+ message:
+ "Registration successful. Please check your email for verification link.",
user: {
email: user.email,
displayName: user.displayName,
@@ -377,8 +530,12 @@ export const sessionCheck = async (
res
.status(400)
.json({ status: 400, error: "Could not find session cookie" });
+ return;
}
+ // Get user data to check email verification status
+ const user = await auth.getUser(decodedSessionCookie.sub);
+
res.status(200).json({
status: 200,
message: "Session is valid",
@@ -386,6 +543,7 @@ export const sessionCheck = async (
user: {
email: decodedSessionCookie.email,
displayName: decodedSessionCookie.name,
+ emailVerified: user.emailVerified,
},
},
});
@@ -396,97 +554,6 @@ export const sessionCheck = async (
}
};
-// Configure Nodemailer to use Mailtrap's SMTP
-const transporter = nodemailer.createTransport({
- host: "live.smtp.mailtrap.io",
- port: 587,
- auth: {
- user: process.env.MAILTRAP_USER,
- pass: process.env.MAILTRAP_PASS,
- },
-});
-
-interface ActionCodeSettings {
- url: string;
- handleCodeInApp: boolean;
-}
-
-interface MailOptions {
- from: string | { name: string; address: string };
- to: string;
- subject: string;
- html: string;
- text: string;
-}
-
-const getActionCodeSettings = (): ActionCodeSettings => ({
- url: process.env.FRONTEND_URL
- ? `${process.env.FRONTEND_URL}/reset-password`
- : "https://portal.garudahacks.com/reset-password",
- handleCodeInApp: true,
-});
-
-const createMailOptions = (email: string, link: string): MailOptions => ({
- from: {
- name: "Garuda Hacks",
- address: "no-reply@garudahacks.com",
- },
- to: email,
- subject: "Reset your Garuda Hacks password",
- html: `
-
-
-
-
-
- Reset Your Password
-
-
-
-
-
-
Reset Your Password
-
You requested a password reset. Click the button below to choose a new password:
-
Reset Password
-
- If you didn't request this, you can safely ignore this email. Your password will remain unchanged.
-
-
- This link will expire in 1 hour for security reasons.
-
-
-
-
-
- `,
- text: `Reset Your Password
-
-You requested a password reset. Click the link below to choose a new password:
-
-${link}
-
-If you didn't request this, you can safely ignore this email. Your password will remain unchanged.
-
-This link will expire in 1 hour for security reasons.
-
-© ${new Date().getFullYear()} Garuda Hacks. All rights reserved.`,
-});
-
-const sendPasswordResetEmail = async (
- email: string,
- link: string
-): Promise => {
- const mailOptions = createMailOptions(email, link);
- await transporter.sendMail(mailOptions);
- functions.logger.info("Password reset email sent successfully to:", email);
-};
-
/**
* Request password reset by sending email
*/
@@ -509,13 +576,9 @@ export const requestPasswordReset = async (
await auth.getUserByEmail(email);
// Generate password reset link
- const actionCodeSettings = getActionCodeSettings();
functions.logger.info("Generating password reset link for:", email);
- const link = await auth.generatePasswordResetLink(
- email,
- actionCodeSettings
- );
+ const link = await auth.generatePasswordResetLink(email);
functions.logger.info("Password reset link generated successfully");
// Send password reset email
@@ -541,61 +604,49 @@ export const requestPasswordReset = async (
};
/**
- * Reset password using verification code
+ * Send verification email to user
*/
-// export const resetPassword = async (
-// req: Request,
-// res: Response
-// ): Promise => {
-// const { oobCode, newPassword } = req.body;
-
-// if (!oobCode || !newPassword) {
-// res.status(400).json({
-// status: 400,
-// error: "Reset code and new password are required",
-// });
-// return;
-// }
-
-// if (!validator.isLength(newPassword, { min: 6 })) {
-// res.status(400).json({
-// status: 400,
-// error: "Password must be at least 6 characters long",
-// });
-// return;
-// }
-
-// try {
-// // Get the user from the reset code
-// const user = await auth.getUserByEmail(oobCode);
-
-// // Update the user's password
-// await auth.updateUser(user.uid, {
-// password: newPassword,
-// });
-
-// // Revoke all refresh tokens
-// await auth.revokeRefreshTokens(user.uid);
-
-// res.status(200).json({
-// status: 200,
-// message: "Password has been reset successfully",
-// });
-// } catch (error) {
-// const err = error as FirebaseError;
-// functions.logger.error("Error resetting password:", err);
-
-// if (err.code === "auth/user-not-found") {
-// res.status(400).json({
-// status: 400,
-// error: "Invalid or expired reset code",
-// });
-// return;
-// }
-
-// res.status(500).json({
-// status: 500,
-// error: "Failed to reset password",
-// });
-// }
-// };
+export const verifyAccount = async (
+ req: Request,
+ res: Response
+): Promise => {
+ try {
+ const decodedSessionCookie = await auth.verifySessionCookie(
+ req.cookies.__session
+ );
+
+ if (!decodedSessionCookie) {
+ functions.logger.error("Could not find session cookie");
+ res
+ .status(400)
+ .json({ status: 400, error: "Could not find session cookie" });
+ return;
+ }
+
+ const email = decodedSessionCookie.email;
+ if (!email) {
+ res.status(400).json({
+ status: 400,
+ error: "Email not found in session",
+ });
+ return;
+ }
+
+ const link = await auth.generateEmailVerificationLink(email);
+
+ await sendVerificationEmail(email, link);
+
+ res.status(200).json({
+ status: 200,
+ message: "Email verification link sent",
+ });
+ } catch (error) {
+ const err = error as FirebaseError;
+ functions.logger.error("Error in account verification:", err);
+
+ res.status(400).json({
+ status: 400,
+ error: "Something went wrong",
+ });
+ }
+};
diff --git a/functions/src/middlewares/auth_middleware.ts b/functions/src/middlewares/auth_middleware.ts
index e3e309f..8202f9b 100644
--- a/functions/src/middlewares/auth_middleware.ts
+++ b/functions/src/middlewares/auth_middleware.ts
@@ -17,7 +17,6 @@ const authExemptRoutes = [
"/auth/register",
"/auth/login",
"/auth/session-login",
- "/auth/request-reset",
"/auth/reset-password",
];
diff --git a/functions/src/middlewares/csrf_middleware.ts b/functions/src/middlewares/csrf_middleware.ts
index 063d842..7056189 100644
--- a/functions/src/middlewares/csrf_middleware.ts
+++ b/functions/src/middlewares/csrf_middleware.ts
@@ -6,9 +6,9 @@ const csrfExemptRoutes = [
"/auth/login",
"/auth/register",
"/auth/session-login",
- "/auth/request-reset",
"/auth/reset-password",
"/auth/logout",
+ "/auth/verify-account",
];
export const csrfProtection: RequestHandler = (
diff --git a/functions/src/routes/auth.ts b/functions/src/routes/auth.ts
index ebab38e..c9cbc75 100644
--- a/functions/src/routes/auth.ts
+++ b/functions/src/routes/auth.ts
@@ -6,6 +6,7 @@ import {
requestPasswordReset,
sessionCheck,
sessionLogin,
+ verifyAccount,
} from "../controllers/auth_controller";
const router = express.Router();
@@ -13,6 +14,9 @@ const router = express.Router();
router.post("/login", (req: Request, res: Response) => login(req, res));
router.post("/register", (req: Request, res: Response) => register(req, res));
router.post("/reset-password", requestPasswordReset);
+router.post("/verify-account", (req: Request, res: Response) =>
+ verifyAccount(req, res)
+);
router.post("/session-login", (req: Request, res: Response) =>
sessionLogin(req, res)
);