diff --git a/services/subscription-service/README.md b/services/subscription-service/README.md index d8b7994..f97af18 100644 --- a/services/subscription-service/README.md +++ b/services/subscription-service/README.md @@ -62,6 +62,10 @@ $ [npm install | yarn add] @sourceloop/ctrl-plane-subscription-service // add Component for subscription-service this.component(SubscriptionServiceComponent); ``` +- If you uses Sequelize as the ORM, make sure to use the Sequelize-compatible components,else use the respective default components. + ```ts + this.component(SubscriptionSequelizeServiceComponent); + ``` - Set up a [Loopback4 Datasource](https://loopback.io/doc/en/lb4/DataSource.html) with `dataSourceName` property set to `SubscriptionDB`. You can see an example datasource [here](#setting-up-a-datasource). - This component internally uses [FeatureToggleServiceComponent](https://www.npmjs.com/package/@sourceloop/feature-toggle-service) that requires a datasource binding with the name 'FeatureToggleDB'. Make sure to create a datasource for it. diff --git a/services/tenant-management-service/README.md b/services/tenant-management-service/README.md index 9503032..24b05a6 100644 --- a/services/tenant-management-service/README.md +++ b/services/tenant-management-service/README.md @@ -2,13 +2,13 @@ [![LoopBack]()](http://loopback.io/) -This is the primary service of the control plane responsible for onboarding a tenant and triggering it's provisioning. +This is the primary service of the ARC SaaS control plane responsible for onboarding a tenant and managing it's provisioning. ## Overview A Microservice for handling tenant management operations. It provides - -- lead creation and verification +- Lead creation and verification - Tenant Onboarding of both pooled and silo tenants - Billing and Invoicing - Provisioning of resources for silo and pooled tenants @@ -35,12 +35,26 @@ $ [npm install | yarn add] @sourceloop/ctrl-plane-tenant-management-service - Set the [environment variables](#environment-variables). - Run the [migrations](#migrations). - Add the `TenantManagementServiceComponent` to your Loopback4 Application (in `application.ts`). + ```typescript // import the TenantManagementServiceComponent import {TenantManagementServiceComponent} from '@sourceloop/ctrl-plane-tenant-management-service'; // add Component for TenantManagementService this.component(TenantManagementServiceComponent); ``` +- If you uses Sequelize as the ORM, make sure to use the Sequelize-compatible components,else use the respective default components. + ```ts + this.component(TenantManagementSequelizeServiceComponent); + ``` + This microservice uses [loopback4-authentication](https://www.npmjs.com/package/loopback4-authentication) and [@sourceloop/core](https://www.npmjs.com/package/@sourceloop/core) and that uses asymmetric token encryption and decryption by default for that setup please refer [their](https://www.npmjs.com/package/@sourceloop/authentication-service) documentation but if you wish to override and use symmetric encryption add the following to your `application.ts` file along with other config values. + +```typecript +this.bind(TenantManagementServiceBindings.Config).to({ + useSymmetricEncryption:true, +}); + +``` + - Set up a [Loopback4 Datasource](https://loopback.io/doc/en/lb4/DataSource.html) with `dataSourceName` property set to `TenantManagementDB`. You can see an example datasource [here](#setting-up-a-datasource). - Bind any of the custom [providers](#providers) you need. @@ -54,12 +68,30 @@ $ [npm install | yarn add] @sourceloop/ctrl-plane-tenant-management-service - The mail has a link which should direct to a front end application, which in turn would call the upcoming api's using a temporary authorization code included in the mail. - The front end application first calls the `/leads/{id}/verify` which updates the validated status of the lead in the DB and returns a new JWT Token that can be used for subsequent calls - If the token is validated in the previous step, the UI should call the `/leads/{id}/tenants` endpoint with the necessary payload(as per swagger documentation). -- This endpoint would onboard the tenant in the DB, and the facade is then supposed to trigger the relevant events using the `/tenants/{id}/provision` endpoint. +- This endpoint would onboard the tenant in the DB, and its success you should trigger the relevant events using the `/tenants/{id}/provision` endpoint. + +## Direct Tenant Onboarding + +In addition to the lead-based onboarding flow, a new tenant can also be onboarded directly without creating a lead first. +This capability is designed specifically for control plane administrators, who can create and provision tenants directly through the management APIs. + +To ensure security and operational control, only users with control plane admin privileges are allowed to perform direct tenant onboarding. +Regular users or leads cannot bypass the standard lead creation and verification process. + +To onboard a tenant directly, you should call the `/tenants` endpoint. ## Event Publishing -This service now supports pluggable event strategies — EventBridge, SQS, and BullMQ — through the loopback4-message-bus-connector. + +The service supports pluggable event strategies — EventBridge, SQS, and BullMQ — through the loopback4-message-bus-connector. You can publish provisioning or deployment events by injecting a Producer for your desired message bus strategy. + +To enable these strategies, bind the following component in your application: +```ts +this.component(EventStreamConnectorComponent); +``` +Once configured, you can publish provisioning or deployment events by injecting a Producer for the desired message bus strategy. + ```ts import {producer, Producer, QueueType} from 'loopback4-message-bus-connector'; @@ -86,7 +118,6 @@ export class TenantEventPublisher { }); } } - ``` ## IDP - Identity Provider @@ -138,6 +169,47 @@ app .bind(TenantManagementServiceBindings.IDP_AUTH0) .toProvider(Auth0IdpProvider); ``` +### Keycloak IdP Provider + +The Keycloak IdP Provider automatically sets up and configures all the required Keycloak resources for a new tenant during onboarding. + +It eliminates manual setup and ensures each tenant has a secure, isolated identity environment. + +When a new tenant is provisioned, the provider automatically: +- Creates a Realm in Keycloak for that tenant. +(Each tenant gets its own isolated authentication space.) + +- Configures SMTP (Email) settings in the realm using AWS SES for password reset and notification emails. + +- Creates a Client inside the realm for the tenant’s application with the correct redirect URIs and credentials. + +- Creates an Admin User for the tenant with a temporary password and triggers a password reset email. + +- Returns the admin user’s ID (authId) after successful setup. + +This setup ensures that every tenant has a ready-to-use Keycloak environment, complete with its own realm, client, and admin user, enabling secure login and user management from day one. + +### Auth0 IdP Provider + +The Auth0 IdP Provider automates the Auth0 setup required for a tenant during onboarding. It creates the Auth0 organization, provisions an initial admin user, and adds that user to the organization — all based on tenant details and stored tenant configuration. + +When a tenant is provisioned, the provider will: + +- Create (or reuse) an Auth0 Organization for the tenant. + + For PREMIUM tenants a dedicated organization is created per tenant. For pooled plans, tenants are grouped under a shared organization named after the plan tier. This ensures correct isolation or pooling based on your plan model. + +- Apply branding and connection settings to the organization using the tenant configuration (logo, colors, enabled connections, etc.). + + This makes tenant login pages and connections (social/database) behave and look as configured for that tenant. + +- Create an Admin User for the tenant using the tenant contact details and a generated temporary password. + + The password is generated securely and the admin is expected to verify or change it through Auth0 flows (no password is persisted in plain text). + +- Add the admin user as a member of the Auth0 Organization so they can manage users, connections, and settings for that tenant. + +- Return the Auth0 user ID (authId) on success so the control plane can reference the identity for audits or future operations. ## Webhook Integration @@ -350,7 +422,96 @@ The identity provider and its related providers are also a part of the 'WebhookT lenght of random key for lead. - + + AUTH0_DOMAIN + Y for Auth0 + Domain + + + + AUTH0_CLIENT_ID + Y for Auth0 + Client id of the Auth0 Application + + + + AUTH0_CLIENT_SECRET + Y for Auth0 + Client secret of the Auth0 Application + + + + AUTH0_AUDIENCE + N + Recipient of the token + + + + AWS_REGION + Y for Keycloak + AWS region for SSM + + + + NAMESPACE + Y for Keycloak + SSM namespace + + + + KEYCLOAK_HOST + Y for keycloak + Keycloak host URL + + + + KEYCLOAK_ADMIN_USERNAME + Y for Keycloak + Username of Admin + + + + KEYCLOAK_ADMIN_PASSWORD + Y for Keycloak + Password of Admin + + + + AWS_SES_SMTP_HOST + Y for Keycloak + SMTP host URL + + + + AWS_SES_SMTP_USERNAME + Y for Keycloak + SMTP username + + + + AWS_SES_SMTP_PASSWORD + Y for Keycloak + SMTP password + + + + SMTP_FROM_EMAIL + Y for Keycloak + Emai Id from which you wish to send email + + + + SMTP_FROM_DISPLAY_NAME + Y for Keycloak + Display name + + + + DOMAIN_NAME + Y for Keycloak + Your domain name + + @@ -376,7 +537,7 @@ const config = { }; @lifeCycleObserver('datasource') -export class AuthenticationDbDataSource +export class TenantManagementDb extends juggler.DataSource implements LifeCycleObserver { @@ -470,7 +631,7 @@ The migrations required for this service can be copied from the service. You can ## Database Schema -![alt text](./docs/tenants.png) +![alt text](./docs/db_schema.png) The major tables in the schema are briefly described below - @@ -483,3 +644,5 @@ The major tables in the schema are briefly described below - **Leads** - this model represents a lead that could eventually be a tenant in the system **Tenants** - main model of the service that represents a tenant in the system, either pooled or siloed + +**TenantMgmtConfig** - to save any tenant specific data related to idP diff --git a/services/tenant-management-service/docs/db_schema.png b/services/tenant-management-service/docs/db_schema.png new file mode 100644 index 0000000..960dfca Binary files /dev/null and b/services/tenant-management-service/docs/db_schema.png differ diff --git a/services/tenant-management-service/docs/tenant-onboarding.png b/services/tenant-management-service/docs/tenant-onboarding.png index 15b6135..88b7dd8 100644 Binary files a/services/tenant-management-service/docs/tenant-onboarding.png and b/services/tenant-management-service/docs/tenant-onboarding.png differ diff --git a/services/tenant-management-service/docs/tenants.png b/services/tenant-management-service/docs/tenants.png deleted file mode 100644 index a9a314c..0000000 Binary files a/services/tenant-management-service/docs/tenants.png and /dev/null differ diff --git a/services/tenant-management-service/src/models/tenant-config.model.ts b/services/tenant-management-service/src/models/tenant-config.model.ts deleted file mode 100644 index 58c1b89..0000000 --- a/services/tenant-management-service/src/models/tenant-config.model.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {belongsTo, model, property} from '@loopback/repository'; -import {UserModifiableEntity} from '@sourceloop/core'; -import {Tenant} from './tenant.model'; - -@model({ - name: 'tenant_configs', - description: 'tenant_configs to save any tenant specific data related to idP', -}) -export class TenantConfig extends UserModifiableEntity { - @property({ - type: 'string', - id: true, - generated: true, - }) - id: string; - - @property({ - type: 'string', - required: true, - name: 'config_key', - }) - configKey: string; - - @property({ - type: 'object', - required: true, - name: 'config_value', - }) - configValue: object; - - @belongsTo( - () => Tenant, - {keyTo: 'id'}, - { - type: 'string', - name: 'tenant_id', - description: 'id of the tenant this invoice is generated for', - required: true, - }, - ) - tenantId: string; - - constructor(data?: Partial) { - super(data); - } -}