-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tenant-management): configure keycloak for idp
configure keycloak for idp BREAKING CHANGE: yes 43
- Loading branch information
1 parent
5e03dcb
commit d85099d
Showing
8 changed files
with
799 additions
and
597 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
66 changes: 66 additions & 0 deletions
66
services/tenant-management-service/src/controllers/idp.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 GitHub Actions / npm_test
|
||
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'; | ||
import { KeycloakIdpProvider } from '../providers/idp/idp-keycloak.provider'; | ||
|
||
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; | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
services/tenant-management-service/src/models/dtos/idp-details-dto.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
137 changes: 137 additions & 0 deletions
137
services/tenant-management-service/src/providers/idp/idp-keycloak.provider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import { Provider, ValueOrPromise } from "@loopback/context"; | ||
import axios from 'axios'; | ||
import qs from 'qs'; | ||
import { ConfigureIdpFunc, IdpDetails } from "../../types"; | ||
|
||
interface TokenResponse { | ||
access_token: string; | ||
} | ||
|
||
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', | ||
client_id: 'admin-cli', | ||
}), | ||
{ | ||
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
15
services/tenant-management-service/src/types/i-idp.interface.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters