Skip to content

Commit

Permalink
feat(tenant-management): configure keycloak for idp
Browse files Browse the repository at this point in the history
configure keycloak for idp

BREAKING CHANGE:
yes

43
  • Loading branch information
Tyagi-Sunny committed Sep 25, 2024
1 parent 5e03dcb commit d85099d
Show file tree
Hide file tree
Showing 8 changed files with 799 additions and 597 deletions.
1,120 changes: 530 additions & 590 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { extensions, Getter, inject, intercept } from '@loopback/core';

Check failure on line 1 in services/tenant-management-service/src/controllers/idp.controller.ts

View workflow job for this annotation

GitHub Actions / npm_test

'extensions' is defined but never used

Check failure on line 1 in services/tenant-management-service/src/controllers/idp.controller.ts

View workflow job for this annotation

GitHub Actions / npm_test

'Getter' is defined but never used
import { getModelSchemaRef, post, requestBody } from '@loopback/rest';
import {
CONTENT_TYPE,
OPERATION_SECURITY_SPEC,
rateLimitKeyGenPublic,
STATUS_CODE,
} from '@sourceloop/core';
import { authorize } from 'loopback4-authorization';
import { ratelimit } from 'loopback4-ratelimiter';
import { TenantManagementServiceBindings, WEBHOOK_VERIFIER } from '../keys';
import { IdpDetailsDTO } from '../models/dtos/idp-details-dto.model';
import { ConfigureIdpFunc, IdPKey, IWebhookHandler } from '../types';

Check failure on line 13 in services/tenant-management-service/src/controllers/idp.controller.ts

View workflow job for this annotation

GitHub Actions / npm_test

'IWebhookHandler' is defined but never used
import { KeycloakIdpProvider } from '../providers/idp/idp-keycloak.provider';

Check failure on line 14 in services/tenant-management-service/src/controllers/idp.controller.ts

View workflow job for this annotation

GitHub Actions / npm_test

'KeycloakIdpProvider' is defined but never used

const basePath = '/manage/users';
export class IdpController {
constructor(
@inject(TenantManagementServiceBindings.IDP_KEYCLOAK)
private readonly idpKeycloakProvider:ConfigureIdpFunc<IdpDetailsDTO>
) { }
@intercept(WEBHOOK_VERIFIER)
@ratelimit(true, {
max: parseInt(process.env.WEBHOOK_API_MAX_ATTEMPTS ?? '10'),
keyGenerator: rateLimitKeyGenPublic,
})
@authorize({
permissions: ['*'],
})
@post(`${basePath}`, {
security: OPERATION_SECURITY_SPEC,
responses: {
[STATUS_CODE.NO_CONTENT]: {
description: 'Webhook success',
},
},
})
async idpConfigure(
@requestBody({
content: {
[CONTENT_TYPE.JSON]: {
schema: getModelSchemaRef(IdpDetailsDTO, {
title: 'IdpDetailsDTO',
}),
},
},
})
payload: IdpDetailsDTO,
): Promise<void> {
switch (payload.identityProvider) {
case IdPKey.AUTH0:

break;
case IdPKey.COGNITO:

break;
case IdPKey.KEYCLOAK:
await this.idpKeycloakProvider(payload);
break;

default:
break;
}

}
}
26 changes: 19 additions & 7 deletions services/tenant-management-service/src/keys.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
import {VerifyFunction} from 'loopback4-authentication';
import { VerifyFunction } from 'loopback4-authentication';
import {
ConfigureIdpFunc,
ITenantManagementServiceConfig,
LeadUser,
WebhookConfig,
WebhookNotificationServiceType,
} from './types';
import {IAuthUser} from 'loopback4-authorization';
import {AnyObject} from '@loopback/repository';
import {WebhookController} from './controllers';
import { IAuthUser } from 'loopback4-authorization';
import { AnyObject } from '@loopback/repository';
import { WebhookController } from './controllers';
import {
BindingKey,
BindingTemplate,
Interceptor,
extensionFor,
} from '@loopback/core';
import {BINDING_PREFIX} from '@sourceloop/core';
import {IEventConnector} from './types/i-event-connector.interface';
import { BINDING_PREFIX } from '@sourceloop/core';
import { IEventConnector } from './types/i-event-connector.interface';
import { ValueOrPromise } from '@loopback/context';

Check failure on line 20 in services/tenant-management-service/src/keys.ts

View workflow job for this annotation

GitHub Actions / npm_test

'ValueOrPromise' is defined but never used

export namespace TenantManagementServiceBindings {
export const Config =
BindingKey.create<ITenantManagementServiceConfig | null>(
`${BINDING_PREFIX}.chat.config`,
);
/**
* Binding key for the Idp keycloak provider.
*/
export const IDP_KEYCLOAK = BindingKey.create<
ConfigureIdpFunc<void>
>('sf.user.idp.keycloak');
}

/**
Expand All @@ -31,6 +39,10 @@ export const LEAD_TOKEN_VERIFIER = BindingKey.create<
VerifyFunction.BearerFn<LeadUser>
>('sf.user.lead.verifier');





/**
* Binding key for the system user.
*/
Expand Down Expand Up @@ -63,7 +75,7 @@ export const WebhookHandlerEP = BindingKey.create<WebhookController<never>>(
*/
export const asWebhookHandler: BindingTemplate = binding => {
extensionFor(WebhookHandlerEP.key)(binding);
binding.tag({namespace: WebhookHandlerEP.key});
binding.tag({ namespace: WebhookHandlerEP.key });
};

export const WebhookNotificationService =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getJsonSchema } from '@loopback/openapi-v3';
import { Model, model, property } from '@loopback/repository';
import { IdpDetails, IdPKey } from '../../types';
import { Tenant } from '../tenant.model';

