Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
provider = "postgresql"
232 changes: 164 additions & 68 deletions backend/src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,190 @@
import { Request, Response } from "express"
import asyncHandler from "../middlewares/async"
import { SuccessResponse, BadRequestResponse } from "../core/api/ApiResponse"
import { BadRequestError } from "../core/api/ApiError"
import UserService from "../services/user.service"
import Jwt from "../utils/security/jwt"
import WalletService from "../services/wallet.service"
import serverSettings from "../core/config/settings"
import EmailNotifier from "../utils/service/emailNotifier"
import Bcrypt from "../utils/security/bcrypt"
import { StrKey } from "@stellar/stellar-sdk"
import { Request, Response } from "express";
import asyncHandler from "../middlewares/async";
import { SuccessResponse, BadRequestResponse } from "../core/api/ApiResponse";
import { BadRequestError, InternalError } from "../core/api/ApiError";
import UserService from "../services/user.service";
import Jwt from "../utils/security/jwt";
import WalletService from "../services/wallet.service";
import serverSettings from "../core/config/settings";
import EmailNotifier from "../utils/service/emailNotifier";
import Bcrypt from "../utils/security/bcrypt";
import { StrKey } from "@stellar/stellar-sdk";
import logger from "../core/config/logger";

export const register = asyncHandler(async (req: Request, res: Response) => {
const { email, password, firstName, lastName, walletAddress } = req.body
const { email, password, firstName, lastName, walletAddress } = req.body;

const existingUser = await UserService.readUserByEmail(email)
if (existingUser) throw new BadRequestError("Email already registered")
logger.info(`Registration attempt for email: ${email}`);

if (!StrKey.isValidEd25519PublicKey(walletAddress)) {
throw new BadRequestError("Invalid Stellar wallet address")
const existingUser = await UserService.readUserByEmail(email);
if (existingUser) {
logger.warn(`Registration attempt with existing email: ${email}`);
throw new BadRequestError("Email already registered");
}

const existingWallet = await WalletService.readWalletByWalletAddress(walletAddress)
if (existingWallet) throw new BadRequestError("Wallet address already registered")

const hashedPassword = await Bcrypt.hashPassword(password)

const result = await UserService.registerUser({
email,
hashedPassword,
firstName,
lastName,
walletAddress,
})

await WalletService.createWallet(result.id, walletAddress)
if (!StrKey.isValidEd25519PublicKey(walletAddress)) {
logger.warn(
`Registration attempt with invalid wallet address: ${walletAddress} for email: ${email}`
);
throw new BadRequestError("Invalid Stellar wallet address");
}

const verificationToken = Jwt.issue({ userId: result.id }, "1d")
const existingWallet =
await WalletService.readWalletByWalletAddress(walletAddress);
if (existingWallet) {
logger.warn(
`Registration attempt with existing wallet address: ${walletAddress} for email: ${email}`
);
throw new BadRequestError("Wallet address already registered");
}

const verificationLink = `${serverSettings.auroraWebApp.baseUrl}/verify-email?token=${verificationToken}`
const hashedPassword = await Bcrypt.hashPassword(password);

console.log('verificationLink:', verificationLink);
EmailNotifier.sendAccountActivationEmail(email, verificationLink)
try {
logger.info(`Starting atomic registration transaction for email: ${email}`);

// Single atomic transaction that creates both user and wallet
const result = await UserService.registerUser({
email,
hashedPassword,
firstName,
lastName,
walletAddress,
});

logger.info(
`Registration transaction completed successfully for email: ${email}, user ID: ${result.id}`
);

// Email notification only after successful transaction completion
const verificationToken = Jwt.issue({ userId: result.id }, "1d");
const verificationLink = `${serverSettings.auroraWebApp.baseUrl}/verify-email?token=${verificationToken}`;

logger.debug(`Generated verification link for user ID: ${result.id}`);

// Email notification failures are logged but don't affect registration success
try {
logger.info(`Sending activation email to: ${email}`);
await EmailNotifier.sendAccountActivationEmail(email, verificationLink);
logger.info(`Activation email sent successfully to: ${email}`);
} catch (emailError) {
// Log detailed email error but don't fail the registration
logger.error(`Failed to send activation email to: ${email}`, {
error:
emailError instanceof Error ? emailError.message : String(emailError),
stack: emailError instanceof Error ? emailError.stack : undefined,
userId: result.id,
verificationLink,
});

// Continue with successful response even if email fails
logger.warn(
`Registration successful but email notification failed for: ${email}`
);
}

const userResponse = {
id: result.id,
email: result.email,
firstName: result.firstName,
lastName: result.lastName,
isEmailVerified: result.isEmailVerified,
createdAt: result.createdAt,
status: result.status,
const userResponse = {
id: result.id,
email: result.email,
firstName: result.firstName,
lastName: result.lastName,
isEmailVerified: result.isEmailVerified,
createdAt: result.createdAt,
status: result.status,
};

logger.info(`Registration completed successfully for email: ${email}`);

return new SuccessResponse(
"Registration successful. Please verify your email.",
{ user: userResponse }
).send(res);
} catch (error) {
// Handle transaction failures with proper error logging and user-friendly messages
logger.error(`Registration failed for email: ${email}`, {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
requestData: {
email,
firstName,
lastName,
walletAddress,
},
});

// Re-throw the error to maintain existing error handling behavior
// The UserService already provides user-friendly error messages
throw error;
}

return new SuccessResponse(
"Registration successful. Please verify your email.",
{ user: userResponse }
).send(res)
})
});

