Skip to content

Commit f08f732

Browse files
authored
Merge pull request #1405 from credebl/develop
Sync Develop branch into QA
2 parents 4189aa1 + c4b4c7b commit f08f732

File tree

17 files changed

+315
-209
lines changed

17 files changed

+315
-209
lines changed

.env.demo

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,12 @@ OTEL_LOGGER_NAME='credebl-platform-logger'
158158
HOSTNAME='localhost'
159159
SESSIONS_LIMIT=10
160160
# SSO
161+
APP_PROTOCOL=http
162+
#To add more clients, simply copy the variable below and change the word 'CREDEBL' to your client's name.
163+
CREDEBL_CLIENT_ALIAS=CREDEBL
164+
CREDEBL_DOMAIN=http://localhost:3000
165+
CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_ID= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY.
166+
CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_SECRET= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY.
161167
# To add more clients, simply add comma separated values of client names
162168
SUPPORTED_SSO_CLIENTS=CREDEBL
163169

.env.sample

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,14 @@ OTEL_LOGGER_NAME='credebl-platform-logger' # Name of the logger used for O
178178
HOSTNAME='localhost' # Hostname or unique identifier for the service instance
179179

180180
# SSO
181+
#To add more clients, simply copy the variable below and change the word 'CREDEBL' to your client's name.
182+
CREDEBL_CLIENT_ALIAS=CREDEBL
183+
CREDEBL_DOMAIN=http://localhost:3000
184+
CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_ID= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY.
185+
CREDEBL_KEYCLOAK_MANAGEMENT_CLIENT_SECRET= #Provide the value in its encrypted form using CRYPTO_PRIVATE_KEY.
181186
# To add more clients, simply add comma separated values of client names
182187
SUPPORTED_SSO_CLIENTS=CREDEBL
183-
NEXTAUTH_PROTOCOL=
188+
APP_PROTOCOL=
184189

185190
# Key for agent base wallet
186191
AGENT_API_KEY='supersecret-that-too-16chars'

apps/agent-service/src/agent-service.service.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,21 @@ export class AgentServiceService {
11561156
return tenantDetails;
11571157
}
11581158

