mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
* fix(auth): update RPC method names for authentication calls Refactor authentication-related RPC method names to use underscores instead of dots for consistency. Updated methods include `get_state`, `get_session_token`, `clear_session`, and `store_session`. chore: update OpenHuman version to 0.51.19 style: standardize string formatting in quickjs_libs/bootstrap.js and other files - Replace single quotes with double quotes for string literals in various functions. - Ensure consistent formatting across console logging and error handling. fix(config): improve token retrieval logic in ops_core.rs - Enhance the logic for retrieving the active session token from the credentials store, accommodating user-specific directories. * test: align auth and OAuth assertions with current behavior Update stale test expectations for underscore-style auth RPC methods and light-theme OAuth button classes, and make the bypass-login E2E assertion resilient to the current auth persistence model. Made-with: Cursor * chore: apply formatter output for login flow spec Include Prettier formatting adjustments produced by the pre-push hook so the branch can pass repository push checks cleanly. Made-with: Cursor
152 lines
6.1 KiB
Markdown
152 lines
6.1 KiB
Markdown
# Proxy Route Flow (`src/routes/proxy.ts`)
|
|
|
|
This document explains how proxy requests move through the backend when calling:
|
|
|
|
- `/proxy/by-id/:integrationId/{*path}`
|
|
- `/proxy/encrypted/:integrationId/{*path}`
|
|
|
|
Both endpoints let clients call third-party provider APIs (Google, Notion, etc.) through our backend while enforcing ownership checks, path restrictions, and token handling.
|
|
|
|
## High-level architecture
|
|
|
|
`src/routes/proxy.ts` is the HTTP entrypoint. It:
|
|
|
|
1. Authenticates the user via JWT
|
|
2. Applies per-user rate limiting (100 requests/minute)
|
|
3. Normalizes request params, headers, and query
|
|
4. Delegates to controller logic:
|
|
- non-encrypted flow: `forwardByIntegrationId(...)`
|
|
- encrypted flow: `forwardWithEncryptedTokens(...)`
|
|
5. Returns upstream status/body with a safe subset of headers
|
|
|
|
## Shared route behavior
|
|
|
|
Both routes in `src/routes/proxy.ts` share these behaviors:
|
|
|
|
- `authenticateJWT` middleware requires a valid bearer token.
|
|
- `proxyRateLimit` limits calls by authenticated user ID (falls back to IP).
|
|
- `integrationId` is validated for presence in the route handler.
|
|
- wildcard `{*path}` is normalized into a leading-slash path (for example, `v1/users/me` -> `/v1/users/me`).
|
|
- query and headers are normalized into `Record<string, string>` using `toSingleValueRecord`.
|
|
|
|
## Flow A: `/proxy/by-id/:integrationId/{*path}` (standard token storage)
|
|
|
|
### Route layer (`src/routes/proxy.ts`)
|
|
|
|
1. Extracts `integrationId` and wildcard `path`.
|
|
2. Builds a `ProxyRequest` object:
|
|
- `method`
|
|
- `path`
|
|
- normalized `query`
|
|
- normalized `headers`
|
|
- `body`
|
|
3. Calls `forwardByIntegrationId(integrationId, userId, request)`.
|
|
4. Sets returned headers and forwards status/data to the client.
|
|
|
|
### Controller layer (`src/controllers/proxy/forward.ts`)
|
|
|
|
`forwardByIntegrationId(...)`:
|
|
|
|
1. Validates ObjectId format.
|
|
2. Loads `OAuthIntegration` by ID.
|
|
3. Verifies integration ownership (`integration.user === userId`).
|
|
4. Delegates to `forward(provider, userId, request)`.
|
|
|
|
`forward(...)`:
|
|
|
|
1. Loads provider proxy config (`getProviderProxyConfig`).
|
|
2. Enforces max body size for non-GET requests.
|
|
3. Blocks sensitive provider paths using `blockedPaths`.
|
|
4. Loads the user's provider integration.
|
|
5. Runs provider-specific request validation via forwarder hook.
|
|
6. Resolves OAuth token refresh config from provider registry.
|
|
7. Gets a valid access token through `getValidAccessToken(...)` (auto-refresh when close to expiry).
|
|
8. Resolves final upstream base URL/path.
|
|
9. Builds outgoing headers:
|
|
- `Accept: application/json`
|
|
- provider auth header (for example, bearer token)
|
|
- `Content-Type: application/json` for non-GET with body
|
|
- provider-specific header overrides via forwarder hook
|
|
10. Sends upstream request with axios (30s timeout).
|
|
11. Converts upstream `401/403` into `NotAuthorizedError`.
|
|
12. Returns upstream response with safe headers only (`content-type`, `x-request-id`, `retry-after`).
|
|
|
|
## Flow B: `/proxy/encrypted/:integrationId/{*path}` (encrypted token storage)
|
|
|
|
This is the key-split flow for integrations saved in encrypted mode.
|
|
|
|
### Route layer (`src/routes/proxy.ts`)
|
|
|
|
1. Reads `X-Encryption-Key` header (required).
|
|
2. Parses it via `parseKeyFromString(...)` into client key share bytes.
|
|
3. Validates `integrationId` and normalizes path/query/headers/body.
|
|
4. Calls `forwardWithEncryptedTokens(integrationId, userId, clientKeyShare, request)`.
|
|
5. For success: forwards status/data and safe headers.
|
|
6. For errors:
|
|
- derives `statusCode` from error object (default `500`)
|
|
- returns `{ success: false, error }`
|
|
- if status is `401` or `403`, also returns:
|
|
- `authError: true`
|
|
- `reconnectRequired: true`
|
|
|
|
### Controller layer (`src/controllers/proxy/forwardEncrypted.ts`)
|
|
|
|
`forwardWithEncryptedTokens(...)`:
|
|
|
|
1. Validates ObjectId format.
|
|
2. Loads integration and verifies ownership.
|
|
3. Ensures integration uses `encryptionMode === 'encrypted'`.
|
|
4. Loads provider config and applies body-size and blocked-path checks.
|
|
5. Runs provider-specific validation hook.
|
|
6. Resolves provider base URL and target URL.
|
|
7. Executes request with one auth retry loop:
|
|
- fetches decrypted valid token via `getValidAccessTokenEncrypted(...)`
|
|
- on retry, passes `forceRefresh = true`
|
|
- builds provider headers and sends upstream request
|
|
- if upstream returns `401` on first attempt, retries once after forced refresh
|
|
8. Returns upstream response (status, data, safe headers).
|
|
9. Maps network/timeout failures to `BadRequestError`.
|
|
|
|
## Token lifecycle differences
|
|
|
|
- Standard route (`/by-id`):
|
|
- uses backend token cache/token manager flow (`getValidAccessToken`)
|
|
- refreshes according to provider refresh configuration
|
|
|
|
- Encrypted route (`/encrypted`):
|
|
- decrypts stored token material using client key share + server share
|
|
- refreshes and then re-encrypts/persists updated tokens through encrypted token service
|
|
- performs one forced-refresh retry after upstream `401`
|
|
|
|
## Security and safety controls
|
|
|
|
- JWT required for all proxy calls.
|
|
- Integration ownership enforced server-side.
|
|
- Per-user rate limiting applied before forwarding.
|
|
- Provider-specific blocked paths prevent unsafe endpoint forwarding.
|
|
- Request body size limits prevent large payload abuse.
|
|
- Only safe response headers are exposed back to clients.
|
|
- Encrypted route requires client key share header.
|
|
|
|
## Typical request sequence (encrypted flow)
|
|
|
|
1. Client sends request to `/proxy/encrypted/:integrationId/...` with:
|
|
- bearer token
|
|
- `X-Encryption-Key`
|
|
2. Route validates inputs and delegates to encrypted controller.
|
|
3. Controller validates integration + ownership + mode.
|
|
4. Token service decrypts and refreshes token if needed.
|
|
5. Backend forwards request to provider API.
|
|
6. If provider returns `401`, backend refreshes token and retries once.
|
|
7. Backend returns provider response (or structured error with reconnect hints).
|
|
|
|
## Files involved
|
|
|
|
- `src/routes/proxy.ts`
|
|
- `src/controllers/proxy/forward.ts`
|
|
- `src/controllers/proxy/forwardEncrypted.ts`
|
|
- `src/services/oauth/providers/tokenManager.ts`
|
|
- `src/services/oauth/encryptedTokenService.ts`
|
|
- `src/services/proxy/providerConfig.ts`
|
|
- `src/services/oauth/forwarder/*`
|