diff --git a/mail/html/password-changed.hbs b/mail/html/password-changed.hbs
new file mode 100644
index 0000000..e69de29
diff --git a/mail/html/verify-email.hbs b/mail/html/verify-email.hbs
new file mode 100644
index 0000000..3b9ef41
--- /dev/null
+++ b/mail/html/verify-email.hbs
@@ -0,0 +1,69 @@
+
+
+
+
+
+ Email Verification
+
+
+
+
+
+
Verify Your Email Address
+
Hi there,
+
+ Thanks for signing up! Please confirm your email address by clicking the button below.
+
+
+
+ If you did not create an account, no further action is required.
+
+
+ Regards,
WaterWolf
+
+
+
+
+ Powered by Waterwolf
+
+
+
+
\ No newline at end of file
diff --git a/mail/html/welcome.hbs b/mail/html/welcome.hbs
new file mode 100644
index 0000000..e69de29
diff --git a/mail/text/password-changed.txt b/mail/text/password-changed.txt
new file mode 100644
index 0000000..e69de29
diff --git a/mail/text/verify-email.txt b/mail/text/verify-email.txt
new file mode 100644
index 0000000..7d113cd
--- /dev/null
+++ b/mail/text/verify-email.txt
@@ -0,0 +1,12 @@
+Verify Your Email Address
+
+Hi there,
+
+Thanks for signing up! Please confirm your email address by clicking the link below.
+
+Verify Email: {{verificationUrl}}
+
+If you did not create an account, no further action is required.
+
+Regards,
+WaterWolf
\ No newline at end of file
diff --git a/mail/text/welcome.txt b/mail/text/welcome.txt
new file mode 100644
index 0000000..e69de29
diff --git a/nest-cli.json b/nest-cli.json
index f9aa683..a8170d1 100644
--- a/nest-cli.json
+++ b/nest-cli.json
@@ -1,8 +1,8 @@
-{
- "$schema": "https://json.schemastore.org/nest-cli",
- "collection": "@nestjs/schematics",
- "sourceRoot": "src",
- "compilerOptions": {
- "deleteOutDir": true
- }
-}
+{
+ "$schema": "https://json.schemastore.org/nest-cli",
+ "collection": "@nestjs/schematics",
+ "sourceRoot": "src",
+ "compilerOptions": {
+ "deleteOutDir": true
+ }
+}
diff --git a/package.json b/package.json
index d6c0aab..a6955ee 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.6",
"dotenv": "^16.4.5",
+ "handlebars": "^4.7.8",
"hbs": "^4.2.0",
"ioredis": "^5.4.1",
"keygrip": "^1.1.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 25e89bd..abaf822 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -59,6 +59,9 @@ importers:
dotenv:
specifier: ^16.4.5
version: 16.4.5
+ handlebars:
+ specifier: ^4.7.8
+ version: 4.7.8
hbs:
specifier: ^4.2.0
version: 4.2.0
@@ -2222,6 +2225,11 @@ packages:
engines: {node: '>=0.4.7'}
hasBin: true
+ handlebars@4.7.8:
+ resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
+ engines: {node: '>=0.4.7'}
+ hasBin: true
+
has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
@@ -6551,6 +6559,15 @@ snapshots:
optionalDependencies:
uglify-js: 3.18.0
+ handlebars@4.7.8:
+ dependencies:
+ minimist: 1.2.8
+ neo-async: 2.6.2
+ source-map: 0.6.1
+ wordwrap: 1.0.0
+ optionalDependencies:
+ uglify-js: 3.18.0
+
has-flag@3.0.0: {}
has-flag@4.0.0: {}
diff --git a/src/auth/controllers/auth.controller.ts b/src/auth/controllers/auth.controller.ts
index 813447b..8b6c565 100644
--- a/src/auth/controllers/auth.controller.ts
+++ b/src/auth/controllers/auth.controller.ts
@@ -1,4 +1,4 @@
-import { Body, Controller, Get, Post, Render, Res } from '@nestjs/common';
+import { Body, Controller, Get, Post, Render, Res, UseGuards } from '@nestjs/common';
import { ApiExcludeEndpoint } from '@nestjs/swagger';
import { AuthService } from '../services/auth.service';
@@ -7,6 +7,7 @@ import { CreateUserDto } from '../dto/register.dto';
import { LoginUserDto } from '../dto/loginUser.dto';
import { Response } from 'express';
import { User } from '../decorators/user.decorator';
+import { LoginGuard } from '../guard/login.guard';
@Controller('auth')
export class AuthController {
@@ -42,6 +43,7 @@ export class AuthController {
// Render pages
@Get('login')
+ @UseGuards(LoginGuard)
@Render('auth/login')
@ApiExcludeEndpoint()
public async getHello(): Promise {
@@ -53,6 +55,7 @@ export class AuthController {
}
@Get('register')
+ @UseGuards(LoginGuard)
@Render('auth/register')
@ApiExcludeEndpoint()
public async getRegister(): Promise {
@@ -62,6 +65,7 @@ export class AuthController {
}
@Get('forgot-password')
+ @UseGuards(LoginGuard)
@Render('auth/forgot-password')
@ApiExcludeEndpoint()
public async getForgotPassword(): Promise {
diff --git a/src/auth/decorators/requiresRole.decorator.ts b/src/auth/decorators/requiresRole.decorator.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/auth/decorators/user.decorator.ts b/src/auth/decorators/user.decorator.ts
index 40740aa..3452eec 100644
--- a/src/auth/decorators/user.decorator.ts
+++ b/src/auth/decorators/user.decorator.ts
@@ -10,7 +10,10 @@ export const User = createParamDecorator(() => {
}
const user = cls.get('user');
- // remove the password from the user object
- delete user.password;
+
+ if (!user) {
+ return null;
+ }
+
return user;
});
diff --git a/src/auth/guard/auth.guard.ts b/src/auth/guard/auth.guard.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/auth/guard/login.guard.ts b/src/auth/guard/login.guard.ts
new file mode 100644
index 0000000..11f2a8e
--- /dev/null
+++ b/src/auth/guard/login.guard.ts
@@ -0,0 +1,19 @@
+import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
+import { ClsService } from 'nestjs-cls';
+import { Observable } from 'rxjs';
+import { Response } from 'express';
+
+@Injectable()
+export class LoginGuard implements CanActivate {
+ constructor(private readonly clsService: ClsService) {}
+ canActivate(context: ExecutionContext): boolean | Promise | Observable {
+ const authType = this.clsService.get('authType');
+ const response = context.switchToHttp().getResponse() as Response;
+
+ if (authType === 'session') {
+ response.redirect('/auth/auth-test');
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts
index 7786151..f95c6f4 100644
--- a/src/auth/services/auth.service.ts
+++ b/src/auth/services/auth.service.ts
@@ -75,7 +75,7 @@ export class AuthService {
}
await this.userService.createUser(username, email, password);
- await this.mailService.sendWelcomeEmail(email);
+ await this.mailService.sendVerificationEmail(email, '11111');
return { error: false, message: 'User registered' };
}
diff --git a/src/mail/mail.service.ts b/src/mail/mail.service.ts
index 8499138..39327ba 100644
--- a/src/mail/mail.service.ts
+++ b/src/mail/mail.service.ts
@@ -1,6 +1,9 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PostalClientService } from 'nestjs-postal-client';
+import Handlebars from 'handlebars';
+import * as path from 'path';
+import { readFile } from 'fs/promises';
@Injectable()
export class MailService {
@@ -21,26 +24,74 @@ export class MailService {
* Send a welcome email to the user
* @param email The email address of the user
*/
- public async sendWelcomeEmail(email: string): Promise {
- this.logger.debug(`Sending welcome email to ${email}`);
-
- this.postalService.sendMessage({
- to: [email],
- subject: 'Welcome to the app!',
- from: this.configService.getOrThrow('MAIL_FROM'),
- plain_body: 'Welcome to the app!',
- });
- }
+ public async sendWelcomeEmail(email: string): Promise {}
/**
* Send a verification email to the user
* @param email The email address of the user
*/
- public async sendVerificationEmail(email: string): Promise {}
+ public async sendVerificationEmail(email: string, code: string): Promise {
+ this.logger.debug(`Sending welcome email to ${email}`);
+
+ const verificationUrl = `${this.configService.getOrThrow('BASE_URL')}/auth/verify?code=${code}`;
+
+ //handlebar render email template
+ const { html: templateHtml, text: templateText } = await this.fetchTemplate('verify-email');
+
+ //replace the placeholders with the actual values
+ const template = Handlebars.compile(templateHtml);
+ const html = template({ verificationUrl });
+ const text = Handlebars.compile(templateText)({ verificationUrl });
+
+ this.postalService.sendMessage({
+ to: [email],
+ subject: 'Verify your email address',
+ from: this.configService.getOrThrow('MAIL_FROM'),
+ plain_body: text,
+ html_body: html,
+ });
+ }
/**
* Send a password changed email to the user
* @param email The email address of the user
*/
public async sendPasswordChangedEmail(email: string): Promise {}
+
+ /**
+ * Private function. Reads the html email template from the file system and returns the content
+ * @param templateName The name of the template
+ * @returns The content of the template
+ */
+ private async readHtmlTemplate(templateName: string): Promise {
+ return await readFile(
+ path.join(__dirname, '../..', 'mail/html', `${templateName}.hbs`),
+ ).toString();
+ }
+
+ /**
+ * Private function. Reads the text email template from the file system and returns the content
+ * @param templateName The name of the template
+ * @returns The content of the template
+ */
+ private async readTextTemplate(templateName: string): Promise {
+ return await readFile(
+ path.join(__dirname, '../..', 'mail/text', `${templateName}.txt`),
+ ).toString();
+ }
+
+ /**
+ * Private template fetch function
+ * @param templateName The name of the template
+ * @returns The content of the template
+ */
+ private async fetchTemplate(templateName: string): Promise<{ html: string; text: string }> {
+ // await both promises
+ const [html, text] = await Promise.all([
+ this.readHtmlTemplate(templateName),
+ this.readTextTemplate(templateName),
+ ]);
+
+ return { html, text };
+ }
}
diff --git a/src/main.ts b/src/main.ts
index ef3d71f..4f07171 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
@@ -13,7 +14,13 @@ async function bootstrap() {
app.use(cookieParser());
// Doing this to make sure it's always the first middleware. Since it does hold auth data.
- app.use(new ClsMiddleware().use);
+ app.use(
+ new ClsMiddleware({
+ setup: (cls, _context) => {
+ cls.set('authType', 'none');
+ },
+ }).use,
+ );
// Rendering
app.useStaticAssets(join(__dirname, '..', 'public'));
diff --git a/src/user/user.service.ts b/src/user/user.service.ts
index 84c0bfd..a5731a3 100644
--- a/src/user/user.service.ts
+++ b/src/user/user.service.ts
@@ -12,6 +12,7 @@ import {
USER_NOT_FOUND_ERROR,
userCacheKey,
} from './user.constant';
+import { ClsService } from 'nestjs-cls';
@Injectable()
export class UserService {
@@ -19,6 +20,7 @@ export class UserService {
@InjectRepository(User)
private readonly userRepository: Repository,
private readonly redisService: RedisService,
+ private readonly clsService: ClsService,
) {}
/**
@@ -30,6 +32,11 @@ export class UserService {
*/
@Span()
async getUserById(id: string, relations: string[] = []): Promise {
+ if (this.clsService.get('authType') === 'session') {
+ if (this.clsService.get('user').id === id) {
+ return this.clsService.get('user');
+ }
+ }
const cacheKey = userCacheKey + id;
const cachedUser = await this.redisService.get(cacheKey);
diff --git a/tsconfig.json b/tsconfig.json
index 20482a3..15b72d4 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -23,4 +23,4 @@
}
]
}
-}
\ No newline at end of file
+}
diff --git a/views/home/privacy-policy.hbs b/views/home/privacy-policy.hbs
new file mode 100644
index 0000000..e69de29
diff --git a/views/home/terms-and-conditions.hbs b/views/home/terms-and-conditions.hbs
new file mode 100644
index 0000000..e69de29