From 8d072affe5eba812ac89fcfaba1ec07a19d4c865 Mon Sep 17 00:00:00 2001 From: Dheeraj Gavini Date: Wed, 30 Oct 2024 10:36:49 -0400 Subject: [PATCH 1/2] Added necessary changes to make sure CTIMS editor works without keycloak --- apps/api/.env.example | 17 ++++++ .../api/src/app/auth/KeycloakPasswordGuard.ts | 17 ++++++ .../src/app/auth/keycloak-disabled-config.ts | 54 +++++++++++++++++++ .../app/auth/keycloak-password.strategy.ts | 39 ++++++++++++++ apps/api/src/app/trial/trial-group.service.ts | 9 ++++ apps/api/src/app/user/user.service.ts | 23 ++++++++ 6 files changed, 159 insertions(+) create mode 100644 apps/api/src/app/auth/keycloak-disabled-config.ts diff --git a/apps/api/.env.example b/apps/api/.env.example index 3580b162..5aea0c30 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -15,6 +15,23 @@ KEYCLOAK_REALM=ctims # The realm needs to have client authentication enabled to see the Credentials tab. KEYCLOAK_CLIENT_SECRET= +# To disable key cloak change the KEYCLOAK_DISABLED value to true and use the below login details to login. +KEYCLOAK_DISABLED=false +KD_TRIALGROUP=default +KD_TRIALGROUP_ADMIN=default-admin +KD_USERNAME=ctims +KD_PASSWORD=ctims +KD_USERNAME_ADMIN=ctims-admin +KD_PASSWORD_ADMIN=ctims-admin +KD_EMAIL=ctims@uhn.ca +KD_EMAIL_ADMIN=ctimsadmin@uhn.ca +KD_FULLNAME=Ctims User +KD_FULLNAME_ADMIN=Ctims Admin +KD_KEYCLOAK_ID=1234 +KD_KEYCLOAK_ID_ADMIN=5678 +KD_TOKEN_ADMIN="env-based-token-admin" +KD_TOKEN_USER="env-based-token-user" + # A client with service account enabled. # It does not need to be a separate client. If your above client already has service account enabled, use the same ID here. KEYCLOAK_ADMIN_CLIENT_ID=ctims-admin diff --git a/apps/api/src/app/auth/KeycloakPasswordGuard.ts b/apps/api/src/app/auth/KeycloakPasswordGuard.ts index 06789246..5d3efc12 100644 --- a/apps/api/src/app/auth/KeycloakPasswordGuard.ts +++ b/apps/api/src/app/auth/KeycloakPasswordGuard.ts @@ -1,14 +1,31 @@ import { ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; import { AuthGuard as PassportAuthGuard } from '@nestjs/passport'; +import { keycloak_disabled_comfig } from "./keycloak-disabled-config"; @Injectable() export class KeycloakPasswordGuard extends PassportAuthGuard('keycloak') { + private keycloakDisabled: boolean; + private envTokenAdmin: string; + + constructor() { + super(); + this.keycloakDisabled = process.env.KEYCLOAK_DISABLED === 'true'; + this.envTokenAdmin = process.env.KD_TOKEN_ADMIN; + } getRequest(context: ExecutionContext) { const req = context.switchToHttp().getRequest(); return req; } handleRequest(err: any, user: any, info: any, context: ExecutionContext) { + if (this.keycloakDisabled) { + const req = context.switchToHttp().getRequest(); + const authHeader = req.headers.authorization; + const token = authHeader ? authHeader.split(' ')[1] : null; + const isAdmin = (token == this.envTokenAdmin) ? "admin" : "notadmin"; + const user = keycloak_disabled_comfig(isAdmin); + return user; + } if (err || !user) { console.log('Error:', err); console.log('Info:', info); diff --git a/apps/api/src/app/auth/keycloak-disabled-config.ts b/apps/api/src/app/auth/keycloak-disabled-config.ts new file mode 100644 index 00000000..1aa36d8b --- /dev/null +++ b/apps/api/src/app/auth/keycloak-disabled-config.ts @@ -0,0 +1,54 @@ +function keycloak_disabled_comfig(role: string) { + + const envUsername = adminStatus(role) ? process.env.KD_USERNAME_ADMIN : process.env.KD_USERNAME; + const envTrialGroup = adminStatus(role) ? process.env.KD_TRIALGROUP_ADMIN : process.env.KD_TRIALGROUP; + const envKeycloakId = adminStatus(role) ? process.env.KD_KEYCLOAK_ID_ADMIN : process.env.KD_KEYCLOAK_ID; + const envEmail = adminStatus(role) ? process.env.KD_EMAIL_ADMIN : process.env.KD_EMAIL + const envFullName = adminStatus(role) ? process.env.KD_FULLNAME_ADMIN : process.env.KD_FULLNAME; + const roles = envTrialGroup; + const userId = adminStatus(role) ? 100 : 99; + + const user = { + id: userId, + email: envEmail, + name: envFullName, + username: envUsername, + first_name: envUsername, + email_verified: true, + last_name: '', + keycloak_id: envKeycloakId, + createdAt: "", + updatedAt: "", + roles: [roles] + } + return user; +} +function keycloak_disabled_fetch_by_KeycloakId(role: string) { + + const envUsername = adminStatus(role) ? process.env.KD_USERNAME_ADMIN : process.env.KD_USERNAME; + const envTrialGroup = adminStatus(role) ? process.env.KD_TRIALGROUP_ADMIN : process.env.KD_TRIALGROUP; + const envKeycloakId = adminStatus(role) ? process.env.KD_KEYCLOAK_ID_ADMIN : process.env.KD_KEYCLOAK_ID; + const envEmail = adminStatus(role) ? process.env.KD_EMAIL_ADMIN : process.env.KD_EMAIL + const envFullName = adminStatus(role) ? process.env.KD_FULLNAME_ADMIN : process.env.KD_FULLNAME; + const roles = envTrialGroup; + + const user = { + id: envKeycloakId, + email: envEmail, + name: envFullName, + username: envUsername, + first_name: envUsername, + email_verified: true, + last_name: '', + keycloak_id: envKeycloakId, + createdAt: "", + updatedAt: "", + roles: [roles] + } + return user; +} +function adminStatus(role: string){ + return (role=="admin") ? true : false; +} +export { keycloak_disabled_comfig, keycloak_disabled_fetch_by_KeycloakId }; + diff --git a/apps/api/src/app/auth/keycloak-password.strategy.ts b/apps/api/src/app/auth/keycloak-password.strategy.ts index 045055ad..343c16d2 100644 --- a/apps/api/src/app/auth/keycloak-password.strategy.ts +++ b/apps/api/src/app/auth/keycloak-password.strategy.ts @@ -21,6 +21,15 @@ const keycloakConfig = { "confidential-port": 0 } +const isKeycloakDisabled = process.env.KEYCLOAK_DISABLED === 'true'; +const envUsername = process.env.KD_USERNAME; +const envPassword = process.env.KD_PASSWORD; +const envUsernameAdmin = process.env.KD_USERNAME_ADMIN; +const envPasswordAdmin = process.env.KD_PASSWORD_ADMIN; +const envKeycloakId = process.env.KD_KEYCLOAK_ID; +const envKeycloakIdAdmin = process.env.KD_KEYCLOAK_ID_ADMIN; +const envTokenUser= process.env.KD_TOKEN_USER; +const envTokenAdmin= process.env.KD_TOKEN_ADMIN; @Injectable() export class KeycloakPasswordStrategy extends PassportStrategy(KeycloakBearerStrategy, 'keycloak') implements OnModuleInit { @@ -39,6 +48,10 @@ export class KeycloakPasswordStrategy extends PassportStrategy(KeycloakBearerStr } async login(username: string, password: string): Promise { + if (isKeycloakDisabled==true) { + return this.loginWithEnvCredentials(username, password); + } + else{ let grant: Grant = null; try { grant = await this.keycloak.grantManager.obtainDirectly(username, password); @@ -90,9 +103,34 @@ export class KeycloakPasswordStrategy extends PassportStrategy(KeycloakBearerStr accessToken: keycloakToken['token'], user }; + } + } + private async loginWithEnvCredentials(username: string, password: string): Promise { + if (username === envUsername && password === envPassword) { + let user = await this.userService.findUserBySub(envKeycloakId) + if(!user){ + user = await this.userService.createUserWithoutKeycloak("notAdmin") + } + const roles= ['default'] + user['roles'] = roles; + return { accessToken: envTokenUser, user }; + } + else if(username === envUsernameAdmin && password === envPasswordAdmin){ + let user = await this.userService.findUserBySub(envKeycloakIdAdmin) + if(!user){ + user = await this.userService.createUserWithoutKeycloak("admin") + } + const roles= ['default-admin'] + user['roles'] = roles; + return { accessToken: envTokenAdmin, user }; + } } async refreshToken(access_token: Token): Promise { + if (isKeycloakDisabled==true) { + return {accessToken: access_token}; + } + else{ const decoded: any = jwt_decode(access_token as unknown as string); const user = await this.userService.findUserBySub(decoded.sub) @@ -113,6 +151,7 @@ export class KeycloakPasswordStrategy extends PassportStrategy(KeycloakBearerStr return {accessToken: keycloakToken['token']}; } + } async validate(accessToken: any, done: (err: any, user: any, info?: any) => void): Promise { // Here you can implement additional validation or store the user information in your application. diff --git a/apps/api/src/app/trial/trial-group.service.ts b/apps/api/src/app/trial/trial-group.service.ts index fadec585..97402468 100644 --- a/apps/api/src/app/trial/trial-group.service.ts +++ b/apps/api/src/app/trial/trial-group.service.ts @@ -3,6 +3,7 @@ import axios, {AxiosResponse} from 'axios'; import { PrismaService } from '../prisma.service'; import { ModuleRef } from '@nestjs/core'; import { trial } from '@prisma/client'; +import { keycloak_disabled_fetch_by_KeycloakId } from "../auth/keycloak-disabled-config"; export interface TrialGroup { id: string; @@ -95,6 +96,13 @@ export class TrialGroupService { async getUsersInTrialGroup(trialGroupId: string): Promise { + const isKeycloakDisabled = process.env.KEYCLOAK_DISABLED === 'true'; + if (isKeycloakDisabled) { + const user = keycloak_disabled_fetch_by_KeycloakId("notadmin"); + const adminUser = keycloak_disabled_fetch_by_KeycloakId("admin"); + return [user, adminUser]; + } + else { try { const accessToken = await this.getToken(); const getUsersInTrialGroupConfig = { @@ -126,6 +134,7 @@ export class TrialGroupService { } catch (error) { this.logger.error(`Error while getting users in trial group from Keycloak: ${error}`) throw new HttpException('Error while getting users in trial group from Keycloak', HttpStatus.BAD_REQUEST) + } } } diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 01fd3c72..58071afc 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -36,6 +36,29 @@ export class UserService { return newUserFromKeycloak; } + async createUserWithoutKeycloak(role: string) { + const envUsername = (role == "admin") ? process.env.KD_USERNAME_ADMIN : process.env.KD_USERNAME; + const envKeycloakId = (role == "admin") ? process.env.KD_KEYCLOAK_ID_ADMIN : process.env.KD_KEYCLOAK_ID; + const envEmail = (role == "admin") ? process.env.KD_EMAIL_ADMIN : process.env.KD_EMAIL; + const envFullName = (role == "admin") ? process.env.KD_FULLNAME_ADMIN : process.env.KD_FULLNAME; + const Id = (role == "admin") ? 100 : 99; + const token = (role == "admin") ? process.env.KD_TOKEN_ADMIN : process.env.KD_TOKEN_USER ; + const newUserWithoutKeycloak = await this.prismaService.user.create({ + data: { + id: Id, + email: envEmail, + keycloak_id: envKeycloakId, + first_name: envUsername, + last_name: "", + name: envFullName, + username: envUsername, + email_verified: true, + refresh_token: token + } + }); + return newUserWithoutKeycloak; + } + findAll() { return `This action returns all user`; } From 9fbb6373c123e2afdd6b49a44ce9f213a88646fe Mon Sep 17 00:00:00 2001 From: Dheeraj Gavini Date: Thu, 31 Oct 2024 14:00:42 -0400 Subject: [PATCH 2/2] Added necessary changes to use adminStatus function to determine the admin status --- apps/api/src/app/auth/keycloak-disabled-config.ts | 2 +- apps/api/src/app/user/user.service.ts | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/api/src/app/auth/keycloak-disabled-config.ts b/apps/api/src/app/auth/keycloak-disabled-config.ts index 1aa36d8b..1d92930f 100644 --- a/apps/api/src/app/auth/keycloak-disabled-config.ts +++ b/apps/api/src/app/auth/keycloak-disabled-config.ts @@ -50,5 +50,5 @@ function keycloak_disabled_fetch_by_KeycloakId(role: string) { function adminStatus(role: string){ return (role=="admin") ? true : false; } -export { keycloak_disabled_comfig, keycloak_disabled_fetch_by_KeycloakId }; +export { keycloak_disabled_comfig, keycloak_disabled_fetch_by_KeycloakId, adminStatus}; diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 58071afc..ba147198 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -6,6 +6,7 @@ import {PrismaService} from "../prisma.service"; import {KeycloakPasswordStrategy} from "../auth/keycloak-password.strategy"; import {ModuleRef} from "@nestjs/core"; import { PrismaPromise, user } from "@prisma/client"; +import {adminStatus} from "../auth/keycloak-disabled-config"; @Injectable() export class UserService { @@ -37,12 +38,12 @@ export class UserService { } async createUserWithoutKeycloak(role: string) { - const envUsername = (role == "admin") ? process.env.KD_USERNAME_ADMIN : process.env.KD_USERNAME; - const envKeycloakId = (role == "admin") ? process.env.KD_KEYCLOAK_ID_ADMIN : process.env.KD_KEYCLOAK_ID; - const envEmail = (role == "admin") ? process.env.KD_EMAIL_ADMIN : process.env.KD_EMAIL; - const envFullName = (role == "admin") ? process.env.KD_FULLNAME_ADMIN : process.env.KD_FULLNAME; - const Id = (role == "admin") ? 100 : 99; - const token = (role == "admin") ? process.env.KD_TOKEN_ADMIN : process.env.KD_TOKEN_USER ; + const envUsername = adminStatus(role) ? process.env.KD_USERNAME_ADMIN : process.env.KD_USERNAME; + const envKeycloakId = adminStatus(role) ? process.env.KD_KEYCLOAK_ID_ADMIN : process.env.KD_KEYCLOAK_ID; + const envEmail = adminStatus(role) ? process.env.KD_EMAIL_ADMIN : process.env.KD_EMAIL; + const envFullName = adminStatus(role) ? process.env.KD_FULLNAME_ADMIN : process.env.KD_FULLNAME; + const Id = adminStatus(role) ? 100 : 99; + const token = adminStatus(role) ? process.env.KD_TOKEN_ADMIN : process.env.KD_TOKEN_USER ; const newUserWithoutKeycloak = await this.prismaService.user.create({ data: { id: Id,