From 4f6fc7ea86671fd53a0d248a9a8b08e6e8ca17ca Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 21 Nov 2025 14:42:36 +0530 Subject: [PATCH 01/55] added deployment file --- .deploy/deployment.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.deploy/deployment.yaml b/.deploy/deployment.yaml index 052958ef..af337a2d 100644 --- a/.deploy/deployment.yaml +++ b/.deploy/deployment.yaml @@ -87,6 +87,8 @@ spec: value: __MAIL_QUEUE__ - name: SERVER_NAME value: __SERVER_NAME__ + - name: NODE_ENV + value: __NODE_ENV__ - name: ALLOWED_ORIGIN value: "__ALLOWED_ORIGIN__" - name: JWT_REFRESH_SECRET From 82b81e3534c7a0a2a3cd9fb6ef19529ae4efa8d3 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 24 Nov 2025 19:02:38 +0530 Subject: [PATCH 02/55] Made login flow secure --- src/app.module.ts | 2 +- .../controller/email-otp-login.controller.ts | 37 +++-- .../controller/social-login.controller.ts | 50 +++---- .../services/social-login.service.ts | 131 ++++++++++-------- src/social-login/social-login.module.ts | 46 +----- src/social-login/strategy/social.strategy.ts | 2 +- .../jwt-authorization.middleware.ts | 42 +++--- src/utils/utils.ts | 13 ++ 8 files changed, 160 insertions(+), 163 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 515e1f7b..4c96d567 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -59,7 +59,7 @@ export class AppModule implements NestModule { .apply(AllowedOriginMiddleware) .exclude( { - path: '/api/v1/login/callback', + path: '/api/v1/auth/google/callback', method: RequestMethod.GET, }, { path: '/api/v1/app/oauth', method: RequestMethod.POST }, diff --git a/src/social-login/controller/email-otp-login.controller.ts b/src/social-login/controller/email-otp-login.controller.ts index 52e6d6e1..a6b525ce 100644 --- a/src/social-login/controller/email-otp-login.controller.ts +++ b/src/social-login/controller/email-otp-login.controller.ts @@ -15,7 +15,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { AllExceptionsFilter } from 'src/utils/utils'; +import { AllExceptionsFilter, getCookieOptions } from 'src/utils/utils'; import { GenerateEmailOtpDto, GenerateEmailOtpResponse, @@ -29,7 +29,7 @@ import { UnauthorizedError } from '../dto/response.dto'; import { TOKEN_MAX_AGE } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') -@Controller('/api/v1/auth/otp') +@Controller('/api/v1/auth/email/otp') export class EmailOtpLoginController { constructor( private readonly config: ConfigService, @@ -45,7 +45,7 @@ export class EmailOtpLoginController { status: 400, type: AppError, }) - @Post('generate') + @Post('request') async generateEmailOtp(@Body() body: GenerateEmailOtpDto) { Logger.log('generateEmailOtp() method starts', 'EmailOtpLoginController'); return this.emailOtpService.generateEmailOtp(body); @@ -74,23 +74,20 @@ export class EmailOtpLoginController { 'EmailOtpLoginController', ); try { - const tokens = await this.socialLoginService.socialLogin(req); - res.cookie('authToken', tokens?.authToken, { - httpOnly: true, - secure: true, - domain: cookieDomain, - maxAge: TOKEN_MAX_AGE.AUTH_TOKEN, - sameSite: 'None', - path: '/', - }); - res.cookie('refreshToken', tokens?.refreshToken, { - httpOnly: true, - secure: true, - sameSite: 'None', - domain: cookieDomain, - maxAge: TOKEN_MAX_AGE.REFRESH_TOKEN, - path: '/', - }); + const result = await this.socialLoginService.socialLogin(req); + if (result.mfaRequired) { + res.redirect(this.config.get('MFA_REDIRECT_URL')); + } + res.cookie( + 'accessToken', + result.accessToken, + getCookieOptions(TOKEN_MAX_AGE.AUTH_TOKEN), + ); + res.cookie( + 'refreshToken', + result.refreshToken, + getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + ); res.redirect(`${this.config.get('REDIRECT_URL')}`); } catch (err) { Logger.error(`Login failed: ${err.message}`, 'EmailOtpLoginController'); diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 522c5644..3661caef 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -24,7 +24,7 @@ import { ApiTags, ApiUnauthorizedResponse, } from '@nestjs/swagger'; -import { AllExceptionsFilter } from 'src/utils/utils'; +import { AllExceptionsFilter, getCookieOptions } from 'src/utils/utils'; import { ConfigService } from '@nestjs/config'; import { AuthResponse, @@ -45,7 +45,7 @@ import { UserRole } from 'src/user/schema/user.schema'; import { TOKEN_MAX_AGE } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') -@Controller() +@Controller('api/v1') export class SocialLoginController { constructor( private readonly socialLoginService: SocialLoginService, @@ -65,7 +65,7 @@ export class SocialLoginController { description: 'Authentication provider', required: true, }) - @Get('/api/v1/login') + @Get('auth/google/authorize') async socialAuthRedirect(@Res() res, @Query() loginProvider) { Logger.log('socialAuthRedirect() method starts', 'SocialLoginController'); const { provider } = loginProvider; @@ -76,33 +76,29 @@ export class SocialLoginController { res.json({ authUrl }); } @ApiExcludeEndpoint() - @Get('/api/v1/login/callback') + @Get('auth/google/callback') @UseGuards(AuthGuard('google')) async socialAuthCallback(@Req() req, @Res() res) { Logger.log('socialAuthCallback() method starts', 'SocialLoginController'); - const cookieDomain = this.config.get('COOKIE_DOMAIN'); - const isProduction = this.config.get('NODE_ENV') === 'production'; - const tokens = await this.socialLoginService.socialLogin(req); - Logger.debug( - `Cookied domain set is ${cookieDomain}`, - 'SocialLoginController', + const result = await this.socialLoginService.socialLogin(req); + if (result.mfaRequired) { + const arrayString = encodeURIComponent( + JSON.stringify(result.authenticators), + ); + return res.redirect( + `${this.config.get('MFA_REDIRECT_URL')}?authenticators=${arrayString}`, + ); + } + res.cookie( + 'accessToken', + result.accessToken, + getCookieOptions(TOKEN_MAX_AGE.AUTH_TOKEN), + ); + res.cookie( + 'refreshToken', + result.refreshToken, + getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), ); - res.cookie('authToken', tokens?.authToken, { - httpOnly: true, - maxAge: TOKEN_MAX_AGE.AUTH_TOKEN, - secure: isProduction, - domain: isProduction ? cookieDomain : undefined, - sameSite: isProduction ? 'None' : 'Lax', - path: '/', - }); - res.cookie('refreshToken', tokens?.refreshToken, { - httpOnly: true, - maxAge: TOKEN_MAX_AGE.REFRESH_TOKEN, - secure: isProduction, - sameSite: isProduction ? 'None' : 'Lax', - domain: isProduction ? cookieDomain : undefined, - path: '/', - }); res.redirect(`${this.config.get('REDIRECT_URL')}`); } @ApiBearerAuth('Authorization') @@ -114,7 +110,7 @@ export class SocialLoginController { status: 401, type: UnauthorizedError, }) - @Post('/api/v1/auth') + @Post('users/me') dispatchUserDetail(@Req() req) { Logger.log('dispatchUserDetail() method starts', 'SocialLoginController'); const userDetail = req.user; diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index e192e633..fe15e2d5 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -22,6 +22,8 @@ import { MFACodeVerificationDto, } 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'; @Injectable() export class SocialLoginService { @@ -42,7 +44,7 @@ export class SocialLoginService { this.config.get('GOOGLE_CALLBACK_URL') || sanitizeUrl( this.config.get('DEVELOPER_DASHBOARD_SERVICE_PUBLIC_EP'), - ) + '/api/v1/login/callback' + ) + '/api/v1/auth/google/callback' }&scope=email%20profile&prompt=select_account&client_id=${this.config.get( 'GOOGLE_CLIENT_ID', )}`; @@ -54,75 +56,58 @@ export class SocialLoginService { } return { authUrl }; } - - async socialLogin(req) { - Logger.log('socialLogin() starts', 'SocialLoginService'); + async socialLogin(req): Promise<{ + mfaRequired: boolean; + refreshToken?: string; + accessToken?: string; + authenticators?: string[]; + }> { + Logger.log( + 'Inside handleGoogleLogin() to create or fetch user detail based on login', + 'SocialLoginService', + ); const { email, name, profileIcon } = req.user; - const rawUrl = this.config.get('INVITATIONURL'); - const url = new URL(rawUrl); - const domain = url.origin; - let userInfo = await this.userRepository.findOne({ + let user = await this.userRepository.findOne({ email, }); - let appUserID; - if (!userInfo) { - appUserID = `${Date.now()}-${uuidv4()}`; - // Giving default access of services... + if (!user) { + const userId = `${Date.now()}-${uuidv4()}`; const ssiAccessList = this.supportedServiceList.getDefaultServicesAccess( SERVICE_TYPES.SSI_API, ); const kycAccessList = this.supportedServiceList.getDefaultServicesAccess( SERVICE_TYPES.CAVACH_API, ); - // const questAccessList = - // this.supportedServiceList.getDefaultServicesAccess(SERVICE_TYPES.QUEST); - userInfo = await this.userRepository.create({ + user = await this.userRepository.create({ email, - userId: appUserID, - name: name, + userId, + name, profileIcon, accessList: [...ssiAccessList, ...kycAccessList], - role: UserRole.ADMIN, }); - } else { - const updates: Partial = {}; - if (!userInfo.name) updates.name = name; - if (!userInfo.profileIcon) updates.profileIcon = profileIcon; - if (Object.keys(updates).length > 0) { - this.userRepository.findOneUpdate({ email }, updates); - } } - Logger.log('socialLogin() starts', 'SocialLoginService'); - - let isVerified = false; - let authenticator = null; - if (userInfo.authenticators && userInfo.authenticators.length > 0) { - authenticator = userInfo.authenticators?.find((x) => { - if (x && x.isTwoFactorAuthenticated) { - return x; - } - }); - isVerified = authenticator - ? authenticator.isTwoFactorAuthenticated - : false; + const updates: Partial = {}; + if (!user.name) updates.name = name; + if (!user.profileIcon) updates.profileIcon = profileIcon; + if (Object.keys(updates).length > 0) + this.userRepository.findOneUpdate({ email }, updates); + const authenticatorList = user.authenticators?.filter( + (auth) => auth?.isTwoFactorAuthenticated, + ); + if (authenticatorList?.length) { + return { + mfaRequired: true, + authenticators: authenticatorList.map((a) => a.type), + }; } - const payload = { - name, - email, - profileIcon, - appUserID: userInfo.userId, - userAccessList: mapUserAccessList(userInfo.accessList), - isTwoFactorEnabled: authenticator ? true : false, - isTwoFactorAuthenticated: req.user.isTwoFactorAuthenticated - ? req.user.isTwoFactorAuthenticated - : false, - authenticatorType: authenticator?.type, - aud: domain, - role: userInfo?.role || UserRole.ADMIN, + const rawUrl = this.config.get('INVITATIONURL'); + const url = new URL(rawUrl); + const domain = url.origin; + const tokens = await this.createSessionAndToken(user, domain); + return { + mfaRequired: false, + ...tokens, }; - const authToken = await this.generateAuthToken(payload); - const refreshToken = await this.generateRefreshToken(payload); - return { authToken, refreshToken }; } async generate2FA(genrate2FADto: Generate2FA, user) { @@ -173,7 +158,6 @@ export class SocialLoginService { secret: authenticatorDetail.secret, }); if (!authenticatorDetail.isTwoFactorAuthenticated && isVerified) { - // update user.authenticators.map((authn) => { if (authn.type === authenticatorType) { authn.isTwoFactorAuthenticated = true; @@ -284,7 +268,7 @@ export class SocialLoginService { }); } - async generateAuthToken(payload: any): Promise { + async generateAuthToken(payload: any, expiry = '4h'): Promise { const secret = this.config.get('JWT_SECRET'); if (!secret) { throw new BadRequestException([ @@ -292,8 +276,41 @@ export class SocialLoginService { ]); } return this.jwt.signAsync(payload, { - expiresIn: '4h', + expiresIn: expiry, secret, }); } + + async createSessionAndToken(user, domain?: string) { + const sessionId = `${Date.now()}-${uuidv4()}`; + const role = user?.role || UserRole.ADMIN; + await redisClient.set( + `session:${sessionId}`, + JSON.stringify({ + sessionId, + userId: user.userId, + role, + refreshVersion: 1, + mfaVerified: false, + mfaEnabled: + user?.authenticator?.some((x) => x.isTwoFactorAuthenticated) ?? false, + }), + 'EX', + TIME.WEEK, + ); + const accessToken = await this.generateAuthToken({ + sid: sessionId, + sub: user.userId, + role, + aud: domain, + }); + const refreshToken = `rt_${uuidv4()}`; + await redisClient.set( + `refresh:${refreshToken}`, + sessionId, + 'EX', + TIME.WEEK, + ); + return { accessToken, refreshToken }; + } } diff --git a/src/social-login/social-login.module.ts b/src/social-login/social-login.module.ts index cc3dc28c..26d85b38 100644 --- a/src/social-login/social-login.module.ts +++ b/src/social-login/social-login.module.ts @@ -14,7 +14,6 @@ import { AppAuthModule } from 'src/app-auth/app-auth.module'; import { JWTAuthorizeMiddleware } from 'src/utils/middleware/jwt-authorization.middleware'; import { SupportedServiceModule } from 'src/supported-service/supported-service.module'; import { SupportedServiceList } from 'src/supported-service/services/service-list'; -import { TwoFAAuthorizationMiddleware } from 'src/utils/middleware/2FA-jwt-authorization.middleware'; import { RateLimitMiddleware } from 'src/utils/middleware/rate-limit.middleware'; import { EmailOtpLoginController } from './controller/email-otp-login.controller'; import { EmailOtpLoginService } from './services/email-otp-login.service'; @@ -42,7 +41,7 @@ export class SocialLoginModule implements NestModule { consumer .apply(WhitelistAppCorsMiddleware) .exclude({ - path: '/api/v1/login/callback', + path: '/api/v1/auth/google/callback', method: RequestMethod.GET, }) .forRoutes(SocialLoginController, EmailOtpLoginController); @@ -50,11 +49,11 @@ export class SocialLoginModule implements NestModule { .apply(JWTAuthorizeMiddleware) .exclude( { - path: '/api/v1/login', + path: '/api/v1/auth/google/authorize', method: RequestMethod.GET, }, { - path: '/api/v1/login/callback', + path: '/api/v1/auth/google/callback', method: RequestMethod.GET, }, { @@ -63,52 +62,19 @@ export class SocialLoginModule implements NestModule { }, ) .forRoutes(SocialLoginController); - consumer - .apply(TwoFAAuthorizationMiddleware) - .exclude( - { - path: '/api/v1/login', - method: RequestMethod.GET, - }, - { - path: '/api/v1/auth/logout', - method: RequestMethod.POST, - }, - { - path: '/api/v1/login/callback', - method: RequestMethod.GET, - }, - { - path: '/api/v1/auth/mfa/generate', - method: RequestMethod.POST, - }, - { - path: '/api/v1/auth/mfa/verify', - method: RequestMethod.POST, - }, - { - path: '/api/v1/auth', - method: RequestMethod.POST, - }, // either do this or send the user data in auth api with a message 2FA is required - { - path: '/api/v1/auth/refresh', - method: RequestMethod.POST, - }, - ) - .forRoutes(SocialLoginController); consumer .apply(RateLimitMiddleware) .exclude( { - path: '/api/v1/login', + path: '/api/v1/auth/google/authorize', method: RequestMethod.GET, }, { - path: '/api/v1/login/callback', + path: '/api/v1/auth/google/callback', method: RequestMethod.GET, }, { - path: '/api/v1/auth', + path: '/api/v1/users/me', method: RequestMethod.POST, }, ) diff --git a/src/social-login/strategy/social.strategy.ts b/src/social-login/strategy/social.strategy.ts index 89e2b73f..2d3a168c 100644 --- a/src/social-login/strategy/social.strategy.ts +++ b/src/social-login/strategy/social.strategy.ts @@ -15,7 +15,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { callbackURL: process.env.GOOGLE_CALLBACK_URL || sanitizeUrl(process.env.DEVELOPER_DASHBOARD_SERVICE_PUBLIC_EP) + - '/api/v1/login/callback', + '/api/v1/auth/google/callback', scope: ['email', 'profile'], session: false, }); diff --git a/src/utils/middleware/jwt-authorization.middleware.ts b/src/utils/middleware/jwt-authorization.middleware.ts index 3c8d1eb9..318fc0da 100644 --- a/src/utils/middleware/jwt-authorization.middleware.ts +++ b/src/utils/middleware/jwt-authorization.middleware.ts @@ -1,4 +1,5 @@ import { + HttpException, Injectable, Logger, NestMiddleware, @@ -8,12 +9,13 @@ import * as jwt from 'jsonwebtoken'; import { NextFunction, Request, Response } from 'express'; import { UserRepository } from 'src/user/repository/user.repository'; import { sanitizeUrl } from '../utils'; +import { redisClient } from '../redis.provider'; @Injectable() export class JWTAuthorizeMiddleware implements NestMiddleware { constructor(private readonly userRepository: UserRepository) {} async use(req: Request, res: Response, next: NextFunction) { Logger.log('Inside JWTAuthorizeMiddleware', 'JWTAuthorizeMiddleware'); - const authToken: string = req?.cookies?.authToken; + const authToken: string = req?.cookies?.accessToken; if (!authToken) { throw new UnauthorizedException([ 'Please pass authorization token in cookie', @@ -56,34 +58,40 @@ export class JWTAuthorizeMiddleware implements NestMiddleware { throw new Error('Token does not contain a valid domain.'); } } + const { sid, sub } = decoded; + if (!sid || !sub) { + throw new UnauthorizedException(['Invalid token']); + } + const sessionRaw = await redisClient.get(`session:${sid}`); + if (!sessionRaw) { + throw new UnauthorizedException(['Session expired or logged out']); + } + const session = JSON.parse(sessionRaw); + if (session.userId !== decoded.sub) { + throw new UnauthorizedException(['Token does not match session']); + } + if (session.mfaEnabled) { + if (!session.mfaVerified) { + throw new UnauthorizedException(['2FA verification required']); + } + } const user = await this.userRepository.findOne({ - userId: decoded.appUserID, + userId: decoded.sub, }); if (!user) { throw new Error('User not found'); } req['user'] = user; - - if (decoded.isTwoFactorEnabled !== undefined) { - req['user']['isTwoFactorEnabled'] = decoded.isTwoFactorEnabled; - } - - if (decoded.isTwoFactorAuthenticated !== undefined) { - req['user']['isTwoFactorAuthenticated'] = - decoded.isTwoFactorAuthenticated; - } - - if (decoded.accessAccount !== undefined) { - req['user']['accessAccount'] = decoded.accessAccount; - } - - Logger.log(JSON.stringify(req.user), 'JWTAuthorizeMiddleware'); + req['session'] = session; } } catch (e) { Logger.error( `JWTAuthorizeMiddleware: Error ${e}`, 'JWTAuthorizeMiddleware', ); + if (e instanceof HttpException) { + throw e; + } throw new UnauthorizedException([e.message]); } next(); diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 48311932..8f36840d 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -154,3 +154,16 @@ export function mapUserAccessList(userAccessList) { }, ]; } + +export function getCookieOptions(maxAge: number) { + const cookieDomain = process.env.COOKIE_DOMAIN; + const isProd = process.env.NODE_ENV || 'production'; + return { + httpOnly: true, + secure: isProd, + sameSite: isProd ? 'None' : 'Lax', + domain: isProd ? cookieDomain : undefined, + path: '/', + maxAge, + }; +} From 06972ef4d6cdd1150084eb5eef646ad20773f9ce Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 24 Nov 2025 19:16:32 +0530 Subject: [PATCH 03/55] added new env --- .deploy/deployment.yaml | 5 ++++- .github/workflows/pipeline.yaml | 2 ++ dev.env.sample | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.deploy/deployment.yaml b/.deploy/deployment.yaml index af337a2d..ad6b3af9 100644 --- a/.deploy/deployment.yaml +++ b/.deploy/deployment.yaml @@ -108,7 +108,10 @@ spec: - name: OTP_EXPIRY_MINUTES value: '__OTP_EXPIRY_MINUTES__' - name: MAX_RETRY_ATTEMPT - value: '__MAX_RETRY_ATTEMPT__' + value: '__MAX_RETRY_ATTEMPT__' + - name: MFA_REDIRECT_URL + value: __MFA_REDIRECT_URL__ + volumeMounts: - name: mongo mountPath: "/data" diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index ced9a817..0c1da8eb 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -147,5 +147,7 @@ jobs: run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__OTP_EXPIRY_MINUTES__#${{ secrets.OTP_EXPIRY_MINUTES }}#" {} \; - name: "Replace Secrets" run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__MAX_RETRY_ATTEMPT__#${{ secrets.MAX_RETRY_ATTEMPT }}#" {} \; + - name: "Replace secrets" + run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__MFA_REDIRECT_URL__#${{ secrets.MFA_REDIRECT_URL }}#" {} \; - name: "Deploy to GKE" run: kubectl apply -f .deploy/deployment.yaml diff --git a/dev.env.sample b/dev.env.sample index bec093dd..c4886613 100644 --- a/dev.env.sample +++ b/dev.env.sample @@ -23,6 +23,7 @@ OTP_HOURLY_LIMIT=10 OTP_EXPIRY_MINUTES=5 MAX_RETRY_ATTEMPT=3 NODE_ENV=development +MFA_REDIRECT_URL='http://localhost:9001/#/studio/mfa' From b40d5e03a739d74158b6bbe04d5f3891b21a2e9c Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 25 Nov 2025 11:53:15 +0530 Subject: [PATCH 04/55] refactore mfa setup and addded new api for mfa verify at the time of setup --- src/social-login/constants/en.ts | 5 ++ .../controller/social-login.controller.ts | 55 +++++++++++++-- .../services/social-login.service.ts | 69 +++++++++++++++++++ 3 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 src/social-login/constants/en.ts diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts new file mode 100644 index 00000000..e4a8990f --- /dev/null +++ b/src/social-login/constants/en.ts @@ -0,0 +1,5 @@ +export const MFA_MESSAGE = { + SESSION_NOT_FOUND: 'Session not found or expired.', + MFA_ALREADY_ENABLED: 'MFA is already enabled for this user.', + INVALID_OTP: 'Invalid or expired OTP code.', +} as const; diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 3661caef..72f7d92a 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -43,6 +43,7 @@ import { import { AppError } from 'src/app-auth/dtos/fetch-app.dto'; import { UserRole } from 'src/user/schema/user.schema'; import { TOKEN_MAX_AGE } from 'src/utils/time-constant'; +import { MFA_MESSAGE } from '../constants/en'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') @Controller('api/v1') @@ -134,14 +135,14 @@ export class SocialLoginController { } @ApiBearerAuth('Authorization') - @Post('/api/v1/auth/login/refresh') + @Post('auth/login/refresh') async generateRefreshToken(@Req() req) { return { authToken: await this.socialLoginService.socialLogin(req), }; } @ApiBearerAuth('Authorization') - @Post('/api/v1/auth/refresh') + @Post('auth/refresh') async refreshTokenGeneration(@Req() req, @Res() res) { const refreshToken = req.cookies['refreshToken']; if (!refreshToken) { @@ -180,7 +181,7 @@ export class SocialLoginController { type: UnauthorizedError, }) @ApiBearerAuth('Authorization') - @Post('/api/v1/auth/mfa/generate') + @Post('auth/mfa/setup/generate') async generateMfa(@Req() req, @Body() body: Generate2FA) { const result = await this.socialLoginService.generate2FA(body, req.user); return { twoFADataUrl: result }; @@ -195,7 +196,7 @@ export class SocialLoginController { type: UnauthorizedError, }) @ApiBearerAuth('Authorization') - @Post('/api/v1/auth/mfa/verify') + @Post('auth/mfa/verify') async verifyMFA( @Req() req, @Body() mfaVerificationDto: MFACodeVerificationDto, @@ -237,7 +238,7 @@ export class SocialLoginController { type: UnauthorizedError, }) @ApiBearerAuth('Authorization') - @Delete('/api/v1/auth/mfa') + @Delete('auth/mfa') async removeMFA(@Req() req, @Body() mfaremoveDto: DeleteMFADto) { return this.socialLoginService.removeMFA(req.user, mfaremoveDto); } @@ -254,7 +255,7 @@ export class SocialLoginController { type: UnauthorizedError, }) @ApiBearerAuth('Authorization') - @Post('/api/v1/auth/logout') + @Post('auth/logout') async logout(@Req() req, @Res() res) { const cookieDomain = this.config.get('COOKIE_DOMAIN'); const isProduction = this.config.get('NODE_ENV') === 'production'; @@ -274,4 +275,46 @@ export class SocialLoginController { }); return res.status(200).json({ message: 'Logged out successfully' }); } + + @ApiOkResponse({ + description: 'Verified MFA code and generated new token', + type: Verify2FARespDto, + }) + @ApiUnauthorizedResponse({ + status: 401, + type: UnauthorizedError, + }) + @ApiBearerAuth('Authorization') + @Post('auth/mfa/setup/verify') + async confirmMfaSetup( + @Req() req, + @Body() mfaVerificationDto: MFACodeVerificationDto, + @Res() res, + ) { + const data = await this.socialLoginService.confirmMfaSetup( + req.user, + req.session, + mfaVerificationDto, + ); + if (!data.isVerified && data?.error === MFA_MESSAGE.SESSION_NOT_FOUND) { + return res.status(401).json({ + statusCode: 401, + message: [data.error], + error: 'Unauthorized', + }); + } + if (!data.isVerified) { + return res.json({ + statusCode: 400, + message: [data.error], + error: 'Bad Request', + }); + } + res.cookie( + 'refreshToken', + data.refreshToken, + getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + ); + return res.json({ isVerified: data.isVerified }); + } } diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index fe15e2d5..4e9338aa 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -24,6 +24,7 @@ import { import { UserDocument, UserRole } from 'src/user/schema/user.schema'; import { redisClient } from 'src/utils/redis.provider'; import { TIME } from 'src/utils/time-constant'; +import { MFA_MESSAGE } from '../constants/en'; @Injectable() export class SocialLoginService { @@ -313,4 +314,72 @@ export class SocialLoginService { ); return { accessToken, refreshToken }; } + async confirmMfaSetup( + user, + session, + mfaVerificationDto: MFACodeVerificationDto, + ): Promise<{ isVerified: boolean; refreshToken?: string; error?: string }> { + Logger.log( + 'Inside confirmMfaSetup() method to Complete MFA setup', + 'SocialLoginService', + ); + const { authenticatorType, twoFactorAuthenticationCode } = + mfaVerificationDto; + const authenticatorDetail = user.authenticators.find( + (auth) => auth.type === authenticatorType, + ); + if (authenticatorDetail.isTwoFactorAuthenticated) { + return { isVerified: false, error: MFA_MESSAGE.MFA_ALREADY_ENABLED }; + } + const isVerified = authenticator.verify({ + token: twoFactorAuthenticationCode, + secret: authenticatorDetail.secret, + }); + if (!isVerified) { + return { isVerified, error: MFA_MESSAGE.INVALID_OTP }; + } + if (!authenticatorDetail.isTwoFactorAuthenticated && isVerified) { + user.authenticators.map((authn) => { + if (authn.type === authenticatorType) { + authn.isTwoFactorAuthenticated = true; + return authn; + } + return authn; + }); + this.userRepository.findOneUpdate( + { userId: user.userId }, + { authenticators: user.authenticators }, + ); + const sessionKey = `session:${session.sessionId}`; + const sessionJson = await redisClient.get(sessionKey); + if (!sessionJson) { + return { + isVerified, + error: MFA_MESSAGE.SESSION_NOT_FOUND, + }; + } + const sessionObj = JSON.parse(sessionJson); + sessionObj.mfaVerified = true; + sessionObj.mfaEnabled = true; + sessionObj.refreshVersion += 1; + await redisClient.set( + sessionKey, + JSON.stringify(sessionObj), + 'EX', + TIME.WEEK, + ); + const newRefreshToken = `rt_${uuidv4()}`; + await redisClient.set( + `refresh:${newRefreshToken}`, + session.sid, + 'EX', + TIME.WEEK, + ); + return { + isVerified, + refreshToken: newRefreshToken, + }; + } + return { isVerified }; + } } From 1b2528093b2a1466641755b6f38f07984cb045a4 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 25 Nov 2025 15:19:40 +0530 Subject: [PATCH 05/55] update login verify mfa api --- .../controller/social-login.controller.ts | 41 ++++--- src/social-login/dto/request.dto.ts | 10 ++ .../services/social-login.service.ts | 103 ++++++++++-------- src/social-login/social-login.module.ts | 4 + src/utils/utils.ts | 2 +- 5 files changed, 89 insertions(+), 71 deletions(-) diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 3661caef..ee405912 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -38,6 +38,7 @@ import { import { DeleteMFADto, Generate2FA, + LoginMFACodeVerificationDto, MFACodeVerificationDto, } from '../dto/request.dto'; import { AppError } from 'src/app-auth/dtos/fetch-app.dto'; @@ -86,7 +87,9 @@ export class SocialLoginController { JSON.stringify(result.authenticators), ); return res.redirect( - `${this.config.get('MFA_REDIRECT_URL')}?authenticators=${arrayString}`, + `${this.config.get( + 'MFA_REDIRECT_URL', + )}?authenticators=${arrayString}&sessionId=${result.sessionId}`, ); } res.cookie( @@ -195,33 +198,27 @@ export class SocialLoginController { type: UnauthorizedError, }) @ApiBearerAuth('Authorization') - @Post('/api/v1/auth/mfa/verify') + @Post('auth/mfa/login/verify') async verifyMFA( - @Req() req, - @Body() mfaVerificationDto: MFACodeVerificationDto, + @Body() mfaVerificationDto: LoginMFACodeVerificationDto, @Res() res, ) { const data = await this.socialLoginService.verifyMFACode( - req.user, mfaVerificationDto, ); - const cookieDomain = this.config.get('COOKIE_DOMAIN'); - const isProduction = this.config.get('NODE_ENV') === 'production'; - res.cookie('authToken', data?.authToken, { - httpOnly: true, - maxAge: TOKEN_MAX_AGE.AUTH_TOKEN, - secure: isProduction, - domain: isProduction ? cookieDomain : undefined, - sameSite: isProduction ? 'None' : 'Lax', - path: '/', - }); - res.cookie('refreshToken', data?.refreshToken, { - httpOnly: true, - maxAge: TOKEN_MAX_AGE.REFRESH_TOKEN, - secure: isProduction, - sameSite: isProduction ? 'None' : 'Lax', - path: '/', - }); + if (data.isVerified) { + res.cookie( + 'accessToken', + data.accessToken, + getCookieOptions(TOKEN_MAX_AGE.AUTH_TOKEN), + ); + res.cookie( + 'refreshToken', + data.refreshToken, + getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + ); + } + res.json({ isVerified: data.isVerified }); } @ApiOkResponse({ diff --git a/src/social-login/dto/request.dto.ts b/src/social-login/dto/request.dto.ts index 058d6b34..80c07058 100644 --- a/src/social-login/dto/request.dto.ts +++ b/src/social-login/dto/request.dto.ts @@ -20,6 +20,16 @@ export class MFACodeVerificationDto { @IsNotEmpty() twoFactorAuthenticationCode: string; } +export class LoginMFACodeVerificationDto extends MFACodeVerificationDto { + @ApiProperty({ + name: 'sessionId', + description: 'Id of the session', + example: '1764056485204-0644acbc-bbc9-4b07-a613-811d2ad1ad4a', + }) + @IsNotEmpty() + @IsString() + sessionId: string; +} export class Generate2FA { @ApiProperty({ diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index fe15e2d5..118c03c4 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -19,6 +19,7 @@ import { toDataURL } from 'qrcode'; import { DeleteMFADto, Generate2FA, + LoginMFACodeVerificationDto, MFACodeVerificationDto, } from '../dto/request.dto'; import { UserDocument, UserRole } from 'src/user/schema/user.schema'; @@ -61,6 +62,7 @@ export class SocialLoginService { refreshToken?: string; accessToken?: string; authenticators?: string[]; + sessionId?: string; }> { Logger.log( 'Inside handleGoogleLogin() to create or fetch user detail based on login', @@ -91,21 +93,22 @@ export class SocialLoginService { if (!user.profileIcon) updates.profileIcon = profileIcon; if (Object.keys(updates).length > 0) this.userRepository.findOneUpdate({ email }, updates); - const authenticatorList = user.authenticators?.filter( - (auth) => auth?.isTwoFactorAuthenticated, + const sessionId = await this.createSession(user); + const activeAuthenticators = user.authenticators?.filter( + (a) => a.isTwoFactorAuthenticated, ); - if (authenticatorList?.length) { + if (activeAuthenticators?.length) { return { mfaRequired: true, - authenticators: authenticatorList.map((a) => a.type), + sessionId, + authenticators: activeAuthenticators.map((a) => a.type), }; } - const rawUrl = this.config.get('INVITATIONURL'); - const url = new URL(rawUrl); - const domain = url.origin; - const tokens = await this.createSessionAndToken(user, domain); + const tokens = await this.generateTokensForSession(sessionId, user); + return { mfaRequired: false, + sessionId, ...tokens, }; } @@ -143,56 +146,52 @@ export class SocialLoginService { const otpAuthUrl = authenticator.keyuri(user.email, issuer, secret); return toDataURL(otpAuthUrl); } - async verifyMFACode(user, mfaVerificationDto: MFACodeVerificationDto) { + async verifyMFACode( + mfaVerificationDto: LoginMFACodeVerificationDto, + ): Promise<{ + isVerified: boolean; + accessToken?: string; + refreshToken?: string; + }> { Logger.log( 'Inside verifyMFACode() method to verify MFA code', 'SocialLoginService', ); - const { authenticatorType, twoFactorAuthenticationCode } = + + const { authenticatorType, twoFactorAuthenticationCode, sessionId } = mfaVerificationDto; - const authenticatorDetail = user.authenticators.find( + const sessionKey = `session:${sessionId}`; + const sessionDetailJson = await redisClient.get(sessionKey); + if (!sessionDetailJson) { + throw new BadRequestException(['Invalid or expired sessionId']); + } + const sessionDetail = JSON.parse(sessionDetailJson); + const userDetail = await this.userRepository.findOne({ + userId: sessionDetail.userId, + }); + const authenticatorDetail = userDetail.authenticators.find( (auth) => auth.type === authenticatorType, ); const isVerified = authenticator.verify({ token: twoFactorAuthenticationCode, secret: authenticatorDetail.secret, }); - if (!authenticatorDetail.isTwoFactorAuthenticated && isVerified) { - user.authenticators.map((authn) => { - if (authn.type === authenticatorType) { - authn.isTwoFactorAuthenticated = true; - return authn; - } - return authn; - }); - this.userRepository.findOneUpdate( - { userId: user.userId }, - { authenticators: user.authenticators }, - ); + if (!isVerified) { + await redisClient.del(sessionKey); + return { isVerified: false }; } - const rawUrl = this.config.get('INVITATIONURL'); - const url = new URL(rawUrl); - const domain = url.origin; - const payload = { - email: user.email, - appUserID: user.userId, - userAccessList: mapUserAccessList(user.accessList), - isTwoFactorEnabled: user.authenticators && user.authenticators.length > 0, - isTwoFactorAuthenticated: isVerified, - authenticatorType, - accessAccount: user.accessAccount, - aud: domain, - role: user?.role || UserRole.ADMIN, - }; - const accessToken = await this.jwt.signAsync(payload, { - expiresIn: '24h', - secret: this.config.get('JWT_SECRET'), - }); - const refreshToken = await this.generateRefreshToken(payload); + sessionDetail.mfaVerified = true; + sessionDetail.mfaEnabled = true; + await redisClient.set( + sessionKey, + JSON.stringify(sessionDetail), + 'EX', + TIME.WEEK, + ); + const tokens = await this.generateTokensForSession(sessionId, userDetail); return { isVerified, - authToken: accessToken, - refreshToken, + ...tokens, }; } @@ -281,9 +280,12 @@ export class SocialLoginService { }); } - async createSessionAndToken(user, domain?: string) { + async createSession(user): Promise { const sessionId = `${Date.now()}-${uuidv4()}`; const role = user?.role || UserRole.ADMIN; + const activeAuthenticators = user.authenticators?.filter( + (auth) => auth.isTwoFactorAuthenticated === true, + ); await redisClient.set( `session:${sessionId}`, JSON.stringify({ @@ -292,16 +294,21 @@ export class SocialLoginService { role, refreshVersion: 1, mfaVerified: false, - mfaEnabled: - user?.authenticator?.some((x) => x.isTwoFactorAuthenticated) ?? false, + mfaEnabled: activeAuthenticators?.length > 0, }), 'EX', TIME.WEEK, ); + return sessionId; + } + + async generateTokensForSession(sessionId, user) { + const rawUrl = this.config.get('INVITATIONURL'); + const domain = new URL(rawUrl).origin; const accessToken = await this.generateAuthToken({ sid: sessionId, sub: user.userId, - role, + role: user.role || UserRole.ADMIN, aud: domain, }); const refreshToken = `rt_${uuidv4()}`; diff --git a/src/social-login/social-login.module.ts b/src/social-login/social-login.module.ts index 26d85b38..0e47f139 100644 --- a/src/social-login/social-login.module.ts +++ b/src/social-login/social-login.module.ts @@ -60,6 +60,10 @@ export class SocialLoginModule implements NestModule { path: '/api/v1/auth/refresh', method: RequestMethod.POST, }, + { + path: '/api/v1/auth/mfa/login/verify', + method: RequestMethod.POST, + }, ) .forRoutes(SocialLoginController); consumer diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 8f36840d..63c40ad0 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -161,7 +161,7 @@ export function getCookieOptions(maxAge: number) { return { httpOnly: true, secure: isProd, - sameSite: isProd ? 'None' : 'Lax', + sameSite: isProd === 'production' ? 'None' : 'Lax', domain: isProd ? cookieDomain : undefined, path: '/', maxAge, From 7702244b5b224a2b87c9d74bacecbb63cde534c2 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 26 Nov 2025 11:13:15 +0530 Subject: [PATCH 06/55] fixed refresh token genertaion flow --- src/social-login/constants/en.ts | 7 +- .../controller/social-login.controller.ts | 42 ++++----- .../services/social-login.service.ts | 90 ++++++++++++------- src/social-login/social-login.module.ts | 2 +- .../jwt-authorization.middleware.ts | 5 ++ 5 files changed, 92 insertions(+), 54 deletions(-) diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts index e4a8990f..f3d24f57 100644 --- a/src/social-login/constants/en.ts +++ b/src/social-login/constants/en.ts @@ -1,5 +1,8 @@ -export const MFA_MESSAGE = { +export const ERROR_MESSAGE = { SESSION_NOT_FOUND: 'Session not found or expired.', MFA_ALREADY_ENABLED: 'MFA is already enabled for this user.', INVALID_OTP: 'Invalid or expired OTP code.', -} as const; + REFRESH_TOKEN_NOT_FOUND: 'Refresh token not found or expired.', + MFA_NOT_VERIFIED: 'Two-factor authentication (2FA) is required.' +} + diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 381d157e..79735ddf 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -44,7 +44,7 @@ import { import { AppError } from 'src/app-auth/dtos/fetch-app.dto'; import { UserRole } from 'src/user/schema/user.schema'; import { TOKEN_MAX_AGE } from 'src/utils/time-constant'; -import { MFA_MESSAGE } from '../constants/en'; +import { ERROR_MESSAGE as MFA_MESSAGE } from '../constants/en'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') @Controller('api/v1') @@ -52,7 +52,7 @@ export class SocialLoginController { constructor( private readonly socialLoginService: SocialLoginService, private readonly config: ConfigService, - ) {} + ) { } @ApiResponse({ status: 200, description: 'Auth url', @@ -145,7 +145,7 @@ export class SocialLoginController { }; } @ApiBearerAuth('Authorization') - @Post('auth/refresh') + @Post('auth/tokens/refresh') async refreshTokenGeneration(@Req() req, @Res() res) { const refreshToken = req.cookies['refreshToken']; if (!refreshToken) { @@ -154,24 +154,24 @@ export class SocialLoginController { const tokens = await this.socialLoginService.verifyAndGenerateRefreshToken( refreshToken, ); - const cookieDomain = this.config.get('COOKIE_DOMAIN'); - const isProduction = this.config.get('NODE_ENV') === 'production'; - res.cookie('authToken', tokens.authToken, { - httpOnly: true, - maxAge: TOKEN_MAX_AGE.AUTH_TOKEN, - secure: isProduction, - domain: isProduction ? cookieDomain : undefined, - sameSite: isProduction ? 'None' : 'Lax', - path: '/', - }); - res.cookie('refreshToken', tokens.refreshToken, { - httpOnly: true, - maxAge: TOKEN_MAX_AGE.REFRESH_TOKEN, - secure: isProduction, - sameSite: isProduction ? 'None' : 'Lax', - domain: isProduction ? cookieDomain : undefined, - path: '/', - }); + if (tokens.error) { + return res.status(401).json({ + statusCode: 401, + message: [tokens.error], + error: 'Unauthorized', + }); + } + + res.cookie( + 'accessToken', + tokens.accessToken, + getCookieOptions(TOKEN_MAX_AGE.AUTH_TOKEN), + ); + res.cookie( + 'refreshToken', + tokens.refreshToken, + getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + ); res.json({ message: 'Tokens refreshed' }); } diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 125ff2d9..fdd45d14 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -10,7 +10,7 @@ import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { v4 as uuidv4 } from 'uuid'; import { Providers } from '../strategy/social.strategy'; -import { mapUserAccessList, sanitizeUrl } from 'src/utils/utils'; +import { sanitizeUrl } from 'src/utils/utils'; import { SupportedServiceList } from 'src/supported-service/services/service-list'; import { SERVICE_TYPES } from 'src/supported-service/services/iServiceList'; import { AuthneticatorType } from '../dto/response.dto'; @@ -25,7 +25,7 @@ import { import { UserDocument, UserRole } from 'src/user/schema/user.schema'; import { redisClient } from 'src/utils/redis.provider'; import { TIME } from 'src/utils/time-constant'; -import { MFA_MESSAGE } from '../constants/en'; +import { ERROR_MESSAGE as MFA_MESSAGE } from '../constants/en'; @Injectable() export class SocialLoginService { @@ -34,22 +34,20 @@ export class SocialLoginService { private readonly config: ConfigService, private readonly jwt: JwtService, private readonly supportedServiceList: SupportedServiceList, - ) {} + ) { } async generateAuthUrlByProvider(provider: string) { let authUrl; switch (provider) { case Providers.google: { - authUrl = `${ - this.config.get('GOOGLE_AUTH_BASE_URL') || + authUrl = `${this.config.get('GOOGLE_AUTH_BASE_URL') || 'https://accounts.google.com/o/oauth2/v2/auth' - }?response_type=code&redirect_uri=${ - this.config.get('GOOGLE_CALLBACK_URL') || + }?response_type=code&redirect_uri=${this.config.get('GOOGLE_CALLBACK_URL') || sanitizeUrl( this.config.get('DEVELOPER_DASHBOARD_SERVICE_PUBLIC_EP'), ) + '/api/v1/auth/google/callback' - }&scope=email%20profile&prompt=select_account&client_id=${this.config.get( - 'GOOGLE_CLIENT_ID', - )}`; + }&scope=email%20profile&prompt=select_account&client_id=${this.config.get( + 'GOOGLE_CLIENT_ID', + )}`; break; } default: { @@ -94,7 +92,7 @@ export class SocialLoginService { if (!user.profileIcon) updates.profileIcon = profileIcon; if (Object.keys(updates).length > 0) this.userRepository.findOneUpdate({ email }, updates); - const sessionId = await this.createSession(user); + const { sessionId, refreshVersion } = await this.createSession(user); const activeAuthenticators = user.authenticators?.filter( (a) => a.isTwoFactorAuthenticated, ); @@ -105,7 +103,11 @@ export class SocialLoginService { authenticators: activeAuthenticators.map((a) => a.type), }; } - const tokens = await this.generateTokensForSession(sessionId, user); + const tokens = await this.generateTokensForSession( + sessionId, + user, + refreshVersion, + ); return { mfaRequired: false, @@ -189,7 +191,11 @@ export class SocialLoginService { 'EX', TIME.WEEK, ); - const tokens = await this.generateTokensForSession(sessionId, userDetail); + const tokens = await this.generateTokensForSession( + sessionId, + userDetail, + sessionDetail.refreshVersion, + ); return { isVerified, ...tokens, @@ -229,24 +235,44 @@ export class SocialLoginService { ); return { message: 'Removed authenticator successfully' }; } - async verifyAndGenerateRefreshToken(token: string) { + async verifyAndGenerateRefreshToken( + token: string, + ): Promise<{ error?: string; accessToken?: string; refreshToken?: string }> { try { - const tokenSecret = this.config.get('JWT_REFRESH_SECRET'); - if (!tokenSecret) { - throw new BadRequestException([ - 'JWT_REFRESH_SECRET is not set. Please contact the admin', - ]); + const sessionId = await redisClient.get(`refresh:${token}`); + if (!sessionId) { + return { error: MFA_MESSAGE.REFRESH_TOKEN_NOT_FOUND }; } - const payload = await this.jwt.verify(token, { secret: tokenSecret }); - delete payload?.exp; - delete payload?.iat; + const sessionKey = `session:${sessionId}` + const sessionDetail = await redisClient.get(sessionKey) + if (!sessionDetail) { + return { + error: MFA_MESSAGE.SESSION_NOT_FOUND + } + } + const sessionJson = JSON.parse(sessionDetail) + if (sessionJson?.mfaEnabled && !sessionJson?.mfaVerified) { + return { + error: MFA_MESSAGE.MFA_NOT_VERIFIED + } + } + sessionJson.refreshVersion += 1; const user = await this.userRepository.findOne({ - userId: payload.appUserID, + userId: sessionJson.userId, }); if (!user) throw new UnauthorizedException(['User not found']); - const newRefreshToken = await this.generateRefreshToken(payload); // make refresh token small - const authToken = await this.generateAuthToken(payload); - return { authToken, refreshToken: newRefreshToken }; + await redisClient.set( + sessionKey, + JSON.stringify(sessionJson), + 'EX', + TIME.WEEK, + ); + const newToken = await this.generateTokensForSession( + sessionJson.sessionId, + user, + sessionJson.refreshVersion + ); + return { ...newToken }; } catch (e) { Logger.error( `Error whaile generating refreshToken ${e}`, @@ -281,29 +307,32 @@ export class SocialLoginService { }); } - async createSession(user): Promise { + async createSession( + user, + ): Promise<{ sessionId: string; refreshVersion: number }> { const sessionId = `${Date.now()}-${uuidv4()}`; const role = user?.role || UserRole.ADMIN; const activeAuthenticators = user.authenticators?.filter( (auth) => auth.isTwoFactorAuthenticated === true, ); + const refreshVersion = 1; await redisClient.set( `session:${sessionId}`, JSON.stringify({ sessionId, userId: user.userId, role, - refreshVersion: 1, + refreshVersion: refreshVersion, mfaVerified: false, mfaEnabled: activeAuthenticators?.length > 0, }), 'EX', TIME.WEEK, ); - return sessionId; + return { sessionId, refreshVersion }; } - async generateTokensForSession(sessionId, user) { + async generateTokensForSession(sessionId, user, refreshVersion = 1) { const rawUrl = this.config.get('INVITATIONURL'); const domain = new URL(rawUrl).origin; const accessToken = await this.generateAuthToken({ @@ -311,6 +340,7 @@ export class SocialLoginService { sub: user.userId, role: user.role || UserRole.ADMIN, aud: domain, + refreshVersion, }); const refreshToken = `rt_${uuidv4()}`; await redisClient.set( diff --git a/src/social-login/social-login.module.ts b/src/social-login/social-login.module.ts index 0e47f139..f13498fc 100644 --- a/src/social-login/social-login.module.ts +++ b/src/social-login/social-login.module.ts @@ -57,7 +57,7 @@ export class SocialLoginModule implements NestModule { method: RequestMethod.GET, }, { - path: '/api/v1/auth/refresh', + path: '/api/v1/auth/tokens/refresh', method: RequestMethod.POST, }, { diff --git a/src/utils/middleware/jwt-authorization.middleware.ts b/src/utils/middleware/jwt-authorization.middleware.ts index 318fc0da..fec70fbd 100644 --- a/src/utils/middleware/jwt-authorization.middleware.ts +++ b/src/utils/middleware/jwt-authorization.middleware.ts @@ -70,6 +70,11 @@ export class JWTAuthorizeMiddleware implements NestMiddleware { if (session.userId !== decoded.sub) { throw new UnauthorizedException(['Token does not match session']); } + if (session.refreshVersion !== decoded.refreshVersion) { + throw new UnauthorizedException([ + 'Your session has expired. Please log in again.', + ]); + } if (session.mfaEnabled) { if (!session.mfaVerified) { throw new UnauthorizedException(['2FA verification required']); From c83f7527ef01b6a9a9e4b147daa7dc70118cff85 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 26 Nov 2025 11:14:46 +0530 Subject: [PATCH 07/55] code prettified --- src/social-login/constants/en.ts | 5 ++-- .../controller/social-login.controller.ts | 2 +- .../services/social-login.service.ts | 30 ++++++++++--------- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts index f3d24f57..d09315c7 100644 --- a/src/social-login/constants/en.ts +++ b/src/social-login/constants/en.ts @@ -3,6 +3,5 @@ export const ERROR_MESSAGE = { MFA_ALREADY_ENABLED: 'MFA is already enabled for this user.', INVALID_OTP: 'Invalid or expired OTP code.', REFRESH_TOKEN_NOT_FOUND: 'Refresh token not found or expired.', - MFA_NOT_VERIFIED: 'Two-factor authentication (2FA) is required.' -} - + MFA_NOT_VERIFIED: 'Two-factor authentication (2FA) is required.', +}; diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 79735ddf..a41b26ce 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -52,7 +52,7 @@ export class SocialLoginController { constructor( private readonly socialLoginService: SocialLoginService, private readonly config: ConfigService, - ) { } + ) {} @ApiResponse({ status: 200, description: 'Auth url', diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index fdd45d14..0d1cc1e3 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -34,20 +34,22 @@ export class SocialLoginService { private readonly config: ConfigService, private readonly jwt: JwtService, private readonly supportedServiceList: SupportedServiceList, - ) { } + ) {} async generateAuthUrlByProvider(provider: string) { let authUrl; switch (provider) { case Providers.google: { - authUrl = `${this.config.get('GOOGLE_AUTH_BASE_URL') || + authUrl = `${ + this.config.get('GOOGLE_AUTH_BASE_URL') || 'https://accounts.google.com/o/oauth2/v2/auth' - }?response_type=code&redirect_uri=${this.config.get('GOOGLE_CALLBACK_URL') || + }?response_type=code&redirect_uri=${ + this.config.get('GOOGLE_CALLBACK_URL') || sanitizeUrl( this.config.get('DEVELOPER_DASHBOARD_SERVICE_PUBLIC_EP'), ) + '/api/v1/auth/google/callback' - }&scope=email%20profile&prompt=select_account&client_id=${this.config.get( - 'GOOGLE_CLIENT_ID', - )}`; + }&scope=email%20profile&prompt=select_account&client_id=${this.config.get( + 'GOOGLE_CLIENT_ID', + )}`; break; } default: { @@ -243,18 +245,18 @@ export class SocialLoginService { if (!sessionId) { return { error: MFA_MESSAGE.REFRESH_TOKEN_NOT_FOUND }; } - const sessionKey = `session:${sessionId}` - const sessionDetail = await redisClient.get(sessionKey) + const sessionKey = `session:${sessionId}`; + const sessionDetail = await redisClient.get(sessionKey); if (!sessionDetail) { return { - error: MFA_MESSAGE.SESSION_NOT_FOUND - } + error: MFA_MESSAGE.SESSION_NOT_FOUND, + }; } - const sessionJson = JSON.parse(sessionDetail) + const sessionJson = JSON.parse(sessionDetail); if (sessionJson?.mfaEnabled && !sessionJson?.mfaVerified) { return { - error: MFA_MESSAGE.MFA_NOT_VERIFIED - } + error: MFA_MESSAGE.MFA_NOT_VERIFIED, + }; } sessionJson.refreshVersion += 1; const user = await this.userRepository.findOne({ @@ -270,7 +272,7 @@ export class SocialLoginService { const newToken = await this.generateTokensForSession( sessionJson.sessionId, user, - sessionJson.refreshVersion + sessionJson.refreshVersion, ); return { ...newToken }; } catch (e) { From 286531b00737b2fee399cd31217ada21567acfc7 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 26 Nov 2025 13:21:56 +0530 Subject: [PATCH 08/55] fixed some issues --- src/social-login/constants/en.ts | 5 ++ .../controller/email-otp-login.controller.ts | 2 +- .../controller/social-login.controller.ts | 20 ++--- .../services/social-login.service.ts | 85 +++++++++++-------- .../jwt-authorization.middleware.ts | 11 +-- src/utils/time-constant.ts | 10 +++ 6 files changed, 82 insertions(+), 51 deletions(-) create mode 100644 src/social-login/constants/en.ts diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts new file mode 100644 index 00000000..4b71d92a --- /dev/null +++ b/src/social-login/constants/en.ts @@ -0,0 +1,5 @@ +export const AUTH_ERRORS = { + SESSION_EXPIRED: 'Session expired or logged out', + SESSION_MISMATCH: 'Token does not match session', + TWO_FA_REQUIRED: '2FA verification required', +}; diff --git a/src/social-login/controller/email-otp-login.controller.ts b/src/social-login/controller/email-otp-login.controller.ts index a6b525ce..109233ca 100644 --- a/src/social-login/controller/email-otp-login.controller.ts +++ b/src/social-login/controller/email-otp-login.controller.ts @@ -75,7 +75,7 @@ export class EmailOtpLoginController { ); try { const result = await this.socialLoginService.socialLogin(req); - if (result.mfaRequired) { + if (result.isMfaRequired) { res.redirect(this.config.get('MFA_REDIRECT_URL')); } res.cookie( diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index ee405912..6a9ceab5 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -43,7 +43,7 @@ import { } from '../dto/request.dto'; import { AppError } from 'src/app-auth/dtos/fetch-app.dto'; import { UserRole } from 'src/user/schema/user.schema'; -import { TOKEN_MAX_AGE } from 'src/utils/time-constant'; +import { TOKEN_MAX_AGE, TOKEN } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') @Controller('api/v1') @@ -82,7 +82,7 @@ export class SocialLoginController { async socialAuthCallback(@Req() req, @Res() res) { Logger.log('socialAuthCallback() method starts', 'SocialLoginController'); const result = await this.socialLoginService.socialLogin(req); - if (result.mfaRequired) { + if (result.isMfaRequired) { const arrayString = encodeURIComponent( JSON.stringify(result.authenticators), ); @@ -93,14 +93,14 @@ export class SocialLoginController { ); } res.cookie( - 'accessToken', + TOKEN.AUTH.name, result.accessToken, - getCookieOptions(TOKEN_MAX_AGE.AUTH_TOKEN), + getCookieOptions(TOKEN.AUTH.expiry), ); res.cookie( - 'refreshToken', + TOKEN.REFRESH.name, result.refreshToken, - getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + getCookieOptions(TOKEN.REFRESH.expiry), ); res.redirect(`${this.config.get('REDIRECT_URL')}`); } @@ -208,14 +208,14 @@ export class SocialLoginController { ); if (data.isVerified) { res.cookie( - 'accessToken', + TOKEN.AUTH.name, data.accessToken, - getCookieOptions(TOKEN_MAX_AGE.AUTH_TOKEN), + getCookieOptions(TOKEN.AUTH.expiry), ); res.cookie( - 'refreshToken', + TOKEN.REFRESH.name, data.refreshToken, - getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + getCookieOptions(TOKEN.REFRESH.expiry), ); } diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 118c03c4..79f29361 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -10,10 +10,9 @@ import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { v4 as uuidv4 } from 'uuid'; import { Providers } from '../strategy/social.strategy'; -import { mapUserAccessList, sanitizeUrl } from 'src/utils/utils'; +import { sanitizeUrl } from 'src/utils/utils'; import { SupportedServiceList } from 'src/supported-service/services/service-list'; import { SERVICE_TYPES } from 'src/supported-service/services/iServiceList'; -import { AuthneticatorType } from '../dto/response.dto'; import { authenticator } from 'otplib'; import { toDataURL } from 'qrcode'; import { @@ -58,7 +57,7 @@ export class SocialLoginService { return { authUrl }; } async socialLogin(req): Promise<{ - mfaRequired: boolean; + isMfaRequired: boolean; refreshToken?: string; accessToken?: string; authenticators?: string[]; @@ -93,21 +92,25 @@ export class SocialLoginService { if (!user.profileIcon) updates.profileIcon = profileIcon; if (Object.keys(updates).length > 0) this.userRepository.findOneUpdate({ email }, updates); - const sessionId = await this.createSession(user); - const activeAuthenticators = user.authenticators?.filter( - (a) => a.isTwoFactorAuthenticated, - ); - if (activeAuthenticators?.length) { + const { sessionId, activeAuthenticators, isMfaRequired } = + await this.createSession(user); + + if (isMfaRequired) { return { - mfaRequired: true, + isMfaRequired, sessionId, authenticators: activeAuthenticators.map((a) => a.type), }; } - const tokens = await this.generateTokensForSession(sessionId, user); + const role = user?.role || UserRole.ADMIN; + const tokens = await this.generateTokensForSession( + sessionId, + user.userId, + role, + ); return { - mfaRequired: false, + isMfaRequired, sessionId, ...tokens, }; @@ -166,10 +169,7 @@ export class SocialLoginService { throw new BadRequestException(['Invalid or expired sessionId']); } const sessionDetail = JSON.parse(sessionDetailJson); - const userDetail = await this.userRepository.findOne({ - userId: sessionDetail.userId, - }); - const authenticatorDetail = userDetail.authenticators.find( + const authenticatorDetail = sessionDetail.authenticators.find( (auth) => auth.type === authenticatorType, ); const isVerified = authenticator.verify({ @@ -180,15 +180,20 @@ export class SocialLoginService { await redisClient.del(sessionKey); return { isVerified: false }; } - sessionDetail.mfaVerified = true; - sessionDetail.mfaEnabled = true; + delete sessionDetail?.authenticators; + sessionDetail.isTwoFactorVerified = true; + sessionDetail.isTwoFactorAuthenticated = true; await redisClient.set( sessionKey, JSON.stringify(sessionDetail), 'EX', TIME.WEEK, ); - const tokens = await this.generateTokensForSession(sessionId, userDetail); + const tokens = await this.generateTokensForSession( + sessionId, + sessionDetail.userId, + sessionDetail.role, + ); return { isVerified, ...tokens, @@ -280,38 +285,48 @@ export class SocialLoginService { }); } - async createSession(user): Promise { + async createSession(user): Promise<{ + sessionId: string; + activeAuthenticators: any[]; + isMfaRequired: boolean; + }> { const sessionId = `${Date.now()}-${uuidv4()}`; const role = user?.role || UserRole.ADMIN; - const activeAuthenticators = user.authenticators?.filter( - (auth) => auth.isTwoFactorAuthenticated === true, - ); + const activeAuthenticators = + user.authenticators?.filter( + (auth) => auth.isTwoFactorAuthenticated === true, + ) || []; + const isMfaRequired = activeAuthenticators.length > 0; + const sessionData: any = { + sessionId, + role, + refreshVersion: 1, + ...user, + isTwoFactorVerified: false, + isTwoFactorAuthenticated: isMfaRequired, + }; + if (isMfaRequired) { + sessionData.authenticators = activeAuthenticators; + } await redisClient.set( `session:${sessionId}`, - JSON.stringify({ - sessionId, - userId: user.userId, - role, - refreshVersion: 1, - mfaVerified: false, - mfaEnabled: activeAuthenticators?.length > 0, - }), + JSON.stringify(sessionId), 'EX', TIME.WEEK, ); - return sessionId; + return { sessionId, activeAuthenticators, isMfaRequired }; } - async generateTokensForSession(sessionId, user) { + async generateTokensForSession(sessionId, userId, role) { const rawUrl = this.config.get('INVITATIONURL'); const domain = new URL(rawUrl).origin; const accessToken = await this.generateAuthToken({ sid: sessionId, - sub: user.userId, - role: user.role || UserRole.ADMIN, + sub: userId, + role: role || UserRole.ADMIN, aud: domain, }); - const refreshToken = `rt_${uuidv4()}`; + const refreshToken = `${uuidv4()}`; await redisClient.set( `refresh:${refreshToken}`, sessionId, diff --git a/src/utils/middleware/jwt-authorization.middleware.ts b/src/utils/middleware/jwt-authorization.middleware.ts index 318fc0da..06a6e2f6 100644 --- a/src/utils/middleware/jwt-authorization.middleware.ts +++ b/src/utils/middleware/jwt-authorization.middleware.ts @@ -10,6 +10,7 @@ import { NextFunction, Request, Response } from 'express'; import { UserRepository } from 'src/user/repository/user.repository'; import { sanitizeUrl } from '../utils'; import { redisClient } from '../redis.provider'; +import { AUTH_ERRORS } from 'src/social-login/constants/en'; @Injectable() export class JWTAuthorizeMiddleware implements NestMiddleware { constructor(private readonly userRepository: UserRepository) {} @@ -64,15 +65,15 @@ export class JWTAuthorizeMiddleware implements NestMiddleware { } const sessionRaw = await redisClient.get(`session:${sid}`); if (!sessionRaw) { - throw new UnauthorizedException(['Session expired or logged out']); + throw new UnauthorizedException([AUTH_ERRORS.SESSION_EXPIRED]); } const session = JSON.parse(sessionRaw); if (session.userId !== decoded.sub) { - throw new UnauthorizedException(['Token does not match session']); + throw new UnauthorizedException([AUTH_ERRORS.SESSION_MISMATCH]); } - if (session.mfaEnabled) { - if (!session.mfaVerified) { - throw new UnauthorizedException(['2FA verification required']); + if (session.isTwoFactorAuthenticated) { + if (!session.isTwoFactorVerified) { + throw new UnauthorizedException([AUTH_ERRORS.TWO_FA_REQUIRED]); } } const user = await this.userRepository.findOne({ diff --git a/src/utils/time-constant.ts b/src/utils/time-constant.ts index f1ad546c..59c72422 100644 --- a/src/utils/time-constant.ts +++ b/src/utils/time-constant.ts @@ -10,6 +10,16 @@ export const TOKEN_MAX_AGE = { AUTH_TOKEN: 4 * TIME.HOUR * 1000, // 4 hours REFRESH_TOKEN: 7 * TIME.DAY * 1000, // 7 days }; +export const TOKEN = { + AUTH: { + name: 'accessToken', + expiry: 4 * TIME.MINUTE * 1000, + }, + REFRESH: { + name: 'refreshToken', + expiry: 7 * TIME.DAY * 1000, + }, +}; export enum JobNames { SEND_EMAIL_LOGIN_OTP = 'send-email-login-otp', From 4a85654c0bd7280be59711d1b67e17fe3ab0c862 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 26 Nov 2025 13:54:50 +0530 Subject: [PATCH 09/55] fixed typo issue --- src/social-login/services/social-login.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 79f29361..6b4343df 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -310,7 +310,7 @@ export class SocialLoginService { } await redisClient.set( `session:${sessionId}`, - JSON.stringify(sessionId), + JSON.stringify(sessionData), 'EX', TIME.WEEK, ); From 89924cbd0d0c0b034c52cf7990daa6c0dadaa114 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 26 Nov 2025 14:17:03 +0530 Subject: [PATCH 10/55] used value from constsnt files --- src/social-login/constants/en.ts | 2 -- .../controller/social-login.controller.ts | 17 +++++------------ .../services/social-login.service.ts | 6 +++--- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts index 5f357654..db788ebf 100644 --- a/src/social-login/constants/en.ts +++ b/src/social-login/constants/en.ts @@ -1,4 +1,3 @@ - export const MFA_MESSAGE = { SESSION_NOT_FOUND: 'Session not found or expired.', MFA_ALREADY_ENABLED: 'MFA is already enabled for this user.', @@ -10,4 +9,3 @@ export const AUTH_ERRORS = { SESSION_MISMATCH: 'Token does not match session', TWO_FA_REQUIRED: '2FA verification required', }; - diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 6da3966d..c59d9df6 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -11,6 +11,7 @@ import { Body, Delete, UnauthorizedException, + BadRequestException, } from '@nestjs/common'; import { SocialLoginService } from '../services/social-login.service'; import { AuthGuard } from '@nestjs/passport'; @@ -294,23 +295,15 @@ export class SocialLoginController { mfaVerificationDto, ); if (!data.isVerified && data?.error === MFA_MESSAGE.SESSION_NOT_FOUND) { - return res.status(401).json({ - statusCode: 401, - message: [data.error], - error: 'Unauthorized', - }); + throw new UnauthorizedException([data.error]); } if (!data.isVerified) { - return res.json({ - statusCode: 400, - message: [data.error], - error: 'Bad Request', - }); + throw new BadRequestException([data.error]); } res.cookie( - 'refreshToken', + TOKEN.REFRESH.name, data.refreshToken, - getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + getCookieOptions(TOKEN.REFRESH.expiry), ); return res.json({ isVerified: data.isVerified }); } diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 3c36ec24..dc5ce68d 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -311,7 +311,7 @@ export class SocialLoginService { } await redisClient.set( `session:${sessionId}`, - JSON.stringify(sessionId), + JSON.stringify(sessionData), 'EX', TIME.WEEK, ); @@ -381,8 +381,8 @@ export class SocialLoginService { }; } const sessionObj = JSON.parse(sessionJson); - sessionObj.mfaVerified = true; - sessionObj.mfaEnabled = true; + sessionObj.isTwoFactorVerifed = true; + sessionObj.isTwoFactorAuthenticated = true; sessionObj.refreshVersion += 1; await redisClient.set( sessionKey, From aa54b7a849ba4de4c96c3bd926c7cb238556d1e8 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 26 Nov 2025 14:18:53 +0530 Subject: [PATCH 11/55] made key format consistent --- src/social-login/services/social-login.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index dc5ce68d..ebe078e5 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -390,7 +390,7 @@ export class SocialLoginService { 'EX', TIME.WEEK, ); - const newRefreshToken = `rt_${uuidv4()}`; + const newRefreshToken = uuidv4(); await redisClient.set( `refresh:${newRefreshToken}`, session.sid, From 29a125f106169e1dfd53eb20582f0dd35c99960d Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 26 Nov 2025 14:32:19 +0530 Subject: [PATCH 12/55] fix --- src/social-login/services/social-login.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 6b4343df..aa4e309f 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -301,7 +301,7 @@ export class SocialLoginService { sessionId, role, refreshVersion: 1, - ...user, + userId: user.userId, isTwoFactorVerified: false, isTwoFactorAuthenticated: isMfaRequired, }; From 33de5960eb5be40bc308422521063a269e5e5aa4 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 26 Nov 2025 14:56:56 +0530 Subject: [PATCH 13/55] used constsnt file for error messages --- src/social-login/constants/en.ts | 13 ++++++- .../controller/social-login.controller.ts | 8 ++--- .../services/social-login.service.ts | 34 +++++++++++-------- .../jwt-authorization.middleware.ts | 24 ++++++------- 4 files changed, 47 insertions(+), 32 deletions(-) diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts index fc386686..6f1e5489 100644 --- a/src/social-login/constants/en.ts +++ b/src/social-login/constants/en.ts @@ -2,12 +2,23 @@ export const ERROR_MESSAGE = { SESSION_NOT_FOUND: 'Session not found or expired.', MFA_ALREADY_ENABLED: 'MFA is already enabled for this user.', INVALID_OTP: 'Invalid or expired OTP code.', - REFRESH_TOKEN_NOT_FOUND: 'Refresh token not found or expired.', MFA_NOT_VERIFIED: 'Two-factor authentication (2FA) is required.', + USER_NOT_FOUND: 'User not found', } as const; export const AUTH_ERRORS = { + EMPTY_TOKEN: 'Please pass authorization token in cookie', SESSION_EXPIRED: 'Session expired or logged out', SESSION_MISMATCH: 'Token does not match session', TWO_FA_REQUIRED: '2FA verification required', + TOKEN_DOMAIN_MISMATCH: + 'This token was issued for a different domain than the one making the request.', + TOKEN_DOMAIN_MISSING: 'Token does not contain a valid domain.', + INVALID_TOKEN: 'Invalid token', +}; + +export const REFRESH_TOKEN_ERROR = { + INVALID_REFRESH_TOKEN: 'Invalid refresh token', + REFRESH_TOKEN_NOT_FOUND: 'Refresh token not found or expired.', + REFRESH_VERSION_MISMATCH: 'Your session has expired. Please log in again.', }; diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 027d5eb3..3a22a1f7 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -164,14 +164,14 @@ export class SocialLoginController { } res.cookie( - 'accessToken', + TOKEN.AUTH.name, tokens.accessToken, - getCookieOptions(TOKEN_MAX_AGE.AUTH_TOKEN), + getCookieOptions(TOKEN.AUTH.expiry), ); res.cookie( - 'refreshToken', + TOKEN.REFRESH.name, tokens.refreshToken, - getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + getCookieOptions(TOKEN.REFRESH.expiry), ); res.json({ message: 'Tokens refreshed' }); } diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index cf76e2bb..2262e340 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -24,7 +24,10 @@ import { import { UserDocument, UserRole } from 'src/user/schema/user.schema'; import { redisClient } from 'src/utils/redis.provider'; import { TIME } from 'src/utils/time-constant'; -import { ERROR_MESSAGE as MFA_MESSAGE } from '../constants/en'; +import { + ERROR_MESSAGE as MFA_MESSAGE, + REFRESH_TOKEN_ERROR, +} from '../constants/en'; @Injectable() export class SocialLoginService { @@ -104,12 +107,11 @@ export class SocialLoginService { authenticators: activeAuthenticators.map((a) => a.type), }; } - const role = user?.role || UserRole.ADMIN; const tokens = await this.generateTokensForSession( sessionId, user.userId, - role, - refreshVersion + user?.role || UserRole.ADMIN, + refreshVersion, ); return { @@ -194,9 +196,9 @@ export class SocialLoginService { ); const tokens = await this.generateTokensForSession( sessionId, - sessionDetail.refreshVersion, sessionDetail.userId, sessionDetail.role, + sessionDetail.refreshVersion, ); return { isVerified, @@ -243,7 +245,7 @@ export class SocialLoginService { try { const sessionId = await redisClient.get(`refresh:${token}`); if (!sessionId) { - return { error: MFA_MESSAGE.REFRESH_TOKEN_NOT_FOUND }; + return { error: REFRESH_TOKEN_ERROR.REFRESH_TOKEN_NOT_FOUND }; } const sessionKey = `session:${sessionId}`; const sessionDetail = await redisClient.get(sessionKey); @@ -271,16 +273,19 @@ export class SocialLoginService { ); const newToken = await this.generateTokensForSession( sessionJson.sessionId, - user, + user.userId, + user?.role || UserRole.ADMIN, sessionJson.refreshVersion, ); return { ...newToken }; } catch (e) { Logger.error( - `Error whaile generating refreshToken ${e}`, + `Error while generating refreshToken ${e}`, 'SocialLoginService', ); - throw new UnauthorizedException(['Invalid refresh token']); + throw new UnauthorizedException([ + REFRESH_TOKEN_ERROR.INVALID_REFRESH_TOKEN, + ]); } } async generateRefreshToken(payload: any): Promise { @@ -309,12 +314,11 @@ export class SocialLoginService { }); } - async createSession(user): Promise<{ sessionId: string; activeAuthenticators: any[]; isMfaRequired: boolean; - refreshVersion: number + refreshVersion: number; }> { const sessionId = `${Date.now()}-${uuidv4()}`; const role = user?.role || UserRole.ADMIN; @@ -322,7 +326,7 @@ export class SocialLoginService { user.authenticators?.filter( (auth) => auth.isTwoFactorAuthenticated === true, ) || []; - const refreshVersion = 1; + const refreshVersion = 1; const isMfaRequired = activeAuthenticators.length > 0; const sessionData: any = { sessionId, @@ -341,16 +345,16 @@ export class SocialLoginService { 'EX', TIME.WEEK, ); - return { sessionId, activeAuthenticators, isMfaRequired }; + return { sessionId, activeAuthenticators, isMfaRequired, refreshVersion }; } - async generateTokensForSession(sessionId, userId, role) { + async generateTokensForSession(sessionId, userId, role, refreshVersion) { const rawUrl = this.config.get('INVITATIONURL'); const domain = new URL(rawUrl).origin; const accessToken = await this.generateAuthToken({ sid: sessionId, sub: userId, - role: role || UserRole.ADMIN, + role, aud: domain, refreshVersion, }); diff --git a/src/utils/middleware/jwt-authorization.middleware.ts b/src/utils/middleware/jwt-authorization.middleware.ts index a08b6395..19742280 100644 --- a/src/utils/middleware/jwt-authorization.middleware.ts +++ b/src/utils/middleware/jwt-authorization.middleware.ts @@ -10,7 +10,11 @@ import { NextFunction, Request, Response } from 'express'; import { UserRepository } from 'src/user/repository/user.repository'; import { sanitizeUrl } from '../utils'; import { redisClient } from '../redis.provider'; -import { AUTH_ERRORS } from 'src/social-login/constants/en'; +import { + AUTH_ERRORS, + ERROR_MESSAGE, + REFRESH_TOKEN_ERROR, +} from 'src/social-login/constants/en'; @Injectable() export class JWTAuthorizeMiddleware implements NestMiddleware { constructor(private readonly userRepository: UserRepository) {} @@ -18,9 +22,7 @@ export class JWTAuthorizeMiddleware implements NestMiddleware { Logger.log('Inside JWTAuthorizeMiddleware', 'JWTAuthorizeMiddleware'); const authToken: string = req?.cookies?.accessToken; if (!authToken) { - throw new UnauthorizedException([ - 'Please pass authorization token in cookie', - ]); + throw new UnauthorizedException([AUTH_ERRORS.EMPTY_TOKEN]); } let decoded; try { @@ -51,17 +53,15 @@ export class JWTAuthorizeMiddleware implements NestMiddleware { sanitizeUrl(decoded.aud, false), ); if (!ifDomainValid) { - throw new Error( - 'This token was issued for a different domain than the one making the request.', - ); + throw new Error(AUTH_ERRORS.TOKEN_DOMAIN_MISMATCH); } } else { - throw new Error('Token does not contain a valid domain.'); + throw new Error(AUTH_ERRORS.TOKEN_DOMAIN_MISSING); } } const { sid, sub } = decoded; if (!sid || !sub) { - throw new UnauthorizedException(['Invalid token']); + throw new UnauthorizedException([AUTH_ERRORS.INVALID_TOKEN]); } const sessionRaw = await redisClient.get(`session:${sid}`); if (!sessionRaw) { @@ -73,10 +73,10 @@ export class JWTAuthorizeMiddleware implements NestMiddleware { } if (session.refreshVersion !== decoded.refreshVersion) { throw new UnauthorizedException([ - 'Your session has expired. Please log in again.', + REFRESH_TOKEN_ERROR.REFRESH_VERSION_MISMATCH, ]); } - + if (session.isTwoFactorAuthenticated) { if (!session.isTwoFactorVerified) { throw new UnauthorizedException([AUTH_ERRORS.TWO_FA_REQUIRED]); @@ -86,7 +86,7 @@ export class JWTAuthorizeMiddleware implements NestMiddleware { userId: decoded.sub, }); if (!user) { - throw new Error('User not found'); + throw new Error(ERROR_MESSAGE.USER_NOT_FOUND); } req['user'] = user; req['session'] = session; From fa5b6be768db270a11946c54bf089749a04db2a6 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Thu, 27 Nov 2025 10:38:18 +0530 Subject: [PATCH 14/55] removed unused middleware --- src/app-auth/app-auth.module.ts | 5 ---- .../customer-onboarding.module.ts | 4 --- .../2FA-jwt-authorization.middleware.ts | 30 ------------------- src/webpage-config/webpage-config.module.ts | 8 ----- 4 files changed, 47 deletions(-) delete mode 100644 src/utils/middleware/2FA-jwt-authorization.middleware.ts diff --git a/src/app-auth/app-auth.module.ts b/src/app-auth/app-auth.module.ts index dad42a7e..4943c504 100644 --- a/src/app-auth/app-auth.module.ts +++ b/src/app-auth/app-auth.module.ts @@ -23,7 +23,6 @@ import { SupportedServiceService } from 'src/supported-service/services/supporte import { SupportedServiceList } from 'src/supported-service/services/service-list'; import { JWTAuthorizeMiddleware } from 'src/utils/middleware/jwt-authorization.middleware'; import { UserModule } from 'src/user/user.module'; -import { TwoFAAuthorizationMiddleware } from 'src/utils/middleware/2FA-jwt-authorization.middleware'; import { CreditModule } from 'src/credits/credits.module'; import { JWTAccessAccountMiddleware } from 'src/utils/middleware/jwt-accessAccount.middlerwere'; import { AdminPeopleRepository } from 'src/people/repository/people.repository'; @@ -80,10 +79,6 @@ export class AppAuthModule implements NestModule { .apply(JWTAccessAccountMiddleware) .exclude({ path: '/api/v1/app/marketplace', method: RequestMethod.GET }) .forRoutes(AppAuthController); - consumer - .apply(TwoFAAuthorizationMiddleware) - .exclude({ path: '/api/v1/app/marketplace', method: RequestMethod.GET }) - .forRoutes(AppAuthController); consumer.apply(RateLimitMiddleware).forRoutes(AppAuthController); } } diff --git a/src/customer-onboarding/customer-onboarding.module.ts b/src/customer-onboarding/customer-onboarding.module.ts index 5da2ed21..8c1b6111 100644 --- a/src/customer-onboarding/customer-onboarding.module.ts +++ b/src/customer-onboarding/customer-onboarding.module.ts @@ -16,7 +16,6 @@ import { TrimMiddleware } from 'src/utils/middleware/trim.middleware'; import { JWTAuthorizeMiddleware } from 'src/utils/middleware/jwt-authorization.middleware'; import { RateLimitMiddleware } from 'src/utils/middleware/rate-limit.middleware'; import { JWTAccessAccountMiddleware } from 'src/utils/middleware/jwt-accessAccount.middlerwere'; -import { TwoFAAuthorizationMiddleware } from 'src/utils/middleware/2FA-jwt-authorization.middleware'; import { UserModule } from 'src/user/user.module'; import { PeopleModule } from 'src/people/people.module'; import { MailNotificationModule } from 'src/mail-notification/mail-notification.module'; @@ -60,9 +59,6 @@ export class CustomerOnboardingModule implements NestModule { consumer .apply(JWTAccessAccountMiddleware) .forRoutes(CustomerOnboardingController); - consumer - .apply(TwoFAAuthorizationMiddleware) - .forRoutes(CustomerOnboardingController); consumer.apply(RateLimitMiddleware).forRoutes(CustomerOnboardingController); } } diff --git a/src/utils/middleware/2FA-jwt-authorization.middleware.ts b/src/utils/middleware/2FA-jwt-authorization.middleware.ts deleted file mode 100644 index 0a71f67d..00000000 --- a/src/utils/middleware/2FA-jwt-authorization.middleware.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { - Injectable, - Logger, - NestMiddleware, - UnauthorizedException, -} from '@nestjs/common'; -import { Request, Response, NextFunction } from 'express'; - -@Injectable() -export class TwoFAAuthorizationMiddleware implements NestMiddleware { - async use(req: Request, res: Response, next: NextFunction) { - Logger.log( - 'Inside TwoFAAuthorizationMiddleware', - 'TwoFAAuthorizationMiddleware', - ); - if (!req['user'] || Object.keys(req['user']).length === 0) { - throw new UnauthorizedException(['User not authenticated']); - } - const user = req['user']; - if (user['authenticators'] && user['authenticators'].length > 0) { - const isAnyAuthenticator2FA = user['authenticators'].some( - (authenticator) => authenticator.isTwoFactorAuthenticated, - ); - if (isAnyAuthenticator2FA && !user['isTwoFactorAuthenticated']) { - throw new UnauthorizedException(['2FA authentication is required']); - } - } - next(); - } -} diff --git a/src/webpage-config/webpage-config.module.ts b/src/webpage-config/webpage-config.module.ts index 86381dc0..3bdc540b 100644 --- a/src/webpage-config/webpage-config.module.ts +++ b/src/webpage-config/webpage-config.module.ts @@ -18,7 +18,6 @@ import { RateLimitMiddleware } from 'src/utils/middleware/rate-limit.middleware' import { TrimMiddleware } from 'src/utils/middleware/trim.middleware'; import { JWTAuthorizeMiddleware } from 'src/utils/middleware/jwt-authorization.middleware'; import { JWTAccessAccountMiddleware } from 'src/utils/middleware/jwt-accessAccount.middlerwere'; -import { TwoFAAuthorizationMiddleware } from 'src/utils/middleware/2FA-jwt-authorization.middleware'; import { UserModule } from 'src/user/user.module'; import { JwtModule } from '@nestjs/jwt'; import { AdminPeopleRepository } from 'src/people/repository/people.repository'; @@ -64,13 +63,6 @@ export class WebpageConfigModule implements NestModule { method: RequestMethod.GET, }) .forRoutes(WebpageConfigController); - consumer - .apply(TwoFAAuthorizationMiddleware) - .exclude({ - path: 'api/v1/app/:appId/kyc-webpage-config', - method: RequestMethod.GET, - }) - .forRoutes(WebpageConfigController); consumer .apply(RateLimitMiddleware) .exclude({ From 60a2d0841895087fcb6d25515e4c5ec4ff3d7b34 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Thu, 27 Nov 2025 11:47:14 +0530 Subject: [PATCH 15/55] fixed issue in mfa --- .deploy/deployment.yaml | 2 + .github/workflows/pipeline.yaml | 2 + dev.env.sample | 1 + src/social-login/constants/en.ts | 14 ++++-- .../controller/social-login.controller.ts | 35 ++------------ .../services/social-login.service.ts | 47 ++++++++++++++----- .../jwt-authorization.middleware.ts | 3 +- src/utils/utils.ts | 4 +- 8 files changed, 60 insertions(+), 48 deletions(-) diff --git a/.deploy/deployment.yaml b/.deploy/deployment.yaml index ad6b3af9..e85535cc 100644 --- a/.deploy/deployment.yaml +++ b/.deploy/deployment.yaml @@ -111,6 +111,8 @@ spec: value: '__MAX_RETRY_ATTEMPT__' - name: MFA_REDIRECT_URL value: __MFA_REDIRECT_URL__ + - name: MAX_MFA_RETRY_ATTEMPT + value: '__MAX_MFA_RETRY_ATTEMPT__' volumeMounts: - name: mongo diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 0c1da8eb..8f972f67 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -147,6 +147,8 @@ jobs: run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__OTP_EXPIRY_MINUTES__#${{ secrets.OTP_EXPIRY_MINUTES }}#" {} \; - name: "Replace Secrets" run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__MAX_RETRY_ATTEMPT__#${{ secrets.MAX_RETRY_ATTEMPT }}#" {} \; + - name: "Replace secrets" + run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__MAX_MFA_RETRY_ATTEMPT__#${{ secrets.MAX_MFA_RETRY_ATTEMPT }}#" {} \; - name: "Replace secrets" run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__MFA_REDIRECT_URL__#${{ secrets.MFA_REDIRECT_URL }}#" {} \; - name: "Deploy to GKE" diff --git a/dev.env.sample b/dev.env.sample index c4886613..84bb8d80 100644 --- a/dev.env.sample +++ b/dev.env.sample @@ -24,6 +24,7 @@ OTP_EXPIRY_MINUTES=5 MAX_RETRY_ATTEMPT=3 NODE_ENV=development MFA_REDIRECT_URL='http://localhost:9001/#/studio/mfa' +MAX_MFA_RETRY_ATTEMPT=3 diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts index 6f1e5489..7bb18d3e 100644 --- a/src/social-login/constants/en.ts +++ b/src/social-login/constants/en.ts @@ -1,8 +1,6 @@ export const ERROR_MESSAGE = { SESSION_NOT_FOUND: 'Session not found or expired.', - MFA_ALREADY_ENABLED: 'MFA is already enabled for this user.', INVALID_OTP: 'Invalid or expired OTP code.', - MFA_NOT_VERIFIED: 'Two-factor authentication (2FA) is required.', USER_NOT_FOUND: 'User not found', } as const; @@ -10,7 +8,6 @@ export const AUTH_ERRORS = { EMPTY_TOKEN: 'Please pass authorization token in cookie', SESSION_EXPIRED: 'Session expired or logged out', SESSION_MISMATCH: 'Token does not match session', - TWO_FA_REQUIRED: '2FA verification required', TOKEN_DOMAIN_MISMATCH: 'This token was issued for a different domain than the one making the request.', TOKEN_DOMAIN_MISSING: 'Token does not contain a valid domain.', @@ -22,3 +19,14 @@ export const REFRESH_TOKEN_ERROR = { REFRESH_TOKEN_NOT_FOUND: 'Refresh token not found or expired.', REFRESH_VERSION_MISMATCH: 'Your session has expired. Please log in again.', }; + +export const MFA_ERROR = { + MFA_ALREADY_ENABLED: 'MFA is already enabled for this user.', + MFA_NOT_VERIFIED: 'Two-factor authentication (2FA) is required.', + MFA_ALREADY_VERIFIED: + 'MFA already verified. No further verification required.', + TWO_FA_REQUIRED: '2FA verification required', + INVALID_MFA_METHOD: 'Invalid MFA method selected for this session.', + MFA_MAX_RETRY_EXCEEDED: + 'MFA verification failed too many times. Session expired.', +}; diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 3a22a1f7..99ed36d2 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -63,15 +63,10 @@ export class SocialLoginController { status: 401, type: UnauthorizedError, }) - @ApiQuery({ - name: 'provider', - description: 'Authentication provider', - required: true, - }) @Get('auth/google/authorize') - async socialAuthRedirect(@Res() res, @Query() loginProvider) { + async socialAuthRedirect(@Res() res) { Logger.log('socialAuthRedirect() method starts', 'SocialLoginController'); - const { provider } = loginProvider; + const provider = 'google'; Logger.log(`Looged in with ${provider}`, 'SocialLoginController'); const { authUrl } = await this.socialLoginService.generateAuthUrlByProvider( provider, @@ -137,14 +132,6 @@ export class SocialLoginController { error: null, }; } - - @ApiBearerAuth('Authorization') - @Post('auth/login/refresh') - async generateRefreshToken(@Req() req) { - return { - authToken: await this.socialLoginService.socialLogin(req), - }; - } @ApiBearerAuth('Authorization') @Post('auth/tokens/refresh') async refreshTokenGeneration(@Req() req, @Res() res) { @@ -255,22 +242,8 @@ export class SocialLoginController { @ApiBearerAuth('Authorization') @Post('auth/logout') async logout(@Req() req, @Res() res) { - const cookieDomain = this.config.get('COOKIE_DOMAIN'); - const isProduction = this.config.get('NODE_ENV') === 'production'; - res.clearCookie('authToken', { - path: '/', - domain: isProduction ? cookieDomain : undefined, - sameSite: isProduction ? 'None' : 'Lax', - secure: isProduction, - httpOnly: true, - }); - res.clearCookie('refreshToken', { - path: '/', - domain: isProduction ? cookieDomain : undefined, - sameSite: isProduction ? 'None' : 'Lax', - secure: isProduction, - httpOnly: true, - }); + res.clearCookie(TOKEN.AUTH.name, getCookieOptions(undefined, true)); + res.clearCookie(TOKEN.REFRESH.name, getCookieOptions(undefined, true)); return res.status(200).json({ message: 'Logged out successfully' }); } diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 2262e340..00540104 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -24,10 +24,7 @@ import { import { UserDocument, UserRole } from 'src/user/schema/user.schema'; import { redisClient } from 'src/utils/redis.provider'; import { TIME } from 'src/utils/time-constant'; -import { - ERROR_MESSAGE as MFA_MESSAGE, - REFRESH_TOKEN_ERROR, -} from '../constants/en'; +import { MFA_ERROR, ERROR_MESSAGE, REFRESH_TOKEN_ERROR } from '../constants/en'; @Injectable() export class SocialLoginService { @@ -174,20 +171,44 @@ export class SocialLoginService { throw new BadRequestException(['Invalid or expired sessionId']); } const sessionDetail = JSON.parse(sessionDetailJson); + if (sessionDetail?.isTwoFactorVerified) { + throw new BadRequestException([MFA_ERROR.MFA_ALREADY_VERIFIED]); + } const authenticatorDetail = sessionDetail.authenticators.find( (auth) => auth.type === authenticatorType, ); + if (!authenticatorDetail) { + throw new BadRequestException([MFA_ERROR.INVALID_MFA_METHOD]); + } + sessionDetail.twoFactorRetryCount = sessionDetail.twoFactorRetryCount ?? 0; const isVerified = authenticator.verify({ token: twoFactorAuthenticationCode, secret: authenticatorDetail.secret, }); + const maxRetryAttempts = this.config.get( + 'MAX_MFA_RETRY_ATTEMPT', + 3, + ); + if (!isVerified) { - await redisClient.del(sessionKey); + sessionDetail.twoFactorRetryCount++; + if (sessionDetail.twoFactorRetryCount > maxRetryAttempts) { + await redisClient.del(sessionKey); + throw new BadRequestException([MFA_ERROR.MFA_MAX_RETRY_EXCEEDED]); + } + await redisClient.set( + sessionKey, + JSON.stringify(sessionDetail), + 'EX', + TIME.WEEK, + ); return { isVerified: false }; } delete sessionDetail?.authenticators; + delete sessionDetail.twoFactorRetryCount; sessionDetail.isTwoFactorVerified = true; - sessionDetail.isTwoFactorAuthenticated = true; + sessionDetail.isTwoFactorAuthenticated = + sessionDetail.isTwoFactorAuthenticated; await redisClient.set( sessionKey, JSON.stringify(sessionDetail), @@ -251,13 +272,13 @@ export class SocialLoginService { const sessionDetail = await redisClient.get(sessionKey); if (!sessionDetail) { return { - error: MFA_MESSAGE.SESSION_NOT_FOUND, + error: ERROR_MESSAGE.SESSION_NOT_FOUND, }; } const sessionJson = JSON.parse(sessionDetail); if (sessionJson?.mfaEnabled && !sessionJson?.mfaVerified) { return { - error: MFA_MESSAGE.MFA_NOT_VERIFIED, + error: MFA_ERROR.MFA_NOT_VERIFIED, }; } sessionJson.refreshVersion += 1; @@ -335,6 +356,7 @@ export class SocialLoginService { userId: user.userId, isTwoFactorVerified: false, isTwoFactorAuthenticated: isMfaRequired, + twoFactorRetryCount: 0, }; if (isMfaRequired) { sessionData.authenticators = activeAuthenticators; @@ -382,14 +404,17 @@ export class SocialLoginService { (auth) => auth.type === authenticatorType, ); if (authenticatorDetail.isTwoFactorAuthenticated) { - return { isVerified: false, error: MFA_MESSAGE.MFA_ALREADY_ENABLED }; + return { + isVerified: false, + error: MFA_ERROR.MFA_ALREADY_ENABLED, + }; } const isVerified = authenticator.verify({ token: twoFactorAuthenticationCode, secret: authenticatorDetail.secret, }); if (!isVerified) { - return { isVerified, error: MFA_MESSAGE.INVALID_OTP }; + return { isVerified, error: ERROR_MESSAGE.INVALID_OTP }; } if (!authenticatorDetail.isTwoFactorAuthenticated && isVerified) { user.authenticators.map((authn) => { @@ -408,7 +433,7 @@ export class SocialLoginService { if (!sessionJson) { return { isVerified, - error: MFA_MESSAGE.SESSION_NOT_FOUND, + error: ERROR_MESSAGE.SESSION_NOT_FOUND, }; } const sessionObj = JSON.parse(sessionJson); diff --git a/src/utils/middleware/jwt-authorization.middleware.ts b/src/utils/middleware/jwt-authorization.middleware.ts index 19742280..37672d48 100644 --- a/src/utils/middleware/jwt-authorization.middleware.ts +++ b/src/utils/middleware/jwt-authorization.middleware.ts @@ -13,6 +13,7 @@ import { redisClient } from '../redis.provider'; import { AUTH_ERRORS, ERROR_MESSAGE, + MFA_ERROR, REFRESH_TOKEN_ERROR, } from 'src/social-login/constants/en'; @Injectable() @@ -79,7 +80,7 @@ export class JWTAuthorizeMiddleware implements NestMiddleware { if (session.isTwoFactorAuthenticated) { if (!session.isTwoFactorVerified) { - throw new UnauthorizedException([AUTH_ERRORS.TWO_FA_REQUIRED]); + throw new UnauthorizedException([MFA_ERROR.TWO_FA_REQUIRED]); } } const user = await this.userRepository.findOne({ diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 63c40ad0..b1bb13e1 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -155,7 +155,7 @@ export function mapUserAccessList(userAccessList) { ]; } -export function getCookieOptions(maxAge: number) { +export function getCookieOptions(maxAge?: number, isClear = false) { const cookieDomain = process.env.COOKIE_DOMAIN; const isProd = process.env.NODE_ENV || 'production'; return { @@ -164,6 +164,6 @@ export function getCookieOptions(maxAge: number) { sameSite: isProd === 'production' ? 'None' : 'Lax', domain: isProd ? cookieDomain : undefined, path: '/', - maxAge, + ...(isClear ? {} : { maxAge }), }; } From 2fd6e4effc2b2e2bedb1b004257bdd2e49fcf06b Mon Sep 17 00:00:00 2001 From: varsha766 Date: Thu, 27 Nov 2025 12:29:56 +0530 Subject: [PATCH 16/55] fix --- .../controller/email-otp-login.controller.ts | 10 +++++----- src/utils/time-constant.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/social-login/controller/email-otp-login.controller.ts b/src/social-login/controller/email-otp-login.controller.ts index 109233ca..21df21ce 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_MAX_AGE } from 'src/utils/time-constant'; +import { TOKEN } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') @Controller('/api/v1/auth/email/otp') @@ -79,14 +79,14 @@ export class EmailOtpLoginController { res.redirect(this.config.get('MFA_REDIRECT_URL')); } res.cookie( - 'accessToken', + TOKEN.AUTH.name, result.accessToken, - getCookieOptions(TOKEN_MAX_AGE.AUTH_TOKEN), + getCookieOptions(TOKEN.AUTH.expiry), ); res.cookie( - 'refreshToken', + TOKEN.REFRESH.name, result.refreshToken, - getCookieOptions(TOKEN_MAX_AGE.REFRESH_TOKEN), + getCookieOptions(TOKEN.REFRESH.expiry), ); res.redirect(`${this.config.get('REDIRECT_URL')}`); } catch (err) { diff --git a/src/utils/time-constant.ts b/src/utils/time-constant.ts index 59c72422..06caeb21 100644 --- a/src/utils/time-constant.ts +++ b/src/utils/time-constant.ts @@ -13,7 +13,7 @@ export const TOKEN_MAX_AGE = { export const TOKEN = { AUTH: { name: 'accessToken', - expiry: 4 * TIME.MINUTE * 1000, + expiry: 30 * TIME.MINUTE * 1000, }, REFRESH: { name: 'refreshToken', From 19e1e7b9f37d48f87fe086b524743d33307f0f9a Mon Sep 17 00:00:00 2001 From: varsha766 Date: Thu, 27 Nov 2025 13:03:14 +0530 Subject: [PATCH 17/55] fixed logout issue --- src/social-login/constants/en.ts | 1 + .../controller/social-login.controller.ts | 14 ++++++++++---- src/social-login/services/social-login.service.ts | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts index 7bb18d3e..0d20e86a 100644 --- a/src/social-login/constants/en.ts +++ b/src/social-login/constants/en.ts @@ -2,6 +2,7 @@ export const ERROR_MESSAGE = { SESSION_NOT_FOUND: 'Session not found or expired.', INVALID_OTP: 'Invalid or expired OTP code.', USER_NOT_FOUND: 'User not found', + LOGOUT_ISSUE: 'Logout failed on server', } as const; export const AUTH_ERRORS = { diff --git a/src/social-login/controller/social-login.controller.ts b/src/social-login/controller/social-login.controller.ts index 99ed36d2..bbe3a8a5 100644 --- a/src/social-login/controller/social-login.controller.ts +++ b/src/social-login/controller/social-login.controller.ts @@ -7,7 +7,6 @@ import { UseFilters, Post, Res, - Query, Body, Delete, UnauthorizedException, @@ -20,7 +19,6 @@ import { ApiBearerAuth, ApiExcludeEndpoint, ApiOkResponse, - ApiQuery, ApiResponse, ApiTags, ApiUnauthorizedResponse, @@ -44,8 +42,8 @@ import { } from '../dto/request.dto'; import { AppError } from 'src/app-auth/dtos/fetch-app.dto'; import { UserRole } from 'src/user/schema/user.schema'; -import { ERROR_MESSAGE as MFA_MESSAGE } from '../constants/en'; -import { TOKEN_MAX_AGE, TOKEN } from 'src/utils/time-constant'; +import { ERROR_MESSAGE, ERROR_MESSAGE as MFA_MESSAGE } from '../constants/en'; +import { TOKEN } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('Authentication') @Controller('api/v1') @@ -242,6 +240,14 @@ export class SocialLoginController { @ApiBearerAuth('Authorization') @Post('auth/logout') async logout(@Req() req, @Res() res) { + const refreshToken = req.cookies[TOKEN.REFRESH.name]; + const result = await this.socialLoginService.logout( + refreshToken, + req.session, + ); + if (!result.success) { + throw new BadRequestException([ERROR_MESSAGE.LOGOUT_ISSUE]); + } res.clearCookie(TOKEN.AUTH.name, getCookieOptions(undefined, true)); res.clearCookie(TOKEN.REFRESH.name, getCookieOptions(undefined, true)); return res.status(200).json({ message: 'Logged out successfully' }); diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 00540104..2d2779af 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -460,4 +460,18 @@ export class SocialLoginService { } return { isVerified }; } + async logout(refreshToken, session) { + try { + const sessionId = session.sessionId; + if (sessionId) await redisClient.del(`session:${sessionId}`); + if (refreshToken) await redisClient.del(`refresh:${refreshToken}`); + return { success: true }; + } catch (e) { + Logger.error( + 'Inside logout() to delete data from redis', + 'SocialLoginService', + ); + return { success: false }; + } + } } From 354ccb901a1a3340182e599f5998aa85d11b98c5 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Thu, 27 Nov 2025 17:39:02 +0530 Subject: [PATCH 18/55] refactor team invitation --- src/people/controller/people.controller.ts | 10 ++++---- src/people/dto/create-person.dto.ts | 10 +++++++- src/people/schema/people.schema.ts | 5 ++++ src/people/services/people.service.ts | 28 ++++++++++++++++++++++ src/roles/repository/role.repository.ts | 3 +++ 5 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/people/controller/people.controller.ts b/src/people/controller/people.controller.ts index c2fa43ba..99524fb4 100644 --- a/src/people/controller/people.controller.ts +++ b/src/people/controller/people.controller.ts @@ -29,7 +29,7 @@ import { TOKEN_MAX_AGE } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('People') @ApiBearerAuth('Authorization') -@Controller('/api/v1/people') +@Controller('/api/v1/tenants') export class PeopleController { constructor( private readonly peopleService: PeopleService, @@ -41,7 +41,7 @@ export class PeopleController { description: 'Invite a user to your account', type: InviteResponseDTO, }) - @Post('/invite') + @Post('/invitations') @UsePipes(ValidationPipe) createInvite(@Body() createInviteDto: CreateInviteDto, @Req() req) { const { user } = req; @@ -53,14 +53,14 @@ export class PeopleController { description: 'Accept invite', type: InviteResponseDTO, }) - @Post('/invite/accept/:inviteCode') + @Post('/invitations/:inviteCode/accept') @UsePipes(ValidationPipe) acceptInvite(@Param('inviteCode') inviteCode: string, @Req() req) { const { user } = req; return this.peopleService.acceptInvite(inviteCode, user); } - @Patch('invite/:inviteCode') + @Patch('invitations/:inviteCode') @UsePipes(ValidationPipe) update(@Param('inviteCode') inviteCode: string, @Req() req) { const { user } = req; @@ -84,7 +84,7 @@ export class PeopleController { type: InviteListResponseDTO, isArray: true, }) - @Get('/invites') + @Get('/invitations') @UsePipes(ValidationPipe) async getAllInvites(@Req() req) { const { user } = req; diff --git a/src/people/dto/create-person.dto.ts b/src/people/dto/create-person.dto.ts index d6cc6508..7bad5ead 100644 --- a/src/people/dto/create-person.dto.ts +++ b/src/people/dto/create-person.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsNotEmpty, IsString, Matches } from 'class-validator'; +import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateInviteDto { @ApiProperty({ @@ -8,6 +8,14 @@ export class CreateInviteDto { }) @IsEmail({}, { message: 'Email must be a valid email address' }) emailId: string; + @ApiProperty({ + name: 'roleId', + type: String, + required: false, + }) + @IsOptional() + @IsString() + roleId?: string; } export class InviteResponseDTO { diff --git a/src/people/schema/people.schema.ts b/src/people/schema/people.schema.ts index d730b058..6bb004b6 100644 --- a/src/people/schema/people.schema.ts +++ b/src/people/schema/people.schema.ts @@ -43,6 +43,11 @@ export class AdminPeople { required: false, }) roleName?: string; + @Prop({ + name: 'inviteeEmail', + required: false, + }) + inviteeEmail?: string; } export const AdminPeopleSchema = SchemaFactory.createForClass(AdminPeople); diff --git a/src/people/services/people.service.ts b/src/people/services/people.service.ts index a10479da..9c2f4bfd 100644 --- a/src/people/services/people.service.ts +++ b/src/people/services/people.service.ts @@ -42,6 +42,7 @@ export class PeopleService { const userDetails = await this.userService.findOne({ email: emailId, }); + // if (userDetails == null) { // throw new NotFoundException( // `Cannot invite an non existing user with email: ${emailId}`, @@ -69,6 +70,30 @@ export class PeopleService { // return isInvitedAlready; // } const invitecode = `${Date.now()}-${uuidv4()}`; + const { roleId } = createPersonDto; + let roleDetail; + if (roleId) { + roleDetail = await this.roleRepository.findOne({ _id: roleId }); + if (!roleDetail) { + throw new BadRequestException([`Role not found for roleId: ${roleId}`]); + } + } else { + const roles = await this.roleRepository.findUsingAggregation([ + { $match: { userId: adminUserData.userId } }, + { + $addFields: { + permissionsCount: { $size: '$permissions' }, + }, + }, + { $sort: { permissionsCount: 1 } }, + { $limit: 1 }, + ]); + roleDetail = roles?.[0]; + } + if (!roleDetail) + throw new BadRequestException([ + 'Add a role first before inviting a member.', + ]); const invite = await this.adminPeopleService.create({ adminId: adminUserData.userId, userId: userDetails?.userId || emailId, @@ -77,6 +102,9 @@ export class PeopleService { invitationValidTill: new Date( Date.now() + 2 * 24 * 60 * 60 * 1000, ).toISOString(), + roleId: roleDetail._id.toString(), + roleName: roleDetail.roleName, + inviteeEmail: emailId, }); this.mailNotificationService.addJobToMailQueue({ mailName: JobNames.SEND_TEAM_MATE_INVITATION_MAIL, diff --git a/src/roles/repository/role.repository.ts b/src/roles/repository/role.repository.ts index 73c1b531..d34c7417 100644 --- a/src/roles/repository/role.repository.ts +++ b/src/roles/repository/role.repository.ts @@ -34,4 +34,7 @@ export class RoleRepository { async findOneAndDelete(roleFilterQuery: FilterQuery) { return this.roleModel.findOneAndDelete(roleFilterQuery); } + async findUsingAggregation(pipeline: any) { + return this.roleModel.aggregate(pipeline); + } } From 4a5584f1867dd2a308a7ce1f6e6c87929280f269 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 28 Nov 2025 17:41:10 +0530 Subject: [PATCH 19/55] fixed switch to tenant account --- src/people/constant/en.ts | 18 +++ src/people/controller/people.controller.ts | 37 ++--- src/people/dto/create-person.dto.ts | 2 +- src/people/services/people.service.ts | 142 +++++++----------- .../jwt-accessAccount.middlerwere.ts | 31 ++-- 5 files changed, 102 insertions(+), 128 deletions(-) create mode 100644 src/people/constant/en.ts diff --git a/src/people/constant/en.ts b/src/people/constant/en.ts new file mode 100644 index 00000000..23f57a5a --- /dev/null +++ b/src/people/constant/en.ts @@ -0,0 +1,18 @@ +export const TENANT_ERRORS = { + ALREADY_IN_TENANT: 'You are already switched to this tenant.', + ADMIN_NOT_FOUND: 'Tenant admin not found.', + NOT_A_MEMBER: (email: string) => + `You are not a member of the tenant managed by ${email}.`, + INVITATION_NOT_ACCEPTED: + 'Please accept the tenant invitation before switching.', + ROLE_NOT_FOUND: 'The assigned role does not exist.', + NO_PERMISSION: 'The assigned role has no permissions.', +}; + +export const TENANT_INVITE_ERRORS = { + SELF_INVITATION_NOT_ALLOWED: 'You cannot send an invitation to yourself.', + ALREADY_INVITED: 'This user is already associated with your account.', + ROLE_NOT_FOUND: (roleId: string) => + `No role found for the provided roleId: ${roleId}.`, + NO_ROLE_ASSIGNED: 'Please add a role before inviting a member.', +}; diff --git a/src/people/controller/people.controller.ts b/src/people/controller/people.controller.ts index 99524fb4..a9b01035 100644 --- a/src/people/controller/people.controller.ts +++ b/src/people/controller/people.controller.ts @@ -14,18 +14,17 @@ import { } from '@nestjs/common'; import { PeopleService } from '../services/people.service'; import { - AdminLoginDTO, AttachRoleDTO, CreateInviteDto, InviteListResponseDTO, InviteResponseDTO, PeopleListResponseDTO, + TenantLoginDTO, } from '../dto/create-person.dto'; import { DeletePersonDto } from '../dto/update-person.dto'; import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; import { AllExceptionsFilter } from 'src/utils/utils'; import { ConfigService } from '@nestjs/config'; -import { TOKEN_MAX_AGE } from 'src/utils/time-constant'; @UseFilters(AllExceptionsFilter) @ApiTags('People') @ApiBearerAuth('Authorization') @@ -104,32 +103,14 @@ export class PeopleController { const { user } = req; return this.peopleService.attachRole(body, user); } - - @Post('/admin/login') + @Post('/access') @UsePipes(ValidationPipe) - async adminLogin(@Body() body: AdminLoginDTO, @Req() req, @Res() res) { - const { user } = req; - const data = await this.peopleService.adminLogin(body, user); - const cookieDomain = this.config.get('COOKIE_DOMAIN'); - const isProduction = this.config.get('NODE_ENV') === 'production'; - res.cookie('authToken', data?.authToken, { - httpOnly: true, - secure: isProduction, - sameSite: isProduction ? 'None' : 'Lax', - maxAge: TOKEN_MAX_AGE.AUTH_TOKEN, - domain: isProduction ? cookieDomain : undefined, - path: '/', - }); - res.cookie('refreshToken', data?.refreshToken, { - httpOnly: true, - secure: isProduction, - sameSite: isProduction ? 'None' : 'Lax', - maxAge: TOKEN_MAX_AGE.REFRESH_TOKEN, - domain: isProduction ? cookieDomain : undefined, - path: '/', - }); - return res.json({ - message: `Successfully switched to the ${data.adminEmail} account`, - }); + async switchTenantAccount(@Body() tenantDto: TenantLoginDTO, @Req() req) { + const { user, session } = req; + return await this.peopleService.switchTenantAccount( + user, + session, + tenantDto, + ); } } diff --git a/src/people/dto/create-person.dto.ts b/src/people/dto/create-person.dto.ts index 7bad5ead..5228fee4 100644 --- a/src/people/dto/create-person.dto.ts +++ b/src/people/dto/create-person.dto.ts @@ -208,7 +208,7 @@ export class AttachRoleDTO { userId: string; } -export class AdminLoginDTO { +export class TenantLoginDTO { @ApiProperty({ name: 'adminId', }) diff --git a/src/people/services/people.service.ts b/src/people/services/people.service.ts index 9c2f4bfd..3510b32c 100644 --- a/src/people/services/people.service.ts +++ b/src/people/services/people.service.ts @@ -3,13 +3,14 @@ import { ConflictException, Injectable, NotFoundException, + UnauthorizedException, } from '@nestjs/common'; import { v4 as uuidv4 } from 'uuid'; import { - AdminLoginDTO, AttachRoleDTO, CreateInviteDto, + TenantLoginDTO, } from '../dto/create-person.dto'; import { DeletePersonDto } from '../dto/update-person.dto'; import { UserRepository } from 'src/user/repository/user.repository'; @@ -19,9 +20,9 @@ import { SocialLoginService } from 'src/social-login/services/social-login.servi import { JwtService } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; import { MailNotificationService } from 'src/mail-notification/services/mail-notification.service'; -import { UserRole } from 'src/user/schema/user.schema'; import { JobNames } from 'src/utils/time-constant'; -import { mapUserAccessList } from 'src/utils/utils'; +import { redisClient } from 'src/utils/redis.provider'; +import { TENANT_ERRORS, TENANT_INVITE_ERRORS } from '../constant/en'; @Injectable() export class PeopleService { @@ -37,7 +38,9 @@ export class PeopleService { async createInvitation(createPersonDto: CreateInviteDto, adminUserData) { const { emailId } = createPersonDto; if (emailId === adminUserData?.email) { - throw new BadRequestException(['Self invitation is not available']); + throw new BadRequestException([ + TENANT_INVITE_ERRORS.SELF_INVITATION_NOT_ALLOWED, + ]); } const userDetails = await this.userService.findOne({ email: emailId, @@ -55,27 +58,17 @@ export class PeopleService { adminId: adminUserData.userId, }); if (adminPeople != null) { - throw new ConflictException([ - 'User already exists to your account', - 'Already Invited', - ]); + throw new ConflictException([TENANT_INVITE_ERRORS.ALREADY_INVITED]); } - - // const isInvitedAlready = await this.inviteRepository.findOne({ - // invitor: adminUserData.userId, - // invitee: userDetails.userId, - // }); - - // if (isInvitedAlready !== null) { - // return isInvitedAlready; - // } const invitecode = `${Date.now()}-${uuidv4()}`; const { roleId } = createPersonDto; let roleDetail; if (roleId) { roleDetail = await this.roleRepository.findOne({ _id: roleId }); if (!roleDetail) { - throw new BadRequestException([`Role not found for roleId: ${roleId}`]); + throw new BadRequestException([ + TENANT_INVITE_ERRORS.ROLE_NOT_FOUND(roleId), + ]); } } else { const roles = await this.roleRepository.findUsingAggregation([ @@ -91,9 +84,7 @@ export class PeopleService { roleDetail = roles?.[0]; } if (!roleDetail) - throw new BadRequestException([ - 'Add a role first before inviting a member.', - ]); + throw new BadRequestException([TENANT_INVITE_ERRORS.NO_ROLE_ASSIGNED]); const invite = await this.adminPeopleService.create({ adminId: adminUserData.userId, userId: userDetails?.userId || emailId, @@ -246,77 +237,56 @@ export class PeopleService { }, ); } - async adminLogin(body: AdminLoginDTO, user: any) { - const rawUrl = this.configService.get('INVITATIONURL'); - const url = new URL(rawUrl); - const domain = url.origin; - const { adminId } = body; + async switchTenantAccount( + userDetail, + sessionDetail, + tenantDto: TenantLoginDTO, + ) { + const { adminId } = tenantDto; + if (tenantDto.adminId == sessionDetail?.tenantId) { + throw new BadRequestException([TENANT_ERRORS.ALREADY_IN_TENANT]); + } const adminData = await this.userService.findOne({ userId: adminId, }); if (adminData == null) { - throw new BadRequestException(['Admin user not found']); + throw new BadRequestException([TENANT_ERRORS.ADMIN_NOT_FOUND]); } - const userId = user.userId; - let adminPeople = user; - let role; - if (userId !== adminId) { - adminPeople = await this.adminPeopleService.findOne({ - adminId, - userId, - }); - - if (adminPeople == null) { - throw new NotFoundException([ - 'You are not the member of ' + adminData.email, - ]); - } - - if (adminPeople.roleId == null) { - throw new BadRequestException([ - 'You do not have any role to access admin account', - ]); - } - - role = await this.roleRepository.findOne({ - _id: adminPeople.roleId, - }); + const tenantDetail = await this.adminPeopleService.findOne({ + adminId, + userId: userDetail.userId, + }); + if (!tenantDetail) { + throw new UnauthorizedException([ + TENANT_ERRORS.NOT_A_MEMBER(adminData.email), + ]); } - // const jwt = await this.socialLoginService.socialLogin({ - // user: { - // email: adminData.email, - // }, - // }); - - delete adminData.accessList; - delete adminData['_id']; - delete adminData.authenticators; - const accessAccount = { - ...adminData, - accessList: mapUserAccessList( - role?.permissions || adminPeople.accessList, - ), - }; - const payload = { - appUserID: user.userId, - ...user, - accessList: mapUserAccessList(user.accessList), - aud: domain, - }; - delete payload._id; - - delete payload.userId; - - payload.accessAccount = accessAccount; - payload.role = adminData?.role || UserRole.ADMIN; - const token = await this.socialLoginService.generateAuthToken(payload); - const refreshToken = await this.socialLoginService.generateRefreshToken( - payload, + if (!tenantDetail.accepted) { + throw new BadRequestException([TENANT_ERRORS.INVITATION_NOT_ACCEPTED]); + } + const permissionsDetail = await this.roleRepository.findOne({ + _id: tenantDetail.roleId, + }); + if (!permissionsDetail) { + throw new BadRequestException([TENANT_ERRORS.ROLE_NOT_FOUND]); + } + if ( + !permissionsDetail.permissions || + permissionsDetail.permissions.length <= 0 + ) { + throw new BadRequestException([TENANT_ERRORS.NO_PERMISSION]); + } + const permissions = permissionsDetail.permissions; + sessionDetail.tenantId = adminId; + sessionDetail.tenantUsersPermissions = permissions; + sessionDetail.createdAt = new Date().toISOString(); + const ttl = await redisClient.ttl(`session: ${sessionDetail.sessionId}`); + await redisClient.set( + `session: ${sessionDetail.sessionId}`, + JSON.stringify(sessionDetail), + 'EX', + ttl, ); - return { - authToken: token, - refreshToken, - adminEmail: adminData.email, - }; + return { message: 'Switched to tenant account successfully' }; } } diff --git a/src/utils/middleware/jwt-accessAccount.middlerwere.ts b/src/utils/middleware/jwt-accessAccount.middlerwere.ts index d67ccc0a..21eaad85 100644 --- a/src/utils/middleware/jwt-accessAccount.middlerwere.ts +++ b/src/utils/middleware/jwt-accessAccount.middlerwere.ts @@ -13,28 +13,33 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { async use(req: Request, res: Response, next: NextFunction) { try { // @ts-ignore - if (req.user?.accessAccount !== undefined) { - // @ts-ignore + const user = req.user; + const session = req['session']; + if (!session.tenantId) { + return next(); + } + const tenantId = req['session'].tenantId; - const userId = req.user.userId; + if (tenantId !== undefined) { // @ts-ignore - const adminId = req.user.accessAccount.userId; - if (adminId !== userId) { - const member = await this.adminPeople.findOne({ adminId, userId }); - if (member == null) { - throw new Error('Your access has been revoked'); - } + const userId = user.userId; + const tenantId = req['session'].tenantId; + // @ts-ignore + const member = await this.adminPeople.findOne({ + adminId: tenantId, + userId, + }); + if (member == null) { + throw new Error('Your access has been revoked'); } // @ts-ignore - req.user.userId = req.user.accessAccount.userId; + user.userId = tenantId; // @ts-ignore - req.user.accessList = req.user.accessAccount.accessList; + user.accessList = session.tenantPermissions; // @ts-ignore - - req.user.email = req.user.accessAccount.email; } Logger.log(JSON.stringify(req.user), 'JWTAccessAccountMiddleware'); From 92e8319002c15baadf16d6680a215090fae9562c Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 28 Nov 2025 18:16:05 +0530 Subject: [PATCH 20/55] handled switch back to own account scenarion --- src/people/constant/en.ts | 5 +++ src/people/services/people.service.ts | 58 +++++++++++++++++++++------ 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/people/constant/en.ts b/src/people/constant/en.ts index 23f57a5a..d49d9e7c 100644 --- a/src/people/constant/en.ts +++ b/src/people/constant/en.ts @@ -16,3 +16,8 @@ export const TENANT_INVITE_ERRORS = { `No role found for the provided roleId: ${roleId}.`, NO_ROLE_ASSIGNED: 'Please add a role before inviting a member.', }; + +export const TENANT_MESSAGES = { + SWITCH_SUCCESS: 'Switched to tenant account successfully', + SWITCH_BACK_SUCCESS: 'Switched back to main account successfully', +}; diff --git a/src/people/services/people.service.ts b/src/people/services/people.service.ts index 3510b32c..ce37b1e7 100644 --- a/src/people/services/people.service.ts +++ b/src/people/services/people.service.ts @@ -22,7 +22,11 @@ import { ConfigService } from '@nestjs/config'; import { MailNotificationService } from 'src/mail-notification/services/mail-notification.service'; import { JobNames } from 'src/utils/time-constant'; import { redisClient } from 'src/utils/redis.provider'; -import { TENANT_ERRORS, TENANT_INVITE_ERRORS } from '../constant/en'; +import { + TENANT_ERRORS, + TENANT_INVITE_ERRORS, + TENANT_MESSAGES, +} from '../constant/en'; @Injectable() export class PeopleService { @@ -243,7 +247,18 @@ export class PeopleService { tenantDto: TenantLoginDTO, ) { const { adminId } = tenantDto; - if (tenantDto.adminId == sessionDetail?.tenantId) { + // switch back to own account + if (userDetail.userId == adminId) { + if (!sessionDetail?.tenantId) { + throw new BadRequestException([TENANT_ERRORS.ALREADY_IN_TENANT]); + } + return this.updateSession({ + sessionDetail, + message: TENANT_MESSAGES.SWITCH_BACK_SUCCESS, + }); + } + // switching to tenant account + if (adminId == sessionDetail?.tenantId) { throw new BadRequestException([TENANT_ERRORS.ALREADY_IN_TENANT]); } const adminData = await this.userService.findOne({ @@ -264,29 +279,46 @@ export class PeopleService { if (!tenantDetail.accepted) { throw new BadRequestException([TENANT_ERRORS.INVITATION_NOT_ACCEPTED]); } - const permissionsDetail = await this.roleRepository.findOne({ + const roleDetail = await this.roleRepository.findOne({ _id: tenantDetail.roleId, }); - if (!permissionsDetail) { + if (!roleDetail) { throw new BadRequestException([TENANT_ERRORS.ROLE_NOT_FOUND]); } - if ( - !permissionsDetail.permissions || - permissionsDetail.permissions.length <= 0 - ) { + if (!roleDetail.permissions?.length) { throw new BadRequestException([TENANT_ERRORS.NO_PERMISSION]); } - const permissions = permissionsDetail.permissions; - sessionDetail.tenantId = adminId; + + return this.updateSession({ + sessionDetail, + message: TENANT_MESSAGES.SWITCH_SUCCESS, + tenantId: adminId, // tenantId + permissions: roleDetail.permissions, // permissions + }); + } + + private async updateSession({ + sessionDetail, + message, + tenantId = null, + permissions = null, + }: { + sessionDetail: any; + tenantId?: string | null; + permissions?: any[] | null; + message: string; + }) { + sessionDetail.tenantId = tenantId; sessionDetail.tenantUsersPermissions = permissions; sessionDetail.createdAt = new Date().toISOString(); - const ttl = await redisClient.ttl(`session: ${sessionDetail.sessionId}`); + const ttl = await redisClient.ttl(`session:${sessionDetail.sessionId}`); await redisClient.set( - `session: ${sessionDetail.sessionId}`, + `session:${sessionDetail.sessionId}`, JSON.stringify(sessionDetail), 'EX', ttl, ); - return { message: 'Switched to tenant account successfully' }; + + return { message }; } } From 3509b4339cd65c92e8a62f404883be5edbcfdb23 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Sun, 30 Nov 2025 06:18:26 +0530 Subject: [PATCH 21/55] handled permission error --- src/people/services/people.service.ts | 2 +- src/social-login/constants/en.ts | 1 + src/utils/middleware/jwt-accessAccount.middlerwere.ts | 11 +++++++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/people/services/people.service.ts b/src/people/services/people.service.ts index ce37b1e7..eb5661e2 100644 --- a/src/people/services/people.service.ts +++ b/src/people/services/people.service.ts @@ -309,7 +309,7 @@ export class PeopleService { message: string; }) { sessionDetail.tenantId = tenantId; - sessionDetail.tenantUsersPermissions = permissions; + sessionDetail.tenantUserPermissions = permissions; sessionDetail.createdAt = new Date().toISOString(); const ttl = await redisClient.ttl(`session:${sessionDetail.sessionId}`); await redisClient.set( diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts index 0d20e86a..fb1af949 100644 --- a/src/social-login/constants/en.ts +++ b/src/social-login/constants/en.ts @@ -13,6 +13,7 @@ export const AUTH_ERRORS = { 'This token was issued for a different domain than the one making the request.', TOKEN_DOMAIN_MISSING: 'Token does not contain a valid domain.', INVALID_TOKEN: 'Invalid token', + TENANT_PERMISSION_ISSUE: 'Tenant does not have any assigned permissions', }; export const REFRESH_TOKEN_ERROR = { diff --git a/src/utils/middleware/jwt-accessAccount.middlerwere.ts b/src/utils/middleware/jwt-accessAccount.middlerwere.ts index 21eaad85..8ca531d3 100644 --- a/src/utils/middleware/jwt-accessAccount.middlerwere.ts +++ b/src/utils/middleware/jwt-accessAccount.middlerwere.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { + BadRequestException, Injectable, Logger, NestMiddleware, @@ -7,6 +8,7 @@ import { } from '@nestjs/common'; import { NextFunction, Request, Response } from 'express'; import { AdminPeopleRepository } from 'src/people/repository/people.repository'; +import { AUTH_ERRORS } from 'src/social-login/constants/en'; @Injectable() export class JWTAccessAccountMiddleware implements NestMiddleware { constructor(private readonly adminPeople: AdminPeopleRepository) {} @@ -36,9 +38,14 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { // @ts-ignore user.userId = tenantId; + if ( + !session?.tenantUsersPermissions || + session.tenantUsersPermissions.length === 0 + ) { + throw new BadRequestException([AUTH_ERRORS.TENANT_PERMISSION_ISSUE]); + } // @ts-ignore - - user.accessList = session.tenantPermissions; + user.accessList = session?.tenantUsersPermissions; // @ts-ignore } From 0fd9171b2302166aa8eaa45345787ef154ee3612 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Sun, 30 Nov 2025 06:28:43 +0530 Subject: [PATCH 22/55] fixed accessList issue --- src/utils/middleware/jwt-accessAccount.middlerwere.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/middleware/jwt-accessAccount.middlerwere.ts b/src/utils/middleware/jwt-accessAccount.middlerwere.ts index 8ca531d3..1f094950 100644 --- a/src/utils/middleware/jwt-accessAccount.middlerwere.ts +++ b/src/utils/middleware/jwt-accessAccount.middlerwere.ts @@ -39,13 +39,13 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { user.userId = tenantId; if ( - !session?.tenantUsersPermissions || - session.tenantUsersPermissions.length === 0 + !session?.tenantUserPermissions || + session.tenantUserPermissions.length === 0 ) { throw new BadRequestException([AUTH_ERRORS.TENANT_PERMISSION_ISSUE]); } // @ts-ignore - user.accessList = session?.tenantUsersPermissions; + user.accessList = session?.tenantUserPermissions; // @ts-ignore } From e48c475e0a1611b6d80dc6c5a13b0d6677984e89 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 1 Dec 2025 09:54:58 +0530 Subject: [PATCH 23/55] added some validtions and type cehcking --- src/people/constant/en.ts | 24 +++++++++---------- src/people/services/people.service.ts | 4 ++-- src/social-login/constants/en.ts | 1 + .../jwt-accessAccount.middlerwere.ts | 6 +++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/people/constant/en.ts b/src/people/constant/en.ts index d49d9e7c..28b4a3c5 100644 --- a/src/people/constant/en.ts +++ b/src/people/constant/en.ts @@ -1,23 +1,21 @@ export const TENANT_ERRORS = { - ALREADY_IN_TENANT: 'You are already switched to this tenant.', - ADMIN_NOT_FOUND: 'Tenant admin not found.', + ALREADY_IN_TENANT: 'You are already using this tenant.', + ADMIN_NOT_FOUND: 'Tenant administrator could not be found.', NOT_A_MEMBER: (email: string) => `You are not a member of the tenant managed by ${email}.`, INVITATION_NOT_ACCEPTED: - 'Please accept the tenant invitation before switching.', - ROLE_NOT_FOUND: 'The assigned role does not exist.', - NO_PERMISSION: 'The assigned role has no permissions.', + 'You must accept the tenant invitation before switching.', + ROLE_NOT_FOUND: 'The specified role does not exist.', + NO_PERMISSION: 'The assigned role has no available permissions.', }; - export const TENANT_INVITE_ERRORS = { - SELF_INVITATION_NOT_ALLOWED: 'You cannot send an invitation to yourself.', - ALREADY_INVITED: 'This user is already associated with your account.', + SELF_INVITATION_NOT_ALLOWED: 'You cannot invite your own account.', + ALREADY_INVITED: 'This user is already associated with your tenant.', ROLE_NOT_FOUND: (roleId: string) => - `No role found for the provided roleId: ${roleId}.`, - NO_ROLE_ASSIGNED: 'Please add a role before inviting a member.', + `No role exists for the provided role ID: ${roleId}.`, + NO_ROLE_ASSIGNED: 'A role must be assigned before inviting a member.', }; - export const TENANT_MESSAGES = { - SWITCH_SUCCESS: 'Switched to tenant account successfully', - SWITCH_BACK_SUCCESS: 'Switched back to main account successfully', + SWITCH_SUCCESS: 'Switched to the tenant successfully.', + SWITCH_BACK_SUCCESS: 'Returned to the primary account successfully.', }; diff --git a/src/people/services/people.service.ts b/src/people/services/people.service.ts index eb5661e2..cde7c82c 100644 --- a/src/people/services/people.service.ts +++ b/src/people/services/people.service.ts @@ -248,7 +248,7 @@ export class PeopleService { ) { const { adminId } = tenantDto; // switch back to own account - if (userDetail.userId == adminId) { + if (userDetail.userId === adminId) { if (!sessionDetail?.tenantId) { throw new BadRequestException([TENANT_ERRORS.ALREADY_IN_TENANT]); } @@ -258,7 +258,7 @@ export class PeopleService { }); } // switching to tenant account - if (adminId == sessionDetail?.tenantId) { + if (adminId === sessionDetail?.tenantId) { throw new BadRequestException([TENANT_ERRORS.ALREADY_IN_TENANT]); } const adminData = await this.userService.findOne({ diff --git a/src/social-login/constants/en.ts b/src/social-login/constants/en.ts index fb1af949..b0aa0081 100644 --- a/src/social-login/constants/en.ts +++ b/src/social-login/constants/en.ts @@ -14,6 +14,7 @@ export const AUTH_ERRORS = { TOKEN_DOMAIN_MISSING: 'Token does not contain a valid domain.', INVALID_TOKEN: 'Invalid token', TENANT_PERMISSION_ISSUE: 'Tenant does not have any assigned permissions', + ACCESS_REVOKED: 'Your access has been revoked', }; export const REFRESH_TOKEN_ERROR = { diff --git a/src/utils/middleware/jwt-accessAccount.middlerwere.ts b/src/utils/middleware/jwt-accessAccount.middlerwere.ts index 1f094950..17f4f55d 100644 --- a/src/utils/middleware/jwt-accessAccount.middlerwere.ts +++ b/src/utils/middleware/jwt-accessAccount.middlerwere.ts @@ -17,6 +17,9 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { // @ts-ignore const user = req.user; const session = req['session']; + if (!session) { + throw new BadRequestException([AUTH_ERRORS.SESSION_EXPIRED]); + } if (!session.tenantId) { return next(); } @@ -26,14 +29,13 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { // @ts-ignore const userId = user.userId; - const tenantId = req['session'].tenantId; // @ts-ignore const member = await this.adminPeople.findOne({ adminId: tenantId, userId, }); if (member == null) { - throw new Error('Your access has been revoked'); + throw new UnauthorizedException([AUTH_ERRORS.ACCESS_REVOKED]); } // @ts-ignore From 2a9233f13e4474b928cb084349251e57f92125f4 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 1 Dec 2025 12:11:17 +0530 Subject: [PATCH 24/55] deleted onboardog detail once user delete the serviec --- src/app-auth/app-auth.module.ts | 8 ++++++++ src/app-auth/services/app-auth.service.ts | 11 +++++++++++ .../services/customer-onboarding.service.ts | 3 ++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/app-auth/app-auth.module.ts b/src/app-auth/app-auth.module.ts index 4943c504..9f736776 100644 --- a/src/app-auth/app-auth.module.ts +++ b/src/app-auth/app-auth.module.ts @@ -32,6 +32,10 @@ import { } from 'src/people/schema/people.schema'; import { RateLimitMiddleware } from 'src/utils/middleware/rate-limit.middleware'; import { WebpageConfigModule } from 'src/webpage-config/webpage-config.module'; +import { + CustomerOnboarding, + CustomerOnboardingSchema, +} from 'src/customer-onboarding/schemas/customer-onboarding.schema'; @Module({ imports: [ @@ -39,6 +43,9 @@ import { WebpageConfigModule } from 'src/webpage-config/webpage-config.module'; MongooseModule.forFeature([ { name: AdminPeople.name, schema: AdminPeopleSchema }, ]), + MongooseModule.forFeature([ + { name: CustomerOnboarding.name, schema: CustomerOnboardingSchema }, + ]), HidWalletModule, EdvModule, UserModule, @@ -46,6 +53,7 @@ import { WebpageConfigModule } from 'src/webpage-config/webpage-config.module'; CreditModule, forwardRef(() => WebpageConfigModule), ], + providers: [ AppAuthService, AppRepository, diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 6e647a5d..403903f0 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -30,6 +30,9 @@ import { AuthZCreditsRepository } from 'src/credits/repositories/authz.repositor import { EdvClientKeysManager } from 'src/edv/services/edv.singleton'; import { UserRole } from 'src/user/schema/user.schema'; import { WebPageConfigRepository } from 'src/webpage-config/repositories/webpage-config.repository'; +import { InjectModel } from '@nestjs/mongoose'; +import { CustomerOnboarding } from 'src/customer-onboarding/schemas/customer-onboarding.schema'; +import { Model } from 'mongoose'; export enum GRANT_TYPES { access_service_kyc = 'access_service_kyc', @@ -54,6 +57,8 @@ export class AppAuthService { private readonly authzCreditService: AuthzCreditService, private readonly authzCreditRepository: AuthZCreditsRepository, private readonly webpageConfigRepo: WebPageConfigRepository, + @InjectModel(CustomerOnboarding.name) + private readonly onboardModel: Model, ) {} async createAnApp( @@ -611,6 +616,12 @@ export class AppAuthService { } const appDbConnectionSuffix = `service:${appDetail.services[0].dBSuffix}:${appDetail.subdomain}`; await this.appRepository.findAndDeleteServiceDB(appDbConnectionSuffix); + if ( + appDetail?.services?.length > 0 && + appDetail.services[0].id === SERVICE_TYPES.CAVACH_API + ) { + await this.onboardModel.deleteOne({ kycServiceId: appId }); + } this.authzCreditRepository.deleteAuthzDetail({ appId }); appDetail = await this.appRepository.findOneAndDelete({ appId, userId }); return { appId: appDetail.appId }; diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index 83bce3e6..bd4beb22 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -855,7 +855,8 @@ export class CustomerOnboardingService { userId: user.userId, }); if (!userOnboardingDetail) { - throw new BadRequestException([`No onboarding detail found for user with id: ${user.userId}`, + throw new BadRequestException([ + `No onboarding detail found for user with id: ${user.userId}`, ]); } return userOnboardingDetail; From 0ae4807b3bc8b7fbb2f95e5dbebd9bea8c9f36aa Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 1 Dec 2025 17:33:33 +0530 Subject: [PATCH 25/55] updated the endpoint and removed token from db --- src/app-auth/app-auth.module.ts | 2 - src/app-auth/services/app-auth.service.ts | 90 ------------------- .../controller/webpage-config.controller.ts | 2 +- .../schema/webpage-config.schema.ts | 7 +- .../services/webpage-config.service.ts | 68 ++++++++++---- 5 files changed, 53 insertions(+), 116 deletions(-) diff --git a/src/app-auth/app-auth.module.ts b/src/app-auth/app-auth.module.ts index 9f736776..7445e11f 100644 --- a/src/app-auth/app-auth.module.ts +++ b/src/app-auth/app-auth.module.ts @@ -31,7 +31,6 @@ import { AdminPeopleSchema, } from 'src/people/schema/people.schema'; import { RateLimitMiddleware } from 'src/utils/middleware/rate-limit.middleware'; -import { WebpageConfigModule } from 'src/webpage-config/webpage-config.module'; import { CustomerOnboarding, CustomerOnboardingSchema, @@ -51,7 +50,6 @@ import { UserModule, JwtModule.register({}), CreditModule, - forwardRef(() => WebpageConfigModule), ], providers: [ diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 403903f0..98bf3e49 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -56,7 +56,6 @@ export class AppAuthService { private readonly userRepository: UserRepository, private readonly authzCreditService: AuthzCreditService, private readonly authzCreditRepository: AuthZCreditsRepository, - private readonly webpageConfigRepo: WebPageConfigRepository, @InjectModel(CustomerOnboarding.name) private readonly onboardModel: Model, ) {} @@ -539,16 +538,6 @@ export class AppAuthService { { appId, userId }, updataAppDto, ); - // update webpage detail - if ((app.services[0].id = SERVICE_TYPES.CAVACH_API)) { - this.updateWebPageConfigDetail(app, userDetail).catch((err) => { - Logger.error( - `updateWebPageConfigDetail failed for ${appId}: ${err.message}`, - err.stack, - ); - }); - } - return this.getAppResponse(app); } @@ -914,83 +903,4 @@ export class AppAuthService { return this.getAccessToken(grantType, app, 12, accessList); } - - private async updateWebPageConfigDetail(app, userDetail) { - const webpageDetail = await this.webpageConfigRepo.findAWebpageConfig({ - serviceId: app.appId, - }); - if (!webpageDetail) { - Logger.warn(`No webpage config found for serviceId ${app.appId}`); - return; - } - let expiryDate = webpageDetail.expiryDate; - const userAccessList = userDetail.accessList; - Logger.log( - 'Inside updateWebPageConfigDetail(): Method to generate ssi and kyc token', - 'AppAuthService', - ); - const ssiAccessList = (userAccessList || []) - .filter( - (x) => - x.serviceType === SERVICE_TYPES.SSI_API && - !this.checkIfDateExpired(x.expiryDate), - ) - .map((x) => x.access); - - const kycAccessList = (userAccessList || []) - .filter( - (x) => - x.serviceType === SERVICE_TYPES.CAVACH_API && - !this.checkIfDateExpired(x.expiryDate), - ) - .map((x) => x.access); - - if (ssiAccessList.length <= 0 || kycAccessList.length <= 0) { - throw new UnauthorizedException([ - `You are not authorized for both SSI and KYC services.`, - ]); - } - expiryDate = new Date(expiryDate); - - if (isNaN(expiryDate.getTime())) { - throw new BadRequestException(['Invalid custom expiry date format.']); - } - const today = new Date(); - today.setHours(0, 0, 0, 0); - if (expiryDate < today) { - throw new BadRequestException([ - 'Custom expiry date cannot be earlier than today.', - ]); - } - const expiresIn = Math.floor( - (expiryDate.getTime() - Date.now()) / (1000 * 60 * 60), - ); - - const ssiServiceDetail = await this.appRepository.findOne({ - appId: app.dependentServices[0], - }); - if (!ssiServiceDetail) { - throw new BadRequestException([ - `No service found with dependentServiceId: ${app.dependentServices[0]}`, - ]); - } - // Get access tokens - const ssiAccessTokenDetail = await this.getAccessToken( - GRANT_TYPES.access_service_ssi, - ssiServiceDetail, - expiresIn, - ); - const kycAccessTokenDetail = await this.getAccessToken( - GRANT_TYPES.access_service_kyc, - app, - expiresIn, - ); - await this.webpageConfigRepo.findOneAndUpdate( - { _id: webpageDetail['_id'] }, - { - ssiAccessToken: ssiAccessTokenDetail.access_token, - kycAccessToken: kycAccessTokenDetail.access_token, - }, - ); - } } diff --git a/src/webpage-config/controller/webpage-config.controller.ts b/src/webpage-config/controller/webpage-config.controller.ts index 9b63ec68..3eeb1317 100644 --- a/src/webpage-config/controller/webpage-config.controller.ts +++ b/src/webpage-config/controller/webpage-config.controller.ts @@ -39,7 +39,7 @@ export class WebpageConfigController { description: 'Webpage configuration saved successfully', type: CreateWebpageConfigResponseWithDetailDto, }) - @Post(':appId/kyc-webpage-config') + @Post(':appId/verifier') configureWebPageDetail( @Param('appId') serviceId: string, @Body() createWebpageConfigDto: CreateWebpageConfigDto, diff --git a/src/webpage-config/schema/webpage-config.schema.ts b/src/webpage-config/schema/webpage-config.schema.ts index 5f0a58c3..c3834ee3 100644 --- a/src/webpage-config/schema/webpage-config.schema.ts +++ b/src/webpage-config/schema/webpage-config.schema.ts @@ -1,15 +1,14 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { ExpiryType, PageType } from '../dto/create-webpage-config.dto'; +import { Types } from 'mongoose'; export type WebpageConfigDocument = WebPageConfig & Document; @Schema({ timestamps: true }) export class WebPageConfig { + @Prop({ type: Types.ObjectId, default: () => new Types.ObjectId() }) + _id: Types.ObjectId; @Prop({ type: String, required: false, default: '#f8f9fa' }) themeColor: string; - @Prop({ type: String }) - ssiAccessToken: string; - @Prop({ type: String }) - kycAccessToken: string; @Prop({ type: String, enum: ExpiryType, required: true }) expiryType: ExpiryType; @Prop({ type: String, unique: true }) diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index a9c85e40..49860950 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -8,6 +8,7 @@ import { import { CreateWebpageConfigDto, CreateWebpageConfigResponseDto, + ExpiryType, } from '../dto/create-webpage-config.dto'; import { UpdateWebpageConfigDto } from '../dto/update-webpage-config.dto'; import { AppRepository } from 'src/app-auth/repositories/app.repository'; @@ -19,6 +20,7 @@ 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'; @Injectable() export class WebpageConfigService { @@ -66,24 +68,24 @@ export class WebpageConfigService { const { appName, logoUrl, env = 'dev' } = serviceDetail; const tenantUrl: string = serviceDetail['tenantUrl']; - const tokenAndExpiryDetail = await this.generateTokenBasedOnExpiry( - serviceDetail, - userDetail.accessList, + const { expiryDate } = await this.generateExpiryDate( expiryType, customExpiryDate, - serviceDetail.dependentServices[0], ); const veriferAppBaseUrl = this.config.get('KYC_VERIFIER_APP_BASE_URL') || 'https://verifier.hypersign.id'; - const generatedUrl = `${urlSanitizer(veriferAppBaseUrl, true)}${serviceId}`; + const id = new Types.ObjectId(); + const generatedUrl = `${urlSanitizer( + veriferAppBaseUrl, + true, + )}${id.toString()}`; const payload = { + _id: id, serviceId, themeColor, - ssiAccessToken: tokenAndExpiryDetail.ssiAccessToken, - kycAccessToken: tokenAndExpiryDetail.kycAccessToken, expiryType, - expiryDate: tokenAndExpiryDetail.expiryDate, + expiryDate, pageDescription, pageTitle, pageType, @@ -96,10 +98,8 @@ export class WebpageConfigService { payload, ); const webpageConfigObject = webpageConfigData; - const { ssiAccessToken, kycAccessToken, ...responseData } = - webpageConfigObject; return { - ...responseData, + ...webpageConfigObject, serviceName: appName, developmentStage: env, logoUrl, @@ -178,19 +178,13 @@ export class WebpageConfigService { 'KYC service must have a dependent SSI service linked to it.', ]); } - let tokenDetail; const dataToUpdate = { ...updateWebpageConfigDto }; if (updateWebpageConfigDto.expiryType) { - tokenDetail = await this.generateTokenBasedOnExpiry( - serviceDetail, - userDetail.accessList, + const { expiryDate } = await this.generateExpiryDate( updateWebpageConfigDto.expiryType, updateWebpageConfigDto.customExpiryDate, - serviceDetail.dependentServices[0], ); - dataToUpdate['expiryDate'] = tokenDetail.expiryDate; - dataToUpdate['ssiAccessToken'] = tokenDetail.ssiAccessToken; - dataToUpdate['kycAccessToken'] = tokenDetail.kycAccessToken; + dataToUpdate['expiryDate'] = expiryDate; } const webpageConfiguration = await this.webPageConfigRepo.findOneAndUpdate( { _id: id }, @@ -317,4 +311,40 @@ export class WebpageConfigService { expiryDate, }; } + private async generateExpiryDate(expiryType, customExpiryDate) { + let expiresIn: number; + let expiryDate: Date; + if (expiryType === ExpiryType.CUSTOM) { + if (!customExpiryDate) { + throw new BadRequestException([ + 'Custom expiry date is required when expiryType is "custom".', + ]); + } + expiryDate = new Date(customExpiryDate); + + if (isNaN(expiryDate.getTime())) { + throw new BadRequestException(['Invalid custom expiry date format.']); + } + const today = new Date(); + today.setHours(0, 0, 0, 0); + if (expiryDate < today) { + throw new BadRequestException([ + 'Custom expiry date cannot be earlier than today.', + ]); + } + expiresIn = Math.floor( + (expiryDate.getTime() - Date.now()) / (1000 * 60 * 60), + ); + } else { + const monthsMap = { + '1month': 30, + '3months': 90, + '6months': 180, + }; + const days = monthsMap[expiryType] || 30; + expiresIn = days * 24; + expiryDate = new Date(Date.now() + expiresIn * 60 * 60 * 1000); + } + return { expiryDate }; + } } From a3babe182b6e312c4ea16858bc93e4f0838c275f Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 1 Dec 2025 17:35:58 +0530 Subject: [PATCH 26/55] fixed endpoint --- .../controller/webpage-config.controller.ts | 8 ++++---- src/webpage-config/services/webpage-config.service.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/webpage-config/controller/webpage-config.controller.ts b/src/webpage-config/controller/webpage-config.controller.ts index 3eeb1317..c469c461 100644 --- a/src/webpage-config/controller/webpage-config.controller.ts +++ b/src/webpage-config/controller/webpage-config.controller.ts @@ -57,7 +57,7 @@ export class WebpageConfigController { description: 'Webpage configuration list', type: FetchWebpageConfigResponseDto, }) - @Get(':appId/kyc-webpage-config') + @Get(':appId/verifier') async fetchWebPageConfigurationDetail(@Param('appId') appId: string) { Logger.log( 'Inside fetchWebPageConfigurationDetail() to fetch webpageData', @@ -70,7 +70,7 @@ export class WebpageConfigController { description: 'Webpage configuration fetched successfully', type: FetchWebpageConfigResponseDto, }) - @Get(':appId/kyc-webpage-config/:id') + @Get(':appId/verifier/:id') fetchAWebPageConfigurationDetail( @Param('appId') appId: string, @Param('id') id: string, @@ -85,7 +85,7 @@ export class WebpageConfigController { description: 'Webpage configuration updated successfully', type: FetchWebpageConfigResponseDto, }) - @Patch(':appId/kyc-webpage-config/:id') + @Patch(':appId/verifier/:id') updateWebPageConfiguration( @Param('appId') appId: string, @Param('id') id: string, @@ -104,7 +104,7 @@ export class WebpageConfigController { description: 'Webpage configuration deleted successfully', type: FetchWebpageConfigResponseDto, }) - @Delete(':appId/kyc-webpage-config/:id') + @Delete(':appId/verifier/:id') removeWebPageConfiguration( @Param('appId') appId: string, @Param('id') id: string, diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 49860950..fe49ce8c 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -207,7 +207,7 @@ export class WebpageConfigService { async removeWebPageConfiguration(id: string, serviceId: string) { const deletedConfig = await this.webPageConfigRepo.findOneAndDelete({ - _id: id, + _id: new Types.ObjectId(id), serviceId, }); if (!deletedConfig) { From 64181de2c4703a6c926030cb78a362220afa626a Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 2 Dec 2025 11:40:38 +0530 Subject: [PATCH 27/55] added new api to genrate and store token in redis --- src/app-auth/app-auth.module.ts | 2 + src/app-auth/services/app-auth.service.ts | 4 + src/utils/time-constant.ts | 4 + src/utils/utils.ts | 8 +- src/webpage-config/constant/en.ts | 9 + .../controller/webpage-config.controller.ts | 18 +- .../dto/create-webpage-config.dto.ts | 35 ++-- .../services/webpage-config.service.ts | 165 ++++++++---------- 8 files changed, 139 insertions(+), 106 deletions(-) create mode 100644 src/webpage-config/constant/en.ts diff --git a/src/app-auth/app-auth.module.ts b/src/app-auth/app-auth.module.ts index 7445e11f..7c9158f0 100644 --- a/src/app-auth/app-auth.module.ts +++ b/src/app-auth/app-auth.module.ts @@ -35,6 +35,7 @@ import { CustomerOnboarding, CustomerOnboardingSchema, } from 'src/customer-onboarding/schemas/customer-onboarding.schema'; +import { WebpageConfigModule } from 'src/webpage-config/webpage-config.module'; @Module({ imports: [ @@ -50,6 +51,7 @@ import { UserModule, JwtModule.register({}), CreditModule, + forwardRef(() => WebpageConfigModule), ], providers: [ diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 98bf3e49..40bb0962 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -58,6 +58,7 @@ export class AppAuthService { private readonly authzCreditRepository: AuthZCreditsRepository, @InjectModel(CustomerOnboarding.name) private readonly onboardModel: Model, + private readonly webpageConfigRepo: WebPageConfigRepository, ) {} async createAnApp( @@ -609,7 +610,10 @@ export class AppAuthService { appDetail?.services?.length > 0 && appDetail.services[0].id === SERVICE_TYPES.CAVACH_API ) { + // delete onboarding data await this.onboardModel.deleteOne({ kycServiceId: appId }); + // delete webpage config data of that service + await this.webpageConfigRepo.findOneAndDelete({ appId }); } this.authzCreditRepository.deleteAuthzDetail({ appId }); appDetail = await this.appRepository.findOneAndDelete({ appId, userId }); diff --git a/src/utils/time-constant.ts b/src/utils/time-constant.ts index 06caeb21..3f686d30 100644 --- a/src/utils/time-constant.ts +++ b/src/utils/time-constant.ts @@ -19,6 +19,10 @@ export const TOKEN = { name: 'refreshToken', expiry: 7 * TIME.DAY * 1000, }, + VERIFIER_TOKEN: { + name: 'verifierPageToken', + expiry: 30 * TIME.MINUTE * 1000, + }, }; export enum JobNames { diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b1bb13e1..3b8f3103 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -160,10 +160,16 @@ export function getCookieOptions(maxAge?: number, isClear = false) { const isProd = process.env.NODE_ENV || 'production'; return { httpOnly: true, - secure: isProd, + secure: isProd === 'production' ? true : false, sameSite: isProd === 'production' ? 'None' : 'Lax', domain: isProd ? cookieDomain : undefined, path: '/', ...(isClear ? {} : { maxAge }), }; } + +export const REDIS_KEYS = { + SESSION: 'session:', + REFRESH_TOKEN: 'refreshToken:', + VERIFIER_PAGE_TOKEN: 'verifierPageToken:', +}; diff --git a/src/webpage-config/constant/en.ts b/src/webpage-config/constant/en.ts new file mode 100644 index 00000000..fde9430b --- /dev/null +++ b/src/webpage-config/constant/en.ts @@ -0,0 +1,9 @@ +export const WEBPAGE_CONFIG_ERRORS = { + WEBPAGE_CONFIG_NOT_FOUND: 'Webpage configuration not found.', + WEBPAGE_CONFIG_LINKED_APP_NOT_FOUND: + 'Linked app not found for the webpage configuration.', + WEBPAGE_CONFIG_SSI_SERVICE_NOT_FOUND: + 'Linked SSI service not found for the webpage configuration.', + WEBPAGE_CONFIG_SSI_SERVICE_DOES_NOT_EXIST: + 'SSI service does not exist or has been deleted.', +}; diff --git a/src/webpage-config/controller/webpage-config.controller.ts b/src/webpage-config/controller/webpage-config.controller.ts index c469c461..c0c25f5a 100644 --- a/src/webpage-config/controller/webpage-config.controller.ts +++ b/src/webpage-config/controller/webpage-config.controller.ts @@ -15,9 +15,9 @@ import { import { WebpageConfigService } from '../services/webpage-config.service'; import { CreateWebpageConfigDto, - CreateWebpageConfigResponseDto, CreateWebpageConfigResponseWithDetailDto, FetchWebpageConfigResponseDto, + VerifierPageTokenResponse, } from '../dto/create-webpage-config.dto'; import { UpdateWebpageConfigDto } from '../dto/update-webpage-config.dto'; import { @@ -111,4 +111,20 @@ export class WebpageConfigController { ) { return this.webpageConfigService.removeWebPageConfiguration(id, appId); } + @ApiOkResponse({ + description: 'Verifier webpage token generated successfully', + type: VerifierPageTokenResponse, + }) + @Post(':appId/verifier/:id/tokens') + generateWebpageConfigTokens( + @Param('appId') appId: string, + @Param('id') id: string, + @Req() req, + ) { + return this.webpageConfigService.generateWebpageConfigTokens( + id, + appId, + req.user, + ); + } } diff --git a/src/webpage-config/dto/create-webpage-config.dto.ts b/src/webpage-config/dto/create-webpage-config.dto.ts index 2a729493..68d5e063 100644 --- a/src/webpage-config/dto/create-webpage-config.dto.ts +++ b/src/webpage-config/dto/create-webpage-config.dto.ts @@ -147,35 +147,38 @@ export class CreateWebpageConfigResponseWithDetailDto extends CreateWebpageConfi export class FetchWebpageConfigResponseDto extends CreateWebpageConfigResponseWithDetailDto { @ApiProperty({ - name: 'ssiAccessToken', - description: 'ssiToken', - example: 'eyJhbGciOiJIUzI1Ni.......', + name: 'createdAt', + description: 'Document creation date', + example: '2025-08-14T11:48:37.389Z', }) @IsString() @IsNotEmpty() - ssiAccessToken: string; + createdAt: string; @ApiProperty({ - name: 'kycAccessToken', - description: 'kycAccessToken', - example: 'eyJhbGciOiJIUzI1Ni.......', + name: 'updatedAt', + description: 'Document updation date', + example: '2025-08-14T12:48:37.389Z', }) @IsString() @IsNotEmpty() - kycAccessToken: string; + updatedAt: string; +} + +export class VerifierPageTokenResponse { @ApiProperty({ - name: 'createdAt', - description: 'Document creation date', - example: '2025-08-14T11:48:37.389Z', + name: 'ssiAccessToken', + description: 'ssiToken', + example: 'eyJhbGciOiJIUzI1Ni.......', }) @IsString() @IsNotEmpty() - createdAt: string; + ssiAccessToken: string; @ApiProperty({ - name: 'updatedAt', - description: 'DOcument updation date', - example: '2025-08-14T12:48:37.389Z', + name: 'kycAccessToken', + description: 'kycAccessToken', + example: 'eyJhbGciOiJIUzI1Ni.......', }) @IsString() @IsNotEmpty() - updatedAt: string; + kycAccessToken: string; } diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index fe49ce8c..1c7e5630 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -21,6 +21,10 @@ 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 { WEBPAGE_CONFIG_ERRORS } from '../constant/en'; +import { redisClient } from 'src/utils/redis.provider'; +import { TOKEN } from 'src/utils/time-constant'; +import { REDIS_KEYS } from 'src/utils/utils'; @Injectable() export class WebpageConfigService { @@ -218,44 +222,10 @@ export class WebpageConfigService { return deletedConfig; } - private async generateTokenBasedOnExpiry( - serviceDetail, - userAccessList, - expiryType, - customExpiryDate, - ssiServiceId, - ) { - // Get both SSI & KYC access lists - Logger.log( - 'Inside generateTokenBasedOnExpiry(): Method to generate ssi and kyc token', - 'removeWebPageConfiguration', - ); - const ssiAccessList = (userAccessList || []) - .filter( - (x) => - x.serviceType === SERVICE_TYPES.SSI_API && - !this.appAuthService.checkIfDateExpired(x.expiryDate), - ) - .map((x) => x.access); - - const kycAccessList = (userAccessList || []) - .filter( - (x) => - x.serviceType === SERVICE_TYPES.CAVACH_API && - !this.appAuthService.checkIfDateExpired(x.expiryDate), - ) - .map((x) => x.access); - - if (ssiAccessList.length <= 0 || kycAccessList.length <= 0) { - throw new UnauthorizedException([ - `You are not authorized for both SSI and KYC services.`, - ]); - } - - // Calculate expiresIn + private async generateExpiryDate(expiryType, customExpiryDate) { let expiresIn: number; let expiryDate: Date; - if (expiryType === 'custom') { + if (expiryType === ExpiryType.CUSTOM) { if (!customExpiryDate) { throw new BadRequestException([ 'Custom expiry date is required when expiryType is "custom".', @@ -286,65 +256,84 @@ export class WebpageConfigService { expiresIn = days * 24; expiryDate = new Date(Date.now() + expiresIn * 60 * 60 * 1000); } - const ssiServiceDetail = await this.appRepository.findOne({ - appId: ssiServiceId, - }); + return { expiryDate }; + } + public async generateWebpageConfigTokens(id, appId, userDetail) { + 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) { + throw new BadRequestException([ + WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_NOT_FOUND, + ]); + } + if (!kycServiceDetail) { + throw new BadRequestException([ + WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_LINKED_APP_NOT_FOUND, + ]); + } + if ( + !kycServiceDetail.dependentServices || + kycServiceDetail.dependentServices.length === 0 + ) { + throw new BadRequestException([ + WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_SSI_SERVICE_NOT_FOUND, + ]); + } + const ssiServiceId = kycServiceDetail.dependentServices[0]; + const [ssiServiceDetail, ssiAccessList, kycAccessList] = await Promise.all([ + this.appRepository.findOne({ appId: ssiServiceId }), + this.buildAccessList(userDetail.accessList, SERVICE_TYPES.SSI_API), + this.buildAccessList(userDetail.accessList, SERVICE_TYPES.CAVACH_API), + ]); if (!ssiServiceDetail) { throw new BadRequestException([ - `No service found with dependentServiceId: ${ssiServiceId}`, + WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_SSI_SERVICE_DOES_NOT_EXIST, ]); } - // Get access tokens - const ssiAccessTokenDetail = await this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_ssi, - ssiServiceDetail, - expiresIn, - ); - const kycAccessTokenDetail = await this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_kyc, - serviceDetail, - expiresIn, - ); - return { + + // generate access tokens + const [ssiAccessTokenDetail, kycAccessTokenDetail] = await Promise.all([ + this.appAuthService.getAccessToken( + GRANT_TYPES.access_service_ssi, + ssiServiceDetail, + 0.5, + ssiAccessList, + ), + this.appAuthService.getAccessToken( + GRANT_TYPES.access_service_kyc, + kycServiceDetail, + 0.5, + kycAccessList, + ), + ]); + const redisPayload = { ssiAccessToken: ssiAccessTokenDetail.access_token, kycAccessToken: kycAccessTokenDetail.access_token, - expiryDate, + }; + redisClient.set( + redisKey, + JSON.stringify(redisPayload), + 'EX', + TOKEN.VERIFIER_TOKEN.expiry, + ); + return { + ...redisPayload, }; } - private async generateExpiryDate(expiryType, customExpiryDate) { - let expiresIn: number; - let expiryDate: Date; - if (expiryType === ExpiryType.CUSTOM) { - if (!customExpiryDate) { - throw new BadRequestException([ - 'Custom expiry date is required when expiryType is "custom".', - ]); - } - expiryDate = new Date(customExpiryDate); - - if (isNaN(expiryDate.getTime())) { - throw new BadRequestException(['Invalid custom expiry date format.']); - } - const today = new Date(); - today.setHours(0, 0, 0, 0); - if (expiryDate < today) { - throw new BadRequestException([ - 'Custom expiry date cannot be earlier than today.', - ]); - } - expiresIn = Math.floor( - (expiryDate.getTime() - Date.now()) / (1000 * 60 * 60), - ); - } else { - const monthsMap = { - '1month': 30, - '3months': 90, - '6months': 180, - }; - const days = monthsMap[expiryType] || 30; - expiresIn = days * 24; - expiryDate = new Date(Date.now() + expiresIn * 60 * 60 * 1000); - } - return { expiryDate }; + private async buildAccessList(accessList = [], serviceType) { + return (accessList || []) + .filter( + (x) => + x.serviceType === serviceType && + !this.appAuthService.checkIfDateExpired(x.expiryDate), + ) + .map((x) => x.access); } } From 63b75333e07f45afbf00cb19c2404391883aefdd Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 2 Dec 2025 12:02:29 +0530 Subject: [PATCH 28/55] format endpoint --- .../controller/webpage-config.controller.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/webpage-config/controller/webpage-config.controller.ts b/src/webpage-config/controller/webpage-config.controller.ts index c0c25f5a..d5d6c95b 100644 --- a/src/webpage-config/controller/webpage-config.controller.ts +++ b/src/webpage-config/controller/webpage-config.controller.ts @@ -31,7 +31,7 @@ import { AllExceptionsFilter } from 'src/utils/utils'; @ApiTags('Webpage-config') @UseFilters(AllExceptionsFilter) @UsePipes(new ValidationPipe()) -@Controller('api/v1/app') +@Controller('api/v1/app/:appId/verifier') export class WebpageConfigController { constructor(private readonly webpageConfigService: WebpageConfigService) {} @@ -39,7 +39,7 @@ export class WebpageConfigController { description: 'Webpage configuration saved successfully', type: CreateWebpageConfigResponseWithDetailDto, }) - @Post(':appId/verifier') + @Post() configureWebPageDetail( @Param('appId') serviceId: string, @Body() createWebpageConfigDto: CreateWebpageConfigDto, @@ -57,7 +57,7 @@ export class WebpageConfigController { description: 'Webpage configuration list', type: FetchWebpageConfigResponseDto, }) - @Get(':appId/verifier') + @Get() async fetchWebPageConfigurationDetail(@Param('appId') appId: string) { Logger.log( 'Inside fetchWebPageConfigurationDetail() to fetch webpageData', @@ -70,7 +70,7 @@ export class WebpageConfigController { description: 'Webpage configuration fetched successfully', type: FetchWebpageConfigResponseDto, }) - @Get(':appId/verifier/:id') + @Get(':id') fetchAWebPageConfigurationDetail( @Param('appId') appId: string, @Param('id') id: string, @@ -85,7 +85,7 @@ export class WebpageConfigController { description: 'Webpage configuration updated successfully', type: FetchWebpageConfigResponseDto, }) - @Patch(':appId/verifier/:id') + @Patch(':id') updateWebPageConfiguration( @Param('appId') appId: string, @Param('id') id: string, @@ -104,7 +104,7 @@ export class WebpageConfigController { description: 'Webpage configuration deleted successfully', type: FetchWebpageConfigResponseDto, }) - @Delete(':appId/verifier/:id') + @Delete(':id') removeWebPageConfiguration( @Param('appId') appId: string, @Param('id') id: string, @@ -115,7 +115,7 @@ export class WebpageConfigController { description: 'Verifier webpage token generated successfully', type: VerifierPageTokenResponse, }) - @Post(':appId/verifier/:id/tokens') + @Post(':id/tokens') generateWebpageConfigTokens( @Param('appId') appId: string, @Param('id') id: string, From 714036f5ed0f3b81cc36bfd67357eb8dd83d5bdd Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 2 Dec 2025 12:27:06 +0530 Subject: [PATCH 29/55] fixed issue of not ablet to fethc resource --- .../services/customer-onboarding.service.ts | 20 ++++++++----------- .../controller/webpage-config.controller.ts | 4 ---- .../services/webpage-config.service.ts | 6 ++---- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index bd4beb22..ebbef8a4 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -702,18 +702,14 @@ export class CustomerOnboardingService { }); const serviceId = kycService?.appId || customerOnboardingData.kycServiceId; - await this.webPageConfig.storeWebPageConfigDetial( - serviceId, - { - pageTitle: 'KYC Verification', - pageDescription: 'Complete your KYC verification to proceed', - expiryType: ExpiryType.ONE_MONTH, - pageType: PageType.KYC, - contactEmail: customerEmail, - themeColor: 'vibrant', - }, - user, - ); + await this.webPageConfig.storeWebPageConfigDetial(serviceId, { + pageTitle: 'KYC Verification', + pageDescription: 'Complete your KYC verification to proceed', + expiryType: ExpiryType.ONE_MONTH, + pageType: PageType.KYC, + contactEmail: customerEmail, + themeColor: 'vibrant', + }); Logger.debug( 'CONFIGURE_KYC_VERIFIER_PAGE step ends', 'CustomerOnboardingService', diff --git a/src/webpage-config/controller/webpage-config.controller.ts b/src/webpage-config/controller/webpage-config.controller.ts index d5d6c95b..d36cd1c2 100644 --- a/src/webpage-config/controller/webpage-config.controller.ts +++ b/src/webpage-config/controller/webpage-config.controller.ts @@ -43,13 +43,11 @@ export class WebpageConfigController { configureWebPageDetail( @Param('appId') serviceId: string, @Body() createWebpageConfigDto: CreateWebpageConfigDto, - @Req() req, ) { Logger.log('inside configureWebPageDetail(): to configure webpage detail'); return this.webpageConfigService.storeWebPageConfigDetial( serviceId, createWebpageConfigDto, - req.user, ); } @@ -90,12 +88,10 @@ export class WebpageConfigController { @Param('appId') appId: string, @Param('id') id: string, @Body() updateWebpageConfigDto: UpdateWebpageConfigDto, - @Req() req, ) { return this.webpageConfigService.updateWebPageConfiguration( id, updateWebpageConfigDto, - req.user, appId, ); } diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 1c7e5630..e8e026ea 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -37,7 +37,6 @@ export class WebpageConfigService { async storeWebPageConfigDetial( serviceId: string, createWebpageConfigDto: CreateWebpageConfigDto, - userDetail, ): Promise { Logger.log( 'Inside storeWebPageConfigDetial to store webpage configuration', @@ -149,7 +148,7 @@ export class WebpageConfigService { ): Promise { const webpageConfiguration = await this.webPageConfigRepo.findAWebpageConfig({ - _id: id, + _id: new Types.ObjectId(id), serviceId, }); if (!webpageConfiguration || webpageConfiguration == null) { @@ -163,7 +162,6 @@ export class WebpageConfigService { async updateWebPageConfiguration( id: string, updateWebpageConfigDto: UpdateWebpageConfigDto, - userDetail, serviceId, ): Promise { const serviceDetail = await this.appRepository.findOne({ @@ -191,7 +189,7 @@ export class WebpageConfigService { dataToUpdate['expiryDate'] = expiryDate; } const webpageConfiguration = await this.webPageConfigRepo.findOneAndUpdate( - { _id: id }, + { _id: new Types.ObjectId(id) }, dataToUpdate, ); if (!webpageConfiguration || webpageConfiguration == null) { From ec5fb73b4088d4fcb92ac379d17f0d0a8dc79ea8 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 2 Dec 2025 16:38:36 +0530 Subject: [PATCH 30/55] Implemented requester based accessList in token --- src/app-auth/services/app-auth.service.ts | 88 ++++++------------- src/config/access-matrix.ts | 50 +++++++++++ .../services/iServiceList.ts | 13 +++ src/utils/utils.ts | 18 ++++ .../repositories/webpage-config.repository.ts | 1 - .../services/webpage-config.service.ts | 23 ++--- 6 files changed, 114 insertions(+), 79 deletions(-) create mode 100644 src/config/access-matrix.ts diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 40bb0962..e7922fa7 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -33,6 +33,8 @@ import { WebPageConfigRepository } from 'src/webpage-config/repositories/webpage import { InjectModel } from '@nestjs/mongoose'; import { CustomerOnboarding } from 'src/customer-onboarding/schemas/customer-onboarding.schema'; import { Model } from 'mongoose'; +import { getAccessListForModule } from 'src/utils/utils'; +import { TokenModule } from 'src/config/access-matrix'; export enum GRANT_TYPES { access_service_kyc = 'access_service_kyc', @@ -643,7 +645,6 @@ export class AppAuthService { Logger.log('generateAccessToken() method: starts....', 'AppAuthService'); const apikeyIndex = appSecreatKey.split('.')[0]; - const appDetail = await this.appRepository.findOne({ apiKeyPrefix: apikeyIndex, }); @@ -684,17 +685,10 @@ export class AppAuthService { switch (serviceType) { case SERVICE_TYPES.SSI_API: { grant_type = GRANT_TYPES.access_service_ssi; - if (userDetails.accessList && userDetails.accessList.length > 0) { - accessList = userDetails.accessList - .map((x) => { - if (x.serviceType === SERVICE_TYPES.SSI_API) { - if (!this.checkIfDateExpired(x.expiryDate)) { - return x.access; - } - } - }) - .filter((x) => x != undefined); - } + accessList = getAccessListForModule( + TokenModule.VERIFIER, + SERVICE_TYPES.SSI_API, + ); break; } case SERVICE_TYPES.CAVACH_API: { @@ -708,32 +702,19 @@ export class AppAuthService { ]); } grant_type = grantType || GRANT_TYPES.access_service_kyc; - if (userDetails.accessList && userDetails.accessList.length > 0) { - accessList = userDetails.accessList - .map((x) => { - if (x.serviceType === SERVICE_TYPES.CAVACH_API) { - if (!this.checkIfDateExpired(x.expiryDate)) { - return x.access; - } - } - }) - .filter((x) => x != undefined); - } + accessList = getAccessListForModule( + TokenModule.VERIFIER, + SERVICE_TYPES.CAVACH_API, + ); + 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); - } + accessList = getAccessListForModule( + TokenModule.VERIFIER, + SERVICE_TYPES.QUEST, + ); break; } default: { @@ -847,15 +828,10 @@ export class AppAuthService { 'Invalid grant type for this service ' + appId, ]); } - accessList = userDetails.accessList - .map((x) => { - if (x.serviceType === SERVICE_TYPES.SSI_API) { - if (!this.checkIfDateExpired(x.expiryDate)) { - return x.access; - } - } - }) - .filter((x) => x != undefined); + accessList = getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.SSI_API, + ); break; } case SERVICE_TYPES.CAVACH_API: { @@ -867,15 +843,10 @@ export class AppAuthService { 'Invalid grant type for this service ' + appId, ]); } - accessList = userDetails.accessList - .map((x) => { - if (x.serviceType === SERVICE_TYPES.CAVACH_API) { - if (!this.checkIfDateExpired(x.expiryDate)) { - return x.access; - } - } - }) - .filter((x) => x != undefined); + accessList = getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.CAVACH_API, + ); break; } case SERVICE_TYPES.QUEST: { @@ -884,15 +855,10 @@ export class AppAuthService { '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); + accessList = getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.QUEST, + ); break; } default: { diff --git a/src/config/access-matrix.ts b/src/config/access-matrix.ts new file mode 100644 index 00000000..f7f7e409 --- /dev/null +++ b/src/config/access-matrix.ts @@ -0,0 +1,50 @@ +import { SERVICES } from '../supported-service/services/iServiceList'; +export enum TokenModule { + DASHBOARD = 'DASHBOARD', + VERIFIER = 'VERIFIER', + APP_AUTH = 'APP_AUTH', +} +export const KYC_ACCESS_MATRIX = { + [TokenModule.DASHBOARD]: [ + SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_CREDIT, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_CREDIT, + SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_WEBHOOK_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_WEBHOOK_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.UPDATE_WEBHOOK_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.DELETE_WEBHOOK_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_USAGE, + SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_WIDGET_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_WIDGET_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.UPDATE_WIDGET_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_VERIFIED_USER, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_ANALYTICS, + ], + [TokenModule.VERIFIER]: [ + 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, + SERVICES.CAVACH_API.ACCESS_TYPES.CHECK_LIVE_STATUS, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_WIDGET_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_USER_CONSENT, + ], + [TokenModule.APP_AUTH]: [ + SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_USER_CONSENT, + SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_PASSIVE_LIVELINESS, + SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_DOC_OCR, + SERVICES.CAVACH_API.ACCESS_TYPES.CHECK_LIVE_STATUS, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_WIDGET_CONFIG, + SERVICES.CAVACH_API.ACCESS_TYPES.READ_USER_CONSENT, + ], +}; +export const SSI_ACCESS_MATRIX = { + // will modify its access later. Assigning ALL for the time being + [TokenModule.DASHBOARD]: [SERVICES.SSI_API.ACCESS_TYPES.ALL], + [TokenModule.VERIFIER]: [SERVICES.SSI_API.ACCESS_TYPES.ALL], + [TokenModule.APP_AUTH]: [SERVICES.SSI_API.ACCESS_TYPES.ALL], +}; +export const QUEST_ACCESS_MATRIX = { + [TokenModule.DASHBOARD]: [], + [TokenModule.VERIFIER]: [], + [TokenModule.APP_AUTH]: [SERVICES.QUEST.ACCESS_TYPES.VERIFY_USER], +}; diff --git a/src/supported-service/services/iServiceList.ts b/src/supported-service/services/iServiceList.ts index e2d3bb31..a8fd3abc 100644 --- a/src/supported-service/services/iServiceList.ts +++ b/src/supported-service/services/iServiceList.ts @@ -63,6 +63,9 @@ export namespace SERVICES { 'RESOLVE_CREDENTIAL_STATUS' = 'RESOLVE_CREDENTIAL_STATUS', 'RESOLVE_SCHEMA' = 'RESOLVE_SCHEMA', 'REGISTER_SCHEMA' = 'REGISTER_SCHEMA', + 'READ_USAGE' = 'READ_USAGE', + 'WRITE_CREDIT' = 'WRITE_CREDIT', + 'READ_CREDIT' = 'READ_CREDIT', } } @@ -79,6 +82,16 @@ export namespace SERVICES { READ_WIDGET_CONFIG = 'READ_WIDGET_CONFIG', WRITE_WIDGET_CONFIG = 'WRITE_WIDGET_CONFIG', UPDATE_WIDGET_CONFIG = 'UPDATE_WIDGET_CONFIG', + WRITE_WEBHOOK_CONFIG = 'WRITE_WEBHOOK_CONFIG', + READ_WEBHOOK_CONFIG = 'READ_WEBHOOK_CONFIG', + UPDATE_WEBHOOK_CONFIG = 'UPDATE_WEBHOOK_CONFIG', + DELETE_WEBHOOK_CONFIG = 'DELETE_WEBHOOK_CONFIG', + READ_VERIFIED_USER = 'READ_VERIFIED_USER', + READ_ANALYTICS = 'READ_ANALYTICS', + READ_USAGE = 'READ_USAGE', + WRITE_CREDIT = 'WRITE_CREDIT', + READ_CREDIT = 'READ_CREDIT', + CHECK_LIVE_STATUS = 'CHECK_LIVE_STATUS', } } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 3b8f3103..fccfe00c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -18,6 +18,11 @@ import { SERVICE_TYPES, SERVICES, } from 'src/supported-service/services/iServiceList'; +import { + KYC_ACCESS_MATRIX, + QUEST_ACCESS_MATRIX, + SSI_ACCESS_MATRIX, +} from 'src/config/access-matrix'; export const existDir = (dirPath) => { if (!dirPath) throw new Error('Directory path undefined'); @@ -173,3 +178,16 @@ export const REDIS_KEYS = { REFRESH_TOKEN: 'refreshToken:', VERIFIER_PAGE_TOKEN: 'verifierPageToken:', }; +export function getAccessListForModule( + module: 'DASHBOARD' | 'VERIFIER' | 'APP_AUTH', + serviceType: SERVICE_TYPES, +) { + switch (serviceType) { + case SERVICE_TYPES.CAVACH_API: + return KYC_ACCESS_MATRIX[module] || []; + case SERVICE_TYPES.SSI_API: + return SSI_ACCESS_MATRIX[module] || []; + case SERVICE_TYPES.QUEST: + return QUEST_ACCESS_MATRIX[module] || []; + } +} diff --git a/src/webpage-config/repositories/webpage-config.repository.ts b/src/webpage-config/repositories/webpage-config.repository.ts index 6cb17fe0..f72ddbf1 100644 --- a/src/webpage-config/repositories/webpage-config.repository.ts +++ b/src/webpage-config/repositories/webpage-config.repository.ts @@ -64,7 +64,6 @@ export class WebPageConfigRepository { 'findOneAndDelete() method: starts, delete app data to db', 'WebPageConfigRepository', ); - return this.webPageConfigModel.findOneAndDelete(appFilterQuery); } } diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index e8e026ea..0acfd3fe 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -24,7 +24,7 @@ import { 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 { REDIS_KEYS } from 'src/utils/utils'; +import { getAccessListForModule, REDIS_KEYS } from 'src/utils/utils'; @Injectable() export class WebpageConfigService { @@ -285,11 +285,9 @@ export class WebpageConfigService { ]); } const ssiServiceId = kycServiceDetail.dependentServices[0]; - const [ssiServiceDetail, ssiAccessList, kycAccessList] = await Promise.all([ - this.appRepository.findOne({ appId: ssiServiceId }), - this.buildAccessList(userDetail.accessList, SERVICE_TYPES.SSI_API), - this.buildAccessList(userDetail.accessList, SERVICE_TYPES.CAVACH_API), - ]); + const ssiServiceDetail = await this.appRepository.findOne({ + appId: ssiServiceId, + }); if (!ssiServiceDetail) { throw new BadRequestException([ WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_SSI_SERVICE_DOES_NOT_EXIST, @@ -302,13 +300,13 @@ export class WebpageConfigService { GRANT_TYPES.access_service_ssi, ssiServiceDetail, 0.5, - ssiAccessList, + getAccessListForModule('VERIFIER', SERVICE_TYPES.SSI_API), ), this.appAuthService.getAccessToken( GRANT_TYPES.access_service_kyc, kycServiceDetail, 0.5, - kycAccessList, + getAccessListForModule('VERIFIER', SERVICE_TYPES.CAVACH_API), ), ]); const redisPayload = { @@ -325,13 +323,4 @@ export class WebpageConfigService { ...redisPayload, }; } - private async buildAccessList(accessList = [], serviceType) { - return (accessList || []) - .filter( - (x) => - x.serviceType === serviceType && - !this.appAuthService.checkIfDateExpired(x.expiryDate), - ) - .map((x) => x.access); - } } From bb8c516c1217ebaa39bb564f67059a6695dc4b18 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 3 Dec 2025 12:59:36 +0530 Subject: [PATCH 31/55] changed the api path by readig appId from query --- .../jwt-accessAccount.middlerwere.ts | 8 ++-- .../controller/webpage-config.controller.ts | 29 ++++++------- .../services/webpage-config.service.ts | 4 +- src/webpage-config/webpage-config.module.ts | 42 +++++++++++++------ 4 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/utils/middleware/jwt-accessAccount.middlerwere.ts b/src/utils/middleware/jwt-accessAccount.middlerwere.ts index 17f4f55d..5962ec44 100644 --- a/src/utils/middleware/jwt-accessAccount.middlerwere.ts +++ b/src/utils/middleware/jwt-accessAccount.middlerwere.ts @@ -18,7 +18,7 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { const user = req.user; const session = req['session']; if (!session) { - throw new BadRequestException([AUTH_ERRORS.SESSION_EXPIRED]); + throw new Error(AUTH_ERRORS.SESSION_EXPIRED); } if (!session.tenantId) { return next(); @@ -35,7 +35,7 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { userId, }); if (member == null) { - throw new UnauthorizedException([AUTH_ERRORS.ACCESS_REVOKED]); + throw new Error(AUTH_ERRORS.ACCESS_REVOKED); } // @ts-ignore @@ -44,7 +44,7 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { !session?.tenantUserPermissions || session.tenantUserPermissions.length === 0 ) { - throw new BadRequestException([AUTH_ERRORS.TENANT_PERMISSION_ISSUE]); + throw new Error(AUTH_ERRORS.TENANT_PERMISSION_ISSUE); } // @ts-ignore user.accessList = session?.tenantUserPermissions; @@ -57,7 +57,7 @@ export class JWTAccessAccountMiddleware implements NestMiddleware { `JWTAccessAccountMiddleware: Error ${e}`, 'JWTAccessAccountMiddleware', ); - throw new UnauthorizedException([e]); + throw new UnauthorizedException([e.message]); } next(); } diff --git a/src/webpage-config/controller/webpage-config.controller.ts b/src/webpage-config/controller/webpage-config.controller.ts index d36cd1c2..7fd2b0c4 100644 --- a/src/webpage-config/controller/webpage-config.controller.ts +++ b/src/webpage-config/controller/webpage-config.controller.ts @@ -11,6 +11,7 @@ import { Req, UsePipes, ValidationPipe, + Query, } from '@nestjs/common'; import { WebpageConfigService } from '../services/webpage-config.service'; import { @@ -24,6 +25,7 @@ import { ApiBearerAuth, ApiCreatedResponse, ApiOkResponse, + ApiQuery, ApiTags, } from '@nestjs/swagger'; import { AllExceptionsFilter } from 'src/utils/utils'; @@ -31,7 +33,7 @@ import { AllExceptionsFilter } from 'src/utils/utils'; @ApiTags('Webpage-config') @UseFilters(AllExceptionsFilter) @UsePipes(new ValidationPipe()) -@Controller('api/v1/app/:appId/verifier') +@Controller('api/v1/app/verifier') export class WebpageConfigController { constructor(private readonly webpageConfigService: WebpageConfigService) {} @@ -40,8 +42,9 @@ export class WebpageConfigController { type: CreateWebpageConfigResponseWithDetailDto, }) @Post() + @ApiQuery({ name: 'appId', required: true, type: String }) configureWebPageDetail( - @Param('appId') serviceId: string, + @Query('appId') serviceId: string, @Body() createWebpageConfigDto: CreateWebpageConfigDto, ) { Logger.log('inside configureWebPageDetail(): to configure webpage detail'); @@ -56,7 +59,8 @@ export class WebpageConfigController { type: FetchWebpageConfigResponseDto, }) @Get() - async fetchWebPageConfigurationDetail(@Param('appId') appId: string) { + @ApiQuery({ name: 'appId', required: true, type: String }) + async fetchWebPageConfigurationDetail(@Query('appId') appId: string) { Logger.log( 'Inside fetchWebPageConfigurationDetail() to fetch webpageData', 'WebpageConfigController', @@ -69,14 +73,8 @@ export class WebpageConfigController { type: FetchWebpageConfigResponseDto, }) @Get(':id') - fetchAWebPageConfigurationDetail( - @Param('appId') appId: string, - @Param('id') id: string, - ) { - return this.webpageConfigService.fetchAWebPageConfigurationDetail( - id, - appId, - ); + fetchAWebPageConfigurationDetail(@Param('id') id: string) { + return this.webpageConfigService.fetchAWebPageConfigurationDetail(id); } @ApiOkResponse({ @@ -84,8 +82,9 @@ export class WebpageConfigController { type: FetchWebpageConfigResponseDto, }) @Patch(':id') + @ApiQuery({ name: 'appId', required: true, type: String }) updateWebPageConfiguration( - @Param('appId') appId: string, + @Query('appId') appId: string, @Param('id') id: string, @Body() updateWebpageConfigDto: UpdateWebpageConfigDto, ) { @@ -101,8 +100,9 @@ export class WebpageConfigController { type: FetchWebpageConfigResponseDto, }) @Delete(':id') + @ApiQuery({ name: 'appId', required: true, type: String }) removeWebPageConfiguration( - @Param('appId') appId: string, + @Query('appId') appId: string, @Param('id') id: string, ) { return this.webpageConfigService.removeWebPageConfiguration(id, appId); @@ -112,8 +112,9 @@ export class WebpageConfigController { type: VerifierPageTokenResponse, }) @Post(':id/tokens') + @ApiQuery({ name: 'appId', required: true, type: String }) generateWebpageConfigTokens( - @Param('appId') appId: string, + @Query('appId') appId: string, @Param('id') id: string, @Req() req, ) { diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 0acfd3fe..3db9c64d 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -144,16 +144,14 @@ export class WebpageConfigService { async fetchAWebPageConfigurationDetail( id: string, - serviceId: string, ): Promise { const webpageConfiguration = await this.webPageConfigRepo.findAWebpageConfig({ _id: new Types.ObjectId(id), - serviceId, }); if (!webpageConfiguration || webpageConfiguration == null) { throw new NotFoundException([ - `No webpage configuration found for serviceId: ${serviceId} and docId: ${id}`, + `No webpage configuration found for id: ${id}`, ]); } return webpageConfiguration; diff --git a/src/webpage-config/webpage-config.module.ts b/src/webpage-config/webpage-config.module.ts index 3bdc540b..a630a11e 100644 --- a/src/webpage-config/webpage-config.module.ts +++ b/src/webpage-config/webpage-config.module.ts @@ -51,24 +51,42 @@ export class WebpageConfigModule implements NestModule { consumer .apply(JWTAuthorizeMiddleware) - .exclude({ - path: 'api/v1/app/:appId/kyc-webpage-config', - method: RequestMethod.GET, - }) + .exclude( + { + path: 'api/v1/app/verifier/:id', + method: RequestMethod.GET, + }, + { + path: 'api/v1/app/verifier/:id/tokens', + method: RequestMethod.POST, + }, + ) .forRoutes(WebpageConfigController); consumer .apply(JWTAccessAccountMiddleware) - .exclude({ - path: 'api/v1/app/:appId/kyc-webpage-config', - method: RequestMethod.GET, - }) + .exclude( + { + path: 'api/v1/app/verifier/:id', + method: RequestMethod.GET, + }, + { + path: 'api/v1/app/verifier/:id/tokens', + method: RequestMethod.POST, + }, + ) .forRoutes(WebpageConfigController); consumer .apply(RateLimitMiddleware) - .exclude({ - path: 'api/v1/app/:appId/kyc-webpage-config', - method: RequestMethod.GET, - }) + .exclude( + { + path: 'api/v1/app/verifier/:id', + method: RequestMethod.GET, + }, + { + path: 'api/v1/app/verifier/:id/tokens', + method: RequestMethod.POST, + }, + ) .forRoutes(WebpageConfigController); } } From 0a26ab8d6314360c44898eb38649cf702e9a9a76 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 3 Dec 2025 13:38:36 +0530 Subject: [PATCH 32/55] fixed redis expiry issue --- src/utils/time-constant.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/time-constant.ts b/src/utils/time-constant.ts index 3f686d30..16283f6d 100644 --- a/src/utils/time-constant.ts +++ b/src/utils/time-constant.ts @@ -21,7 +21,7 @@ export const TOKEN = { }, VERIFIER_TOKEN: { name: 'verifierPageToken', - expiry: 30 * TIME.MINUTE * 1000, + expiry: 30 * TIME.MINUTE, }, }; From 2b34cfa51601f451d47d89b16a47ca09be5d00fd Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 3 Dec 2025 14:56:38 +0530 Subject: [PATCH 33/55] used enum --- src/app-auth/services/app-auth.service.ts | 6 +++--- src/config/access-matrix.ts | 1 + src/supported-service/services/iServiceList.ts | 1 + src/webpage-config/services/webpage-config.service.ts | 5 +++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index e7922fa7..2cb9476a 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -686,7 +686,7 @@ export class AppAuthService { 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 +703,7 @@ export class AppAuthService { } grant_type = grantType || GRANT_TYPES.access_service_kyc; accessList = getAccessListForModule( - TokenModule.VERIFIER, + TokenModule.APP_AUTH, SERVICE_TYPES.CAVACH_API, ); @@ -712,7 +712,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; diff --git a/src/config/access-matrix.ts b/src/config/access-matrix.ts index f7f7e409..c4bda2fc 100644 --- a/src/config/access-matrix.ts +++ b/src/config/access-matrix.ts @@ -27,6 +27,7 @@ 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_USER_CONSENT, diff --git a/src/supported-service/services/iServiceList.ts b/src/supported-service/services/iServiceList.ts index a8fd3abc..ecaf5d57 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', } } diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 0acfd3fe..4734090c 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -25,6 +25,7 @@ import { WEBPAGE_CONFIG_ERRORS } from '../constant/en'; import { redisClient } from 'src/utils/redis.provider'; import { TOKEN } from 'src/utils/time-constant'; import { getAccessListForModule, REDIS_KEYS } from 'src/utils/utils'; +import { TokenModule } from 'src/config/access-matrix'; @Injectable() export class WebpageConfigService { @@ -300,13 +301,13 @@ export class WebpageConfigService { GRANT_TYPES.access_service_ssi, ssiServiceDetail, 0.5, - getAccessListForModule('VERIFIER', SERVICE_TYPES.SSI_API), + getAccessListForModule(TokenModule.VERIFIER, SERVICE_TYPES.SSI_API), ), this.appAuthService.getAccessToken( GRANT_TYPES.access_service_kyc, kycServiceDetail, 0.5, - getAccessListForModule('VERIFIER', SERVICE_TYPES.CAVACH_API), + getAccessListForModule(TokenModule.VERIFIER, SERVICE_TYPES.CAVACH_API), ), ]); const redisPayload = { From b714c7016e07e0ca01d6c8160b0137d97c224d57 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Thu, 4 Dec 2025 17:27:21 +0530 Subject: [PATCH 34/55] removed * from whitelisted cors and provided required url --- .deploy/deployment.yaml | 4 ++++ .github/workflows/pipeline.yaml | 4 ++++ dev.env.sample | 2 ++ .../services/customer-onboarding.service.ts | 6 +++++- .../controller/webpage-config.controller.ts | 17 ++++++++--------- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/.deploy/deployment.yaml b/.deploy/deployment.yaml index e85535cc..714f9491 100644 --- a/.deploy/deployment.yaml +++ b/.deploy/deployment.yaml @@ -111,6 +111,10 @@ spec: value: '__MAX_RETRY_ATTEMPT__' - name: MFA_REDIRECT_URL value: __MFA_REDIRECT_URL__ + - name: CLIENT_APP_URL + value: __CLIENT_APP_URL__ + - name: KYC_WIDGET_URL + value: __KYC_WIDGET_URL__ - name: MAX_MFA_RETRY_ATTEMPT value: '__MAX_MFA_RETRY_ATTEMPT__' diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 8f972f67..e8263b31 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -151,5 +151,9 @@ jobs: run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__MAX_MFA_RETRY_ATTEMPT__#${{ secrets.MAX_MFA_RETRY_ATTEMPT }}#" {} \; - name: "Replace secrets" run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__MFA_REDIRECT_URL__#${{ secrets.MFA_REDIRECT_URL }}#" {} \; + - name: "Replace secrets" + run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__CLIENT_APP_URL__#${{ secrets.CLIENT_APP_URL }}#" {} \; + - name: "Replace secrets" + run: find .deploy/deployment.yaml -type f -exec sed -i -e "s#__KYC_WIDGET_URL__#${{ secrets.KYC_WIDGET_URL }}#" {} \; - name: "Deploy to GKE" run: kubectl apply -f .deploy/deployment.yaml diff --git a/dev.env.sample b/dev.env.sample index 84bb8d80..2a6d2e7a 100644 --- a/dev.env.sample +++ b/dev.env.sample @@ -25,6 +25,8 @@ MAX_RETRY_ATTEMPT=3 NODE_ENV=development MFA_REDIRECT_URL='http://localhost:9001/#/studio/mfa' MAX_MFA_RETRY_ATTEMPT=3 +CLIENT_APP_URL=https://entity.dashboard.hypersign.id +KYC_WIDGET_URL=https://verify.hypersign.id diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index ebbef8a4..f2e78db0 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -541,7 +541,11 @@ export class CustomerOnboardingService { appName: `${companyName}`, domain: domain, serviceIds: [SERVICE_TYPES.CAVACH_API], - whitelistedCors: ['*'], + whitelistedCors: [ + this.config.get('KYC_WIDGET_URL'), + this.config.get('KYC_VERIFIER_APP_BASE_URL'), + this.config.get('CLIENT_APP_URL'), + ], env: APP_ENVIRONMENT.dev, hasDomainVerified: false, dependentServices: [ diff --git a/src/webpage-config/controller/webpage-config.controller.ts b/src/webpage-config/controller/webpage-config.controller.ts index 7fd2b0c4..bb8eb479 100644 --- a/src/webpage-config/controller/webpage-config.controller.ts +++ b/src/webpage-config/controller/webpage-config.controller.ts @@ -33,7 +33,7 @@ import { AllExceptionsFilter } from 'src/utils/utils'; @ApiTags('Webpage-config') @UseFilters(AllExceptionsFilter) @UsePipes(new ValidationPipe()) -@Controller('api/v1/app/verifier') +@Controller('api/v1/app/') export class WebpageConfigController { constructor(private readonly webpageConfigService: WebpageConfigService) {} @@ -41,7 +41,7 @@ export class WebpageConfigController { description: 'Webpage configuration saved successfully', type: CreateWebpageConfigResponseWithDetailDto, }) - @Post() + @Post('verifier') @ApiQuery({ name: 'appId', required: true, type: String }) configureWebPageDetail( @Query('appId') serviceId: string, @@ -58,9 +58,8 @@ export class WebpageConfigController { description: 'Webpage configuration list', type: FetchWebpageConfigResponseDto, }) - @Get() - @ApiQuery({ name: 'appId', required: true, type: String }) - async fetchWebPageConfigurationDetail(@Query('appId') appId: string) { + @Get(':appId/verifier') + async fetchWebPageConfigurationDetail(@Param('appId') appId: string) { Logger.log( 'Inside fetchWebPageConfigurationDetail() to fetch webpageData', 'WebpageConfigController', @@ -72,7 +71,7 @@ export class WebpageConfigController { description: 'Webpage configuration fetched successfully', type: FetchWebpageConfigResponseDto, }) - @Get(':id') + @Get('verifier/:id') fetchAWebPageConfigurationDetail(@Param('id') id: string) { return this.webpageConfigService.fetchAWebPageConfigurationDetail(id); } @@ -81,7 +80,7 @@ export class WebpageConfigController { description: 'Webpage configuration updated successfully', type: FetchWebpageConfigResponseDto, }) - @Patch(':id') + @Patch('verifier/:id') @ApiQuery({ name: 'appId', required: true, type: String }) updateWebPageConfiguration( @Query('appId') appId: string, @@ -99,7 +98,7 @@ export class WebpageConfigController { description: 'Webpage configuration deleted successfully', type: FetchWebpageConfigResponseDto, }) - @Delete(':id') + @Delete('verifier/:id') @ApiQuery({ name: 'appId', required: true, type: String }) removeWebPageConfiguration( @Query('appId') appId: string, @@ -111,7 +110,7 @@ export class WebpageConfigController { description: 'Verifier webpage token generated successfully', type: VerifierPageTokenResponse, }) - @Post(':id/tokens') + @Post('verifier/:id/tokens') @ApiQuery({ name: 'appId', required: true, type: String }) generateWebpageConfigTokens( @Query('appId') appId: string, From d306c6d4d872dc9dbffbfb7e7c4ccd8d1c1776f4 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 5 Dec 2025 10:24:53 +0530 Subject: [PATCH 35/55] added audiance --- src/app-auth/services/app-auth.service.ts | 16 ++++++++++++---- .../services/webpage-config.service.ts | 2 ++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index e7922fa7..311bcb35 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -682,6 +682,7 @@ export class AppAuthService { const serviceType = appDetail.services[0]?.id; // TODO: remove this later let grant_type = ''; let accessList = []; + let audiance; switch (serviceType) { case SERVICE_TYPES.SSI_API: { grant_type = GRANT_TYPES.access_service_ssi; @@ -689,6 +690,7 @@ export class AppAuthService { TokenModule.VERIFIER, SERVICE_TYPES.SSI_API, ); + audiance = this.config.get('SSI_API_DOMAIN') break; } case SERVICE_TYPES.CAVACH_API: { @@ -706,7 +708,7 @@ export class AppAuthService { TokenModule.VERIFIER, SERVICE_TYPES.CAVACH_API, ); - + audiance = this.config.get('CAVACH_API_DOMAIN') break; } case SERVICE_TYPES.QUEST: { @@ -728,7 +730,7 @@ export class AppAuthService { ]); } - return this.getAccessToken(grant_type, appDetail, expiresin, accessList); + return this.getAccessToken(grant_type, appDetail, expiresin, accessList, audiance); } public async getAccessToken( @@ -736,6 +738,7 @@ export class AppAuthService { appDetail, expiresin = 4, accessList = [], + aud? ) { const payload = { appId: appDetail.appId, @@ -749,7 +752,9 @@ export class AppAuthService { env: appDetail.env ? appDetail.env : APP_ENVIRONMENT.dev, appName: appDetail.appName, }; - + if (aud) { + payload['aud'] = aud + } if (appDetail.issuerDid) { payload['issuerDid'] = appDetail.issuerDid; } @@ -821,6 +826,7 @@ export class AppAuthService { const serviceType = app.services[0]?.id; // TODO: remove this later let accessList = []; + let audiance; switch (serviceType) { case SERVICE_TYPES.SSI_API: { if (grantType != 'access_service_ssi') { @@ -832,6 +838,7 @@ export class AppAuthService { TokenModule.DASHBOARD, SERVICE_TYPES.SSI_API, ); + audiance = this.config.get('SSI_API_DOMAIN') break; } case SERVICE_TYPES.CAVACH_API: { @@ -847,6 +854,7 @@ export class AppAuthService { TokenModule.DASHBOARD, SERVICE_TYPES.CAVACH_API, ); + audiance = this.config.get('CAVACH_API_DOMAIN') break; } case SERVICE_TYPES.QUEST: { @@ -871,6 +879,6 @@ export class AppAuthService { ]); } - return this.getAccessToken(grantType, app, 12, accessList); + return this.getAccessToken(grantType, app, 12, accessList, audiance); } } diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 3db9c64d..7049af13 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -299,12 +299,14 @@ export class WebpageConfigService { ssiServiceDetail, 0.5, getAccessListForModule('VERIFIER', SERVICE_TYPES.SSI_API), + this.config.get('SSI_API_DOMAIN') ), this.appAuthService.getAccessToken( GRANT_TYPES.access_service_kyc, kycServiceDetail, 0.5, getAccessListForModule('VERIFIER', SERVICE_TYPES.CAVACH_API), + this.config.get('CAVACH_API_DOMAIN') ), ]); const redisPayload = { From f5e862a2d0c4bbd0538914edfb9854f5ca80cca2 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 5 Dec 2025 10:30:00 +0530 Subject: [PATCH 36/55] code prettified --- src/app-auth/services/app-auth.service.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 311bcb35..8b27349e 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -690,7 +690,7 @@ export class AppAuthService { TokenModule.VERIFIER, SERVICE_TYPES.SSI_API, ); - audiance = this.config.get('SSI_API_DOMAIN') + audiance = this.config.get('SSI_API_DOMAIN'); break; } case SERVICE_TYPES.CAVACH_API: { @@ -708,7 +708,7 @@ export class AppAuthService { TokenModule.VERIFIER, SERVICE_TYPES.CAVACH_API, ); - audiance = this.config.get('CAVACH_API_DOMAIN') + audiance = this.config.get('CAVACH_API_DOMAIN'); break; } case SERVICE_TYPES.QUEST: { @@ -730,7 +730,13 @@ export class AppAuthService { ]); } - return this.getAccessToken(grant_type, appDetail, expiresin, accessList, audiance); + return this.getAccessToken( + grant_type, + appDetail, + expiresin, + accessList, + audiance, + ); } public async getAccessToken( @@ -738,7 +744,7 @@ export class AppAuthService { appDetail, expiresin = 4, accessList = [], - aud? + aud?, ) { const payload = { appId: appDetail.appId, @@ -753,7 +759,7 @@ export class AppAuthService { appName: appDetail.appName, }; if (aud) { - payload['aud'] = aud + payload['aud'] = aud; } if (appDetail.issuerDid) { payload['issuerDid'] = appDetail.issuerDid; @@ -838,7 +844,7 @@ export class AppAuthService { TokenModule.DASHBOARD, SERVICE_TYPES.SSI_API, ); - audiance = this.config.get('SSI_API_DOMAIN') + audiance = this.config.get('SSI_API_DOMAIN'); break; } case SERVICE_TYPES.CAVACH_API: { @@ -854,7 +860,7 @@ export class AppAuthService { TokenModule.DASHBOARD, SERVICE_TYPES.CAVACH_API, ); - audiance = this.config.get('CAVACH_API_DOMAIN') + audiance = this.config.get('CAVACH_API_DOMAIN'); break; } case SERVICE_TYPES.QUEST: { From 9282f4111444712da904d27799beeff5f634df21 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 5 Dec 2025 11:41:36 +0530 Subject: [PATCH 37/55] removed audiance from token --- src/app-auth/services/app-auth.service.ts | 16 ++-------------- .../services/webpage-config.service.ts | 2 -- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 8b27349e..ac343ed6 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -682,7 +682,6 @@ export class AppAuthService { const serviceType = appDetail.services[0]?.id; // TODO: remove this later let grant_type = ''; let accessList = []; - let audiance; switch (serviceType) { case SERVICE_TYPES.SSI_API: { grant_type = GRANT_TYPES.access_service_ssi; @@ -690,7 +689,6 @@ export class AppAuthService { TokenModule.VERIFIER, SERVICE_TYPES.SSI_API, ); - audiance = this.config.get('SSI_API_DOMAIN'); break; } case SERVICE_TYPES.CAVACH_API: { @@ -708,7 +706,6 @@ export class AppAuthService { TokenModule.VERIFIER, SERVICE_TYPES.CAVACH_API, ); - audiance = this.config.get('CAVACH_API_DOMAIN'); break; } case SERVICE_TYPES.QUEST: { @@ -730,13 +727,7 @@ export class AppAuthService { ]); } - return this.getAccessToken( - grant_type, - appDetail, - expiresin, - accessList, - audiance, - ); + return this.getAccessToken(grant_type, appDetail, expiresin, accessList); } public async getAccessToken( @@ -832,7 +823,6 @@ export class AppAuthService { const serviceType = app.services[0]?.id; // TODO: remove this later let accessList = []; - let audiance; switch (serviceType) { case SERVICE_TYPES.SSI_API: { if (grantType != 'access_service_ssi') { @@ -844,7 +834,6 @@ export class AppAuthService { TokenModule.DASHBOARD, SERVICE_TYPES.SSI_API, ); - audiance = this.config.get('SSI_API_DOMAIN'); break; } case SERVICE_TYPES.CAVACH_API: { @@ -860,7 +849,6 @@ export class AppAuthService { TokenModule.DASHBOARD, SERVICE_TYPES.CAVACH_API, ); - audiance = this.config.get('CAVACH_API_DOMAIN'); break; } case SERVICE_TYPES.QUEST: { @@ -885,6 +873,6 @@ export class AppAuthService { ]); } - return this.getAccessToken(grantType, app, 12, accessList, audiance); + return this.getAccessToken(grantType, app, 12, accessList); } } diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 7049af13..3db9c64d 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -299,14 +299,12 @@ export class WebpageConfigService { ssiServiceDetail, 0.5, getAccessListForModule('VERIFIER', SERVICE_TYPES.SSI_API), - this.config.get('SSI_API_DOMAIN') ), this.appAuthService.getAccessToken( GRANT_TYPES.access_service_kyc, kycServiceDetail, 0.5, getAccessListForModule('VERIFIER', SERVICE_TYPES.CAVACH_API), - this.config.get('CAVACH_API_DOMAIN') ), ]); const redisPayload = { From 7b00c6ad477ac975557f66e5b9b46194e0874dfa Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 5 Dec 2025 11:45:42 +0530 Subject: [PATCH 38/55] removed aud from token --- src/app-auth/services/app-auth.service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index ac343ed6..00c90cf4 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -735,7 +735,6 @@ export class AppAuthService { appDetail, expiresin = 4, accessList = [], - aud?, ) { const payload = { appId: appDetail.appId, @@ -749,9 +748,6 @@ export class AppAuthService { env: appDetail.env ? appDetail.env : APP_ENVIRONMENT.dev, appName: appDetail.appName, }; - if (aud) { - payload['aud'] = aud; - } if (appDetail.issuerDid) { payload['issuerDid'] = appDetail.issuerDid; } From f3c3919ed587ee6ce44c15b937624e9b5e2d5e74 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 5 Dec 2025 15:17:58 +0530 Subject: [PATCH 39/55] using constant file --- .../services/customer-onboarding.service.ts | 3 --- src/utils/time-constant.ts | 1 + src/webpage-config/services/webpage-config.service.ts | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index ebbef8a4..ff40eb55 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -697,9 +697,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/utils/time-constant.ts b/src/utils/time-constant.ts index 3f686d30..71577a00 100644 --- a/src/utils/time-constant.ts +++ b/src/utils/time-constant.ts @@ -22,6 +22,7 @@ export const TOKEN = { VERIFIER_TOKEN: { name: 'verifierPageToken', expiry: 30 * TIME.MINUTE * 1000, + jwtExpiry: 0.5, }, }; diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index e8e026ea..fd8d7827 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -301,13 +301,13 @@ export class WebpageConfigService { this.appAuthService.getAccessToken( GRANT_TYPES.access_service_ssi, ssiServiceDetail, - 0.5, + TOKEN.VERIFIER_TOKEN.jwtExpiry, ssiAccessList, ), this.appAuthService.getAccessToken( GRANT_TYPES.access_service_kyc, kycServiceDetail, - 0.5, + TOKEN.VERIFIER_TOKEN.jwtExpiry, kycAccessList, ), ]); From e474ff01f26a5d48cfa7659cce0f193aa6b67083 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 5 Dec 2025 18:21:14 +0530 Subject: [PATCH 40/55] removed senstive data from token --- src/app-auth/services/app-auth.service.ts | 84 ++++++++++++++----- src/config/access-matrix.ts | 1 + .../services/customer-onboarding.service.ts | 39 +++++++-- .../services/iServiceList.ts | 4 + .../controller/webpage-config.controller.ts | 7 +- .../services/webpage-config.service.ts | 80 ++++++++++++------ 6 files changed, 156 insertions(+), 59 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 00c90cf4..a2464f6f 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,8 @@ 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 { TIME } from 'src/utils/time-constant'; export enum GRANT_TYPES { access_service_kyc = 'access_service_kyc', @@ -643,7 +646,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,6 +684,20 @@ 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; @@ -726,15 +742,33 @@ 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( + public async getAccessToken(data, expiresin = 4) { + const secret = this.config.get('JWT_SECRET'); + const token = await this.jwt.signAsync(data, { + 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' }; + } + public async storeDataInRedis( grantType, appDetail, - expiresin = 4, accessList = [], + sessionId, ) { const payload = { appId: appDetail.appId, @@ -763,27 +797,18 @@ 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' }; + redisClient.set(sessionId, JSON.stringify(payload), 'EX', TIME.WEEK); } - //access_service_ssi - //access_service_kyc - async grantPermission( grantType: string, appId: string, user, ): Promise<{ access_token; expiresIn; tokenType }> { + const context = Context.idDashboard; + const sessionId = `${appId}_${context}`; + const savedSession = await redisClient.get(sessionId); switch (grantType) { case GRANT_TYPES.access_service_ssi: break; @@ -803,13 +828,23 @@ export class AppAuthService { } } + if (savedSession) { + const app = JSON.parse(savedSession); + const dataToStore = { + appId, + appName: app.appName, + grantType, + subdomain: app.subdomain, + sessionId, + }; + return this.getAccessToken(dataToStore, 12); + } 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 +903,14 @@ 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, 12); } } diff --git a/src/config/access-matrix.ts b/src/config/access-matrix.ts index f7f7e409..5df04938 100644 --- a/src/config/access-matrix.ts +++ b/src/config/access-matrix.ts @@ -29,6 +29,7 @@ export const KYC_ACCESS_MATRIX = { SERVICES.CAVACH_API.ACCESS_TYPES.READ_USER_CONSENT, ], [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/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index f2e78db0..37c089bc 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -450,8 +450,17 @@ export class CustomerOnboardingService { } ssiAccessToken = await this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_ssi, - ssiService, + { + appId: + ssiService?.appId || customerOnboardingData.ssiServiceId, + grantType: GRANT_TYPES.access_service_ssi, + appname: ssiService?.appName, + subdomain: + ssiService?.subdomain || + customerOnboardingData.ssiSubdomain, + sessionId: + ssiService?.appId || customerOnboardingData.ssiServiceId, + }, 4, ); @@ -483,8 +492,17 @@ export class CustomerOnboardingService { ssiAccessToken = ssiAccessToken || (await this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_ssi, - ssiService, + { + appId: + ssiService?.appId || customerOnboardingData.ssiServiceId, + grantType: GRANT_TYPES.access_service_ssi, + appname: ssiService?.appName, + subdomain: + ssiService?.subdomain || + customerOnboardingData.ssiSubdomain, + sessionId: + ssiService?.appId || customerOnboardingData.ssiServiceId, + }, 4, )); @@ -642,8 +660,17 @@ export class CustomerOnboardingService { }); } kycAccessToken = await this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_kyc, - kycService, + { + appId: + kycService?.appId || customerOnboardingData.kycServiceId, + appName: kycService.appName, + grantType: GRANT_TYPES.access_service_kyc, + subdomain: + kycService?.subdomain || + customerOnboardingData.kycSubdomain, + sessionId: + kycService?.appId || customerOnboardingData.kycServiceId, + }, 4, ); const requestBody = { diff --git a/src/supported-service/services/iServiceList.ts b/src/supported-service/services/iServiceList.ts index a8fd3abc..73daf0e9 100644 --- a/src/supported-service/services/iServiceList.ts +++ b/src/supported-service/services/iServiceList.ts @@ -112,3 +112,7 @@ export namespace SERVICES { } } } + +export enum Context { + idDashboard = 'idDashboard', +} 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..85879a3a 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -254,25 +254,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 +292,47 @@ 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, + { + appId: ssiServiceId, + appName: ssiServiceDetail.appName, + grantType: GRANT_TYPES.access_service_ssi, + sessionId: ssiServiceId, + }, 0.5, - getAccessListForModule('VERIFIER', SERVICE_TYPES.SSI_API), ), this.appAuthService.getAccessToken( - GRANT_TYPES.access_service_kyc, - kycServiceDetail, + { + appId, + appName: kycServiceDetail.appName, + grantType: GRANT_TYPES.access_service_kyc, + sessionId: appId, + }, 0.5, - getAccessListForModule('VERIFIER', SERVICE_TYPES.CAVACH_API), ), ]); const redisPayload = { From dfe6891f9aee01fcfa209828dd65890fe63f72de Mon Sep 17 00:00:00 2001 From: varsha766 Date: Fri, 5 Dec 2025 21:07:28 +0530 Subject: [PATCH 41/55] aded missing field in token --- src/webpage-config/services/webpage-config.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 85879a3a..e72b2d65 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -322,6 +322,7 @@ export class WebpageConfigService { appName: ssiServiceDetail.appName, grantType: GRANT_TYPES.access_service_ssi, sessionId: ssiServiceId, + subdomain: ssiServiceDetail.subdomain }, 0.5, ), From bbfce7940b3cd6911f90c67aaea31ceb5eecbee9 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 8 Dec 2025 10:27:34 +0530 Subject: [PATCH 42/55] updated redis once we update the app --- src/app-auth/services/app-auth.service.ts | 52 ++++++++++++++++++- .../services/webpage-config.service.ts | 3 +- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index a2464f6f..409a385b 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -64,7 +64,7 @@ export class AppAuthService { @InjectModel(CustomerOnboarding.name) private readonly onboardModel: Model, private readonly webpageConfigRepo: WebPageConfigRepository, - ) {} + ) { } async createAnApp( createAppDto: CreateAppDto, @@ -544,7 +544,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 { @@ -622,6 +664,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 }; } diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index e72b2d65..42eb798e 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -322,7 +322,7 @@ export class WebpageConfigService { appName: ssiServiceDetail.appName, grantType: GRANT_TYPES.access_service_ssi, sessionId: ssiServiceId, - subdomain: ssiServiceDetail.subdomain + subdomain: ssiServiceDetail.subdomain, }, 0.5, ), @@ -332,6 +332,7 @@ export class WebpageConfigService { appName: kycServiceDetail.appName, grantType: GRANT_TYPES.access_service_kyc, sessionId: appId, + subdomain: kycServiceDetail.subdomain, }, 0.5, ), From c3a98fda01487f2a7b4414ea0b01468d013fa4e4 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 8 Dec 2025 10:54:24 +0530 Subject: [PATCH 43/55] added backward compatibility for appId and dcoument id to serch verifer page --- .../services/webpage-config.service.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index ca2b8753..7ed6b0a5 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -20,7 +20,7 @@ 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'; @@ -146,10 +146,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}`, From 549d5b5d63a025a8a936d7ddf26b3d14c1697843 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 8 Dec 2025 12:41:26 +0530 Subject: [PATCH 44/55] code prettified --- src/webpage-config/services/webpage-config.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 7ed6b0a5..9d504920 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -25,7 +25,6 @@ import { WEBPAGE_CONFIG_ERRORS } from '../constant/en'; import { redisClient } from 'src/utils/redis.provider'; import { TOKEN } from 'src/utils/time-constant'; import { getAccessListForModule, REDIS_KEYS } from 'src/utils/utils'; -import { TokenModule } from 'src/config/access-matrix'; @Injectable() export class WebpageConfigService { @@ -148,7 +147,7 @@ export class WebpageConfigService { ): Promise { const isValidId = isValidObjectId(id); const query: any = { - $or: [{ serviceId: id }] + $or: [{ serviceId: id }], }; if (isValidId) { query.$or.push({ _id: new Types.ObjectId(id) }); From 2123ec8f3413cb2f81fe7d903dc1ed7694e5248e Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 8 Dec 2025 13:03:12 +0530 Subject: [PATCH 45/55] added decorator to validae base4 and url together --- .../dto/create-customer-onboarding.dto.ts | 5 ++-- .../IsUrlOrBase64Image.decorator.ts | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/utils/customDecorator/IsUrlOrBase64Image.decorator.ts 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/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`; + }, + }, + }); + }; +} From 2fcdc1db7caa080206f4ef19d922cf36ed274f3b Mon Sep 17 00:00:00 2001 From: varsha766 Date: Mon, 8 Dec 2025 21:44:05 +0530 Subject: [PATCH 46/55] fixed credit of both service and token expired error --- src/credits/credits.module.ts | 8 +- .../services/customer-onboarding.service.ts | 81 ++++++++++++++++--- 2 files changed, 77 insertions(+), 12 deletions(-) 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/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index 9231a8f8..cc63a000 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 { TIME } from 'src/utils/time-constant'; +import { TokenModule } from 'src/config/access-matrix'; @Injectable() export class CustomerOnboardingService { @@ -241,12 +246,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 +264,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', + 5 * TIME.MINUTE, + ); + 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 +352,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 +430,7 @@ export class CustomerOnboardingService { 'CREATE_SSI_SERVICE step ends', 'CustomerOnboardingService', ); + ssiRedisKey = `${ssiService.appId}_${Context.idDashboard}`; break; } @@ -430,6 +452,7 @@ export class CustomerOnboardingService { ssiTenantUrl, secret, ssiService?.whitelistedCors, + [SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDIT], ); Logger.debug( 'CREDIT_SSI_SERVICE step ends', @@ -448,7 +471,18 @@ 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( { appId: @@ -458,8 +492,7 @@ export class CustomerOnboardingService { subdomain: ssiService?.subdomain || customerOnboardingData.ssiSubdomain, - sessionId: - ssiService?.appId || customerOnboardingData.ssiServiceId, + sessionId: ssiRedisKey, }, 4, ); @@ -471,6 +504,7 @@ export class CustomerOnboardingService { headers: { authorization: `Bearer ${ssiAccessToken.access_token}`, 'Content-Type': 'application/json', + origin: ssiService.whitelistedCors[0], }, body: JSON.stringify({ namespace: 'testnet' }), }, @@ -489,6 +523,18 @@ 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( @@ -500,8 +546,7 @@ export class CustomerOnboardingService { subdomain: ssiService?.subdomain || customerOnboardingData.ssiSubdomain, - sessionId: - ssiService?.appId || customerOnboardingData.ssiServiceId, + sessionId: ssiRedisKey, }, 4, )); @@ -520,6 +565,7 @@ export class CustomerOnboardingService { headers: { authorization: `Bearer ${ssiAccessToken.access_token}`, 'Content-Type': 'application/json', + origin: ssiService.whitelistedCors[0], }, }, 'Failed to resolve DID', @@ -583,6 +629,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', @@ -642,6 +689,7 @@ export class CustomerOnboardingService { kycTenantUrl, secret, kycService?.whitelistedCors, + [SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_CREDIT], ); Logger.debug( 'CREDIT_KYC_SERVICE step ends', @@ -659,6 +707,18 @@ 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( { appId: @@ -668,8 +728,7 @@ export class CustomerOnboardingService { subdomain: kycService?.subdomain || customerOnboardingData.kycSubdomain, - sessionId: - kycService?.appId || customerOnboardingData.kycServiceId, + sessionId: kycRedisKey, }, 4, ); @@ -710,7 +769,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), }, From 55e6a4b58f71d7a3b6550976332bedb7d4e88ca8 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 9 Dec 2025 17:53:49 +0530 Subject: [PATCH 47/55] storing user access list based on default and user permission --- src/app-auth/services/app-auth.service.ts | 56 ++++++++++-- src/app-oauth/app-oauth.controller.ts | 8 +- .../customer-onboarding.controller.ts | 2 + .../services/customer-onboarding.service.ts | 52 ++++++++--- src/utils/utils.ts | 36 ++++++++ .../services/webpage-config.service.ts | 86 ++++++++++--------- 6 files changed, 177 insertions(+), 63 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index e53baa87..417b7b78 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -34,7 +34,7 @@ import { WebPageConfigRepository } from 'src/webpage-config/repositories/webpage import { InjectModel } from '@nestjs/mongoose'; import { CustomerOnboarding } from 'src/customer-onboarding/schemas/customer-onboarding.schema'; import { Model } from 'mongoose'; -import { getAccessListForModule } from 'src/utils/utils'; +import { evaluateAccessPolicy, getAccessListForModule } from 'src/utils/utils'; import { TokenModule } from 'src/config/access-matrix'; import { redisClient } from 'src/utils/redis.provider'; import { TIME } from 'src/utils/time-constant'; @@ -64,7 +64,7 @@ export class AppAuthService { @InjectModel(CustomerOnboarding.name) private readonly onboardModel: Model, private readonly webpageConfigRepo: WebPageConfigRepository, - ) { } + ) {} async createAnApp( createAppDto: CreateAppDto, @@ -749,10 +749,15 @@ export class AppAuthService { switch (serviceType) { case SERVICE_TYPES.SSI_API: { grant_type = GRANT_TYPES.access_service_ssi; - accessList = getAccessListForModule( + const defaultAccessList = getAccessListForModule( TokenModule.APP_AUTH, SERVICE_TYPES.SSI_API, ); + accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.SSI_API, + [], + ); break; } case SERVICE_TYPES.CAVACH_API: { @@ -766,18 +771,29 @@ export class AppAuthService { ]); } grant_type = grantType || GRANT_TYPES.access_service_kyc; - accessList = getAccessListForModule( + const defaultAccessList = getAccessListForModule( TokenModule.APP_AUTH, SERVICE_TYPES.CAVACH_API, ); + accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.CAVACH_API, + [], + + ); break; } case SERVICE_TYPES.QUEST: { grant_type = GRANT_TYPES.access_service_quest; - accessList = getAccessListForModule( + const defaultAccessList = getAccessListForModule( TokenModule.APP_AUTH, SERVICE_TYPES.QUEST, ); + accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.QUEST, + [], + ); break; } default: { @@ -853,9 +869,13 @@ export class AppAuthService { grantType: string, appId: string, user, + session?, ): Promise<{ access_token; expiresIn; tokenType }> { const context = Context.idDashboard; - const sessionId = `${appId}_${context}`; + let sessionId = `${appId}_${context}_${session.userId}`; + if (session && session.tenantId) { + sessionId = `${sessionId}_tenant`; + } const savedSession = await redisClient.get(sessionId); switch (grantType) { case GRANT_TYPES.access_service_ssi: @@ -909,10 +929,16 @@ export class AppAuthService { 'Invalid grant type for this service ' + appId, ]); } - accessList = getAccessListForModule( + const defaultAccessList = getAccessListForModule( TokenModule.DASHBOARD, SERVICE_TYPES.SSI_API, ); + accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.SSI_API, + user.accessList, + context, + ); break; } case SERVICE_TYPES.CAVACH_API: { @@ -924,10 +950,16 @@ export class AppAuthService { 'Invalid grant type for this service ' + appId, ]); } - accessList = getAccessListForModule( + const defaultAccessList = getAccessListForModule( TokenModule.DASHBOARD, SERVICE_TYPES.CAVACH_API, ); + accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.CAVACH_API, + user.accessList, + context, + ); break; } case SERVICE_TYPES.QUEST: { @@ -936,10 +968,16 @@ export class AppAuthService { 'Invalid grant type for this service ' + appId, ]); } - accessList = getAccessListForModule( + const defaultAccessList = getAccessListForModule( TokenModule.DASHBOARD, SERVICE_TYPES.QUEST, ); + accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.QUEST, + user.accessList, + context, + ); break; } default: { diff --git a/src/app-oauth/app-oauth.controller.ts b/src/app-oauth/app-oauth.controller.ts index 61ab0134..a96feb6e 100644 --- a/src/app-oauth/app-oauth.controller.ts +++ b/src/app-oauth/app-oauth.controller.ts @@ -134,9 +134,15 @@ export class AppOauthController { @Req() request, ): Promise<{ access_token; expiresIn; tokenType }> { const { user } = request; + const { session } = request; // Logger.log('reGenerateAppSecretKey() method: starts', 'AppOAuthController'); - return this.appAuthService.grantPermission(grantType, serviceId, user); + return this.appAuthService.grantPermission( + grantType, + serviceId, + user, + session, + ); } } diff --git a/src/customer-onboarding/controllers/customer-onboarding.controller.ts b/src/customer-onboarding/controllers/customer-onboarding.controller.ts index 786301b7..dc74f5e3 100644 --- a/src/customer-onboarding/controllers/customer-onboarding.controller.ts +++ b/src/customer-onboarding/controllers/customer-onboarding.controller.ts @@ -104,10 +104,12 @@ export class CustomerOnboardingController { processCustomerOnboarding( @Param('id') id: string, @Body() customerOnboardingProcessDto: CustomerOnboardingProcessDto, + @Req() req, ) { return this.customerOnboardingService.processCustomerOnboarding( id, customerOnboardingProcessDto, + req['user'], ); } } diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index cc63a000..76eca83d 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -37,7 +37,11 @@ import { LogDetail, } from '../schemas/customer-onboarding.schema'; import { AppRepository } from 'src/app-auth/repositories/app.repository'; -import { getAccessListForModule, sanitizeUrl } from 'src/utils/utils'; +import { + evaluateAccessPolicy, + 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'; @@ -316,6 +320,7 @@ export class CustomerOnboardingService { async processCustomerOnboarding( id: string, customerOnboardingProcessDto: CustomerOnboardingProcessDto, + user, ) { const onboardingLogs: LogDetail[] = []; const onboardingUpdateData: Partial = {}; @@ -472,14 +477,21 @@ export class CustomerOnboardingService { }); } const ssiServiceDetail = await redisClient.get(ssiRedisKey); + const defaultAccessList = getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.SSI_API, + ); + const accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.SSI_API, + user.accessList, + Context.idDashboard, + ); if (!ssiServiceDetail) { await this.appAuthService.storeDataInRedis( GRANT_TYPES.access_service_ssi, ssiService, - getAccessListForModule( - TokenModule.DASHBOARD, - SERVICE_TYPES.SSI_API, - ), + accessList, ssiRedisKey, ); } @@ -524,14 +536,21 @@ export class CustomerOnboardingService { 'CustomerOnboardingService', ); const ssiServiceDetail = await redisClient.get(ssiRedisKey); + const defaultAccessList = getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.SSI_API, + ); + const accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.SSI_API, + user.accessList, + Context.idDashboard, + ); if (!ssiServiceDetail) { await this.appAuthService.storeDataInRedis( GRANT_TYPES.access_service_ssi, ssiService, - getAccessListForModule( - TokenModule.DASHBOARD, - SERVICE_TYPES.SSI_API, - ), + accessList, ssiRedisKey, ); } @@ -707,15 +726,22 @@ export class CustomerOnboardingService { appId: customerOnboardingData.kycServiceId, }); } + const defaultAccessList = getAccessListForModule( + TokenModule.DASHBOARD, + SERVICE_TYPES.CAVACH_API, + ); + const accessList = evaluateAccessPolicy( + defaultAccessList, + SERVICE_TYPES.CAVACH_API, + user.accessList, + Context.idDashboard, + ); 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, - ), + accessList, kycRedisKey, ); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index fccfe00c..0d4b80ac 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -15,6 +15,7 @@ import { } from '@nestjs/common'; import { Did } from 'hs-ssi-sdk'; import { + Context, SERVICE_TYPES, SERVICES, } from 'src/supported-service/services/iServiceList'; @@ -191,3 +192,38 @@ export function getAccessListForModule( return QUEST_ACCESS_MATRIX[module] || []; } } +export const evaluateAccessPolicy = ( + defaultAccessList: string[], + serviceType: SERVICE_TYPES, + userAccessList?: { + serviceType: SERVICE_TYPES; + access: string; + expiryDate?: Date; + }[], + context?: string, +): string[] => { + if (!context) { + return defaultAccessList; + } + // will remove it once access lsit in fixed in ssi service + if (serviceType !== SERVICE_TYPES.CAVACH_API) { + return defaultAccessList; + } + if (context === Context.idDashboard) { + // No user access info → Return NO access + if (!userAccessList?.length) { + return []; + } + const userServiceAccess = userAccessList + .filter((a) => a.serviceType === serviceType) + .map((a) => a.access); + + // User With ALL access + if (userServiceAccess.includes('ALL')) { + return defaultAccessList; + } + // Intersection rule + return defaultAccessList.filter((p) => userServiceAccess.includes(p)); + } + return defaultAccessList; +}; diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 9d504920..c0a4a438 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -24,7 +24,12 @@ 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 { getAccessListForModule, REDIS_KEYS } from 'src/utils/utils'; +import { + evaluateAccessPolicy, + getAccessListForModule, + REDIS_KEYS, +} from 'src/utils/utils'; +import { TokenModule } from 'src/config/access-matrix'; @Injectable() export class WebpageConfigService { @@ -271,24 +276,11 @@ export class WebpageConfigService { WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_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); - } + const kycServiceDetail = await this.getServiceAndCache( + appId, + SERVICE_TYPES.CAVACH_API, + GRANT_TYPES.access_service_kyc, + ); if ( !kycServiceDetail.dependentServices || kycServiceDetail.dependentServices.length === 0 @@ -298,27 +290,11 @@ export class WebpageConfigService { ]); } 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); - } - + const ssiServiceDetail = await this.getServiceAndCache( + ssiServiceId, + SERVICE_TYPES.SSI_API, + GRANT_TYPES.access_service_ssi, + ); // generate access tokens const [ssiAccessTokenDetail, kycAccessTokenDetail] = await Promise.all([ this.appAuthService.getAccessToken( @@ -356,4 +332,34 @@ export class WebpageConfigService { ...redisPayload, }; } + public async getServiceAndCache( + appId: string, + serviceType: SERVICE_TYPES, + grantType: GRANT_TYPES, + ) { + const cached = await redisClient.get(appId); + if (cached) return JSON.parse(cached); + const serviceDetail = await this.appRepository.findOne({ appId }); + if (!serviceDetail) { + throw new BadRequestException([ + WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_LINKED_APP_NOT_FOUND, + ]); + } + const defaultAccessList = getAccessListForModule( + TokenModule.VERIFIER, + serviceType, + ); + const validateAccessList = evaluateAccessPolicy( + defaultAccessList, + serviceType, + [], + ); + await this.appAuthService.storeDataInRedis( + grantType, + serviceDetail, + validateAccessList, + appId, + ); + return serviceDetail; + } } From c3391b485384404dd4e347111d82d19811e590f9 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 9 Dec 2025 20:42:33 +0530 Subject: [PATCH 48/55] fixed app logourl issue --- src/app-auth/dtos/create-app.dto.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app-auth/dtos/create-app.dto.ts b/src/app-auth/dtos/create-app.dto.ts index 92832536..27ddc99a 100644 --- a/src/app-auth/dtos/create-app.dto.ts +++ b/src/app-auth/dtos/create-app.dto.ts @@ -19,6 +19,7 @@ import { SERVICE_TYPES, APP_ENVIRONMENT, } from 'src/supported-service/services/iServiceList'; +import { IsUrlOrBase64Image } from 'src/utils/customDecorator/IsUrlOrBase64Image.decorator'; export class CreateAppDto { @ApiProperty({ @@ -60,7 +61,7 @@ export class CreateAppDto { }) @IsOptional() @IsString() - @IsUrlEmpty() + @IsUrlOrBase64Image() logoUrl?: string; @ApiProperty({ description: 'services', From 43649d91d7e6e967ccaf0fe1d9104c4b647d2b43 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Tue, 9 Dec 2025 22:22:43 +0530 Subject: [PATCH 49/55] remove default access assignment to user and providing access at the time of onboarding --- .../services/customer-onboarding.service.ts | 27 +++++++++---------- .../services/social-login.service.ts | 7 ----- src/user/schema/user.schema.ts | 4 +-- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index 76eca83d..4c494edb 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -662,23 +662,22 @@ export class CustomerOnboardingService { 'CustomerOnboardingService', ); await this.userRepository.findOneUpdate( - { - userId, - accessList: { - $not: { - $elemMatch: { - serviceType: 'CAVACH_API', - access: 'ALL', - }, - }, - }, - }, + { userId }, { $push: { accessList: { - serviceType: 'CAVACH_API', - access: 'ALL', - expiryDate: null, + $each: [ + { + serviceType: SERVICE_TYPES.CAVACH_API, + access: SERVICES.CAVACH_API.ACCESS_TYPES.ALL, + expiryDate: null, + }, + { + serviceType: SERVICE_TYPES.SSI_API, + access: SERVICES.SSI_API.ACCESS_TYPES.ALL, + expiryDate: null, + }, + ], }, }, }, diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 2d2779af..37187a0f 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -74,18 +74,11 @@ export class SocialLoginService { }); if (!user) { const userId = `${Date.now()}-${uuidv4()}`; - const ssiAccessList = this.supportedServiceList.getDefaultServicesAccess( - SERVICE_TYPES.SSI_API, - ); - const kycAccessList = this.supportedServiceList.getDefaultServicesAccess( - SERVICE_TYPES.CAVACH_API, - ); user = await this.userRepository.create({ email, userId, name, profileIcon, - accessList: [...ssiAccessList, ...kycAccessList], }); } const updates: Partial = {}; diff --git a/src/user/schema/user.schema.ts b/src/user/schema/user.schema.ts index b8af9553..2cfc27ed 100644 --- a/src/user/schema/user.schema.ts +++ b/src/user/schema/user.schema.ts @@ -38,9 +38,9 @@ export class User { name?: string; @Prop({ required: false }) profileIcon?: string; - @Prop({ required: false }) + @Prop({ required: false, default: [] }) @Optional() - accessList: Array; + accessList?: Array; @Prop({ default: [] }) authenticators?: Authenticator[]; @Prop({ From 5ad0f311b4fe2479251170d78f5d727c5ab3fabc Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 10 Dec 2025 09:57:01 +0530 Subject: [PATCH 50/55] reading accessList from user --- .../controllers/customer-onboarding.controller.ts | 1 - .../services/customer-onboarding.service.ts | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/customer-onboarding/controllers/customer-onboarding.controller.ts b/src/customer-onboarding/controllers/customer-onboarding.controller.ts index dc74f5e3..166db997 100644 --- a/src/customer-onboarding/controllers/customer-onboarding.controller.ts +++ b/src/customer-onboarding/controllers/customer-onboarding.controller.ts @@ -109,7 +109,6 @@ export class CustomerOnboardingController { return this.customerOnboardingService.processCustomerOnboarding( id, customerOnboardingProcessDto, - req['user'], ); } } diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index 76eca83d..222f099a 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -320,7 +320,6 @@ export class CustomerOnboardingService { async processCustomerOnboarding( id: string, customerOnboardingProcessDto: CustomerOnboardingProcessDto, - user, ) { const onboardingLogs: LogDetail[] = []; const onboardingUpdateData: Partial = {}; @@ -374,6 +373,7 @@ export class CustomerOnboardingService { throw new BadRequestException(['Customer onboarding is already done']); } let onboardingStatus; + const userDetail = await this.userRepository.findOne({ userId }); // Process each step for (const step of remainingSteps) { try { @@ -484,7 +484,7 @@ export class CustomerOnboardingService { const accessList = evaluateAccessPolicy( defaultAccessList, SERVICE_TYPES.SSI_API, - user.accessList, + userDetail.accessList, Context.idDashboard, ); if (!ssiServiceDetail) { @@ -543,7 +543,7 @@ export class CustomerOnboardingService { const accessList = evaluateAccessPolicy( defaultAccessList, SERVICE_TYPES.SSI_API, - user.accessList, + userDetail.accessList, Context.idDashboard, ); if (!ssiServiceDetail) { @@ -733,7 +733,7 @@ export class CustomerOnboardingService { const accessList = evaluateAccessPolicy( defaultAccessList, SERVICE_TYPES.CAVACH_API, - user.accessList, + userDetail.accessList, Context.idDashboard, ); const kycServiceDetail = await redisClient.get(kycRedisKey); From d0604ffaeefd23632f3801daafeed51002c5ac62 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 10 Dec 2025 13:14:24 +0530 Subject: [PATCH 51/55] fixed user permission and added new access fir ssi service --- src/app-auth/services/app-auth.service.ts | 1 - src/config/access-matrix.ts | 31 ++++++- src/customer-onboarding/constants/enum.ts | 2 +- .../customer-onboarding.controller.ts | 2 - .../services/customer-onboarding.service.ts | 86 ++++++++++--------- .../services/iServiceList.ts | 28 +++--- src/utils/utils.ts | 7 +- .../services/webpage-config.service.ts | 8 +- 8 files changed, 93 insertions(+), 72 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 417b7b78..788f7aa7 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -779,7 +779,6 @@ export class AppAuthService { defaultAccessList, SERVICE_TYPES.CAVACH_API, [], - ); break; } diff --git a/src/config/access-matrix.ts b/src/config/access-matrix.ts index 2c1a76aa..63177cc7 100644 --- a/src/config/access-matrix.ts +++ b/src/config/access-matrix.ts @@ -3,6 +3,8 @@ export enum TokenModule { DASHBOARD = 'DASHBOARD', VERIFIER = 'VERIFIER', APP_AUTH = 'APP_AUTH', + SUPER_ADMIN = 'SUPER_ADMIN', + ID_SERVICE = 'ID_SERVICE', } export const KYC_ACCESS_MATRIX = { [TokenModule.DASHBOARD]: [ @@ -38,12 +40,33 @@ export const KYC_ACCESS_MATRIX = { SERVICES.CAVACH_API.ACCESS_TYPES.READ_WIDGET_CONFIG, SERVICES.CAVACH_API.ACCESS_TYPES.READ_USER_CONSENT, ], + [TokenModule.SUPER_ADMIN]: [SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDIT], }; export const SSI_ACCESS_MATRIX = { - // will modify its access later. Assigning ALL for the time being - [TokenModule.DASHBOARD]: [SERVICES.SSI_API.ACCESS_TYPES.ALL], - [TokenModule.VERIFIER]: [SERVICES.SSI_API.ACCESS_TYPES.ALL], - [TokenModule.APP_AUTH]: [SERVICES.SSI_API.ACCESS_TYPES.ALL], + [TokenModule.DASHBOARD]: [ + SERVICES.SSI_API.ACCESS_TYPES.READ_DID, + SERVICES.SSI_API.ACCESS_TYPES.WRITE_DID, + SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDIT, + SERVICES.SSI_API.ACCESS_TYPES.READ_CREDIT, + SERVICES.SSI_API.ACCESS_TYPES.WRITE_SCHEMA, + SERVICES.SSI_API.ACCESS_TYPES.READ_SCHEMA, + SERVICES.SSI_API.ACCESS_TYPES.CHECK_LIVE_STATUS, + SERVICES.SSI_API.ACCESS_TYPES.READ_TX, + SERVICES.SSI_API.ACCESS_TYPES.READ_CREDENTIAL, + SERVICES.SSI_API.ACCESS_TYPES.VERIFY_CREDENTAIL, + SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDENTAL, + SERVICES.SSI_API.ACCESS_TYPES.READ_USAGE, + SERVICES.SSI_API.ACCESS_TYPES.WRITE_PRESENTATION, + SERVICES.SSI_API.ACCESS_TYPES.VERIFY_PRESENTATION, + ], + [TokenModule.VERIFIER]: [SERVICES.SSI_API.ACCESS_TYPES.READ_DID], + [TokenModule.APP_AUTH]: [], + [TokenModule.ID_SERVICE]: [ + SERVICES.SSI_API.ACCESS_TYPES.READ_TX, + SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDENTAL, + SERVICES.SSI_API.ACCESS_TYPES.VERIFY_PRESENTATION, + ], + [TokenModule.SUPER_ADMIN]: [SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDIT], }; export const QUEST_ACCESS_MATRIX = { [TokenModule.DASHBOARD]: [], diff --git a/src/customer-onboarding/constants/enum.ts b/src/customer-onboarding/constants/enum.ts index fe994f7f..76138284 100644 --- a/src/customer-onboarding/constants/enum.ts +++ b/src/customer-onboarding/constants/enum.ts @@ -133,7 +133,7 @@ export enum OnboardingStep { CREATE_DID = 'CREATE_DID', REGISTER_DID = 'REGISTER_DID', CREATE_KYC_SERVICE = 'CREATE_KYC_SERVICE', - GIVE_KYC_DASHBOARD_ACCESS = 'GIVE_KYC_DASHBOARD_ACCESS', + GIVE_DASHBOARD_ACCESS = 'GIVE_DASHBOARD_ACCESS', CREDIT_KYC_SERVICE = 'CREDIT_KYC_SERVICE', SETUP_KYC_WIDGET = 'SETUP_KYC_WIDGET', CONFIGURE_KYC_VERIFIER_PAGE = 'CONFIGURE_KYC_VERIFIER_PAGE', diff --git a/src/customer-onboarding/controllers/customer-onboarding.controller.ts b/src/customer-onboarding/controllers/customer-onboarding.controller.ts index dc74f5e3..786301b7 100644 --- a/src/customer-onboarding/controllers/customer-onboarding.controller.ts +++ b/src/customer-onboarding/controllers/customer-onboarding.controller.ts @@ -104,12 +104,10 @@ export class CustomerOnboardingController { processCustomerOnboarding( @Param('id') id: string, @Body() customerOnboardingProcessDto: CustomerOnboardingProcessDto, - @Req() req, ) { return this.customerOnboardingService.processCustomerOnboarding( id, customerOnboardingProcessDto, - req['user'], ); } } diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index 4c494edb..14cfaa4c 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -320,7 +320,6 @@ export class CustomerOnboardingService { async processCustomerOnboarding( id: string, customerOnboardingProcessDto: CustomerOnboardingProcessDto, - user, ) { const onboardingLogs: LogDetail[] = []; const onboardingUpdateData: Partial = {}; @@ -345,14 +344,12 @@ export class CustomerOnboardingService { `Customer onboarding detail not found for id: ${id}`, ]); } - // Initialize configuration const { companyName, domain, userId, companyLogo, customerEmail } = customerOnboardingData; const ssiBaseDomain = this.config.get('SSI_API_DOMAIN'); const cavachBaseDomain = this.config.get('CAVACH_API_DOMAIN'); const secret = this.config.get('JWT_SECRET'); - let ssiSubdomain = customerOnboardingData?.ssiSubdomain; let kycSubdomain = customerOnboardingData?.kycSubdomain; let ssiTenantUrl = this.getTenantUrl(ssiBaseDomain, ssiSubdomain); @@ -374,10 +371,43 @@ export class CustomerOnboardingService { throw new BadRequestException(['Customer onboarding is already done']); } let onboardingStatus; + let userDetail = await this.userRepository.findOne({ userId }); // Process each step for (const step of remainingSteps) { try { switch (step) { + case OnboardingStep.GIVE_DASHBOARD_ACCESS: { + Logger.log( + 'GIVE_DASHBOARD_ACCESS step started', + 'CustomerOnboardingService', + ); + userDetail = await this.userRepository.findOneUpdate( + { userId }, + { + $push: { + accessList: { + $each: [ + { + serviceType: SERVICE_TYPES.CAVACH_API, + access: SERVICES.CAVACH_API.ACCESS_TYPES.ALL, + expiryDate: null, + }, + { + serviceType: SERVICE_TYPES.SSI_API, + access: SERVICES.SSI_API.ACCESS_TYPES.ALL, + expiryDate: null, + }, + ], + }, + }, + }, + ); + Logger.debug( + 'GIVE_DASHBOARD_ACCESS step ends', + 'CustomerOnboardingService', + ); + break; + } case OnboardingStep.CREATE_TEAM_ROLE: { Logger.log( 'CREATE_TEAM_ROLE step started', @@ -457,7 +487,10 @@ export class CustomerOnboardingService { ssiTenantUrl, secret, ssiService?.whitelistedCors, - [SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDIT], + getAccessListForModule( + TokenModule.SUPER_ADMIN, + SERVICE_TYPES.SSI_API, + ), ); Logger.debug( 'CREDIT_SSI_SERVICE step ends', @@ -484,7 +517,7 @@ export class CustomerOnboardingService { const accessList = evaluateAccessPolicy( defaultAccessList, SERVICE_TYPES.SSI_API, - user.accessList, + userDetail.accessList, Context.idDashboard, ); if (!ssiServiceDetail) { @@ -543,7 +576,7 @@ export class CustomerOnboardingService { const accessList = evaluateAccessPolicy( defaultAccessList, SERVICE_TYPES.SSI_API, - user.accessList, + userDetail.accessList, Context.idDashboard, ); if (!ssiServiceDetail) { @@ -655,40 +688,6 @@ export class CustomerOnboardingService { ); break; } - - case OnboardingStep.GIVE_KYC_DASHBOARD_ACCESS: { - Logger.log( - 'GIVE_KYC_DASHBOARD_ACCESS step started', - 'CustomerOnboardingService', - ); - await this.userRepository.findOneUpdate( - { userId }, - { - $push: { - accessList: { - $each: [ - { - serviceType: SERVICE_TYPES.CAVACH_API, - access: SERVICES.CAVACH_API.ACCESS_TYPES.ALL, - expiryDate: null, - }, - { - serviceType: SERVICE_TYPES.SSI_API, - access: SERVICES.SSI_API.ACCESS_TYPES.ALL, - expiryDate: null, - }, - ], - }, - }, - }, - ); - Logger.debug( - 'GIVE_KYC_DASHBOARD_ACCESS step ends', - 'CustomerOnboardingService', - ); - break; - } - case OnboardingStep.CREDIT_KYC_SERVICE: { Logger.log( 'CREDIT_KYC_SERVICE step started', @@ -707,7 +706,10 @@ export class CustomerOnboardingService { kycTenantUrl, secret, kycService?.whitelistedCors, - [SERVICES.CAVACH_API.ACCESS_TYPES.WRITE_CREDIT], + getAccessListForModule( + TokenModule.SUPER_ADMIN, + SERVICE_TYPES.CAVACH_API, + ), ); Logger.debug( 'CREDIT_KYC_SERVICE step ends', @@ -732,7 +734,7 @@ export class CustomerOnboardingService { const accessList = evaluateAccessPolicy( defaultAccessList, SERVICE_TYPES.CAVACH_API, - user.accessList, + userDetail.accessList, Context.idDashboard, ); const kycServiceDetail = await redisClient.get(kycRedisKey); diff --git a/src/supported-service/services/iServiceList.ts b/src/supported-service/services/iServiceList.ts index 90d2d34a..89bea53f 100644 --- a/src/supported-service/services/iServiceList.ts +++ b/src/supported-service/services/iServiceList.ts @@ -54,18 +54,20 @@ export namespace SERVICES { export namespace SSI_API { export enum ACCESS_TYPES { ALL = 'ALL', - 'CREATE_DID' = 'CREATE_DID', - 'REGISTER_DID' = 'REGISTER_DID', - 'RESOLVE_DID' = 'RESOLVE_DID', - 'ISSUE_CREDENTIAL' = 'ISSUE_CREDENTIAL', - 'VERIFY_CREDENTIAL' = 'VERIFY_CREDENTIAL', - 'REGISTER_CREDENTIAL_STATUS' = 'REGISTER_CREDENTIAL_STATUS', - 'RESOLVE_CREDENTIAL_STATUS' = 'RESOLVE_CREDENTIAL_STATUS', - 'RESOLVE_SCHEMA' = 'RESOLVE_SCHEMA', - 'REGISTER_SCHEMA' = 'REGISTER_SCHEMA', - 'READ_USAGE' = 'READ_USAGE', - 'WRITE_CREDIT' = 'WRITE_CREDIT', - 'READ_CREDIT' = 'READ_CREDIT', + READ_DID = 'READ_DID', + WRITE_DID = 'WRITE_DID', + WRITE_CREDIT = 'WRITE_CREDIT', + READ_CREDIT = 'READ_CREDIT', + WRITE_SCHEMA = 'WRITE_SCHEMA', + READ_SCHEMA = 'READ_SCHEMA', + CHECK_LIVE_STATUS = 'CHECK_LIVE_STATUS', + READ_TX = 'READ_TX', + READ_CREDENTIAL = 'READ_CREDENTIAL', + VERIFY_CREDENTAIL = 'VERIFY_CREDENTAIL', + WRITE_CREDENTAL = 'WRITE_CREDENTAL', + READ_USAGE = 'READ_USAGE', + WRITE_PRESENTATION = 'WRITE_PRESENTATION', + VERIFY_PRESENTATION = 'VERIFY_PRESENTATION', } } @@ -92,7 +94,7 @@ export namespace SERVICES { WRITE_CREDIT = 'WRITE_CREDIT', READ_CREDIT = 'READ_CREDIT', CHECK_LIVE_STATUS = 'CHECK_LIVE_STATUS', - WRITE_AUTH='WRITE_AUTH', + WRITE_AUTH = 'WRITE_AUTH', } } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 0d4b80ac..1eb696bf 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -23,6 +23,7 @@ import { KYC_ACCESS_MATRIX, QUEST_ACCESS_MATRIX, SSI_ACCESS_MATRIX, + TokenModule, } from 'src/config/access-matrix'; export const existDir = (dirPath) => { @@ -180,7 +181,7 @@ export const REDIS_KEYS = { VERIFIER_PAGE_TOKEN: 'verifierPageToken:', }; export function getAccessListForModule( - module: 'DASHBOARD' | 'VERIFIER' | 'APP_AUTH', + module: TokenModule, serviceType: SERVICE_TYPES, ) { switch (serviceType) { @@ -205,10 +206,6 @@ export const evaluateAccessPolicy = ( if (!context) { return defaultAccessList; } - // will remove it once access lsit in fixed in ssi service - if (serviceType !== SERVICE_TYPES.CAVACH_API) { - return defaultAccessList; - } if (context === Context.idDashboard) { // No user access info → Return NO access if (!userAccessList?.length) { diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index c0a4a438..af4fffdc 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -280,6 +280,7 @@ export class WebpageConfigService { appId, SERVICE_TYPES.CAVACH_API, GRANT_TYPES.access_service_kyc, + TokenModule.VERIFIER, ); if ( !kycServiceDetail.dependentServices || @@ -294,6 +295,7 @@ export class WebpageConfigService { ssiServiceId, SERVICE_TYPES.SSI_API, GRANT_TYPES.access_service_ssi, + TokenModule.ID_SERVICE, ); // generate access tokens const [ssiAccessTokenDetail, kycAccessTokenDetail] = await Promise.all([ @@ -336,6 +338,7 @@ export class WebpageConfigService { appId: string, serviceType: SERVICE_TYPES, grantType: GRANT_TYPES, + tokenModule, ) { const cached = await redisClient.get(appId); if (cached) return JSON.parse(cached); @@ -345,10 +348,7 @@ export class WebpageConfigService { WEBPAGE_CONFIG_ERRORS.WEBPAGE_CONFIG_LINKED_APP_NOT_FOUND, ]); } - const defaultAccessList = getAccessListForModule( - TokenModule.VERIFIER, - serviceType, - ); + const defaultAccessList = getAccessListForModule(tokenModule, serviceType); const validateAccessList = evaluateAccessPolicy( defaultAccessList, serviceType, From 74a9073d63eac98fb316d6769595a42bc9b39ead Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 10 Dec 2025 14:26:22 +0530 Subject: [PATCH 52/55] fixed typo --- src/config/access-matrix.ts | 6 +++--- src/supported-service/services/iServiceList.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config/access-matrix.ts b/src/config/access-matrix.ts index 63177cc7..84fb945d 100644 --- a/src/config/access-matrix.ts +++ b/src/config/access-matrix.ts @@ -53,8 +53,8 @@ export const SSI_ACCESS_MATRIX = { SERVICES.SSI_API.ACCESS_TYPES.CHECK_LIVE_STATUS, SERVICES.SSI_API.ACCESS_TYPES.READ_TX, SERVICES.SSI_API.ACCESS_TYPES.READ_CREDENTIAL, - SERVICES.SSI_API.ACCESS_TYPES.VERIFY_CREDENTAIL, - SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDENTAL, + SERVICES.SSI_API.ACCESS_TYPES.VERIFY_CREDENTIAL, + SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDENTIAL, SERVICES.SSI_API.ACCESS_TYPES.READ_USAGE, SERVICES.SSI_API.ACCESS_TYPES.WRITE_PRESENTATION, SERVICES.SSI_API.ACCESS_TYPES.VERIFY_PRESENTATION, @@ -63,7 +63,7 @@ export const SSI_ACCESS_MATRIX = { [TokenModule.APP_AUTH]: [], [TokenModule.ID_SERVICE]: [ SERVICES.SSI_API.ACCESS_TYPES.READ_TX, - SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDENTAL, + SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDENTIAL, SERVICES.SSI_API.ACCESS_TYPES.VERIFY_PRESENTATION, ], [TokenModule.SUPER_ADMIN]: [SERVICES.SSI_API.ACCESS_TYPES.WRITE_CREDIT], diff --git a/src/supported-service/services/iServiceList.ts b/src/supported-service/services/iServiceList.ts index 89bea53f..ac61f81b 100644 --- a/src/supported-service/services/iServiceList.ts +++ b/src/supported-service/services/iServiceList.ts @@ -63,8 +63,8 @@ export namespace SERVICES { CHECK_LIVE_STATUS = 'CHECK_LIVE_STATUS', READ_TX = 'READ_TX', READ_CREDENTIAL = 'READ_CREDENTIAL', - VERIFY_CREDENTAIL = 'VERIFY_CREDENTAIL', - WRITE_CREDENTAL = 'WRITE_CREDENTAL', + VERIFY_CREDENTIAL = 'VERIFY_CREDENTIAL', + WRITE_CREDENTIAL = 'WRITE_CREDENTIAL', READ_USAGE = 'READ_USAGE', WRITE_PRESENTATION = 'WRITE_PRESENTATION', VERIFY_PRESENTATION = 'VERIFY_PRESENTATION', From 334eb07ff7835032bf74a578eb1eb36ad0932fe9 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 10 Dec 2025 15:11:17 +0530 Subject: [PATCH 53/55] added missing access --- src/supported-service/services/iServiceList.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/supported-service/services/iServiceList.ts b/src/supported-service/services/iServiceList.ts index ac61f81b..dedb4cb9 100644 --- a/src/supported-service/services/iServiceList.ts +++ b/src/supported-service/services/iServiceList.ts @@ -57,6 +57,7 @@ export namespace SERVICES { READ_DID = 'READ_DID', WRITE_DID = 'WRITE_DID', WRITE_CREDIT = 'WRITE_CREDIT', + VERIFY_DID_SIGNATURE = 'VERIFY_DID_SIGNATURE', READ_CREDIT = 'READ_CREDIT', WRITE_SCHEMA = 'WRITE_SCHEMA', READ_SCHEMA = 'READ_SCHEMA', From 50a087b691a0a48b784c6d4ecc69b7ff5132b281 Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 10 Dec 2025 17:45:17 +0530 Subject: [PATCH 54/55] storing hashed redis key --- src/app-auth/services/app-auth.service.ts | 14 ++++++++------ .../services/customer-onboarding.service.ts | 17 +++++++++++++---- src/utils/utils.ts | 6 +++++- .../services/webpage-config.service.ts | 3 ++- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index 788f7aa7..c86a1c15 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -34,7 +34,11 @@ import { WebPageConfigRepository } from 'src/webpage-config/repositories/webpage import { InjectModel } from '@nestjs/mongoose'; import { CustomerOnboarding } from 'src/customer-onboarding/schemas/customer-onboarding.schema'; import { Model } from 'mongoose'; -import { evaluateAccessPolicy, getAccessListForModule } from 'src/utils/utils'; +import { + evaluateAccessPolicy, + generateHash, + getAccessListForModule, +} from 'src/utils/utils'; import { TokenModule } from 'src/config/access-matrix'; import { redisClient } from 'src/utils/redis.provider'; import { TIME } from 'src/utils/time-constant'; @@ -546,10 +550,8 @@ export class AppAuthService { ); const updatedapp = await this.getAppResponse(app); // update redis - - const baseKey = appId; - const dashboardRedisKey = `${appId}_${Context.idDashboard}`; - + const baseKey = generateHash(appId); + const dashboardRedisKey = generateHash(`${appId}_${Context.idDashboard}`); const updatedFields = { whitelistedCors: updatedapp.whitelistedCors, env: updatedapp.env ?? APP_ENVIRONMENT.dev, @@ -732,7 +734,7 @@ export class AppAuthService { const serviceType = appDetail.services[0]?.id; // TODO: remove this later let grant_type = ''; let accessList = []; - const redisKey = appDetail.appId; + const redisKey = generateHash(appDetail.appId); const savedSession = await redisClient.get(redisKey); if (savedSession) { Logger.log('Using redis cached session', 'AppAuthService'); diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index 14cfaa4c..18f201f8 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -39,6 +39,7 @@ import { import { AppRepository } from 'src/app-auth/repositories/app.repository'; import { evaluateAccessPolicy, + generateHash, getAccessListForModule, sanitizeUrl, } from 'src/utils/utils'; @@ -354,8 +355,12 @@ 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}`; + let ssiRedisKey = generateHash( + `${customerOnboardingData?.ssiServiceId}_${Context.idDashboard}`, + ); + let kycRedisKey = generateHash( + `${customerOnboardingData?.kycServiceId}_${Context.idDashboard}`, + ); // Get remaining steps const lastStep = @@ -465,7 +470,9 @@ export class CustomerOnboardingService { 'CREATE_SSI_SERVICE step ends', 'CustomerOnboardingService', ); - ssiRedisKey = `${ssiService.appId}_${Context.idDashboard}`; + ssiRedisKey = generateHash( + `${ssiService.appId}_${Context.idDashboard}`, + ); break; } @@ -681,7 +688,9 @@ export class CustomerOnboardingService { onboardingUpdateData.kycSubdomain = kycService.subdomain; onboardingUpdateData.kycServiceId = kycService.appId; kycTenantUrl = this.getTenantUrl(cavachBaseDomain, kycSubdomain); - kycRedisKey = `${kycService?.appId}_${Context.idDashboard}`; + kycRedisKey = generateHash( + `${kycService?.appId}_${Context.idDashboard}`, + ); Logger.debug( 'CREATE_KYC_SERVICE step ends', 'CustomerOnboardingService', diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 1eb696bf..8ad42d1b 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -25,7 +25,7 @@ import { SSI_ACCESS_MATRIX, TokenModule, } from 'src/config/access-matrix'; - +import { createHash } from 'crypto'; export const existDir = (dirPath) => { if (!dirPath) throw new Error('Directory path undefined'); return fs.existsSync(dirPath); @@ -224,3 +224,7 @@ export const evaluateAccessPolicy = ( } return defaultAccessList; }; + +export function generateHash(input: string): string { + return createHash('sha256').update(input).digest('hex'); +} diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index af4fffdc..6b0c433e 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -26,6 +26,7 @@ import { redisClient } from 'src/utils/redis.provider'; import { TOKEN } from 'src/utils/time-constant'; import { evaluateAccessPolicy, + generateHash, getAccessListForModule, REDIS_KEYS, } from 'src/utils/utils'; @@ -265,7 +266,7 @@ export class WebpageConfigService { return { expiryDate }; } public async generateWebpageConfigTokens(id, appId) { - const redisKey = `${REDIS_KEYS.VERIFIER_PAGE_TOKEN}${id}`; + const redisKey = generateHash(`${REDIS_KEYS.VERIFIER_PAGE_TOKEN}${id}`); const cachedData = await redisClient.get(redisKey); if (cachedData) return JSON.parse(cachedData); const verifierConfig = await this.webPageConfigRepo.findAWebpageConfig({ From 1f647273857b0aec2fd5581452270da219c6884e Mon Sep 17 00:00:00 2001 From: varsha766 Date: Wed, 10 Dec 2025 18:32:35 +0530 Subject: [PATCH 55/55] hased the redis key --- src/app-auth/services/app-auth.service.ts | 9 +++++---- .../services/customer-onboarding.service.ts | 2 +- src/social-login/services/social-login.service.ts | 4 ++-- src/webpage-config/services/webpage-config.service.ts | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/app-auth/services/app-auth.service.ts b/src/app-auth/services/app-auth.service.ts index c86a1c15..23f4bf81 100644 --- a/src/app-auth/services/app-auth.service.ts +++ b/src/app-auth/services/app-auth.service.ts @@ -668,8 +668,8 @@ export class AppAuthService { appDetail = await this.appRepository.findOneAndDelete({ appId, userId }); // delete from redis await Promise.all([ - redisClient.del(appId), - redisClient.del(`${appId}_${Context.idDashboard}`), + redisClient.del(generateHash(appId)), + redisClient.del(generateHash(`${appId}_${Context.idDashboard}`)), ]); Logger.debug(`Redis cache cleaned for appId: ${appId}`); return { appId: appDetail.appId }; @@ -873,10 +873,11 @@ export class AppAuthService { session?, ): Promise<{ access_token; expiresIn; tokenType }> { const context = Context.idDashboard; - let sessionId = `${appId}_${context}_${session.userId}`; + let rawRedisKey = `${appId}_${context}_${session.userId}`; if (session && session.tenantId) { - sessionId = `${sessionId}_tenant`; + rawRedisKey = `${rawRedisKey}_tenant`; } + const sessionId = generateHash(rawRedisKey); const savedSession = await redisClient.get(sessionId); switch (grantType) { case GRANT_TYPES.access_service_ssi: diff --git a/src/customer-onboarding/services/customer-onboarding.service.ts b/src/customer-onboarding/services/customer-onboarding.service.ts index 18f201f8..effa7cac 100644 --- a/src/customer-onboarding/services/customer-onboarding.service.ts +++ b/src/customer-onboarding/services/customer-onboarding.service.ts @@ -258,7 +258,7 @@ export class CustomerOnboardingService { `Inside handleCreditService() to fund credit to the service with tenantUrl ${tenantUrl}`, 'CustomerOnboardingService', ); - const sessionId = `credit:${serviceInfo.appId}:${Date.now()}`; + const sessionId = generateHash(`credit:${serviceInfo.appId}:${Date.now()}`); const creditPayload = { serviceId: serviceInfo.appId, purpose: 'CreditRecharge', diff --git a/src/social-login/services/social-login.service.ts b/src/social-login/services/social-login.service.ts index 37187a0f..3c38efd3 100644 --- a/src/social-login/services/social-login.service.ts +++ b/src/social-login/services/social-login.service.ts @@ -10,7 +10,7 @@ import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; import { v4 as uuidv4 } from 'uuid'; import { Providers } from '../strategy/social.strategy'; -import { sanitizeUrl } from 'src/utils/utils'; +import { generateHash, sanitizeUrl } from 'src/utils/utils'; import { SupportedServiceList } from 'src/supported-service/services/service-list'; import { SERVICE_TYPES } from 'src/supported-service/services/iServiceList'; import { authenticator } from 'otplib'; @@ -334,7 +334,7 @@ export class SocialLoginService { isMfaRequired: boolean; refreshVersion: number; }> { - const sessionId = `${Date.now()}-${uuidv4()}`; + const sessionId = generateHash(`${Date.now()}-${uuidv4()}`); const role = user?.role || UserRole.ADMIN; const activeAuthenticators = user.authenticators?.filter( diff --git a/src/webpage-config/services/webpage-config.service.ts b/src/webpage-config/services/webpage-config.service.ts index 6b0c433e..70cb1c9f 100644 --- a/src/webpage-config/services/webpage-config.service.ts +++ b/src/webpage-config/services/webpage-config.service.ts @@ -341,7 +341,7 @@ export class WebpageConfigService { grantType: GRANT_TYPES, tokenModule, ) { - const cached = await redisClient.get(appId); + const cached = await redisClient.get(generateHash(appId)); if (cached) return JSON.parse(cached); const serviceDetail = await this.appRepository.findOne({ appId }); if (!serviceDetail) {