1159+
private async handleCreateDid(
1160+
agentEndpoint: string,
1161+
didPayload: Record<string, string>,
1162+
apiKey: string
1163+
): Promise<ICreateTenant> {
1164+
try {
1165+
return await this.commonService.httpPost(`${agentEndpoint}${CommonConstants.URL_AGENT_WRITE_DID}`, didPayload, {
1166+
headers: { authorization: apiKey }
1167+
});
1168+
} catch (error) {
1169+
this.logger.error('Error creating did:', error.message || error);
1170+
throw new RpcException(error.response ? error.response : error);
1171+
}
1172+
}
1173+
11591174
/**
11601175
* Create tenant wallet on the agent
11611176
* @param _createDID
@@ -1164,13 +1179,24 @@ export class AgentServiceService {
11641179
private async _createDID(didCreateOption): Promise<ICreateTenant> {
11651180
const { didPayload, agentEndpoint, apiKey } = didCreateOption;
11661181
// Invoke an API request from the agent to create multi-tenant agent
1167-
const didDetails = await this.commonService.httpPost(
1168-
`${agentEndpoint}${CommonConstants.URL_AGENT_WRITE_DID}`,
1169-
didPayload,
1170-
{ headers: { authorization: apiKey } }
1171-
);
1182+
1183+
//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
1184+
const retryOptions = {
1185+
retries: 2
1186+
};
1187+
1188+
const didDetails = await retry(async () => {
1189+
const data = await this.handleCreateDid(agentEndpoint, didPayload, apiKey);
1190+
if (data?.didDocument || data?.didDoc) {
1191+
return data;
1192+
}
1193+
1194+
throw new Error('Invalid response, retrying...');
1195+
}, retryOptions);
1196+
11721197
return didDetails;
11731198
}
1199+
11741200
private async createSocketInstance(): Promise<Socket> {
11751201
return io(`${process.env.SOCKET_HOST}`, {
11761202
reconnection: true,

apps/agent-service/src/interface/agent-service.interface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,8 @@ export interface ICreateTenant {
434434
tenantRecord: ITenantRecord;
435435
did: string;
436436
verkey: string;
437+
didDocument?: Record<string, string>;
438+
didDoc?: Record<string, string>;
437439
}
438440

439441
export interface IOrgAgent {

apps/api-gateway/src/authz/jwt.strategy.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import * as dotenv from 'dotenv';
2+
import * as jwt from 'jsonwebtoken';
23

34
import { ExtractJwt, Strategy } from 'passport-jwt';
4-
import { Injectable, Logger, UnauthorizedException, NotFoundException } from '@nestjs/common';
5+
import { Injectable, Logger, NotFoundException, UnauthorizedException } from '@nestjs/common';
56

7+
import { AuthzService } from './authz.service';
8+
import { CommonConstants } from '@credebl/common/common.constant';
9+
import { IOrganization } from '@credebl/common/interfaces/organization.interface';
610
import { JwtPayload } from './jwt-payload.interface';
11+
import { OrganizationService } from '../organization/organization.service';
712
import { PassportStrategy } from '@nestjs/passport';
13+
import { ResponseMessages } from '@credebl/common/response-messages';
814
import { UserService } from '../user/user.service';
9-
import * as jwt from 'jsonwebtoken';
1015
import { passportJwtSecret } from 'jwks-rsa';
11-
import { CommonConstants } from '@credebl/common/common.constant';
12-
import { OrganizationService } from '../organization/organization.service';
13-
import { IOrganization } from '@credebl/common/interfaces/organization.interface';
14-
import { ResponseMessages } from '@credebl/common/response-messages';
1516

1617
dotenv.config();
1718

@@ -21,15 +22,20 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
2122

2223
constructor(
2324
private readonly usersService: UserService,
24-
private readonly organizationService: OrganizationService
25-
) {
26-
25+
private readonly organizationService: OrganizationService,
26+
private readonly authzService: AuthzService
27+
) {
2728
super({
2829
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
29-
secretOrKeyProvider: (request, jwtToken, done) => {
30+
secretOrKeyProvider: async (request, jwtToken, done) => {
31+
// Todo: We need to add this logic in seprate jwt gurd to handle the token expiration functionality.
3032
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3133
const decodedToken: any = jwt.decode(jwtToken);
32-
34+
const currentTime = Math.floor(Date.now() / 1000);
35+
if (decodedToken?.exp < currentTime) {
36+
const sessionIds = { sessions: [decodedToken?.sid] };
37+
await this.authzService.logout(sessionIds);
38+
}
3339
if (!decodedToken) {
3440
throw new UnauthorizedException(ResponseMessages.user.error.invalidAccessToken);
3541
}
@@ -49,26 +55,25 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
4955
});
5056
},
5157
algorithms: ['RS256']
52-
});
58+
});
5359
}
5460

5561
async validate(payload: JwtPayload): Promise<object> {
56-
5762
let userDetails = null;
5863
let userInfo;
5964

6065
if (payload?.email) {
6166
userInfo = await this.usersService.getUserByUserIdInKeycloak(payload?.email);
6267
}
63-
68+
6469
if (payload.hasOwnProperty('client_id')) {
6570
const orgDetails: IOrganization = await this.organizationService.findOrganizationOwner(payload['client_id']);
66-
71+
6772
this.logger.log('Organization details fetched');
6873
if (!orgDetails) {
6974
throw new NotFoundException(ResponseMessages.organisation.error.orgNotFound);
7075
}
71-
76+
7277
// eslint-disable-next-line prefer-destructuring
7378
const userOrgDetails = 0 < orgDetails.userOrgRoles.length && orgDetails.userOrgRoles[0];
7479

@@ -83,11 +88,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
8388
});
8489

8590
this.logger.log('User details set');
86-
8791
} else {
8892
userDetails = await this.usersService.findUserinKeycloak(payload.sub);
8993
}
90-
94+
9195
if (!userDetails) {
9296
throw new NotFoundException(ResponseMessages.user.error.notFound);
9397
}

apps/api-gateway/src/organization/organization.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ export class OrganizationController {
559559
res.cookie('session_id', orgCredentials.sessionId, {
560560
httpOnly: true,
561561
sameSite: 'none',
562-
secure: 'http' !== process.env.NEXTAUTH_PROTOCOL
562+
secure: 'http' !== process.env.APP_PROTOCOL
563563
});
564564

565565
return res.status(HttpStatus.OK).json(finalResponse);
Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
export enum SortFields {
2-
CREATED_DATE_TIME = 'createDateTime',
3-
SCHEMA_ID = 'schemaId',
4-
CONNECTION_ID = 'connectionId',
5-
STATE = 'state'
6-
}
2+
CREATED_DATE_TIME = 'createDateTime',
3+
SCHEMA_ID = 'schemaId',
4+
CONNECTION_ID = 'connectionId',
5+
STATE = 'state'
6+
}
7+
8+
export enum IssueCredentials {
9+
proposalSent = 'proposal-sent',
10+
proposalReceived = 'proposal-received',
11+
offerSent = 'offer-sent',
12+
offerReceived = 'offer-received',
13+
declined = 'decliend',
14+
requestSent = 'request-sent',
15+
requestReceived = 'request-received',
16+
credentialIssued = 'credential-issued',
17+
credentialReceived = 'credential-received',
18+
done = 'done',
19+
abandoned = 'abandoned'
20+
}
21+
22+
export enum IssuedCredentialStatus {
23+
offerSent = 'Offered',
24+
done = 'Accepted',
25+
abandoned = 'Declined',
26+
received = 'Pending',
27+
proposalReceived = 'Proposal Received',
28+
credIssued = 'Credential Issued'
29+
}

apps/issuance/src/issuance.repository.ts

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
org_agents,
2020
organisation,
2121
platform_config,
22+
Prisma,
2223
schema
2324
} from '@prisma/client';
2425

@@ -28,6 +29,7 @@ import { IIssuedCredentialSearchParams } from 'apps/api-gateway/src/issuance/int
2829
import { IUserRequest } from '@credebl/user-request/user-request.interface';
2930
import { PrismaService } from '@credebl/prisma-service';
3031
import { ResponseMessages } from '@credebl/common/response-messages';
32+
import { IssueCredentials, IssuedCredentialStatus } from '../enum/issuance.enum';
3133

3234
@Injectable()
3335
export class IssuanceRepository {
@@ -127,19 +129,66 @@ export class IssuanceRepository {
127129
}[];
128130
}> {
129131
try {
130-
const issuedCredentialsList = await this.prisma.credentials.findMany({
132+
const schemas = await this.prisma.schema.findMany({
131133
where: {
132-
orgId,
133-
...(schemaIds?.length ? { schemaId: { in: schemaIds } } : {}),
134-
...(!schemaIds?.length && issuedCredentialsSearchCriteria.search
135-
? {
136-
OR: [
137-
{ connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } },
138-
{ schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }
139-
]
140-
}
141-
: {})
134+
name: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' }
142135
},
136+
select: { schemaLedgerId: true }
137+
});
138+
139+
const schemaIdsMatched = schemas.map((s) => s.schemaLedgerId);
140+
let stateInfo = null;
141+
switch (issuedCredentialsSearchCriteria.search.toLowerCase()) {
142+
case IssuedCredentialStatus.offerSent.toLowerCase():
143+
stateInfo = IssueCredentials.offerSent;
144+
break;
145+
146+
case IssuedCredentialStatus.done.toLowerCase():
147+
stateInfo = IssueCredentials.done;
148+
break;
149+
150+
case IssuedCredentialStatus.abandoned.toLowerCase():
151+
stateInfo = IssueCredentials.abandoned;
152+
break;
153+
154+
case IssuedCredentialStatus.received.toLowerCase():
155+
stateInfo = IssueCredentials.requestReceived;
156+
break;
157+
158+
case IssuedCredentialStatus.proposalReceived.toLowerCase():
159+
stateInfo = IssueCredentials.proposalReceived;
160+
break;
161+
162+
case IssuedCredentialStatus.credIssued.toLowerCase():
163+
stateInfo = IssueCredentials.offerSent;
164+
break;
165+
166+
default:
167+
stateInfo = null;
168+
}
169+
170+
const issuanceWhereClause: Prisma.credentialsWhereInput = {
171+
orgId,
172+
...(schemaIds?.length ? { schemaId: { in: schemaIds } } : {}),
173+
...(!schemaIds?.length && issuedCredentialsSearchCriteria.search
174+
? {
175+
OR: [
176+
{ connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } },
177+
{ schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } },
178+
{ schemaId: { in: schemaIdsMatched } },
179+
{
180+
connections: {
181+
theirLabel: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' }
182+
}
183+
},
184+
{ state: { contains: stateInfo ?? issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }
185+
]
186+
}
187+
: {})
188+
};
189+
190+
const issuedCredentialsList = await this.prisma.credentials.findMany({
191+
where: issuanceWhereClause,
143192
select: {
144193
credentialExchangeId: true,
145194
createDateTime: true,
@@ -162,18 +211,7 @@ export class IssuanceRepository {
162211
skip: (issuedCredentialsSearchCriteria.pageNumber - 1) * issuedCredentialsSearchCriteria.pageSize
163212
});
164213
const issuedCredentialsCount = await this.prisma.credentials.count({
165-
where: {
166-
orgId,
167-
...(schemaIds?.length ? { schemaId: { in: schemaIds } } : {}),
168-
...(!schemaIds?.length && issuedCredentialsSearchCriteria.search
169-
? {
170-
OR: [
171-
{ connectionId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } },
172-
{ schemaId: { contains: issuedCredentialsSearchCriteria.search, mode: 'insensitive' } }
173-
]
174-
}
175-
: {})
176-
}
214+
where: issuanceWhereClause
177215
});
178216

179217
return { issuedCredentialsCount, issuedCredentialsList };

apps/organization/src/organization.service.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -726,24 +726,14 @@ export class OrganizationService {
726726
// Otherwise, create a new account and also create the new session
727727
const fetchAccountDetails = await this.userRepository.checkAccountDetails(orgRoleDetails['user'].id);
728728
if (fetchAccountDetails) {
729-
const accountData = {
730-
sessionToken: authenticationResult?.access_token,
731-
userId: orgRoleDetails['user'].id,
732-
expires: authenticationResult?.expires_in
733-
};
734-
735-
await this.userRepository.updateAccountDetails(accountData).then(async (response) => {
736-
const finalSessionData = { ...sessionData, accountId: response.id };
737-
addSessionDetails = await this.userRepository.createSession(finalSessionData);
738-
});
729+
const finalSessionData = { ...sessionData, accountId: fetchAccountDetails.id };
730+
addSessionDetails = await this.userRepository.createSession(finalSessionData);
739731
} else {
740732
// Note:
741733
// This else block is mostly used for already registered users on the platform to create their account & session in the database.
742734
// Once all users are migrated or created their accounts and sessions in the DB, this code can be removed.
743735
const accountData = {
744-
sessionToken: authenticationResult?.access_token,
745736
userId: orgRoleDetails['user'].id,
746-
expires: authenticationResult?.expires_in,
747737
keycloakUserId: orgRoleDetails['user'].keycloakUserId,
748738
type: TokenType.BEARER_TOKEN
749739
};

apps/user/interfaces/user.interface.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export interface IUserSignIn {
182182
}
183183

184184
export interface ISession {
185+
id?: string;
185186
sessionToken?: string;
186187
userId?: string;
187188
expires?: number;

0 commit comments

Comments
 (0)