Skip to content

Integrations Design

Sam Lombardo edited this page Oct 3, 2024 · 1 revision

Integrations Design

Date of Discussion: October 3, 2024

Overview

We explored designing a reusable and scalable integrations feature for the FrontDesk application, accommodating multi-tenancy and the ability for users to be linked to multiple organizations. The main goals are:

  • Multi-Tenancy Support: Users can belong to multiple organizations (tenants), and each organization manages its own properties and integrations.
  • User Roles and Permissions: Different users may have different roles within organizations, affecting their access to properties and integrations.
  • Flexible Integrations: The system should allow organizations to connect to various OTAs (Online Travel Agencies) like Hospitable, Airbnb, and others in a reusable and generic manner.
  • C# Implementation: The backend will be implemented in C#.

System Architecture

Core Components

  • Organization: Represents a tenant in the system.
  • User: Can belong to multiple organizations with specific roles.
  • Property: Belongs to an organization.
  • Integration: Represents an OTA or third-party service.
  • Connection: Represents an organization's connection to an integration.
  • Account: Stores organization-specific account details for an integration.
  • Role-Based Access Control (RBAC): Manages user permissions within organizations.

Models Design

Organization Model

public class Organization
{
    public Guid OrganizationId { get; set; }
    public string Name { get; set; }
    public ICollection<OrganizationUser> OrganizationUsers { get; set; }
    public ICollection<Property> Properties { get; set; }
    public ICollection<Connection> Connections { get; set; }
}

User Model

public class User
{
    public Guid UserId { get; set; }
    public string Email { get; set; }
    public ICollection<OrganizationUser> OrganizationUsers { get; set; }
}

OrganizationUser Model (Join Table for Users and Organizations)

public class OrganizationUser
{
    public Guid OrganizationId { get; set; }
    public Organization Organization { get; set; }

    public Guid UserId { get; set; }
    public User User { get; set; }

    public string Role { get; set; } // e.g., "Owner", "Manager", "Viewer"
}

Property Model

public class Property
{
    public Guid PropertyId { get; set; }
    public string Name { get; set; }
    public Guid OrganizationId { get; set; }
    public Organization Organization { get; set; }
    // Other property-specific fields
}

Integration Model

public class Integration
{
    public Guid IntegrationId { get; set; }
    public string Name { get; set; } // e.g., "Hospitable", "Airbnb"
    public string ProviderKey { get; set; } // e.g., "hospitable", "airbnb"
    public string LogoUrl { get; set; }
    public bool IsActive { get; set; }
}

Connection Model

Represents an organization's connection to an integration.

public class Connection
{
    public Guid ConnectionId { get; set; }

    public Guid OrganizationId { get; set; }
    public Organization Organization { get; set; }

    public Guid IntegrationId { get; set; }
    public Integration Integration { get; set; }

    public string AccessToken { get; set; }
    public string RefreshToken { get; set; }
    public DateTime TokenExpiresAt { get; set; }

    public string Status { get; set; } // e.g., "Connected", "Disconnected"

    public ICollection<Account> Accounts { get; set; }

    public DateTime CreatedAt { get; set; }
    public DateTime UpdatedAt { get; set; }
}

Account Model

Stores integration-specific account details for an organization.

public class Account
{
    public Guid AccountId { get; set; }

    public Guid ConnectionId { get; set; }
    public Connection Connection { get; set; }

    public string ExternalAccountId { get; set; }
    public string AccountName { get; set; }
    public string Email { get; set; }
    public string MetadataJson { get; set; } // JSON string for additional data
}

Role-Based Access Control (RBAC)

Implement RBAC to manage user permissions within organizations.

Roles and Permissions

  • Owner: Full access to all organization resources, including integrations.
  • Manager: Can manage properties and integrations but may have some restrictions.
  • Viewer: Read-only access to properties and bookings.

Access Control Checks

Implement methods to check if a user has the necessary permissions to perform actions.

public class AuthorizationService
{
    public bool CanManageIntegrations(Guid userId, Guid organizationId)
    {
        // Fetch the OrganizationUser record
        var orgUser = _dbContext.OrganizationUsers
            .FirstOrDefault(ou => ou.UserId == userId && ou.OrganizationId == organizationId);

        if (orgUser == null)
            return false;

        return orgUser.Role == "Owner" || orgUser.Role == "Manager";
    }

    // Other permission checks...
}

Abstraction Layer

Use C# interfaces and abstract classes to define a common contract for all integrations.

IIntegration Interface

public interface IIntegration
{
    Task AuthenticateAsync(Guid organizationId, AuthenticationParameters parameters);
    Task RefreshTokenAsync(Guid connectionId);
    Task<Account> GetAccountDetailsAsync(Guid connectionId);
    Task DisconnectAsync(Guid connectionId);
}

AuthenticationParameters Class

public class AuthenticationParameters
{
    // Parameters required for authentication
    public string AuthorizationCode { get; set; }
    public string RedirectUri { get; set; }
    public Dictionary<string, string> AdditionalData { get; set; }
}

Integration Implementations

HospitableIntegration Class

public class HospitableIntegration : IIntegration
{
    public async Task AuthenticateAsync(Guid organizationId, AuthenticationParameters parameters)
    {
        // Hospitable-specific authentication logic
    }

    public async Task RefreshTokenAsync(Guid connectionId)
    {
        // Hospitable-specific token refresh logic
    }

    public async Task<Account> GetAccountDetailsAsync(Guid connectionId)
    {
        // Fetch account details from Hospitable API
    }

    public async Task DisconnectAsync(Guid connectionId)
    {
        // Hospitable-specific disconnect logic
    }
}

