2025-06-16 15:53:15 +05:30
|
|
|
import { BadRequestException, Injectable, InternalServerErrorException } from '@nestjs/common';
|
2025-02-24 10:32:41 +05:30
|
|
|
import { OracleDBService } from 'src/db/db.service';
|
|
|
|
|
import { AuthLoginDTO } from './auth.dto';
|
|
|
|
|
import * as oracledb from 'oracledb';
|
2025-06-16 15:53:15 +05:30
|
|
|
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';
|
2025-02-24 10:32:41 +05:30
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class AuthService {
|
2025-06-16 15:53:15 +05:30
|
|
|
private keyCache: Record<string, string> = {};
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
private readonly oracleDBService: OracleDBService,
|
|
|
|
|
private readonly configService: ConfigService,
|
|
|
|
|
) { }
|
2025-02-24 10:32:41 +05:30
|
|
|
|
|
|
|
|
async login(body: AuthLoginDTO) {
|
|
|
|
|
let connection;
|
|
|
|
|
let rows = [];
|
|
|
|
|
try {
|
|
|
|
|
connection = await this.oracleDBService.getConnection();
|
|
|
|
|
if (!connection) {
|
|
|
|
|
throw new Error('No DB Connected');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = await connection.execute(
|
|
|
|
|
`BEGIN
|
2025-06-09 16:46:22 +05:30
|
|
|
USERLOGIN_PKG.ValidateUser(:P_EMAILADDR,:P_PASSWORD,:p_login_cursor);
|
2025-02-24 10:32:41 +05:30
|
|
|
END;`,
|
|
|
|
|
{
|
2025-06-09 16:46:22 +05:30
|
|
|
P_EMAILADDR: {
|
|
|
|
|
val: body.P_EMAILADDR,
|
2025-02-24 10:32:41 +05:30
|
|
|
type: oracledb.DB_TYPE_NVARCHAR,
|
|
|
|
|
},
|
2025-06-09 16:46:22 +05:30
|
|
|
P_PASSWORD: {
|
|
|
|
|
val: body.P_PASSWORD,
|
2025-02-24 10:32:41 +05:30
|
|
|
type: oracledb.DB_TYPE_NVARCHAR,
|
|
|
|
|
},
|
|
|
|
|
p_login_cursor: {
|
|
|
|
|
type: oracledb.CURSOR,
|
|
|
|
|
dir: oracledb.BIND_OUT,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
outFormat: oracledb.OUT_FORMAT_OBJECT,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (result.outBinds && result.outBinds.p_login_cursor) {
|
|
|
|
|
const cursor = result.outBinds.p_login_cursor;
|
|
|
|
|
let rowsBatch;
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
rowsBatch = await cursor.getRows(100);
|
|
|
|
|
rows = rows.concat(rowsBatch);
|
|
|
|
|
} while (rowsBatch.length > 0);
|
|
|
|
|
|
|
|
|
|
await cursor.close();
|
|
|
|
|
} else {
|
|
|
|
|
throw new BadRequestException({
|
|
|
|
|
Error: 'Error executing request try after some time!',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rows[0]['ERRORMESG']) {
|
|
|
|
|
throw new BadRequestException({ error: 'Invalid username or password!' });
|
|
|
|
|
}
|
|
|
|
|
return { msg: 'Logged in successfully' };
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new BadRequestException({ error: 'Invalid username or password' });
|
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
if (connection) {
|
|
|
|
|
try {
|
|
|
|
|
await connection.close();
|
|
|
|
|
} catch (closeErr) {
|
|
|
|
|
console.error('Failed to close connection:', closeErr);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-16 15:53:15 +05:30
|
|
|
|
|
|
|
|
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');
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-02-24 10:32:41 +05:30
|
|
|
}
|