Banata

Infrastructure

Projects

Multi-tenant project isolation with per-project users, organizations, branding, and configuration.

Projects are the foundation of multi-tenant isolation in Banata Auth. They allow a single Banata Auth deployment to serve multiple independent applications, each with its own users, organizations, branding, and configuration.


Why Projects?

Consider a software engineering firm that builds and operates authentication for several client products from a single dashboard. Without project isolation, all users, organizations, email templates, roles, and audit logs would be mixed together. With projects, each client product gets a fully isolated auth tenant:

  • Project A ("Twitter Clone") has its own user base, branding, roles, and SSO providers.
  • Project B ("E-Commerce Platform") has a completely separate set of users, organizations, and permissions.
  • Both are managed from the same admin dashboard by the firm's engineering team.

Switching projects in the dashboard shows a completely clean slate -- zero data spillover between projects.


Concepts

Project

A project is a fully isolated auth tenant. Everything that constitutes an application's auth system lives within a project boundary:

Isolated per projectDescription
UsersUser accounts and profiles
SessionsActive sessions and tokens
AccountsLinked OAuth/social accounts
OrganizationsMulti-tenant workspaces
MembersOrganization memberships
InvitationsPending org invitations
API keysProgrammatic access keys
BrandingColors, logo, custom CSS, fonts
Email templatesCustomized auth and transactional emails
Email configPer-email-type enable/disable toggles
Email provider configActive email provider and API keys
RolesCustom RBAC role definitions
PermissionsCustom RBAC permission definitions
Resource typesFine-grained authorization resource types
Dashboard configRuntime configuration overrides
Webhook endpointsRegistered webhook URLs and secrets
Domain configsService domain settings (email, auth API, admin portal)
Redirect configsRedirect URIs and callback URLs
Action configsAutomated event-triggered webhook actions
Radar configBot protection / rate limiting settings
Addon configsThird-party integration settings
Audit eventsAll auditable auth events
SSO connectionsSAML/OIDC enterprise SSO providers
SCIM directoriesDirectory sync configurations
Vault secretsEncrypted secrets (envelope encryption)
Domain verificationsDomain ownership verification records

The only entities shared across projects are dashboard admin users (the people who log in to the admin dashboard itself).


Data Model

Project

typescript
interface Project {
  id: string;
  name: string;            // e.g., "Twitter Clone"
  slug: string;            // URL-friendly, unique (e.g., "twitter-clone")
  description?: string;    // Optional description (max 500 chars)
  logoUrl?: string;        // Project logo URL
  ownerId: string;         // Dashboard admin who created the project
  createdAt: number;       // Unix timestamp (ms)
  updatedAt: number;
}

Slug validation: lowercase alphanumeric characters and hyphens only (/^[a-z0-9-]+$/), 1-100 characters.

Project Scoping

Every record in the database (except the project table itself) has an optional projectId field. This field links the record to its parent project. Records without a projectId are excluded from dashboard views (strict isolation).

All 33 non-project tables have a projectId index for efficient filtering.


Default Bootstrapping

On the first dashboard load, the dashboard calls the ensure-default endpoint. If no projects exist in the database and autoCreateDefault is enabled (the default), Banata Auth automatically creates:

  1. A "Default Project" with slug default.
  2. A super_admin role seeded with all built-in permissions.

No default organization is created automatically. Organizations are created explicitly by developers when needed.

Plugin Options

typescript
import { projectsPlugin } from "@banata-auth/convex/plugins";
 
projectsPlugin({
  autoCreateDefault: true,               // Default: true
  defaultProjectName: "Default Project", // Default: "Default Project"
});

Set autoCreateDefault: false to disable automatic bootstrapping and require manual project creation.


Dashboard Usage

Project Switcher

The dashboard sidebar includes a ProjectSwitcher component that lists all projects. Selecting a project updates the active scope, and all subsequent API calls from the dashboard are automatically scoped to that project.

Automatic Scope Injection

The dashboard API layer automatically injects the active projectId into every outgoing POST request body, unless the endpoint is scope-exempt (project management endpoints themselves are exempt). This means dashboard components do not need to manually pass scope identifiers.

typescript
Request to /api/auth/admin/list-users
  -> Body automatically includes { projectId: "..." }
 
Request to /api/auth/banata/projects/list
  -> Body is NOT modified (scope-exempt)

The client-side cache is also keyed by project, so switching projects does not return stale data from a different project.


SDK Usage

The @banata-auth/sdk provides a Projects resource for programmatic management of projects.

Initialize the SDK

typescript
import { BanataAuth } from "@banata-auth/sdk";
 
const client = new BanataAuth({
  apiKey: "sk_live_...",
  baseUrl: "https://your-deployment.convex.site",
});

With the normal managed-service flow, that API key already identifies the project. You do not need to pass a separate projectId for routine SDK calls.

List All Projects

typescript
const projects = await client.projects.listProjects();
 
for (const project of projects) {
  console.log(`${project.name} (${project.slug})`);
}

With a project-scoped API key, this returns the project attached to that key. Broader project lifecycle operations are intended for the dashboard and internal admin tooling.

Create a Project

Creating a project seeds RBAC permissions and a super_admin role automatically.

For the product's dashboard-first workflow, create projects in the dashboard first. Project-scoped API keys operate inside an existing project; they are not the primary bootstrap path for creating new ones.

typescript
const result = await client.projects.createProject({
  name: "Twitter Clone",
  slug: "twitter-clone",
  description: "Social media auth backend",
});
 
if (result.success) {
  console.log(`Project: ${result.project.name} (${result.project.id})`);
}

Get a Project

typescript
const project = await client.projects.getProject("project-id-here");
 
if (project) {
  console.log(project.name, project.slug);
}

Update a Project

typescript
const result = await client.projects.updateProject("project-id-here", {
  name: "Twitter Clone v2",
  description: "Updated social media auth",
});

Delete a Project

Deleting a project removes the project record. Note that this does not cascade-delete project-scoped data (users, organizations, etc.) which requires a separate migration.

typescript
await client.projects.deleteProject("project-id-here");

Bootstrap Default Project

Typically called by the dashboard on first load. If no projects exist, creates a "Default Project" with seeded RBAC permissions.

typescript
const result = await client.projects.ensureDefaultProject();
 
if (result.created) {
  console.log("Default project created:", result.project.name);
} else {
  console.log("Projects already exist:", result.project?.name);
}

API Endpoints

All project management endpoints are under /api/auth/banata/projects/. Every endpoint requires admin authentication.

MethodPathDescriptionBody
POST/projects/listList all projects(none)
POST/projects/getGet a project by ID{ id }
POST/projects/createCreate project + seed RBAC{ name, slug, description?, logoUrl? }
POST/projects/updateUpdate project metadata{ id, name?, slug?, description?, logoUrl? }
POST/projects/deleteDelete project record{ id }
POST/projects/ensure-defaultBootstrap default project if none exist(none)

Validation Rules

  • Project slug: 1-100 characters, lowercase alphanumeric with hyphens (/^[a-z0-9-]+$/), must be unique across all projects.
  • Project name: 1-100 characters.
  • Description: Max 500 characters.
  • Logo URL: Must be a valid URL.

Response Examples

Create Project

json
{
  "success": true,
  "project": {
    "id": "jh77abc123def456...",
    "name": "Twitter Clone",
    "slug": "twitter-clone",
    "ownerId": "jh77xyz789...",
    "createdAt": 1709337600000,
    "updatedAt": 1709337600000
  }
}

Migrations

Banata Auth includes built-in migrations for managing project data. These run inside the Convex component via CLI.

Backfill Project IDs

If you have existing records that were created before project isolation was enforced, they may be missing projectId. These records are hidden from dashboard views. To assign them to a project:

bash
# Auto-detect first project:
npx convex run --component banataAuth migrations:backfillProjectId
 
# Target a specific project:
npx convex run --component banataAuth migrations:backfillProjectId '{"targetProjectId": "abc123"}'

Clear All Data

For a complete fresh start (wipes ALL records from ALL tables):

bash
npx convex run --component banataAuth migrations:clearAllData

All migrations are batched. If a migration returns done: false, re-run with the startFromTable value from the response.


Security Considerations

  1. Admin-only access: All project management endpoints require admin authentication. Regular users cannot create, modify, or delete projects.

  2. Cascade on delete: Deleting a project removes only the project record. Project-scoped data (users, sessions, organizations, etc.) is not automatically cascade-deleted. Run the clearAllData migration or plan data cleanup separately before deleting a project.

  3. Scope injection: The dashboard automatically injects projectId into its own API calls. Remote SDK integrations normally rely on project-scoped API keys instead of passing projectId manually.

  4. Strict isolation: Records without projectId are excluded from all dashboard views. This prevents legacy/orphaned data from appearing in any project.


What's Next