From 7f41fbb2fc62a7101ae53af477019bd98d633b28 Mon Sep 17 00:00:00 2001 From: sumitbajaj-tarento Date: Mon, 9 Jun 2025 14:15:36 +0530 Subject: [PATCH 1/8] Update publicApiV8.ts --- src/publicApi_v8/publicApiV8.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/publicApi_v8/publicApiV8.ts b/src/publicApi_v8/publicApiV8.ts index 27b2b022..d4a74eab 100644 --- a/src/publicApi_v8/publicApiV8.ts +++ b/src/publicApi_v8/publicApiV8.ts @@ -29,6 +29,7 @@ import { maternityFoundationAuth } from './maternityFoundationAuth' import { publicReadForm } from './publicReadForm' import { ssoLogin } from './ssoLogin' import { tnaiAuth } from './tnaiAuth' +import { tnnmcAuth } from './tnnmcAuth' import { publicTnc } from './tnc' import { upsmfUserCreation } from './upsmfUser' import { deactivateUser } from './userDeactivation' @@ -78,6 +79,7 @@ publicApiV8.use('/deactivateUser', deactivateUser) publicApiV8.use('/testUserOtp', userOtp) publicApiV8.use('/ssoLogin', ssoLogin) publicApiV8.use('/tnai', tnaiAuth) +publicApiV8.use('/tnnmc', tnnmcAuth) publicApiV8.use('/bnrcUserCreation', bnrcUserCreation) publicApiV8.use('/courseRecommendation', courseRecommendation) publicApiV8.use('/ratingsSearch', ratingsSearch) From 2de3eb016bcd6b1426278fc4c46c0d34ac62913d Mon Sep 17 00:00:00 2001 From: sumitbajaj-tarento Date: Mon, 9 Jun 2025 14:16:19 +0530 Subject: [PATCH 2/8] Create tnnmcAuth.ts --- src/publicApi_v8/tnnmcAuth.ts | 281 ++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/publicApi_v8/tnnmcAuth.ts diff --git a/src/publicApi_v8/tnnmcAuth.ts b/src/publicApi_v8/tnnmcAuth.ts new file mode 100644 index 00000000..b9fa9d93 --- /dev/null +++ b/src/publicApi_v8/tnnmcAuth.ts @@ -0,0 +1,281 @@ +import axios from 'axios' +import crypto from 'crypto' +import express, { Response } from 'express' +import jwt_decode from 'jwt-decode' +import _ from 'lodash' +import qs from 'querystring' +import { axiosRequestConfig } from '../configs/request.config' +import { CONSTANTS } from '../utils/env' +import { logError, logInfo } from '../utils/logger' +import { generateRandomPassword } from '../utils/randomPasswordGenerator' +import { getCurrentUserRoles } from './rolePermission' + +const AUTH_FAIL = + 'Authentication failed ! Please check credentials and try again.' +const API_END_POINTS = { + createUser: `${CONSTANTS.KONG_API_BASE}/user/v3/create`, + fetchUserByEmail: `${CONSTANTS.KONG_API_BASE}/user/v1/exists/email/`, + fetchUserByMobileNo: `${CONSTANTS.KONG_API_BASE}/user/v1/exists/phone/`, + generateToken: `${CONSTANTS.HTTPS_HOST}/auth/realms/sunbird/protocol/openid-connect/token`, + profileUpdate: `${CONSTANTS.SUNBIRD_PROXY_API_BASE}/user/private/v1/update`, + tnnmcUserDetailsUrl: + CONSTANTS.TNNMC_USER_DETAILS_URL, + userRoles: `${CONSTANTS.SUNBIRD_PROXY_API_BASE}/user/private/v1/assign/role`, +} + +const tnnmcApiKey=CONSTANTS.TNNMC_API_KEY +const tnmcApiSecret=CONSTANTS.TNNMC_API_SECRET + +// Signature generation function +function generateSignature(data, secret) { + return crypto + .createHmac('sha256', secret) + .update(data) + .digest('hex'); +} +export const tnnmcAuth = express.Router() +// Endpoint to create TNAI foundation SSO +// tslint:disable-next-line: no-any +tnnmcAuth.post('/login', async (req: any, res: Response) => { + logInfo('Entered into tnnmc route') + try { + const tnnmcAccessToken = decodeURIComponent(req.body.token) + const requestTime = new Date().toISOString(); // ISO 8601 UTC + const verb = 'POST'; + const uri = 'IsValidUser'; // Used for signature generation only + const stringToSign = `${requestTime}${verb}${uri}`; + const signature = generateSignature(stringToSign, tnmcApiSecret); + logInfo('Signature generated:', signature); + logInfo('Request Time:', requestTime); + const headers = { + 'Content-Type': 'application/json', + 'Request-Time': requestTime, + APIKey: tnnmcApiKey, + Signature: signature, + 'Cache-Control': 'no-cache' + }; + const body = { + Token: tnnmcAccessToken + }; + let userDetailResponseFromTnnmc + // Validating user details from TNNMC endpoints + try { + userDetailResponseFromTnnmc = await axios.post( + API_END_POINTS.tnnmcUserDetailsUrl, + body, + { headers } + ); + logInfo('User details from TNNMC', JSON.stringify(userDetailResponseFromTnnmc.data)) + if (userDetailResponseFromTnnmc.data.success != true) { + return res.status(400).json({ + msg: 'Token invalid or User not present in TNNMC', + status: 'error', + status_code: 400, + }) + } + } catch (error) { + logInfo('Error details from TNNMC', JSON.stringify(error)) + return res.status(400).json({ + msg: 'Issued occured while fetching user details from TNNMC', + status: 'error', + status_code: 400, + }) + } + + const tnnmcUserData = + userDetailResponseFromTnnmc.data.data + const tnnmcUserEmail = tnnmcUserData.email || "" + const tnnmcUserPhone = tnnmcUserData.mobile || "" + logInfo("tnnmcuseremail", tnnmcUserEmail, "tnnmcuserphone", tnnmcUserPhone) + const typeOfLogin = tnnmcUserEmail ? 'email' : 'phone' + const tnnmcLoginType = tnnmcUserEmail ? 'email' : 'mobile' + logInfo( + 'Type of login and tnnmcLoginType', + typeOfLogin, + tnnmcLoginType + ) + logInfo('User details from tnai', JSON.stringify(tnnmcUserData)) + const resultEmail = await fetchUserBymobileorEmail( + tnnmcUserEmail, + 'email' + ) + logInfo(resultEmail, 'resultemail') + const resultPhone = await fetchUserBymobileorEmail( + tnnmcUserPhone, + 'phone' + ) + logInfo(resultPhone, 'resultPhone') + if (!resultEmail && !resultPhone) { + logInfo("User doesn't exists user creation process begins") + const randomPassword = generateRandomPassword(8, { + digits: true, + lowercase: true, + symbols: true, + uppercase: true, + }) + const trimmedName = tnnmcUserData.name.trim(); + const parts = trimmedName.split(' '); + let firstName: string, lastName: string + + if (parts.length > 1) { + firstName = parts[0]; + lastName = parts.slice(1).join(' '); // Handles middle names too + } else { + firstName = lastName = trimmedName; + } + logInfo('First Name:', firstName, 'Last Name:', lastName) + const responseCreateUser = await axios({ + ...axiosRequestConfig, + data: { + request: { + channel: 'Tamil nadu Nurses & Midwives Council', + firstName, + lastName, + password: randomPassword, + [typeOfLogin]: tnnmcUserData[tnnmcLoginType], + tcStatus: false, + }, + }, + headers: { + Authorization: CONSTANTS.SB_API_KEY, + }, + method: 'POST', + url: API_END_POINTS.createUser, + }) + logInfo('Response after user creation', responseCreateUser.data) + const userRoleUpdate = await axios({ + ...axiosRequestConfig, + data: { + request: { + organisationId: '01432740157737369679', + roles: ['PUBLIC'], + userId: responseCreateUser.data.result.userId, + }, + }, + headers: { Authorization: CONSTANTS.SB_API_KEY }, + method: 'POST', + url: API_END_POINTS.userRoles, + }) + logInfo('Data after role update', userRoleUpdate.data) + const userProfileUpdate = await axios({ + ...axiosRequestConfig, + data: { + request: { + profileDetails: { + preferences: { + language: 'en', + }, + profileReq: { + academics: [ + { + nameOfInstitute: '', + nameOfQualification: '', + type: 'GRADUATE', + yearOfPassing: '', + }, + ], + id: responseCreateUser.data.result.userId, + personalDetails: { + email: tnnmcUserEmail, + phone: tnnmcUserPhone, + firstname: firstName, + surname: lastName, + tnncno: tnnmcUserData.tnncno, + tnnmcGender:tnnmcUserData.gender, + tnnmcCategory: tnnmcUserData.category, + }, + userId: responseCreateUser.data.result.userId, + }, + }, + userId: responseCreateUser.data.result.userId, + }, + }, + headers: { Authorization: CONSTANTS.SB_API_KEY }, + method: 'PATCH', + url: API_END_POINTS.profileUpdate, + }) + logInfo('Data after profile update', userProfileUpdate.data) + } + const encodedData = qs.stringify({ + client_id: 'TNNMC', + client_secret: CONSTANTS.KEYCLOAK_CLIENT_SECRET_TNNMC, + grant_type: 'password', + scope: 'offline_access', + username: tnnmcUserPhone || tnnmcUserEmail, + }) + logInfo('Entered into authorization part.' + encodedData) + + const authTokenResponse = await axios({ + ...axiosRequestConfig, + data: encodedData, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + method: 'POST', + url: API_END_POINTS.generateToken, + }) + if (authTokenResponse.data) { + const accessToken = authTokenResponse.data.access_token + // tslint:disable-next-line: no-any + const decodedToken: any = jwt_decode(accessToken) + const decodedTokenArray = decodedToken.sub.split(':') + const userId = decodedTokenArray[decodedTokenArray.length - 1] + req.session.userId = userId + logInfo(userId, 'userid......................') + req.kauth = { + grant: { + access_token: { content: decodedToken, token: accessToken }, + }, + } + req.session.grant = { + access_token: { content: decodedToken, token: accessToken }, + } + logInfo('Success ! Entered into usertokenResponse..') + await getCurrentUserRoles(req, accessToken) + } else { + res.status(302).json({ + msg: AUTH_FAIL, + status: 'error', + }) + } + } catch (err) { + logError('Failed to process callback API.. error: ' + JSON.stringify(err)) + return res.status(400).json({ + msg: AUTH_FAIL, + message: 'error', + }) + } + res.status(200).json({ + message: 'success', + }) +}) + +const fetchUserBymobileorEmail = async ( + searchValue: string, + searchType: string +) => { + try { + const response = await axios({ + ...axiosRequestConfig, + headers: { + Authorization: CONSTANTS.SB_API_KEY, + }, + method: 'GET', + url: + searchType === 'email' + ? API_END_POINTS.fetchUserByEmail + searchValue + : API_END_POINTS.fetchUserByMobileNo + searchValue, + }) + logInfo('Response Data in JSON :', JSON.stringify(response.data)) + logInfo('Response Data in Success :', response.data.responseCode) + if (response.data.responseCode === 'OK') { + logInfo( + 'Response result.exists :', + _.get(response, 'data.result.exists') + ) + return _.get(response, 'data.result.exists') + } + } catch (err) { + logError('fetchUserByMobile failed') + } +} From e4fdb2686cc42aa6a376176f43f0ba2e52aa4b2c Mon Sep 17 00:00:00 2001 From: amantarento Date: Tue, 10 Jun 2025 15:12:34 +0530 Subject: [PATCH 3/8] lint fixes --- src/publicApi_v8/tnnmcAuth.ts | 50 ++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/publicApi_v8/tnnmcAuth.ts b/src/publicApi_v8/tnnmcAuth.ts index b9fa9d93..99c66e86 100644 --- a/src/publicApi_v8/tnnmcAuth.ts +++ b/src/publicApi_v8/tnnmcAuth.ts @@ -1,3 +1,5 @@ +/* tslint:disable */ +/* tslint:disable:no-console no-any function-length */ import axios from 'axios' import crypto from 'crypto' import express, { Response } from 'express' @@ -23,15 +25,15 @@ const API_END_POINTS = { userRoles: `${CONSTANTS.SUNBIRD_PROXY_API_BASE}/user/private/v1/assign/role`, } -const tnnmcApiKey=CONSTANTS.TNNMC_API_KEY -const tnmcApiSecret=CONSTANTS.TNNMC_API_SECRET +const tnnmcApiKey = CONSTANTS.TNNMC_API_KEY +const tnmcApiSecret = CONSTANTS.TNNMC_API_SECRET // Signature generation function function generateSignature(data, secret) { return crypto .createHmac('sha256', secret) .update(data) - .digest('hex'); + .digest('hex') } export const tnnmcAuth = express.Router() // Endpoint to create TNAI foundation SSO @@ -40,23 +42,23 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { logInfo('Entered into tnnmc route') try { const tnnmcAccessToken = decodeURIComponent(req.body.token) - const requestTime = new Date().toISOString(); // ISO 8601 UTC - const verb = 'POST'; - const uri = 'IsValidUser'; // Used for signature generation only - const stringToSign = `${requestTime}${verb}${uri}`; - const signature = generateSignature(stringToSign, tnmcApiSecret); - logInfo('Signature generated:', signature); - logInfo('Request Time:', requestTime); + const requestTime = new Date().toISOString() // ISO 8601 UTC + const verb = 'POST' + const uri = 'IsValidUser' // Used for signature generation only + const stringToSign = `${requestTime}${verb}${uri}` + const signature = generateSignature(stringToSign, tnmcApiSecret) + logInfo('Signature generated:', signature) + logInfo('Request Time:', requestTime) const headers = { 'Content-Type': 'application/json', 'Request-Time': requestTime, APIKey: tnnmcApiKey, Signature: signature, - 'Cache-Control': 'no-cache' - }; + 'Cache-Control': 'no-cache', + } const body = { - Token: tnnmcAccessToken - }; + Token: tnnmcAccessToken, + } let userDetailResponseFromTnnmc // Validating user details from TNNMC endpoints try { @@ -64,7 +66,7 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { API_END_POINTS.tnnmcUserDetailsUrl, body, { headers } - ); + ) logInfo('User details from TNNMC', JSON.stringify(userDetailResponseFromTnnmc.data)) if (userDetailResponseFromTnnmc.data.success != true) { return res.status(400).json({ @@ -84,9 +86,9 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { const tnnmcUserData = userDetailResponseFromTnnmc.data.data - const tnnmcUserEmail = tnnmcUserData.email || "" - const tnnmcUserPhone = tnnmcUserData.mobile || "" - logInfo("tnnmcuseremail", tnnmcUserEmail, "tnnmcuserphone", tnnmcUserPhone) + const tnnmcUserEmail = tnnmcUserData.email || '' + const tnnmcUserPhone = tnnmcUserData.mobile || '' + logInfo('tnnmcuseremail', tnnmcUserEmail, 'tnnmcuserphone', tnnmcUserPhone) const typeOfLogin = tnnmcUserEmail ? 'email' : 'phone' const tnnmcLoginType = tnnmcUserEmail ? 'email' : 'mobile' logInfo( @@ -113,15 +115,15 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { symbols: true, uppercase: true, }) - const trimmedName = tnnmcUserData.name.trim(); - const parts = trimmedName.split(' '); + const trimmedName = tnnmcUserData.name.trim() + const parts = trimmedName.split(' ') let firstName: string, lastName: string if (parts.length > 1) { - firstName = parts[0]; - lastName = parts.slice(1).join(' '); // Handles middle names too + firstName = parts[0] + lastName = parts.slice(1).join(' ') // Handles middle names too } else { - firstName = lastName = trimmedName; + firstName = lastName = trimmedName } logInfo('First Name:', firstName, 'Last Name:', lastName) const responseCreateUser = await axios({ @@ -181,7 +183,7 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { firstname: firstName, surname: lastName, tnncno: tnnmcUserData.tnncno, - tnnmcGender:tnnmcUserData.gender, + tnnmcGender: tnnmcUserData.gender, tnnmcCategory: tnnmcUserData.category, }, userId: responseCreateUser.data.result.userId, From e1b4ce4ed4e18298c3d5cfdcbe4963cd2dbc785c Mon Sep 17 00:00:00 2001 From: likhithgowda Date: Tue, 10 Jun 2025 16:21:06 +0530 Subject: [PATCH 4/8] constants added --- src/utils/env.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/utils/env.ts b/src/utils/env.ts index a26689ed..10637fc1 100644 --- a/src/utils/env.ts +++ b/src/utils/env.ts @@ -83,7 +83,10 @@ export const CONSTANTS = { KEYCLOAK_SESSION_TTL: 30 * 24 * 60 * 60 * 1000, KHUB_CLIENT_SECRET: env.KHUB_CLIENT_SECRET || 'axc123', KHUB_GRAPH_DATA: env.KHUB_GRAPH_DATA || 'http://localhost:3016', - + TNNMC_USER_DETAILS_URL: env.TNNMC_USER_DETAILS_URL || '', + TNNMC_API_KEY: env.TNNMC_API_KEY || '', + TNNMC_API_SECRET: env.TNNMC_API_SECRET || '', + KEYCLOAK_CLIENT_SECRET_TNNMC: env.KEYCLOAK_CLIENT_SECRET_TNNMC || '', KHUB_SEARCH_BASE: env.KHUB_SEARCH_BASE || 'http://localhost:3014', KNOWLEDGE_MW_API_BASE: env.KNOWLEDGE_MW_API_BASE || 'http://knowledge-mw-service:5000', From f825d6f81c2f115541cb1a84f93362f76e431fe2 Mon Sep 17 00:00:00 2001 From: sumitbajaj-tarento Date: Tue, 10 Jun 2025 20:56:02 +0530 Subject: [PATCH 5/8] Update tnnmcAuth.ts --- src/publicApi_v8/tnnmcAuth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/publicApi_v8/tnnmcAuth.ts b/src/publicApi_v8/tnnmcAuth.ts index 99c66e86..4e6a2c2e 100644 --- a/src/publicApi_v8/tnnmcAuth.ts +++ b/src/publicApi_v8/tnnmcAuth.ts @@ -203,7 +203,7 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { client_secret: CONSTANTS.KEYCLOAK_CLIENT_SECRET_TNNMC, grant_type: 'password', scope: 'offline_access', - username: tnnmcUserPhone || tnnmcUserEmail, + username: tnnmcUserEmail || tnnmcUserPhone, }) logInfo('Entered into authorization part.' + encodedData) From 9e1597d382101cf2d28cdeec4a5568c137ab1063 Mon Sep 17 00:00:00 2001 From: likhithgowda Date: Wed, 11 Jun 2025 11:22:36 +0530 Subject: [PATCH 6/8] changes in user name --- src/publicApi_v8/tnnmcAuth.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/publicApi_v8/tnnmcAuth.ts b/src/publicApi_v8/tnnmcAuth.ts index 4e6a2c2e..35241f9c 100644 --- a/src/publicApi_v8/tnnmcAuth.ts +++ b/src/publicApi_v8/tnnmcAuth.ts @@ -200,11 +200,12 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { } const encodedData = qs.stringify({ client_id: 'TNNMC', - client_secret: CONSTANTS.KEYCLOAK_CLIENT_SECRET_TNNMC, + client_secret: CONSTANTS.KEYCLOAK_CLIENT_SECRET_TNNMC, grant_type: 'password', scope: 'offline_access', username: tnnmcUserEmail || tnnmcUserPhone, }) + logInfo('Entered into authorization part.' + encodedData) const authTokenResponse = await axios({ From 31569fbc0d342f90d17b4408c30d774235f0dec7 Mon Sep 17 00:00:00 2001 From: likhithgowda Date: Wed, 18 Jun 2025 10:56:23 +0530 Subject: [PATCH 7/8] Added professional details to profile --- src/publicApi_v8/tnnmcAuth.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/publicApi_v8/tnnmcAuth.ts b/src/publicApi_v8/tnnmcAuth.ts index 35241f9c..6b94051c 100644 --- a/src/publicApi_v8/tnnmcAuth.ts +++ b/src/publicApi_v8/tnnmcAuth.ts @@ -27,7 +27,11 @@ const API_END_POINTS = { const tnnmcApiKey = CONSTANTS.TNNMC_API_KEY const tnmcApiSecret = CONSTANTS.TNNMC_API_SECRET - +const getUserDesignationFromRole = { + "RANM": "Registered Auxiliary Nurse Midwife", + "RHV": "Registered Health Visitor", + "RNM": "Registered Nurse Midwife" +} // Signature generation function function generateSignature(data, secret) { return crypto @@ -182,10 +186,18 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { phone: tnnmcUserPhone, firstname: firstName, surname: lastName, - tnncno: tnnmcUserData.tnncno, - tnnmcGender: tnnmcUserData.gender, - tnnmcCategory: tnnmcUserData.category, + regNurseRegMidwifeNumber: tnnmcUserData.tnncno, + gender: tnnmcUserData.gender, + postalAddress: 'India,Tamil Nadu,Chennai', + dob: "01/01/2000" }, + professionalDetails: [ + { + profession: "Healthcare Worker", + designation: getUserDesignationFromRole[tnnmcUserData.category], + orgType: "Public/Government Sector", + } + ], userId: responseCreateUser.data.result.userId, }, }, From 71866b7a46954485e322cfd1fb9bfd8312b0d7cc Mon Sep 17 00:00:00 2001 From: likhithgowda Date: Wed, 18 Jun 2025 15:30:24 +0530 Subject: [PATCH 8/8] fix for email/phone number missing --- src/publicApi_v8/tnnmcAuth.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/publicApi_v8/tnnmcAuth.ts b/src/publicApi_v8/tnnmcAuth.ts index 6b94051c..c938896c 100644 --- a/src/publicApi_v8/tnnmcAuth.ts +++ b/src/publicApi_v8/tnnmcAuth.ts @@ -111,7 +111,9 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { 'phone' ) logInfo(resultPhone, 'resultPhone') + let newAccount = false if (!resultEmail && !resultPhone) { + newAccount = true logInfo("User doesn't exists user creation process begins") const randomPassword = generateRandomPassword(8, { digits: true, @@ -210,12 +212,19 @@ tnnmcAuth.post('/login', async (req: any, res: Response) => { }) logInfo('Data after profile update', userProfileUpdate.data) } + let userData = "" + if (!newAccount) { + userData = resultEmail ? tnnmcUserEmail : tnnmcUserPhone + } else { + logInfo('Using TNNMC user email for userData:', tnnmcUserEmail) + userData = tnnmcUserEmail || tnnmcUserPhone + } const encodedData = qs.stringify({ client_id: 'TNNMC', client_secret: CONSTANTS.KEYCLOAK_CLIENT_SECRET_TNNMC, grant_type: 'password', scope: 'offline_access', - username: tnnmcUserEmail || tnnmcUserPhone, + username: userData }) logInfo('Entered into authorization part.' + encodedData)