Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 24 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

[![Security Audit](https://github.com/nearai/chat-api/actions/workflows/security-audit.yml/badge.svg)](https://github.com/nearai/chat-api/actions/workflows/security-audit.yml)

A Rust backend service that proxies requests to **NEAR AI Cloud API** (using OpenAI-compatible API format) while tracking user conversations in PostgreSQL. Provides OAuth authentication (Google/GitHub), user session management, and serves a frontend as static files. Designed to run in a Trusted Execution Environment (TEE) for enhanced security and privacy.
A Rust backend service that proxies OpenAI-compatible inference requests to **NEAR AI Cloud API**. It provides OAuth authentication (Google/GitHub), user session management, and serves a frontend as static files. Designed to run in a Trusted Execution Environment (TEE) for enhanced security and privacy.

## Features

- 🔒 **TEE Execution**: Runs in a Trusted Execution Environment with cryptographic attestation
- 🤖 **OpenAI-Compatible API**: Drop-in replacement for OpenAI API endpoints (proxies to NEAR AI Cloud API)
- 🔐 **OAuth Authentication**: Google and GitHub OAuth support
- 💬 **Conversation Tracking**: Persistent conversation management in PostgreSQL
- 🧠 **Stateless Responses**: `/v1/responses` always forwards requests with `store: false`
- 📊 **User Management**: Session management, user settings, and analytics
- ⚡ **Streaming**: Real-time SSE streaming for AI responses

Expand All @@ -20,7 +20,7 @@ A Rust backend service that proxies requests to **NEAR AI Cloud API** (using Ope
```
crates/
├── api/ # Axum HTTP server, routes, middleware, OpenAPI docs (utoipa)
├── services/ # Business logic: auth, conversation, response proxy, user management
├── services/ # Business logic: auth, temporary read views, response proxy, user management
├── database/ # PostgreSQL (tokio-postgres, deadpool), migrations, repositories
└── config/ # Environment-based configuration structs
```
Expand All @@ -29,14 +29,16 @@ crates/

- **Repository Pattern**: Database access through trait-based repositories (`PostgresUserRepository`, etc.)
- **Service Layer**: Business logic in `services` crate, injected into `AppState`
- **NEAR AI Cloud API Proxy**: All `/v1/*` routes forward to NEAR AI Cloud API with auth; conversation endpoints (`/v1/conversations/*`) track IDs in PostgreSQL
- **NEAR AI Cloud API Proxy**: OpenAI-compatible inference routes forward to NEAR AI Cloud API with auth; Responses requests are stateless
- **Temporary Read Views**: Owner-only Conversation and File GET endpoints remain available for the Stage I migration/export window. Ordinary Conversation, File, and sharing writes return `410 Gone`; the existing `DELETE /v1/users/me` account-deletion flow remains available.
- **Patroni Support**: Optional cluster discovery for HA PostgreSQL via `DATABASE_PRIMARY_APP_ID`

### Request Flow

1. Request → Auth middleware (validates session token) → Route handler
2. Conversation operations → Forward to NEAR AI Cloud API → Parse response → Track in DB
3. Generic `/v1/{*path}` → Forward to NEAR AI Cloud API (pass-through)
1. Request → its historical authentication boundary → route handler
2. `/v1/responses` → validate stateless linkage fields → forward to NEAR AI Cloud API with `store: false`
3. Temporary owner-only Conversation/File GET views → local ownership lookup and, where needed, Cloud read view
4. Usage, subscription, rate-limit, and attestation-related proxy behavior remain local to Chat API

## Development

Expand Down Expand Up @@ -81,9 +83,11 @@ docker compose down # Stop services
### Testing

```bash
cargo test --features test # All tests
cargo test --test admin_tests --features test # Admin tests only
cargo test --test e2e_api_tests --features test -- --ignored --nocapture # E2E tests (real API calls)
cargo test --features test # All tests
cargo test --test admin_tests --features test # Admin tests only
cargo test --test responses_stateless_tests --features test
cargo test --test conversations_tests --features test
cargo test --test files_tests --features test
```

### Code Quality
Expand Down Expand Up @@ -184,13 +188,20 @@ OpenAPI docs available at `/docs`.

**Key endpoints**:
- `/v1/auth/*` - OAuth authentication
- `/v1/conversations/*` - Conversation management
- `/v1/responses` - OpenAI-compatible Responses API (proxied to NEAR AI Cloud API)
- `/v1/responses` - OpenAI-compatible, stateless Responses API (proxied to NEAR AI Cloud API)
- `/v1/conversations/*` - Temporary owner-only Conversation views for migration/export
- `/v1/files/*` - Temporary read-only File views for migration/export
- `/v1/attestation/report` - TEE attestation reports
- `/v1/users/*` - User management
- `/v1/admin/*` - Admin operations

**Note**: All requests are proxied to **NEAR AI Cloud API** (with OpenAI compatible endpoints). Set `OPENAI_BASE_URL` to your NEAR AI Cloud API endpoint.
**Stage I migration**: owner-only Conversation and File GET views remain temporarily available for authenticated private-chat data export. Ordinary Conversation, File, and sharing state writes (create/update/delete, item creation, upload, pin/archive, clone, and share-group mutation), plus unsupported methods and descendants within those legacy namespaces, return `410 Gone` with `Cache-Control: no-store` after session authentication. These views will be removed in Stage III.

**Account-deletion exception**: `DELETE /v1/users/me` remains available. Its existing asynchronous worker continues Cloud Conversation/File cleanup through Cloud API's retained, API-key/workspace-scoped resource DELETE endpoints before it performs local finalization; this flow is outside the retired session-proxy write surface.

`/v1/responses` is stateless: requests are normalized to `store: false`, and response/conversation linkage fields such as `conversation`, `previous_response_id`, and `background: true` are rejected. Clients may use custom function tools and replay their own function results; Cloud validates tool and input shapes.

**Note**: OpenAI-compatible inference requests are proxied to **NEAR AI Cloud API**. Set `OPENAI_BASE_URL` to your NEAR AI Cloud API endpoint.

## Security & Privacy

Expand Down
87 changes: 48 additions & 39 deletions crates/api/src/openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use utoipa::OpenApi;
#[openapi(
info(
title = "NEAR AI Chat API",
description = "A comprehensive chat API for Private Chat.",
description = "An authenticated OpenAI-compatible inference proxy with temporary read-only Private Chat views for migration and export.",
version = "1.0.0",
contact(name = "NEAR AI Team", email = "support@near.ai"),
license(name = "MIT",)
Expand All @@ -25,33 +25,13 @@ use utoipa::OpenApi;
crate::routes::users::get_user_status,
crate::routes::users::delete_current_user,
crate::routes::users::get_my_usage,
// Conversation endpoints
crate::routes::api::create_conversation,
// Temporary Stage I owner-only Conversation endpoints
crate::routes::api::list_conversations,
crate::routes::api::get_conversation,
crate::routes::api::update_conversation,
crate::routes::api::delete_conversation,
crate::routes::api::create_conversation_share,
crate::routes::api::list_conversation_shares,
crate::routes::api::delete_conversation_share,
crate::routes::api::create_conversation_items,
crate::routes::api::list_conversation_items,
crate::routes::api::pin_conversation,
crate::routes::api::unpin_conversation,
crate::routes::api::archive_conversation,
crate::routes::api::unarchive_conversation,
crate::routes::api::clone_conversation,
// Share group endpoints
crate::routes::api::create_share_group,
crate::routes::api::list_share_groups,
crate::routes::api::update_share_group,
crate::routes::api::delete_share_group,
crate::routes::api::list_shared_with_me,
// File endpoints
crate::routes::api::upload_file,
// Temporary Stage I read-only File endpoints
crate::routes::api::list_files,
crate::routes::api::get_file,
crate::routes::api::delete_file,
crate::routes::api::get_file_content,
// Proxy endpoints
crate::routes::api::proxy_responses,
Expand Down Expand Up @@ -175,20 +155,9 @@ use utoipa::OpenApi;
// Admin usage models (UserUsageResponse shared with /users/me/usage)
crate::models::UserUsageResponse,
crate::routes::admin::TopUsageResponse,
// Conversation share models
crate::routes::api::ErrorResponse,
crate::routes::api::ShareRecipientPayload,
crate::routes::api::ShareTargetPayload,
crate::routes::api::CreateConversationShareRequest,
crate::routes::api::ConversationShareResponse,
crate::routes::api::OwnerInfo,
crate::routes::api::ConversationSharesListResponse,
// Share group models
crate::routes::api::CreateShareGroupRequest,
crate::routes::api::UpdateShareGroupRequest,
crate::routes::api::ShareGroupResponse,
crate::routes::api::SharedConversationInfo,
// File models
// Temporary owner-only Conversation models
// Temporary read-only File models
crate::models::FileListResponse,
crate::models::FileGetResponse,
crate::routes::api::ListFilesParams,
Expand Down Expand Up @@ -267,9 +236,8 @@ use utoipa::OpenApi;
(name = "Health", description = "Health check and service status endpoints"),
(name = "Auth", description = "OAuth authentication endpoints"),
(name = "Users", description = "User profile management endpoints"),
(name = "Conversations", description = "Conversation management endpoints (supports optional authentication for public sharing)"),
(name = "Share Groups", description = "Share group management endpoints"),
(name = "Files", description = "File management endpoints"),
(name = "Conversations", description = "Temporary owner-only Conversation views for migration/export. Conversation sharing and all mutations return 410 Gone."),
(name = "Files", description = "Temporary owner-only File views for migration/export. File mutations and unsupported legacy paths return 410 Gone."),
(name = "Proxy", description = "Proxy endpoints for OpenAI-compatible APIs"),
(name = "Credits", description = "Credit purchase and balance endpoints"),
(name = "Subscriptions", description = "Subscription management endpoints"),
Expand Down Expand Up @@ -300,3 +268,44 @@ impl utoipa::Modify for SecurityAddon {
}
}
}

#[cfg(test)]
mod tests {
use super::ApiDoc;
use utoipa::OpenApi;

#[test]
fn documents_owner_only_views_but_not_stateful_or_sharing_surfaces() {
let spec = serde_json::to_value(ApiDoc::openapi()).expect("OpenAPI serialization");

for path in [
"/v1/conversations",
"/v1/conversations/{conversation_id}",
"/v1/conversations/{conversation_id}/items",
"/v1/files",
"/v1/files/{file_id}",
"/v1/files/{file_id}/content",
] {
assert!(
spec["paths"].get(path).is_some(),
"temporary read path {path} must be in OpenAPI"
);
}

for path in [
"/v1/conversations/{conversation_id}/shares",
"/v1/conversations/{conversation_id}/shares/{share_id}",
"/v1/conversations/{conversation_id}/pin",
"/v1/conversations/{conversation_id}/archive",
"/v1/conversations/{conversation_id}/clone",
"/v1/share-groups",
"/v1/share-groups/{group_id}",
"/v1/shared-with-me",
] {
assert!(
spec["paths"].get(path).is_none(),
"disabled stateful or sharing path {path} must not be in OpenAPI"
);
}
}
}
Loading
Loading