@model({
description: 'model describing payload for IDP controller',
})
export class IdpDetailsDTO extends Model implements IdpDetails {
@property({
type: 'string',
description: 'identity provider - auth0 , keycloak , cognito',
required: true,
default: IdPKey.AUTH0,
jsonSchema: {
enum: Object.values(IdPKey),
},
})
identityProvider: IdPKey;

@property({
type: 'object',
description: 'address object to be created for the lead',
jsonSchema: getJsonSchema(Tenant),
})
tenant: Tenant;

constructor(data?: Partial<IdpDetailsDTO>) {
super(data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { Provider, ValueOrPromise } from "@loopback/context";

Check failure on line 1 in services/tenant-management-service/src/providers/idp/idp-keycloak.provider.ts

View workflow job for this annotation

GitHub Actions / npm_test

'ValueOrPromise' is defined but never used
import axios from 'axios';
import qs from 'qs';
import { ConfigureIdpFunc, IdpDetails } from "../../types";

interface TokenResponse {
access_token: string;

Check failure on line 7 in services/tenant-management-service/src/providers/idp/idp-keycloak.provider.ts

View workflow job for this annotation

GitHub Actions / npm_test

Type Property name `access_token` must match one of the following formats: camelCase, UPPER_CASE, PascalCase
}

interface Credentials {
type: string;
value: string;
temporary: boolean;
}

export class KeycloakIdpProvider implements Provider<ConfigureIdpFunc<void>>{
constructor(){}

value(): ConfigureIdpFunc<void> {
return (payload)=>this.configure(payload);
}
async configure(payload: IdpDetails): Promise<void> {
const { tenant } = payload;

try {
// 1. Create a new realm using the tenant key
await this.createRealm(tenant.key);

// 2. Create a new client within the realm
const clientId = `client-${tenant.key}`; // You can customize this as needed
await this.createClient(tenant.key, clientId);

// 3. Create a new admin user for the tenant
const adminUsername = `${tenant.key}-admin`; // Customize this as needed
const adminPassword = 'your-secure-password'; // This can be dynamic or set in the environment
await this.createUser(tenant.key, adminUsername, adminPassword);

console.log(`Successfully configured Keycloak for tenant: ${tenant.name}`);
} catch (error) {
console.error(`Error configuring Keycloak for tenant: ${tenant.name}`, error);
throw new Error(`Failed to configure Keycloak for tenant: ${tenant.name}`);
}
}




async authenticateAdmin(): Promise<string> {
const response = await axios.post<TokenResponse>(
`${process.env.KEYCLOAK_HOST}/realms/master/protocol/openid-connect/token`,
qs.stringify({
username: process.env.KEYCLOAK_ADMIN_USERNAME,
password: process.env.KEYCLOAK_ADMIN_PASSWORD,
grant_type: 'password',

Check failure on line 54 in services/tenant-management-service/src/providers/idp/idp-keycloak.provider.ts

View workflow job for this annotation

GitHub Actions / npm_test

Object Literal Property name `grant_type` must match one of the following formats: camelCase, UPPER_CASE, PascalCase
client_id: 'admin-cli',

Check failure on line 55 in services/tenant-management-service/src/providers/idp/idp-keycloak.provider.ts

View workflow job for this annotation

GitHub Actions / npm_test

Object Literal Property name `client_id` must match one of the following formats: camelCase, UPPER_CASE, PascalCase
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
}
);

return response.data.access_token;
}

async createRealm(realmName: string): Promise<void> {
const token = await this.authenticateAdmin();

const response = await axios.post(
`${process.env.KEYCLOAK_HOST}/admin/realms`,
{
realm: realmName,
enabled: true,
},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);

console.log('Realm created:', response.data);
}

async createClient(realmName: string, clientId: string): Promise<void> {
const token = await this.authenticateAdmin();

const response = await axios.post(
`${process.env.KEYCLOAK_HOST}/admin/realms/${realmName}/clients`,
{
clientId: clientId,
publicClient: true,
directAccessGrantsEnabled: true,
protocol: 'openid-connect',
enabled: true,
},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);

console.log('Client created:', response.data);
}

async createUser(realmName: string, username: string, password: string): Promise<void> {
const token = await this.authenticateAdmin();

const response = await axios.post(
`${process.env.KEYCLOAK_HOST}/admin/realms/${realmName}/users`,
{
username: username,
enabled: true,
credentials: [
{
type: 'password',
value: password,
temporary: false,
},
] as Credentials[],
},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);

console.log('User created:', response.data);
}


}


Empty file.
15 changes: 15 additions & 0 deletions services/tenant-management-service/src/types/i-idp.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Tenant } from "../models";

export enum IdPKey {
AUTH0 = 'auth0',
COGNITO = 'cognito',
KEYCLOAK = 'keycloak',
}

export type ConfigureIdpFunc<T>=(payload:IdpDetails)=>Promise<T>;

export interface IdpDetails {
identityProvider: IdPKey;
tenant: Tenant;
}

1 change: 1 addition & 0 deletions services/tenant-management-service/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ export * from './resource.type';
export * from './i-provisioning-service.interface';
export * from './i-subscription.interface';
export * from './i-event-connector.interface';
export * from './i-idp.interface';

0 comments on commit d85099d

Please sign in to comment.