# AionUi Pairing and Authorization System: 6-Digit Codes and Session Isolation for IM Platforms

> Discover AionUi's secure IM pairing system. Use 6-digit codes for cryptographically isolated, expiring sessions. Enhance your chat security with AionUi.

- Repository: [OfficeAI/AionUi](https://github.com/iofficeai/aionui)
- Tags: internals
- Published: 2026-02-16

---

**AionUi uses a desktop-controlled pairing workflow where remote IM bots display 6-digit codes that users confirm in the local Settings UI, creating cryptographically isolated per-chat sessions stored in SQLite with 10-minute expiry windows.**

AionUi connects external instant-messaging platforms like Telegram, Lark, and DingTalk to a local AI assistant through a secure pairing and authorization system. This architecture, implemented in the [iOfficeAI/AionUi](https://github.com/iOfficeAI/AionUi) repository, ensures that each remote chat group or private conversation runs in complete isolation, preventing cross-contamination between different user sessions.

## How the Pairing Workflow Works

The pairing process is managed entirely on the desktop side, with the remote bot acting only as a display mechanism for the 6-digit authorization code.

### Step 1: Initiating the Pairing Request

When a user sends a start command (such as `/start`) to the bot, the platform-specific plugin detects this action and triggers the pairing sequence. In [`src/channels/plugins/telegram/TelegramPlugin.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/telegram/TelegramPlugin.ts) (lines 177-183), the plugin emits a `pairingRequested` event through the `channelBridge`, signaling that a new pairing code must be generated for the remote user.

### Step 2: Generating the 6-Digit Code

The `PairingService` class in [`src/channels/pairing/PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/pairing/PairingService.ts) handles code generation through the `generatePairingCode` method (lines 44-64). The service:

- Generates a random 6-digit numeric code using `generateRandomCode`
- Validates uniqueness against the `assistant_pairing_codes` SQLite table to avoid collisions
- Stores the request with a `pending` status and a 10-minute expiry timestamp

The database schema in [`src/process/database/migrations.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/database/migrations.ts) (lines 24-34) defines the `assistant_pairing_codes` table with a CHECK constraint enforcing valid status values: `pending`, `approved`, `rejected`, or `expired`.

### Step 3: Bot Displays the Code

Once generated, the bot replies to the user with the pairing code formatted through platform-specific card templates. For Lark, [`src/channels/plugins/lark/LarkCards.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/channels/plugins/lark/LarkCards.ts) (lines 145-151) constructs a message card displaying the 6-digit code and instructions to enter it in the AionUi Settings.

### Step 4: User Authorization in Settings

The user opens AionUi's WebUI, navigates to **Settings → Channels → [Platform]**, and enters the 6-digit code. This triggers `PairingService.approvePairing` (lines 141-189 in [`PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/PairingService.ts)), which:

- Validates the code exists and is still pending
- Creates an entry in the `assistant_users` table linking the platform user ID to the local assistant
- Updates the pairing code status to `approved`
- Emits `userAuthorized` through the IPC bridge ([`src/common/ipcBridge.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/common/ipcBridge.ts), lines 486-502)

### Step 5: Confirmation and Session Establishment

The platform plugin receives the `userAuthorized` event and sends a confirmation message to the remote chat. At this point, the user is fully authorized, and subsequent messages from that chat will be routed to an isolated session.

If the user never approves, the code automatically expires after **10 minutes** (see `PAIRING_CONFIG.CODE_EXPIRY_MS`). A background timer (`startCleanupInterval`) runs every minute and removes stale rows (`cleanupExpired`).

## Authorization Verification for Incoming Messages

Every incoming message from an IM platform undergoes an authorization check before processing. The `PairingService.isUserAuthorized` method (lines 9-13 in [`PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/PairingService.ts)) queries the `assistant_users` table to verify that the platform-specific user ID has completed the pairing process. If unauthorized, the system automatically re-initiates the pairing workflow.

```typescript
if (!pairingService.isUserAuthorized(platformUserId, platformType)) {
  // trigger pairing
}

```

## Per-Chat Session Isolation Architecture

AionUi guarantees that conversations from different chat groups or private chats never share context or history, even when originating from the same IM platform account.

### Database Schema for Isolation

The isolation mechanism relies on two key schema additions in [`src/process/database/migrations.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/database/migrations.ts):

- **Conversations table**: Migration v14 adds a `channel_chat_id` column (lines 720-750) to store the remote chat identifier
- **Assistant sessions table**: Migration adds a `chat_id` column (lines 753-758) linking sessions to specific remote chats

### Runtime Session Resolution

When processing incoming messages, platform adapters extract the `chat_id` from the platform event (e.g., `event.chat_id` in Lark or DingTalk) and pass it through the bridge to the core session manager. The database query in [`src/process/database/index.ts`](https://github.com/iOfficeAI/AionUi/blob/main/src/process/database/index.ts) (lines 1020-1035) resolves the correct session by matching `source`, `channel_chat_id`, and `type`, ensuring complete separation between different chat contexts.

## Security Features and Resilience

The pairing system implements multiple security layers to prevent unauthorized access and ensure system stability:

- **Short-lived numeric codes**: 6-digit codes expire after 10 minutes (`PAIRING_CONFIG.CODE_EXPIRY_MS`)
- **Collision avoidance**: The `generateUniqueCode` method attempts up to 10 times to generate a non-conflicting code
- **Status enforcement**: The database schema enforces valid state transitions through CHECK constraints on the `status` column (`pending`, `approved`, `rejected`, `expired`)
- **Automatic cleanup**: A background interval (`startCleanupInterval`) runs every minute to purge expired codes via `cleanupExpired` (lines 85-93 in [`PairingService.ts`](https://github.com/iOfficeAI/AionUi/blob/main/PairingService.ts))

## Implementation Examples

The following TypeScript examples demonstrate how to interact with the pairing system programmatically.

### Generating a Pairing Code

```typescript
import { getPairingService } from '@/channels/pairing/PairingService';

async function startPairing(userId: string) {
  const { code, expiresAt } = await getPairingService().generatePairingCode(
    userId,
    'telegram',
    'John Doe'          // optional display name
  );
  console.log(`Send this code to the user: ${code} (expires ${new Date(expiresAt)})`);
}

```

### Approving a Code in the Settings UI

```typescript
import { getPairingService } from '@/channels/pairing/PairingService';

async function approve(code: string) {
  const result = await getPairingService().approvePairing(code);
  if (result.success) {
    console.log('User authorised:', result.user?.id);
  } else {
    console.error('Failed to approve:', result.error);
  }
}

```

### Resolving Isolated Sessions for Incoming Messages

```typescript
import { getDatabase } from '@/process/database';
import { PluginType } from '@/channels/types';

function findSession(platformUserId: string, chatId: string) {
  const db = getDatabase();
  const rows = db.prepare(`
    SELECT * FROM assistant_sessions
    WHERE user_id = (SELECT id FROM assistant_users WHERE platform_user_id = ? AND platform_type = ?)
      AND chat_id = ?
  `).all(platformUserId, 'dingtalk' as PluginType, chatId);
  return rows[0];
}

```

## Summary

- AionUi implements a **desktop-controlled pairing workflow** where IM bots only display 6-digit codes while authorization happens locally in the Settings UI.
- **PairingService** manages code generation, collision detection, and 10-minute expiry windows through the `assistant_pairing_codes` table.
- **Session isolation** is enforced via `channel_chat_id` and `chat_id` columns in the database, ensuring separate conversation histories for different IM chat groups.
- **Security features** include automatic cleanup of expired codes, status enforcement via CHECK constraints, and up to 10 attempts for unique code generation.

## Frequently Asked Questions

### How long does a pairing code remain valid?

Pairing codes expire after **10 minutes** from generation. The `PAIRING_CONFIG.CODE_EXPIRY_MS` constant defines this timeout, and a background cleanup task runs every minute to purge expired entries from the `assistant_pairing_codes` table.

### Can the same user pair with multiple IM platforms simultaneously?

Yes. The `assistant_users` table stores unique entries keyed by both `platform_user_id` and `platform_type`. A single local assistant can maintain authorized sessions across Telegram, Lark, DingTalk, and other supported platforms without conflict.

### What prevents two different chat groups from seeing each other's conversation history?

AionUi enforces **per-chat session isolation** through database schema design. The `conversations` table includes a `channel_chat_id` column, and `assistant_sessions` stores a `chat_id` reference. When processing messages, the system queries by both user ID and chat ID, ensuring complete separation between different remote chat contexts.

### How does the system handle duplicate pairing code generation?

The `generateUniqueCode` method in `PairingService` implements collision avoidance by attempting up to **10 times** to generate a non-conflicting 6-digit code. If all attempts fail (extremely unlikely given the 1,000,000 possible combinations), the method throws an error. Successfully generated codes are immediately inserted into the `assistant_pairing_codes` table with a unique constraint.