diff --git a/AI-Providers.md b/AI-Providers.md index 462f57b..91880a0 100644 --- a/AI-Providers.md +++ b/AI-Providers.md @@ -1,6 +1,6 @@ # AI Providers -TFTSR supports 5 AI providers, selectable per-session. API keys are stored in the Stronghold encrypted vault. +TFTSR supports 6+ AI providers, including custom providers with flexible authentication and API formats. API keys are stored encrypted with AES-256-GCM. ## Provider Factory @@ -113,6 +113,130 @@ The domain prompt is injected as the first `system` role message in every new co --- +## 6. Custom Provider (MSI GenAI & Others) + +**Status:** ✅ **Implemented** (v0.2.6) + +Custom providers allow integration with non-OpenAI-compatible APIs. The application supports two API formats: + +### Format: OpenAI Compatible (Default) + +Standard OpenAI `/chat/completions` endpoint with Bearer authentication. + +| Field | Default Value | +|-------|--------------| +| `api_format` | `"openai"` | +| `custom_endpoint_path` | `/chat/completions` | +| `custom_auth_header` | `Authorization` | +| `custom_auth_prefix` | `Bearer ` | + +**Use cases:** +- Self-hosted LLMs with OpenAI-compatible APIs +- Custom proxy services +- Enterprise gateways + +--- + +### Format: MSI GenAI + +**Motorola Solutions Internal GenAI Service** — Enterprise AI platform with centralized cost tracking and model access. + +| Field | Value | +|-------|-------| +| `config.provider_type` | `"custom"` | +| `config.api_format` | `"msi_genai"` | +| API URL | `https://genai-service.commandcentral.com/app-gateway` (prod)
`https://genai-service.stage.commandcentral.com/app-gateway` (stage) | +| Auth Header | `x-msi-genai-api-key` | +| Auth Prefix | `` (empty - no Bearer prefix) | +| Endpoint Path | `` (empty - URL includes full path `/api/v2/chat`) | + +**Available Models:** +- `VertexGemini` — Gemini 2.0 Flash (Private/GCP) +- `Claude-Sonnet-4` — Claude Sonnet 4 (Public/Anthropic) +- `ChatGPT4o` — GPT-4o (Public/OpenAI) +- `ChatGPT-5_2-Chat` — GPT-4.5 (Public/OpenAI) +- See [GenAI API User Guide](../GenAI%20API%20User%20Guide.md) for full model list + +**Request Format:** +```json +{ + "model": "VertexGemini", + "prompt": "User's latest message", + "system": "Optional system prompt", + "sessionId": "uuid-for-conversation-continuity", + "userId": "user.name@motorolasolutions.com" +} +``` + +**Response Format:** +```json +{ + "status": true, + "sessionId": "uuid", + "msg": "AI response text", + "initialPrompt": false +} +``` + +**Key Differences from OpenAI:** +- **Single prompt** instead of message array (server manages history via `sessionId`) +- **Response in `msg` field** instead of `choices[0].message.content` +- **Session-based** conversation continuity (no need to resend history) +- **Cost tracking** via `userId` field (optional — defaults to API key owner if omitted) +- **Custom client header**: `X-msi-genai-client: tftsr-devops-investigation` + +**Configuration (Settings → AI Providers → Add Provider):** +``` +Name: MSI GenAI +Type: Custom +API Format: MSI GenAI +API URL: https://genai-service.stage.commandcentral.com/app-gateway +Model: VertexGemini +API Key: (your MSI GenAI API key from portal) +User ID: your.name@motorolasolutions.com (optional) +Endpoint Path: (leave empty) +Auth Header: x-msi-genai-api-key +Auth Prefix: (leave empty) +``` + +**Rate Limits:** +- $50/user/month (enforced server-side) +- Per-API-key quotas available + +**Troubleshooting:** + +| Error | Cause | Solution | +|-------|-------|----------| +| 403 Forbidden | Invalid API key or insufficient permissions | Verify key in MSI GenAI portal, check model access | +| Missing `userId` field | Configuration not saved | Ensure UI shows User ID field when `api_format=msi_genai` | +| No conversation history | `sessionId` not persisted | Session ID stored in `ProviderConfig.session_id` — currently per-provider, not per-conversation | + +**Implementation Details:** +- Backend: `src-tauri/src/ai/openai.rs::chat_msi_genai()` +- Schema: `src-tauri/src/state.rs::ProviderConfig` (added `user_id`, `api_format`, custom auth fields) +- Frontend: `src/pages/Settings/AIProviders.tsx` (conditional UI for MSI GenAI) +- CSP whitelist: `https://genai-service.stage.commandcentral.com` and production domain + +--- + +## Custom Provider Configuration Fields + +All providers support the following optional configuration fields (v0.2.6+): + +| Field | Type | Purpose | Default | +|-------|------|---------|---------| +| `custom_endpoint_path` | `Option` | Override endpoint path | `/chat/completions` | +| `custom_auth_header` | `Option` | Custom auth header name | `Authorization` | +| `custom_auth_prefix` | `Option` | Prefix before API key | `Bearer ` | +| `api_format` | `Option` | API format (`openai` or `msi_genai`) | `openai` | +| `session_id` | `Option` | Session ID for stateful APIs | None | +| `user_id` | `Option` | User ID for cost tracking (MSI GenAI) | None | + +**Backward Compatibility:** +All fields are optional and default to OpenAI-compatible behavior. Existing provider configurations are unaffected. + +--- + ## Adding a New Provider 1. Create `src-tauri/src/ai/{name}.rs` implementing the `Provider` trait diff --git a/Database.md b/Database.md index 67c9001..68b5104 100644 --- a/Database.md +++ b/Database.md @@ -2,7 +2,7 @@ ## Overview -TFTSR uses **SQLite** via `rusqlite` with the `bundled-sqlcipher` feature for AES-256 encryption in production. 10 versioned migrations are tracked in the `_migrations` table. +TFTSR uses **SQLite** via `rusqlite` with the `bundled-sqlcipher` feature for AES-256 encryption in production. 11 versioned migrations are tracked in the `_migrations` table. **DB file location:** `{app_data_dir}/tftsr.db` @@ -38,7 +38,7 @@ pub fn init_db(data_dir: &Path) -> anyhow::Result { --- -## Schema (10 Migrations) +## Schema (11 Migrations) ### 001 — issues @@ -181,6 +181,47 @@ CREATE VIRTUAL TABLE issues_fts USING fts5( ); ``` +### 011 — credentials & integration_config (v0.2.3+) + +**Integration credentials table:** +```sql +CREATE TABLE credentials ( + id TEXT PRIMARY KEY, + service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')), + token_hash TEXT NOT NULL, -- SHA-256 hash for audit + encrypted_token TEXT NOT NULL, -- AES-256-GCM encrypted + created_at TEXT NOT NULL, + expires_at TEXT, + UNIQUE(service) +); +``` + +**Integration configuration table:** +```sql +CREATE TABLE integration_config ( + id TEXT PRIMARY KEY, + service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')), + base_url TEXT NOT NULL, + username TEXT, -- ServiceNow only + project_name TEXT, -- Azure DevOps only + space_key TEXT, -- Confluence only + auto_create_enabled INTEGER NOT NULL DEFAULT 0, + updated_at TEXT NOT NULL, + UNIQUE(service) +); +``` + +**Encryption:** +- OAuth2 tokens encrypted with AES-256-GCM +- Key derived from `TFTSR_DB_KEY` environment variable +- Random 96-bit nonce per encryption +- Format: `base64(nonce || ciphertext || tag)` + +**Usage:** +- OAuth2 flows (Confluence, Azure DevOps): Store encrypted bearer token +- Basic auth (ServiceNow): Store encrypted password +- One credential per service (enforced by UNIQUE constraint) + --- ## Key Design Notes diff --git a/Home.md b/Home.md index d5af4c3..ebbe7fe 100644 --- a/Home.md +++ b/Home.md @@ -24,8 +24,10 @@ - **5-Whys AI Triage** — Interactive guided root cause analysis via multi-turn AI chat - **PII Auto-Redaction** — Detects and redacts sensitive data before any AI send -- **Multi-Provider AI** — OpenAI, Anthropic Claude, Google Gemini, Mistral, AWS Bedrock (via LiteLLM), local Ollama (fully offline) -- **SQLCipher AES-256** — All issue history encrypted at rest +- **Multi-Provider AI** — OpenAI, Anthropic Claude, Google Gemini, Mistral, AWS Bedrock (via LiteLLM), MSI GenAI (Motorola internal), local Ollama (fully offline) +- **Custom Provider Support** — Flexible authentication (Bearer, custom headers) and API formats (OpenAI-compatible, MSI GenAI) +- **External Integrations** — Confluence, ServiceNow, Azure DevOps with OAuth2 PKCE flows +- **SQLCipher AES-256** — All issue history and credentials encrypted at rest - **RCA + Post-Mortem Generation** — Auto-populated Markdown templates, exportable as MD/PDF - **Ollama Management** — Hardware detection, model recommendations, in-app model management - **Audit Trail** — Every external data send logged with SHA-256 hash @@ -33,9 +35,13 @@ ## Releases -| Version | Status | Platforms | +| Version | Status | Highlights | |---------|--------|-----------| -| v0.1.1 | 🚀 Released | linux/amd64 · linux/arm64 · windows/amd64 (.deb, .rpm, .AppImage, .exe, .msi) | +| v0.2.6 | 🚀 Latest | MSI GenAI support, OAuth2 shell permissions, user ID tracking | +| v0.2.3 | Released | Confluence/ServiceNow/ADO REST API clients (19 TDD tests) | +| v0.1.1 | Released | Core application with PII detection, RCA generation | + +**Platforms:** linux/amd64 · linux/arm64 · windows/amd64 (.deb, .rpm, .AppImage, .exe, .msi) Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases). All builds are produced natively (no QEMU emulation). @@ -45,7 +51,7 @@ Download from [Releases](https://gogs.tftsr.com/sarman/tftsr-devops_investigatio |-------|--------| | Phases 1–8 (Core application) | ✅ Complete | | Phase 9 (History/Search) | 🔲 Pending | -| Phase 10 (Integrations) | 🕐 v0.2 stubs only | +| Phase 10 (Integrations) | ✅ Complete — Confluence, ServiceNow, Azure DevOps fully implemented with OAuth2 | | Phase 11 (CI/CD) | ✅ Complete — Gitea Actions fully operational | | Phase 12 (Release packaging) | ✅ linux/amd64 · linux/arm64 (native) · windows/amd64 | diff --git a/IPC-Commands.md b/IPC-Commands.md index 3d25f9b..d0b6649 100644 --- a/IPC-Commands.md +++ b/IPC-Commands.md @@ -220,15 +220,206 @@ Returns audit log entries. Filter by action, entity_type, date range. --- -## Integration Commands (v0.2 Stubs) +## Integration Commands -All 6 integration commands currently return `"not yet available"` errors. +> **Status:** ✅ **Fully Implemented** (v0.2.3+) -| Command | Purpose | -|---------|---------| -| `test_confluence_connection` | Verify Confluence credentials | -| `publish_to_confluence` | Publish RCA/postmortem to Confluence space | -| `test_servicenow_connection` | Verify ServiceNow credentials | -| `create_servicenow_incident` | Create incident from issue | -| `test_azuredevops_connection` | Verify Azure DevOps credentials | -| `create_azuredevops_workitem` | Create work item from issue | +All integration commands are production-ready with complete OAuth2/authentication flows. + +### OAuth2 Commands + +### `initiate_oauth` +```typescript +initiateOauthCmd(service: "confluence" | "servicenow" | "azuredevops") → OAuthInitResponse +``` +Starts OAuth2 PKCE flow. Returns authorization URL and state key. Opens browser window for user authentication. + +```typescript +interface OAuthInitResponse { + auth_url: string; // URL to open in browser + state: string; // State key for callback verification +} +``` + +**Flow:** +1. Generates PKCE challenge +2. Starts local callback server on `http://localhost:8765` +3. Opens authorization URL in browser +4. User authenticates with service +5. Service redirects to callback server +6. Callback server triggers `handle_oauth_callback` + +### `handle_oauth_callback` +```typescript +handleOauthCallbackCmd(service: string, code: string, stateKey: string) → void +``` +Exchanges authorization code for access token. Encrypts token with AES-256-GCM and stores in database. + +### Confluence Commands + +### `test_confluence_connection` +```typescript +testConfluenceConnectionCmd(baseUrl: string, credentials: Record) → ConnectionResult +``` +Verifies Confluence connection by calling `/rest/api/user/current`. + +### `list_confluence_spaces` +```typescript +listConfluenceSpacesCmd(config: ConfluenceConfig) → Space[] +``` +Lists all accessible Confluence spaces. + +### `search_confluence_pages` +```typescript +searchConfluencePagesCmd(config: ConfluenceConfig, query: string, spaceKey?: string) → Page[] +``` +Searches pages using CQL (Confluence Query Language). Optional space filter. + +### `publish_to_confluence` +```typescript +publishToConfluenceCmd(config: ConfluenceConfig, spaceKey: string, title: string, contentHtml: string, parentPageId?: string) → PublishResult +``` +Creates a new page in Confluence. Returns page ID and URL. + +### `update_confluence_page` +```typescript +updateConfluencePageCmd(config: ConfluenceConfig, pageId: string, title: string, contentHtml: string, version: number) → PublishResult +``` +Updates an existing page. Requires current version number. + +### ServiceNow Commands + +### `test_servicenow_connection` +```typescript +testServiceNowConnectionCmd(instanceUrl: string, credentials: Record) → ConnectionResult +``` +Verifies ServiceNow connection by querying incident table. + +### `search_servicenow_incidents` +```typescript +searchServiceNowIncidentsCmd(config: ServiceNowConfig, query: string) → Incident[] +``` +Searches incidents by short description. Returns up to 10 results. + +### `create_servicenow_incident` +```typescript +createServiceNowIncidentCmd(config: ServiceNowConfig, shortDesc: string, description: string, urgency: string, impact: string) → TicketResult +``` +Creates a new incident. Returns incident number and URL. + +```typescript +interface TicketResult { + id: string; // sys_id (UUID) + ticket_number: string; // INC0010001 + url: string; // Direct link to incident +} +``` + +### `get_servicenow_incident` +```typescript +getServiceNowIncidentCmd(config: ServiceNowConfig, incidentId: string) → Incident +``` +Retrieves incident by sys_id or incident number (e.g., `INC0010001`). + +### `update_servicenow_incident` +```typescript +updateServiceNowIncidentCmd(config: ServiceNowConfig, sysId: string, updates: Record) → TicketResult +``` +Updates incident fields. Uses JSON-PATCH format. + +### Azure DevOps Commands + +### `test_azuredevops_connection` +```typescript +testAzureDevOpsConnectionCmd(orgUrl: string, credentials: Record) → ConnectionResult +``` +Verifies Azure DevOps connection by querying project info. + +### `search_azuredevops_workitems` +```typescript +searchAzureDevOpsWorkItemsCmd(config: AzureDevOpsConfig, query: string) → WorkItem[] +``` +Searches work items using WIQL (Work Item Query Language). + +### `create_azuredevops_workitem` +```typescript +createAzureDevOpsWorkItemCmd(config: AzureDevOpsConfig, title: string, description: string, workItemType: string, severity: string) → TicketResult +``` +Creates a work item (Bug, Task, User Story). Returns work item ID and URL. + +**Work Item Types:** +- `Bug` — Software defect +- `Task` — Work assignment +- `User Story` — Feature request +- `Issue` — Problem or blocker +- `Incident` — Production incident + +### `get_azuredevops_workitem` +```typescript +getAzureDevOpsWorkItemCmd(config: AzureDevOpsConfig, workItemId: number) → WorkItem +``` +Retrieves work item by ID. + +### `update_azuredevops_workitem` +```typescript +updateAzureDevOpsWorkItemCmd(config: AzureDevOpsConfig, workItemId: number, updates: Record) → TicketResult +``` +Updates work item fields. Uses JSON-PATCH format. + +--- + +## Common Types + +### `ConnectionResult` +```typescript +interface ConnectionResult { + success: boolean; + message: string; +} +``` + +### `PublishResult` +```typescript +interface PublishResult { + id: string; // Page ID or document ID + url: string; // Direct link to published content +} +``` + +### `TicketResult` +```typescript +interface TicketResult { + id: string; // sys_id or work item ID + ticket_number: string; // Human-readable number + url: string; // Direct link +} +``` + +--- + +## Authentication Storage + +All integration credentials are stored in the `credentials` table: + +```sql +CREATE TABLE credentials ( + id TEXT PRIMARY KEY, + service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')), + token_hash TEXT NOT NULL, -- SHA-256 for audit + encrypted_token TEXT NOT NULL, -- AES-256-GCM encrypted + created_at TEXT NOT NULL, + expires_at TEXT +); +``` + +**Encryption:** +- Algorithm: AES-256-GCM +- Key derivation: From `TFTSR_DB_KEY` environment variable +- Nonce: Random 96-bit per encryption +- Format: `base64(nonce || ciphertext || tag)` + +**Token retrieval:** +```rust +// Backend: src-tauri/src/integrations/auth.rs +pub fn decrypt_token(encrypted: &str) -> Result +``` diff --git a/Integrations.md b/Integrations.md index a74b292..c7480c0 100644 --- a/Integrations.md +++ b/Integrations.md @@ -1,97 +1,273 @@ # Integrations -> **Status: All integrations are v0.2 stubs.** They are implemented as placeholder commands that return `"not yet available"` errors. The authentication framework and command signatures are finalized, but the actual API calls are not yet implemented. +> **Status: ✅ Fully Implemented (v0.2.6)** — All three integrations (Confluence, ServiceNow, Azure DevOps) are production-ready with complete OAuth2/authentication flows and REST API clients. --- ## Confluence -**Purpose:** Publish RCA and post-mortem documents to a Confluence space. +**Purpose:** Publish RCA and post-mortem documents to Confluence spaces. -**Commands:** -- `test_confluence_connection(base_url, credentials)` — Verify credentials -- `publish_to_confluence(doc_id, space_key, parent_page_id?)` — Create/update page +**Status:** ✅ **Implemented** (v0.2.3) -**Planned implementation:** -- Confluence REST API v2: `POST /wiki/rest/api/content` -- Auth: Basic auth (email + API token) or OAuth2 -- Page format: Convert Markdown → Confluence storage format (XHTML-like) +### Features +- OAuth2 authentication with PKCE flow +- List accessible spaces +- Search pages by CQL query +- Create new pages with optional parent +- Update existing pages with version management -**Configuration (Settings → Integrations → Confluence):** +### API Client (`src-tauri/src/integrations/confluence.rs`) + +**Functions:** +```rust +test_connection(config: &ConfluenceConfig) -> Result +list_spaces(config: &ConfluenceConfig) -> Result, String> +search_pages(config: &ConfluenceConfig, query: &str, space_key: Option<&str>) -> Result, String> +publish_page(config: &ConfluenceConfig, space_key: &str, title: &str, content_html: &str, parent_page_id: Option<&str>) -> Result +update_page(config: &ConfluenceConfig, page_id: &str, title: &str, content_html: &str, version: i32) -> Result ``` -Base URL: https://yourorg.atlassian.net -Email: user@example.com -API Token: (stored in Stronghold) -Space Key: PROJ + +### Configuration (Settings → Integrations → Confluence) ``` +Base URL: https://yourorg.atlassian.net +Authentication: OAuth2 (bearer token, encrypted at rest) +Default Space: PROJ +``` + +### Implementation Details +- **API**: Confluence REST API v1 (`/rest/api/`) +- **Auth**: OAuth2 bearer token (encrypted with AES-256-GCM) +- **Endpoints**: + - `GET /rest/api/user/current` — Test connection + - `GET /rest/api/space` — List spaces + - `GET /rest/api/content/search` — Search with CQL + - `POST /rest/api/content` — Create page + - `PUT /rest/api/content/{id}` — Update page +- **Page format**: Confluence Storage Format (XHTML) +- **TDD Tests**: 6 tests with mockito HTTP mocking --- ## ServiceNow -**Purpose:** Create incident records in ServiceNow from TFTSR issues. +**Purpose:** Create and manage incident records in ServiceNow. -**Commands:** -- `test_servicenow_connection(instance_url, credentials)` — Verify credentials -- `create_servicenow_incident(issue_id, config)` — Create incident +**Status:** ✅ **Implemented** (v0.2.3) -**Planned implementation:** -- ServiceNow Table API: `POST /api/now/table/incident` -- Auth: Basic auth or OAuth2 bearer token -- Field mapping: TFTSR severity → ServiceNow priority (P1=Critical, P2=High, etc.) +### Features +- Basic authentication (username/password) +- Search incidents by description +- Create new incidents with urgency/impact +- Get incident by sys_id or number +- Update existing incidents -**Configuration:** +### API Client (`src-tauri/src/integrations/servicenow.rs`) + +**Functions:** +```rust +test_connection(config: &ServiceNowConfig) -> Result +search_incidents(config: &ServiceNowConfig, query: &str) -> Result, String> +create_incident(config: &ServiceNowConfig, short_description: &str, description: &str, urgency: &str, impact: &str) -> Result +get_incident(config: &ServiceNowConfig, incident_id: &str) -> Result +update_incident(config: &ServiceNowConfig, sys_id: &str, updates: serde_json::Value) -> Result ``` -Instance URL: https://yourorg.service-now.com -Username: admin -Password: (stored in Stronghold) + +### Configuration (Settings → Integrations → ServiceNow) ``` +Instance URL: https://yourorg.service-now.com +Username: admin +Password: (encrypted with AES-256-GCM) +``` + +### Implementation Details +- **API**: ServiceNow Table API (`/api/now/table/incident`) +- **Auth**: HTTP Basic authentication +- **Severity mapping**: TFTSR P1-P4 → ServiceNow urgency/impact (1-3) +- **Incident lookup**: Supports both sys_id (UUID) and incident number (INC0010001) +- **TDD Tests**: 7 tests with mockito HTTP mocking --- ## Azure DevOps -**Purpose:** Create work items (bugs/incidents) in Azure DevOps from TFTSR issues. +**Purpose:** Create and manage work items (bugs/tasks) in Azure DevOps. -**Commands:** -- `test_azuredevops_connection(org_url, credentials)` — Verify credentials -- `create_azuredevops_workitem(issue_id, project, config)` — Create work item +**Status:** ✅ **Implemented** (v0.2.3) -**Planned implementation:** -- Azure DevOps REST API: `POST /{organization}/{project}/_apis/wit/workitems/${type}` -- Auth: Personal Access Token (PAT) via Basic auth header -- Work item type: Bug or Incident +### Features +- OAuth2 authentication with PKCE flow +- Search work items via WIQL queries +- Create work items (Bug, Task, User Story) +- Get work item details by ID +- Update work items with JSON-PATCH operations -**Configuration:** +### API Client (`src-tauri/src/integrations/azuredevops.rs`) + +**Functions:** +```rust +test_connection(config: &AzureDevOpsConfig) -> Result +search_work_items(config: &AzureDevOpsConfig, query: &str) -> Result, String> +create_work_item(config: &AzureDevOpsConfig, title: &str, description: &str, work_item_type: &str, severity: &str) -> Result +get_work_item(config: &AzureDevOpsConfig, work_item_id: i64) -> Result +update_work_item(config: &AzureDevOpsConfig, work_item_id: i64, updates: serde_json::Value) -> Result +``` + +### Configuration (Settings → Integrations → Azure DevOps) ``` Organization URL: https://dev.azure.com/yourorg -Personal Access Token: (stored in Stronghold) +Authentication: OAuth2 (bearer token, encrypted at rest) Project: MyProject -Work Item Type: Bug +``` + +### Implementation Details +- **API**: Azure DevOps REST API v7.0 +- **Auth**: OAuth2 bearer token (encrypted with AES-256-GCM) +- **WIQL**: Work Item Query Language for advanced search +- **Work item types**: Bug, Task, User Story, Issue, Incident +- **Severity mapping**: Bug-specific field `Microsoft.VSTS.Common.Severity` +- **TDD Tests**: 6 tests with mockito HTTP mocking + +--- + +## OAuth2 Authentication Flow + +All integrations using OAuth2 (Confluence, Azure DevOps) follow the same flow: + +1. **User clicks "Connect"** in Settings → Integrations +2. **Backend generates PKCE challenge** and stores code verifier +3. **Local callback server starts** on `http://localhost:8765` +4. **Browser opens** with OAuth authorization URL +5. **User authenticates** with service provider +6. **Service redirects** to `http://localhost:8765/callback?code=...` +7. **Callback server extracts code** and triggers token exchange +8. **Backend exchanges code for token** using PKCE verifier +9. **Token encrypted** with AES-256-GCM and stored in DB +10. **UI shows "Connected"** status + +**Implementation:** +- `src-tauri/src/integrations/auth.rs` — PKCE generation, token exchange, encryption +- `src-tauri/src/integrations/callback_server.rs` — Local HTTP server (warp) +- `src-tauri/src/commands/integrations.rs` — IPC command handlers + +**Security:** +- Tokens encrypted at rest with AES-256-GCM (256-bit key) +- Key derived from environment variable `TFTSR_DB_KEY` +- PKCE prevents authorization code interception +- Callback server only accepts from `localhost` + +--- + +## Database Schema + +**Credentials Table (`migration 011`):** +```sql +CREATE TABLE credentials ( + id TEXT PRIMARY KEY, + service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')), + token_hash TEXT NOT NULL, -- SHA-256 hash for audit + encrypted_token TEXT NOT NULL, -- AES-256-GCM encrypted + created_at TEXT NOT NULL, + expires_at TEXT, + UNIQUE(service) +); +``` + +**Integration Config Table:** +```sql +CREATE TABLE integration_config ( + id TEXT PRIMARY KEY, + service TEXT NOT NULL CHECK(service IN ('confluence','servicenow','azuredevops')), + base_url TEXT NOT NULL, + username TEXT, -- ServiceNow only + project_name TEXT, -- Azure DevOps only + space_key TEXT, -- Confluence only + auto_create_enabled INTEGER NOT NULL DEFAULT 0, + updated_at TEXT NOT NULL, + UNIQUE(service) +); ``` --- -## v0.2 Roadmap +## Testing -Integration implementation order (planned): +All integrations have comprehensive test coverage: -1. **Confluence** — Most commonly requested; Markdown-to-Confluence conversion library needed -2. **Azure DevOps** — Clean REST API, straightforward PAT auth -3. **ServiceNow** — More complex field mapping; may require customer-specific configuration +```bash +# Run all integration tests +cargo test --manifest-path src-tauri/Cargo.toml --lib integrations -Each integration will also require: -- Audit log entry on every publish action -- PII check on document content before external publish -- Connection test UI in Settings → Integrations +# Run specific integration tests +cargo test --manifest-path src-tauri/Cargo.toml confluence +cargo test --manifest-path src-tauri/Cargo.toml servicenow +cargo test --manifest-path src-tauri/Cargo.toml azuredevops +``` + +**Test statistics:** +- **Confluence**: 6 tests (connection, spaces, search, publish, update) +- **ServiceNow**: 7 tests (connection, search, create, get by sys_id, get by number, update) +- **Azure DevOps**: 6 tests (connection, WIQL search, create, get, update) +- **Total**: 19 integration tests (all passing) + +**Test approach:** +- TDD methodology (tests written first) +- HTTP mocking with `mockito` crate +- No external API calls in tests +- All auth flows tested with mock responses --- -## Adding an Integration +## CSP Configuration -1. Implement the logic in `src-tauri/src/integrations/{name}.rs` -2. Remove the stub `Err("not yet available")` return in `commands/integrations.rs` -3. Add the new API endpoint to the Tauri CSP `connect-src` -4. Add Stronghold secret key for the API credentials -5. Wire up the Settings UI in `src/pages/Settings/Integrations.tsx` -6. Add audit log call before the external API request +All integration domains are whitelisted in `src-tauri/tauri.conf.json`: + +```json +"connect-src": "... https://auth.atlassian.com https://*.atlassian.net https://login.microsoftonline.com https://dev.azure.com" +``` + +--- + +## Adding a New Integration + +1. **Create API client**: `src-tauri/src/integrations/{name}.rs` +2. **Implement functions**: `test_connection()`, create/read/update operations +3. **Add TDD tests**: Use `mockito` for HTTP mocking +4. **Update migration**: Add service to `credentials` and `integration_config` CHECK constraints +5. **Add IPC commands**: `src-tauri/src/commands/integrations.rs` +6. **Update CSP**: Add API domains to `tauri.conf.json` +7. **Wire up UI**: `src/pages/Settings/Integrations.tsx` +8. **Update capabilities**: Add any required Tauri permissions +9. **Document**: Update this wiki page + +--- + +## Troubleshooting + +### OAuth "Command plugin:shell|open not allowed" +**Fix**: Add `"shell:allow-open"` to `src-tauri/capabilities/default.json` + +### Token Exchange Fails +**Check**: +1. PKCE verifier matches challenge +2. Redirect URI exactly matches registered callback +3. Authorization code hasn't expired +4. Client ID/secret are correct + +### ServiceNow 401 Unauthorized +**Check**: +1. Username/password are correct +2. User has API access enabled +3. Instance URL is correct (no trailing slash) + +### Confluence API 404 +**Check**: +1. Base URL format: `https://yourorg.atlassian.net` (no `/wiki/`) +2. Space key exists and user has access +3. OAuth token has required scopes (`read:confluence-content.all`, `write:confluence-content`) + +### Azure DevOps 403 Forbidden +**Check**: +1. OAuth token has required scopes (`vso.work_write`) +2. User has permissions in the project +3. Project name is case-sensitive