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
18 changes: 9 additions & 9 deletions apps/backend/src/allocations/allocations.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ import { Order } from '../orders/order.entity';
@Entity('allocations')
export class Allocation {
@PrimaryGeneratedColumn({ name: 'allocation_id' })
allocationId: number;
allocationId!: number;

@Column({ name: 'order_id', type: 'int', nullable: false })
orderId: number;
orderId!: number;

@ManyToOne(() => Order, (order) => order.allocations)
@JoinColumn({ name: 'order_id' })
order: Order;
order!: Order;

@Column({ name: 'item_id', type: 'int', nullable: false })
itemId: number;
itemId!: number;

@ManyToOne(() => DonationItem, (item) => item.allocations)
@JoinColumn({ name: 'item_id' })
item: DonationItem;
item!: DonationItem;

@Column({ name: 'allocated_quantity', type: 'int' })
allocatedQuantity: number;
allocatedQuantity!: number;

@Column({ name: 'reserved_at', type: 'timestamp' })
reservedAt: Date;
reservedAt!: Date;

@Column({ name: 'fulfilled_at', type: 'timestamp' })
fulfilledAt: Date;
@Column({ name: 'fulfilled_at', type: 'timestamp', nullable: true })
fulfilledAt!: Date | null;
}
2 changes: 1 addition & 1 deletion apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { ScheduleModule } from '@nestjs/schedule';
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: async (configService: ConfigService) =>
configService.get('typeorm'),
configService.getOrThrow('typeorm'),
}),
ScheduleModule.forRoot(),
UsersModule,
Expand Down
24 changes: 18 additions & 6 deletions apps/backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ export class AuthController {
// By default, creates a standard user
try {
await this.authService.signup(signUpDto);
} catch (e) {
throw new BadRequestException(e.message);
} catch (e: unknown) {
const message =
e instanceof Error
? e.message
: 'Unexpected error occurred when signing up user';
throw new BadRequestException(message);
}

const user = await this.usersService.create(
Expand All @@ -45,8 +49,12 @@ export class AuthController {
verifyUser(@Body() body: VerifyUserDto): void {
try {
this.authService.verifyUser(body.email, body.verificationCode);
} catch (e) {
throw new BadRequestException(e.message);
} catch (e: unknown) {
const message =
e instanceof Error
? e.message
: 'Unexpected error occurred when verifying user';
throw new BadRequestException(message);
}
}

Expand Down Expand Up @@ -76,8 +84,12 @@ export class AuthController {

try {
await this.authService.deleteUser(user.email);
} catch (e) {
throw new BadRequestException(e.message);
} catch (e: unknown) {
const message =
e instanceof Error
? e.message
: 'Unexpected error occurred when deleting user';
throw new BadRequestException(message);
}

this.usersService.remove(user.id);
Expand Down
5 changes: 5 additions & 0 deletions apps/backend/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ describe('AuthService', () => {
let service: AuthService;

beforeEach(async () => {
process.env.AWS_ACCESS_KEY_ID = 'test';
process.env.AWS_SECRET_ACCESS_KEY = 'test';
process.env.COGNITO_CLIENT_SECRET = 'test';
process.env.AWS_REGION = 'us-east-1';

const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();
Expand Down
86 changes: 75 additions & 11 deletions apps/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Injectable } from '@nestjs/common';
import {
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import {
AdminDeleteUserCommand,
AdminInitiateAuthCommand,
AdminInitiateAuthCommandOutput,
CognitoIdentityProviderClient,
ConfirmForgotPasswordCommand,
ConfirmSignUpCommand,
Expand All @@ -17,6 +22,7 @@ import { createHmac } from 'crypto';
import { RefreshTokenDto } from './dtos/refresh-token.dto';
import { Role } from '../users/types';
import { ConfirmPasswordDto } from './dtos/confirm-password.dto';
import { validateEnv } from '../utils/validation.utils';

@Injectable()
export class AuthService {
Expand All @@ -27,12 +33,12 @@ export class AuthService {
this.providerClient = new CognitoIdentityProviderClient({
region: CognitoAuthConfig.region,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
accessKeyId: validateEnv('AWS_ACCESS_KEY_ID'),
secretAccessKey: validateEnv('AWS_SECRET_ACCESS_KEY'),
},
});

this.clientSecret = process.env.COGNITO_CLIENT_SECRET;
this.clientSecret = validateEnv('COGNITO_CLIENT_SECRET');
}

// Computes secret hash to authenticate this backend to Cognito
Expand Down Expand Up @@ -69,8 +75,19 @@ export class AuthService {
],
});

const response = await this.providerClient.send(signUpCommand);
return response.UserConfirmed;
try {
const response = await this.providerClient.send(signUpCommand);

if (response.UserConfirmed == null) {
throw new InternalServerErrorException(
'Missing UserConfirmed from Cognito',
);
}

return response.UserConfirmed;
} catch (err: unknown) {
throw new InternalServerErrorException('Failed to sign up user');
}
}

async verifyUser(email: string, verificationCode: string): Promise<void> {
Expand Down Expand Up @@ -98,10 +115,14 @@ export class AuthService {

const response = await this.providerClient.send(signInCommand);

this.validateAuthenticationResultTokensForSignIn(response);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For both of these functions, can we return the authentication result from the function with a type that will avoid having to do non-null assertions?


const authResult = response.AuthenticationResult!;

return {
accessToken: response.AuthenticationResult.AccessToken,
refreshToken: response.AuthenticationResult.RefreshToken,
idToken: response.AuthenticationResult.IdToken,
accessToken: authResult.AccessToken!,
refreshToken: authResult.RefreshToken!,
idToken: authResult.IdToken!,
};
}

Expand All @@ -122,10 +143,14 @@ export class AuthService {

const response = await this.providerClient.send(refreshCommand);

this.validateAuthenticationResultTokensForRefresh(response);

const authResult = response.AuthenticationResult!;

return {
accessToken: response.AuthenticationResult.AccessToken,
accessToken: authResult.AccessToken!,
refreshToken: refreshToken,
idToken: response.AuthenticationResult.IdToken,
idToken: authResult.IdToken!,
};
}

Expand Down Expand Up @@ -163,4 +188,43 @@ export class AuthService {

await this.providerClient.send(adminDeleteUserCommand);
}

private validateAuthenticationResultTokensForSignIn(
commandOutput: AdminInitiateAuthCommandOutput,
): void {
if (commandOutput.AuthenticationResult == null) {
throw new NotFoundException(
'No associated authentication result for sign in',
);
}

if (
commandOutput.AuthenticationResult.AccessToken == null ||
commandOutput.AuthenticationResult.RefreshToken == null ||
commandOutput.AuthenticationResult.IdToken == null
) {
throw new NotFoundException(
'Necessary Authentication Result tokens not found for sign in ',
);
}
}

private validateAuthenticationResultTokensForRefresh(
commandOutput: AdminInitiateAuthCommandOutput,
): void {
if (commandOutput.AuthenticationResult == null) {
throw new NotFoundException(
'No associated authentication result for refresh',
);
}

if (
commandOutput.AuthenticationResult.AccessToken == null ||
commandOutput.AuthenticationResult.IdToken == null
) {
throw new NotFoundException(
'Necessary Authentication Result tokens not found for refresh',
);
}
}
}
7 changes: 7 additions & 0 deletions apps/backend/src/auth/authenticated-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Request } from 'express';
import { User } from '../users/user.entity';

// user does not have to be provided by the client but is added automatically by the auth backend
export interface AuthenticatedRequest extends Request {
user: User;
}
6 changes: 3 additions & 3 deletions apps/backend/src/auth/dtos/confirm-password.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { IsEmail, IsString } from 'class-validator';

export class ConfirmPasswordDto {
@IsEmail()
email: string;
email!: string;

@IsString()
newPassword: string;
newPassword!: string;

@IsString()
confirmationCode: string;
confirmationCode!: string;
}
2 changes: 1 addition & 1 deletion apps/backend/src/auth/dtos/delete-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { IsPositive } from 'class-validator';

export class DeleteUserDto {
@IsPositive()
userId: number;
userId!: number;
}
2 changes: 1 addition & 1 deletion apps/backend/src/auth/dtos/forgot-password.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { IsEmail } from 'class-validator';

export class ForgotPasswordDto {
@IsEmail()
email: string;
email!: string;
}
4 changes: 2 additions & 2 deletions apps/backend/src/auth/dtos/refresh-token.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { IsString } from 'class-validator';

export class RefreshTokenDto {
@IsString()
refreshToken: string;
refreshToken!: string;

@IsString()
userSub: string;
userSub!: string;
}
6 changes: 3 additions & 3 deletions apps/backend/src/auth/dtos/sign-in-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export class SignInResponseDto {
accessToken: string;
accessToken!: string;

refreshToken: string;
refreshToken!: string;

idToken: string;
idToken!: string;
}
4 changes: 2 additions & 2 deletions apps/backend/src/auth/dtos/sign-in.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { IsEmail, IsString } from 'class-validator';

export class SignInDto {
@IsEmail()
email: string;
email!: string;

@IsString()
password: string;
password!: string;
}
10 changes: 5 additions & 5 deletions apps/backend/src/auth/dtos/sign-up.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ import { IsEmail, IsNotEmpty, IsString, IsPhoneNumber } from 'class-validator';

export class SignUpDto {
@IsString()
firstName: string;
firstName!: string;

@IsString()
lastName: string;
lastName!: string;

@IsEmail()
email: string;
email!: string;

@IsString()
password: string;
password!: string;

@IsString()
@IsNotEmpty()
@IsPhoneNumber('US', {
message:
'phone must be a valid phone number (make sure all the digits are correct)',
})
phone: string;
phone!: string;
}
4 changes: 2 additions & 2 deletions apps/backend/src/auth/dtos/verify-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { IsEmail, IsString } from 'class-validator';

export class VerifyUserDto {
@IsEmail()
email: string;
email!: string;

@IsString()
verificationCode: string;
verificationCode!: string;
}
7 changes: 4 additions & 3 deletions apps/backend/src/aws/aws-s3.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable } from '@nestjs/common';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { validateEnv } from '../utils/validation.utils';

@Injectable()
export class AWSS3Service {
Expand All @@ -9,15 +10,15 @@ export class AWSS3Service {

constructor() {
this.region = process.env.AWS_REGION || 'us-east-2';
this.bucket = process.env.AWS_BUCKET_NAME;
this.bucket = validateEnv('AWS_BUCKET_NAME');
if (!this.bucket) {
throw new Error('AWS_BUCKET_NAME is not defined');
}
this.client = new S3Client({
region: this.region,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
accessKeyId: validateEnv('AWS_ACCESS_KEY_ID'),
secretAccessKey: validateEnv('AWS_SECRET_ACCESS_KEY'),
},
});
}
Expand Down
2 changes: 2 additions & 0 deletions apps/backend/src/config/migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { AddDonationRecurrenceFields1770080947285 } from '../migrations/17700809
import { UpdateManufacturerEntity1768680807820 } from '../migrations/1768680807820-UpdateManufacturerEntity';
import { AddUserPoolId1769189327767 } from '../migrations/1769189327767-AddUserPoolId';
import { UpdateOrderEntity1769990652833 } from '../migrations/1769990652833-UpdateOrderEntity';
import { DonationItemFoodTypeNotNull1771524930613 } from '../migrations/1771524930613-DonationItemFoodTypeNotNull';

const schemaMigrations = [
User1725726359198,
Expand Down Expand Up @@ -62,6 +63,7 @@ const schemaMigrations = [
UpdateManufacturerEntity1768680807820,
AddUserPoolId1769189327767,
UpdateOrderEntity1769990652833,
DonationItemFoodTypeNotNull1771524930613,
];

export default schemaMigrations;
Loading