AirbnbIntegration Class

public class AirbnbIntegration : IIntegration
{
    public async Task AuthenticateAsync(Guid organizationId, AuthenticationParameters parameters)
    {
        // Airbnb-specific authentication logic
    }

    public async Task RefreshTokenAsync(Guid connectionId)
    {
        // Airbnb-specific token refresh logic
    }

    public async Task<Account> GetAccountDetailsAsync(Guid connectionId)
    {
        // Fetch account details from Airbnb API
    }

    public async Task DisconnectAsync(Guid connectionId)
    {
        // Airbnb-specific disconnect logic
    }
}

Dynamic Integration Loading

Implement a factory to create integration instances based on the provider key.

IntegrationFactory Class

public static class IntegrationFactory
{
    public static IIntegration CreateIntegration(string providerKey)
    {
        switch (providerKey.ToLower())
        {
            case "hospitable":
                return new HospitableIntegration();
            case "airbnb":
                return new AirbnbIntegration();
            // Add cases for other integrations
            default:
                throw new NotSupportedException($"Provider '{providerKey}' is not supported.");
        }
    }
}

Authentication Flow Handling

OAuth 2.0 Authentication

For integrations using OAuth 2.0 (e.g., Hospitable):

  1. Initiate OAuth Flow:

    • Redirect the user to the OTA's authorization URL with necessary query parameters.
  2. Handle OAuth Callback:

    • Create an endpoint to handle the OTA's redirect with an authorization code.
    • Use the code to request access and refresh tokens.
  3. Store Tokens:

    • Save the tokens in the Connection model associated with the organization.

API Key Authentication

For integrations using API keys:

  1. API Key Input:

    • Provide a secure form for the user to enter the API key.
  2. Validate API Key:

    • Optionally validate the API key by making a test API call.
  3. Store API Key:

    • Save the API key in the Connection model.

Example Authentication Method

public async Task AuthenticateIntegrationAsync(Guid organizationId, string providerKey, AuthenticationParameters parameters)
{
    var integration = IntegrationFactory.CreateIntegration(providerKey);

    // Check if the user has permission
    if (!_authorizationService.CanManageIntegrations(_currentUserId, organizationId))
        throw new UnauthorizedAccessException("You do not have permission to manage integrations for this organization.");

    await integration.AuthenticateAsync(organizationId, parameters);
}

Secure Credential Management

  • Encryption: Use encryption mechanisms like Data Protection API (DPAPI) or Azure Key Vault for storing sensitive data.
  • Hashing: Do not hash tokens; they need to be retrieved in plaintext for API calls.
  • Secure Storage: Store secrets securely in your configuration files or environment variables, not in source code.

Example of Secure Token Storage

public class SecureTokenService
{
    private readonly IDataProtector _protector;

    public SecureTokenService(IDataProtectionProvider dataProtectionProvider)
    {
        _protector = dataProtectionProvider.CreateProtector("SecureTokenProtector");
    }

    public string Protect(string plaintext)
    {
        return _protector.Protect(plaintext);
    }

    public string Unprotect(string protectedText)
    {
        return _protector.Unprotect(protectedText);
    }
}

User Interface Design

Integrations Page

  • Organization Context: Display integrations within the context of the selected organization.
  • List Available Integrations: Show all supported OTAs with connection status for the organization.
  • Action Buttons: Allow users to connect, reconnect, or disconnect integrations based on their permissions.

Organization Switching

  • Provide a mechanism for users to switch between organizations they belong to.

Permission-Based Access

  • Disable or hide integration management options for users without sufficient permissions.

Error Handling and Notifications

  • Graceful Errors: Catch exceptions and provide user-friendly error messages.
  • Logging: Use logging frameworks like Serilog or NLog for error logging.
  • Notifications: Inform users of successful connections, errors, or required actions (e.g., re-authentication).

Handling User Stories

Scenario 1: Property Owner Outsources Management

  • Property Owner:

    • Has an organization representing their properties.
    • Invites the management company as users with appropriate roles (e.g., "Manager").
  • Management Company:

    • Users can switch between different organizations (property owners) they manage.
    • Access is governed by their role within each organization.

Scenario 2: Management Company Manages Multiple Properties

  • The management company can:

    • Switch between organizations (clients) they have access to.
    • Manage properties and integrations within each organization, depending on their permissions.

Permissions Enforcement

  • Use the AuthorizationService to enforce permissions on actions like connecting integrations, managing properties, etc.

Security Best Practices

  • Input Validation: Validate all user inputs to prevent injection attacks.
  • HTTPS Enforcement: Ensure all communications are over HTTPS.
  • Token Expiry Handling: Implement logic to refresh tokens before they expire.
  • Audit Trails: Keep logs of user actions related to integrations for auditing purposes.

Testing

  • Unit Tests: Write unit tests for each component, mocking external dependencies.
  • Integration Tests: Test the end-to-end flow with sandbox environments or mocks.
  • Permission Tests: Verify that users cannot perform actions beyond their permissions.

Next Steps

  1. Implement the Updated Models: Update your database schema and data models to reflect the multi-tenancy design.
  2. Develop the Integration Interfaces: Code the interfaces and OTA-specific implementations.
  3. Set Up Authentication Flows: Configure OAuth 2.0 and API key authentication mechanisms.
  4. Build the User Interface: Design the UI to handle organization switching and integration management.
  5. Enforce Permissions: Implement authorization checks throughout your application.
  6. Test Thoroughly: Perform unit, integration, and permission tests.
  7. Gather User Feedback: Pilot the feature with a subset of users and iterate based on feedback.

Back to Important Concepts

Clone this wiki locally