Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: keycloak clientId and client secret manage on the user table #780

Merged
merged 10 commits into from
Jun 18, 2024
8 changes: 4 additions & 4 deletions apps/api-gateway/src/organization/organization.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ export class OrganizationController {
@ApiResponse({ status: HttpStatus.OK, description: 'Success', type: ApiResponseDto })
@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth()
async getOrgRoles(@Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @Res() res: Response): Promise<Response> {
async getOrgRoles(@Param('orgId', new ParseUUIDPipe({exceptionFactory: (): Error => { throw new BadRequestException(ResponseMessages.organisation.error.invalidOrgId); }})) orgId: string, @User() user: user, @Res() res: Response): Promise<Response> {

const orgRoles = await this.organizationService.getOrgRoles(orgId.trim());
const orgRoles = await this.organizationService.getOrgRoles(orgId.trim(), user);

const finalResponse: IResponse = {
statusCode: HttpStatus.OK,
Expand Down Expand Up @@ -564,9 +564,9 @@ export class OrganizationController {
@ApiBearerAuth()
@ApiExcludeEndpoint()
@UseGuards(AuthGuard('jwt'))
async deleteOrgClientCredentials(@Param('orgId') orgId: string, @Res() res: Response): Promise<Response> {
async deleteOrgClientCredentials(@Param('orgId') orgId: string, @Res() res: Response, @User() user: user): Promise<Response> {

const deleteResponse = await this.organizationService.deleteOrgClientCredentials(orgId);
const deleteResponse = await this.organizationService.deleteOrgClientCredentials(orgId, user);

const finalResponse: IResponse = {
statusCode: HttpStatus.ACCEPTED,
Expand Down
9 changes: 5 additions & 4 deletions apps/api-gateway/src/organization/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ export class OrganizationService extends BaseService {
* @returns get organization roles
*/

async getOrgRoles(orgId: string): Promise<IClientRoles[]> {
const payload = {orgId};
async getOrgRoles(orgId: string, user: user): Promise<IClientRoles[]> {
const payload = {orgId, user};
return this.sendNatsMessage(this.serviceProxy, 'get-org-roles', payload);
}

Expand Down Expand Up @@ -217,9 +217,10 @@ export class OrganizationService extends BaseService {
}

async deleteOrgClientCredentials(
orgId: string
orgId: string,
user: user
): Promise<string> {
const payload = { orgId };
const payload = { orgId, user };

return this.sendNatsMessage(this.serviceProxy, 'delete-org-client-credentials', payload);
}
Expand Down
10 changes: 9 additions & 1 deletion apps/api-gateway/src/user/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IsEmail, IsNotEmpty, MaxLength } from 'class-validator';
import { IsEmail, IsNotEmpty, IsString, MaxLength } from 'class-validator';
import { toLowerCase, trim } from '@credebl/common/cast.helper';

import { ApiProperty } from '@nestjs/swagger';
Expand All @@ -12,4 +12,12 @@ export class UserEmailVerificationDto {
@MaxLength(256, { message: 'Email must be at most 256 character.' })
@IsEmail({}, { message: 'Please provide a valid email' })
email: string;

@ApiProperty({ example: 'xxxx-xxxx-xxxx' })
@IsString({ message: 'clientId should be string' })
clientId: string;

@ApiProperty({ example: 'xxxx-xxxxx-xxxxx' })
@IsString({ message: 'clientSecret should be string' })
clientSecret: string;
}
15 changes: 14 additions & 1 deletion apps/organization/repositories/organization.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { ConflictException, Injectable, Logger, NotFoundException } from '@nestjs/common';
// eslint-disable-next-line camelcase
import { Prisma, agent_invitations, org_agents, org_invitations, user_org_roles } from '@prisma/client';
import { Prisma, agent_invitations, org_agents, org_invitations, user, user_org_roles } from '@prisma/client';

import { CreateOrganizationDto } from '../dtos/create-organization.dto';
import { IDidDetails, IDidList, IGetOrgById, IGetOrganization, IPrimaryDidDetails, IUpdateOrganization, OrgInvitation } from '../interfaces/organization.interface';
Expand Down Expand Up @@ -385,6 +385,19 @@ export class OrganizationRepository {
}
}

async getUser(id: string): Promise<user> {
try {
const getUserById = await this.prisma.user.findUnique({
where:{
id
}});
return getUserById;
} catch (error) {
this.logger.error(`error: ${JSON.stringify(error)}`);
throw new error;
}
}

async getOrganization(queryObject: object): Promise<IGetOrgById> {
try {
return this.prisma.organisation.findFirst({
Expand Down
8 changes: 4 additions & 4 deletions apps/organization/src/organization.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ export class OrganizationController {
*/

@MessagePattern({ cmd: 'get-org-roles' })
async getOrgRoles(payload: {orgId: string}): Promise<IClientRoles[]> {
return this.organizationService.getOrgRoles(payload.orgId);
async getOrgRoles(payload: {orgId: string, user: user}): Promise<IClientRoles[]> {
return this.organizationService.getOrgRoles(payload.orgId, payload.user);
}

@MessagePattern({ cmd: 'register-orgs-users-map' })
Expand Down Expand Up @@ -235,8 +235,8 @@ export class OrganizationController {
}

@MessagePattern({ cmd: 'delete-org-client-credentials' })
async deleteOrganizationCredentials(payload: { orgId: string }): Promise<string> {
return this.organizationService.deleteClientCredentials(payload.orgId);
async deleteOrganizationCredentials(payload: { orgId: string, user: user }): Promise<string> {
return this.organizationService.deleteClientCredentials(payload.orgId, payload.user);
}

@MessagePattern({ cmd: 'delete-organization-invitation' })
Expand Down
29 changes: 18 additions & 11 deletions apps/organization/src/organization.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,9 @@ export class OrganizationService {
let generatedClientSecret = '';

if (organizationDetails.idpId) {
const token = await this.clientRegistrationService.getManagementToken();

const userDetails = await this.organizationRepository.getUser(userId);
const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret);

generatedClientSecret = await this.clientRegistrationService.generateClientSecret(
organizationDetails.idpId,
Expand Down Expand Up @@ -312,7 +314,8 @@ export class OrganizationService {
userId: string,
shouldUpdateRole: boolean
): Promise<IOrgCredentials> {
const token = await this.clientRegistrationService.getManagementToken();
const userDetails = await this.organizationRepository.getUser(userId);
const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret);
const orgDetails = await this.clientRegistrationService.createClient(orgName, orgId, token);

const orgRolesList = [OrgRoles.OWNER, OrgRoles.ADMIN, OrgRoles.ISSUER, OrgRoles.VERIFIER, OrgRoles.MEMBER];
Expand Down Expand Up @@ -361,8 +364,8 @@ export class OrganizationService {
return orgDetails;
}

async deleteClientCredentials(orgId: string): Promise<string> {
const token = await this.clientRegistrationService.getManagementToken();
async deleteClientCredentials(orgId: string, user: user): Promise<string> {
const token = await this.clientRegistrationService.getManagementToken(user.clientId, user.clientSecret);

const organizationDetails = await this.organizationRepository.getOrganizationDetails(orgId);

Expand Down Expand Up @@ -706,7 +709,7 @@ export class OrganizationService {
* @returns organization roles
*/

async getOrgRoles(orgId: string): Promise<IClientRoles[]> {
async getOrgRoles(orgId: string, user: user): Promise<IClientRoles[]> {
try {
if (!orgId) {
throw new BadRequestException(ResponseMessages.organisation.error.orgIdIsRequired);
Expand All @@ -722,7 +725,7 @@ export class OrganizationService {
return this.orgRoleService.getOrgRoles();
}

const token = await this.clientRegistrationService.getManagementToken();
const token = await this.clientRegistrationService.getManagementToken(user.clientId, user.clientSecret);

return this.clientRegistrationService.getAllClientRoles(organizationDetails.idpId, token);
} catch (error) {
Expand Down Expand Up @@ -814,7 +817,8 @@ export class OrganizationService {
): Promise<void> {
const { invitations, orgId } = bulkInvitationDto;

const token = await this.clientRegistrationService.getManagementToken();
const userDetails = await this.organizationRepository.getUser(userId);
const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret);
const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token);
const orgRoles = await this.orgRoleService.getOrgRoles();

Expand Down Expand Up @@ -1034,7 +1038,8 @@ export class OrganizationService {
orgId: string,
status: string
): Promise<void> {
const token = await this.clientRegistrationService.getManagementToken();
const userDetails = await this.organizationRepository.getUser(userId);
const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret);
const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token);

const orgRoles = await this.orgRoleService.getOrgRolesByIds(invitation.orgRoles);
Expand Down Expand Up @@ -1139,7 +1144,8 @@ export class OrganizationService {
userId: string,
orgId: string
): Promise<boolean> {
const token = await this.clientRegistrationService.getManagementToken();
const userDetails = await this.organizationRepository.getUser(userId);
const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret);
const clientRolesList = await this.clientRegistrationService.getAllClientRoles(
idpId,
token
Expand Down Expand Up @@ -1431,7 +1437,7 @@ export class OrganizationService {
try {
// Fetch token and organization details in parallel
const [token, organizationDetails] = await Promise.all([
this.clientRegistrationService.getManagementToken(),
this.clientRegistrationService.getManagementToken(user.clientId, user.clientSecret),
this.organizationRepository.getOrganizationDetails(orgId)
]);

Expand Down Expand Up @@ -1638,7 +1644,8 @@ export class OrganizationService {

const usersToRegisterList = userOrgRoles.filter(userOrgRole => null !== userOrgRole.user.keycloakUserId);

const token = await this.clientRegistrationService.getManagementToken();
const userDetails = await this.organizationRepository.getUser(orgObj.ownerId);
const token = await this.clientRegistrationService.getManagementToken(userDetails.clientId, userDetails.clientSecret);
const clientRolesList = await this.clientRegistrationService.getAllClientRoles(idpId, token);

const deletedUserDetails: string[] = [];
Expand Down
2 changes: 2 additions & 0 deletions apps/user/interfaces/user.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ interface IUserOrgRole {
export interface ISendVerificationEmail {
email: string;
username?: string;
clientId?: string;
clientSecret?: string;
}

export interface IUserInformation {
Expand Down
2 changes: 2 additions & 0 deletions apps/user/repositories/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export class UserRepository {
username: userEmailVerification.username,
email: userEmailVerification.email,
verificationCode: verifyCode.toString(),
clientId: userEmailVerification.clientId,
clientSecret: userEmailVerification.clientSecret,
publicProfile: true
},
update: {
Expand Down
29 changes: 20 additions & 9 deletions apps/user/src/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import { ISendVerificationEmail, ISignInUser, IVerifyUserEmail, IUserInvitations
import { AddPasskeyDetailsDto } from 'apps/api-gateway/src/user/dto/add-user.dto';
import { URLUserResetPasswordTemplate } from '../templates/reset-password-template';
import { toNumber } from '@credebl/common/cast.helper';
import * as jwt from 'jsonwebtoken';

@Injectable()
export class UserService {
Expand Down Expand Up @@ -112,8 +113,16 @@ export class UserService {
userEmailVerification.username = uniqueUsername;
const resUser = await this.userRepository.createUser(userEmailVerification, verifyCode);

const token = await this.clientRegistrationService.getManagementToken(resUser.clientId, resUser.clientSecret);
const getClientData = await this.clientRegistrationService.getClientRedirectUrl(resUser.clientId, token);
try {
await this.sendEmailForVerification(email, resUser.verificationCode);
const [redirectUrl] = getClientData[0]?.redirectUris || [];

if (!redirectUrl) {
throw new NotFoundException(ResponseMessages.user.error.redirectUrlNotFound);
}

await this.sendEmailForVerification(email, resUser.verificationCode, redirectUrl, resUser.clientId);
} catch (error) {
throw new InternalServerErrorException(ResponseMessages.user.error.emailSend);
}
Expand Down Expand Up @@ -155,7 +164,7 @@ export class UserService {
* @returns
*/

async sendEmailForVerification(email: string, verificationCode: string): Promise<boolean> {
async sendEmailForVerification(email: string, verificationCode: string, redirectUrl: string, clientId: string): Promise<boolean> {
try {
const platformConfigData = await this.prisma.platform_config.findMany();

Expand All @@ -165,7 +174,7 @@ export class UserService {
emailData.emailTo = email;
emailData.emailSubject = `[${process.env.PLATFORM_NAME}] Verify your email to activate your account`;

emailData.emailHtml = await urlEmailTemplate.getUserURLTemplate(email, verificationCode);
emailData.emailHtml = await urlEmailTemplate.getUserURLTemplate(email, verificationCode, redirectUrl, clientId);
const isEmailSent = await sendEmail(emailData);
if (isEmailSent) {
return isEmailSent;
Expand Down Expand Up @@ -240,8 +249,7 @@ export class UserService {

let keycloakDetails = null;

const token = await this.clientRegistrationService.getManagementToken();

const token = await this.clientRegistrationService.getManagementToken(checkUserDetails.clientId, checkUserDetails.clientSecret);
if (userInfo.isPasskey) {
const resUser = await this.userRepository.addUserPassword(email.toLowerCase(), userInfo.password);
const userDetails = await this.userRepository.getUserDetails(email.toLowerCase());
Expand Down Expand Up @@ -380,7 +388,9 @@ export class UserService {

try {
try {
const tokenResponse = await this.clientRegistrationService.getAccessToken(refreshToken);
const data = jwt.decode(refreshToken);
const userByKeycloakId = await this.userRepository.getUserByKeycloakId(data?.sub);
const tokenResponse = await this.clientRegistrationService.getAccessToken(refreshToken, userByKeycloakId?.['clientId'], userByKeycloakId?.['clientSecret']);
return tokenResponse;
} catch (error) {
throw new BadRequestException(ResponseMessages.user.error.invalidRefreshToken);
Expand Down Expand Up @@ -504,7 +514,8 @@ export class UserService {
const decryptedPassword = await this.commonService.decryptPassword(password);
try {

const authToken = await this.clientRegistrationService.getManagementToken();

const authToken = await this.clientRegistrationService.getManagementToken(userData.clientId, userData.clientSecret);
userData.password = decryptedPassword;
if (userData.keycloakUserId) {
await this.clientRegistrationService.resetPasswordOfUser(userData, process.env.KEYCLOAK_REALM, authToken);
Expand Down Expand Up @@ -567,7 +578,7 @@ export class UserService {
userData.password = newDecryptedPassword;
try {
let keycloakDetails = null;
const token = await this.clientRegistrationService.getManagementToken();
const token = await this.clientRegistrationService.getManagementToken(userData.clientId, userData.clientSecret);

if (userData.keycloakUserId) {

Expand Down Expand Up @@ -605,7 +616,7 @@ export class UserService {
if (userData.keycloakUserId) {

try {
const tokenResponse = await this.clientRegistrationService.getUserToken(email, password);
const tokenResponse = await this.clientRegistrationService.getUserToken(email, password, userData.clientId, userData.clientSecret);
tokenResponse.isRegisteredToSupabase = false;
return tokenResponse;
} catch (error) {
Expand Down
17 changes: 9 additions & 8 deletions apps/user/templates/user-email-template.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as url from 'url';
export class URLUserEmailTemplate {
public getUserURLTemplate(email: string, verificationCode: string): string {
const endpoint = `${process.env.FRONT_END_URL}`;
public getUserURLTemplate(email: string, verificationCode: string, redirectUrl: string, clientId: string): string {

const apiUrl = url.parse(
`${endpoint}/verify-email-success?verificationCode=${verificationCode}&email=${encodeURIComponent(email)}`
const apiUrl = new URL(
clientId === process.env.KEYCLOAK_MANAGEMENT_CLIENT_ID ? '/verify-email-success' : '',
redirectUrl
);

const validUrl = apiUrl.href.replace('/:', ':');

apiUrl.searchParams.append('verificationCode', verificationCode);
apiUrl.searchParams.append('email', encodeURIComponent(email));

const validUrl = apiUrl.href;

try {
return `<!DOCTYPE html>
Expand Down Expand Up @@ -59,7 +61,6 @@ export class URLUserEmailTemplate {
</div>
</body>
</html>`;

} catch (error) {}
}
}
Loading