Skip to content

Commit

Permalink
Merge pull request #39 from hypersign-protocol/implement/questService
Browse files Browse the repository at this point in the history
added new service type quest
  • Loading branch information
Vishwas1 authored Oct 16, 2024
2 parents 4d6082c + ebf3ab8 commit 762a630
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 41 deletions.
19 changes: 8 additions & 11 deletions src/app-auth/controllers/app-auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import {
Delete,
Logger,
} from '@nestjs/common';
import { CreateAppDto } from 'src/app-auth/dtos/create-app.dto';
import {
CreateAppDto,
DeleteAppResponse,
} from 'src/app-auth/dtos/create-app.dto';
import { RegenrateAppApiSecretResponse } from '../dtos/generate-token.dto';
import { AppAuthService } from 'src/app-auth/services/app-auth.service';
import {
Expand Down Expand Up @@ -203,27 +206,21 @@ export class AppAuthController {
@Delete(':appId')
@ApiResponse({
status: 200,
description: 'App deleted',
type: App,
description: 'App deleted successfully',
type: DeleteAppResponse,
})
@ApiNotFoundResponse({
description: ' App not found',
type: AppError,
})
@UseInterceptors(
MongooseClassSerializerInterceptor(App, {
excludePrefixes: ['apiKeySecret', 'apiKeyPrefix', '_', '__'],
}),
)
async deleteApp(
@Req() req: any,
@Param('appId') appId: string,
): Promise<App> {
): Promise<DeleteAppResponse> {
Logger.log('deleteApp() method: starts', 'AppAuthController');

const userId = req.user.userId;
const app = await this.appAuthService.deleteApp(appId, userId);
return app;
return this.appAuthService.deleteApp(appId, userId);
}

@ApiBearerAuth('Authorization')
Expand Down
11 changes: 10 additions & 1 deletion src/app-auth/dtos/create-app.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export class CreateAppDto {
description: 'services',
example: [SERVICE_TYPES.SSI_API],
required: true,
enum: SERVICE_TYPES,
isArray: true,
})
@IsArray({ message: 'serviceId must be an array' })
Expand All @@ -77,7 +78,7 @@ export class CreateAppDto {
@IsEnum(SERVICE_TYPES, {
each: true,
message:
"services must be one of the following values: 'CAVACH_API', 'SSI_API'",
"services must be one of the following values: 'CAVACH_API', 'SSI_API', 'QUEST'",
})
serviceIds: [SERVICE_TYPES];

Expand Down Expand Up @@ -132,3 +133,11 @@ export class CreateAppDto {
@IsBoolean()
hasDomainVerified: boolean;
}

export class DeleteAppResponse {
@ApiProperty({
description: 'Application id',
example: '3420d77a5547b60fe265a463a0e5897578d3',
})
appId: string;
}
65 changes: 52 additions & 13 deletions src/app-auth/repositories/app.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { InjectModel } from '@nestjs/mongoose';
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { SupportedServiceService } from 'src/supported-service/services/supported-service.service';
import * as url from 'url';

import * as mongoose from 'mongoose';
@Injectable()
export class AppRepository {
constructor(
Expand Down Expand Up @@ -43,8 +42,13 @@ export class AppRepository {
],
},
'://',
'$subdomain',
'.',
{
$cond: {
if: { $ifNull: ['$subdomain', false] },
then: { $concat: ['$subdomain', '.'] },
else: '',
},
},
{
$arrayElemAt: [
{
Expand Down Expand Up @@ -95,13 +99,25 @@ export class AppRepository {
{
$set: {
tenantUrl: {
$concat: [
'$serviceDomainProtocol',
'//:',
'$subdomain',
'.',
'$serviceDomainHostname',
],
$cond: {
if: { $ifNull: ['$subdomain', false] },
then: {
$concat: [
'$serviceDomainProtocol',
'//:',
'$subdomain',
'.',
'$serviceDomainHostname',
],
},
else: {
$concat: [
'$serviceDomainProtocol',
'//:',
'$serviceDomainHostname',
],
},
},
},
},
},
Expand All @@ -113,11 +129,11 @@ export class AppRepository {
'findOne() method: starts, finding particular app from db',
'AppRepository',
);
const aggrerationPipeline = [
const aggregationPipeline = [
{ $match: appFilterQuery },
...this.getTenantUrlAggeration(),
];
const apps = await this.appModel.aggregate(aggrerationPipeline);
const apps = await this.appModel.aggregate(aggregationPipeline);
return apps[0];
}
async find(appsFilterQuery: FilterQuery<App>): Promise<App[]> {
Expand Down Expand Up @@ -178,4 +194,27 @@ export class AppRepository {

return this.appModel.findOneAndDelete(appFilterQuery);
}
async findAndDeleteServiceDB(connectionStringPrefix: string) {
try {
Logger.log('findAndDeleteServiceDB() method to delte service database');
// Establish a connection to the MongoDB server
const mainConnection = await mongoose.connect(process.env.DB_BASE_PATH);
// Switch to the target database
const dbConnection = mainConnection.connection.useDb(
connectionStringPrefix,
);
// Drop the selected database
await dbConnection.dropDatabase();
Logger.log(
`Database ${connectionStringPrefix} has been successfully dropped.`,
'AppRepository',
);
} catch (error) {
Logger.error(
'Error while deleting the database:',
error,
'AppRepository',
);
}
}
}
2 changes: 1 addition & 1 deletion src/app-auth/schemas/app.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export class App {

@IsOptional()
@IsString()
@Prop({ required: false, unique: true })
@Prop({ required: false, unique: false })
subdomain: string;
@Prop({ required: true })
@ApiProperty({
Expand Down
91 changes: 81 additions & 10 deletions src/app-auth/services/app-auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
BadRequestException,
} from '@nestjs/common';
import { SigningStargateClient } from '@cosmjs/stargate';
import { CreateAppDto } from '../dtos/create-app.dto';
import { CreateAppDto, DeleteAppResponse } from '../dtos/create-app.dto';
import { App, createAppResponse } from 'src/app-auth/schemas/app.schema';
import { AppRepository } from '../repositories/app.repository';
import { UpdateAppDto } from '../dtos/update-app.dto';
Expand Down Expand Up @@ -35,10 +35,13 @@ import {
MSG_UPDATE_DID_TYPEURL,
} from 'src/utils/authz';
import { AuthzCreditService } from 'src/credits/services/credits.service';
import { AuthZCreditsRepository } from 'src/credits/repositories/authz.repository';
import { EdvClientKeysManager } from 'src/edv/services/edv.singleton';

enum GRANT_TYPES {
access_service_kyc = 'access_service_kyc',
access_service_ssi = 'access_service_ssi',
access_service_quest = 'access_service_quest',
}

@Injectable()
Expand All @@ -55,6 +58,7 @@ export class AppAuthService {
private readonly supportedServices: SupportedServiceService,
private readonly userRepository: UserRepository,
private readonly authzCreditService: AuthzCreditService,
private readonly authzCreditRepository: AuthZCreditsRepository,
) {}

async createAnApp(
Expand Down Expand Up @@ -150,7 +154,10 @@ export class AppAuthService {
'createAnApp() method: before creating new app doc in db',
'AppAuthService',
);
const subdomain = await this.getRandomSubdomain();
let subdomain;
if (service.id !== SERVICE_TYPES.QUEST) {
subdomain = await this.getRandomSubdomain();
}
// AUTHZ
if (service.id == SERVICE_TYPES.SSI_API) {
// Perform AuthZ Grant
Expand Down Expand Up @@ -247,7 +254,10 @@ export class AppAuthService {
const appResponse: createAppResponse = {
...appData['_doc'],
apiSecretKey,
tenantUrl: this.getTenantUrl(appData.subdomain, appData.services[0]), // only one service per app
tenantUrl:
appData.services[0].id === 'QUEST'
? appData.services[0].domain
: this.getTenantUrl(appData.subdomain, appData.services[0]),
};

delete appResponse.userId;
Expand Down Expand Up @@ -523,7 +533,7 @@ export class AppAuthService {
return this.getAppResponse(app);
}

async deleteApp(appId: string, userId: string): Promise<App> {
async deleteApp(appId: string, userId: string): Promise<DeleteAppResponse> {
Logger.log('deleteApp() method: starts....', 'AppAuthService');

let appDetail = await this.appRepository.findOne({ appId, userId });
Expand All @@ -532,13 +542,40 @@ export class AppAuthService {

throw new NotFoundException([`No App found for appId ${appId}`]);
}
//commenting this code as delete operation is not implemented in edvClient

// const { edvId, edvDocId } = appDetail;
// await this.edvService.init(edvId);
// await this.edvService.deleteDoc(edvDocId);
const { edvId, kmsId } = appDetail;
const appDataFromVault = await globalThis.kmsVault.getDecryptedDocument(
kmsId,
);
if (!appDataFromVault) {
throw new BadRequestException('App detail does not exists in datavault');
}
const appKmsVaultWallet = await VaultWalletManager.getWallet(
appDataFromVault.mnemonic,
);
const kmsVaultManager = new EdvClientKeysManager();
const appKmsVault = await kmsVaultManager.createVault(
appKmsVaultWallet,
edvId,
);
try {
await appKmsVault.deleteVault(edvId);
await globalThis.kmsVault.deleteDocument(kmsId);
} catch (vaultError) {
Logger.error(
`Error deleting KMS or EDV vault: ${vaultError}`,
'AppAuthService',
);
throw new BadRequestException(['Failed to delete vault']);
}
// delete app db also
if (!appDetail.services || appDetail.services.length === 0) {
throw new BadRequestException(['Invalid app']);
}
const appDbConnectionSuffix = `service:${appDetail.services[0].dBSuffix}:${appDetail.subdomain}`;
await this.appRepository.findAndDeleteServiceDB(appDbConnectionSuffix);
this.authzCreditRepository.deleteAuthzDetail({ appId });
appDetail = await this.appRepository.findOneAndDelete({ appId, userId });
return appDetail;
return { appId: appDetail.appId };
}

private checkIfDateExpired(expiryDate: Date | null) {
Expand Down Expand Up @@ -632,6 +669,21 @@ export class AppAuthService {
}
break;
}
case SERVICE_TYPES.QUEST: {
grant_type = GRANT_TYPES.access_service_quest;
if (userDetails.accessList && userDetails.accessList.length > 0) {
accessList = userDetails.accessList
.map((x) => {
if (x.serviceType === SERVICE_TYPES.QUEST) {
if (!this.checkIfDateExpired(x.expiryDate)) {
return x.access;
}
}
})
.filter((x) => x != undefined);
}
break;
}
default: {
throw new BadRequestException('Invalid service ' + appDetail.appId);
}
Expand Down Expand Up @@ -702,6 +754,8 @@ export class AppAuthService {
break;
case GRANT_TYPES.access_service_kyc:
break;
case GRANT_TYPES.access_service_quest:
break;
default: {
throw new BadRequestException(
'Grant type not supported, supported grant types are: ' +
Expand Down Expand Up @@ -763,6 +817,23 @@ export class AppAuthService {
.filter((x) => x != undefined);
break;
}
case SERVICE_TYPES.QUEST: {
if (grantType != 'access_service_quest') {
throw new BadRequestException(
'Invalid grant type for this service ' + appId,
);
}
accessList = userDetails.accessList
.map((x) => {
if (x.serviceType === SERVICE_TYPES.QUEST) {
if (!this.checkIfDateExpired(x.expiryDate)) {
return x.access;
}
}
})
.filter((x) => x != undefined);
break;
}
default: {
throw new BadRequestException('Invalid service ' + appId);
}
Expand Down
3 changes: 3 additions & 0 deletions src/credits/repositories/authz.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ export class AuthZCreditsRepository {
async find(authZCreditsFilterQuery: FilterQuery<AuthZCredits>) {
return this.authZCreditModel.find(authZCreditsFilterQuery);
}
async deleteAuthzDetail(authZCreditsFilterQuery: FilterQuery<AuthZCredits>) {
return this.authZCreditModel.findOneAndDelete(authZCreditsFilterQuery);
}
}
23 changes: 20 additions & 3 deletions src/edv/services/edvClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export interface IEdvClientManager {
initate(): Promise<IEdvClientManager>;
insertDocument(doc: EDVDocType): any;
updateDocument(): any;
deleteDocument(): any;
deleteDocument(id: string): any;
deleteVault(id: string): any;
getDecryptedDocument(id: string): Promise<any>;
getDocument(id: string): Promise<IResponse>;
prepareEdvDocument(
Expand Down Expand Up @@ -113,10 +114,26 @@ export class EdvClientManger implements IEdvClientManager {
updateDocument(): any {
throw new Error('not implemented');
}
deleteDocument(): any {
throw new Error('not implemented');
async deleteDocument(id: string) {
Logger.log(
'deleteDocument() method: starts, deleting docs from edvClient',
'EdvService',
);
const resp: IResponse = await this.vault.deleteDoc({
edvId: this.edvId,
documentId: id,
});
return resp;
}

async deleteVault(edvId: string) {
Logger.log(
'deletVault() method: starts, deleting entire vault',
'EdvService',
);
const resp = await this.vault.deleteVaultData({ edvId: edvId });
return resp;
}
async getDocument(id: string): Promise<IResponse> {
Logger.log(
'getDocument() method: starts, fetching docs from edvClient',
Expand Down
Loading

0 comments on commit 762a630

Please sign in to comment.