AionUi Pairing and Authorization System: 6-Digit Codes and Session Isolation for IM Platforms
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 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 (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 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_codesSQLite table to avoid collisions - Stores the request with a
pendingstatus and a 10-minute expiry timestamp
The database schema in 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 (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), which:
- Validates the code exists and is still pending
- Creates an entry in the
assistant_userstable linking the platform user ID to the local assistant - Updates the pairing code status to
approved - Emits
userAuthorizedthrough the IPC bridge (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) 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.
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:
- Conversations table: Migration v14 adds a
channel_chat_idcolumn (lines 720-750) to store the remote chat identifier - Assistant sessions table: Migration adds a
chat_idcolumn (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 (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
generateUniqueCodemethod 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
statuscolumn (pending,approved,rejected,expired) - Automatic cleanup: A background interval (
startCleanupInterval) runs every minute to purge expired codes viacleanupExpired(lines 85-93 inPairingService.ts)
Implementation Examples
The following TypeScript examples demonstrate how to interact with the pairing system programmatically.
Generating a Pairing Code
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
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
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_codestable. - Session isolation is enforced via
channel_chat_idandchat_idcolumns 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →