register and forget pwd flow

This commit is contained in:
Kallesh B S 2025-07-02 17:37:04 +05:30
parent 1f318d0039
commit 2eb3fe504b
13 changed files with 428 additions and 81 deletions

57
package-lock.json generated
View File

@ -22,6 +22,8 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"handlebars": "^4.7.8",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jwk-to-pem": "^2.0.7", "jwk-to-pem": "^2.0.7",
"mssql": "^10.0.4", "mssql": "^10.0.4",
@ -1422,6 +1424,21 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
"license": "BSD-3-Clause"
},
"node_modules/@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@ -3320,6 +3337,27 @@
"url": "https://ko-fi.com/killymxi" "url": "https://ko-fi.com/killymxi"
} }
}, },
"node_modules/@sideway/address": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
"integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@sideway/formula": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
"license": "BSD-3-Clause"
},
"node_modules/@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"license": "BSD-3-Clause"
},
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.27.8", "version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@ -8598,7 +8636,6 @@
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
"license": "MIT", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"minimist": "^1.2.5", "minimist": "^1.2.5",
"neo-async": "^2.6.2", "neo-async": "^2.6.2",
@ -8620,7 +8657,6 @@
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"optional": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -10351,6 +10387,19 @@
"url": "https://github.com/chalk/supports-color?sponsor=1" "url": "https://github.com/chalk/supports-color?sponsor=1"
} }
}, },
"node_modules/joi": {
"version": "17.13.3",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
"integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
"license": "BSD-3-Clause",
"dependencies": {
"@hapi/hoek": "^9.3.0",
"@hapi/topo": "^5.1.0",
"@sideway/address": "^4.1.5",
"@sideway/formula": "^3.0.1",
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/js-beautify": { "node_modules/js-beautify": {
"version": "1.15.4", "version": "1.15.4",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz",
@ -12134,7 +12183,6 @@
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/nice-try": { "node_modules/nice-try": {
@ -16784,8 +16832,7 @@
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"license": "MIT", "license": "MIT"
"optional": true
}, },
"node_modules/wrap-ansi": { "node_modules/wrap-ansi": {
"version": "6.2.0", "version": "6.2.0",

View File

@ -33,6 +33,8 @@
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.14.1", "class-validator": "^0.14.1",
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"handlebars": "^4.7.8",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jwk-to-pem": "^2.0.7", "jwk-to-pem": "^2.0.7",
"mssql": "^10.0.4", "mssql": "^10.0.4",

View File

@ -0,0 +1,14 @@
<div style="font-family: Arial, sans-serif; padding: 20px; background-color: #f9f9f9;">
<div style="max-width: 600px; margin: auto; background-color: #fff; padding: 30px; border-radius: 8px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<h2 style="color: #4A90E2;">🔐 Registration Initiated</h2>
<p style="font-size: 16px;">A Registration has been successfully initiated for the email: <strong>{{to}}</strong></p>
<p>Please click below to complete your registration:</p>
<a href={{url}} style="display: inline-block; margin-top: 20px; background-color: #007BFF; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-size: 16px;">
Register Now
</a>
<div style="margin-top: 40px;">
<p style="color: #888;">Powered by</p>
<img src="https://yourapp.com/logo.png" alt="App Logo" style="max-height: 50px;">
</div>
</div>
</div>

View File

@ -0,0 +1,14 @@
<div style="font-family: Arial, sans-serif; padding: 20px; background-color: #f9f9f9;">
<div style="max-width: 600px; margin: auto; background-color: #fff; padding: 30px; border-radius: 8px; text-align: center; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<h2 style="color: #4A90E2;">Reset your password</h2>
{{!-- <p style="font-size: 16px;">A Registration has been successfully initiated for the email: <strong>{{to}}</strong></p> --}}
<p>Please click below to complete your password reset:</p>
<a href={{url}} style="display: inline-block; margin-top: 20px; background-color: #007BFF; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-size: 16px;">
Register Now
</a>
<div style="margin-top: 40px;">
<p style="color: #888;">Powered by</p>
<img src="https://yourapp.com/logo.png" alt="App Logo" style="max-height: 50px;">
</div>
</div>
</div>

View File

@ -0,0 +1 @@
<h1>Testing....</h1>

View File

@ -1,14 +1,21 @@
import { Body, Controller, Get, HttpCode, Post, Put, Req, Res, UseInterceptors } from '@nestjs/common'; import { Body, Controller, Get, HttpCode, Post, Put, Req, Res, UseGuards, UseInterceptors } from '@nestjs/common';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { ApiTags } from '@nestjs/swagger'; import { ApiTags } from '@nestjs/swagger';
import { AuthLoginDTO } from './auth.dto'; import { AuthLoginDTO, SendMailDTO } from './auth.dto';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { LogoutInterceptor } from 'src/interceptors/logout.interceptor'; import { LogoutInterceptor } from 'src/interceptors/logout.interceptor';
import { RegisterGuard } from 'src/guards/register.guard';
import { UserMaintenanceService } from 'src/oracle/user-maintenance/user-maintenance.service';
import { RolesGuard } from 'src/guards/roles.guard';
import { Roles } from 'src/decorators/roles.decorator';
@ApiTags('Auth') @ApiTags('Auth')
@Controller() @Controller()
export class AuthController { export class AuthController {
constructor(private readonly authService: AuthService) { } constructor(
private readonly authService: AuthService,
private readonly userMaintenanceService: UserMaintenanceService
) { }
@Post('login') @Post('login')
@HttpCode(200) @HttpCode(200)
@ -36,7 +43,7 @@ export class AuthController {
return { statusCode: 200, message: "Logged-In Successfully", email: k.email } return { statusCode: 200, message: "Logged-In Successfully", email: k.email }
} }
// @UseGuards(RegisterGuard) @UseGuards(RegisterGuard)
@Post('register') @Post('register')
async register(@Body() body: AuthLoginDTO, @Req() req: Request) { async register(@Body() body: AuthLoginDTO, @Req() req: Request) {
return this.authService.registerUser(body, req); return this.authService.registerUser(body, req);
@ -55,9 +62,10 @@ export class AuthController {
res.json({ ...rst }) res.json({ ...rst })
} }
@UseGuards(RegisterGuard)
@Put('forgot-password') @Put('forgot-password')
async forgotPassword() { async forgotPassword(@Body() body: AuthLoginDTO, @Req() req: Request) {
return this.authService.forgotPassword(); return this.authService.forgotPassword(body, req);
} }
@Get('refresh-tokens') @Get('refresh-tokens')
@ -87,6 +95,15 @@ export class AuthController {
return { statusCode: 200, message: "Tokens refreshed successfully" } return { statusCode: 200, message: "Tokens refreshed successfully" }
} }
@UseGuards(RolesGuard)
@Roles('sa')
@Post('sendmail')
@HttpCode(200)
async sendMail(@Body() body: SendMailDTO) {
// let token = await this.jwtService.generateToken({ email: mail })
return this.authService.sendMail(body);
}
// @Get('checkRoles') // @Get('checkRoles')
// @UseGuards(JwtAuthGuard, RolesGuard) // @UseGuards(JwtAuthGuard, RolesGuard)
// @Roles('ca') // @Roles('ca')

View File

@ -1,5 +1,6 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator'; import { Transform, Type } from 'class-transformer';
import { IsEmail, IsEnum, IsString, ValidateNested } from 'class-validator';
export class AuthLoginDTO { export class AuthLoginDTO {
@ApiProperty({ required: true }) @ApiProperty({ required: true })
@ -10,3 +11,56 @@ export class AuthLoginDTO {
@IsString() @IsString()
P_PASSWORD: string; P_PASSWORD: string;
} }
export enum MailTypeDTO {
REGISTER_CLIENT = "REGISTER_CLIENT",
REGISTER_SP = "REGISTER_SP",
REGISTER_USCIB = "REGISTER_USCIB",
FORGOT_PASSWORD = "FORGOT_PASSWORD"
// DEMO_MAIL = "DEMO_MAIL"
}
export class SendMailDTO {
@ApiProperty({ required: true, example: 'user@example.com' })
@IsEmail({}, { message: 'P_TO must be a valid email address' })
P_TO: string;
@ApiProperty({ enum: MailTypeDTO, required: true })
@Transform(({ value }) => value?.toUpperCase())
@IsEnum(MailTypeDTO, { message: "Invalid P_MAIL_TYPE" })
P_MAIL_TYPE: MailTypeDTO;
}
export class MailContextDTO {
@ApiProperty({ required: true })
@IsString()
to: string;
@ApiProperty({ required: true })
@IsString()
url: string;
}
export class MailTemplateDTO {
@ApiProperty({ required: true })
@IsString()
to: string;
@ApiProperty({ required: true })
@IsString()
subject: string;
// @ApiProperty({ required: true })
// @IsString()
// text: string;
@ApiProperty({ required: true })
@IsString()
template: string;
@ApiProperty({ type: () => MailContextDTO, required: true })
@ValidateNested()
@Type(() => MailContextDTO)
context: MailContextDTO;
}

View File

@ -1,11 +1,59 @@
import { Module } from '@nestjs/common'; import { forwardRef, Module } from '@nestjs/common';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AuthController } from './auth.controller'; import { AuthController } from './auth.controller';
import { DbModule } from 'src/db/db.module'; import { DbModule } from 'src/db/db.module';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { OracleModule } from 'src/oracle/oracle.module';
@Module({ @Module({
imports: [DbModule], imports: [DbModule, forwardRef(() => OracleModule)],
providers: [AuthService], providers: [
AuthService,
{
provide: "REGISTER_SIGN_JWT",
useFactory: (config: ConfigService) => {
const base64Key = config.get<string>('JWT_REGISTER_PRIVATE_KEY');
if (!base64Key) {
throw new Error('JWT_REGISTER_PRIVATE_KEY is not defined in the config');
}
const secretKey = Buffer.from(base64Key, 'base64').toString('utf-8');
const expiry = config.get<string>('JWT_REGISTER_EXPIRY');
if (!expiry) {
throw new Error('JWT_REGISTER_PRIVATE_KEY is not defined in the config');
}
return new JwtService({
secret: secretKey,
signOptions: {
expiresIn: expiry,
algorithm: 'ES384'
},
})
},
inject: [ConfigService],
},
{
provide: "REGISTER_VERIFY_JWT",
useFactory: (config: ConfigService) => {
const base64Key = config.get<string>('JWT_REGISTER_PUBLIC_KEY');
if (!base64Key) {
throw new Error('JWT_REGISTER_PRIVATE_KEY is not defined in the config');
}
const secretKey = Buffer.from(base64Key, 'base64').toString('utf-8');
return new JwtService({
secret: secretKey,
verifyOptions: {
algorithms: ['ES384']
}
})
},
inject: [ConfigService],
}
],
controllers: [AuthController], controllers: [AuthController],
exports: [AuthService] exports: [AuthService]
}) })

View File

@ -1,6 +1,6 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { Inject, Injectable, InternalServerErrorException } from '@nestjs/common';
import { OracleDBService } from 'src/db/db.service'; import { OracleDBService } from 'src/db/db.service';
import { AuthLoginDTO } from './auth.dto'; import { AuthLoginDTO, MailTemplateDTO, MailTypeDTO, SendMailDTO } from './auth.dto';
import * as oracledb from 'oracledb'; import * as oracledb from 'oracledb';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import axios, { AxiosRequestConfig } from 'axios'; import axios, { AxiosRequestConfig } from 'axios';
@ -10,6 +10,9 @@ import * as jwt from "jsonwebtoken"
import { UnauthorizedException } from 'src/exceptions/unauthorized.exception'; import { UnauthorizedException } from 'src/exceptions/unauthorized.exception';
import { ConflictException } from 'src/exceptions/conflict.exception'; import { ConflictException } from 'src/exceptions/conflict.exception';
import { BadRequestException } from 'src/exceptions/badRequest.exception'; import { BadRequestException } from 'src/exceptions/badRequest.exception';
import { MailerService } from '@nestjs-modules/mailer';
import { JwtService } from '@nestjs/jwt';
import { UserMaintenanceService } from 'src/oracle/user-maintenance/user-maintenance.service';
interface DecodedToken { interface DecodedToken {
email: string; email: string;
@ -33,11 +36,25 @@ export class AuthService {
private readonly CLIENT_SECRET: string; private readonly CLIENT_SECRET: string;
private readonly TOKEN_URL: string; private readonly TOKEN_URL: string;
private readonly USERS_URL: string; private readonly USERS_URL: string;
private readonly JWT_REGISTER_PRIVATE_KEY: string;
private readonly JWT_REGISTER_PUBLIC_KEY: string;
constructor( constructor(
private readonly oracleDBService: OracleDBService, private readonly oracleDBService: OracleDBService,
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly mailService: MailerService,
private readonly userMaintenanceService: UserMaintenanceService,
@Inject("REGISTER_SIGN_JWT") readonly REGISTER_SIGN_JWT: JwtService,
@Inject("REGISTER_VERIFY_JWT") readonly REGISTER_VERIFY_JWT: JwtService
) { ) {
const JWT_REGISTER_PRIVATE_KEY = this.configService.get<string>('JWT_REGISTER_PRIVATE_KEY');
if (!JWT_REGISTER_PRIVATE_KEY) throw new Error('Environment variable JWT_REGISTER_PRIVATE_KEY is not set');
this.JWT_REGISTER_PRIVATE_KEY = Buffer.from(JWT_REGISTER_PRIVATE_KEY, 'base64').toString('utf-8');
const JWT_REGISTER_PUBLIC_KEY = this.configService.get<string>('JWT_REGISTER_PUBLIC_KEY');
if (!JWT_REGISTER_PUBLIC_KEY) throw new Error('Environment variable JWT_REGISTER_PRIVATE_KEY is not set');
this.JWT_REGISTER_PUBLIC_KEY = Buffer.from(JWT_REGISTER_PUBLIC_KEY, 'base64').toString('utf-8');
const KEYCLOAK_URL = this.configService.get<string>('KEYCLOAK_URL'); const KEYCLOAK_URL = this.configService.get<string>('KEYCLOAK_URL');
if (!KEYCLOAK_URL) throw new Error('Environment variable KEYCLOAK_URL is not set'); if (!KEYCLOAK_URL) throw new Error('Environment variable KEYCLOAK_URL is not set');
this.KEYCLOAK_URL = KEYCLOAK_URL; this.KEYCLOAK_URL = KEYCLOAK_URL;
@ -190,7 +207,7 @@ export class AuthService {
} }
} }
async getUserSession(email:string) { async getUserSession(email: string) {
try { try {
const uid = await this.getUserIdByEmail(email); const uid = await this.getUserIdByEmail(email);
@ -258,7 +275,7 @@ export class AuthService {
if (access_token && refresh_token && userSession.length > 0) { if (access_token && refresh_token && userSession.length > 0) {
let tokens = await this.getTokenFromRefreshToken(req); let tokens = await this.getTokenFromRefreshToken(req);
const decoded = jwt.decode(access_token, { complete: true }); const decoded = jwt.decode(access_token, { complete: true });
const email:any = (decoded && typeof decoded === 'object') ? (decoded as any).payload?.email : undefined; const email: any = (decoded && typeof decoded === 'object') ? (decoded as any).payload?.email : undefined;
return { ...tokens, email } return { ...tokens, email }
} }
@ -313,35 +330,21 @@ export class AuthService {
} }
} }
async forgotPassword() { async forgotPassword(body: AuthLoginDTO, req: Request) {
let det = await req['user'];
if (det?.email !== body.P_EMAILADDR) {
throw new BadRequestException();
}
const validatePasswordURLSearchParams = new URLSearchParams({ const validatePasswordURLSearchParams = new URLSearchParams({
grant_type: 'password', grant_type: 'password',
client_id: `${this.CLIENT_ID}`, client_id: `${this.CLIENT_ID}`,
client_secret: `${this.CLIENT_SECRET}`, client_secret: `${this.CLIENT_SECRET}`,
username: `${'a@gmail.com'}`, username: `${body.P_EMAILADDR}`,
password: `${'A1!bcdef'}`, password: `${body.P_PASSWORD}`,
}); });
let userID = await this.getUserIdByEmail("");
let resetPasswordURL = `${this.USERS_URL}/${userID}/reset-password`;
let adminAccessToken = await this.getAdminAccessToken();
const options1: AxiosRequestConfig = {
method: 'PUT',
url: resetPasswordURL,
headers: {
Authorization: `Bearer ${adminAccessToken}`
},
data: {
type: 'password',
value: 'A1!bcdef',
temporary: false,
}
};
const options2: AxiosRequestConfig = { const options2: AxiosRequestConfig = {
method: 'POST', method: 'POST',
url: this.TOKEN_URL, url: this.TOKEN_URL,
@ -427,21 +430,45 @@ export class AuthService {
const uid = await this.getUserIdByEmail(body.P_EMAILADDR); const uid = await this.getUserIdByEmail(body.P_EMAILADDR);
const res = await this.assignRoleToUser(uid, "ca"); let ROLE = "";
const response: any = await this.userMaintenanceService.ValidateEmail({ P_EMAILADDR: body.P_EMAILADDR })
if (Array.isArray(response) && !response[0].ERRORMESG) {
switch (response[0].SOURCE) {
case "USCIB": ROLE = 'ua'; break;
case "SP": ROLE = 'sa'; break;
case "CLIENT": ROLE = 'ca'; break;
}
}
else if (Array.isArray(response) && response[0].ERRORMESG) {
throw new BadRequestException();
}
if (!ROLE) {
console.log("ERROR while giving access : ");
throw new InternalServerErrorException();
}
const res = await this.assignRoleToUser(uid, ROLE);
console.log(res); console.log(res);
return { message: 'User created successfully' }; return { statusCode: 201, message: 'User registered successfully' };
} }
} catch (error) { } catch (error) {
// handle errors like user exists, validation errors, etc.
console.log(error.message); console.log(error.message);
if (error.message === "Request failed with status code 409") { if (error.message === "Request failed with status code 409") {
throw new ConflictException("User already exist"); throw new ConflictException("User already exist");
} }
else if (error instanceof BadRequestException) {
console.log("ERROR while registering : ", error.message);
return error;
}
console.log("ERROR while giving access : ", error.message);
throw new InternalServerErrorException('Failed to create user'); throw new InternalServerErrorException('Failed to create user');
} }
} }
@ -601,4 +628,112 @@ export class AuthService {
throw new UnauthorizedException('Authentication failed'); throw new UnauthorizedException('Authentication failed');
} }
} }
async getMailTemplate(body: SendMailDTO, token: string, source: string): Promise<MailTemplateDTO | null> {
switch (body.P_MAIL_TYPE) {
case MailTypeDTO.REGISTER_CLIENT:
return {
to: body.P_TO,
subject: `🔐 Register Your Account`,
template: 'a',
context: {
to: body.P_TO,
url: `https://client.alphaomegainfosys.com/register/${token}`
}
}
case MailTypeDTO.FORGOT_PASSWORD:
return {
to: body.P_TO,
subject: `Reset your password`,
template: 'b',
context: {
to: body.P_TO,
url: `${source === 'ua' ? `https://policy.alphaomegainfosys.com/register/${token}`
: source === 'sa' ? `https://sp.alphaomegainfosys.com/register/${token}`
: source === 'ca' ? `https://client.alphaomegainfosys.com/register/${token}` : ''}`
}
}
// case MailTypeDTO.DEMO_MAIL:
// return {
// to: body.P_TO,
// subject: `Testing`,
// template: 'demo',
// context: {
// to: body.P_TO,
// url: `https://client.alphaomegainfosys.com/register/${token}`
// }
// }
default:
return null;
}
}
async sendMail(body: SendMailDTO) {
console.log("mail body : ", body);
let token: string;
let ROLE: string = "";
if (body.P_MAIL_TYPE === MailTypeDTO.FORGOT_PASSWORD) {
try {
const response: any = await this.userMaintenanceService.ValidateEmail({ P_EMAILADDR: body.P_TO })
if (Array.isArray(response) && !response[0].ERRORMESG) {
switch (response[0].SOURCE) {
case "USCIB": ROLE = 'ua'; break;
case "SP": ROLE = 'sa'; break;
case "CLIENT": ROLE = 'ca'; break;
}
}
else if (Array.isArray(response) && response[0].ERRORMESG) {
throw new BadRequestException();
}
if (!ROLE) {
console.log("ERROR while giving access : ");
throw new InternalServerErrorException();
}
} catch (error) {
if (error instanceof BadRequestException) {
throw error
}
throw new InternalServerErrorException();
}
}
try {
token = this.REGISTER_SIGN_JWT.sign({ email: body.P_TO });
console.log("Register token : ", token);
if (!token) {
console.log("Register Token Generation failed");
throw new InternalServerErrorException();
}
}
catch (error) {
console.log(error.message);
throw new InternalServerErrorException("Register Token Generation failed")
}
const mailTemplate: MailTemplateDTO | null = await this.getMailTemplate(body, token, ROLE);
if (!mailTemplate) {
throw new BadRequestException();
}
try {
await this.mailService.sendMail({ ...mailTemplate });
console.log('Email sent successfully');
return { statusCode: 200, message: 'Email sent successfully' };
} catch (error) {
console.error('Failed to send email:', error.message, error);
throw new InternalServerErrorException();
}
}
} }

View File

@ -2,6 +2,7 @@
import { import {
CanActivate, CanActivate,
ExecutionContext, ExecutionContext,
Inject,
Injectable, Injectable,
UnauthorizedException, UnauthorizedException,
} from '@nestjs/common'; } from '@nestjs/common';
@ -10,44 +11,28 @@ import { Request } from 'express';
@Injectable() @Injectable()
export class RegisterGuard implements CanActivate { export class RegisterGuard implements CanActivate {
constructor(private readonly jwtService: JwtService) {} constructor(
@Inject("REGISTER_VERIFY_JWT") readonly REGISTER_VERIFY_JWT: JwtService
) { }
canActivate(context: ExecutionContext): boolean { canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>(); const request = context.switchToHttp().getRequest<Request>();
const authHeader = request.headers['authorization']; const authHeader = request.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Bearer ')) { if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Authorization header missing or malformed'); throw new UnauthorizedException('Invalid Request');
} }
const token = authHeader.split(' ')[1]; const token = authHeader.split(' ')[1];
try { try {
// Decode without verifying to check expiry const payload = this.REGISTER_VERIFY_JWT.verify(token);
const decoded: any = this.jwtService.decode(token); request['user'] = payload;
console.log(decoded);
if (!decoded || !decoded.exp) {
throw new UnauthorizedException('Invalid token payload');
}
// Check expiration (exp is in seconds, Date.now() in ms)
const now = Math.floor(Date.now() / 1000);
if (decoded.exp < now) {
throw new UnauthorizedException('Token expired');
}
// Verify the token (checks signature and expiration again)
this.jwtService.verify(token, {secret:'yourSecretKey'});
// Optionally attach user info to request object for further use
request['user'] = decoded;
return true; return true;
} catch (err) { } catch (err) {
throw new UnauthorizedException('Invalid or expired token'); console.log("Error in register guard : ", err.message);
throw new UnauthorizedException("Invalid Register Token");
} }
} }
} }

