Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.demo
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 6 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
36 changes: 31 additions & 5 deletions apps/agent-service/src/agent-service.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1156,6 +1156,21 @@ export class AgentServiceService {
return tenantDetails;
}

private async handleCreateDid(
agentEndpoint: string,
didPayload: Record<string, string>,
apiKey: string
): Promise<ICreateTenant> {
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
Expand All @@ -1164,13 +1179,24 @@ export class AgentServiceService {
private async _createDID(didCreateOption): Promise<ICreateTenant> {
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<Socket> {
return io(`${process.env.SOCKET_HOST}`, {
reconnection: true,
Expand Down
2 changes: 2 additions & 0 deletions apps/agent-service/src/interface/agent-service.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,8 @@ export interface ICreateTenant {
tenantRecord: ITenantRecord;
did: string;
verkey: string;
didDocument?: Record<string, string>;
didDoc?: Record<string, string>;
}

export interface IOrgAgent {
Expand Down
40 changes: 22 additions & 18 deletions apps/api-gateway/src/authz/jwt.strategy.ts
Original file line number Diff line number Diff line change
@@ -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();

Expand All @@ -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);
}
Expand All @@ -49,26 +55,25 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
});
},
algorithms: ['RS256']
});
});
}

async validate(payload: JwtPayload): Promise<object> {

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];

Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
33 changes: 28 additions & 5 deletions apps/issuance/enum/issuance.enum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
export enum SortFields {
CREATED_DATE_TIME = 'createDateTime',
SCHEMA_ID = 'schemaId',
CONNECTION_ID = 'connectionId',
STATE = 'state'
}
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'
}
84 changes: 61 additions & 23 deletions apps/issuance/src/issuance.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
org_agents,
organisation,
platform_config,
Prisma,
schema
} from '@prisma/client';

Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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 };
Expand Down
14 changes: 2 additions & 12 deletions apps/organization/src/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
Expand Down
1 change: 1 addition & 0 deletions apps/user/interfaces/user.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ export interface IUserSignIn {
}

export interface ISession {
id?: string;
sessionToken?: string;
userId?: string;
expires?: number;
Expand Down
Loading