Skip to content

Commit 0012787

Browse files
authored
Merge pull request #1402 from credebl/fix/create-account-problem
Fix: create account with large token size
2 parents 92238cd + 820863a commit 0012787

File tree

11 files changed

+97
-129
lines changed

11 files changed

+97
-129
lines changed

.env.demo

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

.env.sample

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

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

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

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

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

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

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

1617
dotenv.config();
1718

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

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

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

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

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

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

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

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

565565
return res.status(HttpStatus.OK).json(finalResponse);

apps/organization/src/organization.service.ts

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

apps/user/interfaces/user.interface.ts

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

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

apps/user/repositories/user.repository.ts

Lines changed: 18 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
/* eslint-disable camelcase */
12
/* eslint-disable prefer-destructuring */
23

34
import {
45
IOrgUsers,
56
ISendVerificationEmail,
67
ISession,
78
IShareUserCertificate,
8-
IUpdateAccountDetails,
99
IUserDeletedActivity,
1010
IUserInformation,
1111
IUsersProfile,
@@ -17,7 +17,6 @@ import {
1717
UserRoleMapping
1818
} from '../interfaces/user.interface';
1919
import { Injectable, InternalServerErrorException, Logger, NotFoundException } from '@nestjs/common';
20-
// eslint-disable-next-line camelcase
2120
import {
2221
Prisma,
2322
RecordType,
@@ -682,6 +681,7 @@ export class UserRepository {
682681
const { sessionToken, userId, expires, refreshToken, accountId, sessionType } = tokenDetails;
683682
const sessionResponse = await this.prisma.session.create({
684683
data: {
684+
id: tokenDetails.id,
685685
sessionToken,
686686
expires,
687687
userId,
@@ -725,67 +725,13 @@ export class UserRepository {
725725
}
726726
}
727727

728-
async fetchAccountByRefreshToken(userId: string, refreshToken: string): Promise<account> {
729-
try {
730-
return await this.prisma.account.findUnique({
731-
where: {
732-
userId,
733-
refreshToken
734-
}
735-
});
736-
} catch (error) {
737-
this.logger.error(`Error in getting account details: ${error.message} `);
738-
throw error;
739-
}
740-
}
741-
742-
async updateAccountDetailsById(accountDetails: IUpdateAccountDetails): Promise<account> {
743-
try {
744-
return await this.prisma.account.update({
745-
where: {
746-
id: accountDetails.accountId
747-
},
748-
data: {
749-
accessToken: accountDetails.accessToken,
750-
refreshToken: accountDetails.refreshToken,
751-
expiresAt: accountDetails.expiresAt
752-
}
753-
});
754-
} catch (error) {
755-
this.logger.error(`Error in getting account details: ${error.message} `);
756-
throw error;
757-
}
758-
}
759-
760-
async updateAccountDetails(accountDetails: ISession): Promise<account> {
761-
try {
762-
const userAccountDetails = await this.prisma.account.update({
763-
where: {
764-
userId: accountDetails.userId
765-
},
766-
data: {
767-
accessToken: accountDetails.sessionToken,
768-
refreshToken: accountDetails.refreshToken,
769-
expiresAt: accountDetails.expires
770-
}
771-
});
772-
return userAccountDetails;
773-
} catch (error) {
774-
this.logger.error(`Error in updateAccountDetails: ${error.message}`);
775-
throw error;
776-
}
777-
}
778-
779728
async addAccountDetails(accountDetails: ISession): Promise<account> {
780729
try {
781730
const userAccountDetails = await this.prisma.account.create({
782731
data: {
783732
userId: accountDetails.userId,
784733
provider: ProviderType.KEYCLOAK,
785734
providerAccountId: accountDetails.keycloakUserId,
786-
accessToken: accountDetails.sessionToken,
787-
refreshToken: accountDetails.refreshToken,
788-
expiresAt: accountDetails.expires,
789735
tokenType: accountDetails.type
790736
}
791737
});
@@ -1014,11 +960,11 @@ export class UserRepository {
1014960
}
1015961
}
1016962

1017-
async deleteSessionRecordByRefreshToken(refreshToken: string): Promise<session> {
963+
async deleteSession(sessionId: string): Promise<session> {
1018964
try {
1019965
const userSession = await this.prisma.session.delete({
1020966
where: {
1021-
refreshToken
967+
id: sessionId
1022968
}
1023969
});
1024970
return userSession;
@@ -1027,4 +973,18 @@ export class UserRepository {
1027973
throw error;
1028974
}
1029975
}
976+
977+
async fetchSessionByRefreshToken(refreshToken: string): Promise<session> {
978+
try {
979+
const sessionDetails = await this.prisma.session.findFirst({
980+
where: {
981+
refreshToken
982+
}
983+
});
984+
return sessionDetails;
985+
} catch (error) {
986+
this.logger.error(`Error in fetching session details::${error.message}`);
987+
throw error;
988+
}
989+
}
1030990
}

apps/user/src/user.service.ts

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,7 @@ export class UserService {
449449
try {
450450
this.validateEmail(email.toLowerCase());
451451
const userData = await this.userRepository.checkUserExist(email.toLowerCase());
452-
453452
const userSessionDetails = await this.userRepository.fetchUserSessions(userData?.id);
454-
455453
if (Number(process.env.SESSIONS_LIMIT) <= userSessionDetails?.length) {
456454
throw new BadRequestException(ResponseMessages.user.error.sessionLimitReached);
457455
}
@@ -475,8 +473,10 @@ export class UserService {
475473
} else {
476474
const decryptedPassword = await this.commonService.decryptPassword(password);
477475
const tokenDetails = await this.generateToken(email.toLowerCase(), decryptedPassword, userData);
478-
476+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
477+
const decodedToken: any = jwt.decode(tokenDetails?.access_token);
479478
const sessionData = {
479+
id: decodedToken.sid,
480480
sessionToken: tokenDetails?.access_token,
481481
userId: userData?.id,
482482
expires: tokenDetails?.expires_in,
@@ -489,10 +489,7 @@ export class UserService {
489489
let accountData;
490490
if (null === fetchAccountDetails) {
491491
accountData = {
492-
sessionToken: tokenDetails?.access_token,
493492
userId: userData?.id,
494-
expires: tokenDetails?.expires_in,
495-
refreshToken: tokenDetails?.refresh_token,
496493
keycloakUserId: userData?.keycloakUserId,
497494
type: TokenType.BEARER_TOKEN
498495
};
@@ -502,17 +499,8 @@ export class UserService {
502499
addSessionDetails = await this.userRepository.createSession(finalSessionData);
503500
});
504501
} else {
505-
accountData = {
506-
sessionToken: tokenDetails?.access_token,
507-
userId: userData?.id,
508-
expires: tokenDetails?.expires_in,
509-
refreshToken: tokenDetails?.refresh_token
510-
};
511-
512-
await this.userRepository.updateAccountDetails(accountData).then(async (response) => {
513-
const finalSessionData = { ...sessionData, accountId: response.id };
514-
addSessionDetails = await this.userRepository.createSession(finalSessionData);
515-
});
502+
const finalSessionData = { ...sessionData, accountId: fetchAccountDetails.id };
503+
addSessionDetails = await this.userRepository.createSession(finalSessionData);
516504
}
517505

518506
const finalResponse = {
@@ -554,26 +542,18 @@ export class UserService {
554542
);
555543
this.logger.debug(`tokenResponse::::${JSON.stringify(tokenResponse)}`);
556544
// Fetch the details from account table based on userid and refresh token
557-
const userAccountDetails = await this.userRepository.fetchAccountByRefreshToken(
558-
userByKeycloakId?.['id'],
559-
refreshToken
560-
);
545+
const userAccountDetails = await this.userRepository.checkAccountDetails(userByKeycloakId?.['id']);
561546
// Update the account details with latest access token, refresh token and exp date
562547
if (!userAccountDetails) {
563548
throw new NotFoundException(ResponseMessages.user.error.userAccountNotFound);
564549
}
565-
const updateAccountDetails: IUpdateAccountDetails = {
566-
accessToken: tokenResponse.access_token,
567-
refreshToken: tokenResponse.refresh_token,
568-
expiresAt: tokenResponse.expires_in,
569-
accountId: userAccountDetails.id
570-
};
571-
const updateAccountDetailsResponse = await this.userRepository.updateAccountDetailsById(updateAccountDetails);
572-
// Delete the preveious session record and create new one
573-
if (!updateAccountDetailsResponse) {
574-
throw new InternalServerErrorException(ResponseMessages.user.error.errorInUpdateAccountDetails);
550+
// Fetch session details
551+
const sessionDetails = await this.userRepository.fetchSessionByRefreshToken(refreshToken);
552+
if (!sessionDetails) {
553+
throw new NotFoundException(ResponseMessages.user.error.userSeesionNotFound);
575554
}
576-
const deletePreviousSession = await this.userRepository.deleteSessionRecordByRefreshToken(refreshToken);
555+
// Delete previous session
556+
const deletePreviousSession = await this.userRepository.deleteSession(sessionDetails.id);
577557
if (!deletePreviousSession) {
578558
throw new InternalServerErrorException(ResponseMessages.user.error.errorInDeleteSession);
579559
}
@@ -583,7 +563,7 @@ export class UserService {
583563
expires: tokenResponse.expires_in,
584564
refreshToken: tokenResponse.refresh_token,
585565
sessionType: SessionType.USER_SESSION,
586-
accountId: updateAccountDetailsResponse.id
566+
accountId: userAccountDetails.id
587567
};
588568
const addSessionDetails = await this.userRepository.createSession(sessionData);
589569
if (!addSessionDetails) {

0 commit comments

Comments
 (0)