Initial commit: OpenClaw Mattermost Extension
This commit is contained in:
+864
@@ -0,0 +1,864 @@
|
||||
# API Reference
|
||||
|
||||
Complete reference for the Enhanced Mattermost Extension API.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Actions](#actions)
|
||||
- [Interactive Directives](#interactive-directives)
|
||||
- [Configuration Types](#configuration-types)
|
||||
- [Error Handling](#error-handling)
|
||||
- [Security Audit](#security-audit)
|
||||
- [Client API](#client-api)
|
||||
|
||||
## Actions
|
||||
|
||||
### editMessage
|
||||
|
||||
Edit an existing message posted by the bot.
|
||||
|
||||
```typescript
|
||||
import { editMessage } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
const result = await editMessage({
|
||||
cfg: OpenClawConfig,
|
||||
postId: string,
|
||||
channelId: string,
|
||||
message?: string,
|
||||
props?: Record<string, unknown>,
|
||||
accountId?: string | null,
|
||||
fetchImpl?: MattermostFetch
|
||||
});
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration object |
|
||||
| `postId` | `string` | Yes | ID of the post to edit |
|
||||
| `channelId` | `string` | Yes | Channel ID containing the post |
|
||||
| `message` | `string` | No | New message content |
|
||||
| `props` | `Record<string, unknown>` | No | Additional post properties |
|
||||
| `accountId` | `string \| null` | No | Specific account to use |
|
||||
| `fetchImpl` | `MattermostFetch` | No | Custom fetch implementation |
|
||||
|
||||
**Returns:**
|
||||
|
||||
```typescript
|
||||
type EditMessageResult =
|
||||
| { ok: true; postId: string; channelId: string }
|
||||
| { ok: false; error: string; errorCode?: string };
|
||||
```
|
||||
|
||||
**Error Codes:**
|
||||
- `PERMISSION_DENIED` - Cannot edit other users' messages or system messages
|
||||
- `POST_NOT_FOUND` - Post was deleted or doesn't exist
|
||||
- `RATE_LIMITED` - Too many edit requests
|
||||
- `AUTH_FAILED` - Invalid or expired token
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
const result = await editMessage({
|
||||
cfg: openclawConfig,
|
||||
postId: 'abc123def456ghi789jkl012',
|
||||
channelId: 'channel123',
|
||||
message: 'Updated message content'
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
console.log(`Edited post ${result.postId}`);
|
||||
} else {
|
||||
console.error(`Edit failed: ${result.error} (${result.errorCode})`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### deleteMessage
|
||||
|
||||
Delete a message posted by the bot.
|
||||
|
||||
```typescript
|
||||
import { deleteMessage } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
const result = await deleteMessage({
|
||||
cfg: OpenClawConfig,
|
||||
postId: string,
|
||||
channelId?: string,
|
||||
accountId?: string | null,
|
||||
fetchImpl?: MattermostFetch
|
||||
});
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration object |
|
||||
| `postId` | `string` | Yes | ID of the post to delete |
|
||||
| `channelId` | `string` | No | Channel ID (for validation) |
|
||||
| `accountId` | `string \| null` | No | Specific account to use |
|
||||
| `fetchImpl` | `MattermostFetch` | No | Custom fetch implementation |
|
||||
|
||||
**Returns:**
|
||||
|
||||
```typescript
|
||||
type DeleteMessageResult = { ok: true } | { ok: false; error: string };
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- Returns `ok: true` if post is already deleted (idempotent)
|
||||
- Cannot delete system messages
|
||||
- Can only delete bot's own messages
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
const result = await deleteMessage({
|
||||
cfg: openclawConfig,
|
||||
postId: 'abc123def456ghi789jkl012'
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
console.log('Message deleted successfully');
|
||||
} else {
|
||||
console.error(`Delete failed: ${result.error}`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### downloadFile
|
||||
|
||||
Download a file from Mattermost with security validations.
|
||||
|
||||
```typescript
|
||||
import { downloadFile } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
const result = await downloadFile({
|
||||
cfg: OpenClawConfig,
|
||||
fileId: string,
|
||||
destinationPath: string,
|
||||
maxSize?: number,
|
||||
accountId?: string | null,
|
||||
fetchImpl?: MattermostFetch,
|
||||
allowedMimePrefixes?: string[]
|
||||
});
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Required | Default | Description |
|
||||
|-----------|------|----------|---------|-------------|
|
||||
| `cfg` | `OpenClawConfig` | Yes | - | OpenClaw configuration |
|
||||
| `fileId` | `string` | Yes | - | Mattermost file ID |
|
||||
| `destinationPath` | `string` | Yes | - | Local path to save file |
|
||||
| `maxSize` | `number` | No | 100MB | Maximum file size in bytes |
|
||||
| `accountId` | `string \| null` | No | - | Specific account to use |
|
||||
| `fetchImpl` | `MattermostFetch` | No | - | Custom fetch implementation |
|
||||
| `allowedMimePrefixes` | `string[]` | No | Built-in list | Allowed MIME types |
|
||||
|
||||
**Returns:**
|
||||
|
||||
```typescript
|
||||
type DownloadFileResult =
|
||||
| { ok: true; filePath: string; fileId: string; metadata: FileMetadata }
|
||||
| { ok: false; error: string; errorCode?: string };
|
||||
|
||||
type FileMetadata = {
|
||||
id: string;
|
||||
name: string;
|
||||
mimeType: string;
|
||||
size: number;
|
||||
extension: string;
|
||||
};
|
||||
```
|
||||
|
||||
**Error Codes:**
|
||||
- `CONFIG_ERROR` - Missing botToken or baseUrl
|
||||
- `INVALID_PARAMS` - Missing fileId or destinationPath
|
||||
- `FILE_TOO_LARGE` - File exceeds maxSize
|
||||
- `EMPTY_FILE` - File has no content
|
||||
- `BLOCKED_FILE_TYPE` - Executable file extension
|
||||
- `BLOCKED_MIME_TYPE` - MIME type not in allowlist
|
||||
- `FILE_NOT_FOUND` - File doesn't exist
|
||||
- `PERMISSION_DENIED` - No access to file
|
||||
- `AUTH_FAILED` - Invalid token
|
||||
- `DOWNLOAD_FAILED` - Network or server error
|
||||
|
||||
**Security Restrictions:**
|
||||
|
||||
Blocked file extensions:
|
||||
```
|
||||
.exe, .sh, .bat, .cmd, .com, .msi, .dll, .so, .dylib,
|
||||
.app, .dmg, .pkg, .deb, .rpm, .apk, .ipa, .jar, .war,
|
||||
.ear, .py, .rb, .pl, .php, .js, .ts, .vbs, .ps1, .psm1,
|
||||
.scr, .hta, .bin, .run, .out, .elf
|
||||
```
|
||||
|
||||
Default allowed MIME types:
|
||||
```
|
||||
image/*, video/*, audio/*, text/*
|
||||
application/pdf, application/json, application/xml
|
||||
application/csv, application/zip, application/gzip, application/tar
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
const result = await downloadFile({
|
||||
cfg: openclawConfig,
|
||||
fileId: 'file123abc',
|
||||
destinationPath: '/tmp/report.pdf',
|
||||
maxSize: 50 * 1024 * 1024 // 50MB
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
console.log(`Downloaded: ${result.filePath}`);
|
||||
console.log(`Size: ${result.metadata.size} bytes`);
|
||||
console.log(`Type: ${result.metadata.mimeType}`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### sendMessage
|
||||
|
||||
Send a message to a Mattermost channel.
|
||||
|
||||
```typescript
|
||||
import { sendMessage } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
await sendMessage({
|
||||
cfg: OpenClawConfig,
|
||||
channelId: string,
|
||||
message: string,
|
||||
rootId?: string,
|
||||
fileIds?: string[],
|
||||
props?: Record<string, unknown>,
|
||||
accountId?: string | null
|
||||
});
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|------|----------|-------------|
|
||||
| `cfg` | `OpenClawConfig` | Yes | OpenClaw configuration |
|
||||
| `channelId` | `string` | Yes | Target channel ID |
|
||||
| `message` | `string` | Yes | Message content (supports Markdown) |
|
||||
| `rootId` | `string` | No | Parent post ID for threaded replies |
|
||||
| `fileIds` | `string[]` | No | Array of uploaded file IDs to attach |
|
||||
| `props` | `Record<string, unknown>` | No | Additional post properties |
|
||||
| `accountId` | `string \| null` | No | Specific account to use |
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
await sendMessage({
|
||||
cfg: openclawConfig,
|
||||
channelId: 'channel123',
|
||||
message: 'Hello **World**!', // Markdown supported
|
||||
rootId: 'parent-post-id' // Thread reply
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### addReaction
|
||||
|
||||
Add an emoji reaction to a message.
|
||||
|
||||
```typescript
|
||||
import { addReaction } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
await addReaction({
|
||||
cfg: OpenClawConfig,
|
||||
postId: string,
|
||||
emoji: string, // e.g., 'thumbsup', 'white_check_mark'
|
||||
accountId?: string | null
|
||||
});
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
await addReaction({
|
||||
cfg: openclawConfig,
|
||||
postId: 'post123',
|
||||
emoji: 'thumbsup'
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### uploadFile
|
||||
|
||||
Upload a file to Mattermost.
|
||||
|
||||
```typescript
|
||||
import { uploadFile } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
const fileInfo = await uploadFile({
|
||||
cfg: OpenClawConfig,
|
||||
channelId: string,
|
||||
buffer: Buffer,
|
||||
fileName: string,
|
||||
contentType?: string,
|
||||
accountId?: string | null
|
||||
});
|
||||
```
|
||||
|
||||
**Returns:** `MattermostFileInfo` object with `id`, `name`, `mime_type`, `size`
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
const buffer = readFileSync('./document.pdf');
|
||||
const fileInfo = await uploadFile({
|
||||
cfg: openclawConfig,
|
||||
channelId: 'channel123',
|
||||
buffer,
|
||||
fileName: 'document.pdf',
|
||||
contentType: 'application/pdf'
|
||||
});
|
||||
|
||||
// Use the file ID when sending a message
|
||||
await sendMessage({
|
||||
cfg: openclawConfig,
|
||||
channelId: 'channel123',
|
||||
message: 'Here is the document:',
|
||||
fileIds: [fileInfo.id]
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Interactive Directives
|
||||
|
||||
Interactive directives allow you to embed buttons and dropdowns in messages using a special syntax. These are parsed and converted to Mattermost interactive components.
|
||||
|
||||
### Button Directive
|
||||
|
||||
Syntax:
|
||||
```
|
||||
[[mattermost_buttons: Label1:value1:style1, Label2:value2:style2, ...]]
|
||||
```
|
||||
|
||||
**Format:**
|
||||
- `Label`: Display text on the button (max 30 chars)
|
||||
- `value`: Value sent when button is clicked
|
||||
- `style`: Button style - `default`, `primary`, or `danger` (optional, defaults to `default`)
|
||||
|
||||
**Maximum:** 5 buttons per directive
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
const message = `
|
||||
Please review this request:
|
||||
|
||||
[[mattermost_buttons:
|
||||
Approve:approve_request:primary,
|
||||
Reject:reject_request:danger,
|
||||
Review Later:review_later:default
|
||||
]]
|
||||
`;
|
||||
|
||||
await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Select/Dropdown Directive
|
||||
|
||||
Syntax:
|
||||
```
|
||||
[[mattermost_select: Placeholder | Label1:value1, Label2:value2, ...]]
|
||||
```
|
||||
|
||||
**Format:**
|
||||
- `Placeholder`: Text shown when nothing selected (optional, defaults to "Choose an option")
|
||||
- `Label`: Display text for the option
|
||||
- `value`: Value sent when option is selected
|
||||
|
||||
**Maximum:** 100 options per directive
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
const message = `
|
||||
Select a priority level:
|
||||
|
||||
[[mattermost_select:
|
||||
Select Priority |
|
||||
Low:low,
|
||||
Medium:medium,
|
||||
High:high,
|
||||
Critical:critical
|
||||
]]
|
||||
`;
|
||||
|
||||
await sendMessage({ cfg: openclawConfig, channelId: 'channel123', message });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Multiple Directives
|
||||
|
||||
You can combine multiple directives in a single message:
|
||||
|
||||
```typescript
|
||||
const message = `
|
||||
Deployment ready for production.
|
||||
|
||||
[[mattermost_buttons: Deploy:deploy:primary, Cancel:cancel:danger]]
|
||||
|
||||
Select deployment region:
|
||||
[[mattermost_select: Choose Region | US East:us-east, US West:us-west, EU:eu, APAC:apac]]
|
||||
`;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Handling Interactions
|
||||
|
||||
When a user interacts with buttons or selects, Mattermost sends a callback to your configured `interactions.callbackBaseUrl`:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "button" | "select",
|
||||
"value": "the_button_or_option_value",
|
||||
"user_id": "mattermost_user_id",
|
||||
"channel_id": "channel_id",
|
||||
"post_id": "original_post_id",
|
||||
"context": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```json
|
||||
{
|
||||
"channels": {
|
||||
"mattermost": {
|
||||
"interactions": {
|
||||
"callbackBaseUrl": "https://your-bot.example.com/callbacks",
|
||||
"allowedSourceIps": ["10.0.0.0/8", "192.168.1.0/24"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Types
|
||||
|
||||
### MattermostAccountConfig
|
||||
|
||||
```typescript
|
||||
interface MattermostAccountConfig {
|
||||
// Core settings
|
||||
baseUrl?: string;
|
||||
botToken?: SecretInput;
|
||||
pat?: SecretInput;
|
||||
enabled?: boolean;
|
||||
name?: string;
|
||||
|
||||
// Access control
|
||||
dmPolicy?: 'open' | 'allowlist' | 'pairing';
|
||||
allowFrom?: Array<string | number>;
|
||||
groupPolicy?: 'open' | 'allowlist';
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
requireMention?: boolean;
|
||||
|
||||
// Chat modes
|
||||
chatmode?: 'oncall' | 'onmessage' | 'onchar';
|
||||
oncharPrefixes?: string[];
|
||||
|
||||
// Message settings
|
||||
textChunkLimit?: number;
|
||||
chunkMode?: 'length' | 'newline';
|
||||
blockStreaming?: boolean;
|
||||
blockStreamingCoalesce?: BlockStreamingConfig;
|
||||
replyToMode?: 'off' | 'first' | 'all';
|
||||
responsePrefix?: string;
|
||||
|
||||
// Slash commands
|
||||
commands?: {
|
||||
native?: boolean | 'auto';
|
||||
nativeSkills?: boolean | 'auto';
|
||||
callbackPath?: string;
|
||||
callbackUrl?: string;
|
||||
};
|
||||
|
||||
// Interactions
|
||||
interactions?: {
|
||||
callbackBaseUrl?: string;
|
||||
allowedSourceIps?: string[];
|
||||
};
|
||||
|
||||
// Actions
|
||||
actions?: {
|
||||
reactions?: boolean;
|
||||
downloadFile?: boolean;
|
||||
delete?: boolean;
|
||||
};
|
||||
|
||||
// Security
|
||||
allowPrivateNetwork?: boolean;
|
||||
configWrites?: boolean;
|
||||
dangerouslyAllowNameMatching?: boolean;
|
||||
|
||||
// Retry
|
||||
dmChannelRetry?: {
|
||||
maxRetries?: number;
|
||||
initialDelayMs?: number;
|
||||
maxDelayMs?: number;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
// Group-specific settings
|
||||
groups?: Record<string, {
|
||||
requireMention?: boolean;
|
||||
}>;
|
||||
|
||||
// Capabilities
|
||||
capabilities?: string[] | { interactiveReplies?: boolean };
|
||||
markdown?: MarkdownConfig;
|
||||
}
|
||||
```
|
||||
|
||||
### SecretInput
|
||||
|
||||
Secret values can be provided in multiple formats:
|
||||
|
||||
```typescript
|
||||
type SecretInput =
|
||||
| string // Raw value (not recommended for production)
|
||||
| { $env: string } // Environment variable reference
|
||||
| { $file: string } // File path containing secret
|
||||
| { $secretRef: string }; // Secret manager reference
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
|
||||
```json
|
||||
{
|
||||
"botToken": "${MATTERMOST_BOT_TOKEN}",
|
||||
"botToken": { "$env": "MATTERMOST_BOT_TOKEN" },
|
||||
"botToken": { "$file": "/run/secrets/bot_token" }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Error Classes
|
||||
|
||||
All errors extend `MattermostError` with structured information:
|
||||
|
||||
```typescript
|
||||
class MattermostError extends Error {
|
||||
readonly code: ErrorCode;
|
||||
readonly context: ErrorContext;
|
||||
readonly retryable: boolean;
|
||||
readonly userMessage: string;
|
||||
readonly retryAfterMs?: number;
|
||||
|
||||
toJSON(): Record<string, unknown>;
|
||||
toLogString(): string;
|
||||
}
|
||||
```
|
||||
|
||||
### Error Codes
|
||||
|
||||
| Code | Description | Retryable |
|
||||
|------|-------------|-----------|
|
||||
| `MATTERMOST_API_ERROR` | General API error | Depends on status |
|
||||
| `MATTERMOST_API_TIMEOUT` | Request timeout | Yes |
|
||||
| `MATTERMOST_API_RATE_LIMIT` | Rate limited | Yes |
|
||||
| `AUTHENTICATION_FAILED` | Invalid credentials | No |
|
||||
| `TOKEN_INVALID` | Token revoked | No |
|
||||
| `TOKEN_EXPIRED` | Token expired | No |
|
||||
| `PERMISSION_DENIED` | Access denied | No |
|
||||
| `POST_EDIT_DENIED` | Cannot edit post | No |
|
||||
| `CHANNEL_ACCESS_DENIED` | Channel access denied | No |
|
||||
| `RESOURCE_NOT_FOUND` | Resource not found | No |
|
||||
| `VALIDATION_ERROR` | Invalid input | No |
|
||||
| `CONFIGURATION_ERROR` | Bad configuration | No |
|
||||
| `MISSING_BOT_TOKEN` | No bot token | No |
|
||||
| `MISSING_BASE_URL` | No base URL | No |
|
||||
| `NETWORK_ERROR` | Network failure | Yes |
|
||||
| `CONNECTION_ERROR` | Cannot connect | Yes |
|
||||
| `TIMEOUT_ERROR` | Operation timed out | Yes |
|
||||
| `FILE_TOO_LARGE` | File exceeds limit | No |
|
||||
| `BLOCKED_FILE_TYPE` | Executable file | No |
|
||||
| `DOWNLOAD_FAILED` | Download error | Yes |
|
||||
|
||||
### Using Error Boundaries
|
||||
|
||||
```typescript
|
||||
import { globalErrorBoundary, MattermostError } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
// Register error handler
|
||||
const unsubscribe = globalErrorBoundary.onError((error) => {
|
||||
if (error instanceof MattermostError) {
|
||||
console.error(`[${error.code}] ${error.toLogString()}`);
|
||||
|
||||
if (error.retryable) {
|
||||
console.log(`Retry after: ${error.retryAfterMs}ms`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Wrap operations
|
||||
const safeOperation = globalErrorBoundary.wrap(
|
||||
riskyOperation,
|
||||
'my-operation',
|
||||
{ accountId: 'default' }
|
||||
);
|
||||
|
||||
// Later: unsubscribe
|
||||
disunsubscribe();
|
||||
```
|
||||
|
||||
### Retry Configuration
|
||||
|
||||
```typescript
|
||||
import { withRetry } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
const result = await withRetry(
|
||||
'my-operation',
|
||||
async () => {
|
||||
// Your operation here
|
||||
return await someApiCall();
|
||||
},
|
||||
{
|
||||
maxRetries: 5,
|
||||
initialDelayMs: 1000,
|
||||
maxDelayMs: 30000,
|
||||
timeoutMs: 60000,
|
||||
onRetry: ({ attempt, maxRetries, delayMs, error }) => {
|
||||
console.log(`Retry ${attempt}/${maxRetries} after ${delayMs}ms: ${error.message}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Audit
|
||||
|
||||
### collectMattermostSecurityAuditFindings
|
||||
|
||||
Run a comprehensive security audit on your configuration:
|
||||
|
||||
```typescript
|
||||
import { collectMattermostSecurityAuditFindings } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
const findings = await collectMattermostSecurityAuditFindings({
|
||||
cfg: OpenClawConfig,
|
||||
accountId?: string | null,
|
||||
account: ResolvedMattermostAccount
|
||||
});
|
||||
```
|
||||
|
||||
**Returns:** Array of `SecurityAuditFinding`:
|
||||
|
||||
```typescript
|
||||
type SecurityAuditFinding = {
|
||||
checkId: string; // Unique check identifier
|
||||
severity: 'info' | 'warn' | 'critical';
|
||||
title: string;
|
||||
detail: string;
|
||||
remediation?: string;
|
||||
};
|
||||
```
|
||||
|
||||
### Audit Checks
|
||||
|
||||
| Check ID | Severity | Description |
|
||||
|----------|----------|-------------|
|
||||
| `channels.mattermost.pat.hardcoded_token` | Critical | Bot token hardcoded in config |
|
||||
| `channels.mattermost.pat.short_token` | Warn | Token unusually short |
|
||||
| `channels.mattermost.pat.missing_token` | Critical | No bot token configured |
|
||||
| `channels.mattermost.https.insecure_url` | Critical | HTTP instead of HTTPS |
|
||||
| `channels.mattermost.https.missing_url` | Critical | No base URL configured |
|
||||
| `channels.mattermost.input.insecure_callback` | Critical | Callback URL uses HTTP |
|
||||
| `channels.mattermost.input.no_source_ip_restriction` | Warn | No IP allowlist on callbacks |
|
||||
| `channels.mattermost.network.private_access_enabled` | Warn | Private network access enabled |
|
||||
| `channels.mattermost.commands.access_groups_disabled` | Critical | Commands bypass access groups |
|
||||
| `channels.mattermost.commands.no_allowlist` | Warn | No allowlist for slash commands |
|
||||
| `channels.mattermost.config.legacy_dm_policy` | Info | Legacy dm.policy config found |
|
||||
| `channels.mattermost.config.legacy_allow_from` | Info | Legacy dm.allowFrom config found |
|
||||
| `channels.mattermost.allowlist.mutable_entries` | Warn | Non-ID entries in allowlist |
|
||||
| `channels.mattermost.actions.download_file_enabled` | Info | File download enabled |
|
||||
| `channels.mattermost.actions.delete_enabled` | Info | Message delete enabled |
|
||||
| `channels.mattermost.groups.open_policy` | Warn | Open group policy |
|
||||
| `channels.mattermost.dm.open_policy` | Warn | Open DM policy |
|
||||
| `channels.mattermost.dm.pairing_mode` | Info | DM pairing mode active |
|
||||
|
||||
**Example:**
|
||||
|
||||
```typescript
|
||||
import {
|
||||
collectMattermostSecurityAuditFindings,
|
||||
collectAllMattermostSecurityAuditFindings
|
||||
} from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
// Audit single account
|
||||
const findings = await collectMattermostSecurityAuditFindings({
|
||||
cfg: openclawConfig,
|
||||
accountId: 'default',
|
||||
account: resolvedAccount
|
||||
});
|
||||
|
||||
// Audit all accounts
|
||||
const allFindings = await collectAllMattermostSecurityAuditFindings({
|
||||
cfg: openclawConfig,
|
||||
listAccounts: (cfg) => resolveAllAccounts(cfg)
|
||||
});
|
||||
|
||||
// Display findings by severity
|
||||
const critical = findings.filter(f => f.severity === 'critical');
|
||||
const warnings = findings.filter(f => f.severity === 'warn');
|
||||
|
||||
console.log(`Critical issues: ${critical.length}`);
|
||||
for (const finding of critical) {
|
||||
console.log(`\n[!] ${finding.title}`);
|
||||
console.log(` ${finding.detail}`);
|
||||
console.log(` Fix: ${finding.remediation}`);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Client API
|
||||
|
||||
### createMattermostClient
|
||||
|
||||
Create a low-level Mattermost API client:
|
||||
|
||||
```typescript
|
||||
import { createMattermostClient } from '@lumbridgecorp/openclaw-mattermost';
|
||||
|
||||
const client = createMattermostClient({
|
||||
baseUrl: string;
|
||||
botToken: string;
|
||||
pat?: string;
|
||||
fetchImpl?: MattermostFetch;
|
||||
allowPrivateNetwork?: boolean;
|
||||
accountId?: string;
|
||||
});
|
||||
```
|
||||
|
||||
**Client Methods:**
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `request<T>(path, init?)` | Make authenticated API request |
|
||||
| `fetchImpl(input, init?)` | Low-level fetch with guards |
|
||||
|
||||
**Request Examples:**
|
||||
|
||||
```typescript
|
||||
// Get current user
|
||||
const me = await client.request<MattermostUser>('/users/me');
|
||||
|
||||
// Get a channel
|
||||
const channel = await client.request<MattermostChannel>(`/channels/${channelId}`);
|
||||
|
||||
// Create a post
|
||||
const post = await client.request<MattermostPost>('/posts', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
channel_id: channelId,
|
||||
message: 'Hello World'
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
### Helper Functions
|
||||
|
||||
```typescript
|
||||
// User operations
|
||||
fetchMattermostMe(client) -> MattermostUser
|
||||
fetchMattermostUser(client, userId) -> MattermostUser
|
||||
fetchMattermostUserByUsername(client, username) -> MattermostUser
|
||||
|
||||
// Channel operations
|
||||
fetchMattermostChannel(client, channelId) -> MattermostChannel
|
||||
fetchMattermostChannelByName(client, teamId, channelName) -> MattermostChannel
|
||||
createMattermostDirectChannel(client, userIds) -> MattermostChannel
|
||||
createMattermostDirectChannelWithRetry(client, userIds, options) -> MattermostChannel
|
||||
|
||||
// Post operations
|
||||
createMattermostPost(client, params) -> MattermostPost
|
||||
updateMattermostPost(client, postId, params) -> MattermostPost
|
||||
|
||||
// File operations
|
||||
uploadMattermostFile(client, params) -> MattermostFileInfo
|
||||
|
||||
// Other
|
||||
sendMattermostTyping(client, { channelId, parentId }) -> void
|
||||
fetchMattermostUserTeams(client, userId) -> MattermostTeam[]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Type Exports
|
||||
|
||||
```typescript
|
||||
import {
|
||||
// Config types
|
||||
MattermostAccountConfig,
|
||||
MattermostConfig,
|
||||
SecretInput,
|
||||
|
||||
// Client types
|
||||
MattermostClient,
|
||||
MattermostFetch,
|
||||
MattermostUser,
|
||||
MattermostChannel,
|
||||
MattermostPost,
|
||||
MattermostFileInfo,
|
||||
MattermostTeam,
|
||||
|
||||
// Action types
|
||||
EditMessageParams,
|
||||
EditMessageResult,
|
||||
DeleteMessageParams,
|
||||
DeleteMessageResult,
|
||||
DownloadFileParams,
|
||||
DownloadFileResult,
|
||||
FileMetadata,
|
||||
|
||||
// Error types
|
||||
MattermostError,
|
||||
MattermostErrorOptions,
|
||||
ErrorContext,
|
||||
ErrorCode,
|
||||
MattermostAPIError,
|
||||
AuthenticationError,
|
||||
ValidationError,
|
||||
PermissionError,
|
||||
RateLimitError,
|
||||
ResourceNotFoundError,
|
||||
NetworkError,
|
||||
ConfigurationError,
|
||||
|
||||
// Security types
|
||||
SecurityAuditFinding,
|
||||
|
||||
// Retry types
|
||||
RetryConfig,
|
||||
RetryAttemptInfo
|
||||
} from '@lumbridgecorp/openclaw-mattermost';
|
||||
```
|
||||
Reference in New Issue
Block a user