import { BadRequestException, Injectable, InternalServerErrorException } from '@nestjs/common'; import { OracleDBService } from 'src/db/db.service'; import { AuthLoginDTO } from './auth.dto'; 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() export class AuthService { private keyCache: Record = {}; constructor( private readonly oracleDBService: OracleDBService, private readonly configService: ConfigService, ) { } 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 USERLOGIN_PKG.ValidateUser(:P_EMAILADDR,:P_PASSWORD,:p_login_cursor); END;`, { P_EMAILADDR: { val: body.P_EMAILADDR, type: oracledb.DB_TYPE_NVARCHAR, }, P_PASSWORD: { val: body.P_PASSWORD, 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); } } } } private async getPublicKey(kid: string): Promise { 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 { 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 { 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 { 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 { 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 { 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'); } } }