auth template
This commit is contained in:
parent
d932953950
commit
63b08e9883
3548
package-lock.json
generated
3548
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -20,17 +20,26 @@
|
|||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nestjs-modules/mailer": "^2.0.2",
|
||||||
"@nestjs/common": "^11.0.1",
|
"@nestjs/common": "^11.0.1",
|
||||||
"@nestjs/config": "^4.0.2",
|
"@nestjs/config": "^4.0.2",
|
||||||
"@nestjs/core": "^11.0.1",
|
"@nestjs/core": "^11.0.1",
|
||||||
|
"@nestjs/jwt": "^11.0.0",
|
||||||
|
"@nestjs/passport": "^11.0.5",
|
||||||
"@nestjs/platform-express": "^11.0.1",
|
"@nestjs/platform-express": "^11.0.1",
|
||||||
"@nestjs/swagger": "^11.0.6",
|
"@nestjs/swagger": "^11.0.6",
|
||||||
"@nestjs/typeorm": "^11.0.0",
|
"@nestjs/typeorm": "^11.0.0",
|
||||||
|
"axios": "^1.10.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.1",
|
"class-validator": "^0.14.1",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"jwk-to-pem": "^2.0.7",
|
||||||
"mssql": "^10.0.4",
|
"mssql": "^10.0.4",
|
||||||
"mysql2": "^3.12.0",
|
"mysql2": "^3.12.0",
|
||||||
|
"nodemailer": "^7.0.3",
|
||||||
"oracledb": "^6.7.2",
|
"oracledb": "^6.7.2",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
"pg": "^8.13.3",
|
"pg": "^8.13.3",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
@ -46,6 +55,7 @@
|
|||||||
"@swc/core": "^1.10.7",
|
"@swc/core": "^1.10.7",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
|
"@types/jwk-to-pem": "^2.0.3",
|
||||||
"@types/mssql": "^9.1.7",
|
"@types/mssql": "^9.1.7",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/oracledb": "^6.5.4",
|
"@types/oracledb": "^6.5.4",
|
||||||
|
|||||||
@ -2,15 +2,15 @@ import { MiddlewareConsumer, Module } from '@nestjs/common';
|
|||||||
import { DbModule } from './db/db.module';
|
import { DbModule } from './db/db.module';
|
||||||
import { OracleModule } from './oracle/oracle.module';
|
import { OracleModule } from './oracle/oracle.module';
|
||||||
import { AuthModule } from './auth/auth.module';
|
import { AuthModule } from './auth/auth.module';
|
||||||
import { MssqlModule } from './mssql/mssql.module';
|
|
||||||
import { OriginCheckMiddleware } from './middleware/OriginCheck.middleware';
|
import { OriginCheckMiddleware } from './middleware/OriginCheck.middleware';
|
||||||
import { ReqBodyKeysToUppercaseMiddleware } from './middleware/reqBodyKeysToUppercase.middleware';
|
import { ReqBodyKeysToUppercaseMiddleware } from './middleware/reqBodyKeysToUppercase.middleware';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { MailModule } from './mail/mail.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({ isGlobal: true }),
|
ConfigModule.forRoot({ isGlobal: true }),
|
||||||
AuthModule, DbModule, OracleModule
|
AuthModule, DbModule, OracleModule, MailModule
|
||||||
],
|
],
|
||||||
controllers: [],
|
controllers: [],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
import { Body, Controller, Post } from '@nestjs/common';
|
import { Body, Controller, Get, HttpCode, Post, Put, Req, Res } 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 } from './auth.dto';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
@ -12,4 +13,81 @@ export class AuthController {
|
|||||||
login(@Body() body: AuthLoginDTO) {
|
login(@Body() body: AuthLoginDTO) {
|
||||||
return this.authService.login(body);
|
return this.authService.login(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('login-client')
|
||||||
|
@HttpCode(200)
|
||||||
|
async loginClient(@Body() body: AuthLoginDTO, @Res({ passthrough: true }) res: Response, @Req() req: Request) {
|
||||||
|
let k: any = await this.authService.loginUser(body.P_EMAILADDR, body.P_PASSWORD, req);
|
||||||
|
|
||||||
|
if (k.access_token) {
|
||||||
|
res.cookie('access_token', k.access_token, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false, // set to true if you're on HTTPS
|
||||||
|
sameSite: 'lax', // adjust based on your needs (strict, lax , none)
|
||||||
|
maxAge: 90 * 1000, // convert seconds to ms if applicable
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k.refresh_token) {
|
||||||
|
res.cookie('refresh_token', k.refresh_token, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false, // set to true if you're on HTTPS
|
||||||
|
sameSite: 'lax', // adjust based on your needs (strict, lax , none)
|
||||||
|
maxAge: 90 * 1000, // convert seconds to ms if applicable
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { statusCode: 200, message: "Logged-In Successfully", email: k.email }
|
||||||
|
}
|
||||||
|
|
||||||
|
// @UseGuards(RegisterGuard)
|
||||||
|
@Post('register-client')
|
||||||
|
async register(@Body() body: AuthLoginDTO, @Req() req: Request) {
|
||||||
|
return this.authService.registerUser(body, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @UseInterceptors(LogoutInterceptor)
|
||||||
|
@HttpCode(200)
|
||||||
|
@Post('logout-client')
|
||||||
|
async logout(@Req() req: any, @Res() res: Response) {
|
||||||
|
const refreshToken = req.user?.refreshToken;
|
||||||
|
let rst: any = await this.authService.logoutUser(refreshToken);
|
||||||
|
if (rst.statusCode === 200) {
|
||||||
|
res.clearCookie('access_token');
|
||||||
|
res.clearCookie('refresh_token');
|
||||||
|
}
|
||||||
|
res.json({ ...rst })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('forgot-password-client')
|
||||||
|
async forgotPassword() {
|
||||||
|
return this.authService.forgotPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('refresh-client-tokens')
|
||||||
|
async getTokenFromRefreshToken(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
|
||||||
|
let k: any = await this.authService.getTokenFromRefreshToken(req)
|
||||||
|
|
||||||
|
if (k.access_token) {
|
||||||
|
res.cookie('access_token', k.access_token, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false, // set to true if you're on HTTPS
|
||||||
|
sameSite: 'lax', // adjust based on your needs (strict, lax , none)
|
||||||
|
maxAge: 90 * 1000, // convert seconds to ms if applicable
|
||||||
|
path: '/'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k.refresh_token) {
|
||||||
|
res.cookie('refresh_token', k.refresh_token, {
|
||||||
|
httpOnly: true,
|
||||||
|
secure: false, // set to true if you're on HTTPS
|
||||||
|
sameSite: 'lax', // adjust based on your needs (strict, lax , none)
|
||||||
|
maxAge: 90 * 1000, // convert seconds to ms if applicable
|
||||||
|
path: '/'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { statusCode: 200, message: "Tokens refreshed successfully" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,23 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, 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 } from './auth.dto';
|
||||||
import * as oracledb from 'oracledb';
|
import * as oracledb from 'oracledb';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import * as jwkToPem from "jwk-to-pem"
|
||||||
|
import * as jwt from "jsonwebtoken"
|
||||||
|
import { UnauthorizedException } from 'src/exceptions/unauthorized.exception';
|
||||||
|
import { ConflictException } from 'src/exceptions/conflict.exception';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
constructor(private readonly oracleDBService: OracleDBService) { }
|
private keyCache: Record<string, string> = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly oracleDBService: OracleDBService,
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
) { }
|
||||||
|
|
||||||
async login(body: AuthLoginDTO) {
|
async login(body: AuthLoginDTO) {
|
||||||
let connection;
|
let connection;
|
||||||
@ -72,4 +84,375 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getPublicKey(kid: string): Promise<string> {
|
||||||
|
if (this.keyCache[kid]) return this.keyCache[kid];
|
||||||
|
|
||||||
|
const jwksUri = `${this.configService.get('KEYCLOAK_URL')}/realms/${this.configService.get(
|
||||||
|
'REALM',
|
||||||
|
)}/protocol/openid-connect/certs`;
|
||||||
|
|
||||||
|
const { data } = await axios.get(jwksUri);
|
||||||
|
const key = data.keys.find((k) => k.kid === kid);
|
||||||
|
if (!key) throw new InternalServerErrorException('Key not found');
|
||||||
|
|
||||||
|
const pem = jwkToPem(key);
|
||||||
|
this.keyCache[kid] = pem;
|
||||||
|
return pem;
|
||||||
|
}
|
||||||
|
|
||||||
|
async introspectToken(token: string): Promise<any> {
|
||||||
|
const url = `${this.configService.get('KEYCLOAK_URL')}/realms/${this.configService.get(
|
||||||
|
'REALM',
|
||||||
|
)}/protocol/openid-connect/token/introspect`;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('token', token);
|
||||||
|
params.append('client_id', this.configService.get('CLIENT_ID') || '');
|
||||||
|
params.append('client_secret', this.configService.get('CLIENT_SECRET') || '');
|
||||||
|
|
||||||
|
|
||||||
|
const response = await axios.post(url, params, {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async decodeToken(token: string): Promise<any> {
|
||||||
|
const decodedHeader = jwt.decode(token, { complete: true });
|
||||||
|
if (!decodedHeader || typeof decodedHeader !== 'object') {
|
||||||
|
throw new Error('Invalid token format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const kid: any = decodedHeader.header.kid;
|
||||||
|
const publicKey = await this.getPublicKey(kid);
|
||||||
|
|
||||||
|
const introspection = await this.introspectToken(token);
|
||||||
|
|
||||||
|
console.log('introspec : ', introspection);
|
||||||
|
|
||||||
|
if (!introspection.active) {
|
||||||
|
throw new Error('Unauthorized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const verified = jwt.verify(token, publicKey, { algorithms: ['RS256'] });
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAdminAccessTokenx(): Promise<string> {
|
||||||
|
const data = new URLSearchParams();
|
||||||
|
data.append('grant_type', 'client_credentials');
|
||||||
|
data.append('client_id', this.configService.get('CLIENT_ID') || '');
|
||||||
|
data.append('client_secret', this.configService.get('CLIENT_SECRET') || '');
|
||||||
|
let KEYCLOAK_URL = await this.configService.get('KEYCLOAK_URL');
|
||||||
|
try {
|
||||||
|
const response: AxiosResponse = await axios.post(KEYCLOAK_URL, data, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data.access_token;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting Keycloak access token:', error.response ? error.response.data : error.message);
|
||||||
|
throw new Error('Failed to obtain Keycloak access token');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loginUser(username: string, password: string, req: Request): Promise<any> {
|
||||||
|
|
||||||
|
console.log(username, password);
|
||||||
|
|
||||||
|
|
||||||
|
const accessToken = req.cookies['access_token'];
|
||||||
|
const refreshToken = req.cookies['refresh_token'];
|
||||||
|
|
||||||
|
console.log("---------cookies--------------");
|
||||||
|
console.log(accessToken, refreshToken);
|
||||||
|
|
||||||
|
// return {accessToken, refreshToken, expires_in:2000}
|
||||||
|
|
||||||
|
if (!accessToken && !refreshToken) {
|
||||||
|
|
||||||
|
console.log("I am here getting token ....");
|
||||||
|
|
||||||
|
const url = `${this.configService.get('KEYCLOAK_URL')}/realms/${this.configService.get('REALM')}/protocol/openid-connect/token`;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('grant_type', 'password');
|
||||||
|
params.append('client_id', this.configService.get('CLIENT_ID') || '');
|
||||||
|
params.append('client_secret', this.configService.get('CLIENT_SECRET') || '');
|
||||||
|
params.append('username', username);
|
||||||
|
params.append('password', password);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(url, params, {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
});
|
||||||
|
console.log('first time data ...');
|
||||||
|
|
||||||
|
let k = { ...response.data, email: username };
|
||||||
|
|
||||||
|
return k; // tokens and user info
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error.message);
|
||||||
|
|
||||||
|
throw new BadRequestException('Invalid username or password');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (accessToken && refreshToken) {
|
||||||
|
return { access_token: accessToken, refresh_token: refreshToken, email: username }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserIdByEmail() {
|
||||||
|
|
||||||
|
let url = `${this.configService.get('KEYCLOAK_URL')}/admin/realms/${this.configService.get('REALM')}/users?email=a@gmail.com`;
|
||||||
|
|
||||||
|
let adminAccessToken = await this.getAdminAccessToken();
|
||||||
|
|
||||||
|
const options: AxiosRequestConfig = {
|
||||||
|
method: 'GET',
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${adminAccessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.request(options);
|
||||||
|
return data[0]?.id;
|
||||||
|
} catch (error) {
|
||||||
|
// console.error(error);
|
||||||
|
console.log(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async forgotPassword() {
|
||||||
|
let urlx = `${this.configService.get('KEYCLOAK_URL')}/admin/realms/${this.configService.get('REALM')}/users`;
|
||||||
|
let urlxx = `${this.configService.get('KEYCLOAK_URL')}/admin/realms/${this.configService.get('REALM')}/users?email=${encodeURIComponent('a@gmail.com')}`;
|
||||||
|
let url = `${this.configService.get('KEYCLOAK_URL')}/admin/realms/${this.configService.get('REALM')}/users?email=a@gmail.com`;
|
||||||
|
|
||||||
|
let validatePasswordURL = `${this.configService.get('KEYCLOAK_URL')}/realms/${this.configService.get('REALM')}/protocol/openid-connect/token`
|
||||||
|
|
||||||
|
const validatePasswordURLSearchParams = new URLSearchParams({
|
||||||
|
grant_type: 'password',
|
||||||
|
client_id: `${this.configService.get('CLIENT_ID')}`,
|
||||||
|
client_secret: `${this.configService.get('CLIENT_SECRET')}`,
|
||||||
|
username: `${'a@gmail.com'}`,
|
||||||
|
password: `${'A1!bcdef'}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
let userID = await this.getUserIdByEmail();
|
||||||
|
|
||||||
|
console.log("userID: ", userID);
|
||||||
|
|
||||||
|
let resetPasswordURL = `${this.configService.get('KEYCLOAK_URL')}/admin/realms/${this.configService.get('REALM')}/users/${userID}/reset-password`;
|
||||||
|
|
||||||
|
let adminAccessToken = await this.getAdminAccessToken();
|
||||||
|
|
||||||
|
const options: AxiosRequestConfig = {
|
||||||
|
method: 'GET',
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${adminAccessToken}`,
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const options1: AxiosRequestConfig = {
|
||||||
|
method: 'PUT',
|
||||||
|
url: resetPasswordURL,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${adminAccessToken}`
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: 'password',
|
||||||
|
value: 'A1!bcdef',
|
||||||
|
temporary: false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const options2: AxiosRequestConfig = {
|
||||||
|
method: 'POST',
|
||||||
|
url: validatePasswordURL,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
data: validatePasswordURLSearchParams
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await axios.request(options2);
|
||||||
|
return { statusCode: 200, message: 'password reset successfull' }
|
||||||
|
} catch (error) {
|
||||||
|
// console.error(error);
|
||||||
|
console.log(error.message);
|
||||||
|
|
||||||
|
throw new InternalServerErrorException()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAdminAccessToken() {
|
||||||
|
try {
|
||||||
|
const adminTokenUrl = `${this.configService.get('KEYCLOAK_URL')}/realms/${this.configService.get('REALM')}/protocol/openid-connect/token`;
|
||||||
|
|
||||||
|
console.log(adminTokenUrl);
|
||||||
|
|
||||||
|
const adminParams = new URLSearchParams();
|
||||||
|
adminParams.append('grant_type', 'client_credentials');
|
||||||
|
adminParams.append('client_id', this.configService.get('CLIENT_ID') || '');
|
||||||
|
adminParams.append('client_secret', this.configService.get('CLIENT_SECRET') || '');
|
||||||
|
|
||||||
|
const adminTokenResponse = await axios.post(adminTokenUrl,
|
||||||
|
adminParams,
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
});
|
||||||
|
console.log(adminTokenResponse.data);
|
||||||
|
|
||||||
|
const adminAccessToken = adminTokenResponse.data.access_token;
|
||||||
|
|
||||||
|
return adminAccessToken;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error.message);
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async registerUser(body: AuthLoginDTO, req: Request) {
|
||||||
|
// First, get an admin access token using client credentials
|
||||||
|
let det = await req['user'];
|
||||||
|
|
||||||
|
if (det?.email !== body.P_EMAILADDR) {
|
||||||
|
throw new BadRequestException();
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminTokenUrl = `${this.configService.get('KEYCLOAK_URL')}/realms/${this.configService.get('REALM')}/protocol/openid-connect/token`;
|
||||||
|
|
||||||
|
console.log(adminTokenUrl);
|
||||||
|
|
||||||
|
const adminParams = new URLSearchParams();
|
||||||
|
adminParams.append('grant_type', 'client_credentials');
|
||||||
|
adminParams.append('client_id', this.configService.get('CLIENT_ID') || '');
|
||||||
|
adminParams.append('client_secret', this.configService.get('CLIENT_SECRET') || '');
|
||||||
|
|
||||||
|
const adminTokenResponse = await axios.post(adminTokenUrl,
|
||||||
|
adminParams,
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
});
|
||||||
|
console.log(adminTokenResponse.data);
|
||||||
|
|
||||||
|
// const adminAccessToken = adminTokenResponse.data.access_token;
|
||||||
|
const adminAccessToken = adminTokenResponse.data.access_token;
|
||||||
|
|
||||||
|
// Now create user
|
||||||
|
const createUserUrl = `${this.configService.get('KEYCLOAK_URL')}/admin/realms/${this.configService.get('REALM')}/users`;
|
||||||
|
|
||||||
|
const userPayload = {
|
||||||
|
username: body.P_EMAILADDR,
|
||||||
|
// firstName:"A",
|
||||||
|
// lastName:"B",
|
||||||
|
email: body.P_EMAILADDR,
|
||||||
|
emailVerified: true,
|
||||||
|
enabled: true,
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
type: 'password',
|
||||||
|
value: body.P_PASSWORD,
|
||||||
|
temporary: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(userPayload);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(createUserUrl, userPayload, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${adminAccessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 201) {
|
||||||
|
return { message: 'User created successfully' };
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// handle errors like user exists, validation errors, etc.
|
||||||
|
console.log(error.message);
|
||||||
|
|
||||||
|
if (error.message === "Request failed with status code 409") {
|
||||||
|
throw new ConflictException("User already exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InternalServerErrorException('Failed to create user');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async logoutUser(refreshToken: string): Promise<any> {
|
||||||
|
const url = `${this.configService.get('KEYCLOAK_URL')}/realms/${this.configService.get('REALM')}/protocol/openid-connect/logout`;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('client_id', this.configService.get('CLIENT_ID') || '');
|
||||||
|
params.append('client_secret', this.configService.get('CLIENT_SECRET') || '');
|
||||||
|
params.append('refresh_token', refreshToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(url, params, {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
});
|
||||||
|
return { statusCode: 200, message: 'Logged-Out successfully' };
|
||||||
|
} catch (error) {
|
||||||
|
throw new UnauthorizedException('Logout failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTokenFromRefreshToken(req: Request) {
|
||||||
|
const refreshToken = req.cookies['refresh_token'];
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
|
throw new UnauthorizedException('Authentication failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
let dk = await this.introspectToken(refreshToken);
|
||||||
|
|
||||||
|
console.log('--------------validation------------------------');
|
||||||
|
console.log(dk);
|
||||||
|
|
||||||
|
console.log('--------------validation------------------------');
|
||||||
|
|
||||||
|
const url = `${this.configService.get('KEYCLOAK_URL')}/realms/${this.configService.get('REALM')}/protocol/openid-connect/token`;
|
||||||
|
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('grant_type', 'refresh_token');
|
||||||
|
params.append('client_id', this.configService.get('CLIENT_ID') || '');
|
||||||
|
params.append('client_secret', this.configService.get('CLIENT_SECRET') || '');
|
||||||
|
params.append('refresh_token', refreshToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(url, params, {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('refresh token response...');
|
||||||
|
console.log(response.data);
|
||||||
|
|
||||||
|
return response.data; // tokens and user info
|
||||||
|
|
||||||
|
// console.log(response.data);
|
||||||
|
|
||||||
|
// return { message: "tokens refreshed successfully...." }
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error.message);
|
||||||
|
throw new UnauthorizedException('Authentication failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
19
src/exceptions/conflict.exception.ts
Normal file
19
src/exceptions/conflict.exception.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||||
|
|
||||||
|
export class ConflictException extends HttpException {
|
||||||
|
constructor(
|
||||||
|
message = 'CONFLICT',
|
||||||
|
errorCode = 'INTERNAL_ERROR',
|
||||||
|
data: any = null,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
statusCode: HttpStatus.CONFLICT,
|
||||||
|
message,
|
||||||
|
// errorCode,
|
||||||
|
// data,
|
||||||
|
},
|
||||||
|
HttpStatus.CONFLICT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/exceptions/unauthorized.exception.ts
Normal file
19
src/exceptions/unauthorized.exception.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||||
|
|
||||||
|
export class UnauthorizedException extends HttpException {
|
||||||
|
constructor(
|
||||||
|
message = 'Unauthorized',
|
||||||
|
errorCode = 'INTERNAL_ERROR',
|
||||||
|
data: any = null,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
{
|
||||||
|
statusCode: HttpStatus.UNAUTHORIZED,
|
||||||
|
message,
|
||||||
|
// errorCode,
|
||||||
|
// data,
|
||||||
|
},
|
||||||
|
HttpStatus.UNAUTHORIZED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/guards/jwt-auth.guard.ts
Normal file
46
src/guards/jwt-auth.guard.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
UnauthorizedException,
|
||||||
|
Global,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AuthService } from 'src/auth/auth.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Injectable()
|
||||||
|
export class JwtAuthGuard implements CanActivate {
|
||||||
|
constructor(private readonly authService: AuthService) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
|
||||||
|
// Read token from cookie
|
||||||
|
console.log('------------------------------');
|
||||||
|
let g = await request.cookies
|
||||||
|
console.log("g : ",g);
|
||||||
|
console.log(' guard cookies ------------------------------');
|
||||||
|
|
||||||
|
const token = request.cookies?.access_token;
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
console.log('error from inspect token .....');
|
||||||
|
|
||||||
|
throw new UnauthorizedException('Authentication failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decoded = await this.authService.decodeToken(token);
|
||||||
|
request.user = decoded;
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err.message);
|
||||||
|
|
||||||
|
throw new UnauthorizedException({
|
||||||
|
message: 'Invalid Token',
|
||||||
|
error: 'Unauthorized',
|
||||||
|
statusCode: 401,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/guards/register.guard.ts
Normal file
53
src/guards/register.guard.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// jwt-auth.guard.ts
|
||||||
|
import {
|
||||||
|
CanActivate,
|
||||||
|
ExecutionContext,
|
||||||
|
Injectable,
|
||||||
|
UnauthorizedException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { Request } from 'express';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RegisterGuard implements CanActivate {
|
||||||
|
constructor(private readonly jwtService: JwtService) {}
|
||||||
|
|
||||||
|
canActivate(context: ExecutionContext): boolean {
|
||||||
|
const request = context.switchToHttp().getRequest<Request>();
|
||||||
|
const authHeader = request.headers['authorization'];
|
||||||
|
|
||||||
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||||
|
throw new UnauthorizedException('Authorization header missing or malformed');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = authHeader.split(' ')[1];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Decode without verifying to check expiry
|
||||||
|
const decoded: any = this.jwtService.decode(token);
|
||||||
|
|
||||||
|
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;
|
||||||
|
} catch (err) {
|
||||||
|
throw new UnauthorizedException('Invalid or expired token');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/interceptors/logout.interceptor.ts
Normal file
30
src/interceptors/logout.interceptor.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// refresh-token.interceptor.ts
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
ExecutionContext,
|
||||||
|
CallHandler,
|
||||||
|
BadRequestException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LogoutInterceptor implements NestInterceptor {
|
||||||
|
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
|
||||||
|
const refreshToken = request.cookies?.refresh_token;
|
||||||
|
|
||||||
|
if (!refreshToken) {
|
||||||
|
throw new BadRequestException('Refresh token not found in cookies');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach refresh token to request object for later use
|
||||||
|
request.user = {
|
||||||
|
...(request.user || {}),
|
||||||
|
refreshToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
return next.handle();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/mail/mail.module.ts
Normal file
18
src/mail/mail.module.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { MailerModule } from '@nestjs-modules/mailer';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
MailerModule.forRoot({
|
||||||
|
transport: {
|
||||||
|
host: 'localhost',
|
||||||
|
port: 1025,
|
||||||
|
ignoreTLS: true,
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
from: '"No Reply" <no-reply@example.com>',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class MailModule { }
|
||||||
Loading…
x
Reference in New Issue
Block a user