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
64 changes: 63 additions & 1 deletion backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import { ApiBearerAuth, ApiResponse } from '@nestjs/swagger';
export class AuthController {
constructor(private readonly authService: AuthService) {}

/**
* Checks if the user has a valid session
*/
@Get('session')
async getSession(@Req() req: any) {
try {
Expand Down Expand Up @@ -61,8 +64,27 @@ export class AuthController {
await this.authService.register(body.username, body.password, body.email);
return { message: 'User registered successfully' };
}


/**
* Logs in a user
*/
@Post('login')
@ApiResponse({
status: 200,
description: "User logged in successfully"
})
@ApiResponse({
status: 400,
description: "{Error encountered}"
})
@ApiResponse({
status: 401,
description: "Invalid credentials"
})
@ApiResponse({
status: 500,
description: "Internal server error"
})
async login(
@Res({ passthrough: true }) response: Response,
@Body('username') username: string,
Expand Down Expand Up @@ -91,9 +113,29 @@ export class AuthController {
return result
}

/**
*
* Set new password
*/
@Post('set-password')
@UseGuards(VerifyUserGuard)
@ApiBearerAuth()
@ApiResponse({
status: 200,
description: "Password set successfully"
})
@ApiResponse({
status: 400,
description: "{Error encountered}"
})
@ApiResponse({
status: 401,
description: "Invalid credentials"
})
@ApiResponse({
status: 500,
description: "Internal server error"
})
async setNewPassword(
@Body('newPassword') newPassword: string,
@Body('session') session: string,
Expand All @@ -103,9 +145,29 @@ export class AuthController {
return await this.authService.setNewPassword(newPassword, session, username, email);
}

/**
*
* Update user profile for username, email, and position_or_role
*/
@Post('update-profile')
@UseGuards(VerifyUserGuard)
@ApiBearerAuth()
@ApiResponse({
status: 200,
description: "Profile updated successfully"
})
@ApiResponse({
status: 400,
description: "{Error encountered}"
})
@ApiResponse({
status: 401,
description: "Invalid credentials"
})
@ApiResponse({
status: 500,
description: "Internal server error"
})
async updateProfile(
@Body('username') username: string,
@Body('email') email: string,
Expand Down
63 changes: 61 additions & 2 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ constructor() {
}


// purpose statement: registers an user into cognito and dynamodb
// use case: new employee is joining
async register(
username: string,
password: string,
Expand All @@ -72,21 +74,25 @@ constructor() {
throw new InternalServerErrorException("Server configuration error");
}

// Validate environment variables
if (!tableName) {
this.logger.error("DynamoDB User Table Name is not defined in environment variables.");
throw new InternalServerErrorException("Server configuration error");
}

// Validate input parameters
// Validate input parameters for username, password, and email
if (!username || username.trim().length === 0) {
this.logger.warn("Registration failed: Username is required");
throw new BadRequestException("Username is required");
}

if (!password || password.length < 8) {
this.logger.warn("Registration failed: Password must be at least 8 characters long");
throw new BadRequestException("Password must be at least 8 characters long");
}

if (!email || !this.isValidEmail(email)) {
this.logger.warn("Registration failed: Valid email address is required");
throw new BadRequestException("Valid email address is required");
}

Expand Down Expand Up @@ -298,7 +304,8 @@ private isValidEmail(email: string): boolean {



// Overall, needs better undefined handling and optional adding
// purpose statement: logs in an user via cognito and retrieves user data from dynamodb
// use case: employee is trying to access the app, needs to have an account already
async login(
username: string,
password: string
Expand All @@ -314,11 +321,21 @@ private isValidEmail(email: string): boolean {
const clientId = process.env.COGNITO_CLIENT_ID;
const clientSecret = process.env.COGNITO_CLIENT_SECRET;

// Validate environment variables
if (!clientId || !clientSecret) {
this.logger.error("Cognito Client ID or Secret is not defined.");
throw new Error("Cognito Client ID or Secret is not defined.");
}

// Validate input parameters for username and password
if (!username || username.trim().length === 0) {
throw new BadRequestException("Username is required");
}

if (!password || password.length === 0) {
throw new BadRequestException("Password is required");
}

const hatch = this.computeHatch(username, clientId, clientSecret);

// Todo, change constants of AUTH_FLOW types & other constants in repo
Expand Down Expand Up @@ -460,6 +477,8 @@ private isValidEmail(email: string): boolean {
}
}

// purpose statement: sets a new password for an user in cognito
// use case: employee changing password after forgetting password
async setNewPassword(
newPassword: string,
session: string,
Expand All @@ -474,6 +493,22 @@ private isValidEmail(email: string): boolean {
throw new Error("Cognito Client ID or Secret is not defined.");
}

// Validate input parameters for newPassword, session, and username
if (!newPassword || newPassword.length === 0) {
this.logger.error("Set New Password failed: New password is required");
throw new BadRequestException("New password is required");
}

if (!session || session.length === 0) {
this.logger.error("Set New Password failed: Session is required");
throw new BadRequestException("Session is required");
}

if (!username || username.trim().length === 0) {
this.logger.error("Set New Password failed: Username is required");
throw new BadRequestException("Username is required");
}

const hatch = this.computeHatch(username, clientId, clientSecret);

const challengeResponses: any = {
Expand All @@ -483,6 +518,7 @@ private isValidEmail(email: string): boolean {
};

if (email) {
this.logger.log("Including email in challenge responses");
challengeResponses.email = email;
}

Expand All @@ -497,6 +533,7 @@ private isValidEmail(email: string): boolean {
const response = await this.cognito
.respondToAuthChallenge(params)
.promise();
this.logger.log("Responded to auth challenge for new password");

if (
!response.AuthenticationResult ||
Expand All @@ -516,11 +553,29 @@ private isValidEmail(email: string): boolean {
}
}

// purpose statement: updates user profile info in dynamodb
// use case: employee is updating their profile information
async updateProfile(
username: string,
email: string,
position_or_role: string
) {
// Validate input parameters for username, email, and position_or_role
if (!username || username.trim().length === 0) {
this.logger.error("Update Profile failed: Username is required");
throw new BadRequestException("Username is required");
}

if (!email || email.trim().length === 0) {
this.logger.error("Update Profile failed: Email is required");
throw new BadRequestException("Email is required");
}

if (!position_or_role || position_or_role.trim().length === 0) {
this.logger.error("Update Profile failed: Position or role is required");
throw new BadRequestException("Position or role is required");
}
this.logger.log(`Updating profile for user ${username}`);
const tableName = process.env.DYNAMODB_USER_TABLE_NAME || "TABLE_FAILURE";

const params = {
Expand Down Expand Up @@ -551,6 +606,8 @@ private isValidEmail(email: string): boolean {

// Add this to auth.service.ts

// purpose statement: validates a user's session token via cognito and retrieves user data from dynamodb
// use case: employee is accessing the app with an existing session token
async validateSession(accessToken: string): Promise<any> {
try {
// Use Cognito's getUser method to validate the token
Expand All @@ -564,6 +621,7 @@ async validateSession(accessToken: string): Promise<any> {
// Extract email from user attributes
for (const attribute of getUserResponse.UserAttributes) {
if (attribute.Name === 'email') {
this.logger.log(`Extracted email from user attributes: ${attribute.Value}`);
email = attribute.Value;
break;
}
Expand All @@ -582,6 +640,7 @@ async validateSession(accessToken: string): Promise<any> {
const user = userResult.Item;

if (!user) {
this.logger.error(`User not found in database for username: ${username}`);
throw new Error('User not found in database');
}

Expand Down