View File

@ -1,18 +1,47 @@
import { MailerModule } from '@nestjs-modules/mailer'; import { MailerModule } from '@nestjs-modules/mailer';
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { join } from 'path';
import * as Joi from 'joi';
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
@Module({ @Module({
imports: [ imports: [
MailerModule.forRoot({ ConfigModule.forRoot({
transport: { isGlobal: true,
host: 'localhost', validationSchema: Joi.object({
port: 1025, MAIL_USER: Joi.string().required(),
ignoreTLS: true, MAIL_PASS: Joi.string().required(),
}, }),
defaults: { }),
from: '"No Reply" <no-reply@example.com>',
}, MailerModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
transport: {
host: 'smtp.gmail.com',
port: 587,
// ignoreTLS: true,
secure: false,
auth: {
user: configService.get<string>('MAIL_USER'),
pass: configService.get<string>('MAIL_PASS'),
},
},
defaults: {
from: `"Carnet APP" <${configService.get<string>('MAIL_USER')}>`,
},
template: {
dir: join(__dirname, '../../..', 'public', 'mail-templates'),
adapter: new HandlebarsAdapter(), // import from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'
options: {
strict: true,
},
},
}),
inject: [ConfigService],
}), }),
], ],
}) })
export class MailModule { } export class MailModule { }

View File

@ -26,6 +26,6 @@ import { AuthModule } from 'src/auth/auth.module';
], ],
providers: [OracleService], providers: [OracleService],
controllers: [], controllers: [],
exports: [OracleService], exports: [OracleService, UserMaintenanceModule],
}) })
export class OracleModule {} export class OracleModule {}

View File

@ -6,6 +6,7 @@ import { AuthModule } from 'src/auth/auth.module';
@Module({ @Module({
imports: [AuthModule], imports: [AuthModule],
controllers: [UserMaintenanceController], controllers: [UserMaintenanceController],
providers: [UserMaintenanceService] providers: [UserMaintenanceService],
exports: [UserMaintenanceService]
}) })
export class UserMaintenanceModule { } export class UserMaintenanceModule { }