export const verifyEmail = asyncHandler(async (req: Request, res: Response) => {
try {
const { token } = req.query
const { token } = req.query;

logger.info(
`Email verification attempt with token: ${token ? "provided" : "missing"}`
);

if (!token || typeof token !== "string") {
throw new BadRequestError("Verification token is required")
logger.warn("Email verification attempted without token");
throw new BadRequestError("Verification token is required");
}

const decoded = Jwt.verify(token)
const userId = (decoded as any).payload.userId
const decoded = Jwt.verify(token);
const userId = (decoded as any).payload.userId;

logger.info(`Email verification for user ID: ${userId}`);

const updatedUser = await UserService.activateEmail(userId);
if (!updatedUser) {
logger.warn(
`Email verification failed - user not found for ID: ${userId}`
);
throw new BadRequestError("User not found");
}

const updatedUser = await UserService.activateEmail(userId)
if (!updatedUser) throw new BadRequestError("User not found")
logger.info(
`Email verification completed successfully for user ID: ${userId}`
);

return new SuccessResponse("Email verified successfully", {}).send(res)
return new SuccessResponse("Email verified successfully", {}).send(res);
} catch (err) {
console.log(err)
throw new BadRequestError("Invalid token")
logger.error("Email verification failed", {
error: err instanceof Error ? err.message : String(err),
stack: err instanceof Error ? err.stack : undefined,
token: req.query.token ? "provided" : "missing",
});

throw new BadRequestError("Invalid token");
}
})
});

export const login = asyncHandler(async (req: Request, res: Response) => {
const { email, password } = req.body
const { email, password } = req.body;

logger.info(`Login attempt for email: ${email}`);

const user = await UserService.readUserByEmail(email);
if (!user) {
logger.warn(`Login attempt with non-existent email: ${email}`);
throw new BadRequestError("Invalid credentials");
}

const user = await UserService.readUserByEmail(email)
if (!user) throw new BadRequestError("Invalid credentials")
if (!user.isEmailVerified) {
throw new BadRequestError("Email not verified. Please verify your email first.")
logger.warn(`Login attempt with unverified email: ${email}`);
throw new BadRequestError(
"Email not verified. Please verify your email first."
);
}

const isPasswordValid = await Bcrypt.compare(password, user.password)
if (!isPasswordValid) throw new BadRequestError("Invalid credentials")
const isPasswordValid = await Bcrypt.compare(password, user.password);
if (!isPasswordValid) {
logger.warn(`Login attempt with invalid password for email: ${email}`);
throw new BadRequestError("Invalid credentials");
}

const token = Jwt.issue({ id: user.id }, "1d")
const token = Jwt.issue({ id: user.id }, "1d");

const userResponse = {
id: user.id,
Expand All @@ -100,10 +194,12 @@ export const login = asyncHandler(async (req: Request, res: Response) => {
isEmailVerified: user.isEmailVerified,
createdAt: user.createdAt,
status: user.status,
}
};

logger.info(`Login successful for email: ${email}, user ID: ${user.id}`);

return new SuccessResponse("Login successful", {
user: userResponse,
token,
}).send(res)
})
}).send(res);
});
Loading