From addfec5607548ed013ad0decfb8661f5a4c18d8d Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 16 Jul 2025 17:27:50 +0530 Subject: [PATCH 01/18] fix: taking variable value from env Signed-off-by: bhavanakarwade --- libs/prisma-service/prisma/seed.ts | 800 ++++++++++++++--------------- 1 file changed, 392 insertions(+), 408 deletions(-) diff --git a/libs/prisma-service/prisma/seed.ts b/libs/prisma-service/prisma/seed.ts index 22919fec1..c3983f250 100644 --- a/libs/prisma-service/prisma/seed.ts +++ b/libs/prisma-service/prisma/seed.ts @@ -6,7 +6,7 @@ import { CommonConstants } from '../../common/src/common.constant'; import * as CryptoJS from 'crypto-js'; import { exec } from 'child_process'; import * as util from 'util'; -const execPromise = util.promisify(exec); +const execPromise = util.promisify(exec); const prisma = new PrismaClient(); const logger = new Logger('Init seed DB'); @@ -14,494 +14,478 @@ let platformUserId = ''; const configData = fs.readFileSync(`${process.cwd()}/prisma/data/credebl-master-table.json`, 'utf8'); const createPlatformConfig = async (): Promise => { - try { - const existPlatformAdmin = await prisma.platform_config.findMany(); - - if (0 === existPlatformAdmin.length) { - const { platformConfigData } = JSON.parse(configData); - const platformConfig = await prisma.platform_config.create({ - data: platformConfigData - }); - - logger.log(platformConfig); - } else { - logger.log('Already seeding in platform config'); - } - } catch (error) { - logger.error('An error occurred seeding platformConfig:', error); - throw error; + try { + const existPlatformAdmin = await prisma.platform_config.findMany(); + + if (0 === existPlatformAdmin.length) { + const { platformConfigData } = JSON.parse(configData); + const platformConfig = await prisma.platform_config.create({ + data: platformConfigData + }); + + logger.log(platformConfig); + } else { + logger.log('Already seeding in platform config'); } + } catch (error) { + logger.error('An error occurred seeding platformConfig:', error); + throw error; + } }; const createOrgRoles = async (): Promise => { - try { - const { orgRoleData } = JSON.parse(configData); - const roleNames = orgRoleData.map(role => role.name); - const existOrgRole = await prisma.org_roles.findMany({ - where: { - name: { - in: roleNames - } - } - }); - - if (0 === existOrgRole.length) { - const orgRoles = await prisma.org_roles.createMany({ - data: orgRoleData - }); - - logger.log(orgRoles); - } else { - logger.log('Already seeding in org role'); + try { + const { orgRoleData } = JSON.parse(configData); + const roleNames = orgRoleData.map((role) => role.name); + const existOrgRole = await prisma.org_roles.findMany({ + where: { + name: { + in: roleNames } + } + }); + + if (0 === existOrgRole.length) { + const orgRoles = await prisma.org_roles.createMany({ + data: orgRoleData + }); - } catch (error) { - logger.error('An error occurred seeding orgRoles:', error); - throw error; + logger.log(orgRoles); + } else { + logger.log('Already seeding in org role'); } + } catch (error) { + logger.error('An error occurred seeding orgRoles:', error); + throw error; + } }; const createAgentTypes = async (): Promise => { - try { - const { agentTypeData } = JSON.parse(configData); - - const agentType = agentTypeData.map(agentType => agentType.agent); - const existAgentType = await prisma.agents_type.findMany({ - where: { - agent: { - in: agentType - } - } - }); - - if (0 === existAgentType.length) { - const agentTypes = await prisma.agents_type.createMany({ - data: agentTypeData - }); - - logger.log(agentTypes); - } else { - logger.log('Already seeding in agent type'); + try { + const { agentTypeData } = JSON.parse(configData); + + const agentType = agentTypeData.map((agentType) => agentType.agent); + const existAgentType = await prisma.agents_type.findMany({ + where: { + agent: { + in: agentType } + } + }); + if (0 === existAgentType.length) { + const agentTypes = await prisma.agents_type.createMany({ + data: agentTypeData + }); - } catch (error) { - logger.error('An error occurred seeding agentTypes:', error); - throw error; + logger.log(agentTypes); + } else { + logger.log('Already seeding in agent type'); } + } catch (error) { + logger.error('An error occurred seeding agentTypes:', error); + throw error; + } }; const createOrgAgentTypes = async (): Promise => { - try { - const { orgAgentTypeData } = JSON.parse(configData); - const orgAgentType = orgAgentTypeData.map(orgAgentType => orgAgentType.agent); - const existAgentType = await prisma.org_agents_type.findMany({ - where: { - agent: { - in: orgAgentType - } - } - }); - - if (0 === existAgentType.length) { - const orgAgentTypes = await prisma.org_agents_type.createMany({ - data: orgAgentTypeData - }); - - logger.log(orgAgentTypes); - } else { - logger.log('Already seeding in org agent type'); + try { + const { orgAgentTypeData } = JSON.parse(configData); + const orgAgentType = orgAgentTypeData.map((orgAgentType) => orgAgentType.agent); + const existAgentType = await prisma.org_agents_type.findMany({ + where: { + agent: { + in: orgAgentType } + } + }); + if (0 === existAgentType.length) { + const orgAgentTypes = await prisma.org_agents_type.createMany({ + data: orgAgentTypeData + }); - } catch (error) { - logger.error('An error occurred seeding orgAgentTypes:', error); - throw error; + logger.log(orgAgentTypes); + } else { + logger.log('Already seeding in org agent type'); } + } catch (error) { + logger.error('An error occurred seeding orgAgentTypes:', error); + throw error; + } }; const createPlatformUser = async (): Promise => { - try { - const { platformAdminData } = JSON.parse(configData); - platformAdminData.email = process.env.PLATFORM_ADMIN_EMAIL; - platformAdminData.username = process.env.PLATFORM_ADMIN_EMAIL; - - const existPlatformAdminUser = await prisma.user.findMany({ - where: { - email: platformAdminData.email - } - }); + try { + const { platformAdminData } = JSON.parse(configData); + platformAdminData.email = process.env.PLATFORM_ADMIN_EMAIL; + platformAdminData.username = process.env.PLATFORM_ADMIN_EMAIL; + + const existPlatformAdminUser = await prisma.user.findMany({ + where: { + email: platformAdminData.email + } + }); - if (0 === existPlatformAdminUser.length) { - const platformUser = await prisma.user.create({ - data: platformAdminData - }); + if (0 === existPlatformAdminUser.length) { + const platformUser = await prisma.user.create({ + data: platformAdminData + }); - platformUserId = platformUser.id; + platformUserId = platformUser.id; - logger.log(platformUser); - } else { - logger.log('Already seeding in user'); - } - - } catch (error) { - logger.error('An error occurred seeding platformUser:', error); - throw error; + logger.log(platformUser); + } else { + logger.log('Already seeding in user'); } + } catch (error) { + logger.error('An error occurred seeding platformUser:', error); + throw error; + } }; - const createPlatformOrganization = async (): Promise => { - try { - const { platformAdminOrganizationData } = JSON.parse(configData); - platformAdminOrganizationData.createdBy = platformUserId; - platformAdminOrganizationData.lastChangedBy = platformUserId; - - const existPlatformAdminUser = await prisma.organisation.findMany({ - where: { - name: platformAdminOrganizationData.name - } - }); - - if (0 === existPlatformAdminUser.length) { - const platformOrganization = await prisma.organisation.create({ - data: platformAdminOrganizationData - }); - - logger.log(platformOrganization); - } else { - logger.log('Already seeding in organization'); - } + try { + const { platformAdminOrganizationData } = JSON.parse(configData); + platformAdminOrganizationData.createdBy = platformUserId; + platformAdminOrganizationData.lastChangedBy = platformUserId; + + const existPlatformAdminUser = await prisma.organisation.findMany({ + where: { + name: platformAdminOrganizationData.name + } + }); + + if (0 === existPlatformAdminUser.length) { + const platformOrganization = await prisma.organisation.create({ + data: platformAdminOrganizationData + }); - } catch (error) { - logger.error('An error occurred seeding platformOrganization:', error); - throw error; + logger.log(platformOrganization); + } else { + logger.log('Already seeding in organization'); } + } catch (error) { + logger.error('An error occurred seeding platformOrganization:', error); + throw error; + } }; const createPlatformUserOrgRoles = async (): Promise => { - try { - - const userId = await prisma.user.findUnique({ - where: { - email: `${CommonConstants.PLATFORM_ADMIN_EMAIL}` - } - }); - - const orgId = await prisma.organisation.findFirst({ - where: { - name: `${CommonConstants.PLATFORM_ADMIN_ORG}` - } - }); - - const orgRoleId = await prisma.org_roles.findUnique({ - where: { - name: `${CommonConstants.PLATFORM_ADMIN_ORG_ROLE}` - } - }); - - if (!userId && !orgId && !orgRoleId) { - const platformOrganization = await prisma.user_org_roles.create({ - data: { - userId: userId.id, - orgRoleId: orgRoleId.id, - orgId: orgId.id - } - }); - logger.log(platformOrganization); - } else { - logger.log('Already seeding in org_roles'); - } + try { + const userId = await prisma.user.findUnique({ + where: { + email: `${process.env.PLATFORM_ADMIN_EMAIL}` + } + }); + const orgId = await prisma.organisation.findFirst({ + where: { + name: `${CommonConstants.PLATFORM_ADMIN_ORG}` + } + }); - } catch (error) { - logger.error('An error occurred seeding platformOrganization:', error); - throw error; + const orgRoleId = await prisma.org_roles.findUnique({ + where: { + name: `${CommonConstants.PLATFORM_ADMIN_ORG_ROLE}` + } + }); + + if (!userId && !orgId && !orgRoleId) { + const platformOrganization = await prisma.user_org_roles.create({ + data: { + userId: userId.id, + orgRoleId: orgRoleId.id, + orgId: orgId.id + } + }); + logger.log(platformOrganization); + } else { + logger.log('Already seeding in org_roles'); } + } catch (error) { + logger.error('An error occurred seeding platformOrganization:', error); + throw error; + } }; const createLedger = async (): Promise => { - try { - const { ledgerData } = JSON.parse(configData); - - const existingLedgers = await prisma.ledgers.findMany(); - - if (0 === existingLedgers.length) { - const createLedger = await prisma.ledgers.createMany({ - data: ledgerData - }); - logger.log('All ledgers inserted:', createLedger); - } else { - const updatesNeeded = []; - - if (existingLedgers.length !== ledgerData.length) { - updatesNeeded.push(ledgerData); - if (0 < updatesNeeded.length) { - await prisma.ledgers.deleteMany(); - - const createLedger = await prisma.ledgers.createMany({ - data: ledgerData - }); - logger.log('Updated ledgers:', createLedger); - } else { - logger.log('No changes in ledger data'); - } + try { + const { ledgerData } = JSON.parse(configData); + + const existingLedgers = await prisma.ledgers.findMany(); + + if (0 === existingLedgers.length) { + const createLedger = await prisma.ledgers.createMany({ + data: ledgerData + }); + logger.log('All ledgers inserted:', createLedger); + } else { + const updatesNeeded = []; + + if (existingLedgers.length !== ledgerData.length) { + updatesNeeded.push(ledgerData); + if (0 < updatesNeeded.length) { + await prisma.ledgers.deleteMany(); + + const createLedger = await prisma.ledgers.createMany({ + data: ledgerData + }); + logger.log('Updated ledgers:', createLedger); } else { - logger.log('No changes in ledger data'); + logger.log('No changes in ledger data'); } + } else { + logger.log('No changes in ledger data'); } - } catch (error) { - logger.error('An error occurred seeding createLedger:', error); - throw error; } - }; + } catch (error) { + logger.error('An error occurred seeding createLedger:', error); + throw error; + } +}; const createLedgerConfig = async (): Promise => { - try { - const { ledgerConfig } = JSON.parse(configData); - - const ledgerConfigList = await prisma.ledgerConfig.findMany(); - - const checkDataIsEqual = (ledgerConfig, ledgerConfigList): boolean => { - if (ledgerConfig.length !== ledgerConfigList.length) { - return false; - } - - for (let i = 0; i < ledgerConfig.length; i++) { - const config1 = ledgerConfig[i]; - const config2 = ledgerConfigList.find(item => item.name === config1.name && JSON.stringify(item.details) === JSON.stringify(config1.details)); - - if (!config2) { - return false; - } - } - return true; - }; - - if (0 === ledgerConfigList.length) { - const configDetails = await prisma.ledgerConfig.createMany({ - data: ledgerConfig - }); - logger.log('Ledger config created:', configDetails); - - } else if (!checkDataIsEqual(ledgerConfig, ledgerConfigList)) { - await prisma.ledgerConfig.deleteMany({}); - const configDetails = await prisma.ledgerConfig.createMany({ - data: ledgerConfig - }); - logger.log('Existing ledger config deleted and new ones created:', configDetails); - } else { - logger.log('Already seeding in ledger config'); + try { + const { ledgerConfig } = JSON.parse(configData); + + const ledgerConfigList = await prisma.ledgerConfig.findMany(); + + const checkDataIsEqual = (ledgerConfig, ledgerConfigList): boolean => { + if (ledgerConfig.length !== ledgerConfigList.length) { + return false; + } + + for (let i = 0; i < ledgerConfig.length; i++) { + const config1 = ledgerConfig[i]; + const config2 = ledgerConfigList.find( + (item) => item.name === config1.name && JSON.stringify(item.details) === JSON.stringify(config1.details) + ); + + if (!config2) { + return false; } - } catch (error) { - logger.error('An error occurred while configuring ledger:', error); - throw error; + } + return true; + }; + + if (0 === ledgerConfigList.length) { + const configDetails = await prisma.ledgerConfig.createMany({ + data: ledgerConfig + }); + logger.log('Ledger config created:', configDetails); + } else if (!checkDataIsEqual(ledgerConfig, ledgerConfigList)) { + await prisma.ledgerConfig.deleteMany({}); + const configDetails = await prisma.ledgerConfig.createMany({ + data: ledgerConfig + }); + logger.log('Existing ledger config deleted and new ones created:', configDetails); + } else { + logger.log('Already seeding in ledger config'); } + } catch (error) { + logger.error('An error occurred while configuring ledger:', error); + throw error; + } }; const createUserRole = async (): Promise => { - try { - const { userRoleData } = JSON.parse(configData); - - const userRoleDetails = userRoleData.map(userRole => userRole.role); - const existUserRole = await prisma.user_role.findMany({ - where: { - role: { - in: userRoleDetails - } - } - }); - - if (0 === existUserRole.length) { - const userRole = await prisma.user_role.createMany({ - data: userRoleData - }); - - logger.log(userRole); - } else { - logger.log('Already seeding in user role'); + try { + const { userRoleData } = JSON.parse(configData); + + const userRoleDetails = userRoleData.map((userRole) => userRole.role); + const existUserRole = await prisma.user_role.findMany({ + where: { + role: { + in: userRoleDetails } + } + }); + if (0 === existUserRole.length) { + const userRole = await prisma.user_role.createMany({ + data: userRoleData + }); - } catch (error) { - logger.error('An error occurred seeding user role:', error); - throw error; + logger.log(userRole); + } else { + logger.log('Already seeding in user role'); } + } catch (error) { + logger.error('An error occurred seeding user role:', error); + throw error; + } }; const migrateOrgAgentDids = async (): Promise => { - try { - const orgAgents = await prisma.org_agents.findMany({ - where: { - walletName: { - not: 'platform-admin' - } - } - }); - - const orgDids = orgAgents.map((agent) => agent.orgDid).filter((did) => null !== did && '' !== did); - const existingDids = await prisma.org_dids.findMany({ - where: { - did: { - in: orgDids - } - } - }); - - const filteredOrgAgents = orgAgents.filter( - (agent) => null !== agent.orgDid && '' !== agent.orgDid - ); + try { + const orgAgents = await prisma.org_agents.findMany({ + where: { + walletName: { + not: 'platform-admin' + } + } + }); - // If there are org DIDs that do not exist in org_dids table - if (orgDids.length !== existingDids.length) { - const newOrgAgents = filteredOrgAgents.filter( - (agent) => !existingDids.some((did) => did.did === agent.orgDid) - ); - - const newDidRecords = newOrgAgents.map((agent) => ({ - orgId: agent.orgId, - did: agent.orgDid, - didDocument: agent.didDocument, - isPrimaryDid: true, - createdBy: agent.createdBy, - lastChangedBy: agent.lastChangedBy, - orgAgentId: agent.id - })); - - const didInsertResult = await prisma.org_dids.createMany({ - data: newDidRecords - }); - - logger.log(didInsertResult); - } else { - logger.log('No new DIDs to migrate in migrateOrgAgentDids'); + const orgDids = orgAgents.map((agent) => agent.orgDid).filter((did) => null !== did && '' !== did); + const existingDids = await prisma.org_dids.findMany({ + where: { + did: { + in: orgDids } - } catch (error) { - logger.error('An error occurred during migrateOrgAgentDids:', error); - throw error; + } + }); + + const filteredOrgAgents = orgAgents.filter((agent) => null !== agent.orgDid && '' !== agent.orgDid); + + // If there are org DIDs that do not exist in org_dids table + if (orgDids.length !== existingDids.length) { + const newOrgAgents = filteredOrgAgents.filter((agent) => !existingDids.some((did) => did.did === agent.orgDid)); + + const newDidRecords = newOrgAgents.map((agent) => ({ + orgId: agent.orgId, + did: agent.orgDid, + didDocument: agent.didDocument, + isPrimaryDid: true, + createdBy: agent.createdBy, + lastChangedBy: agent.lastChangedBy, + orgAgentId: agent.id + })); + + const didInsertResult = await prisma.org_dids.createMany({ + data: newDidRecords + }); + + logger.log(didInsertResult); + } else { + logger.log('No new DIDs to migrate in migrateOrgAgentDids'); } + } catch (error) { + logger.error('An error occurred during migrateOrgAgentDids:', error); + throw error; + } }; const addSchemaType = async (): Promise => { - try { - const emptyTypeSchemaList = await prisma.schema.findMany({ - where: { - OR: [ - { type: null }, - { type: '' } - ] - } - }); - if (0 < emptyTypeSchemaList.length) { - const updatePromises = emptyTypeSchemaList.map((schema) => prisma.schema.update({ - where: { id: schema.id }, - data: { type: 'indy' } - }) - ); - await Promise.all(updatePromises); - - logger.log('Schemas updated successfully'); - } else { - logger.log('No schemas to update'); - } - } catch (error) { - logger.error('An error occurred during addSchemaType:', error); - throw error; + try { + const emptyTypeSchemaList = await prisma.schema.findMany({ + where: { + OR: [{ type: null }, { type: '' }] + } + }); + if (0 < emptyTypeSchemaList.length) { + const updatePromises = emptyTypeSchemaList.map((schema) => + prisma.schema.update({ + where: { id: schema.id }, + data: { type: 'indy' } + }) + ); + await Promise.all(updatePromises); + + logger.log('Schemas updated successfully'); + } else { + logger.log('No schemas to update'); } + } catch (error) { + logger.error('An error occurred during addSchemaType:', error); + throw error; + } }; const importGeoLocationMasterData = async (): Promise => { - try { - const scriptPath = process.env.GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT; - const dbUrl = process.env.DATABASE_URL; - - if (!scriptPath || !dbUrl) { - throw new Error('Environment variables GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT or DATABASE_URL are not set.'); - } - - const command = `${process.cwd()}/${scriptPath} ${dbUrl}`; - - const { stdout, stderr } = await execPromise(command); - - if (stdout) { - logger.log(`Shell script output: ${stdout}`); - } - if (stderr) { - logger.error(`Shell script error: ${stderr}`); - } - } catch (error) { - logger.error('An error occurred during importGeoLocationMasterData:', error); - throw error; + try { + const scriptPath = process.env.GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT; + const dbUrl = process.env.DATABASE_URL; + + if (!scriptPath || !dbUrl) { + throw new Error('Environment variables GEO_LOCATION_MASTER_DATA_IMPORT_SCRIPT or DATABASE_URL are not set.'); } - }; -const encryptClientCredential = async (clientCredential: string): Promise => { - try { - const encryptedToken = CryptoJS.AES.encrypt(JSON.stringify(clientCredential), process.env.CRYPTO_PRIVATE_KEY).toString(); + const command = `${process.cwd()}/${scriptPath} ${dbUrl}`; + + const { stdout, stderr } = await execPromise(command); - return encryptedToken; - } catch (error) { - logger.error('An error occurred during encryptClientCredential:', error); - throw error; + if (stdout) { + logger.log(`Shell script output: ${stdout}`); + } + if (stderr) { + logger.error(`Shell script error: ${stderr}`); } + } catch (error) { + logger.error('An error occurred during importGeoLocationMasterData:', error); + throw error; + } +}; + +const encryptClientCredential = async (clientCredential: string): Promise => { + try { + const encryptedToken = CryptoJS.AES.encrypt( + JSON.stringify(clientCredential), + process.env.CRYPTO_PRIVATE_KEY + ).toString(); + + return encryptedToken; + } catch (error) { + logger.error('An error occurred during encryptClientCredential:', error); + throw error; + } }; const updateClientCredential = async (): Promise => { - try { - const scriptPath = process.env.UPDATE_CLIENT_CREDENTIAL_SCRIPT; - const dbUrl = process.env.DATABASE_URL; - const clientId = process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID; - const clientSecret = process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET; - - if (!scriptPath || !dbUrl || !clientId || !clientSecret) { - throw new Error('Environment variables UPDATE_CLIENT_CREDENTIAL_SCRIPT or DATABASE_URL or clientId or clientSecret are not set.'); - } - - const encryptedClientId = await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID); - const encryptedClientSecret = await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET); - - const command = `${process.cwd()}/${scriptPath} ${dbUrl} ${encryptedClientId} ${encryptedClientSecret}`; - - const { stdout, stderr } = await execPromise(command); - - if (stdout) { - logger.log(`Shell script output: ${stdout}`); - } - if (stderr) { - logger.error(`Shell script error: ${stderr}`); - } - - } catch (error) { - logger.error('An error occurred during updateClientCredential:', error); - throw error; + try { + const scriptPath = process.env.UPDATE_CLIENT_CREDENTIAL_SCRIPT; + const dbUrl = process.env.DATABASE_URL; + const clientId = process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID; + const clientSecret = process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET; + + if (!scriptPath || !dbUrl || !clientId || !clientSecret) { + throw new Error( + 'Environment variables UPDATE_CLIENT_CREDENTIAL_SCRIPT or DATABASE_URL or clientId or clientSecret are not set.' + ); } -}; + const encryptedClientId = await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID); + const encryptedClientSecret = await encryptClientCredential(process.env.KEYCLOAK_MANAGEMENT_CLIENT_SECRET); -async function main(): Promise { + const command = `${process.cwd()}/${scriptPath} ${dbUrl} ${encryptedClientId} ${encryptedClientSecret}`; + + const { stdout, stderr } = await execPromise(command); + + if (stdout) { + logger.log(`Shell script output: ${stdout}`); + } + if (stderr) { + logger.error(`Shell script error: ${stderr}`); + } + } catch (error) { + logger.error('An error occurred during updateClientCredential:', error); + throw error; + } +}; - await createPlatformConfig(); - await createOrgRoles(); - await createAgentTypes(); - await createPlatformUser(); - await createPlatformOrganization(); - await createPlatformUserOrgRoles(); - await createOrgAgentTypes(); - await createLedger(); - await createLedgerConfig(); - await createUserRole(); - await migrateOrgAgentDids(); - await addSchemaType(); - await importGeoLocationMasterData(); - await updateClientCredential(); +async function main(): Promise { + await createPlatformConfig(); + await createOrgRoles(); + await createAgentTypes(); + await createPlatformUser(); + await createPlatformOrganization(); + await createPlatformUserOrgRoles(); + await createOrgAgentTypes(); + await createLedger(); + await createLedgerConfig(); + await createUserRole(); + await migrateOrgAgentDids(); + await addSchemaType(); + await importGeoLocationMasterData(); + await updateClientCredential(); } main() - .then(async () => { - await prisma.$disconnect(); - }) - .catch(async (error) => { - logger.error(`In prisma seed initialize`, error); - await prisma.$disconnect(); - process.exit(1); - }); \ No newline at end of file + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (error) => { + logger.error(`In prisma seed initialize`, error); + await prisma.$disconnect(); + process.exit(1); + }); From 804c0b95928eee0a3b2281180df3ec073bbb7e89 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 23 Jul 2025 16:14:18 +0530 Subject: [PATCH 02/18] wip: implemented fetch session details api Signed-off-by: bhavanakarwade --- .../api-gateway/src/authz/authz.controller.ts | 34 +++++++ apps/api-gateway/src/authz/authz.service.ts | 6 ++ apps/api-gateway/src/main.ts | 3 +- apps/user/interfaces/user.interface.ts | 97 +++++++++++-------- apps/user/repositories/user.repository.ts | 90 ++++++++++++++++- apps/user/src/user.controller.ts | 6 ++ apps/user/src/user.service.ts | 42 +++++++- libs/common/src/interfaces/user.interface.ts | 1 + libs/common/src/response-messages/index.ts | 1 + libs/enum/src/enum.ts | 10 ++ .../migration.sql | 53 ++++++++++ libs/prisma-service/prisma/schema.prisma | 32 ++++++ package.json | 1 + 13 files changed, 334 insertions(+), 42 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20250718131738_added_session_account_user/migration.sql diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index ef55abec9..fd708d773 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -7,6 +7,7 @@ import { Param, Post, Query, + Req, Res, UnauthorizedException, UseFilters @@ -30,6 +31,7 @@ import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { RefreshTokenDto } from './dtos/refresh-token.dto'; import { getDefaultClient } from '../user/utils'; import { ClientAliasValidationPipe } from './decorators/user-auth-client'; +import { Request } from 'express'; @Controller('auth') @ApiTags('auth') @@ -162,12 +164,44 @@ export class AuthzController { data: userData }; + res.cookie('session_id', userData.sessionId, { + httpOnly: true, + sameSite: 'lax', + secure: false, + maxAge: 30 * 24 * 60 * 60 * 1000 + }); + return res.status(HttpStatus.OK).json(finalResponse); } else { throw new UnauthorizedException(`Please provide valid credentials`); } } + /** + * Fetch session details + * + * @returns User's access token details + */ + @Get('/sessionDetails') + @ApiOperation({ + summary: 'Fetch session details', + description: 'Fetch session details against logged in user' + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) + async sessionDetails(@Req() req: Request, @Res() res: Response): Promise { + const sessionId = req.cookies['session_id']; + + const sessionDetails = await this.authzService.getSession(sessionId); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.fetchSession, + data: sessionDetails + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } + /** * Resets user's password. * diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index 8ee8a5b4d..03188eec3 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -53,6 +53,12 @@ export class AuthzService extends BaseService { return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-holder-login', payload); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async getSession(sessionId: string): Promise { + const payload = { sessionId }; + return this.natsClient.sendNatsMessage(this.authServiceProxy, 'fetch-session-details', payload); + } + async resetPassword(resetPasswordDto: ResetPasswordDto): Promise { return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-reset-password', resetPasswordDto); } diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index 3bbe4b317..aabe4b3a5 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -4,7 +4,7 @@ import * as express from 'express'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { Logger, VERSION_NEUTRAL, VersioningType } from '@nestjs/common'; - +import * as cookieParser from 'cookie-parser'; import { AppModule } from './app.module'; import { HttpAdapterHost, NestFactory, Reflector } from '@nestjs/core'; import { AllExceptionsFilter } from '@credebl/common/exception-handler'; @@ -45,6 +45,7 @@ async function bootstrap(): Promise { expressApp.set('x-powered-by', false); app.use(express.json({ limit: '100mb' })); app.use(express.urlencoded({ limit: '100mb', extended: true })); + app.use(cookieParser()); app.use((req, res, next) => { let err = null; diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index dd6c1d60d..8cdf48a49 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -12,20 +12,20 @@ export interface IUsersProfile { } interface IUserOrgRole { - id: string; - userId: string; - orgRoleId: string; - orgId: string; - orgRole :IOrgRole; - organisation:IOrganisation; -} - export interface IOrgRole{ - id: string; + id: string; + userId: string; + orgRoleId: string; + orgId: string; + orgRole: IOrgRole; + organisation: IOrganisation; +} +export interface IOrgRole { + id: string; name: string; description: string; - }; - export interface IOrganisation{ - id: string; +} +export interface IOrganisation { + id: string; name: string; description: string; orgSlug: string; @@ -106,12 +106,12 @@ export interface ICheckUserDetails { isFidoVerified?: boolean; isRegistrationCompleted?: boolean; userId?: number; - message?:string; + message?: string; } export interface IOrgUsers { - totalPages: number, - users: OrgUser[] + totalPages: number; + users: OrgUser[]; } export interface IDidList { @@ -137,7 +137,7 @@ interface UserOrgRoles { orgId: string; orgRoleId: string; orgRole: OrgRole; - organisation: Organization + organisation: Organization; } interface OrgRole { id: string; @@ -146,22 +146,22 @@ interface OrgRole { } interface Organization { - id: string, - name: string, - description: string, - orgSlug: string, - logoUrl: string, + id: string; + name: string; + description: string; + orgSlug: string; + logoUrl: string; org_agents: OrgAgents[]; } interface OrgAgents { - id: string, - orgDid: string, - walletName: string, - agentSpinUpStatus: number, - agentsTypeId: string, - createDateTime: Date, - orgAgentTypeId:string + id: string; + orgDid: string; + walletName: string; + agentSpinUpStatus: number; + agentsTypeId: string; + createDateTime: Date; + orgAgentTypeId: string; } export interface Payload { @@ -170,29 +170,36 @@ export interface Payload { search: string; } -export interface IVerifyUserEmail{ +export interface IVerifyUserEmail { email: string; verificationCode: string; } -export interface IUserSignIn{ +export interface IUserSignIn { email: string; password: string; isPasskey?: boolean; } -export interface IUserResetPassword{ +export interface ISession { + sessionToken: string; + userId: string; + expires?: number; + refreshToken?: string; +} + +export interface IUserResetPassword { email: string; oldPassword?: string; newPassword?: string; token?: string; password?: string; } -export interface IUserForgotPassword{ +export interface IUserForgotPassword { email: string; - brandLogoUrl?: string, - platformName?: string, - endpoint?: string + brandLogoUrl?: string; + platformName?: string; + endpoint?: string; } export interface IIssueCertificate { courseCode: string; @@ -202,7 +209,7 @@ export interface IIssueCertificate { practicalGradeCredits: string; practicalObtainedEarned: string; } -export interface IPuppeteerOption{ +export interface IPuppeteerOption { width: number; height: number; } @@ -229,7 +236,7 @@ export interface UserRoleMapping { userRoleId: string; } -export interface UserRoleDetails{ +export interface UserRoleDetails { id: string; role: $Enums.UserRole; } @@ -243,4 +250,18 @@ export interface IEcosystemConfig { lastChangedDateTime: Date; lastChangedBy: string; deletedAt: Date | null; -} \ No newline at end of file +} + +export interface IAccountDetails { + userId: string; + type?: string; + provider?: string; + providerAccountId?: string; + refresh_token?: string; + access_token?: string; + expires_at?: string; + scope?: string; + token_type?: string; + id_token?: string; + session_state?: string; +} diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 2e21c4ec8..7d9465b8a 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -1,8 +1,10 @@ /* eslint-disable prefer-destructuring */ import { + IAccountDetails, IOrgUsers, ISendVerificationEmail, + ISession, IShareUserCertificate, IUserDeletedActivity, IUserInformation, @@ -16,7 +18,7 @@ import { } from '../interfaces/user.interface'; import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { RecordType, client_aliases, schema, token, user, user_org_roles } from '@prisma/client'; +import { RecordType, account, client_aliases, schema, session, token, user, user_org_roles } from '@prisma/client'; import { PrismaService } from '@credebl/prisma-service'; import { UserRole } from '@credebl/enum/enum'; @@ -119,6 +121,24 @@ export class UserRepository { } } + /** + * + * @param sessionId + * @returns Session details + */ + async getSession(sessionId: string): Promise { + try { + return this.prisma.session.findUnique({ + where: { + id: sessionId + } + }); + } catch (error) { + this.logger.error(`Not Found: ${JSON.stringify(error)}`); + throw new NotFoundException(error); + } + } + /** * * @param id @@ -638,6 +658,56 @@ export class UserRepository { } } + async createSession(tokenDetails: ISession): Promise { + try { + const { sessionToken, userId, expires, refreshToken } = tokenDetails; + const sessionResponse = await this.prisma.session.upsert({ + where: { + userId + }, + create: { + sessionToken, + userId, + expires, + // eslint-disable-next-line camelcase + refresh_token: refreshToken + }, + update: { + sessionToken, + expires, + // eslint-disable-next-line camelcase + refresh_token: refreshToken + } + }); + return sessionResponse; + } catch (error) { + this.logger.error(`Error in createTokenForResetPassword: ${error.message} `); + throw error; + } + } + + // async updateAccountDetails(accountDetails: ISession): Promise { + // try { + // const userAccountDetails = await this.prisma.account.update({ + // where: { + // userId: accountDetails?.userId + // }, + // data: { + // // eslint-disable-next-line camelcase + // access_token: accountDetails?.sessionToken, + // // eslint-disable-next-line camelcase + // refresh_token: accountDetails?.refreshToken, + // // eslint-disable-next-line camelcase + // expires_at: accountDetails?.expires + // } + // }); + // return userAccountDetails; + // } catch (error) { + // this.logger.error(`Error in createTokenForResetPassword: ${error.message} `); + // throw error; + // } + // } + /** * * @param userId @@ -809,6 +879,24 @@ export class UserRepository { } } + async storeUserAccountDetails(accountDetails: IAccountDetails): Promise { + try { + const userAccountDetails = await this.prisma.account.create({ + data: { + userId: accountDetails?.userId, + provider: accountDetails?.provider, + providerAccountId: accountDetails?.providerAccountId, + // eslint-disable-next-line camelcase + token_type: accountDetails?.token_type + } + }); + return userAccountDetails; + } catch (error) { + this.logger.error(`Error in storing account details: ${error.message} `); + throw error; + } + } + async getUserRole(role: UserRole): Promise { try { const getUserRole = await this.prisma.user_role.findFirstOrThrow({ diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 580de2c4f..8dd8ca2e4 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -77,6 +77,12 @@ export class UserController { return loginRes; } + @MessagePattern({ cmd: 'fetch-session-details' }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async getSession(sessionId: string): Promise { + return this.userService.getSession(sessionId); + } + @MessagePattern({ cmd: 'refresh-token-details' }) async refreshTokenDetails(refreshToken: string): Promise { return this.userService.refreshTokenDetails(refreshToken); diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index e102079de..fd25a24ba 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -46,7 +46,7 @@ import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; import { v4 as uuidv4 } from 'uuid'; -import { Invitation, UserRole } from '@credebl/enum/enum'; +import { Invitation, ProviderType, TokenType, UserRole } from '@credebl/enum/enum'; import validator from 'validator'; import { DISALLOWED_EMAIL_DOMAIN } from '@credebl/common/common.constant'; import { AwsService } from '@credebl/aws'; @@ -378,6 +378,16 @@ export class UserService { const holderOrgRole = await this.orgRoleService.getRole(OrgRoles.HOLDER); await this.userOrgRoleService.createUserOrgRole(userDetails.id, holderOrgRole.id, null, holderRoleData.id); + const userAccountDetails = { + userId: userDetails?.id, + provider: ProviderType.KEYCLOAK, + providerAccountId: keycloakDetails.keycloakUserId.toString(), + // eslint-disable-next-line camelcase + token_type: TokenType.USER_TOKEN + }; + + await this.userRepository.storeUserAccountDetails(userAccountDetails); + return { userId: userDetails?.id }; } catch (error) { this.logger.error(`Error in createUserForToken: ${JSON.stringify(error)}`); @@ -455,7 +465,24 @@ export class UserService { return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); } else { const decryptedPassword = await this.commonService.decryptPassword(password); - return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + const tokenDetails = await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + + const sessionData = { + sessionToken: tokenDetails?.access_token, + userId: userData?.id, + expires: tokenDetails?.expires_in, + refreshToken: tokenDetails?.refresh_token + }; + const addSessionDetails = await this.userRepository.createSession(sessionData); + + // await this.userRepository.updateAccountDetails(sessionData); + + const finalResponse = { + ...tokenDetails, + seesionId: addSessionDetails.id + }; + + return finalResponse; } } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); @@ -463,6 +490,17 @@ export class UserService { } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async getSession(sessionId: string): Promise { + try { + const sessionDetails = await this.userRepository.getSession(sessionId); + return sessionDetails; + } catch (error) { + this.logger.error(`In fetching session details : ${JSON.stringify(error)}`); + throw new RpcException(error.response ? error.response : error); + } + } + async refreshTokenDetails(refreshToken: string): Promise { try { try { diff --git a/libs/common/src/interfaces/user.interface.ts b/libs/common/src/interfaces/user.interface.ts index 900a1a843..041458bff 100644 --- a/libs/common/src/interfaces/user.interface.ts +++ b/libs/common/src/interfaces/user.interface.ts @@ -5,6 +5,7 @@ export interface ISignInUser { expires_at?: number; refresh_token?: string; isRegisteredToSupabase?: boolean; + sessionId?: string; } export interface IVerifyUserEmail { email: string; diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 3205a4a24..47cb5153a 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -6,6 +6,7 @@ export const ResponseMessages = { emaiVerified: 'Email verified successfully', fetchClientAliases: 'Client aliases fetched successfully', login: 'User login successfully', + fetchSession: 'Session details fetched successfully', fetchProfile: 'User fetched successfully', fetchInvitations: 'Org invitations fetched successfully', invitationReject: 'Organization invitation rejected', diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index fa0eba90b..09c7b967c 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -257,3 +257,13 @@ export enum ProofType { POLYGON_PROOFTYPE = 'EcdsaSecp256k1Signature2019', NO_LEDGER_PROOFTYPE = 'Ed25519Signature2018' } + +export enum TokenType { + USER_TOKEN = 'user-token', + ORG_TOKEN = 'organization-token' +} + +export enum ProviderType { + KEYCLOAK = 'keycloak', + SUPABASE = 'supabase' +} diff --git a/libs/prisma-service/prisma/migrations/20250718131738_added_session_account_user/migration.sql b/libs/prisma-service/prisma/migrations/20250718131738_added_session_account_user/migration.sql new file mode 100644 index 000000000..951877c55 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250718131738_added_session_account_user/migration.sql @@ -0,0 +1,53 @@ +-- CreateTable +CREATE TABLE "account" ( + "id" UUID NOT NULL, + "userId" UUID NOT NULL, + "type" TEXT, + "provider" TEXT NOT NULL, + "providerAccountId" TEXT NOT NULL, + "refresh_token" TEXT, + "access_token" TEXT, + "expires_at" INTEGER, + "token_type" TEXT, + "scope" TEXT, + "id_token" TEXT, + "session_state" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "account_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "session" ( + "id" UUID NOT NULL, + "sessionToken" TEXT NOT NULL, + "userId" UUID NOT NULL, + "expires" INTEGER NOT NULL, + "refresh_token" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "session_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "account_refresh_token_key" ON "account"("refresh_token"); + +-- CreateIndex +CREATE UNIQUE INDEX "account_access_token_key" ON "account"("access_token"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_sessionToken_key" ON "session"("sessionToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_userId_key" ON "session"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_refresh_token_key" ON "session"("refresh_token"); + +-- AddForeignKey +ALTER TABLE "account" ADD CONSTRAINT "account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "session" ADD CONSTRAINT "session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 02bcbb91d..d37175da2 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -34,8 +34,40 @@ model user { token token[] user_role_mapping user_role_mapping[] cloud_wallet_user_info cloud_wallet_user_info[] + accounts account[] + sessions session[] +} + +model account { + id String @id @default(uuid()) @db.Uuid + userId String @db.Uuid + type String? + provider String + providerAccountId String + refresh_token String? @unique + access_token String? @unique + expires_at Int? + token_type String? + scope String? + id_token String? + session_state String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + user user @relation(fields: [userId], references: [id]) + } + +model session { + id String @id @default(uuid()) @db.Uuid + sessionToken String @unique + userId String @unique @db.Uuid + expires Int + refresh_token String? @unique + user user @relation(fields: [userId], references: [id]) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } + model token { id String @id @default(uuid()) @db.Uuid token String @unique diff --git a/package.json b/package.json index 3bae65e70..214141f89 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "cache-manager-redis-store": "^2.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "cookie-parser": "^1.4.7", "crypto-js": "^4.1.1", "crypto-random-string": "^5.0.0", "dotenv": "^16.0.3", From 1a183c0e9546a20149cd1c48ef9361dfad5245ac Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 23 Jul 2025 16:29:19 +0530 Subject: [PATCH 03/18] fix: added pnpm file Signed-off-by: bhavanakarwade --- pnpm-lock.yaml | 50 +++++++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 05fd8cd04..3c325bcf7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,10 +16,10 @@ importers: version: 3.1.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(axios@0.26.1)(rxjs@7.8.2) '@nestjs/bull': specifier: ^10.0.1 - version: 10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(bull@4.16.5) + version: 10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(bull@4.16.5) '@nestjs/cache-manager': specifier: ^2.1.0 - version: 2.3.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(cache-manager@5.7.6)(rxjs@7.8.2) + version: 2.3.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(cache-manager@5.7.6)(rxjs@7.8.2) '@nestjs/common': specifier: ^10.2.7 version: 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -46,13 +46,13 @@ importers: version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/websockets@10.4.19)(rxjs@7.8.2) '@nestjs/schedule': specifier: ^3.0.1 - version: 3.0.4(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14) + version: 3.0.4(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14) '@nestjs/swagger': specifier: ^7.1.6 - version: 7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) + version: 7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) '@nestjs/typeorm': specifier: ^10.0.0 - version: 10.0.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.25(ioredis@5.6.1)(pg@8.16.2)(redis@3.1.2)(reflect-metadata@0.1.14)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3))) + version: 10.0.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.25(ioredis@5.6.1)(pg@8.16.2)(redis@3.1.2)(reflect-metadata@0.1.14)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3))) '@nestjs/websockets': specifier: ^10.1.3 version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/platform-socket.io@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -143,6 +143,9 @@ importers: class-validator: specifier: ^0.14.0 version: 0.14.2 + cookie-parser: + specifier: ^1.4.7 + version: 1.4.7 crypto-js: specifier: ^4.1.1 version: 4.2.0 @@ -199,7 +202,7 @@ importers: version: 2.29.3 nestjs-cls: specifier: ^4.3.0 - version: 4.5.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2) + version: 4.5.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) nestjs-rate-limiter: specifier: ^3.1.0 version: 3.1.0 @@ -302,7 +305,7 @@ importers: version: 10.2.3(chokidar@3.6.0)(typescript@5.8.3) '@nestjs/testing': specifier: ^10.1.3 - version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)) + version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19) '@types/express': specifier: ^4.17.17 version: 4.17.23 @@ -395,7 +398,7 @@ importers: version: 10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/testing': specifier: ^10.1.3 - version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)) + version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19) aws-sdk: specifier: ^2.1510.0 version: 2.1692.0 @@ -426,10 +429,10 @@ importers: version: 10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/swagger': specifier: ^7.1.6 - version: 7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) + version: 7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14) '@nestjs/testing': specifier: ^10.1.3 - version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)) + version: 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19) '@prisma/client': specifier: ^5.1.1 version: 5.22.0(prisma@5.22.0) @@ -2402,6 +2405,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-parser@1.4.7: + resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==} + engines: {node: '>= 0.8.0'} + cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -6617,21 +6624,21 @@ snapshots: axios: 0.26.1 rxjs: 7.8.2 - '@nestjs/bull-shared@10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))': + '@nestjs/bull-shared@10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bull@10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(bull@4.16.5)': + '@nestjs/bull@10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(bull@4.16.5)': dependencies: - '@nestjs/bull-shared': 10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)) + '@nestjs/bull-shared': 10.2.3(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19) '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) bull: 4.16.5 tslib: 2.8.1 - '@nestjs/cache-manager@2.3.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(cache-manager@5.7.6)(rxjs@7.8.2)': + '@nestjs/cache-manager@2.3.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(cache-manager@5.7.6)(rxjs@7.8.2)': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -6762,7 +6769,7 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@3.0.4(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)': + '@nestjs/schedule@3.0.4(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -6792,7 +6799,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)': + '@nestjs/swagger@7.4.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)': dependencies: '@microsoft/tsdoc': 0.15.1 '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -6807,7 +6814,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.2 - '@nestjs/testing@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/platform-express@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19))': + '@nestjs/testing@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -6816,7 +6823,7 @@ snapshots: '@nestjs/microservices': 10.4.19(@grpc/grpc-js@1.13.4)(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(@nestjs/websockets@10.4.19)(cache-manager@5.7.6)(ioredis@5.6.1)(nats@2.29.3)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/platform-express': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19) - '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.25(ioredis@5.6.1)(pg@8.16.2)(redis@3.1.2)(reflect-metadata@0.1.14)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)))': + '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2)(typeorm@0.3.25(ioredis@5.6.1)(pg@8.16.2)(redis@3.1.2)(reflect-metadata@0.1.14)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)))': dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) @@ -8439,6 +8446,11 @@ snapshots: convert-source-map@2.0.0: {} + cookie-parser@1.4.7: + dependencies: + cookie: 0.7.2 + cookie-signature: 1.0.6 + cookie-signature@1.0.6: {} cookie@0.5.0: @@ -10845,7 +10857,7 @@ snapshots: neo-async@2.6.2: {} - nestjs-cls@4.5.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2))(reflect-metadata@0.1.14)(rxjs@7.8.2): + nestjs-cls@4.5.0(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/core@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2): dependencies: '@nestjs/common': 10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2) '@nestjs/core': 10.4.19(@nestjs/common@10.4.19(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.1.14)(rxjs@7.8.2))(@nestjs/microservices@10.4.19)(@nestjs/platform-express@10.4.19)(@nestjs/websockets@10.4.19)(reflect-metadata@0.1.14)(rxjs@7.8.2) From 541f603ea28ddf5d26b76d84e63038912c2ae08b Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 24 Jul 2025 11:33:45 +0530 Subject: [PATCH 04/18] wip: fetch session details Signed-off-by: bhavanakarwade --- .../api-gateway/src/authz/authz.controller.ts | 16 +++++++----- apps/api-gateway/src/authz/authz.module.ts | 24 +++++++++-------- .../src/authz/guards/session.guard.ts | 26 +++++++++++++++++++ apps/user/repositories/user.repository.ts | 10 +++++++ apps/user/src/user.controller.ts | 4 +-- apps/user/src/user.service.ts | 2 +- 6 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 apps/api-gateway/src/authz/guards/session.guard.ts diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index fd708d773..197b78180 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -10,7 +10,8 @@ import { Req, Res, UnauthorizedException, - UseFilters + UseFilters, + UseGuards } from '@nestjs/common'; import { AuthzService } from './authz.service'; import { CommonService } from '../../../../libs/common/src/common.service'; @@ -19,7 +20,7 @@ import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Response } from 'express'; +import { Request, Response } from 'express'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; import { AuthTokenResponse } from './dtos/auth-token-res.dto'; import { LoginUserDto } from '../user/dto/login-user.dto'; @@ -31,8 +32,7 @@ import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { RefreshTokenDto } from './dtos/refresh-token.dto'; import { getDefaultClient } from '../user/utils'; import { ClientAliasValidationPipe } from './decorators/user-auth-client'; -import { Request } from 'express'; - +import { SessionGuard } from './guards/session.guard'; @Controller('auth') @ApiTags('auth') @UseFilters(CustomExceptionFilter) @@ -170,7 +170,6 @@ export class AuthzController { secure: false, maxAge: 30 * 24 * 60 * 60 * 1000 }); - return res.status(HttpStatus.OK).json(finalResponse); } else { throw new UnauthorizedException(`Please provide valid credentials`); @@ -187,10 +186,15 @@ export class AuthzController { summary: 'Fetch session details', description: 'Fetch session details against logged in user' }) + @UseGuards(SessionGuard) + // @ApiQuery({ + // name: 'sessionId', + // type: String, + // required: true + // }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) async sessionDetails(@Req() req: Request, @Res() res: Response): Promise { const sessionId = req.cookies['session_id']; - const sessionDetails = await this.authzService.getSession(sessionId); const finalResponse: IResponseType = { diff --git a/apps/api-gateway/src/authz/authz.module.ts b/apps/api-gateway/src/authz/authz.module.ts index 78b862ca0..de7c42222 100644 --- a/apps/api-gateway/src/authz/authz.module.ts +++ b/apps/api-gateway/src/authz/authz.module.ts @@ -1,25 +1,27 @@ import { ClientsModule, Transport } from '@nestjs/microservices'; +import { Logger, Module } from '@nestjs/common'; import { AgentService } from '../agent/agent.service'; import { AuthzController } from './authz.controller'; import { AuthzService } from './authz.service'; +import { CommonConstants } from '@credebl/common/common.constant'; import { CommonModule } from '../../../../libs/common/src/common.module'; import { CommonService } from '../../../../libs/common/src/common.service'; import { ConnectionService } from '../connection/connection.service'; import { HttpModule } from '@nestjs/axios'; import { JwtStrategy } from './jwt.strategy'; import { MobileJwtStrategy } from './mobile-jwt.strategy'; -import { Module } from '@nestjs/common'; +import { NATSClient } from '@credebl/common/NATSClient'; +import { OrganizationService } from '../organization/organization.service'; import { PassportModule } from '@nestjs/passport'; +import { PrismaServiceModule } from '@credebl/prisma-service'; import { SocketGateway } from './socket.gateway'; import { SupabaseService } from '@credebl/supabase'; import { UserModule } from '../user/user.module'; +import { UserRepository } from 'apps/user/repositories/user.repository'; import { UserService } from '../user/user.service'; import { VerificationService } from '../verification/verification.service'; import { getNatsOptions } from '@credebl/common/nats.config'; -import { OrganizationService } from '../organization/organization.service'; -import { CommonConstants } from '@credebl/common/common.constant'; -import { NATSClient } from '@credebl/common/NATSClient'; @Module({ imports: [ @@ -36,7 +38,8 @@ import { NATSClient } from '@credebl/common/NATSClient'; }, CommonModule ]), - UserModule + UserModule, + PrismaServiceModule ], providers: [ JwtStrategy, @@ -50,12 +53,11 @@ import { NATSClient } from '@credebl/common/NATSClient'; CommonService, UserService, SupabaseService, - OrganizationService - ], - exports: [ - PassportModule, - AuthzService + OrganizationService, + UserRepository, + Logger ], + exports: [PassportModule, AuthzService], controllers: [AuthzController] }) -export class AuthzModule { } \ No newline at end of file +export class AuthzModule {} diff --git a/apps/api-gateway/src/authz/guards/session.guard.ts b/apps/api-gateway/src/authz/guards/session.guard.ts new file mode 100644 index 000000000..64812bbb1 --- /dev/null +++ b/apps/api-gateway/src/authz/guards/session.guard.ts @@ -0,0 +1,26 @@ +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; + +import { Request } from 'express'; +import { UserRepository } from 'apps/user/repositories/user.repository'; + +@Injectable() +export class SessionGuard implements CanActivate { + constructor(private userRepository: UserRepository) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const sessionId = request.cookies['session_id']; + if (!sessionId) { + throw new UnauthorizedException('Missing session cookie'); + } + + const user = await this.userRepository.validateSession(sessionId); + + if (!user) { + throw new UnauthorizedException('Invalid session'); + } + + request.user = user; + return true; + } +} diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 7d9465b8a..5f523f3e5 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -139,6 +139,16 @@ export class UserRepository { } } + async validateSession(sessionId: string): Promise { + const session = await this.prisma.session.findUnique({ + where: { id: sessionId }, + include: { user: true } + }); + + // if (!session || new Date() > session.expires) return null; + return session.user; + } + /** * * @param id diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 8dd8ca2e4..e6d20d754 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -79,8 +79,8 @@ export class UserController { @MessagePattern({ cmd: 'fetch-session-details' }) // eslint-disable-next-line @typescript-eslint/no-explicit-any - async getSession(sessionId: string): Promise { - return this.userService.getSession(sessionId); + async getSession(payload: { sessionId: string }): Promise { + return this.userService.getSession(payload?.sessionId); } @MessagePattern({ cmd: 'refresh-token-details' }) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index fd25a24ba..eb9513641 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -479,7 +479,7 @@ export class UserService { const finalResponse = { ...tokenDetails, - seesionId: addSessionDetails.id + sessionId: addSessionDetails.id }; return finalResponse; From ecfac3ddfca4493efb37141bd022d491dae57a5e Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 24 Jul 2025 18:55:50 +0530 Subject: [PATCH 05/18] wip: fetch session details Signed-off-by: bhavanakarwade --- .../api-gateway/src/authz/authz.controller.ts | 28 ++++++------------- apps/api-gateway/src/authz/authz.service.ts | 6 ++-- apps/user/interfaces/user.interface.ts | 6 ++++ apps/user/repositories/user.repository.ts | 14 ++++++++++ apps/user/src/user.controller.ts | 4 +-- apps/user/src/user.service.ts | 11 +++++--- .../migration.sql | 3 ++ libs/prisma-service/prisma/schema.prisma | 2 +- 8 files changed, 45 insertions(+), 29 deletions(-) rename libs/prisma-service/prisma/migrations/{20250718131738_added_session_account_user => 20250724130424_added_session_account}/migration.sql (95%) diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index 197b78180..e3b31ec3c 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -7,11 +7,9 @@ import { Param, Post, Query, - Req, Res, UnauthorizedException, - UseFilters, - UseGuards + UseFilters } from '@nestjs/common'; import { AuthzService } from './authz.service'; import { CommonService } from '../../../../libs/common/src/common.service'; @@ -20,7 +18,7 @@ import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Request, Response } from 'express'; +import { Response } from 'express'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; import { AuthTokenResponse } from './dtos/auth-token-res.dto'; import { LoginUserDto } from '../user/dto/login-user.dto'; @@ -32,7 +30,6 @@ import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { RefreshTokenDto } from './dtos/refresh-token.dto'; import { getDefaultClient } from '../user/utils'; import { ClientAliasValidationPipe } from './decorators/user-auth-client'; -import { SessionGuard } from './guards/session.guard'; @Controller('auth') @ApiTags('auth') @UseFilters(CustomExceptionFilter) @@ -141,6 +138,7 @@ export class AuthzController { }; return res.status(HttpStatus.CREATED).json(finalResponse); } + /** * Authenticates a user and returns an access token. * @@ -164,12 +162,6 @@ export class AuthzController { data: userData }; - res.cookie('session_id', userData.sessionId, { - httpOnly: true, - sameSite: 'lax', - secure: false, - maxAge: 30 * 24 * 60 * 60 * 1000 - }); return res.status(HttpStatus.OK).json(finalResponse); } else { throw new UnauthorizedException(`Please provide valid credentials`); @@ -186,15 +178,13 @@ export class AuthzController { summary: 'Fetch session details', description: 'Fetch session details against logged in user' }) - @UseGuards(SessionGuard) - // @ApiQuery({ - // name: 'sessionId', - // type: String, - // required: true - // }) + @ApiQuery({ + name: 'sessionId', + type: String, + required: true + }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) - async sessionDetails(@Req() req: Request, @Res() res: Response): Promise { - const sessionId = req.cookies['session_id']; + async sessionDetails(@Query() sessionId: string, @Res() res: Response): Promise { const sessionDetails = await this.authzService.getSession(sessionId); const finalResponse: IResponseType = { diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index 03188eec3..d785cf230 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -17,6 +17,7 @@ import { ForgotPasswordDto } from './dtos/forgot-password.dto'; import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { NATSClient } from '@credebl/common/NATSClient'; import { user } from '@prisma/client'; +import { ISessionDetails } from 'apps/user/interfaces/user.interface'; @Injectable() @WebSocketGateway() export class AuthzService extends BaseService { @@ -53,9 +54,8 @@ export class AuthzService extends BaseService { return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-holder-login', payload); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async getSession(sessionId: string): Promise { - const payload = { sessionId }; + async getSession(sessionId): Promise { + const payload = { ...sessionId }; return this.natsClient.sendNatsMessage(this.authServiceProxy, 'fetch-session-details', payload); } diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 8cdf48a49..cf62d8a90 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -188,6 +188,12 @@ export interface ISession { refreshToken?: string; } +export interface ISessionDetails extends ISession { + id: string; + createdAt: Date; + updatedAt: Date; +} + export interface IUserResetPassword { email: string; oldPassword?: string; diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 5f523f3e5..e61760cf0 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -696,6 +696,20 @@ export class UserRepository { } } + async checkAccountDetails(userId: string): Promise { + try { + const accountDetails = await this.prisma.account.findUnique({ + where: { + userId + } + }); + return accountDetails; + } catch (error) { + this.logger.error(`Error in getting account details: ${error.message} `); + throw error; + } + } + // async updateAccountDetails(accountDetails: ISession): Promise { // try { // const userAccountDetails = await this.prisma.account.update({ diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index e6d20d754..4031c2e55 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -1,6 +1,7 @@ import { ICheckUserDetails, IOrgUsers, + ISessionDetails, IUserDeletedActivity, IUserForgotPassword, IUserInformation, @@ -78,8 +79,7 @@ export class UserController { } @MessagePattern({ cmd: 'fetch-session-details' }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async getSession(payload: { sessionId: string }): Promise { + async getSession(payload: { sessionId: string }): Promise { return this.userService.getSession(payload?.sessionId); } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index eb9513641..1d09c4d45 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -39,7 +39,8 @@ import { IUserDeletedActivity, UserKeycloakId, IEcosystemConfig, - IUserForgotPassword + IUserForgotPassword, + ISessionDetails } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; @@ -474,6 +475,8 @@ export class UserService { refreshToken: tokenDetails?.refresh_token }; const addSessionDetails = await this.userRepository.createSession(sessionData); + // const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); + // console.log("🚀 ~ UserService ~ login ~ fetchAccountDetails:", fetchAccountDetails) // await this.userRepository.updateAccountDetails(sessionData); @@ -490,10 +493,10 @@ export class UserService { } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async getSession(sessionId: string): Promise { + async getSession(sessionId: string): Promise { try { - const sessionDetails = await this.userRepository.getSession(sessionId); + const decryptedSessionId = await this.commonService.decryptPassword(sessionId); + const sessionDetails = await this.userRepository.getSession(decryptedSessionId); return sessionDetails; } catch (error) { this.logger.error(`In fetching session details : ${JSON.stringify(error)}`); diff --git a/libs/prisma-service/prisma/migrations/20250718131738_added_session_account_user/migration.sql b/libs/prisma-service/prisma/migrations/20250724130424_added_session_account/migration.sql similarity index 95% rename from libs/prisma-service/prisma/migrations/20250718131738_added_session_account_user/migration.sql rename to libs/prisma-service/prisma/migrations/20250724130424_added_session_account/migration.sql index 951877c55..ddaf77772 100644 --- a/libs/prisma-service/prisma/migrations/20250718131738_added_session_account_user/migration.sql +++ b/libs/prisma-service/prisma/migrations/20250724130424_added_session_account/migration.sql @@ -31,6 +31,9 @@ CREATE TABLE "session" ( CONSTRAINT "session_pkey" PRIMARY KEY ("id") ); +-- CreateIndex +CREATE UNIQUE INDEX "account_userId_key" ON "account"("userId"); + -- CreateIndex CREATE UNIQUE INDEX "account_refresh_token_key" ON "account"("refresh_token"); diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index d37175da2..f848a9d46 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -40,7 +40,7 @@ model user { model account { id String @id @default(uuid()) @db.Uuid - userId String @db.Uuid + userId String @unique @db.Uuid type String? provider String providerAccountId String From ff8aef30fd5e5db329da6576dda8b1ce1d8c80e1 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Fri, 25 Jul 2025 13:38:42 +0530 Subject: [PATCH 06/18] fix: encryption issues Signed-off-by: bhavanakarwade --- apps/user/src/user.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 1d09c4d45..56c95356f 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -495,7 +495,8 @@ export class UserService { async getSession(sessionId: string): Promise { try { - const decryptedSessionId = await this.commonService.decryptPassword(sessionId); + const decodedSessionId = decodeURIComponent(sessionId); + const decryptedSessionId = await this.commonService.decryptPassword(decodedSessionId); const sessionDetails = await this.userRepository.getSession(decryptedSessionId); return sessionDetails; } catch (error) { From b13693246c90d1c736f95488b3c37a0ea1616fdb Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Tue, 29 Jul 2025 14:08:29 +0530 Subject: [PATCH 07/18] fix:Added realtion between session and account table Signed-off-by: shitrerohit --- .../migration.sql | 14 ++++++++++++++ libs/prisma-service/prisma/schema.prisma | 5 ++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 libs/prisma-service/prisma/migrations/20250729083628_reltion_between_account_and_session/migration.sql diff --git a/libs/prisma-service/prisma/migrations/20250729083628_reltion_between_account_and_session/migration.sql b/libs/prisma-service/prisma/migrations/20250729083628_reltion_between_account_and_session/migration.sql new file mode 100644 index 000000000..0e6137796 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250729083628_reltion_between_account_and_session/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - A unique constraint covering the columns `[accountId]` on the table `session` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "session" ADD COLUMN "accountId" UUID; + +-- CreateIndex +CREATE UNIQUE INDEX "session_accountId_key" ON "session"("accountId"); + +-- AddForeignKey +ALTER TABLE "session" ADD CONSTRAINT "session_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "account"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index f848a9d46..0cba419ec 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -43,7 +43,7 @@ model account { userId String @unique @db.Uuid type String? provider String - providerAccountId String + providerAccountId String refresh_token String? @unique access_token String? @unique expires_at Int? @@ -54,6 +54,7 @@ model account { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user user @relation(fields: [userId], references: [id]) + sessions session[] } model session { @@ -65,6 +66,8 @@ model session { user user @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + accountId String? @unique @db.Uuid + account account? @relation(fields: [accountId], references:[id]) } From c4d66f2bc5139da1cc83343fddcf88307094b532 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Tue, 29 Jul 2025 15:39:02 +0530 Subject: [PATCH 08/18] wip: update session Signed-off-by: bhavanakarwade --- apps/user/interfaces/user.interface.ts | 6 +- apps/user/repositories/user.repository.ts | 118 ++++++++++-------- apps/user/src/user.service.ts | 37 +++++- libs/common/src/response-messages/index.ts | 2 + .../migration.sql | 2 + libs/prisma-service/prisma/schema.prisma | 7 +- 6 files changed, 108 insertions(+), 64 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20250729090416_removed_userid_unique_constraint/migration.sql diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index cf62d8a90..d647cfce5 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -182,10 +182,12 @@ export interface IUserSignIn { } export interface ISession { - sessionToken: string; - userId: string; + sessionToken?: string; + userId?: string; expires?: number; refreshToken?: string; + keycloakUserId?: string; + type?: string; } export interface ISessionDetails extends ISession { diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index e61760cf0..b5dfc165e 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -1,7 +1,6 @@ /* eslint-disable prefer-destructuring */ import { - IAccountDetails, IOrgUsers, ISendVerificationEmail, ISession, @@ -17,11 +16,11 @@ import { UserRoleMapping } from '../interfaces/user.interface'; import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; +import { ProviderType, UserRole } from '@credebl/enum/enum'; // eslint-disable-next-line camelcase import { RecordType, account, client_aliases, schema, session, token, user, user_org_roles } from '@prisma/client'; import { PrismaService } from '@credebl/prisma-service'; -import { UserRole } from '@credebl/enum/enum'; interface UserQueryOptions { id?: string; // Use the appropriate type based on your data model @@ -671,27 +670,32 @@ export class UserRepository { async createSession(tokenDetails: ISession): Promise { try { const { sessionToken, userId, expires, refreshToken } = tokenDetails; - const sessionResponse = await this.prisma.session.upsert({ - where: { - userId - }, - create: { - sessionToken, - userId, - expires, - // eslint-disable-next-line camelcase - refresh_token: refreshToken - }, - update: { + const sessionResponse = await this.prisma.session.create({ + data: { sessionToken, expires, + userId, // eslint-disable-next-line camelcase refresh_token: refreshToken } }); return sessionResponse; } catch (error) { - this.logger.error(`Error in createTokenForResetPassword: ${error.message} `); + this.logger.error(`Error in creating session: ${error.message} `); + throw error; + } + } + + async fetchUserSessions(userId: string): Promise { + try { + const userSessionCount = await this.prisma.session.findMany({ + where: { + userId + } + }); + return userSessionCount; + } catch (error) { + this.logger.error(`Error in getting user session details: ${error.message} `); throw error; } } @@ -710,27 +714,51 @@ export class UserRepository { } } - // async updateAccountDetails(accountDetails: ISession): Promise { - // try { - // const userAccountDetails = await this.prisma.account.update({ - // where: { - // userId: accountDetails?.userId - // }, - // data: { - // // eslint-disable-next-line camelcase - // access_token: accountDetails?.sessionToken, - // // eslint-disable-next-line camelcase - // refresh_token: accountDetails?.refreshToken, - // // eslint-disable-next-line camelcase - // expires_at: accountDetails?.expires - // } - // }); - // return userAccountDetails; - // } catch (error) { - // this.logger.error(`Error in createTokenForResetPassword: ${error.message} `); - // throw error; - // } - // } + async updateAccountDetails(accountDetails: ISession): Promise { + try { + const userAccountDetails = await this.prisma.account.update({ + where: { + userId: accountDetails.userId + }, + data: { + // eslint-disable-next-line camelcase + access_token: accountDetails.sessionToken, + // eslint-disable-next-line camelcase + refresh_token: accountDetails.refreshToken, + // eslint-disable-next-line camelcase + expires_at: accountDetails.expires + } + }); + return userAccountDetails; + } catch (error) { + this.logger.error(`Error in updateAccountDetails: ${error.message}`); + throw error; + } + } + + async addAccountDetails(accountDetails: ISession): Promise { + try { + const userAccountDetails = await this.prisma.account.create({ + data: { + userId: accountDetails.userId, + provider: ProviderType.KEYCLOAK, + providerAccountId: accountDetails.keycloakUserId, + // eslint-disable-next-line camelcase + access_token: accountDetails.sessionToken, + // eslint-disable-next-line camelcase + refresh_token: accountDetails.refreshToken, + // eslint-disable-next-line camelcase + expires_at: accountDetails.expires, + // eslint-disable-next-line camelcase + token_type: accountDetails.type + } + }); + return userAccountDetails; + } catch (error) { + this.logger.error(`Error in creating account: ${error.message}`); + throw error; + } + } /** * @@ -903,24 +931,6 @@ export class UserRepository { } } - async storeUserAccountDetails(accountDetails: IAccountDetails): Promise { - try { - const userAccountDetails = await this.prisma.account.create({ - data: { - userId: accountDetails?.userId, - provider: accountDetails?.provider, - providerAccountId: accountDetails?.providerAccountId, - // eslint-disable-next-line camelcase - token_type: accountDetails?.token_type - } - }); - return userAccountDetails; - } catch (error) { - this.logger.error(`Error in storing account details: ${error.message} `); - throw error; - } - } - async getUserRole(role: UserRole): Promise { try { const getUserRole = await this.prisma.user_role.findFirstOrThrow({ diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 56c95356f..0a7f651d3 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -387,7 +387,7 @@ export class UserService { token_type: TokenType.USER_TOKEN }; - await this.userRepository.storeUserAccountDetails(userAccountDetails); + await this.userRepository.addAccountDetails(userAccountDetails); return { userId: userDetails?.id }; } catch (error) { @@ -448,6 +448,13 @@ export class UserService { try { this.validateEmail(email.toLowerCase()); const userData = await this.userRepository.checkUserExist(email.toLowerCase()); + + const userSessionDetails = await this.userRepository.fetchUserSessions(userData?.id); + + if (3 <= userSessionDetails?.length) { + throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); + } + if (!userData) { throw new NotFoundException(ResponseMessages.user.error.notFound); } @@ -474,11 +481,33 @@ export class UserService { expires: tokenDetails?.expires_in, refreshToken: tokenDetails?.refresh_token }; + const addSessionDetails = await this.userRepository.createSession(sessionData); - // const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); - // console.log("🚀 ~ UserService ~ login ~ fetchAccountDetails:", fetchAccountDetails) - // await this.userRepository.updateAccountDetails(sessionData); + const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); + + let accountData; + if (null === fetchAccountDetails) { + accountData = { + sessionToken: tokenDetails?.access_token, + userId: userData?.id, + expires: tokenDetails?.expires_in, + refreshToken: tokenDetails?.refresh_token, + keycloakUserId: userData?.keycloakUserId, + type: TokenType.USER_TOKEN + }; + + await this.userRepository.addAccountDetails(accountData); + } else { + accountData = { + sessionToken: tokenDetails?.access_token, + userId: userData?.id, + expires: tokenDetails?.expires_in, + refreshToken: tokenDetails?.refresh_token + }; + + await this.userRepository.updateAccountDetails(accountData); + } const finalResponse = { ...tokenDetails, diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 47cb5153a..79567a798 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -38,6 +38,8 @@ export const ResponseMessages = { invalidEmailUrl: 'Invalid verification code or EmailId!', verifiedEmail: 'Email already verified', notFound: 'User not found', + sessionLimitReached: + 'You have reached the maximum number of allowed sessions. Please remove an existing session to add a new one', verifyMail: 'Please verify your email', invalidCredentials: 'Invalid Credentials', registerFido: 'Please complete your fido registration', diff --git a/libs/prisma-service/prisma/migrations/20250729090416_removed_userid_unique_constraint/migration.sql b/libs/prisma-service/prisma/migrations/20250729090416_removed_userid_unique_constraint/migration.sql new file mode 100644 index 000000000..5c5bf63c5 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250729090416_removed_userid_unique_constraint/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "session_userId_key"; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 0cba419ec..6023294cb 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -60,17 +60,16 @@ model account { model session { id String @id @default(uuid()) @db.Uuid sessionToken String @unique - userId String @unique @db.Uuid + userId String @db.Uuid expires Int refresh_token String? @unique user user @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - accountId String? @unique @db.Uuid - account account? @relation(fields: [accountId], references:[id]) + accountId String? @unique @db.Uuid + account account? @relation(fields: [accountId], references:[id]) } - model token { id String @id @default(uuid()) @db.Uuid token String @unique From 1b4b699ed636d9e85b1285da7b437646c2a23dc6 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Wed, 30 Jul 2025 14:40:30 +0530 Subject: [PATCH 09/18] fix: Flow modification in generate token using clientId and secrat API Signed-off-by: shitrerohit --- .../api-gateway/src/authz/authz.controller.ts | 30 +- .../src/authz/guards/session.guard.ts | 19 +- .../organization/organization.controller.ts | 7 + .../repositories/organization.repository.ts | 24 +- apps/organization/src/organization.service.ts | 733 ++++++++++-------- apps/user/repositories/user.repository.ts | 3 +- apps/user/src/user.service.ts | 12 +- libs/common/src/interfaces/interface.ts | 1 + 8 files changed, 463 insertions(+), 366 deletions(-) diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index e3b31ec3c..407358d2f 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -7,9 +7,11 @@ import { Param, Post, Query, + Req, Res, UnauthorizedException, - UseFilters + UseFilters, + UseGuards } from '@nestjs/common'; import { AuthzService } from './authz.service'; import { CommonService } from '../../../../libs/common/src/common.service'; @@ -18,7 +20,7 @@ import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; import { ResponseMessages } from '@credebl/common/response-messages'; -import { Response } from 'express'; +import { Response, Request } from 'express'; import { EmailVerificationDto } from '../user/dto/email-verify.dto'; import { AuthTokenResponse } from './dtos/auth-token-res.dto'; import { LoginUserDto } from '../user/dto/login-user.dto'; @@ -30,6 +32,10 @@ import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { RefreshTokenDto } from './dtos/refresh-token.dto'; import { getDefaultClient } from '../user/utils'; import { ClientAliasValidationPipe } from './decorators/user-auth-client'; +import { SessionGuard } from './guards/session.guard'; +interface SessionDetails { + sessionId: string; +} @Controller('auth') @ApiTags('auth') @UseFilters(CustomExceptionFilter) @@ -174,18 +180,30 @@ export class AuthzController { * @returns User's access token details */ @Get('/sessionDetails') + @UseGuards(SessionGuard) @ApiOperation({ summary: 'Fetch session details', description: 'Fetch session details against logged in user' }) @ApiQuery({ name: 'sessionId', - type: String, - required: true + required: false }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) - async sessionDetails(@Query() sessionId: string, @Res() res: Response): Promise { - const sessionDetails = await this.authzService.getSession(sessionId); + async sessionDetails( + @Res() res: Response, + @Req() req: Request, + @Query() sessionId: SessionDetails + ): Promise { + this.logger.debug(`in authz controller`); + + let sessionDetails; + if (0 < Object.keys(sessionId).length) { + sessionDetails = await this.authzService.getSession(sessionId); + } + if (req.user) { + sessionDetails = req.user; + } const finalResponse: IResponseType = { statusCode: HttpStatus.OK, diff --git a/apps/api-gateway/src/authz/guards/session.guard.ts b/apps/api-gateway/src/authz/guards/session.guard.ts index 64812bbb1..fafa1f76e 100644 --- a/apps/api-gateway/src/authz/guards/session.guard.ts +++ b/apps/api-gateway/src/authz/guards/session.guard.ts @@ -1,4 +1,4 @@ -import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Request } from 'express'; import { UserRepository } from 'apps/user/repositories/user.repository'; @@ -10,17 +10,18 @@ export class SessionGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); const sessionId = request.cookies['session_id']; - if (!sessionId) { - throw new UnauthorizedException('Missing session cookie'); - } - - const user = await this.userRepository.validateSession(sessionId); - if (!user) { - throw new UnauthorizedException('Invalid session'); + // if (!sessionId) { + // throw new UnauthorizedException('Missing session cookie'); + // } + if (sessionId) { + const user = await this.userRepository.validateSession(sessionId); + request.user = user; } - request.user = user; + // if (!user) { + // throw new UnauthorizedException('Invalid session'); + // } return true; } } diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 72f012fe4..8257119f9 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -550,11 +550,18 @@ export class OrganizationController { } const orgCredentials = await this.organizationService.clientLoginCredentials(clientCredentialsDto); + const finalResponse: IResponse = { statusCode: HttpStatus.OK, message: ResponseMessages.organisation.success.clientCredentials, data: orgCredentials }; + res.cookie('session_id', orgCredentials.sessionId, { + httpOnly: true, + sameSite: 'lax', + secure: false + }); + return res.status(HttpStatus.OK).json(finalResponse); } /** diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index d5dab6222..ad9d9b964 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -760,7 +760,29 @@ export class OrganizationRepository { throw error; } } - + async getOrgAndAdminUser(orgId: string): Promise { + try { + return this.prisma.user_org_roles.findFirst({ + where: { + orgId + // orgRole:{ + // name:'admin' + // } + }, + include: { + user: { + select: { + id: true, + keycloakUserId: true + } + } + } + }); + } catch (error) { + this.logger.error(`Error in fetch in organization with admin details`); + throw error; + } + } async getCredDefByOrg(orgId: string): Promise< { tag: string; diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index edf05c322..ef507b214 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -27,8 +27,19 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { CreateOrganizationDto } from '../dtos/create-organization.dto'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; -import { DidMethod, Invitation, Ledgers, PrismaTables, transition } from '@credebl/enum/enum'; -import { IGetOrgById, IGetOrganization, IUpdateOrganization, IClientCredentials, ICreateConnectionUrl, IOrgRole, IDidList, IPrimaryDidDetails, IEcosystemOrgStatus, IOrgDetails } from '../interfaces/organization.interface'; +import { DidMethod, Invitation, Ledgers, PrismaTables, TokenType, transition } from '@credebl/enum/enum'; +import { + IGetOrgById, + IGetOrganization, + IUpdateOrganization, + IClientCredentials, + ICreateConnectionUrl, + IOrgRole, + IDidList, + IPrimaryDidDetails, + IEcosystemOrgStatus, + IOrgDetails +} from '../interfaces/organization.interface'; import { UserActivityService } from '@credebl/user-activity'; import { ClientRegistrationService } from '@credebl/client-registration/client-registration.service'; import { map } from 'rxjs/operators'; @@ -52,6 +63,7 @@ import { UserActivityRepository } from 'libs/user-activity/repositories'; import { DeleteOrgInvitationsEmail } from '../templates/delete-organization-invitations.template'; import { IOrgRoles } from 'libs/org-roles/interfaces/org-roles.interface'; import { NATSClient } from '@credebl/common/NATSClient'; +import { UserRepository } from 'apps/user/repositories/user.repository'; @Injectable() export class OrganizationService { constructor( @@ -67,9 +79,10 @@ export class OrganizationService { @Inject(CACHE_MANAGER) private cacheService: Cache, private readonly clientRegistrationService: ClientRegistrationService, private readonly userActivityRepository: UserActivityRepository, - private readonly natsClient : NATSClient + private readonly natsClient: NATSClient, + private readonly userRepository: UserRepository ) {} - + async getPlatformConfigDetails(): Promise { try { const getPlatformDetails = await this.organizationRepository.getPlatformConfigDetails(); @@ -79,7 +92,7 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - + /** * * @param registerOrgDto @@ -93,10 +106,10 @@ export class OrganizationService { keycloakUserId: string ): Promise { try { - const userOrgCount = await this.organizationRepository.userOrganizationCount(userId); - + const userOrgCount = await this.organizationRepository.userOrganizationCount(userId); + if (userOrgCount >= toNumber(`${process.env.MAX_ORG_LIMIT}`)) { - throw new BadRequestException(ResponseMessages.organisation.error.MaximumOrgsLimit); + throw new BadRequestException(ResponseMessages.organisation.error.MaximumOrgsLimit); } const organizationExist = await this.organizationRepository.checkOrganizationNameExist(createOrgDto.name); @@ -111,7 +124,7 @@ export class OrganizationService { if (isOrgSlugExist) { throw new ConflictException(ResponseMessages.organisation.error.exists); - } + } createOrgDto.orgSlug = orgSlug; createOrgDto.createdBy = userId; @@ -124,7 +137,6 @@ export class OrganizationService { createOrgDto.logo = ''; } - const organizationDetails = await this.organizationRepository.createOrganization(createOrgDto); // To return selective object data @@ -148,12 +160,12 @@ export class OrganizationService { clientId, idpId }; - + const updatedOrg = await this.organizationRepository.updateOrganizationById( updateOrgData, organizationDetails.id ); - + if (!updatedOrg) { throw new InternalServerErrorException(ResponseMessages.organisation.error.credentialsNotUpdate); } @@ -180,18 +192,14 @@ export class OrganizationService { } } - /** + /** * * @param registerOrgDto * @returns */ // eslint-disable-next-line camelcase - async setPrimaryDid( - orgId:string, - did:string, - id:string - ): Promise { + async setPrimaryDid(orgId: string, did: string, id: string): Promise { try { const organizationExist = await this.organizationRepository.getOrgProfile(orgId); if (!organizationExist) { @@ -204,7 +212,7 @@ export class OrganizationService { //check user DID exist in the organization's did list const organizationDidList = await this.organizationRepository.getAllOrganizationDid(orgId); - const isDidMatch = organizationDidList.some(item => item.did === did); + const isDidMatch = organizationDidList.some((item) => item.did === did); if (!isDidMatch) { throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); @@ -214,25 +222,25 @@ export class OrganizationService { if (!didDetails) { throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); } - + const dids = await this.organizationRepository.getDids(orgId); - const noPrimaryDid = dids.every(orgDids => false === orgDids.isPrimaryDid); + const noPrimaryDid = dids.every((orgDids) => false === orgDids.isPrimaryDid); let existingPrimaryDid; let priviousDidFalse; if (!noPrimaryDid) { existingPrimaryDid = await this.organizationRepository.getPerviousPrimaryDid(orgId); - + if (!existingPrimaryDid) { throw new NotFoundException(ResponseMessages.organisation.error.didNotFound); } - + priviousDidFalse = await this.organizationRepository.setPreviousDidFlase(existingPrimaryDid.id); - } + } const didParts = did.split(':'); let nameSpace: string | null = null; - + // This condition will handle the multi-ledger support if (DidMethod.INDY === didParts[1]) { nameSpace = `${didParts[2]}:${didParts[3]}`; @@ -264,9 +272,7 @@ export class OrganizationService { await Promise.all([setPrimaryDid, existingPrimaryDid, priviousDidFalse]); - return ResponseMessages.organisation.success.primaryDid; - } catch (error) { this.logger.error(`In setPrimaryDid method: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -290,9 +296,11 @@ export class OrganizationService { let generatedClientSecret = ''; if (organizationDetails.idpId) { - const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); generatedClientSecret = await this.clientRegistrationService.generateClientSecret( organizationDetails.idpId, @@ -303,7 +311,6 @@ export class OrganizationService { clientSecret: this.maskString(generatedClientSecret) }; } else { - try { const orgCredentials = await this.registerToKeycloak( organizationDetails.name, @@ -312,11 +319,11 @@ export class OrganizationService { userId, true ); - + const { clientId, idpId, clientSecret } = orgCredentials; - + generatedClientSecret = clientSecret; - + updateOrgData = { clientId, clientSecret: this.maskString(clientSecret), @@ -359,14 +366,17 @@ export class OrganizationService { shouldUpdateRole: boolean ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); const orgDetails = await this.clientRegistrationService.createClient(orgName, orgId, token); const orgRolesList = [OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER]; - for (const role of orgRolesList) { - await this.clientRegistrationService.createClientRole(orgDetails.idpId, token, role, role); - } + for (const role of orgRolesList) { + await this.clientRegistrationService.createClientRole(orgDetails.idpId, token, role, role); + } const ownerRoleClient = await this.clientRegistrationService.getClientSpecificRoles( orgDetails.idpId, @@ -384,20 +394,18 @@ export class OrganizationService { const ownerRoleData = await this.orgRoleService.getRole(OrgRoles.OWNER); if (!shouldUpdateRole) { - await Promise.all([ this.clientRegistrationService.createUserClientRole(orgDetails.idpId, token, keycloakUserId, payload), this.userOrgRoleService.createUserOrgRole(userId, ownerRoleData.id, orgId, ownerRoleClient.id) ]); - } else { const roleIdList = [ { roleId: ownerRoleData.id, idpRoleId: ownerRoleClient.id } - ]; - + ]; + await Promise.all([ this.clientRegistrationService.createUserClientRole(orgDetails.idpId, token, keycloakUserId, payload), this.userOrgRoleService.deleteOrgRoles(userId, orgId), @@ -509,7 +517,6 @@ export class OrganizationService { // eslint-disable-next-line camelcase async updateOrganization(updateOrgDto: IUpdateOrganization, userId: string, orgId: string): Promise { try { - const organizationExist = await this.organizationRepository.checkOrganizationNameExist(updateOrgDto.name); if (organizationExist && organizationExist.id !== orgId) { @@ -531,13 +538,24 @@ export class OrganizationService { const checkAgentIsExists = await this.organizationRepository.getAgentInvitationDetails(orgId); if (!checkAgentIsExists?.connectionInvitation && !checkAgentIsExists?.agentId) { - organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); - } else if (organizationDetails?.logoUrl !== organizationExist?.logoUrl || organizationDetails?.name !== organizationExist?.name) { + organizationDetails = await this.organizationRepository.updateOrganization(updateOrgDto); + } else if ( + organizationDetails?.logoUrl !== organizationExist?.logoUrl || + organizationDetails?.name !== organizationExist?.name + ) { const invitationData = await this._createConnection(updateOrgDto?.logo, updateOrgDto?.name, orgId); - await this.organizationRepository.updateConnectionInvitationDetails(orgId, invitationData?.connectionInvitation); + await this.organizationRepository.updateConnectionInvitationDetails( + orgId, + invitationData?.connectionInvitation + ); } - await this.userActivityService.createActivity(userId, organizationDetails.id, `${organizationDetails.name} organization updated`, 'Organization details updated successfully'); + await this.userActivityService.createActivity( + userId, + organizationDetails.id, + `${organizationDetails.name} organization updated`, + 'Organization details updated successfully' + ); return organizationDetails; } catch (error) { this.logger.error(`In update organization : ${JSON.stringify(error)}`); @@ -545,11 +563,7 @@ export class OrganizationService { } } - async _createConnection( - orgName: string, - logoUrl: string, - orgId: string - ): Promise { + async _createConnection(orgName: string, logoUrl: string, orgId: string): Promise { const pattern = { cmd: 'create-connection-invitation' }; const payload = { @@ -561,7 +575,7 @@ export class OrganizationService { }; const connectionInvitationData = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -576,12 +590,8 @@ export class OrganizationService { return connectionInvitationData; } - async countTotalOrgs( - userId: string - - ): Promise { + async countTotalOrgs(userId: string): Promise { try { - const getOrgs = await this.organizationRepository.userOrganizationCount(userId); return getOrgs; } catch (error) { @@ -589,11 +599,11 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - + /** * @returns Get created organizations details */ - + async getOrganizations( userId: string, pageNumber: number, @@ -611,11 +621,11 @@ export class OrganizationService { { description: { contains: search, mode: 'insensitive' } } ] }; - + const filterOptions = { userId }; - + const getOrgs = await this.organizationRepository.getOrganizations( query, filterOptions, @@ -626,7 +636,7 @@ export class OrganizationService { ); const { organizations } = getOrgs; - + if (0 === organizations?.length) { throw new NotFoundException(ResponseMessages.organisation.error.organizationNotFound); } @@ -635,25 +645,25 @@ export class OrganizationService { let updatedOrgs; if ('true' === process.env.IS_ECOSYSTEM_ENABLE) { - orgIds = organizations?.map(item => item.id); - + orgIds = organizations?.map((item) => item.id); + const orgEcosystemDetails = await this._getOrgEcosystems(orgIds); - - updatedOrgs = getOrgs.organizations.map(org => { + + updatedOrgs = getOrgs.organizations.map((org) => { const matchingEcosystems = orgEcosystemDetails - .filter(ecosystem => ecosystem.orgId === org.id) - .map(ecosystem => ({ ecosystemId: ecosystem.ecosystemId })); + .filter((ecosystem) => ecosystem.orgId === org.id) + .map((ecosystem) => ({ ecosystemId: ecosystem.ecosystemId })); return { ...org, ecosystemOrgs: 0 < matchingEcosystems.length ? matchingEcosystems : [] }; }); } else { - updatedOrgs = getOrgs?.organizations?.map(org => ({ + updatedOrgs = getOrgs?.organizations?.map((org) => ({ ...org })); } - + return { totalCount: getOrgs.totalCount, totalPages: getOrgs.totalPages, @@ -687,26 +697,71 @@ export class OrganizationService { } async clientLoginCredentails(clientCredentials: IClientCredentials): Promise { - const {clientId, clientSecret} = clientCredentials; - return this.authenticateClientKeycloak(clientId, clientSecret); -} + const { clientId, clientSecret } = clientCredentials; + const authenticationResult = await this.authenticateClientKeycloak(clientId, clientSecret); + let addSessionDetails; + // Fetch organization details for getting the user id + const orgRoleDetails = await this.organizationRepository.getOrgAndAdminUser(clientId); + this.logger.debug(`orgRoleDetails::::${JSON.stringify(orgRoleDetails)}`); + // check seesion details + const userSessionDetails = await this.userRepository.fetchUserSessions(orgRoleDetails['user'].id); + if (10 <= userSessionDetails?.length) { + throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); + } + // Creation sessison and account + const sessionData = { + sessionToken: authenticationResult?.access_token, + userId: orgRoleDetails['user'].id, + expires: authenticationResult?.expires_in + }; - async authenticateClientKeycloak(clientId: string, clientSecret: string): Promise { - - try { - const payload = new ClientCredentialTokenPayloadDto(); - // eslint-disable-next-line camelcase - payload.client_id = clientId; - // eslint-disable-next-line camelcase - payload.client_secret = clientSecret; + const fetchAccountDetails = await this.userRepository.checkAccountDetails(orgRoleDetails['user'].id); + if (fetchAccountDetails) { + const accountData = { + sessionToken: authenticationResult?.access_token, + userId: orgRoleDetails['user'].id, + expires: authenticationResult?.expires_in + }; - try { - const mgmtTokenResponse = await this.clientRegistrationService.getToken(payload); - return mgmtTokenResponse; - } catch (error) { - throw new UnauthorizedException(ResponseMessages.organisation.error.invalidClient); + await this.userRepository.updateAccountDetails(accountData).then(async () => { + addSessionDetails = await this.userRepository.createSession(sessionData); + }); + } else { + const accountData = { + sessionToken: authenticationResult?.access_token, + userId: orgRoleDetails['user'].id, + expires: authenticationResult?.expires_in, + keycloakUserId: orgRoleDetails['user'].keycloakUserId, + type: TokenType.ORG_TOKEN + }; + + await this.userRepository.addAccountDetails(accountData).then(async () => { + addSessionDetails = await this.userRepository.createSession(sessionData); + }); } + // Response: add session id as cookies + const finalResponse = { + ...authenticationResult, + sessionId: addSessionDetails.id + }; + // In fetch session API need to handle the conditon for session is comes from cookies or query parameter + return finalResponse; + } + async authenticateClientKeycloak(clientId: string, clientSecret: string): Promise { + try { + const payload = new ClientCredentialTokenPayloadDto(); + // eslint-disable-next-line camelcase + payload.client_id = clientId; + // eslint-disable-next-line camelcase + payload.client_secret = clientSecret; + + try { + const mgmtTokenResponse = await this.clientRegistrationService.getToken(payload); + return mgmtTokenResponse; + } catch (error) { + throw new UnauthorizedException(ResponseMessages.organisation.error.invalidClient); + } } catch (error) { this.logger.error(`Error in authenticateClientKeycloak : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -883,36 +938,35 @@ export class OrganizationService { userEmail: string, userId: string, orgName: string - ): Promise { + ): Promise { const { invitations, orgId } = bulkInvitationDto; - for (const invitation of invitations) { - const { orgRoleId, email } = invitation; + for (const invitation of invitations) { + const { orgRoleId, email } = invitation; - const isUserExist = await this.checkUserExistInPlatform(email); + const isUserExist = await this.checkUserExistInPlatform(email); - const userData = await this.getUserFirstName(userEmail); - - const {firstName} = userData; - const orgRolesDetails = await this.orgRoleService.getOrgRolesByIds(orgRoleId); - - if (0 === orgRolesDetails.length) { - throw new NotFoundException(ResponseMessages.organisation.error.orgRoleIdNotFound); - } + const userData = await this.getUserFirstName(userEmail); - const isInvitationExist = await this.checkInvitationExist(email, orgId); + const { firstName } = userData; + const orgRolesDetails = await this.orgRoleService.getOrgRolesByIds(orgRoleId); - if (!isInvitationExist && userEmail !== invitation.email) { + if (0 === orgRolesDetails.length) { + throw new NotFoundException(ResponseMessages.organisation.error.orgRoleIdNotFound); + } - await this.organizationRepository.createSendInvitation(email, String(orgId), String(userId), orgRoleId); + const isInvitationExist = await this.checkInvitationExist(email, orgId); - try { - await this.sendInviteEmailTemplate(email, orgName, orgRolesDetails, firstName, isUserExist); - } catch (error) { - throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); - } + if (!isInvitationExist && userEmail !== invitation.email) { + await this.organizationRepository.createSendInvitation(email, String(orgId), String(userId), orgRoleId); + + try { + await this.sendInviteEmailTemplate(email, orgName, orgRolesDetails, firstName, isUserExist); + } catch (error) { + throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } } + } } async createInvitationByClientRoles( @@ -921,11 +975,14 @@ export class OrganizationService { userId: string, orgName: string, idpId: string - ): Promise { + ): Promise { const { invitations, orgId } = bulkInvitationDto; const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); const orgRoles = await this.orgRoleService.getOrgRoles(); @@ -951,7 +1008,6 @@ export class OrganizationService { const isInvitationExist = await this.checkInvitationExist(email, orgId); if (!isInvitationExist && userEmail !== invitation.email) { - await this.organizationRepository.createSendInvitation( email, String(orgId), @@ -960,13 +1016,7 @@ export class OrganizationService { ); try { - await this.sendInviteEmailTemplate( - email, - orgName, - filteredOrgRoles, - firstName, - isUserExist - ); + await this.sendInviteEmailTemplate(email, orgName, filteredOrgRoles, firstName, isUserExist); } catch (error) { throw new InternalServerErrorException(ResponseMessages.user.error.emailSend); } @@ -991,12 +1041,7 @@ export class OrganizationService { } if (!organizationDetails.idpId) { - await this.createInvitationByOrgRoles( - bulkInvitationDto, - userEmail, - userId, - organizationDetails.name - ); + await this.createInvitationByOrgRoles(bulkInvitationDto, userEmail, userId, organizationDetails.name); } else { await this.createInvitationByClientRoles( bulkInvitationDto, @@ -1063,7 +1108,7 @@ export class OrganizationService { const userData: user = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1086,7 +1131,7 @@ export class OrganizationService { const userData = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1104,19 +1149,17 @@ export class OrganizationService { const pattern = { cmd: 'get-user-by-user-id' }; // const payload = { id: userId }; - const userData = await this.natsClient - .send(this.organizationServiceProxy, pattern, userId) - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.error, - message: error.message - }, - error.status - ); - }); + const userData = await this.natsClient.send(this.organizationServiceProxy, pattern, userId).catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.error, + message: error.message + }, + error.status + ); + }); return userData; } @@ -1145,38 +1188,45 @@ export class OrganizationService { status: string ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); - const orgRoles = await this.orgRoleService.getOrgRolesByIds(invitation.orgRoles); + const orgRoles = await this.orgRoleService.getOrgRolesByIds(invitation.orgRoles); - const rolesPayload: { roleId: string; name: string; idpRoleId: string }[] = orgRoles.map((orgRole: IOrgRole) => { - let roleObj: { roleId: string; name: string; idpRoleId: string} = null; + const rolesPayload: { roleId: string; name: string; idpRoleId: string }[] = orgRoles.map((orgRole: IOrgRole) => { + let roleObj: { roleId: string; name: string; idpRoleId: string } = null; - for (let index = 0; index < clientRolesList.length; index++) { - if (clientRolesList[index].name === orgRole.name) { - roleObj = { - roleId: orgRole.id, - name: orgRole.name, - idpRoleId: clientRolesList[index].id - }; - break; - } + for (let index = 0; index < clientRolesList.length; index++) { + if (clientRolesList[index].name === orgRole.name) { + roleObj = { + roleId: orgRole.id, + name: orgRole.name, + idpRoleId: clientRolesList[index].id + }; + break; } + } - return roleObj; - }); - - const data = { - status - }; + return roleObj; + }); - await Promise.all([ - this.organizationRepository.updateOrgInvitation(invitation.id, data), - this.clientRegistrationService.createUserClientRole(idpId, token, keycloakUserId, rolesPayload.map(role => ({id: role.idpRoleId, name: role.name}))), - this.userOrgRoleService.updateUserOrgRole(userId, orgId, rolesPayload) - ]); + const data = { + status + }; + await Promise.all([ + this.organizationRepository.updateOrgInvitation(invitation.id, data), + this.clientRegistrationService.createUserClientRole( + idpId, + token, + keycloakUserId, + rolesPayload.map((role) => ({ id: role.idpRoleId, name: role.name })) + ), + this.userOrgRoleService.updateUserOrgRole(userId, orgId, rolesPayload) + ]); } /** @@ -1251,11 +1301,11 @@ export class OrganizationService { orgId: string ): Promise { const userDetails = await this.organizationRepository.getUser(userId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles( - idpId, - token + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret ); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); const orgRoles = await this.orgRoleService.getOrgRoles(); const matchedClientRoles = clientRolesList.filter((role) => roleIds.includes(role.id.trim())); @@ -1286,11 +1336,7 @@ export class OrganizationService { const userData = await this.getUserUserId(userId); const [, deletedUserRoleRecords] = await Promise.all([ - this.clientRegistrationService.deleteUserClientRoles( - idpId, - token, - userData.keycloakUserId - ), + this.clientRegistrationService.deleteUserClientRoles(idpId, token, userData.keycloakUserId), this.userOrgRoleService.deleteOrgRoles(userId, orgId) ]); @@ -1351,15 +1397,8 @@ export class OrganizationService { return true; } else { - - return this.updateUserClientRoles( - roleIds, - organizationDetails.idpId, - userId, - organizationDetails.id - ); + return this.updateUserClientRoles(roleIds, organizationDetails.idpId, userId, organizationDetails.id); } - } catch (error) { this.logger.error(`Error in updateUserRoles: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -1375,26 +1414,26 @@ export class OrganizationService { } } - async getOrganizationActivityCount(orgId: string, userId: string): Promise { try { - const [ - verificationRecordsCount, - issuanceRecordsCount, - connectionRecordsCount, - orgInvitationsCount, - orgUsers - ] = await Promise.all([ - this._getVerificationRecordsCount(orgId, userId), - this._getIssuanceRecordsCount(orgId, userId), - this._getConnectionRecordsCount(orgId, userId), - this.organizationRepository.getOrgInvitationsCount(orgId), - this.organizationRepository.getOrgDashboard(orgId) - ]); + const [verificationRecordsCount, issuanceRecordsCount, connectionRecordsCount, orgInvitationsCount, orgUsers] = + await Promise.all([ + this._getVerificationRecordsCount(orgId, userId), + this._getIssuanceRecordsCount(orgId, userId), + this._getConnectionRecordsCount(orgId, userId), + this.organizationRepository.getOrgInvitationsCount(orgId), + this.organizationRepository.getOrgDashboard(orgId) + ]); const orgUsersCount = orgUsers?.['usersCount']; - return {verificationRecordsCount, issuanceRecordsCount, connectionRecordsCount, orgUsersCount, orgInvitationsCount}; + return { + verificationRecordsCount, + issuanceRecordsCount, + connectionRecordsCount, + orgUsersCount, + orgInvitationsCount + }; } catch (error) { this.logger.error(`In fetch organization references count : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -1408,24 +1447,22 @@ export class OrganizationService { orgId, userId }; - const ecosystemsCount = await (this.natsClient - .send(this.organizationServiceProxy, pattern, payload) as unknown as Promise) - - .catch((error) => { - this.logger.error(`catch: ${JSON.stringify(error)}`); - throw new HttpException( - { - status: error.status, - error: error.message - }, - error.status - ); - }); + const ecosystemsCount = await ( + this.natsClient.send(this.organizationServiceProxy, pattern, payload) as unknown as Promise + ).catch((error) => { + this.logger.error(`catch: ${JSON.stringify(error)}`); + throw new HttpException( + { + status: error.status, + error: error.message + }, + error.status + ); + }); return ecosystemsCount; } - async _getConnectionRecordsCount(orgId: string, userId: string): Promise { const pattern = { cmd: 'get-connection-records' }; @@ -1435,7 +1472,7 @@ export class OrganizationService { }; const connectionsCount = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1450,7 +1487,6 @@ export class OrganizationService { return connectionsCount; } - async _getIssuanceRecordsCount(orgId: string, userId: string): Promise { const pattern = { cmd: 'get-issuance-records' }; @@ -1460,7 +1496,7 @@ export class OrganizationService { }; const issuanceCount = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1484,7 +1520,7 @@ export class OrganizationService { }; const verificationCount = await this.natsClient .send(this.organizationServiceProxy, pattern, payload) - + .catch((error) => { this.logger.error(`catch: ${JSON.stringify(error)}`); throw new HttpException( @@ -1547,7 +1583,7 @@ export class OrganizationService { throw new RpcException(error.response ? error.response : error); } } - + async deleteOrganization(orgId: string, user: user): Promise { try { const getUser = await this.organizationRepository.getUser(user?.id); @@ -1556,113 +1592,128 @@ export class OrganizationService { this.clientRegistrationService.getManagementToken(getUser?.clientId, getUser?.clientSecret), this.organizationRepository.getOrganizationDetails(orgId) ]); - + if (!organizationDetails) { throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); } - + const organizationInvitationDetails = await this.organizationRepository.getOrgInvitationsByOrg(orgId); - - const arrayEmail = organizationInvitationDetails.map(userData => userData.email); + + const arrayEmail = organizationInvitationDetails.map((userData) => userData.email); this.logger.debug(`arrayEmail ::: ${JSON.stringify(arrayEmail)}`); - + // Fetch Keycloak IDs only if there are emails to process - const keycloakUserIds = 0 < arrayEmail.length - ? (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map(user => user.keycloakUserId) - : []; - + const keycloakUserIds = + 0 < arrayEmail.length + ? (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map((user) => user.keycloakUserId) + : []; + this.logger.log('Keycloak User Ids'); // Delete user client roles in parallel - const deleteUserRolesPromises = keycloakUserIds.map(keycloakUserId => this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, keycloakUserId) + const deleteUserRolesPromises = keycloakUserIds.map((keycloakUserId) => + this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, keycloakUserId) ); deleteUserRolesPromises.push( this.clientRegistrationService.deleteUserClientRoles(organizationDetails?.idpId, token, getUser?.keycloakUserId) ); - + this.logger.debug(`deleteUserRolesPromises ::: ${JSON.stringify(deleteUserRolesPromises)}`); const deleteUserRolesResults = await Promise.allSettled(deleteUserRolesPromises); - + // Check for failures in deleting user roles - const deletionFailures = deleteUserRolesResults.filter(result => 'rejected' === result?.status); - + const deletionFailures = deleteUserRolesResults.filter((result) => 'rejected' === result?.status); + if (0 < deletionFailures.length) { this.logger.error(`deletionFailures ::: ${JSON.stringify(deletionFailures)}`); throw new NotFoundException(ResponseMessages.organisation.error.orgDataNotFoundInkeycloak); } - - const deletedOrgInvitationInfo: { email?: string, orgName?: string, orgRoleNames?: string[] }[] = []; - const userIds = (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map(user => user.id); - await Promise.all(userIds.map(async (userId) => { - const userOrgRoleIds = await this.organizationRepository.getUserOrgRole(userId, orgId); - this.logger.debug(`userOrgRoleIds ::::: ${JSON.stringify(userOrgRoleIds)}`); - const userDetails = await this.organizationRepository.getUser(userId); - this.logger.debug(`userDetails ::::: ${JSON.stringify(userDetails)}`); - - const orgRoles = await this.organizationRepository.getOrgRole(userOrgRoleIds); - this.logger.debug(`orgRoles ::::: ${JSON.stringify(orgRoles)}`); - - const orgRoleNames = orgRoles.map(orgRoleName => orgRoleName.name); - const sendEmail = await this.sendEmailForOrgInvitationsMember(userDetails?.email, organizationDetails?.name, orgRoleNames); - const newInvitation = { - email: userDetails.email, - orgName: organizationDetails?.name, - orgRoleNames - }; - - // Step 3: Push the data into the array - deletedOrgInvitationInfo.push(newInvitation); - - this.logger.log(`email: ${userDetails.email}, orgName: ${organizationDetails?.name}, orgRoles: ${JSON.stringify(orgRoleNames)}, sendEmail: ${sendEmail}`); - })); - + const deletedOrgInvitationInfo: { email?: string; orgName?: string; orgRoleNames?: string[] }[] = []; + const userIds = (await this.getUserKeycloakIdByEmail(arrayEmail)).response.map((user) => user.id); + await Promise.all( + userIds.map(async (userId) => { + const userOrgRoleIds = await this.organizationRepository.getUserOrgRole(userId, orgId); + this.logger.debug(`userOrgRoleIds ::::: ${JSON.stringify(userOrgRoleIds)}`); + + const userDetails = await this.organizationRepository.getUser(userId); + this.logger.debug(`userDetails ::::: ${JSON.stringify(userDetails)}`); + + const orgRoles = await this.organizationRepository.getOrgRole(userOrgRoleIds); + this.logger.debug(`orgRoles ::::: ${JSON.stringify(orgRoles)}`); + + const orgRoleNames = orgRoles.map((orgRoleName) => orgRoleName.name); + const sendEmail = await this.sendEmailForOrgInvitationsMember( + userDetails?.email, + organizationDetails?.name, + orgRoleNames + ); + const newInvitation = { + email: userDetails.email, + orgName: organizationDetails?.name, + orgRoleNames + }; + + // Step 3: Push the data into the array + deletedOrgInvitationInfo.push(newInvitation); + + this.logger.log( + `email: ${userDetails.email}, orgName: ${organizationDetails?.name}, orgRoles: ${JSON.stringify(orgRoleNames)}, sendEmail: ${sendEmail}` + ); + }) + ); + // Delete organization data - const { deletedUserActivity, deletedUserOrgRole, deleteOrg, deletedOrgInvitations, deletedNotification } = await this.organizationRepository.deleteOrg(orgId); - + const { deletedUserActivity, deletedUserOrgRole, deleteOrg, deletedOrgInvitations, deletedNotification } = + await this.organizationRepository.deleteOrg(orgId); + this.logger.debug(`deletedUserActivity ::: ${JSON.stringify(deletedUserActivity)}`); this.logger.debug(`deletedUserOrgRole ::: ${JSON.stringify(deletedUserOrgRole)}`); this.logger.debug(`deleteOrg ::: ${JSON.stringify(deleteOrg)}`); this.logger.debug(`deletedOrgInvitations ::: ${JSON.stringify(deletedOrgInvitations)}`); - + const deletions = [ { records: deletedUserActivity.count, tableName: `${PrismaTables.USER_ACTIVITY}` }, { records: deletedUserOrgRole.count, tableName: `${PrismaTables.USER_ORG_ROLES}` }, - { records: deletedOrgInvitations.count, deletedOrgInvitationInfo, tableName: `${PrismaTables.ORG_INVITATIONS}` }, + { + records: deletedOrgInvitations.count, + deletedOrgInvitationInfo, + tableName: `${PrismaTables.ORG_INVITATIONS}` + }, { records: deletedNotification.count, tableName: `${PrismaTables.NOTIFICATION}` }, { records: deleteOrg ? 1 : 0, tableName: `${PrismaTables.ORGANIZATION}` } ]; - + // Log deletion activities in parallel - await Promise.all(deletions.map(async ({ records, tableName, deletedOrgInvitationInfo }) => { - if (records) { - const txnMetadata: { - deletedRecordsCount: number; - deletedRecordInTable: string; - deletedOrgInvitationInfo?: object[] - } = { - deletedRecordsCount: records, - deletedRecordInTable: tableName - }; - - if (deletedOrgInvitationInfo) { - txnMetadata.deletedOrgInvitationInfo = deletedOrgInvitationInfo; + await Promise.all( + deletions.map(async ({ records, tableName, deletedOrgInvitationInfo }) => { + if (records) { + const txnMetadata: { + deletedRecordsCount: number; + deletedRecordInTable: string; + deletedOrgInvitationInfo?: object[]; + } = { + deletedRecordsCount: records, + deletedRecordInTable: tableName + }; + + if (deletedOrgInvitationInfo) { + txnMetadata.deletedOrgInvitationInfo = deletedOrgInvitationInfo; + } + + const recordType = RecordType.ORGANIZATION; + await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); } - - const recordType = RecordType.ORGANIZATION; - await this.userActivityRepository._orgDeletedActivity(orgId, user, txnMetadata, recordType); - } - })); - + }) + ); + return deleteOrg; - } catch (error) { this.logger.error(`delete organization: ${JSON.stringify(error)}`); throw new RpcException(error.response ?? error); } } - async sendEmailForOrgInvitationsMember(email: string, orgName: string, orgRole: string[]): Promise { const platformConfigData = await this.prisma.platform_config.findMany(); @@ -1672,11 +1723,7 @@ export class OrganizationService { emailData.emailTo = email; emailData.emailSubject = `Removal of participation of “${orgName}”`; - emailData.emailHtml = await urlEmailTemplate.sendDeleteOrgMemberEmailTemplate( - email, - orgName, - orgRole - ); + emailData.emailHtml = await urlEmailTemplate.sendDeleteOrgMemberEmailTemplate(email, orgName, orgRole); //Email is sent to user for the verification through emailData const isEmailSent = await sendEmail(emailData); @@ -1737,20 +1784,19 @@ export class OrganizationService { } async registerOrgsMapUsers(): Promise { - try { - const unregisteredOrgsList = await this.organizationRepository.getUnregisteredClientOrgs(); - + if (!unregisteredOrgsList || 0 === unregisteredOrgsList.length) { throw new NotFoundException('Unregistered client organizations not found'); - } + } for (const org of unregisteredOrgsList) { const userOrgRoles = 0 < org['userOrgRoles'].length && org['userOrgRoles']; - const ownerUserList = 0 < org['userOrgRoles'].length - && userOrgRoles.filter(userOrgRole => userOrgRole.orgRole.name === OrgRoles.OWNER); + const ownerUserList = + 0 < org['userOrgRoles'].length && + userOrgRoles.filter((userOrgRole) => userOrgRole.orgRole.name === OrgRoles.OWNER); const ownerUser = 0 < ownerUserList.length && ownerUserList[0].user; @@ -1773,7 +1819,7 @@ export class OrganizationService { ); const { clientId, idpId, clientSecret } = orgCredentials; - + const updateOrgData = { clientId, clientSecret: this.maskString(clientSecret), @@ -1781,58 +1827,59 @@ export class OrganizationService { }; const updatedOrg = await this.organizationRepository.updateOrganizationById(updateOrgData, orgObj.id); - + this.logger.log(`updatedOrg::`, updatedOrg); - const usersToRegisterList = userOrgRoles.filter(userOrgRole => null !== userOrgRole.user.keycloakUserId); - - const userDetails = await this.organizationRepository.getUser(orgObj.ownerId); - const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret); - const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); - - const deletedUserDetails: string[] = []; - for (const userRole of usersToRegisterList) { - const user = userRole.user; - - const matchedClientRoles = clientRolesList.filter((role) => userRole.orgRole.name === role.name) - .map(clientRole => ({roleId: userRole.orgRole.id, idpRoleId: clientRole.id, name: clientRole.name})); - - if (!deletedUserDetails.includes(user.id)) { - const [, deletedUserRoleRecords] = await Promise.all([ - this.clientRegistrationService.deleteUserClientRoles(idpId, token, user.keycloakUserId), - this.userOrgRoleService.deleteOrgRoles(user.id, orgObj.id) - ]); - - this.logger.log(`deletedUserRoleRecords::`, deletedUserRoleRecords); - - deletedUserDetails.push(user.id); - } - - - await Promise.all([ - this.clientRegistrationService.createUserClientRole( - idpId, - token, - user.keycloakUserId, - matchedClientRoles.map((role) => ({ id: role.idpRoleId, name: role.name })) - ), - this.userOrgRoleService.updateUserOrgRole( - user.id, - orgObj.id, - matchedClientRoles.map((role) => ({ roleId: role.roleId, idpRoleId: role.idpRoleId })) - ) + const usersToRegisterList = userOrgRoles.filter((userOrgRole) => null !== userOrgRole.user.keycloakUserId); + + const userDetails = await this.organizationRepository.getUser(orgObj.ownerId); + const token = await this.clientRegistrationService.getManagementToken( + userDetails.clientId, + userDetails.clientSecret + ); + const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token); + + const deletedUserDetails: string[] = []; + for (const userRole of usersToRegisterList) { + const user = userRole.user; + + const matchedClientRoles = clientRolesList + .filter((role) => userRole.orgRole.name === role.name) + .map((clientRole) => ({ roleId: userRole.orgRole.id, idpRoleId: clientRole.id, name: clientRole.name })); + + if (!deletedUserDetails.includes(user.id)) { + const [, deletedUserRoleRecords] = await Promise.all([ + this.clientRegistrationService.deleteUserClientRoles(idpId, token, user.keycloakUserId), + this.userOrgRoleService.deleteOrgRoles(user.id, orgObj.id) ]); - this.logger.log(`Organization client created and users mapped to roles`); - } - } + this.logger.log(`deletedUserRoleRecords::`, deletedUserRoleRecords); + + deletedUserDetails.push(user.id); + } + + await Promise.all([ + this.clientRegistrationService.createUserClientRole( + idpId, + token, + user.keycloakUserId, + matchedClientRoles.map((role) => ({ id: role.idpRoleId, name: role.name })) + ), + this.userOrgRoleService.updateUserOrgRole( + user.id, + orgObj.id, + matchedClientRoles.map((role) => ({ roleId: role.roleId, idpRoleId: role.idpRoleId })) + ) + ]); + this.logger.log(`Organization client created and users mapped to roles`); + } + } } - + return ''; } catch (error) { this.logger.error(`Error in registerOrgsMapUsers: ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); - } } @@ -1948,18 +1995,18 @@ export class OrganizationService { } } - async getOrgAgentDetailsForEcosystem(data: {orgIds: string[], search: string}): Promise { + async getOrgAgentDetailsForEcosystem(data: { orgIds: string[]; search: string }): Promise { try { - const getAllOrganizationDetails = await this.organizationRepository.handleGetOrganisationData(data); + const getAllOrganizationDetails = await this.organizationRepository.handleGetOrganisationData(data); - if (!getAllOrganizationDetails) { - throw new NotFoundException(ResponseMessages.ledger.error.NotFound); - } + if (!getAllOrganizationDetails) { + throw new NotFoundException(ResponseMessages.ledger.error.NotFound); + } - return getAllOrganizationDetails; + return getAllOrganizationDetails; } catch (error) { - this.logger.error(`Error in getOrgAgentDetailsForEcosystem: ${error}`); - throw new RpcException(error.response ? error.response : error); + this.logger.error(`Error in getOrgAgentDetailsForEcosystem: ${error}`); + throw new RpcException(error.response ? error.response : error); } + } } -} \ No newline at end of file diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index b5dfc165e..7cca14c9a 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -143,9 +143,8 @@ export class UserRepository { where: { id: sessionId }, include: { user: true } }); - // if (!session || new Date() > session.expires) return null; - return session.user; + return session; } /** diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 0a7f651d3..aab860fc1 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -482,10 +482,8 @@ export class UserService { refreshToken: tokenDetails?.refresh_token }; - const addSessionDetails = await this.userRepository.createSession(sessionData); - const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); - + let addSessionDetails; let accountData; if (null === fetchAccountDetails) { accountData = { @@ -497,7 +495,9 @@ export class UserService { type: TokenType.USER_TOKEN }; - await this.userRepository.addAccountDetails(accountData); + await this.userRepository.addAccountDetails(accountData).then(async () => { + addSessionDetails = await this.userRepository.createSession(sessionData); + }); } else { accountData = { sessionToken: tokenDetails?.access_token, @@ -506,7 +506,9 @@ export class UserService { refreshToken: tokenDetails?.refresh_token }; - await this.userRepository.updateAccountDetails(accountData); + await this.userRepository.updateAccountDetails(accountData).then(async () => { + addSessionDetails = await this.userRepository.createSession(sessionData); + }); } const finalResponse = { diff --git a/libs/common/src/interfaces/interface.ts b/libs/common/src/interfaces/interface.ts index 4116d21f8..802654cbe 100644 --- a/libs/common/src/interfaces/interface.ts +++ b/libs/common/src/interfaces/interface.ts @@ -13,6 +13,7 @@ export interface IAccessTokenData { refresh_expires_in: number; token_type: string; scope: string; + sessionId?: string; } export interface IOptionalParams { From ddbfb579f209814a6565f12446c5eeddec7bc6e6 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Thu, 31 Jul 2025 19:39:30 +0530 Subject: [PATCH 10/18] feat: implemented destroy session functionality Signed-off-by: bhavanakarwade --- .env.demo | 2 +- .env.sample | 2 +- .../api-gateway/src/authz/authz.controller.ts | 40 ++++++++++++++----- apps/api-gateway/src/authz/authz.service.ts | 5 +++ .../src/authz/dtos/user-logout.dto.ts | 15 +++++++ apps/organization/src/organization.service.ts | 2 +- apps/user/interfaces/user.interface.ts | 6 +++ apps/user/repositories/user.repository.ts | 31 +++++++++++++- apps/user/src/user.controller.ts | 6 +++ apps/user/src/user.service.ts | 18 ++++++++- libs/common/src/response-messages/index.ts | 3 +- 11 files changed, 113 insertions(+), 17 deletions(-) create mode 100644 apps/api-gateway/src/authz/dtos/user-logout.dto.ts diff --git a/.env.demo b/.env.demo index f520dbc17..c73e721c6 100644 --- a/.env.demo +++ b/.env.demo @@ -156,7 +156,7 @@ OTEL_LOGS_OTLP_ENDPOINT='http://localhost:4318/v1/logs' OTEL_HEADERS_KEY=88ca6b1XXXXXXXXXXXXXXXXXXXXXXXXXXX OTEL_LOGGER_NAME='credebl-platform-logger' HOSTNAME='localhost' - +SESSIONS_LIMIT=10 # SSO # To add more clients, simply add comma separated values of client names SUPPORTED_SSO_CLIENTS=CREDEBL diff --git a/.env.sample b/.env.sample index 7b50c2c6a..7c78754c9 100644 --- a/.env.sample +++ b/.env.sample @@ -153,7 +153,7 @@ SCHEMA_FILE_SERVER_URL= // Please provide schema URL SCHEMA_FILE_SERVER_TOKEN=xxxxxxxx // Please provide schema file server token for polygon FILEUPLOAD_CACHE_TTL= //Provide file upload cache ttl - +SESSIONS_LIMIT= //Provide limits of sessions FIELD_UPLOAD_SIZE= //Provide field upload size IS_ECOSYSTEM_ENABLE= //Set this flag to `true` to enable the ecosystem, or `false` to disable it. diff --git a/apps/api-gateway/src/authz/authz.controller.ts b/apps/api-gateway/src/authz/authz.controller.ts index 407358d2f..c31c8a1f1 100644 --- a/apps/api-gateway/src/authz/authz.controller.ts +++ b/apps/api-gateway/src/authz/authz.controller.ts @@ -15,7 +15,7 @@ import { } from '@nestjs/common'; import { AuthzService } from './authz.service'; import { CommonService } from '../../../../libs/common/src/common.service'; -import { ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiResponseDto } from '../dtos/apiResponse.dto'; import { UserEmailVerificationDto } from '../user/dto/create-user.dto'; import IResponseType from '@credebl/common/interfaces/response.interface'; @@ -33,9 +33,9 @@ import { RefreshTokenDto } from './dtos/refresh-token.dto'; import { getDefaultClient } from '../user/utils'; import { ClientAliasValidationPipe } from './decorators/user-auth-client'; import { SessionGuard } from './guards/session.guard'; -interface SessionDetails { - sessionId: string; -} +import { UserLogoutDto } from './dtos/user-logout.dto'; +import { AuthGuard } from '@nestjs/passport'; +import { ISessionData } from 'apps/user/interfaces/user.interface'; @Controller('auth') @ApiTags('auth') @UseFilters(CustomExceptionFilter) @@ -190,11 +190,7 @@ export class AuthzController { required: false }) @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: AuthTokenResponse }) - async sessionDetails( - @Res() res: Response, - @Req() req: Request, - @Query() sessionId: SessionDetails - ): Promise { + async sessionDetails(@Res() res: Response, @Req() req: Request, @Query() sessionId: ISessionData): Promise { this.logger.debug(`in authz controller`); let sessionDetails; @@ -309,4 +305,30 @@ export class AuthzController { return res.status(HttpStatus.OK).json(finalResponse); } + + /** + * Log out user. + * + * @body LogoutUserDto + * @returns Logged out user from current session + */ + @Post('/signout') + @ApiOperation({ + summary: 'Logout user', + description: 'Logout user from current session.' + }) + @ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto }) + @UseGuards(AuthGuard('jwt')) + @ApiBearerAuth() + @ApiBody({ type: UserLogoutDto }) + async logout(@Body() logoutUserDto: UserLogoutDto, @Res() res: Response): Promise { + await this.authzService.logout(logoutUserDto); + + const finalResponse: IResponseType = { + statusCode: HttpStatus.OK, + message: ResponseMessages.user.success.logout + }; + + return res.status(HttpStatus.OK).json(finalResponse); + } } diff --git a/apps/api-gateway/src/authz/authz.service.ts b/apps/api-gateway/src/authz/authz.service.ts index d785cf230..f9dc909f6 100644 --- a/apps/api-gateway/src/authz/authz.service.ts +++ b/apps/api-gateway/src/authz/authz.service.ts @@ -18,6 +18,7 @@ import { ResetTokenPasswordDto } from './dtos/reset-token-password'; import { NATSClient } from '@credebl/common/NATSClient'; import { user } from '@prisma/client'; import { ISessionDetails } from 'apps/user/interfaces/user.interface'; +import { UserLogoutDto } from './dtos/user-logout.dto'; @Injectable() @WebSocketGateway() export class AuthzService extends BaseService { @@ -79,4 +80,8 @@ export class AuthzService extends BaseService { const payload = { userInfo }; return this.natsClient.sendNatsMessage(this.authServiceProxy, 'add-user', payload); } + + async logout(logoutUserDto: UserLogoutDto): Promise { + return this.natsClient.sendNatsMessage(this.authServiceProxy, 'user-logout', logoutUserDto); + } } diff --git a/apps/api-gateway/src/authz/dtos/user-logout.dto.ts b/apps/api-gateway/src/authz/dtos/user-logout.dto.ts new file mode 100644 index 000000000..ec836eebb --- /dev/null +++ b/apps/api-gateway/src/authz/dtos/user-logout.dto.ts @@ -0,0 +1,15 @@ +import { IsArray, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +import { ApiPropertyOptional } from '@nestjs/swagger'; + +export class UserLogoutDto { + @ApiPropertyOptional({ + description: 'List of session IDs to log out', + type: [String] + }) + @IsOptional() + @IsArray({ message: 'sessions must be an array' }) + @IsString({ each: true, message: 'each session Id must be a string' }) + @IsNotEmpty({ each: true, message: 'session Id must not be empty' }) + sessions?: string[]; +} diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index ef507b214..bb1cbaa3d 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -705,7 +705,7 @@ export class OrganizationService { this.logger.debug(`orgRoleDetails::::${JSON.stringify(orgRoleDetails)}`); // check seesion details const userSessionDetails = await this.userRepository.fetchUserSessions(orgRoleDetails['user'].id); - if (10 <= userSessionDetails?.length) { + if (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) { throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); } // Creation sessison and account diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index d647cfce5..608f3b76a 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -244,6 +244,9 @@ export interface UserRoleMapping { userRoleId: string; } +export interface ISessions { + sessions: string[]; +} export interface UserRoleDetails { id: string; role: $Enums.UserRole; @@ -273,3 +276,6 @@ export interface IAccountDetails { id_token?: string; session_state?: string; } +export interface ISessionData { + sessionId: string; +} diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 7cca14c9a..95950d5dd 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -16,9 +16,19 @@ import { UserRoleMapping } from '../interfaces/user.interface'; import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; -import { ProviderType, UserRole } from '@credebl/enum/enum'; // eslint-disable-next-line camelcase -import { RecordType, account, client_aliases, schema, session, token, user, user_org_roles } from '@prisma/client'; +import { + Prisma, + RecordType, + account, + client_aliases, + schema, + session, + token, + user, + user_org_roles +} from '@prisma/client'; +import { ProviderType, UserRole } from '@credebl/enum/enum'; import { PrismaService } from '@credebl/prisma-service'; @@ -959,4 +969,21 @@ export class UserRepository { throw error; } } + + async destroySession(sessions: string[]): Promise { + try { + const userSessions = await this.prisma.session.deleteMany({ + where: { + id: { + in: sessions + } + } + }); + + return userSessions; + } catch (error) { + this.logger.error(`Error in logging out user: ${error.message}`); + throw error; + } + } } diff --git a/apps/user/src/user.controller.ts b/apps/user/src/user.controller.ts index 4031c2e55..a539d8a99 100644 --- a/apps/user/src/user.controller.ts +++ b/apps/user/src/user.controller.ts @@ -2,6 +2,7 @@ import { ICheckUserDetails, IOrgUsers, ISessionDetails, + ISessions, IUserDeletedActivity, IUserForgotPassword, IUserInformation, @@ -263,4 +264,9 @@ export class UserController { async getuserOrganizationByUserId(payload: { userId: string }): Promise { return this.userService.getuserOrganizationByUserId(payload.userId); } + + @MessagePattern({ cmd: 'user-logout' }) + async logout(logoutUserDto: ISessions): Promise { + return this.userService.logout(logoutUserDto); + } } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index aab860fc1..81132205d 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -40,7 +40,8 @@ import { UserKeycloakId, IEcosystemConfig, IUserForgotPassword, - ISessionDetails + ISessionDetails, + ISessions } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; @@ -451,7 +452,7 @@ export class UserService { const userSessionDetails = await this.userRepository.fetchUserSessions(userData?.id); - if (3 <= userSessionDetails?.length) { + if (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) { throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); } @@ -1300,4 +1301,17 @@ export class UserService { throw new RpcException(error.response ? error.response : error); } } + + async logout(logoutUserDto: ISessions): Promise { + try { + if (logoutUserDto?.sessions) { + await this.userRepository.destroySession(logoutUserDto?.sessions); + } + + return 'user logged out successfully'; + } catch (error) { + this.logger.error(`Error in logging out session: ${error}`); + throw new RpcException(error.response ? error.response : error); + } + } } diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 79567a798..4928b9396 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -25,7 +25,8 @@ export const ResponseMessages = { refreshToken: 'Token details fetched successfully', countriesVerificationCode: 'All countries has been fetched successfully', stateVerificationCode: 'All states has been fetched successfully', - cityVerificationCode: 'All cities has been fetched successfully' + cityVerificationCode: 'All cities has been fetched successfully', + logout: 'User logout successfully' }, error: { exists: 'User already exists', From 97d8f7999081e89ac726fe690ccb325fb0fb9a5b Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Thu, 31 Jul 2025 12:42:29 +0530 Subject: [PATCH 11/18] fix:worked on generate client token API related workflow Signed-off-by: shitrerohit --- apps/organization/src/organization.service.ts | 10 ++++++---- apps/user/interfaces/user.interface.ts | 1 + apps/user/repositories/user.repository.ts | 5 +++-- apps/user/src/user.service.ts | 10 ++++++---- .../migration.sql | 2 ++ libs/prisma-service/prisma/schema.prisma | 2 +- 6 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20250730104449_remove_unique_constraint_for_acoountid/migration.sql diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index bb1cbaa3d..7be3c892f 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -723,8 +723,9 @@ export class OrganizationService { expires: authenticationResult?.expires_in }; - await this.userRepository.updateAccountDetails(accountData).then(async () => { - addSessionDetails = await this.userRepository.createSession(sessionData); + await this.userRepository.updateAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); }); } else { const accountData = { @@ -735,8 +736,9 @@ export class OrganizationService { type: TokenType.ORG_TOKEN }; - await this.userRepository.addAccountDetails(accountData).then(async () => { - addSessionDetails = await this.userRepository.createSession(sessionData); + await this.userRepository.addAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); }); } // Response: add session id as cookies diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 608f3b76a..1001984e2 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -188,6 +188,7 @@ export interface ISession { refreshToken?: string; keycloakUserId?: string; type?: string; + accountId?: string; } export interface ISessionDetails extends ISession { diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 95950d5dd..af39599b2 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -678,14 +678,15 @@ export class UserRepository { async createSession(tokenDetails: ISession): Promise { try { - const { sessionToken, userId, expires, refreshToken } = tokenDetails; + const { sessionToken, userId, expires, refreshToken, accountId } = tokenDetails; const sessionResponse = await this.prisma.session.create({ data: { sessionToken, expires, userId, // eslint-disable-next-line camelcase - refresh_token: refreshToken + refresh_token: refreshToken, + accountId } }); return sessionResponse; diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 81132205d..da7accf11 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -496,8 +496,9 @@ export class UserService { type: TokenType.USER_TOKEN }; - await this.userRepository.addAccountDetails(accountData).then(async () => { - addSessionDetails = await this.userRepository.createSession(sessionData); + await this.userRepository.addAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); }); } else { accountData = { @@ -507,8 +508,9 @@ export class UserService { refreshToken: tokenDetails?.refresh_token }; - await this.userRepository.updateAccountDetails(accountData).then(async () => { - addSessionDetails = await this.userRepository.createSession(sessionData); + await this.userRepository.updateAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); }); } diff --git a/libs/prisma-service/prisma/migrations/20250730104449_remove_unique_constraint_for_acoountid/migration.sql b/libs/prisma-service/prisma/migrations/20250730104449_remove_unique_constraint_for_acoountid/migration.sql new file mode 100644 index 000000000..bb1c4da87 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250730104449_remove_unique_constraint_for_acoountid/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "session_accountId_key"; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 6023294cb..59773552b 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -66,7 +66,7 @@ model session { user user @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - accountId String? @unique @db.Uuid + accountId String? @db.Uuid account account? @relation(fields: [accountId], references:[id]) } From b19ec58dc86550c9310b5c4565ccbe153f7eb07d Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Thu, 31 Jul 2025 15:01:25 +0530 Subject: [PATCH 12/18] account and session table related migration added Signed-off-by: shitrerohit --- .../migration.sql | 51 +++++++++++++++++++ libs/prisma-service/prisma/schema.prisma | 15 +++--- 2 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 libs/prisma-service/prisma/migrations/20250731092810_account_session_table_column_name_changes/migration.sql diff --git a/libs/prisma-service/prisma/migrations/20250731092810_account_session_table_column_name_changes/migration.sql b/libs/prisma-service/prisma/migrations/20250731092810_account_session_table_column_name_changes/migration.sql new file mode 100644 index 000000000..8921a348a --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250731092810_account_session_table_column_name_changes/migration.sql @@ -0,0 +1,51 @@ +/* + Warnings: + + - You are about to drop the column `access_token` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `expires_at` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `id_token` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `refresh_token` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `session_state` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `token_type` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `refresh_token` on the `session` table. All the data in the column will be lost. + - A unique constraint covering the columns `[refreshToken]` on the table `account` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[accessToken]` on the table `account` will be added. If there are existing duplicate values, this will fail. + - A unique constraint covering the columns `[refreshToken]` on the table `session` will be added. If there are existing duplicate values, this will fail. + +*/ +-- DropIndex +DROP INDEX "account_access_token_key"; + +-- DropIndex +DROP INDEX "account_refresh_token_key"; + +-- DropIndex +DROP INDEX "session_refresh_token_key"; + +-- AlterTable +ALTER TABLE "account" DROP COLUMN "access_token", +DROP COLUMN "expires_at", +DROP COLUMN "id_token", +DROP COLUMN "refresh_token", +DROP COLUMN "session_state", +DROP COLUMN "token_type", +ADD COLUMN "accessToken" TEXT, +ADD COLUMN "expiresAt" INTEGER, +ADD COLUMN "idToken" TEXT, +ADD COLUMN "refreshToken" TEXT, +ADD COLUMN "sessionState" TEXT, +ADD COLUMN "tokenType" TEXT; + +-- AlterTable +ALTER TABLE "session" DROP COLUMN "refresh_token", +ADD COLUMN "refreshToken" TEXT, +ADD COLUMN "sessionType" TEXT; + +-- CreateIndex +CREATE UNIQUE INDEX "account_refreshToken_key" ON "account"("refreshToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "account_accessToken_key" ON "account"("accessToken"); + +-- CreateIndex +CREATE UNIQUE INDEX "session_refreshToken_key" ON "session"("refreshToken"); diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index 59773552b..a06926c6f 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -44,13 +44,13 @@ model account { type String? provider String providerAccountId String - refresh_token String? @unique - access_token String? @unique - expires_at Int? - token_type String? + refreshToken String? @unique + accessToken String? @unique + expiresAt Int? + tokenType String? scope String? - id_token String? - session_state String? + idToken String? + sessionState String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user user @relation(fields: [userId], references: [id]) @@ -62,11 +62,12 @@ model session { sessionToken String @unique userId String @db.Uuid expires Int - refresh_token String? @unique + refreshToken String? @unique user user @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt accountId String? @db.Uuid + sessionType String? account account? @relation(fields: [accountId], references:[id]) } From 48ab9139ad20dae4c146608bc6599990eecb8fa4 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Thu, 31 Jul 2025 16:24:26 +0530 Subject: [PATCH 13/18] fix:modification in session creation and account creation Signed-off-by: shitrerohit --- apps/organization/src/organization.service.ts | 7 +++-- apps/user/interfaces/user.interface.ts | 1 + apps/user/repositories/user.repository.ts | 29 +++++++------------ apps/user/src/user.service.ts | 9 +++--- libs/enum/src/enum.ts | 8 +++-- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 7be3c892f..680cf46ef 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -27,7 +27,7 @@ import { sendEmail } from '@credebl/common/send-grid-helper-file'; import { CreateOrganizationDto } from '../dtos/create-organization.dto'; import { BulkSendInvitationDto } from '../dtos/send-invitation.dto'; import { UpdateInvitationDto } from '../dtos/update-invitation.dt'; -import { DidMethod, Invitation, Ledgers, PrismaTables, TokenType, transition } from '@credebl/enum/enum'; +import { DidMethod, Invitation, Ledgers, PrismaTables, SessionType, TokenType, transition } from '@credebl/enum/enum'; import { IGetOrgById, IGetOrganization, @@ -712,7 +712,8 @@ export class OrganizationService { const sessionData = { sessionToken: authenticationResult?.access_token, userId: orgRoleDetails['user'].id, - expires: authenticationResult?.expires_in + expires: authenticationResult?.expires_in, + sessionType: SessionType.ORG_SESSION }; const fetchAccountDetails = await this.userRepository.checkAccountDetails(orgRoleDetails['user'].id); @@ -733,7 +734,7 @@ export class OrganizationService { userId: orgRoleDetails['user'].id, expires: authenticationResult?.expires_in, keycloakUserId: orgRoleDetails['user'].keycloakUserId, - type: TokenType.ORG_TOKEN + type: TokenType.BEARER_TOKEN }; await this.userRepository.addAccountDetails(accountData).then(async (response) => { diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 1001984e2..05e537003 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -189,6 +189,7 @@ export interface ISession { keycloakUserId?: string; type?: string; accountId?: string; + sessionType?: string; } export interface ISessionDetails extends ISession { diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index af39599b2..498459b48 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -678,15 +678,15 @@ export class UserRepository { async createSession(tokenDetails: ISession): Promise { try { - const { sessionToken, userId, expires, refreshToken, accountId } = tokenDetails; + const { sessionToken, userId, expires, refreshToken, accountId, sessionType } = tokenDetails; const sessionResponse = await this.prisma.session.create({ data: { sessionToken, expires, userId, - // eslint-disable-next-line camelcase - refresh_token: refreshToken, - accountId + refreshToken, + accountId, + sessionType } }); return sessionResponse; @@ -731,12 +731,9 @@ export class UserRepository { userId: accountDetails.userId }, data: { - // eslint-disable-next-line camelcase - access_token: accountDetails.sessionToken, - // eslint-disable-next-line camelcase - refresh_token: accountDetails.refreshToken, - // eslint-disable-next-line camelcase - expires_at: accountDetails.expires + accessToken: accountDetails.sessionToken, + refreshToken: accountDetails.refreshToken, + expiresAt: accountDetails.expires } }); return userAccountDetails; @@ -753,14 +750,10 @@ export class UserRepository { userId: accountDetails.userId, provider: ProviderType.KEYCLOAK, providerAccountId: accountDetails.keycloakUserId, - // eslint-disable-next-line camelcase - access_token: accountDetails.sessionToken, - // eslint-disable-next-line camelcase - refresh_token: accountDetails.refreshToken, - // eslint-disable-next-line camelcase - expires_at: accountDetails.expires, - // eslint-disable-next-line camelcase - token_type: accountDetails.type + accessToken: accountDetails.sessionToken, + refreshToken: accountDetails.refreshToken, + expiresAt: accountDetails.expires, + tokenType: accountDetails.type } }); return userAccountDetails; diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index da7accf11..6005521ab 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -48,7 +48,7 @@ import { UserActivityService } from '@credebl/user-activity'; import { SupabaseService } from '@credebl/supabase'; import { UserDevicesRepository } from '../repositories/user-device.repository'; import { v4 as uuidv4 } from 'uuid'; -import { Invitation, ProviderType, TokenType, UserRole } from '@credebl/enum/enum'; +import { Invitation, ProviderType, SessionType, TokenType, UserRole } from '@credebl/enum/enum'; import validator from 'validator'; import { DISALLOWED_EMAIL_DOMAIN } from '@credebl/common/common.constant'; import { AwsService } from '@credebl/aws'; @@ -385,7 +385,7 @@ export class UserService { provider: ProviderType.KEYCLOAK, providerAccountId: keycloakDetails.keycloakUserId.toString(), // eslint-disable-next-line camelcase - token_type: TokenType.USER_TOKEN + token_type: TokenType.BEARER_TOKEN }; await this.userRepository.addAccountDetails(userAccountDetails); @@ -480,7 +480,8 @@ export class UserService { sessionToken: tokenDetails?.access_token, userId: userData?.id, expires: tokenDetails?.expires_in, - refreshToken: tokenDetails?.refresh_token + refreshToken: tokenDetails?.refresh_token, + sessionType: SessionType.USER_SESSION }; const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); @@ -493,7 +494,7 @@ export class UserService { expires: tokenDetails?.expires_in, refreshToken: tokenDetails?.refresh_token, keycloakUserId: userData?.keycloakUserId, - type: TokenType.USER_TOKEN + type: TokenType.BEARER_TOKEN }; await this.userRepository.addAccountDetails(accountData).then(async (response) => { diff --git a/libs/enum/src/enum.ts b/libs/enum/src/enum.ts index 09c7b967c..9dc8b31ca 100644 --- a/libs/enum/src/enum.ts +++ b/libs/enum/src/enum.ts @@ -259,8 +259,12 @@ export enum ProofType { } export enum TokenType { - USER_TOKEN = 'user-token', - ORG_TOKEN = 'organization-token' + BEARER_TOKEN = 'Bearer' +} + +export enum SessionType { + USER_SESSION = 'user-session', + ORG_SESSION = 'organization-session' } export enum ProviderType { From 9751adc58e08db24be6217c7a995fc04781db46f Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Thu, 31 Jul 2025 19:15:19 +0530 Subject: [PATCH 14/18] fix:modified the client login credential mathod response Signed-off-by: shitrerohit --- .env.sample | 1 + apps/api-gateway/src/organization/organization.controller.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.sample b/.env.sample index 7c78754c9..a041eba5d 100644 --- a/.env.sample +++ b/.env.sample @@ -180,6 +180,7 @@ HOSTNAME='localhost' # Hostname or unique identifier # SSO # To add more clients, simply add comma separated values of client names SUPPORTED_SSO_CLIENTS=CREDEBL +NEXTAUTH_PROTOCOL= # Key for agent base wallet AGENT_API_KEY='supersecret-that-too-16chars' diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 8257119f9..22fba3876 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -558,8 +558,8 @@ export class OrganizationController { }; res.cookie('session_id', orgCredentials.sessionId, { httpOnly: true, - sameSite: 'lax', - secure: false + sameSite: 'none', + secure: 'http' !== process.env.NEXTAUTH_PROTOCOL }); return res.status(HttpStatus.OK).json(finalResponse); From c6b330b86c13bd737bdb5f380926bd94e4056b02 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Thu, 31 Jul 2025 19:57:18 +0530 Subject: [PATCH 15/18] fix:update fetch owner org details query Signed-off-by: shitrerohit --- .../repositories/organization.repository.ts | 10 +++---- apps/organization/src/organization.service.ts | 26 +++++++++++++------ 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/apps/organization/repositories/organization.repository.ts b/apps/organization/repositories/organization.repository.ts index ad9d9b964..deed11a4f 100644 --- a/apps/organization/repositories/organization.repository.ts +++ b/apps/organization/repositories/organization.repository.ts @@ -760,14 +760,14 @@ export class OrganizationRepository { throw error; } } - async getOrgAndAdminUser(orgId: string): Promise { + async getOrgAndOwnerUser(orgId: string): Promise { try { return this.prisma.user_org_roles.findFirst({ where: { - orgId - // orgRole:{ - // name:'admin' - // } + orgId, + orgRole: { + name: 'owner' + } }, include: { user: { diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index 680cf46ef..b24257f49 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -696,26 +696,34 @@ export class OrganizationService { return response; } + /** + * Method used for generate access token based on client-id and client secret + * @param clientCredentials + * @returns session and access token both + */ async clientLoginCredentails(clientCredentials: IClientCredentials): Promise { const { clientId, clientSecret } = clientCredentials; + // This method used to authenticate the requested user on keycloak const authenticationResult = await this.authenticateClientKeycloak(clientId, clientSecret); let addSessionDetails; - // Fetch organization details for getting the user id - const orgRoleDetails = await this.organizationRepository.getOrgAndAdminUser(clientId); - this.logger.debug(`orgRoleDetails::::${JSON.stringify(orgRoleDetails)}`); - // check seesion details + // Fetch owner organization details for getting the user id + const orgRoleDetails = await this.organizationRepository.getOrgAndOwnerUser(clientId); + // Fetch the total number of sessions for the requested user to check and restrict the creation of multiple sessions. const userSessionDetails = await this.userRepository.fetchUserSessions(orgRoleDetails['user'].id); if (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) { throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); } - // Creation sessison and account + // Session payload const sessionData = { sessionToken: authenticationResult?.access_token, userId: orgRoleDetails['user'].id, expires: authenticationResult?.expires_in, sessionType: SessionType.ORG_SESSION }; - + // Note: + // Fetch account details to check whether the requested user account exists + // If the account exists, update it with the latest details and create a new session + // Otherwise, create a new account and also create the new session const fetchAccountDetails = await this.userRepository.checkAccountDetails(orgRoleDetails['user'].id); if (fetchAccountDetails) { const accountData = { @@ -729,6 +737,9 @@ export class OrganizationService { addSessionDetails = await this.userRepository.createSession(finalSessionData); }); } else { + // Note: + // This else block is mostly used for already registered users on the platform to create their account & session in the database. + // Once all users are migrated or created their accounts and sessions in the DB, this code can be removed. const accountData = { sessionToken: authenticationResult?.access_token, userId: orgRoleDetails['user'].id, @@ -742,12 +753,11 @@ export class OrganizationService { addSessionDetails = await this.userRepository.createSession(finalSessionData); }); } - // Response: add session id as cookies + // Response: add session id const finalResponse = { ...authenticationResult, sessionId: addSessionDetails.id }; - // In fetch session API need to handle the conditon for session is comes from cookies or query parameter return finalResponse; } From 084a268214e509f59f74e783d85df5a58efa6668 Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Mon, 4 Aug 2025 16:12:53 +0530 Subject: [PATCH 16/18] WIP:refresh token flow modification Signed-off-by: shitrerohit --- apps/user/interfaces/user.interface.ts | 7 ++++ apps/user/repositories/user.repository.ts | 47 ++++++++++++++++++++++ apps/user/src/user.service.ts | 42 ++++++++++++++++++- libs/common/src/response-messages/index.ts | 6 ++- 4 files changed, 100 insertions(+), 2 deletions(-) diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 05e537003..9f17b54fb 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -192,6 +192,13 @@ export interface ISession { sessionType?: string; } +export interface IUpdateAccountDetails { + accessToken: string; + refreshToken?: string; + expiresAt: number; + accountId: string; +} + export interface ISessionDetails extends ISession { id: string; createdAt: Date; diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index 498459b48..c02ef0d99 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -5,6 +5,7 @@ import { ISendVerificationEmail, ISession, IShareUserCertificate, + IUpdateAccountDetails, IUserDeletedActivity, IUserInformation, IUsersProfile, @@ -724,6 +725,38 @@ export class UserRepository { } } + async fetchAccountByRefreshToken(userId: string, refreshToken: string): Promise { + try { + return await this.prisma.account.findUnique({ + where: { + userId, + refreshToken + } + }); + } catch (error) { + this.logger.error(`Error in getting account details: ${error.message} `); + throw error; + } + } + + async updateAccountDetailsById(accountDetails: IUpdateAccountDetails): Promise { + try { + return await this.prisma.account.update({ + where: { + id: accountDetails.accountId + }, + data: { + accessToken: accountDetails.accessToken, + refreshToken: accountDetails.refreshToken, + expiresAt: accountDetails.expiresAt + } + }); + } catch (error) { + this.logger.error(`Error in getting account details: ${error.message} `); + throw error; + } + } + async updateAccountDetails(accountDetails: ISession): Promise { try { const userAccountDetails = await this.prisma.account.update({ @@ -980,4 +1013,18 @@ export class UserRepository { throw error; } } + + async deleteSessionRecordByRefreshToken(refreshToken: string): Promise { + try { + const userSession = await this.prisma.session.delete({ + where: { + refreshToken + } + }); + return userSession; + } catch (error) { + this.logger.error(`Error in logging out user: ${error.message}`); + throw error; + } + } } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index 6005521ab..ed7482d70 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -41,7 +41,8 @@ import { IEcosystemConfig, IUserForgotPassword, ISessionDetails, - ISessions + ISessions, + IUpdateAccountDetails } from '../interfaces/user.interface'; import { AcceptRejectInvitationDto } from '../dtos/accept-reject-invitation.dto'; import { UserActivityService } from '@credebl/user-activity'; @@ -545,11 +546,50 @@ export class UserService { try { const data = jwt.decode(refreshToken) as jwt.JwtPayload; const userByKeycloakId = await this.userRepository.getUserByKeycloakId(data?.sub); + this.logger.debug(`User details::;${JSON.stringify(userByKeycloakId)}`); const tokenResponse = await this.clientRegistrationService.getAccessToken( refreshToken, userByKeycloakId?.['clientId'], userByKeycloakId?.['clientSecret'] ); + this.logger.debug(`tokenResponse::::${JSON.stringify(tokenResponse)}`); + // Fetch the details from account table based on userid and refresh token + const userAccountDetails = await this.userRepository.fetchAccountByRefreshToken( + userByKeycloakId?.['id'], + refreshToken + ); + // Update the account details with latest access token, refresh token and exp date + if (!userAccountDetails) { + throw new NotFoundException(ResponseMessages.user.error.userAccountNotFound); + } + const updateAccountDetails: IUpdateAccountDetails = { + accessToken: tokenResponse.access_token, + // refreshToken: tokenResponse.refresh_token, + expiresAt: tokenResponse.expires_in, + accountId: userAccountDetails.id + }; + const updateAccountDetailsResponse = await this.userRepository.updateAccountDetailsById(updateAccountDetails); + // Delete the preveious session record and create new one + if (!updateAccountDetailsResponse) { + throw new InternalServerErrorException(ResponseMessages.user.error.errorInUpdateAccountDetails); + } + const deletePreviousSession = await this.userRepository.deleteSessionRecordByRefreshToken(refreshToken); + if (!deletePreviousSession) { + throw new InternalServerErrorException(ResponseMessages.user.error.errorInDeleteSession); + } + const sessionData = { + sessionToken: tokenResponse.access_token, + userId: userByKeycloakId?.['id'], + expires: tokenResponse.expires_in, + // refreshToken: tokenResponse.refresh_token, + sessionType: SessionType.USER_SESSION, + accountId: updateAccountDetailsResponse.id + }; + const addSessionDetails = await this.userRepository.createSession(sessionData); + if (!addSessionDetails) { + throw new InternalServerErrorException(ResponseMessages.user.error.errorInSessionCreation); + } + return tokenResponse; } catch (error) { throw new BadRequestException(ResponseMessages.user.error.invalidRefreshToken); diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index 4928b9396..e0bacaac2 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -66,7 +66,11 @@ export const ResponseMessages = { invalidResetLink: 'Invalid or expired reset password link', invalidAccessToken: 'Authentication failed', invalidRefreshToken: 'Invalid refreshToken provided', - userOrgsLimit: 'Limit reached: You can be associated with or create maximum 10 organizations.' + userOrgsLimit: 'Limit reached: You can be associated with or create maximum 10 organizations.', + errorInUpdateAccountDetails: 'Error in updating the account details', + errorInDeleteSession: 'Error in deleting the session', + errorInSessionCreation: 'Error in create session', + userAccountNotFound: 'User account not found' } }, organisation: { From d0a626725f933899f7aaf6162c35c6e2a3a91fd3 Mon Sep 17 00:00:00 2001 From: bhavanakarwade Date: Wed, 6 Aug 2025 12:53:53 +0530 Subject: [PATCH 17/18] fix: session id decoding issue Signed-off-by: bhavanakarwade --- apps/user/src/user.service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index ed7482d70..f77f9c87d 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -531,7 +531,8 @@ export class UserService { async getSession(sessionId: string): Promise { try { - const decodedSessionId = decodeURIComponent(sessionId); + const onceDecoded = decodeURIComponent(sessionId); + const decodedSessionId = decodeURIComponent(onceDecoded); const decryptedSessionId = await this.commonService.decryptPassword(decodedSessionId); const sessionDetails = await this.userRepository.getSession(decryptedSessionId); return sessionDetails; From d616a850c9d5279f408ed3ac7ecf7aeb683ed77a Mon Sep 17 00:00:00 2001 From: shitrerohit Date: Mon, 11 Aug 2025 20:32:10 +0530 Subject: [PATCH 18/18] fix:store correct account details issue Signed-off-by: shitrerohit --- apps/user/src/user.service.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index f77f9c87d..c3941e745 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -380,13 +380,12 @@ export class UserService { ); const holderOrgRole = await this.orgRoleService.getRole(OrgRoles.HOLDER); await this.userOrgRoleService.createUserOrgRole(userDetails.id, holderOrgRole.id, null, holderRoleData.id); - const userAccountDetails = { userId: userDetails?.id, provider: ProviderType.KEYCLOAK, - providerAccountId: keycloakDetails.keycloakUserId.toString(), + keycloakUserId: keycloakDetails.keycloakUserId, // eslint-disable-next-line camelcase - token_type: TokenType.BEARER_TOKEN + type: TokenType.BEARER_TOKEN }; await this.userRepository.addAccountDetails(userAccountDetails); @@ -565,7 +564,7 @@ export class UserService { } const updateAccountDetails: IUpdateAccountDetails = { accessToken: tokenResponse.access_token, - // refreshToken: tokenResponse.refresh_token, + refreshToken: tokenResponse.refresh_token, expiresAt: tokenResponse.expires_in, accountId: userAccountDetails.id }; @@ -582,7 +581,7 @@ export class UserService { sessionToken: tokenResponse.access_token, userId: userByKeycloakId?.['id'], expires: tokenResponse.expires_in, - // refreshToken: tokenResponse.refresh_token, + refreshToken: tokenResponse.refresh_token, sessionType: SessionType.USER_SESSION, accountId: updateAccountDetailsResponse.id };