diff --git a/.env.demo b/.env.demo index c73e721c6..51e70592b 100644 --- a/.env.demo +++ b/.env.demo @@ -158,6 +158,12 @@ OTEL_LOGGER_NAME='credebl-platform-logger' HOSTNAME='localhost' SESSIONS_LIMIT=10 # SSO +APP_PROTOCOL=http +#To add more clients, simply copy the variable below and change the word 'CREDEBL' to your client's name. +CREDEBL_CLIENT_ALIAS=CREDEBL +CREDEBL_DOMAIN=http://localhost:3000 +CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_ID= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY. +CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_SECRET= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY. # 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 a041eba5d..4da82816f 100644 --- a/.env.sample +++ b/.env.sample @@ -178,9 +178,14 @@ OTEL_LOGGER_NAME='credebl-platform-logger' # Name of the logger used for O HOSTNAME='localhost' # Hostname or unique identifier for the service instance # SSO +#To add more clients, simply copy the variable below and change the word 'CREDEBL' to your client's name. +CREDEBL_CLIENT_ALIAS=CREDEBL +CREDEBL_DOMAIN=http://localhost:3000 +CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_ID= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY. +CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_SECRET= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY. # To add more clients, simply add comma separated values of client names SUPPORTED_SSO_CLIENTS=CREDEBL -NEXTAUTH_PROTOCOL= +APP_PROTOCOL= # Key for agent base wallet AGENT_API_KEY='supersecret-that-too-16chars' diff --git a/apps/agent-service/src/agent-service.service.ts b/apps/agent-service/src/agent-service.service.ts index b844083cd..fdb98e8cc 100644 --- a/apps/agent-service/src/agent-service.service.ts +++ b/apps/agent-service/src/agent-service.service.ts @@ -1156,6 +1156,21 @@ export class AgentServiceService { return tenantDetails; } + private async handleCreateDid( + agentEndpoint: string, + didPayload: Record, + apiKey: string + ): Promise { + try { + return await this.commonService.httpPost(`${agentEndpoint}${CommonConstants.URL_AGENT_WRITE_DID}`, didPayload, { + headers: { authorization: apiKey } + }); + } catch (error) { + this.logger.error('Error creating did:', error.message || error); + throw new RpcException(error.response ? error.response : error); + } + } + /** * Create tenant wallet on the agent * @param _createDID @@ -1164,13 +1179,24 @@ export class AgentServiceService { private async _createDID(didCreateOption): Promise { const { didPayload, agentEndpoint, apiKey } = didCreateOption; // Invoke an API request from the agent to create multi-tenant agent - const didDetails = await this.commonService.httpPost( - `${agentEndpoint}${CommonConstants.URL_AGENT_WRITE_DID}`, - didPayload, - { headers: { authorization: apiKey } } - ); + + //To Do : this is a temporary fix in normal case the api should return correct data in first attempt , to be removed in future on fixing did/write api response + const retryOptions = { + retries: 2 + }; + + const didDetails = await retry(async () => { + const data = await this.handleCreateDid(agentEndpoint, didPayload, apiKey); + if (data?.didDocument || data?.didDoc) { + return data; + } + + throw new Error('Invalid response, retrying...'); + }, retryOptions); + return didDetails; } + private async createSocketInstance(): Promise { return io(`${process.env.SOCKET_HOST}`, { reconnection: true, diff --git a/apps/agent-service/src/interface/agent-service.interface.ts b/apps/agent-service/src/interface/agent-service.interface.ts index 4121cf4c3..28b711b93 100644 --- a/apps/agent-service/src/interface/agent-service.interface.ts +++ b/apps/agent-service/src/interface/agent-service.interface.ts @@ -434,6 +434,8 @@ export interface ICreateTenant { tenantRecord: ITenantRecord; did: string; verkey: string; + didDocument?: Record; + didDoc?: Record; } export interface IOrgAgent { diff --git a/apps/api-gateway/src/authz/jwt.strategy.ts b/apps/api-gateway/src/authz/jwt.strategy.ts index a7bac5265..a5dc0a8b6 100644 --- a/apps/api-gateway/src/authz/jwt.strategy.ts +++ b/apps/api-gateway/src/authz/jwt.strategy.ts @@ -1,17 +1,18 @@ import * as dotenv from 'dotenv'; +import * as jwt from 'jsonwebtoken'; import { ExtractJwt, Strategy } from 'passport-jwt'; -import { Injectable, Logger, UnauthorizedException, NotFoundException } from '@nestjs/common'; +import { Injectable, Logger, NotFoundException, UnauthorizedException } from '@nestjs/common'; +import { AuthzService } from './authz.service'; +import { CommonConstants } from '@credebl/common/common.constant'; +import { IOrganization } from '@credebl/common/interfaces/organization.interface'; import { JwtPayload } from './jwt-payload.interface'; +import { OrganizationService } from '../organization/organization.service'; import { PassportStrategy } from '@nestjs/passport'; +import { ResponseMessages } from '@credebl/common/response-messages'; import { UserService } from '../user/user.service'; -import * as jwt from 'jsonwebtoken'; import { passportJwtSecret } from 'jwks-rsa'; -import { CommonConstants } from '@credebl/common/common.constant'; -import { OrganizationService } from '../organization/organization.service'; -import { IOrganization } from '@credebl/common/interfaces/organization.interface'; -import { ResponseMessages } from '@credebl/common/response-messages'; dotenv.config(); @@ -21,15 +22,20 @@ export class JwtStrategy extends PassportStrategy(Strategy) { constructor( private readonly usersService: UserService, - private readonly organizationService: OrganizationService - ) { - + private readonly organizationService: OrganizationService, + private readonly authzService: AuthzService + ) { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKeyProvider: (request, jwtToken, done) => { + secretOrKeyProvider: async (request, jwtToken, done) => { + // Todo: We need to add this logic in seprate jwt gurd to handle the token expiration functionality. // eslint-disable-next-line @typescript-eslint/no-explicit-any const decodedToken: any = jwt.decode(jwtToken); - + const currentTime = Math.floor(Date.now() / 1000); + if (decodedToken?.exp < currentTime) { + const sessionIds = { sessions: [decodedToken?.sid] }; + await this.authzService.logout(sessionIds); + } if (!decodedToken) { throw new UnauthorizedException(ResponseMessages.user.error.invalidAccessToken); } @@ -49,26 +55,25 @@ export class JwtStrategy extends PassportStrategy(Strategy) { }); }, algorithms: ['RS256'] - }); + }); } async validate(payload: JwtPayload): Promise { - let userDetails = null; let userInfo; if (payload?.email) { userInfo = await this.usersService.getUserByUserIdInKeycloak(payload?.email); } - + if (payload.hasOwnProperty('client_id')) { const orgDetails: IOrganization = await this.organizationService.findOrganizationOwner(payload['client_id']); - + this.logger.log('Organization details fetched'); if (!orgDetails) { throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound); } - + // eslint-disable-next-line prefer-destructuring const userOrgDetails = 0 < orgDetails.userOrgRoles.length && orgDetails.userOrgRoles[0]; @@ -83,11 +88,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) { }); this.logger.log('User details set'); - } else { userDetails = await this.usersService.findUserinKeycloak(payload.sub); } - + if (!userDetails) { throw new NotFoundException(ResponseMessages.user.error.notFound); } diff --git a/apps/api-gateway/src/organization/organization.controller.ts b/apps/api-gateway/src/organization/organization.controller.ts index 22fba3876..dd034e418 100644 --- a/apps/api-gateway/src/organization/organization.controller.ts +++ b/apps/api-gateway/src/organization/organization.controller.ts @@ -559,7 +559,7 @@ export class OrganizationController { res.cookie('session_id', orgCredentials.sessionId, { httpOnly: true, sameSite: 'none', - secure: 'http' !== process.env.NEXTAUTH_PROTOCOL + secure: 'http' !== process.env.APP_PROTOCOL }); return res.status(HttpStatus.OK).json(finalResponse); diff --git a/apps/issuance/enum/issuance.enum.ts b/apps/issuance/enum/issuance.enum.ts index aa09cabc9..2d25b147f 100644 --- a/apps/issuance/enum/issuance.enum.ts +++ b/apps/issuance/enum/issuance.enum.ts @@ -1,6 +1,29 @@ export enum SortFields { - CREATED_DATE_TIME = 'createDateTime', - SCHEMA_ID = 'schemaId', - CONNECTION_ID = 'connectionId', - STATE = 'state' -} \ No newline at end of file + CREATED_DATE_TIME = 'createDateTime', + SCHEMA_ID = 'schemaId', + CONNECTION_ID = 'connectionId', + STATE = 'state' +} + +export enum IssueCredentials { + proposalSent = 'proposal-sent', + proposalReceived = 'proposal-received', + offerSent = 'offer-sent', + offerReceived = 'offer-received', + declined = 'decliend', + requestSent = 'request-sent', + requestReceived = 'request-received', + credentialIssued = 'credential-issued', + credentialReceived = 'credential-received', + done = 'done', + abandoned = 'abandoned' +} + +export enum IssuedCredentialStatus { + offerSent = 'Offered', + done = 'Accepted', + abandoned = 'Declined', + received = 'Pending', + proposalReceived = 'Proposal Received', + credIssued = 'Credential Issued' +} diff --git a/apps/issuance/src/issuance.repository.ts b/apps/issuance/src/issuance.repository.ts index 7b0ea6abf..a18635224 100644 --- a/apps/issuance/src/issuance.repository.ts +++ b/apps/issuance/src/issuance.repository.ts @@ -19,6 +19,7 @@ import { org_agents, organisation, platform_config, + Prisma, schema } from '@prisma/client'; @@ -28,6 +29,7 @@ import { IIssuedCredentialSearchParams } from 'apps/api-gateway/src/issuance/int import { IUserRequest } from '@credebl/user-request/user-request.interface'; import { PrismaService } from '@credebl/prisma-service'; import { ResponseMessages } from '@credebl/common/response-messages'; +import { IssueCredentials, IssuedCredentialStatus } from '../enum/issuance.enum'; @Injectable() export class IssuanceRepository { @@ -127,19 +129,66 @@ export class IssuanceRepository { }[]; }> { try { - const issuedCredentialsList = await this.prisma.credentials.findMany({ + const schemas = await this.prisma.schema.findMany({ where: { - orgId, - ...(schemaIds?.length ? { schemaId: { in: schemaIds } } : {}), - ...(!schemaIds?.length && issuedCredentialsSearchCriteria.search - ? { - OR: [ - { connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, - { schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } } - ] - } - : {}) + name: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, + select: { schemaLedgerId: true } + }); + + const schemaIdsMatched = schemas.map((s) => s.schemaLedgerId); + let stateInfo = null; + switch (issuedCredentialsSearchCriteria.search.toLowerCase()) { + case IssuedCredentialStatus.offerSent.toLowerCase(): + stateInfo = IssueCredentials.offerSent; + break; + + case IssuedCredentialStatus.done.toLowerCase(): + stateInfo = IssueCredentials.done; + break; + + case IssuedCredentialStatus.abandoned.toLowerCase(): + stateInfo = IssueCredentials.abandoned; + break; + + case IssuedCredentialStatus.received.toLowerCase(): + stateInfo = IssueCredentials.requestReceived; + break; + + case IssuedCredentialStatus.proposalReceived.toLowerCase(): + stateInfo = IssueCredentials.proposalReceived; + break; + + case IssuedCredentialStatus.credIssued.toLowerCase(): + stateInfo = IssueCredentials.offerSent; + break; + + default: + stateInfo = null; + } + + const issuanceWhereClause: Prisma.credentialsWhereInput = { + orgId, + ...(schemaIds?.length ? { schemaId: { in: schemaIds } } : {}), + ...(!schemaIds?.length && issuedCredentialsSearchCriteria.search + ? { + OR: [ + { connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, + { schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, + { schemaId: { in: schemaIdsMatched } }, + { + connections: { + theirLabel: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } + } + }, + { state: { contains: stateInfo ?? issuedCredentialsSearchCriteria.search, mode: 'insensitive' } } + ] + } + : {}) + }; + + const issuedCredentialsList = await this.prisma.credentials.findMany({ + where: issuanceWhereClause, select: { credentialExchangeId: true, createDateTime: true, @@ -162,18 +211,7 @@ export class IssuanceRepository { skip: (issuedCredentialsSearchCriteria.pageNumber - 1) * issuedCredentialsSearchCriteria.pageSize }); const issuedCredentialsCount = await this.prisma.credentials.count({ - where: { - orgId, - ...(schemaIds?.length ? { schemaId: { in: schemaIds } } : {}), - ...(!schemaIds?.length && issuedCredentialsSearchCriteria.search - ? { - OR: [ - { connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }, - { schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } } - ] - } - : {}) - } + where: issuanceWhereClause }); return { issuedCredentialsCount, issuedCredentialsList }; diff --git a/apps/organization/src/organization.service.ts b/apps/organization/src/organization.service.ts index b24257f49..b1e31764d 100644 --- a/apps/organization/src/organization.service.ts +++ b/apps/organization/src/organization.service.ts @@ -726,24 +726,14 @@ export class OrganizationService { // Otherwise, create a new account and also create the new session 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 - }; - - await this.userRepository.updateAccountDetails(accountData).then(async (response) => { - const finalSessionData = { ...sessionData, accountId: response.id }; - addSessionDetails = await this.userRepository.createSession(finalSessionData); - }); + const finalSessionData = { ...sessionData, accountId: fetchAccountDetails.id }; + 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, - expires: authenticationResult?.expires_in, keycloakUserId: orgRoleDetails['user'].keycloakUserId, type: TokenType.BEARER_TOKEN }; diff --git a/apps/user/interfaces/user.interface.ts b/apps/user/interfaces/user.interface.ts index 9f17b54fb..cc1d4f97a 100644 --- a/apps/user/interfaces/user.interface.ts +++ b/apps/user/interfaces/user.interface.ts @@ -182,6 +182,7 @@ export interface IUserSignIn { } export interface ISession { + id?: string; sessionToken?: string; userId?: string; expires?: number; diff --git a/apps/user/repositories/user.repository.ts b/apps/user/repositories/user.repository.ts index c02ef0d99..b66899d99 100644 --- a/apps/user/repositories/user.repository.ts +++ b/apps/user/repositories/user.repository.ts @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ /* eslint-disable prefer-destructuring */ import { @@ -5,7 +6,6 @@ import { ISendVerificationEmail, ISession, IShareUserCertificate, - IUpdateAccountDetails, IUserDeletedActivity, IUserInformation, IUsersProfile, @@ -17,7 +17,6 @@ import { UserRoleMapping } from '../interfaces/user.interface'; import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common'; -// eslint-disable-next-line camelcase import { Prisma, RecordType, @@ -682,6 +681,7 @@ export class UserRepository { const { sessionToken, userId, expires, refreshToken, accountId, sessionType } = tokenDetails; const sessionResponse = await this.prisma.session.create({ data: { + id: tokenDetails.id, sessionToken, expires, userId, @@ -725,57 +725,6 @@ 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({ - where: { - userId: accountDetails.userId - }, - data: { - accessToken: accountDetails.sessionToken, - refreshToken: accountDetails.refreshToken, - expiresAt: 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({ @@ -783,9 +732,6 @@ export class UserRepository { userId: accountDetails.userId, provider: ProviderType.KEYCLOAK, providerAccountId: accountDetails.keycloakUserId, - accessToken: accountDetails.sessionToken, - refreshToken: accountDetails.refreshToken, - expiresAt: accountDetails.expires, tokenType: accountDetails.type } }); @@ -1014,11 +960,11 @@ export class UserRepository { } } - async deleteSessionRecordByRefreshToken(refreshToken: string): Promise { + async deleteSession(sessionId: string): Promise { try { const userSession = await this.prisma.session.delete({ where: { - refreshToken + id: sessionId } }); return userSession; @@ -1027,4 +973,18 @@ export class UserRepository { throw error; } } + + async fetchSessionByRefreshToken(refreshToken: string): Promise { + try { + const sessionDetails = await this.prisma.session.findFirst({ + where: { + refreshToken + } + }); + return sessionDetails; + } catch (error) { + this.logger.error(`Error in fetching session details::${error.message}`); + throw error; + } + } } diff --git a/apps/user/src/user.service.ts b/apps/user/src/user.service.ts index c3941e745..fcfa39c82 100644 --- a/apps/user/src/user.service.ts +++ b/apps/user/src/user.service.ts @@ -449,9 +449,7 @@ 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 (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) { throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached); } @@ -467,61 +465,51 @@ export class UserService { if (true === isPasskey && false === userData?.isFidoVerified) { throw new UnauthorizedException(ResponseMessages.user.error.registerFido); } - + let tokenDetails; if (true === isPasskey && userData?.username && true === userData?.isFidoVerified) { const getUserDetails = await this.userRepository.getUserDetails(userData.email.toLowerCase()); const decryptedPassword = await this.commonService.decryptPassword(getUserDetails.password); - return await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + tokenDetails = await this.generateToken(email.toLowerCase(), decryptedPassword, userData); } else { const decryptedPassword = await this.commonService.decryptPassword(password); - const tokenDetails = await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + tokenDetails = await this.generateToken(email.toLowerCase(), decryptedPassword, userData); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const decodedToken: any = jwt.decode(tokenDetails?.access_token); + const sessionData = { + id: decodedToken.sid, + sessionToken: tokenDetails?.access_token, + userId: userData?.id, + expires: tokenDetails?.expires_in, + refreshToken: tokenDetails?.refresh_token, + sessionType: SessionType.USER_SESSION + }; - const sessionData = { - sessionToken: tokenDetails?.access_token, + const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); + let addSessionDetails; + let accountData; + if (null === fetchAccountDetails) { + accountData = { userId: userData?.id, - expires: tokenDetails?.expires_in, - refreshToken: tokenDetails?.refresh_token, - sessionType: SessionType.USER_SESSION + keycloakUserId: userData?.keycloakUserId, + type: TokenType.BEARER_TOKEN }; - const fetchAccountDetails = await this.userRepository.checkAccountDetails(userData?.id); - let addSessionDetails; - 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.BEARER_TOKEN - }; - - await this.userRepository.addAccountDetails(accountData).then(async (response) => { - const finalSessionData = { ...sessionData, accountId: response.id }; - addSessionDetails = await this.userRepository.createSession(finalSessionData); - }); - } else { - accountData = { - sessionToken: tokenDetails?.access_token, - userId: userData?.id, - expires: tokenDetails?.expires_in, - refreshToken: tokenDetails?.refresh_token - }; - - await this.userRepository.updateAccountDetails(accountData).then(async (response) => { - const finalSessionData = { ...sessionData, accountId: response.id }; - addSessionDetails = await this.userRepository.createSession(finalSessionData); - }); - } + await this.userRepository.addAccountDetails(accountData).then(async (response) => { + const finalSessionData = { ...sessionData, accountId: response.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); + }); + } else { + const finalSessionData = { ...sessionData, accountId: fetchAccountDetails.id }; + addSessionDetails = await this.userRepository.createSession(finalSessionData); + } - const finalResponse = { - ...tokenDetails, - sessionId: addSessionDetails.id - }; + const finalResponse = { + ...tokenDetails, + sessionId: addSessionDetails.id + }; - return finalResponse; - } + return finalResponse; } catch (error) { this.logger.error(`In Login User : ${JSON.stringify(error)}`); throw new RpcException(error.response ? error.response : error); @@ -554,26 +542,18 @@ export class UserService { ); 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 - ); + const userAccountDetails = await this.userRepository.checkAccountDetails(userByKeycloakId?.['id']); // 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); + // Fetch session details + const sessionDetails = await this.userRepository.fetchSessionByRefreshToken(refreshToken); + if (!sessionDetails) { + throw new NotFoundException(ResponseMessages.user.error.userSeesionNotFound); } - const deletePreviousSession = await this.userRepository.deleteSessionRecordByRefreshToken(refreshToken); + // Delete previous session + const deletePreviousSession = await this.userRepository.deleteSession(sessionDetails.id); if (!deletePreviousSession) { throw new InternalServerErrorException(ResponseMessages.user.error.errorInDeleteSession); } @@ -583,7 +563,7 @@ export class UserService { expires: tokenResponse.expires_in, refreshToken: tokenResponse.refresh_token, sessionType: SessionType.USER_SESSION, - accountId: updateAccountDetailsResponse.id + accountId: userAccountDetails.id }; const addSessionDetails = await this.userRepository.createSession(sessionData); if (!addSessionDetails) { diff --git a/apps/verification/src/interfaces/verification.interface.ts b/apps/verification/src/interfaces/verification.interface.ts index 0eb86b934..2e259a785 100644 --- a/apps/verification/src/interfaces/verification.interface.ts +++ b/apps/verification/src/interfaces/verification.interface.ts @@ -278,3 +278,23 @@ export interface IEmailResponse { outOfBandRecordId: string; proofRecordThId: string; } + +export enum ProofRequest { + presentationReceived = 'presentation-received', + offerReceived = 'offer-received', + declined = 'decliend', + requestSent = 'request-sent', + requestReceived = 'request-received', + credentialIssued = 'credential-issued', + credentialReceived = 'credential-received', + done = 'done', + abandoned = 'abandoned' +} + +export enum ProofRequestState { + requestSent = 'Requested', + requestReceived = 'Received', + done = 'Verified', + abandoned = 'Declined', + presentationReceived = 'Presentation Received' +} diff --git a/apps/verification/src/repositories/verification.repository.ts b/apps/verification/src/repositories/verification.repository.ts index 256fa951d..ded0a3b0e 100644 --- a/apps/verification/src/repositories/verification.repository.ts +++ b/apps/verification/src/repositories/verification.repository.ts @@ -1,8 +1,14 @@ -import { IEmailResponse, IProofPresentation, IProofRequestSearchCriteria } from '../interfaces/verification.interface'; +import { + IEmailResponse, + IProofPresentation, + IProofRequestSearchCriteria, + ProofRequest, + ProofRequestState +} from '../interfaces/verification.interface'; import { IProofPresentationsListCount, IVerificationRecords } from '@credebl/common/interfaces/verification.interface'; import { Injectable, Logger, NotFoundException } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { agent_invitations, org_agents, organisation, platform_config, presentations } from '@prisma/client'; +import { agent_invitations, org_agents, organisation, platform_config, presentations, Prisma } from '@prisma/client'; import { CommonService } from '@credebl/common'; import { IUserRequest } from '@credebl/user-request/user-request.interface'; @@ -87,15 +93,45 @@ export class VerificationRepository { proofRequestsSearchCriteria: IProofRequestSearchCriteria ): Promise { try { + let verificationStateInfo = null; + + switch (proofRequestsSearchCriteria.search.toLowerCase()) { + case ProofRequestState.requestSent.toLowerCase(): + verificationStateInfo = ProofRequest.requestSent; + break; + case ProofRequestState.requestReceived.toLowerCase(): + verificationStateInfo = ProofRequest.requestReceived; + break; + case ProofRequestState.done.toLowerCase(): + verificationStateInfo = ProofRequest.done; + break; + case ProofRequestState.abandoned.toLowerCase(): + verificationStateInfo = ProofRequest.abandoned; + break; + case ProofRequestState.presentationReceived.toLowerCase(): + verificationStateInfo = ProofRequest.presentationReceived; + break; + default: + verificationStateInfo = null; + } + + const whereClause: Prisma.presentationsWhereInput = { + orgId, + OR: [ + { connectionId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, + { presentationId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, + { emailId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, + { + connections: { + theirLabel: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } + } + }, + { state: { contains: verificationStateInfo ?? proofRequestsSearchCriteria.search, mode: 'insensitive' } } + ] + }; + const proofRequestsList = await this.prisma.presentations.findMany({ - where: { - orgId, - OR: [ - { connectionId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, - { state: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, - { presentationId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } } - ] - }, + where: whereClause, select: { createDateTime: true, createdBy: true, @@ -122,14 +158,7 @@ export class VerificationRepository { }); const proofRequestsCount = await this.prisma.presentations.count({ - where: { - orgId, - OR: [ - { connectionId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, - { state: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } }, - { presentationId: { contains: proofRequestsSearchCriteria.search, mode: 'insensitive' } } - ] - } + where: whereClause }); return { proofRequestsCount, proofRequestsList }; diff --git a/libs/common/src/response-messages/index.ts b/libs/common/src/response-messages/index.ts index e0bacaac2..6174734e8 100644 --- a/libs/common/src/response-messages/index.ts +++ b/libs/common/src/response-messages/index.ts @@ -70,7 +70,8 @@ export const ResponseMessages = { errorInUpdateAccountDetails: 'Error in updating the account details', errorInDeleteSession: 'Error in deleting the session', errorInSessionCreation: 'Error in create session', - userAccountNotFound: 'User account not found' + userAccountNotFound: 'User account not found', + userSeesionNotFound: 'User session not found' } }, organisation: { diff --git a/libs/prisma-service/prisma/migrations/20250822115351_account_session_table_modification/migration.sql b/libs/prisma-service/prisma/migrations/20250822115351_account_session_table_modification/migration.sql new file mode 100644 index 000000000..dc29d4425 --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20250822115351_account_session_table_modification/migration.sql @@ -0,0 +1,24 @@ +/* + Warnings: + + - You are about to drop the column `accessToken` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `expiresAt` on the `account` table. All the data in the column will be lost. + - You are about to drop the column `refreshToken` on the `account` table. All the data in the column will be lost. + +*/ +-- DropIndex +DROP INDEX "account_accessToken_key"; + +-- DropIndex +DROP INDEX "account_refreshToken_key"; + +-- DropIndex +DROP INDEX "session_refreshToken_key"; + +-- DropIndex +DROP INDEX "session_sessionToken_key"; + +-- AlterTable +ALTER TABLE "account" DROP COLUMN "accessToken", +DROP COLUMN "expiresAt", +DROP COLUMN "refreshToken"; diff --git a/libs/prisma-service/prisma/schema.prisma b/libs/prisma-service/prisma/schema.prisma index a06926c6f..01c803d54 100644 --- a/libs/prisma-service/prisma/schema.prisma +++ b/libs/prisma-service/prisma/schema.prisma @@ -44,9 +44,6 @@ model account { type String? provider String providerAccountId String - refreshToken String? @unique - accessToken String? @unique - expiresAt Int? tokenType String? scope String? idToken String? @@ -59,10 +56,10 @@ model account { model session { id String @id @default(uuid()) @db.Uuid - sessionToken String @unique + sessionToken String userId String @db.Uuid expires Int - refreshToken String? @unique + refreshToken String? user user @relation(fields: [userId], references: [id]) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt