diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 00c90cf4..8910b737 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -22,6 +22,7 @@ import * as url from 'url'; import { SupportedServiceService } from 'src/supported-service/services/supported-service.service'; import { APP_ENVIRONMENT, + Context, SERVICE_TYPES, } from 'src/supported-service/services/iServiceList'; import { UserRepository } from 'src/user/repository/user.repository'; @@ -35,6 +36,12 @@ import { CustomerOnboarding } from 'src/customer-onboarding/schemas/customer-onb import { Model } from 'mongoose'; import { getAccessListForModule } from 'src/utils/utils'; import { TokenModule } from 'src/config/access-matrix'; +import { redisClient } from 'src/utils/redis.provider'; +import { + EXPIRY_CONFIG, + getSecondsFromUnit, + TIME_UNIT, +} from 'src/utils/time-constant'; export enum GRANT_TYPES { access_service_kyc = 'access_service_kyc', @@ -61,7 +68,7 @@ export class AppAuthService { @InjectModel(CustomerOnboarding.name) private readonly onboardModel: Model, private readonly webpageConfigRepo: WebPageConfigRepository, - ) {} + ) { } async createAnApp( createAppDto: CreateAppDto, @@ -393,9 +400,8 @@ export class AppAuthService { } private async verifyDNS01(domain: URL, txt: string) { - const resolveDNSURL = `https://dns.google/resolve?name=${ - new URL(domain).host - }&type=TXT`; + const resolveDNSURL = `https://dns.google/resolve?name=${new URL(domain).host + }&type=TXT`; const actuaTxt = txt; const res = await fetch(resolveDNSURL, { headers: { @@ -435,7 +441,7 @@ export class AppAuthService { if (fetchedTxtRecord && fetchedTxtRecord.error) { throw new BadRequestException([ fetchedTxtRecord.error?.message + - '. If you have already added then it may take a while to complete. Please try again in sometime.', + '. If you have already added then it may take a while to complete. Please try again in sometime.', ]); } if (fetchedTxtRecord.verified) { @@ -541,7 +547,49 @@ export class AppAuthService { { appId, userId }, updataAppDto, ); - return this.getAppResponse(app); + const updatedapp = await this.getAppResponse(app); + // update redis + + const baseKey = appId; + const dashboardRedisKey = `${appId}_${Context.idDashboard}`; + + const updatedFields = { + whitelistedCors: updatedapp.whitelistedCors, + env: updatedapp.env ?? APP_ENVIRONMENT.dev, + appName: updatedapp.appName, + }; + + const [baseDataString, dashboardDataString] = await Promise.all([ + redisClient.get(baseKey), + redisClient.get(dashboardRedisKey), + ]); + + const updatePromises = []; + + if (baseDataString) { + const baseData = JSON.parse(baseDataString); + const updatedBase = { ...baseData, ...updatedFields }; + updatePromises.push( + redisClient.set(baseKey, JSON.stringify(updatedBase), 'KEEPTTL'), + ); + } + + if (dashboardDataString) { + const dashboardData = JSON.parse(dashboardDataString); + const updatedDashboard = { ...dashboardData, ...updatedFields }; + updatePromises.push( + redisClient.set( + dashboardRedisKey, + JSON.stringify(updatedDashboard), + 'KEEPTTL', + ), + ); + } + + if (updatePromises.length > 0) { + await Promise.all(updatePromises); + } + return updatedapp; } async deleteApp(appId: string, userId: string): Promise { @@ -619,6 +667,12 @@ export class AppAuthService { } this.authzCreditRepository.deleteAuthzDetail({ appId }); appDetail = await this.appRepository.findOneAndDelete({ appId, userId }); + // delete from redis + await Promise.all([ + redisClient.del(appId), + redisClient.del(`${appId}_${Context.idDashboard}`), + ]); + Logger.debug(`Redis cache cleaned for appId: ${appId}`); return { appId: appDetail.appId }; } @@ -643,7 +697,6 @@ export class AppAuthService { grantType, ): Promise<{ access_token; expiresIn; tokenType }> { Logger.log('generateAccessToken() method: starts....', 'AppAuthService'); - const apikeyIndex = appSecreatKey.split('.')[0]; const appDetail = await this.appRepository.findOne({ apiKeyPrefix: apikeyIndex, @@ -682,11 +735,25 @@ export class AppAuthService { const serviceType = appDetail.services[0]?.id; // TODO: remove this later let grant_type = ''; let accessList = []; + const redisKey = appDetail.appId; + const savedSession = await redisClient.get(redisKey); + if (savedSession) { + Logger.log('Using redis cached session', 'AppAuthService'); + const sessionJson = JSON.parse(savedSession); + const jwtPayload = { + appId: sessionJson.appId, + appName: sessionJson.appName, + grantType: sessionJson.grantType, + subdomain: sessionJson.subdomain, + sessionId: redisKey, + }; + return this.getAccessToken(jwtPayload, expiresin); + } switch (serviceType) { case SERVICE_TYPES.SSI_API: { grant_type = GRANT_TYPES.access_service_ssi; accessList = getAccessListForModule( - TokenModule.VERIFIER, + TokenModule.APP_AUTH, SERVICE_TYPES.SSI_API, ); break; @@ -703,7 +770,7 @@ export class AppAuthService { } grant_type = grantType || GRANT_TYPES.access_service_kyc; accessList = getAccessListForModule( - TokenModule.VERIFIER, + TokenModule.APP_AUTH, SERVICE_TYPES.CAVACH_API, ); break; @@ -711,7 +778,7 @@ export class AppAuthService { case SERVICE_TYPES.QUEST: { grant_type = GRANT_TYPES.access_service_quest; accessList = getAccessListForModule( - TokenModule.VERIFIER, + TokenModule.APP_AUTH, SERVICE_TYPES.QUEST, ); break; @@ -726,15 +793,37 @@ export class AppAuthService { `You are not authorized to access service of type ${serviceType}`, ]); } - - return this.getAccessToken(grant_type, appDetail, expiresin, accessList); + const jwtPayload = { + appId: appDetail.appId, + appName: appDetail.appName, + grantType: grant_type, + subdomain: appDetail.subdomain, + sessionId: redisKey, + }; + await this.storeDataInRedis(grant_type, appDetail, accessList, redisKey); + return this.getAccessToken(jwtPayload, expiresin); } public async getAccessToken( + data, + time = 4, + unit: TIME_UNIT = TIME_UNIT.HOUR, + ) { + const secret = this.config.get('JWT_SECRET'); + const token = await this.jwt.signAsync(data, { + expiresIn: `${time}${unit}`, + secret, + }); + const expiresIn = getSecondsFromUnit(time, unit); + Logger.log('generateAccessToken() method: ends....', 'AppAuthService'); + + return { access_token: token, expiresIn, tokenType: 'Bearer' }; + } + public async storeDataInRedis( grantType, appDetail, - expiresin = 4, accessList = [], + sessionId, ) { const payload = { appId: appDetail.appId, @@ -763,27 +852,22 @@ export class AppAuthService { ) { payload['dependentServices'] = appDetail.dependentServices; } - - const secret = this.config.get('JWT_SECRET'); - - const token = await this.jwt.signAsync(payload, { - expiresIn: expiresin.toString() + 'h', - secret, - }); - const expiresIn = (expiresin * 1 * 60 * 60 * 1000) / 1000; - Logger.log('generateAccessToken() method: ends....', 'AppAuthService'); - - return { access_token: token, expiresIn, tokenType: 'Bearer' }; + Logger.log('storeDataInRedis() method: ends....', 'AppAuthService'); + redisClient.set( + sessionId, + JSON.stringify(payload), + 'EX', + EXPIRY_CONFIG.SERVICE_ACCESS.redisExpiryTime, + ); } - //access_service_ssi - //access_service_kyc - async grantPermission( grantType: string, appId: string, user, ): Promise<{ access_token; expiresIn; tokenType }> { + const sessionId = `${appId}_${Context.idDashboard}`; + const savedSession = await redisClient.get(sessionId); switch (grantType) { case GRANT_TYPES.access_service_ssi: break; @@ -796,20 +880,34 @@ export class AppAuthService { default: { throw new BadRequestException([ 'Grant type not supported, supported grant types are: ' + - GRANT_TYPES.access_service_kyc + - ',' + - GRANT_TYPES.access_service_ssi, + GRANT_TYPES.access_service_kyc + + ',' + + GRANT_TYPES.access_service_ssi, ]); } } + if (savedSession) { + const app = JSON.parse(savedSession); + const dataToStore = { + appId, + appName: app.appName, + grantType, + subdomain: app.subdomain, + sessionId, + }; + return this.getAccessToken( + dataToStore, + EXPIRY_CONFIG.SERVICE_ACCESS.jwtTime, + EXPIRY_CONFIG.SERVICE_ACCESS.jwtUnit, + ); + } const app = await this.getAppById(appId, user.userId); if (!app) { throw new BadRequestException([ 'Invalid service id or you do not have access of this service', ]); } - const userDetails = user; if (!userDetails) { throw new UnauthorizedException([ @@ -868,7 +966,18 @@ export class AppAuthService { `You are not authorized to access service of type ${serviceType}`, ]); } - - return this.getAccessToken(grantType, app, 12, accessList); + const tokenPayload = { + appId, + appName: app.appName, + grantType, + subdomain: app.subdomain, + sessionId, + }; + await this.storeDataInRedis(grantType, app, accessList, sessionId); + return this.getAccessToken( + tokenPayload, + EXPIRY_CONFIG.SERVICE_ACCESS.jwtTime, + EXPIRY_CONFIG.SERVICE_ACCESS.jwtUnit, + ); } } diff --git a/src/config/access-matrix.ts b/src/config/access-matrix.ts index f7f7e409..2c1a76aa 100644 --- a/src/config/access-matrix.ts +++ b/src/config/access-matrix.ts @@ -27,8 +27,10 @@ export const KYC_ACCESS_MATRIX = { SERVICES.CAVACH_API.ACCESS_TYPES.CHECK_LIVE_STATUS, SERVICES.CAVACH_API.ACCESS_TYPES.READ_WIDGET_CONFIG, SERVICES.CAVACH_API.ACCESS_TYPES.READ_USER_CONSENT, + SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_AUTH, ], [TokenModule.APP_AUTH]: [ + SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_SESSION, SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_USER_CONSENT, SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_PASSIVE_LIVELINESS, SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_DOC_OCR, diff --git a/src/credits/credits.module.ts b/src/credits/credits.module.ts index 0cee0c0b..f4d52726 100644 --- a/src/credits/credits.module.ts +++ b/src/credits/credits.module.ts @@ -55,7 +55,13 @@ export class CreditModule implements NestModule { method: RequestMethod.GET, }) .forRoutes(CreditsController); - consumer.apply(JWTAccessAccountMiddleware).forRoutes(CreditsController); + consumer + .apply(JWTAccessAccountMiddleware) + .exclude({ + path: '/api/v1/credits/authz/:appId', + method: RequestMethod.GET, + }) + .forRoutes(CreditsController); consumer.apply(RateLimitMiddleware).forRoutes(CreditsController); } } diff --git a/src/customer-onboarding/dto/create-customer-onboarding.dto.ts b/src/customer-onboarding/dto/create-customer-onboarding.dto.ts index e2d65e11..fc8bd3be 100644 --- a/src/customer-onboarding/dto/create-customer-onboarding.dto.ts +++ b/src/customer-onboarding/dto/create-customer-onboarding.dto.ts @@ -26,6 +26,7 @@ import { } from '../constants/enum'; import { IsPhoneNumberByCountry } from 'src/utils/customDecorator/validate-phone-no-country.decorator'; import { Type } from 'class-transformer'; +import { IsUrlOrBase64Image } from 'src/utils/customDecorator/IsUrlOrBase64Image.decorator'; export class CustomerOnboardingBasicDto { @ApiProperty({ @@ -47,9 +48,7 @@ export class CustomerOnboardingBasicDto { @IsOptional() @IsNotEmpty() @IsString() - @IsUrl({ - require_protocol: true, - }) + @IsUrlOrBase64Image() companyLogo?: string; @ApiProperty({ name: 'customerEmail', diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index f2e78db0..84954ce6 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -20,7 +20,9 @@ import { } from 'src/app-auth/services/app-auth.service'; import { APP_ENVIRONMENT, + Context, SERVICE_TYPES, + SERVICES, } from 'src/supported-service/services/iServiceList'; import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; @@ -35,7 +37,7 @@ import { LogDetail, } from '../schemas/customer-onboarding.schema'; import { AppRepository } from 'src/app-auth/repositories/app.repository'; -import { sanitizeUrl } from 'src/utils/utils'; +import { getAccessListForModule, sanitizeUrl } from 'src/utils/utils'; import { RoleRepository } from 'src/roles/repository/role.repository'; import { ONBORDING_CONSTANT_DATA } from '../constants/en'; import { WebpageConfigService } from 'src/webpage-config/services/webpage-config.service'; @@ -44,6 +46,9 @@ import { PageType, } from 'src/webpage-config/dto/create-webpage-config.dto'; import getOnboardingRetryNotificationMail from 'src/mail-notification/constants/templates/request-retry-onboarding'; +import { redisClient } from 'src/utils/redis.provider'; +import { EXPIRY_CONFIG, TIME } from 'src/utils/time-constant'; +import { TokenModule } from 'src/config/access-matrix'; @Injectable() export class CustomerOnboardingService { @@ -221,8 +226,10 @@ export class CustomerOnboardingService { payload: any, secret: string, ): Promise { + const config = EXPIRY_CONFIG.CREDIT_TOKEN; + const expiresIn = `${config.jwtTime}${config.jwtUnit}`; // e.g. "5m" Logger.log('inside generateCreditToken method', 'generateCreditToken'); - return this.jwt.signAsync(payload, { expiresIn: '5m', secret }); + return this.jwt.signAsync(payload, { expiresIn, secret }); } /** @@ -241,12 +248,14 @@ export class CustomerOnboardingService { tenantUrl: string, secret: string, whitelistedCors: string[] = ['*'], + accessList: string[], ) { Logger.debug(tenantUrl); Logger.log( `Inside handleCreditService() to fund credit to the service with tenantUrl ${tenantUrl}`, 'CustomerOnboardingService', ); + const sessionId = `credit:${serviceInfo.appId}:${Date.now()}`; const creditPayload = { serviceId: serviceInfo.appId, purpose: 'CreditRecharge', @@ -257,9 +266,21 @@ export class CustomerOnboardingService { subdomain: serviceInfo.subdomain, grantType, whitelistedCors, + accessList, }; - - const creditToken = await this.generateCreditToken(creditPayload, secret); + await redisClient.set( + sessionId, + JSON.stringify(creditPayload), + 'EX', + EXPIRY_CONFIG.CREDIT_TOKEN.redisExpiryTime, + ); + const tokenPayload = { + appId: serviceInfo.appId, + sessionId, + subdomain: serviceInfo.subdomain, + grantType, + }; + const creditToken = await this.generateCreditToken(tokenPayload, secret); let headers: Record = { authorization: `Bearer ${creditToken}`, 'Content-Type': 'application/json', @@ -333,6 +354,8 @@ export class CustomerOnboardingService { let kycSubdomain = customerOnboardingData?.kycSubdomain; let ssiTenantUrl = this.getTenantUrl(ssiBaseDomain, ssiSubdomain); let kycTenantUrl = this.getTenantUrl(cavachBaseDomain, kycSubdomain); + let ssiRedisKey = `${customerOnboardingData?.ssiServiceId}_${Context.idDashboard}`; + let kycRedisKey = `${customerOnboardingData?.kycServiceId}_${Context.idDashboard}`; // Get remaining steps const lastStep = @@ -409,6 +432,7 @@ export class CustomerOnboardingService { 'CREATE_SSI_SERVICE step ends', 'CustomerOnboardingService', ); + ssiRedisKey = `${ssiService.appId}_${Context.idDashboard}`; break; } @@ -430,6 +454,7 @@ export class CustomerOnboardingService { ssiTenantUrl, secret, ssiService?.whitelistedCors, + [SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDIT], ); Logger.debug( 'CREDIT_SSI_SERVICE step ends', @@ -448,11 +473,31 @@ export class CustomerOnboardingService { appId: customerOnboardingData.ssiServiceId, }); } - + const ssiServiceDetail = await redisClient.get(ssiRedisKey); + if (!ssiServiceDetail) { + await this.appAuthService.storeDataInRedis( + GRANT_TYPES.access_service_ssi, + ssiService, + getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.SSI_API, + ), + ssiRedisKey, + ); + } ssiAccessToken = await this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_ssi, - ssiService, - 4, + { + appId: + ssiService?.appId || customerOnboardingData.ssiServiceId, + grantType: GRANT_TYPES.access_service_ssi, + appname: ssiService?.appName, + subdomain: + ssiService?.subdomain || + customerOnboardingData.ssiSubdomain, + sessionId: ssiRedisKey, + }, + EXPIRY_CONFIG.ONBOARDING_ACCESS.jwtTime, + EXPIRY_CONFIG.ONBOARDING_ACCESS.jwtUnit, ); const didData = await this.makeExternalRequest( @@ -462,6 +507,7 @@ export class CustomerOnboardingService { headers: { authorization: `Bearer ${ssiAccessToken.access_token}`, 'Content-Type': 'application/json', + origin: ssiService.whitelistedCors[0], }, body: JSON.stringify({ namespace: 'testnet' }), }, @@ -480,12 +526,33 @@ export class CustomerOnboardingService { 'REGISTER_DID step started', 'CustomerOnboardingService', ); + const ssiServiceDetail = await redisClient.get(ssiRedisKey); + if (!ssiServiceDetail) { + await this.appAuthService.storeDataInRedis( + GRANT_TYPES.access_service_ssi, + ssiService, + getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.SSI_API, + ), + ssiRedisKey, + ); + } ssiAccessToken = ssiAccessToken || (await this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_ssi, - ssiService, - 4, + { + appId: + ssiService?.appId || customerOnboardingData.ssiServiceId, + grantType: GRANT_TYPES.access_service_ssi, + appname: ssiService?.appName, + subdomain: + ssiService?.subdomain || + customerOnboardingData.ssiSubdomain, + sessionId: ssiRedisKey, + }, + EXPIRY_CONFIG.ONBOARDING_ACCESS.jwtTime, + EXPIRY_CONFIG.ONBOARDING_ACCESS.jwtUnit, )); const didToRegister = @@ -502,6 +569,7 @@ export class CustomerOnboardingService { headers: { authorization: `Bearer ${ssiAccessToken.access_token}`, 'Content-Type': 'application/json', + origin: ssiService.whitelistedCors[0], }, }, 'Failed to resolve DID', @@ -565,6 +633,7 @@ export class CustomerOnboardingService { onboardingUpdateData.kycSubdomain = kycService.subdomain; onboardingUpdateData.kycServiceId = kycService.appId; kycTenantUrl = this.getTenantUrl(cavachBaseDomain, kycSubdomain); + kycRedisKey = `${kycService?.appId}_${Context.idDashboard}`; Logger.debug( 'CREATE_KYC_SERVICE step ends', 'CustomerOnboardingService', @@ -624,6 +693,7 @@ export class CustomerOnboardingService { kycTenantUrl, secret, kycService?.whitelistedCors, + [SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_CREDIT], ); Logger.debug( 'CREDIT_KYC_SERVICE step ends', @@ -641,10 +711,31 @@ export class CustomerOnboardingService { appId: customerOnboardingData.kycServiceId, }); } + const kycServiceDetail = await redisClient.get(kycRedisKey); + if (!kycServiceDetail) { + await this.appAuthService.storeDataInRedis( + GRANT_TYPES.access_service_kyc, + kycService, + getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.CAVACH_API, + ), + kycRedisKey, + ); + } kycAccessToken = await this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_kyc, - kycService, - 4, + { + appId: + kycService?.appId || customerOnboardingData.kycServiceId, + appName: kycService.appName, + grantType: GRANT_TYPES.access_service_kyc, + subdomain: + kycService?.subdomain || + customerOnboardingData.kycSubdomain, + sessionId: kycRedisKey, + }, + EXPIRY_CONFIG.ONBOARDING_ACCESS.jwtTime, + EXPIRY_CONFIG.ONBOARDING_ACCESS.jwtUnit, ); const requestBody = { faceRecog: true, @@ -683,7 +774,7 @@ export class CustomerOnboardingService { headers: { 'x-kyc-access-token': kycAccessToken.access_token, 'Content-Type': 'application/json', - origin: '*', + origin: kycService.whitelistedCors[0], }, body: JSON.stringify(requestBody), }, @@ -701,9 +792,6 @@ export class CustomerOnboardingService { 'CustomerOnboardingService', ); - const user = await this.userRepository.findOne({ - userId: userId, - }); const serviceId = kycService?.appId || customerOnboardingData.kycServiceId; await this.webPageConfig.storeWebPageConfigDetial(serviceId, { diff --git a/src/social-login/controller/email-otp-login.controller.ts b/src/social-login/controller/email-otp-login.controller.ts index 21df21ce..c91ac5ee 100644 --- a/src/social-login/controller/email-otp-login.controller.ts +++ b/src/social-login/controller/email-otp-login.controller.ts @@ -26,7 +26,7 @@ import { AppError } from 'src/app-auth/dtos/fetch-app.dto'; import { Request } from 'express'; import { SocialLoginService } from '../services/social-login.service'; import { UnauthorizedError } from '../dto/response.dto'; -import { TOKEN } from 'src/utils/time-constant'; +import { COOKIE_CONFIG as TOKEN } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') @Controller('/api/v1/auth/email/otp') diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index bbe3a8a5..38f37110 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -43,7 +43,7 @@ import { import { AppError } from 'src/app-auth/dtos/fetch-app.dto'; import { UserRole } from 'src/user/schema/user.schema'; import { ERROR_MESSAGE, ERROR_MESSAGE as MFA_MESSAGE } from '../constants/en'; -import { TOKEN } from 'src/utils/time-constant'; +import { COOKIE_CONFIG as TOKEN } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') @Controller('api/v1') diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 2d2779af..1b523a2b 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -23,7 +23,7 @@ import { } from '../dto/request.dto'; import { UserDocument, UserRole } from 'src/user/schema/user.schema'; import { redisClient } from 'src/utils/redis.provider'; -import { TIME } from 'src/utils/time-constant'; +import { COOKIE_CONFIG, EXPIRY_CONFIG, TIME } from 'src/utils/time-constant'; import { MFA_ERROR, ERROR_MESSAGE, REFRESH_TOKEN_ERROR } from '../constants/en'; @Injectable() @@ -200,7 +200,7 @@ export class SocialLoginService { sessionKey, JSON.stringify(sessionDetail), 'EX', - TIME.WEEK, + EXPIRY_CONFIG.LOGIN.redisExpiryTime, ); return { isVerified: false }; } @@ -444,14 +444,14 @@ export class SocialLoginService { sessionKey, JSON.stringify(sessionObj), 'EX', - TIME.WEEK, + COOKIE_CONFIG.AUTH.redisExpiryTime, ); const newRefreshToken = uuidv4(); await redisClient.set( `refresh:${newRefreshToken}`, session.sid, 'EX', - TIME.WEEK, + COOKIE_CONFIG.REFRESH.redisExpiryTime, ); return { isVerified, diff --git a/src/supported-service/services/iServiceList.ts b/src/supported-service/services/iServiceList.ts index a8fd3abc..90d2d34a 100644 --- a/src/supported-service/services/iServiceList.ts +++ b/src/supported-service/services/iServiceList.ts @@ -92,6 +92,7 @@ export namespace SERVICES { WRITE_CREDIT = 'WRITE_CREDIT', READ_CREDIT = 'READ_CREDIT', CHECK_LIVE_STATUS = 'CHECK_LIVE_STATUS', + WRITE_AUTH='WRITE_AUTH', } } @@ -112,3 +113,7 @@ export namespace SERVICES { } } } + +export enum Context { + idDashboard = 'idDashboard', +} diff --git a/src/utils/customDecorator/IsUrlOrBase64Image.decorator.ts b/src/utils/customDecorator/IsUrlOrBase64Image.decorator.ts new file mode 100644 index 00000000..507673a4 --- /dev/null +++ b/src/utils/customDecorator/IsUrlOrBase64Image.decorator.ts @@ -0,0 +1,30 @@ +import { + registerDecorator, + ValidationArguments, + ValidationOptions, +} from 'class-validator'; + +export function IsUrlOrBase64Image(options?: ValidationOptions) { + return function (object: any, propertyName: string) { + registerDecorator({ + name: 'IsImageUrlOrBase64', + target: object.constructor, + propertyName, + options, + validator: { + validate(value: any, args: ValidationArguments) { + if (!value) return true; // allow empty if optional + + const urlRegex = /^(https?:\/\/)[^\s]+$/i; + const base64Regex = + /^data:(image\/(png|jpe?g|gif|webp));base64,[A-Za-z0-9+/=]+$/; + + return urlRegex.test(value) || base64Regex.test(value); + }, + defaultMessage(args: ValidationArguments) { + return `${args.property} must be a valid URL or Base64 encoded image`; + }, + }, + }); + }; +} diff --git a/src/utils/time-constant.ts b/src/utils/time-constant.ts index 16283f6d..e65b65fa 100644 --- a/src/utils/time-constant.ts +++ b/src/utils/time-constant.ts @@ -6,22 +6,16 @@ export const TIME = { WEEK: 7 * 24 * 60 * 60 * 1, }; -export const TOKEN_MAX_AGE = { - AUTH_TOKEN: 4 * TIME.HOUR * 1000, // 4 hours - REFRESH_TOKEN: 7 * TIME.DAY * 1000, // 7 days -}; -export const TOKEN = { +export const COOKIE_CONFIG = { AUTH: { name: 'accessToken', expiry: 30 * TIME.MINUTE * 1000, + redisExpiryTime: TIME.WEEK, }, REFRESH: { name: 'refreshToken', expiry: 7 * TIME.DAY * 1000, - }, - VERIFIER_TOKEN: { - name: 'verifierPageToken', - expiry: 30 * TIME.MINUTE, + redisExpiryTime: TIME.WEEK, }, }; @@ -29,3 +23,48 @@ export enum JobNames { SEND_EMAIL_LOGIN_OTP = 'send-email-login-otp', SEND_TEAM_MATE_INVITATION_MAIL = 'sendTeamMatemail', } + +export enum TIME_UNIT { + SECOND = 's', + MINUTE = 'm', + HOUR = 'h', + DAY = 'd', +} +export const getSecondsFromUnit = (time: number, unit: TIME_UNIT) => { + return time * TIME[unit.toUpperCase()]; +}; + +/*** + * EXPIRY_CONFIG defines the expiry times for different types of tokens + * and Redis cache entries used across the system. + * */ + +export const EXPIRY_CONFIG = { + // Token generated for kyc verifier page + VERIFIER_ACCESS: { + jwtTime: 30, + jwtUnit: TIME_UNIT.MINUTE, + redisExpiryTime: 30 * TIME.MINUTE, + }, + // Token generated during onboarding to provide credit access + ONBOARDING_ACCESS: { + jwtTime: 10, + jwtUnit: TIME_UNIT.MINUTE, + }, + // Token used to access KYC and SSI services. + SERVICE_ACCESS: { + jwtTime: 12, + jwtUnit: TIME_UNIT.HOUR, + redisExpiryTime: TIME.WEEK, + }, + // Token generated for providing credit + CREDIT_TOKEN: { + jwtTime: 5, + jwtUnit: TIME_UNIT.MINUTE, + redisExpiryTime: 5 * TIME.MINUTE, + }, + // Used for storing login flow–related data in Redis + LOGIN: { + redisExpiryTime: TIME.WEEK, + }, +} as const; diff --git a/src/webpage-config/controller/webpage-config.controller.ts b/src/webpage-config/controller/webpage-config.controller.ts index bb8eb479..1083837f 100644 --- a/src/webpage-config/controller/webpage-config.controller.ts +++ b/src/webpage-config/controller/webpage-config.controller.ts @@ -115,12 +115,7 @@ export class WebpageConfigController { generateWebpageConfigTokens( @Query('appId') appId: string, @Param('id') id: string, - @Req() req, ) { - return this.webpageConfigService.generateWebpageConfigTokens( - id, - appId, - req.user, - ); + return this.webpageConfigService.generateWebpageConfigTokens(id, appId); } } diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 3db9c64d..0e96683c 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -20,10 +20,10 @@ import { WebPageConfigRepository } from '../repositories/webpage-config.reposito import { SERVICE_TYPES } from 'src/supported-service/services/iServiceList'; import { ConfigService } from '@nestjs/config'; import { urlSanitizer } from 'src/utils/sanitizeUrl.validator'; -import { Types } from 'mongoose'; +import { isValidObjectId, Types } from 'mongoose'; import { WEBPAGE_CONFIG_ERRORS } from '../constant/en'; import { redisClient } from 'src/utils/redis.provider'; -import { TOKEN } from 'src/utils/time-constant'; +import { EXPIRY_CONFIG } from 'src/utils/time-constant'; import { getAccessListForModule, REDIS_KEYS } from 'src/utils/utils'; @Injectable() @@ -33,7 +33,7 @@ export class WebpageConfigService { private readonly appAuthService: AppAuthService, private readonly webPageConfigRepo: WebPageConfigRepository, private readonly config: ConfigService, - ) {} + ) { } async storeWebPageConfigDetial( serviceId: string, createWebpageConfigDto: CreateWebpageConfigDto, @@ -145,10 +145,15 @@ export class WebpageConfigService { async fetchAWebPageConfigurationDetail( id: string, ): Promise { + const isValidId = isValidObjectId(id); + const query: any = { + $or: [{ serviceId: id }], + }; + if (isValidId) { + query.$or.push({ _id: new Types.ObjectId(id) }); + } const webpageConfiguration = - await this.webPageConfigRepo.findAWebpageConfig({ - _id: new Types.ObjectId(id), - }); + await this.webPageConfigRepo.findAWebpageConfig(query); if (!webpageConfiguration || webpageConfiguration == null) { throw new NotFoundException([ `No webpage configuration found for id: ${id}`, @@ -254,25 +259,35 @@ export class WebpageConfigService { } return { expiryDate }; } - public async generateWebpageConfigTokens(id, appId, userDetail) { + public async generateWebpageConfigTokens(id, appId) { const redisKey = `${REDIS_KEYS.VERIFIER_PAGE_TOKEN}${id}`; const cachedData = await redisClient.get(redisKey); if (cachedData) return JSON.parse(cachedData); - const [verifierConfig, kycServiceDetail] = await Promise.all([ - this.webPageConfigRepo.findAWebpageConfig({ - _id: new Types.ObjectId(id), - }), - this.appRepository.findOne({ appId }), - ]); - if (!verifierConfig || verifierConfig == null) { + const verifierConfig = await this.webPageConfigRepo.findAWebpageConfig({ + _id: new Types.ObjectId(id), + }); + if (!verifierConfig) { throw new BadRequestException([ WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_NOT_FOUND, ]); } - if (!kycServiceDetail) { - throw new BadRequestException([ - WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_LINKED_APP_NOT_FOUND, - ]); + let kycServiceDetail; + const kycService = await redisClient.get(appId); + if (!kycService) { + kycServiceDetail = await this.appRepository.findOne({ appId }); + if (!kycServiceDetail) { + throw new BadRequestException([ + WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_LINKED_APP_NOT_FOUND, + ]); + } + await this.appAuthService.storeDataInRedis( + GRANT_TYPES.access_service_kyc, + kycServiceDetail, + getAccessListForModule('VERIFIER', SERVICE_TYPES.CAVACH_API), + appId, + ); + } else { + kycServiceDetail = JSON.parse(kycService); } if ( !kycServiceDetail.dependentServices || @@ -282,29 +297,51 @@ export class WebpageConfigService { WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_SSI_SERVICE_NOT_FOUND, ]); } - const ssiServiceId = kycServiceDetail.dependentServices[0]; - const ssiServiceDetail = await this.appRepository.findOne({ - appId: ssiServiceId, - }); - if (!ssiServiceDetail) { - throw new BadRequestException([ - WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_SSI_SERVICE_DOES_NOT_EXIST, - ]); + const ssiServiceId = kycServiceDetail?.dependentServices?.[0]; + let ssiServiceDetail; + const ssiService = await redisClient.get(ssiServiceId); + if (!ssiService) { + ssiServiceDetail = await this.appRepository.findOne({ + appId: ssiServiceId, + }); + if (!ssiServiceDetail) { + throw new BadRequestException([ + WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_SSI_SERVICE_DOES_NOT_EXIST, + ]); + } + await this.appAuthService.storeDataInRedis( + GRANT_TYPES.access_service_ssi, + ssiServiceDetail, + getAccessListForModule('VERIFIER', SERVICE_TYPES.SSI_API), + ssiServiceId, + ); + } else { + ssiServiceDetail = JSON.parse(ssiService); } // generate access tokens const [ssiAccessTokenDetail, kycAccessTokenDetail] = await Promise.all([ this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_ssi, - ssiServiceDetail, - 0.5, - getAccessListForModule('VERIFIER', SERVICE_TYPES.SSI_API), + { + appId: ssiServiceId, + appName: ssiServiceDetail.appName, + grantType: GRANT_TYPES.access_service_ssi, + sessionId: ssiServiceId, + subdomain: ssiServiceDetail.subdomain, + }, + EXPIRY_CONFIG.VERIFIER_ACCESS.jwtTime, + EXPIRY_CONFIG.VERIFIER_ACCESS.jwtUnit, ), this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_kyc, - kycServiceDetail, - 0.5, - getAccessListForModule('VERIFIER', SERVICE_TYPES.CAVACH_API), + { + appId, + appName: kycServiceDetail.appName, + grantType: GRANT_TYPES.access_service_kyc, + sessionId: appId, + subdomain: kycServiceDetail.subdomain, + }, + EXPIRY_CONFIG.VERIFIER_ACCESS.jwtTime, + EXPIRY_CONFIG.VERIFIER_ACCESS.jwtUnit, ), ]); const redisPayload = { @@ -315,7 +352,7 @@ export class WebpageConfigService { redisKey, JSON.stringify(redisPayload), 'EX', - TOKEN.VERIFIER_TOKEN.expiry, + EXPIRY_CONFIG.VERIFIER_ACCESS.redisExpiryTime, ); return { ...redisPayload,