Add smart-ide-global API layer, SSO delegates proxy, .logs access logs
- New smart-ide-global-api (127.0.0.1:37149): internal bearer, upstream proxy, X-OIDC forward - SSO gateway calls global API with GLOBAL_API_INTERNAL_TOKEN; logs to .logs/sso-gateway/ - Aggregated config example, docs, VERSION 0.0.2, claw proxy local URL hint
This commit is contained in:
parent
3b3e1e67de
commit
0af507143a
3
.gitignore
vendored
3
.gitignore
vendored
@ -15,6 +15,9 @@ config/services.local.env
|
|||||||
# logs/ : le répertoire reste versionné (README.md, logs/.gitignore) ; aucun fichier journal versionné
|
# logs/ : le répertoire reste versionné (README.md, logs/.gitignore) ; aucun fichier journal versionné
|
||||||
logs/**/*.log
|
logs/**/*.log
|
||||||
|
|
||||||
|
# .logs/ : journaux SSO / API globale (README + .gitignore versionnés)
|
||||||
|
.logs/**/*.log
|
||||||
|
|
||||||
# projects/ : ignorer tout répertoire d'id sous projects/ sauf les squelettes versionnés (conf.json par id, gabarit example/)
|
# projects/ : ignorer tout répertoire d'id sous projects/ sauf les squelettes versionnés (conf.json par id, gabarit example/)
|
||||||
projects/*
|
projects/*
|
||||||
!projects/README.md
|
!projects/README.md
|
||||||
|
|||||||
1
.logs/.gitignore
vendored
Normal file
1
.logs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
**/*.log
|
||||||
5
.logs/README.md
Normal file
5
.logs/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Journaux locaux (`.logs/`)
|
||||||
|
|
||||||
|
Fichiers produits par les services (ex. passerelle SSO, API globale) : **une ligne JSON par requête** dans `sso-gateway/access.log` et `global-api/access.log`.
|
||||||
|
|
||||||
|
Les fichiers `*.log` sous `.logs/` sont ignorés par Git (voir `.gitignore` à la racine du monorepo).
|
||||||
@ -1,3 +1,5 @@
|
|||||||
# Configuration locale
|
# Configuration locale (`config/`)
|
||||||
|
|
||||||
- **`services.local.env.example`** : variables pour l’IDE et tous les services HTTP ; copier vers **`services.local.env`** (non versionné, voir racine **`.gitignore`**).
|
- **`services.local.env.example`** : variables d’environnement agrégées pour les micro-services (ports sur `127.0.0.1`, jetons, URL de l’API globale et du SSO). Copier vers **`services.local.env`** (gitignoré à la racine du monorepo).
|
||||||
|
|
||||||
|
Voir aussi [docs/README.md](../docs/README.md).
|
||||||
|
|||||||
@ -1,74 +1,59 @@
|
|||||||
# Local IDE / agent configuration for all smart_ide HTTP services and the tools bridge.
|
# Copier vers config/services.local.env (gitignoré) et adapter.
|
||||||
# Copy to config/services.local.env and fill secrets. Do not commit services.local.env.
|
# Profil micro-services : écoute et cibles **127.0.0.1** uniquement pour le maillage smart_ide.
|
||||||
#
|
# Les jetons utilisateur OIDC sont validés par la passerelle SSO ; l’IdP peut être distant (docv / Enso) — ce fichier concerne les **ports et secrets locaux** du monorepo.
|
||||||
# Load before starting processes, e.g.: set -a && source config/services.local.env && set +a
|
|
||||||
|
|
||||||
# Monorepo root (path validation for tools-bridge jobs)
|
SMART_IDE_MONOREPO_ROOT=
|
||||||
SMART_IDE_MONOREPO_ROOT=/absolute/path/to/smart_ide
|
|
||||||
|
|
||||||
# Optional: extra allowed path prefixes (comma-separated) for tools-bridge file jobs
|
# --- smart-ide-global-api (démarrer avant le SSO) ---
|
||||||
# TOOLS_ALLOWED_PATH_PREFIXES=/data/clones,/home/user/projects
|
GLOBAL_API_HOST=127.0.0.1
|
||||||
|
GLOBAL_API_PORT=37149
|
||||||
|
GLOBAL_API_INTERNAL_TOKEN=
|
||||||
|
|
||||||
# --- smart-ide-tools-bridge (Carbonyl / PageIndex / Chandra + registry) ---
|
# --- smart-ide-sso-gateway ---
|
||||||
TOOLS_BRIDGE_HOST=127.0.0.1
|
SSO_GATEWAY_HOST=127.0.0.1
|
||||||
TOOLS_BRIDGE_PORT=37147
|
SSO_GATEWAY_PORT=37148
|
||||||
TOOLS_BRIDGE_TOKEN=
|
GLOBAL_API_URL=http://127.0.0.1:37149
|
||||||
TOOLS_BRIDGE_JOB_TIMEOUT_MS=3600000
|
# OIDC_ISSUER= # URL issuer OpenID (JWKS) — hors maillage HTTP interne
|
||||||
|
# OIDC_AUDIENCE=
|
||||||
|
# OIDC_JWKS_URI=
|
||||||
|
# SSO_CORS_ORIGIN=
|
||||||
|
# SSO_GATEWAY_MAX_BODY_BYTES=33554432
|
||||||
|
|
||||||
|
# --- Jetons / hôtes micro-services (consommés par smart-ide-global-api) ---
|
||||||
|
ORCHESTRATOR_HOST=127.0.0.1
|
||||||
|
ORCHESTRATOR_PORT=37145
|
||||||
|
ORCHESTRATOR_TOKEN=
|
||||||
|
|
||||||
# --- Core HTTP micro-services ---
|
|
||||||
REPOS_DEVTOOLS_HOST=127.0.0.1
|
REPOS_DEVTOOLS_HOST=127.0.0.1
|
||||||
REPOS_DEVTOOLS_PORT=37140
|
REPOS_DEVTOOLS_PORT=37140
|
||||||
REPOS_DEVTOOLS_TOKEN=
|
REPOS_DEVTOOLS_TOKEN=
|
||||||
REPOS_DEVTOOLS_ROOT=
|
|
||||||
|
|
||||||
LANGEXTRACT_API_HOST=127.0.0.1
|
|
||||||
LANGEXTRACT_API_PORT=37141
|
|
||||||
LANGEXTRACT_SERVICE_TOKEN=
|
|
||||||
|
|
||||||
CLAW_PROXY_HOST=127.0.0.1
|
|
||||||
CLAW_PROXY_PORT=37142
|
|
||||||
CLAW_PROXY_TOKEN=
|
|
||||||
CLAW_UPSTREAM_URL=
|
|
||||||
|
|
||||||
REGEX_SEARCH_HOST=127.0.0.1
|
|
||||||
REGEX_SEARCH_PORT=37143
|
|
||||||
REGEX_SEARCH_TOKEN=
|
|
||||||
REGEX_SEARCH_ROOT=
|
|
||||||
|
|
||||||
LOCAL_OFFICE_URL=http://127.0.0.1:8000
|
|
||||||
LOCAL_OFFICE_API_KEY=
|
|
||||||
|
|
||||||
IA_DEV_GATEWAY_HOST=127.0.0.1
|
IA_DEV_GATEWAY_HOST=127.0.0.1
|
||||||
IA_DEV_GATEWAY_PORT=37144
|
IA_DEV_GATEWAY_PORT=37144
|
||||||
IA_DEV_GATEWAY_TOKEN=
|
IA_DEV_GATEWAY_TOKEN=
|
||||||
|
|
||||||
ORCHESTRATOR_HOST=127.0.0.1
|
|
||||||
ORCHESTRATOR_PORT=37145
|
|
||||||
ORCHESTRATOR_TOKEN=
|
|
||||||
|
|
||||||
ANYTHINGLLM_DEVTOOLS_HOST=127.0.0.1
|
ANYTHINGLLM_DEVTOOLS_HOST=127.0.0.1
|
||||||
ANYTHINGLLM_DEVTOOLS_PORT=37146
|
ANYTHINGLLM_DEVTOOLS_PORT=37146
|
||||||
ANYTHINGLLM_DEVTOOLS_TOKEN=
|
ANYTHINGLLM_DEVTOOLS_TOKEN=
|
||||||
ANYTHINGLLM_BASE_URL=
|
|
||||||
ANYTHINGLLM_API_KEY=
|
|
||||||
REPOS_DEVTOOLS_URL=http://127.0.0.1:37140
|
|
||||||
|
|
||||||
# Orchestrator → tools bridge (for intent resolution hints)
|
TOOLS_BRIDGE_HOST=127.0.0.1
|
||||||
TOOLS_BRIDGE_URL=http://127.0.0.1:37147
|
TOOLS_BRIDGE_PORT=37147
|
||||||
|
TOOLS_BRIDGE_TOKEN=
|
||||||
|
|
||||||
# --- smart-ide-sso-gateway (OIDC user token → proxy to micro-services) ---
|
LANGEXTRACT_API_HOST=127.0.0.1
|
||||||
SSO_GATEWAY_HOST=127.0.0.1
|
LANGEXTRACT_API_PORT=37141
|
||||||
SSO_GATEWAY_PORT=37148
|
LANGEXTRACT_SERVICE_TOKEN=
|
||||||
# SSO_CORS_ORIGIN=
|
|
||||||
# Required when running the gateway: docv / Enso issuer URL
|
|
||||||
OIDC_ISSUER=
|
|
||||||
# OIDC_AUDIENCE=
|
|
||||||
# OIDC_JWKS_URI=
|
|
||||||
|
|
||||||
# Ollama / AnythingLLM (orchestrator)
|
REGEX_SEARCH_HOST=127.0.0.1
|
||||||
OLLAMA_URL=http://127.0.0.1:11434
|
REGEX_SEARCH_PORT=37143
|
||||||
ANYTHINGLLM_BASE_URL=
|
REGEX_SEARCH_TOKEN=
|
||||||
|
|
||||||
# Carbonyl (docker / native — used by bridge open-plan response)
|
CLAW_PROXY_HOST=127.0.0.1
|
||||||
CARBONYL_DOCKER_IMAGE=fathyb/carbonyl
|
CLAW_PROXY_PORT=37142
|
||||||
CARBONYL_RUNNER=docker
|
CLAW_PROXY_TOKEN=
|
||||||
|
|
||||||
|
LOCAL_OFFICE_URL=http://127.0.0.1:8000
|
||||||
|
LOCAL_OFFICE_API_KEY=
|
||||||
|
|
||||||
|
# --- claw-harness-proxy : amont HTTP du binaire claw (machine locale uniquement en profil strict) ---
|
||||||
|
# CLAW_UPSTREAM_URL=http://127.0.0.1:<port>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ Documentation des **API HTTP** exposées par les services sous [`services/`](../
|
|||||||
| **smart_ide-orchestrator** | Bearer (spécification) | `37145` (spécification) | [orchestrator.md](./orchestrator.md) |
|
| **smart_ide-orchestrator** | Bearer (spécification) | `37145` (spécification) | [orchestrator.md](./orchestrator.md) |
|
||||||
| **anythingllm-devtools** | Bearer | `37146` | [anythingllm-devtools-api.md](./anythingllm-devtools-api.md) |
|
| **anythingllm-devtools** | Bearer | `37146` | [anythingllm-devtools-api.md](./anythingllm-devtools-api.md) |
|
||||||
| **smart-ide-tools-bridge** | Bearer (sauf `/health`) | `37147` | [smart-ide-tools-bridge-api.md](./smart-ide-tools-bridge-api.md) |
|
| **smart-ide-tools-bridge** | Bearer (sauf `/health`) | `37147` | [smart-ide-tools-bridge-api.md](./smart-ide-tools-bridge-api.md) |
|
||||||
|
| **smart-ide-global-api** | Bearer interne (`GLOBAL_API_INTERNAL_TOKEN`, sauf `/health`) | `37149` | [global-api.md](./global-api.md) |
|
||||||
| **smart-ide-sso-gateway** | Bearer utilisateur OIDC (sauf `/health`) | `37148` | [sso-gateway-api.md](./sso-gateway-api.md) |
|
| **smart-ide-sso-gateway** | Bearer utilisateur OIDC (sauf `/health`) | `37148` | [sso-gateway-api.md](./sso-gateway-api.md) |
|
||||||
| **docv** (externe) | selon dépôt Enso | selon déploiement | [docv.md](./docv.md) |
|
| **docv** (externe) | selon dépôt Enso | selon déploiement | [docv.md](./docv.md) |
|
||||||
|
|
||||||
|
|||||||
53
docs/API/global-api.md
Normal file
53
docs/API/global-api.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# API — smart-ide-global-api
|
||||||
|
|
||||||
|
Écoute par défaut : **`127.0.0.1:37149`**. Configuration : `services/smart-ide-global-api/.env.example`, agrégat [config/services.local.env.example](../../config/services.local.env.example).
|
||||||
|
|
||||||
|
## Rôle
|
||||||
|
|
||||||
|
Agrégateur HTTP **interne** : reçoit les requêtes authentifiées par `Authorization: Bearer <GLOBAL_API_INTERNAL_TOKEN>` (fourni par `smart-ide-sso-gateway`), relaie vers chaque micro-service avec son **jeton technique** et propage les en-têtes `X-OIDC-Sub` / `X-OIDC-Email` lorsqu’ils sont présents.
|
||||||
|
|
||||||
|
Les navigateurs et applications utilisateur ne doivent **pas** appeler ce port directement : passer par la **passerelle SSO** (`/proxy/<clé>/...`).
|
||||||
|
|
||||||
|
## Authentification
|
||||||
|
|
||||||
|
| Route | Auth |
|
||||||
|
|-------|------|
|
||||||
|
| `GET /health` | Aucune |
|
||||||
|
| Toutes les autres | `Authorization: Bearer` égal à `GLOBAL_API_INTERNAL_TOKEN` |
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### `GET /health`
|
||||||
|
|
||||||
|
Réponse `200` : `{ "status": "ok", "service": "smart-ide-global-api" }`.
|
||||||
|
|
||||||
|
### `GET /v1/upstreams`
|
||||||
|
|
||||||
|
Liste les clés d’amont : `{ "upstreams": [ "orchestrator", ... ] }` (même liste que côté SSO).
|
||||||
|
|
||||||
|
### Proxy — `ANY /v1/upstream/<upstream_key>/<path>`
|
||||||
|
|
||||||
|
- **`<upstream_key>`** : `orchestrator`, `repos_devtools`, `ia_dev_gateway`, `anythingllm_devtools`, `tools_bridge`, `langextract`, `regex_search`, `claw_proxy`, `local_office`.
|
||||||
|
- **`<path>`** : transmis à l’URL de base du service (ex. `/v1/upstream/orchestrator/v1/...` → `http://127.0.0.1:37145/v1/...`).
|
||||||
|
- **Corps** : relayé (limite `GLOBAL_API_MAX_BODY_BYTES`, défaut 32 MiB).
|
||||||
|
- **Erreurs** : `401` si Bearer interne absent ou incorrect ; `404` si clé inconnue ; `503` si `local_office` sans `LOCAL_OFFICE_API_KEY`.
|
||||||
|
|
||||||
|
## Journaux
|
||||||
|
|
||||||
|
Fichier **`.logs/global-api/access.log`** : lignes JSON (`ts`, `method`, `path`, `upstream`, `status`, `durationMs`, `oidcSub` si présent).
|
||||||
|
|
||||||
|
## Variables d’environnement
|
||||||
|
|
||||||
|
| Variable | Rôle |
|
||||||
|
|----------|------|
|
||||||
|
| `GLOBAL_API_HOST` / `GLOBAL_API_PORT` | Bind HTTP |
|
||||||
|
| `GLOBAL_API_INTERNAL_TOKEN` | Obligatoire — secret partagé avec la passerelle SSO |
|
||||||
|
| `GLOBAL_API_MAX_BODY_BYTES` | Taille max du corps |
|
||||||
|
| `SMART_IDE_MONOREPO_ROOT` | Racine monorepo pour `.logs/` (sinon déduction depuis le module) |
|
||||||
|
|
||||||
|
Jetons et hôtes des micro-services : mêmes noms que dans `config/services.local.env.example`.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [sso-gateway-api.md](./sso-gateway-api.md)
|
||||||
|
- [sso-gateway-service.md](../features/sso-gateway-service.md)
|
||||||
@ -27,11 +27,15 @@ Liste les clés de proxy disponibles : `{ "upstreams": [ "orchestrator", ... ] }
|
|||||||
### Proxy — `ANY /proxy/<upstream_key>/<path>`
|
### Proxy — `ANY /proxy/<upstream_key>/<path>`
|
||||||
|
|
||||||
- **`<upstream_key>`** : voir liste ci-dessus (`repos_devtools`, `orchestrator`, etc.).
|
- **`<upstream_key>`** : voir liste ci-dessus (`repos_devtools`, `orchestrator`, etc.).
|
||||||
- **`<path>`** : chemin transmis tel quel à l’URL de base du service (ex. `/proxy/orchestrator/v1/...` → `http://ORCHESTRATOR_HOST:PORT/v1/...`).
|
- **`<path>`** : relayé vers **smart-ide-global-api** sous `/v1/upstream/<upstream_key><path>` (ex. `/proxy/orchestrator/v1/...` → `GLOBAL_API_URL/v1/upstream/orchestrator/v1/...`).
|
||||||
- **Corps** : relayé pour les méthodes avec body (limite `SSO_GATEWAY_MAX_BODY_BYTES`, défaut 32 MiB).
|
- **Corps** : relayé pour les méthodes avec body (limite `SSO_GATEWAY_MAX_BODY_BYTES`, défaut 32 MiB).
|
||||||
- **Réponses d’erreur** : `401` si Bearer utilisateur absent ou invalide ; `404` si clé inconnue ; `503` si `local_office` est ciblé sans `LOCAL_OFFICE_API_KEY`.
|
- **Réponses d’erreur** : `401` si Bearer utilisateur absent ou invalide ; `404` si clé inconnue ; erreurs amont si l’API globale ou un micro-service refuse la requête.
|
||||||
|
|
||||||
L’en-tête `Authorization` utilisateur n’est **pas** transmis à l’amont ; il est remplacé par le jeton de service configuré. Voir [sso-gateway-service.md](../features/sso-gateway-service.md).
|
L’en-tête `Authorization` utilisateur n’est **pas** transmis à l’API globale ; il est remplacé par `GLOBAL_API_INTERNAL_TOKEN`. Les claims OIDC sont transmis en `X-OIDC-Sub` / `X-OIDC-Email` jusqu’aux micro-services. Voir [sso-gateway-service.md](../features/sso-gateway-service.md) et [global-api.md](./global-api.md).
|
||||||
|
|
||||||
|
### Journaux
|
||||||
|
|
||||||
|
Fichier **`.logs/sso-gateway/access.log`** : lignes JSON (`ts`, `method`, `path`, `upstream`, `status`, `durationMs`, `oidcSub` si présent). Les `GET /health` et `OPTIONS` ne sont pas journalisés.
|
||||||
|
|
||||||
### Comptes et projets
|
### Comptes et projets
|
||||||
|
|
||||||
@ -47,8 +51,11 @@ Aucun stockage d’**utilisateurs** ou de **comptes par projet** dans ce service
|
|||||||
| `SSO_GATEWAY_HOST` / `SSO_GATEWAY_PORT` | Bind HTTP |
|
| `SSO_GATEWAY_HOST` / `SSO_GATEWAY_PORT` | Bind HTTP |
|
||||||
| `SSO_CORS_ORIGIN` | Si défini, en-têtes CORS sur les réponses |
|
| `SSO_CORS_ORIGIN` | Si défini, en-têtes CORS sur les réponses |
|
||||||
| `SSO_GATEWAY_MAX_BODY_BYTES` | Taille max du corps en entrée |
|
| `SSO_GATEWAY_MAX_BODY_BYTES` | Taille max du corps en entrée |
|
||||||
|
| `GLOBAL_API_URL` | Base HTTP de smart-ide-global-api (défaut `http://127.0.0.1:37149`) |
|
||||||
|
| `GLOBAL_API_INTERNAL_TOKEN` | Obligatoire — même valeur que sur smart-ide-global-api |
|
||||||
|
| `SMART_IDE_MONOREPO_ROOT` | Optionnel — racine pour écrire sous `.logs/sso-gateway/` |
|
||||||
|
|
||||||
Les jetons et hôtes des micro-services : mêmes noms que dans `config/services.local.env.example`.
|
Les jetons et hôtes des micro-services sont lus par **smart-ide-global-api** ; voir `config/services.local.env.example` et [global-api.md](./global-api.md).
|
||||||
|
|
||||||
## Voir aussi
|
## Voir aussi
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,8 @@ Vue d’ensemble et index complet : **[repo/README.md](./repo/README.md)**. Règ
|
|||||||
| [repo/service-carbonyl.md](./repo/service-carbonyl.md) | Carbonyl (navigateur terminal), prévisualisation test |
|
| [repo/service-carbonyl.md](./repo/service-carbonyl.md) | Carbonyl (navigateur terminal), prévisualisation test |
|
||||||
| [repo/service-pageindex.md](./repo/service-pageindex.md) | PageIndex (index vectorless, définition sémantique documents) |
|
| [repo/service-pageindex.md](./repo/service-pageindex.md) | PageIndex (index vectorless, définition sémantique documents) |
|
||||||
| [repo/service-smart-ide-tools-bridge.md](./repo/service-smart-ide-tools-bridge.md) | Pont HTTP IDE + sous-modules CLI |
|
| [repo/service-smart-ide-tools-bridge.md](./repo/service-smart-ide-tools-bridge.md) | Pont HTTP IDE + sous-modules CLI |
|
||||||
| [repo/service-smart-ide-sso-gateway.md](./repo/service-smart-ide-sso-gateway.md) | Passerelle OIDC utilisateur → micro-services |
|
| [repo/service-smart-ide-global-api.md](./repo/service-smart-ide-global-api.md) | API HTTP interne : proxy vers micro-services (jetons techniques) |
|
||||||
|
| [repo/service-smart-ide-sso-gateway.md](./repo/service-smart-ide-sso-gateway.md) | Passerelle OIDC utilisateur → API globale → micro-services |
|
||||||
| [repo/service-chandra.md](./repo/service-chandra.md) | Chandra OCR (PDF / images structurés) |
|
| [repo/service-chandra.md](./repo/service-chandra.md) | Chandra OCR (PDF / images structurés) |
|
||||||
| [repo/extension-anythingllm-workspaces.md](./repo/extension-anythingllm-workspaces.md) | Extension AnythingLLM IDE (supprimée ; voir anythingllm-devtools) |
|
| [repo/extension-anythingllm-workspaces.md](./repo/extension-anythingllm-workspaces.md) | Extension AnythingLLM IDE (supprimée ; voir anythingllm-devtools) |
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ Permettre au **front web** de la plateforme `smart_ide` (déployé par environne
|
|||||||
| **docv / IdP Enso** | Émet `id_token` / `access_token`, expose JWKS |
|
| **docv / IdP Enso** | Émet `id_token` / `access_token`, expose JWKS |
|
||||||
| **Front SPA** | Échange code OAuth (PKCE recommandé), stocke session |
|
| **Front SPA** | Échange code OAuth (PKCE recommandé), stocke session |
|
||||||
| **Backend API** (orchestrateur ou BFF) | Valide JWT (signature JWKS, `iss`, `aud`, `exp`), mappe rôles → droits policy |
|
| **Backend API** (orchestrateur ou BFF) | Valide JWT (signature JWKS, `iss`, `aud`, `exp`), mappe rôles → droits policy |
|
||||||
| **smart-ide-sso-gateway** (monorepo) | Point d’entrée optionnel : même validation JWT + proxy vers les micro-services avec jetons techniques — [sso-gateway-service.md](./sso-gateway-service.md) |
|
| **smart-ide-sso-gateway** (monorepo) | Point d’entrée optionnel : validation JWT + proxy via **smart-ide-global-api** vers les micro-services avec jetons techniques — [sso-gateway-service.md](./sso-gateway-service.md) |
|
||||||
|
|
||||||
## Flux (authorization code + PKCE)
|
## Flux (authorization code + PKCE)
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
## Rôle
|
## Rôle
|
||||||
|
|
||||||
Le service **`smart-ide-sso-gateway`** (`services/smart-ide-sso-gateway/`) centralise la **validation OIDC** (jeton utilisateur émis par **docv / Enso**) et le **proxy** vers les micro-services du monorepo avec leurs **jetons techniques**.
|
Le service **`smart-ide-sso-gateway`** (`services/smart-ide-sso-gateway/`) centralise la **validation OIDC** (jeton utilisateur émis par **docv / Enso**) puis délègue le **proxy HTTP** au service **`smart-ide-global-api`** (`services/smart-ide-global-api/`), qui applique les **jetons techniques** vers chaque micro-service.
|
||||||
|
|
||||||
- Le **navigateur** ou un **BFF** présente un `access_token` utilisateur (`Authorization: Bearer`).
|
- Le **navigateur** ou un **BFF** présente un `access_token` utilisateur (`Authorization: Bearer`).
|
||||||
- La passerelle vérifie la signature (**JWKS**), `iss`, éventuellement `aud`, puis appelle l’amont avec le **Bearer de service** ou la **clé API** attendue par chaque cible.
|
- La passerelle vérifie la signature (**JWKS**), `iss`, éventuellement `aud`, puis appelle **smart-ide-global-api** avec `GLOBAL_API_INTERNAL_TOKEN` et propage `X-OIDC-Sub` / `X-OIDC-Email` pour les journaux et règles amont.
|
||||||
|
|
||||||
Les appels **machine à machine** sans contexte utilisateur (scripts, `ia_dev`) restent possibles **en direct** vers chaque service, comme avant : la passerelle est une **option** pour les parcours « utilisateur authentifié par docv ».
|
Les micro-services **n’implémentent pas le SSO** ; ils écoutent en **local** (`127.0.0.1`) avec authentification par jeton de service. Les appels **machine à machine** sans contexte utilisateur (scripts, `ia_dev`) peuvent toujours cibler **directement** un micro-service ou, pour un seul point d’entrée technique, **smart-ide-global-api** avec le Bearer interne.
|
||||||
|
|
||||||
## Lien avec docv / Enso
|
## Lien avec docv / Enso
|
||||||
|
|
||||||
@ -15,7 +15,9 @@ Le flux **authorization code + PKCE** et le rôle d’**IdP** restent décrits d
|
|||||||
|
|
||||||
## Amonts proxy
|
## Amonts proxy
|
||||||
|
|
||||||
Les clés exposées par `GET /v1/upstreams` et utilisées dans `/proxy/<clé>/...` sont : `orchestrator`, `repos_devtools`, `ia_dev_gateway`, `anythingllm_devtools`, `tools_bridge`, `langextract`, `regex_search`, `claw_proxy`, `local_office`. Les variables d’environnement sont les mêmes que pour le reste de la plateforme ([`config/services.local.env.example`](../../config/services.local.env.example)).
|
Les clés exposées par `GET /v1/upstreams` et utilisées dans `/proxy/<clé>/...` sont : `orchestrator`, `repos_devtools`, `ia_dev_gateway`, `anythingllm_devtools`, `tools_bridge`, `langextract`, `regex_search`, `claw_proxy`, `local_office`. La résolution des URL et jetons par clé est implémentée dans **smart-ide-global-api** ; fichier d’exemple agrégé : [`config/services.local.env.example`](../../config/services.local.env.example).
|
||||||
|
|
||||||
|
**Journaux** : `.logs/sso-gateway/access.log` (passerelle) et `.logs/global-api/access.log` (agrégateur), lignes JSON par requête (hors `GET /health` pour l’API globale ; hors `GET /health` et `OPTIONS` pour le SSO).
|
||||||
|
|
||||||
## En-têtes vers l’amont
|
## En-têtes vers l’amont
|
||||||
|
|
||||||
@ -37,4 +39,6 @@ Les services amont qui reçoivent `X-OIDC-Sub` / `X-OIDC-Email` sont responsable
|
|||||||
## Documentation détaillée
|
## Documentation détaillée
|
||||||
|
|
||||||
- [API/sso-gateway-api.md](../API/sso-gateway-api.md)
|
- [API/sso-gateway-api.md](../API/sso-gateway-api.md)
|
||||||
|
- [API/global-api.md](../API/global-api.md)
|
||||||
- [services/smart-ide-sso-gateway/README.md](../../services/smart-ide-sso-gateway/README.md)
|
- [services/smart-ide-sso-gateway/README.md](../../services/smart-ide-sso-gateway/README.md)
|
||||||
|
- [services/smart-ide-global-api/README.md](../../services/smart-ide-global-api/README.md)
|
||||||
|
|||||||
@ -41,7 +41,8 @@ Toute la documentation **opérationnelle** qui vivait auparavant sous des `READM
|
|||||||
| [service-pageindex.md](./service-pageindex.md) | PageIndex (index sémantique vectorless), sous-module amont |
|
| [service-pageindex.md](./service-pageindex.md) | PageIndex (index sémantique vectorless), sous-module amont |
|
||||||
| [service-chandra.md](./service-chandra.md) | Chandra OCR, sous-module amont |
|
| [service-chandra.md](./service-chandra.md) | Chandra OCR, sous-module amont |
|
||||||
| [service-smart-ide-tools-bridge.md](./service-smart-ide-tools-bridge.md) | Pont HTTP IDE + outils sous-modules |
|
| [service-smart-ide-tools-bridge.md](./service-smart-ide-tools-bridge.md) | Pont HTTP IDE + outils sous-modules |
|
||||||
| [service-smart-ide-sso-gateway.md](./service-smart-ide-sso-gateway.md) | Passerelle OIDC utilisateur → micro-services |
|
| [service-smart-ide-global-api.md](./service-smart-ide-global-api.md) | API HTTP interne : proxy vers micro-services (Bearer partagé avec SSO) |
|
||||||
|
| [service-smart-ide-sso-gateway.md](./service-smart-ide-sso-gateway.md) | Passerelle OIDC utilisateur → API globale → micro-services |
|
||||||
| [extension-anythingllm-workspaces.md](./extension-anythingllm-workspaces.md) | Extension AnythingLLM IDE (supprimée ; anythingllm-devtools) |
|
| [extension-anythingllm-workspaces.md](./extension-anythingllm-workspaces.md) | Extension AnythingLLM IDE (supprimée ; anythingllm-devtools) |
|
||||||
|
|
||||||
Les **spécifications** détaillées (contrats HTTP, sécurité, orchestration) restent dans [../API/README.md](../API/README.md) et [../features/](../features/).
|
Les **spécifications** détaillées (contrats HTTP, sécurité, orchestration) restent dans [../API/README.md](../API/README.md) et [../features/](../features/).
|
||||||
|
|||||||
@ -16,3 +16,7 @@ Variable interne : **`SMART_IDE_LOG_IA_DEV_ROOT`** (racine `ia_dev`), posée par
|
|||||||
Contrat service : [ia-dev-smart-ide-integration.md](./ia-dev-smart-ide-integration.md), implémentation `ia_dev/lib/smart_ide_logs.sh`.
|
Contrat service : [ia-dev-smart-ide-integration.md](./ia-dev-smart-ide-integration.md), implémentation `ia_dev/lib/smart_ide_logs.sh`.
|
||||||
|
|
||||||
Configuration du pull planifié : [cron-git-pull.md](./cron-git-pull.md).
|
Configuration du pull planifié : [cron-git-pull.md](./cron-git-pull.md).
|
||||||
|
|
||||||
|
## Journaux passerelle SSO et API globale (`.logs/`)
|
||||||
|
|
||||||
|
Sous la **racine du monorepo**, le répertoire **`.logs/`** contient des journaux d’accès JSON (fichiers `*.log` ignorés par Git) pour **`smart-ide-sso-gateway`** (`sso-gateway/access.log`) et **`smart-ide-global-api`** (`global-api/access.log`). Voir [`.logs/README.md`](../../.logs/README.md), [sso-gateway-service.md](../features/sso-gateway-service.md) et [global-api.md](../API/global-api.md).
|
||||||
|
|||||||
17
docs/repo/service-smart-ide-global-api.md
Normal file
17
docs/repo/service-smart-ide-global-api.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# Service smart-ide-global-api (`services/smart-ide-global-api/`)
|
||||||
|
|
||||||
|
Couche HTTP **interne** : proxy vers les micro-services avec jetons techniques, **sans OIDC**. Consommée exclusivement par **`smart-ide-sso-gateway`** (Bearer `GLOBAL_API_INTERNAL_TOKEN`).
|
||||||
|
|
||||||
|
## Configuration locale
|
||||||
|
|
||||||
|
- Agrégat : **[`config/services.local.env.example`](../../config/services.local.env.example)**
|
||||||
|
- Service : **`services/smart-ide-global-api/.env.example`**
|
||||||
|
|
||||||
|
## Exploitation
|
||||||
|
|
||||||
|
Démarrer **avant** la passerelle SSO. Voir **[`services/smart-ide-global-api/README.md`](../../services/smart-ide-global-api/README.md)** et **[`docs/API/global-api.md`](../API/global-api.md)**.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [service-smart-ide-sso-gateway.md](./service-smart-ide-sso-gateway.md)
|
||||||
|
- [sso-gateway-service.md](../features/sso-gateway-service.md)
|
||||||
@ -1,11 +1,11 @@
|
|||||||
# Service smart-ide-sso-gateway (`services/smart-ide-sso-gateway/`)
|
# Service smart-ide-sso-gateway (`services/smart-ide-sso-gateway/`)
|
||||||
|
|
||||||
Passerelle HTTP : validation **JWT utilisateur** (issuer docv / Enso) et **proxy** vers les micro-services `smart_ide` avec authentification **technique** par service.
|
Passerelle HTTP : validation **JWT utilisateur** (issuer docv / Enso), puis **proxy** via **smart-ide-global-api** vers les micro-services (jetons techniques côté API globale).
|
||||||
|
|
||||||
## Configuration locale
|
## Configuration locale
|
||||||
|
|
||||||
- Fichier agrégé : **[`config/services.local.env.example`](../../config/services.local.env.example)** — y ajouter `OIDC_ISSUER` (et optionnellement `SSO_GATEWAY_*`, `SSO_CORS_ORIGIN`) lorsque la passerelle est utilisée.
|
- Fichier agrégé : **[`config/services.local.env.example`](../../config/services.local.env.example)** — `OIDC_ISSUER`, `GLOBAL_API_URL`, `GLOBAL_API_INTERNAL_TOKEN`, jetons micro-services pour l’API globale.
|
||||||
- Service : **`services/smart-ide-sso-gateway/.env.example`**.
|
- Passerelle : **`services/smart-ide-sso-gateway/.env.example`** — API globale : **`services/smart-ide-global-api/.env.example`**.
|
||||||
|
|
||||||
## Exploitation
|
## Exploitation
|
||||||
|
|
||||||
@ -13,4 +13,5 @@ Voir **[`services/smart-ide-sso-gateway/README.md`](../../services/smart-ide-sso
|
|||||||
|
|
||||||
## Voir aussi
|
## Voir aussi
|
||||||
|
|
||||||
|
- [service-smart-ide-global-api.md](./service-smart-ide-global-api.md)
|
||||||
- [sso-docv-enso.md](../features/sso-docv-enso.md)
|
- [sso-docv-enso.md](../features/sso-docv-enso.md)
|
||||||
|
|||||||
@ -27,7 +27,7 @@ Tous écoutent en principe sur **`127.0.0.1`** ; ports par défaut : [API/README
|
|||||||
| **claw-harness-api** (proxy) | **Proxy HTTP** vers un exécuteur claw-code amont | Outillage modèles alternatifs en dev | **Optionnel** si politique projet autorise ce proxy en interne |
|
| **claw-harness-api** (proxy) | **Proxy HTTP** vers un exécuteur claw-code amont | Outillage modèles alternatifs en dev | **Optionnel** si politique projet autorise ce proxy en interne |
|
||||||
| **local-office** | **Manipulation programmatique** de fichiers Office (docx, etc.), clé `X-API-Key` | Tests / génération de documents depuis l’IDE | **Possible** pour génération batch ou transformations Office côté serveur applicatif (hors UI ONLYOFFICE) |
|
| **local-office** | **Manipulation programmatique** de fichiers Office (docx, etc.), clé `X-API-Key` | Tests / génération de documents depuis l’IDE | **Possible** pour génération batch ou transformations Office côté serveur applicatif (hors UI ONLYOFFICE) |
|
||||||
| **smart-ide-tools-bridge** | **Registre** des URLs locales ; **jobs** Carbonyl / PageIndex / Chandra (chemins validés sous `SMART_IDE_MONOREPO_ROOT`) | Découverte des services ; lancer OCR / index / plan terminal depuis l’IDE | **Non prévu pour le trafic utilisateur final** : jobs longs, liés au poste / au monorepo ; les **backs produit** doivent préférer leurs propres pipelines (OCR, index) en prod si besoin |
|
| **smart-ide-tools-bridge** | **Registre** des URLs locales ; **jobs** Carbonyl / PageIndex / Chandra (chemins validés sous `SMART_IDE_MONOREPO_ROOT`) | Découverte des services ; lancer OCR / index / plan terminal depuis l’IDE | **Non prévu pour le trafic utilisateur final** : jobs longs, liés au poste / au monorepo ; les **backs produit** doivent préférer leurs propres pipelines (OCR, index) en prod si besoin |
|
||||||
| **smart-ide-sso-gateway** | **Validation JWT** docv / Enso + **proxy** vers les micro-services avec jetons techniques | Alternative au BFF maison pour un front ou un outil qui appelle déjà l’IdP | **Possible** quand le produit veut un seul point d’entrée HTTP local vers la plateforme IA sous identité utilisateur OIDC ; ne remplace pas les appels M2M directs — [features/sso-gateway-service.md](./features/sso-gateway-service.md) |
|
| **smart-ide-sso-gateway** | **Validation JWT** docv / Enso + **proxy** via **smart-ide-global-api** vers les micro-services | Alternative au BFF maison pour un front ou un outil qui appelle déjà l’IdP | **Possible** quand le produit veut un seul point d’entrée HTTP local vers la plateforme IA sous identité utilisateur OIDC ; les appels M2M peuvent rester directs vers un micro-service ou vers l’API globale — [features/sso-gateway-service.md](./features/sso-gateway-service.md), [repo/service-smart-ide-global-api.md](./repo/service-smart-ide-global-api.md) |
|
||||||
|
|
||||||
## 3. Outils CLI et sous-modules (sans listener HTTP dédié)
|
## 3. Outils CLI et sous-modules (sans listener HTTP dédié)
|
||||||
|
|
||||||
@ -54,13 +54,14 @@ Tous écoutent en principe sur **`127.0.0.1`** ; ports par défaut : [API/README
|
|||||||
|
|
||||||
## 6. Rôles respectifs (rappel)
|
## 6. Rôles respectifs (rappel)
|
||||||
|
|
||||||
- **IDE** : orchestrateur (intentions), tools-bridge (registre + jobs outils), devtools AnythingLLM, repos-devtools, regex search, ia-dev-gateway, **smart-ide-sso-gateway** (OIDC utilisateur → micro-services, si activé), accès directs Ollama / AnythingLLM selon l’UX.
|
- **IDE** : orchestrateur (intentions), tools-bridge (registre + jobs outils), devtools AnythingLLM, repos-devtools, regex search, ia-dev-gateway, **smart-ide-global-api** + **smart-ide-sso-gateway** (OIDC utilisateur → API globale → micro-services, si activé), accès directs Ollama / AnythingLLM selon l’UX.
|
||||||
- **Backends applicatifs** : consommer en priorité les services **stables et documentés** pour l’IA générique (**langextract**, **AnythingLLM**, **Ollama**, éventuellement **orchestrateur**) ; éviter de dépendre du **tools-bridge** pour la charge utilisateur ; respecter périmètres fichiers (`REGEX_SEARCH_ROOT`, SSH / données déployées — [remote-deployed-data-ssh.md](./features/remote-deployed-data-ssh.md)).
|
- **Backends applicatifs** : consommer en priorité les services **stables et documentés** pour l’IA générique (**langextract**, **AnythingLLM**, **Ollama**, éventuellement **orchestrateur**) ; éviter de dépendre du **tools-bridge** pour la charge utilisateur ; respecter périmètres fichiers (`REGEX_SEARCH_ROOT`, SSH / données déployées — [remote-deployed-data-ssh.md](./features/remote-deployed-data-ssh.md)).
|
||||||
|
|
||||||
## Voir aussi
|
## Voir aussi
|
||||||
|
|
||||||
- [config/services.local.env.example](../config/services.local.env.example)
|
- [config/services.local.env.example](../config/services.local.env.example)
|
||||||
- [repo/service-smart-ide-tools-bridge.md](./repo/service-smart-ide-tools-bridge.md)
|
- [repo/service-smart-ide-tools-bridge.md](./repo/service-smart-ide-tools-bridge.md)
|
||||||
|
- [repo/service-smart-ide-global-api.md](./repo/service-smart-ide-global-api.md)
|
||||||
- [repo/service-smart-ide-sso-gateway.md](./repo/service-smart-ide-sso-gateway.md)
|
- [repo/service-smart-ide-sso-gateway.md](./repo/service-smart-ide-sso-gateway.md)
|
||||||
- [platform-target.md](./platform-target.md)
|
- [platform-target.md](./platform-target.md)
|
||||||
- [deployment-target.md](./deployment-target.md)
|
- [deployment-target.md](./deployment-target.md)
|
||||||
|
|||||||
@ -44,7 +44,9 @@ Services d’appoint sur **`127.0.0.1`** (souvent auth **Bearer**) : Git devtool
|
|||||||
|
|
||||||
**smart-ide-tools-bridge** (`services/smart-ide-tools-bridge/`) : **API** locale (registre + Carbonyl / PageIndex / Chandra) — [API/smart-ide-tools-bridge-api.md](../API/smart-ide-tools-bridge-api.md).
|
**smart-ide-tools-bridge** (`services/smart-ide-tools-bridge/`) : **API** locale (registre + Carbonyl / PageIndex / Chandra) — [API/smart-ide-tools-bridge-api.md](../API/smart-ide-tools-bridge-api.md).
|
||||||
|
|
||||||
**smart-ide-sso-gateway** (`services/smart-ide-sso-gateway/`) : validation **JWT** docv / Enso et **proxy** vers les autres micro-services avec jetons techniques — [features/sso-gateway-service.md](./features/sso-gateway-service.md), [API/sso-gateway-api.md](./API/sso-gateway-api.md).
|
**smart-ide-global-api** (`services/smart-ide-global-api/`) : agrégateur HTTP **interne** (Bearer partagé avec le SSO), proxy vers les micro-services avec jetons techniques — [repo/service-smart-ide-global-api.md](./repo/service-smart-ide-global-api.md), [API/global-api.md](./API/global-api.md).
|
||||||
|
|
||||||
|
**smart-ide-sso-gateway** (`services/smart-ide-sso-gateway/`) : validation **JWT** docv / Enso puis **proxy** via l’API globale — [features/sso-gateway-service.md](./features/sso-gateway-service.md), [API/sso-gateway-api.md](./API/sso-gateway-api.md).
|
||||||
|
|
||||||
**Configuration locale** : [config/services.local.env.example](../config/services.local.env.example).
|
**Configuration locale** : [config/services.local.env.example](../config/services.local.env.example).
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
CLAW_PROXY_HOST=127.0.0.1
|
CLAW_PROXY_HOST=127.0.0.1
|
||||||
CLAW_PROXY_PORT=37142
|
CLAW_PROXY_PORT=37142
|
||||||
CLAW_PROXY_TOKEN=
|
CLAW_PROXY_TOKEN=
|
||||||
|
# Profil strictement local : URL HTTP du serveur claw sur cette machine (ex. http://127.0.0.1:<port>)
|
||||||
CLAW_UPSTREAM_URL=
|
CLAW_UPSTREAM_URL=
|
||||||
|
|||||||
48
services/smart-ide-global-api/.env.example
Normal file
48
services/smart-ide-global-api/.env.example
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# smart-ide-global-api — copy to .env, do not commit .env
|
||||||
|
# Or merge into config/services.local.env (repo root)
|
||||||
|
|
||||||
|
GLOBAL_API_HOST=127.0.0.1
|
||||||
|
GLOBAL_API_PORT=37149
|
||||||
|
# GLOBAL_API_MAX_BODY_BYTES=33554432
|
||||||
|
|
||||||
|
# Shared secret with smart-ide-sso-gateway (Bearer toward this service)
|
||||||
|
GLOBAL_API_INTERNAL_TOKEN=
|
||||||
|
|
||||||
|
# Optional: monorepo root for .logs/ (default: three levels above dist/)
|
||||||
|
# SMART_IDE_MONOREPO_ROOT=
|
||||||
|
|
||||||
|
# Micro-services: loopback only — same variables as historical SSO direct proxy
|
||||||
|
ORCHESTRATOR_HOST=127.0.0.1
|
||||||
|
ORCHESTRATOR_PORT=37145
|
||||||
|
ORCHESTRATOR_TOKEN=
|
||||||
|
|
||||||
|
REPOS_DEVTOOLS_HOST=127.0.0.1
|
||||||
|
REPOS_DEVTOOLS_PORT=37140
|
||||||
|
REPOS_DEVTOOLS_TOKEN=
|
||||||
|
|
||||||
|
IA_DEV_GATEWAY_HOST=127.0.0.1
|
||||||
|
IA_DEV_GATEWAY_PORT=37144
|
||||||
|
IA_DEV_GATEWAY_TOKEN=
|
||||||
|
|
||||||
|
ANYTHINGLLM_DEVTOOLS_HOST=127.0.0.1
|
||||||
|
ANYTHINGLLM_DEVTOOLS_PORT=37146
|
||||||
|
ANYTHINGLLM_DEVTOOLS_TOKEN=
|
||||||
|
|
||||||
|
TOOLS_BRIDGE_HOST=127.0.0.1
|
||||||
|
TOOLS_BRIDGE_PORT=37147
|
||||||
|
TOOLS_BRIDGE_TOKEN=
|
||||||
|
|
||||||
|
LANGEXTRACT_API_HOST=127.0.0.1
|
||||||
|
LANGEXTRACT_API_PORT=37141
|
||||||
|
LANGEXTRACT_SERVICE_TOKEN=
|
||||||
|
|
||||||
|
REGEX_SEARCH_HOST=127.0.0.1
|
||||||
|
REGEX_SEARCH_PORT=37143
|
||||||
|
REGEX_SEARCH_TOKEN=
|
||||||
|
|
||||||
|
CLAW_PROXY_HOST=127.0.0.1
|
||||||
|
CLAW_PROXY_PORT=37142
|
||||||
|
CLAW_PROXY_TOKEN=
|
||||||
|
|
||||||
|
LOCAL_OFFICE_URL=http://127.0.0.1:8000
|
||||||
|
LOCAL_OFFICE_API_KEY=
|
||||||
2
services/smart-ide-global-api/.gitignore
vendored
Normal file
2
services/smart-ide-global-api/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
30
services/smart-ide-global-api/README.md
Normal file
30
services/smart-ide-global-api/README.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# smart-ide-global-api
|
||||||
|
|
||||||
|
Couche HTTP **interne** : agrège les appels vers les micro-services `smart_ide` avec les **jetons techniques** par service. **Pas d’OIDC** : seul `smart-ide-sso-gateway` appelle cette API, avec `Authorization: Bearer` égal à `GLOBAL_API_INTERNAL_TOKEN`.
|
||||||
|
|
||||||
|
Les micro-services n’exposent pas le SSO ; ils restent sur **127.0.0.1** avec Bearer (ou clé API pour local-office).
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
Démarrer **avant** la passerelle SSO.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd services/smart-ide-global-api
|
||||||
|
cp .env.example .env
|
||||||
|
# définir GLOBAL_API_INTERNAL_TOKEN et les jetons des services
|
||||||
|
set -a && source .env && set +a
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Écoute par défaut : `http://127.0.0.1:37149`.
|
||||||
|
|
||||||
|
## Journaux
|
||||||
|
|
||||||
|
Une ligne JSON par requête (hors `GET /health`) dans **`.logs/global-api/access.log`** (répertoire `.logs/` à la racine du monorepo, créé au besoin).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- API : [`docs/API/global-api.md`](../../docs/API/global-api.md)
|
||||||
|
- Passerelle SSO : [`docs/features/sso-gateway-service.md`](../../docs/features/sso-gateway-service.md)
|
||||||
51
services/smart-ide-global-api/package-lock.json
generated
Normal file
51
services/smart-ide-global-api/package-lock.json
generated
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "@4nk/smart-ide-global-api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@4nk/smart-ide-global-api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.19.39",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz",
|
||||||
|
"integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
services/smart-ide-global-api/package.json
Normal file
20
services/smart-ide-global-api/package.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@4nk/smart-ide-global-api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Internal HTTP aggregation layer: service tokens only, no OIDC. Consumed by smart-ide-sso-gateway.",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/server.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p ./",
|
||||||
|
"start": "node dist/server.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
services/smart-ide-global-api/src/accessLog.ts
Normal file
19
services/smart-ide-global-api/src/accessLog.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { repoRoot } from "./repoRoot.js";
|
||||||
|
|
||||||
|
const logFile = async (): Promise<string> => {
|
||||||
|
const dir = path.join(repoRoot(), ".logs", "global-api");
|
||||||
|
await fs.mkdir(dir, { recursive: true });
|
||||||
|
return path.join(dir, "access.log");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appendGlobalApiAccessLog = async (
|
||||||
|
entry: Record<string, unknown>,
|
||||||
|
): Promise<void> => {
|
||||||
|
const file = await logFile();
|
||||||
|
const line =
|
||||||
|
JSON.stringify({ ts: new Date().toISOString(), service: "smart-ide-global-api", ...entry }) +
|
||||||
|
"\n";
|
||||||
|
await fs.appendFile(file, line, "utf8");
|
||||||
|
};
|
||||||
11
services/smart-ide-global-api/src/repoRoot.ts
Normal file
11
services/smart-ide-global-api/src/repoRoot.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
export const repoRoot = (): string => {
|
||||||
|
const fromEnv = process.env.SMART_IDE_MONOREPO_ROOT?.trim();
|
||||||
|
if (fromEnv) {
|
||||||
|
return path.resolve(fromEnv);
|
||||||
|
}
|
||||||
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
return path.resolve(here, "..", "..", "..");
|
||||||
|
};
|
||||||
204
services/smart-ide-global-api/src/server.ts
Normal file
204
services/smart-ide-global-api/src/server.ts
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import * as http from "node:http";
|
||||||
|
import { appendGlobalApiAccessLog } from "./accessLog.js";
|
||||||
|
import { listUpstreamKeys, resolveUpstream, type UpstreamAuth } from "./upstreams.js";
|
||||||
|
|
||||||
|
const HOST = process.env.GLOBAL_API_HOST ?? "127.0.0.1";
|
||||||
|
const PORT = Number(process.env.GLOBAL_API_PORT ?? "37149");
|
||||||
|
const MAX_BODY_BYTES = Number(process.env.GLOBAL_API_MAX_BODY_BYTES ?? "33554432");
|
||||||
|
|
||||||
|
const readExpectedToken = (): string => process.env.GLOBAL_API_INTERNAL_TOKEN?.trim() ?? "";
|
||||||
|
|
||||||
|
const json = (res: http.ServerResponse, status: number, body: unknown): void => {
|
||||||
|
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
||||||
|
res.end(JSON.stringify(body));
|
||||||
|
};
|
||||||
|
|
||||||
|
const readBearer = (req: http.IncomingMessage): string | null => {
|
||||||
|
const raw = req.headers.authorization ?? "";
|
||||||
|
const m = /^Bearer\s+(.+)$/i.exec(raw);
|
||||||
|
return m?.[1]?.trim() ?? null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readBodyBuffer = async (req: http.IncomingMessage): Promise<Buffer> => {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
let total = 0;
|
||||||
|
for await (const chunk of req) {
|
||||||
|
const b = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
||||||
|
total += b.length;
|
||||||
|
if (total > MAX_BODY_BYTES) {
|
||||||
|
throw new Error(`Request body exceeds ${MAX_BODY_BYTES} bytes`);
|
||||||
|
}
|
||||||
|
chunks.push(b);
|
||||||
|
}
|
||||||
|
return Buffer.concat(chunks);
|
||||||
|
};
|
||||||
|
|
||||||
|
const hopByHop = new Set([
|
||||||
|
"connection",
|
||||||
|
"keep-alive",
|
||||||
|
"proxy-authenticate",
|
||||||
|
"proxy-authorization",
|
||||||
|
"te",
|
||||||
|
"trailers",
|
||||||
|
"transfer-encoding",
|
||||||
|
"upgrade",
|
||||||
|
"host",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const buildForwardHeaders = (req: http.IncomingMessage, serviceAuth: UpstreamAuth): Headers => {
|
||||||
|
const out = new Headers();
|
||||||
|
for (const [k, v] of Object.entries(req.headers)) {
|
||||||
|
if (!v) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const lk = k.toLowerCase();
|
||||||
|
if (hopByHop.has(lk)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (lk === "authorization") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out.set(k, Array.isArray(v) ? v.join(", ") : v);
|
||||||
|
}
|
||||||
|
if (serviceAuth.kind === "bearer") {
|
||||||
|
if (serviceAuth.token) {
|
||||||
|
out.set("Authorization", `Bearer ${serviceAuth.token}`);
|
||||||
|
}
|
||||||
|
} else if (serviceAuth.value) {
|
||||||
|
out.set(serviceAuth.name, serviceAuth.value);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseHopByHop = new Set([
|
||||||
|
"connection",
|
||||||
|
"keep-alive",
|
||||||
|
"transfer-encoding",
|
||||||
|
"content-encoding",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const proxyToUpstream = async (
|
||||||
|
res: http.ServerResponse,
|
||||||
|
targetUrl: string,
|
||||||
|
headers: Headers,
|
||||||
|
body: Buffer,
|
||||||
|
method: string,
|
||||||
|
): Promise<number> => {
|
||||||
|
const init: RequestInit = {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
redirect: "manual",
|
||||||
|
};
|
||||||
|
if (method !== "GET" && method !== "HEAD" && body.length > 0) {
|
||||||
|
init.body = new Uint8Array(body);
|
||||||
|
}
|
||||||
|
const out = await fetch(targetUrl, init);
|
||||||
|
res.statusCode = out.status;
|
||||||
|
for (const [k, v] of out.headers) {
|
||||||
|
if (responseHopByHop.has(k.toLowerCase())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
res.setHeader(k, v);
|
||||||
|
}
|
||||||
|
const buf = Buffer.from(await out.arrayBuffer());
|
||||||
|
res.end(buf);
|
||||||
|
return out.status;
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = (): void => {
|
||||||
|
const internal = readExpectedToken();
|
||||||
|
if (internal.length === 0) {
|
||||||
|
console.error("smart-ide-global-api: set GLOBAL_API_INTERNAL_TOKEN (non-empty secret).");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
void (async () => {
|
||||||
|
const started = Date.now();
|
||||||
|
const method = req.method ?? "GET";
|
||||||
|
const url = new URL(req.url ?? "/", `http://${HOST}`);
|
||||||
|
const pathname = url.pathname;
|
||||||
|
let logPath = pathname;
|
||||||
|
let upstreamKey = "";
|
||||||
|
let status = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (method === "GET" && (pathname === "/health" || pathname === "/health/")) {
|
||||||
|
status = 200;
|
||||||
|
json(res, status, { status: "ok", service: "smart-ide-global-api" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = readBearer(req);
|
||||||
|
if (!token || token !== internal) {
|
||||||
|
status = 401;
|
||||||
|
json(res, status, { error: "Missing or invalid internal Authorization bearer" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === "GET" && pathname === "/v1/upstreams") {
|
||||||
|
status = 200;
|
||||||
|
json(res, status, { upstreams: listUpstreamKeys() });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyMatch = /^\/v1\/upstream\/([^/]+)(\/.*)?$/.exec(pathname);
|
||||||
|
if (!proxyMatch) {
|
||||||
|
status = 404;
|
||||||
|
json(res, status, { error: "Not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
upstreamKey = proxyMatch[1];
|
||||||
|
const rest = proxyMatch[2] ?? "/";
|
||||||
|
logPath = `/proxy/${upstreamKey}${rest}`;
|
||||||
|
const upstream = resolveUpstream(upstreamKey);
|
||||||
|
if (!upstream) {
|
||||||
|
status = 404;
|
||||||
|
json(res, status, { error: `Unknown upstream: ${upstreamKey}` });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (upstream.auth.kind === "header" && !upstream.auth.value) {
|
||||||
|
status = 503;
|
||||||
|
json(res, status, {
|
||||||
|
error: `API key not configured for upstream ${upstreamKey} (LOCAL_OFFICE_API_KEY)`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = await readBodyBuffer(req);
|
||||||
|
const targetUrl = `${upstream.baseUrl}${rest}${url.search}`;
|
||||||
|
const headers = buildForwardHeaders(req, upstream.auth);
|
||||||
|
status = await proxyToUpstream(res, targetUrl, headers, body, method);
|
||||||
|
} catch (e) {
|
||||||
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
|
status = status || 400;
|
||||||
|
if (!res.headersSent) {
|
||||||
|
json(res, 400, { error: msg });
|
||||||
|
status = 400;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
const skipLog =
|
||||||
|
method === "GET" && (pathname === "/health" || pathname === "/health/");
|
||||||
|
if (!skipLog) {
|
||||||
|
void appendGlobalApiAccessLog({
|
||||||
|
method,
|
||||||
|
path: logPath,
|
||||||
|
upstream: upstreamKey || undefined,
|
||||||
|
status,
|
||||||
|
durationMs: Date.now() - started,
|
||||||
|
oidcSub:
|
||||||
|
typeof req.headers["x-oidc-sub"] === "string"
|
||||||
|
? req.headers["x-oidc-sub"]
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, HOST, () => {
|
||||||
|
console.error(`smart-ide-global-api listening on http://${HOST}:${PORT}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
||||||
96
services/smart-ide-global-api/src/upstreams.ts
Normal file
96
services/smart-ide-global-api/src/upstreams.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
export type UpstreamAuth =
|
||||||
|
| { kind: "bearer"; token: string }
|
||||||
|
| { kind: "header"; name: string; value: string };
|
||||||
|
|
||||||
|
export type UpstreamTarget = {
|
||||||
|
baseUrl: string;
|
||||||
|
auth: UpstreamAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
const trimSlash = (s: string): string => s.replace(/\/+$/, "");
|
||||||
|
|
||||||
|
const env = (k: string, d: string): string => process.env[k]?.trim() ?? d;
|
||||||
|
|
||||||
|
export const resolveUpstream = (key: string): UpstreamTarget | null => {
|
||||||
|
switch (key) {
|
||||||
|
case "orchestrator":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(
|
||||||
|
`http://${env("ORCHESTRATOR_HOST", "127.0.0.1")}:${env("ORCHESTRATOR_PORT", "37145")}`,
|
||||||
|
),
|
||||||
|
auth: { kind: "bearer", token: env("ORCHESTRATOR_TOKEN", "") },
|
||||||
|
};
|
||||||
|
case "repos_devtools":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(
|
||||||
|
`http://${env("REPOS_DEVTOOLS_HOST", "127.0.0.1")}:${env("REPOS_DEVTOOLS_PORT", "37140")}`,
|
||||||
|
),
|
||||||
|
auth: { kind: "bearer", token: env("REPOS_DEVTOOLS_TOKEN", "") },
|
||||||
|
};
|
||||||
|
case "ia_dev_gateway":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(
|
||||||
|
`http://${env("IA_DEV_GATEWAY_HOST", "127.0.0.1")}:${env("IA_DEV_GATEWAY_PORT", "37144")}`,
|
||||||
|
),
|
||||||
|
auth: { kind: "bearer", token: env("IA_DEV_GATEWAY_TOKEN", "") },
|
||||||
|
};
|
||||||
|
case "anythingllm_devtools":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(
|
||||||
|
`http://${env("ANYTHINGLLM_DEVTOOLS_HOST", "127.0.0.1")}:${env("ANYTHINGLLM_DEVTOOLS_PORT", "37146")}`,
|
||||||
|
),
|
||||||
|
auth: { kind: "bearer", token: env("ANYTHINGLLM_DEVTOOLS_TOKEN", "") },
|
||||||
|
};
|
||||||
|
case "tools_bridge":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(
|
||||||
|
`http://${env("TOOLS_BRIDGE_HOST", "127.0.0.1")}:${env("TOOLS_BRIDGE_PORT", "37147")}`,
|
||||||
|
),
|
||||||
|
auth: { kind: "bearer", token: env("TOOLS_BRIDGE_TOKEN", "") },
|
||||||
|
};
|
||||||
|
case "langextract":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(
|
||||||
|
`http://${env("LANGEXTRACT_API_HOST", "127.0.0.1")}:${env("LANGEXTRACT_API_PORT", "37141")}`,
|
||||||
|
),
|
||||||
|
auth: { kind: "bearer", token: env("LANGEXTRACT_SERVICE_TOKEN", "") },
|
||||||
|
};
|
||||||
|
case "regex_search":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(
|
||||||
|
`http://${env("REGEX_SEARCH_HOST", "127.0.0.1")}:${env("REGEX_SEARCH_PORT", "37143")}`,
|
||||||
|
),
|
||||||
|
auth: { kind: "bearer", token: env("REGEX_SEARCH_TOKEN", "") },
|
||||||
|
};
|
||||||
|
case "claw_proxy":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(
|
||||||
|
`http://${env("CLAW_PROXY_HOST", "127.0.0.1")}:${env("CLAW_PROXY_PORT", "37142")}`,
|
||||||
|
),
|
||||||
|
auth: { kind: "bearer", token: env("CLAW_PROXY_TOKEN", "") },
|
||||||
|
};
|
||||||
|
case "local_office":
|
||||||
|
return {
|
||||||
|
baseUrl: trimSlash(env("LOCAL_OFFICE_URL", "http://127.0.0.1:8000")),
|
||||||
|
auth: {
|
||||||
|
kind: "header",
|
||||||
|
name: "X-API-Key",
|
||||||
|
value: env("LOCAL_OFFICE_API_KEY", ""),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listUpstreamKeys = (): string[] => [
|
||||||
|
"orchestrator",
|
||||||
|
"repos_devtools",
|
||||||
|
"ia_dev_gateway",
|
||||||
|
"anythingllm_devtools",
|
||||||
|
"tools_bridge",
|
||||||
|
"langextract",
|
||||||
|
"regex_search",
|
||||||
|
"claw_proxy",
|
||||||
|
"local_office",
|
||||||
|
];
|
||||||
16
services/smart-ide-global-api/tsconfig.json
Normal file
16
services/smart-ide-global-api/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"declaration": false
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
# smart-ide-sso-gateway — copy to .env, do not commit .env
|
# smart-ide-sso-gateway — copy to .env, do not commit .env
|
||||||
# Or merge into config/services.local.env (see repo root config/services.local.env.example)
|
# Or merge into config/services.local.env (repo root)
|
||||||
|
|
||||||
SSO_GATEWAY_HOST=127.0.0.1
|
SSO_GATEWAY_HOST=127.0.0.1
|
||||||
SSO_GATEWAY_PORT=37148
|
SSO_GATEWAY_PORT=37148
|
||||||
@ -7,45 +7,19 @@ SSO_GATEWAY_PORT=37148
|
|||||||
# SSO_CORS_ORIGIN=https://app.example.test
|
# SSO_CORS_ORIGIN=https://app.example.test
|
||||||
# SSO_GATEWAY_MAX_BODY_BYTES=33554432
|
# SSO_GATEWAY_MAX_BODY_BYTES=33554432
|
||||||
|
|
||||||
# Required: docv / Enso OpenID issuer URL (no trailing slash issues tolerated)
|
# Required: docv / Enso OpenID issuer URL (JWKS discovery)
|
||||||
OIDC_ISSUER=https://docv.example.test
|
OIDC_ISSUER=https://docv.example.test
|
||||||
# Optional: validate access_token audience
|
# Optional: validate access_token audience
|
||||||
# OIDC_AUDIENCE=smart-ide-gateway
|
# OIDC_AUDIENCE=smart-ide-gateway
|
||||||
# Optional: override JWKS URL (otherwise discovery or {issuer}/.well-known/jwks.json)
|
# Optional: override JWKS URL
|
||||||
# OIDC_JWKS_URI=https://docv.example.test/.well-known/jwks.json
|
# OIDC_JWKS_URI=https://docv.example.test/.well-known/jwks.json
|
||||||
|
|
||||||
# Same tokens as other services — gateway injects them toward upstreams
|
# smart-ide-global-api (must be running; same secret on both sides)
|
||||||
ORCHESTRATOR_HOST=127.0.0.1
|
GLOBAL_API_URL=http://127.0.0.1:37149
|
||||||
ORCHESTRATOR_PORT=37145
|
GLOBAL_API_INTERNAL_TOKEN=
|
||||||
ORCHESTRATOR_TOKEN=
|
|
||||||
|
|
||||||
REPOS_DEVTOOLS_HOST=127.0.0.1
|
# Optional: monorepo root for .logs/sso-gateway/
|
||||||
REPOS_DEVTOOLS_PORT=37140
|
# SMART_IDE_MONOREPO_ROOT=
|
||||||
REPOS_DEVTOOLS_TOKEN=
|
|
||||||
|
|
||||||
IA_DEV_GATEWAY_HOST=127.0.0.1
|
# Micro-service tokens and hosts are read by smart-ide-global-api, not this process.
|
||||||
IA_DEV_GATEWAY_PORT=37144
|
# See services/smart-ide-global-api/.env.example
|
||||||
IA_DEV_GATEWAY_TOKEN=
|
|
||||||
|
|
||||||
ANYTHINGLLM_DEVTOOLS_HOST=127.0.0.1
|
|
||||||
ANYTHINGLLM_DEVTOOLS_PORT=37146
|
|
||||||
ANYTHINGLLM_DEVTOOLS_TOKEN=
|
|
||||||
|
|
||||||
TOOLS_BRIDGE_HOST=127.0.0.1
|
|
||||||
TOOLS_BRIDGE_PORT=37147
|
|
||||||
TOOLS_BRIDGE_TOKEN=
|
|
||||||
|
|
||||||
LANGEXTRACT_API_HOST=127.0.0.1
|
|
||||||
LANGEXTRACT_API_PORT=37141
|
|
||||||
LANGEXTRACT_SERVICE_TOKEN=
|
|
||||||
|
|
||||||
REGEX_SEARCH_HOST=127.0.0.1
|
|
||||||
REGEX_SEARCH_PORT=37143
|
|
||||||
REGEX_SEARCH_TOKEN=
|
|
||||||
|
|
||||||
CLAW_PROXY_HOST=127.0.0.1
|
|
||||||
CLAW_PROXY_PORT=37142
|
|
||||||
CLAW_PROXY_TOKEN=
|
|
||||||
|
|
||||||
LOCAL_OFFICE_URL=http://127.0.0.1:8000
|
|
||||||
LOCAL_OFFICE_API_KEY=
|
|
||||||
|
|||||||
@ -1,21 +1,25 @@
|
|||||||
# smart-ide-sso-gateway
|
# smart-ide-sso-gateway
|
||||||
|
|
||||||
HTTP gateway that validates **user** access tokens from the docv / Enso OIDC issuer, then proxies requests to internal `smart_ide` micro-services using each service’s **technical** credentials (Bearer or `X-API-Key`).
|
HTTP gateway that validates **user** access tokens from the docv / Enso OIDC issuer, then forwards requests to **`smart-ide-global-api`**, which proxies to internal `smart_ide` micro-services using each service’s **technical** credentials (Bearer or `X-API-Key`).
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
|
|
||||||
- Verify `Authorization: Bearer <access_token>` with JWKS (`OIDC_ISSUER`, optional `OIDC_AUDIENCE`, optional `OIDC_JWKS_URI`).
|
- Verify `Authorization: Bearer <access_token>` with JWKS (`OIDC_ISSUER`, optional `OIDC_AUDIENCE`, optional `OIDC_JWKS_URI`).
|
||||||
- Expose `GET /health` without auth.
|
- Expose `GET /health` without auth.
|
||||||
- Expose `GET /v1/token/verify` and `GET /v1/upstreams` with user Bearer.
|
- Expose `GET /v1/token/verify` and `GET /v1/upstreams` with user Bearer.
|
||||||
- Proxy `ANY /proxy/<upstream_key>/<path>` to the configured upstream, replacing the user token with the service token and adding `X-OIDC-Sub` / `X-OIDC-Email` when present in the JWT.
|
- Proxy `ANY /proxy/<upstream_key>/<path>` to **smart-ide-global-api** (`GLOBAL_API_URL`, `GLOBAL_API_INTERNAL_TOKEN`), which relays to the target service and adds upstream auth plus `X-OIDC-Sub` / `X-OIDC-Email` when present in the JWT.
|
||||||
|
|
||||||
|
Structured request logs (except `GET /health` and `OPTIONS`) are appended to **`.logs/sso-gateway/access.log`** under the monorepo root.
|
||||||
|
|
||||||
User accounts, project membership, and product databases stay in **each application’s backend** (docv, Enso, etc.); this gateway does not store them.
|
User accounts, project membership, and product databases stay in **each application’s backend** (docv, Enso, etc.); this gateway does not store them.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
|
Start **smart-ide-global-api** first, then:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd services/smart-ide-sso-gateway
|
cd services/smart-ide-sso-gateway
|
||||||
cp .env.example .env # edit OIDC_ISSUER and service tokens
|
cp .env.example .env # edit OIDC_ISSUER, GLOBAL_API_INTERNAL_TOKEN (match global API)
|
||||||
set -a && source .env && set +a
|
set -a && source .env && set +a
|
||||||
npm ci
|
npm ci
|
||||||
npm run build
|
npm run build
|
||||||
@ -24,9 +28,10 @@ npm start
|
|||||||
|
|
||||||
Default listen: `http://127.0.0.1:37148`.
|
Default listen: `http://127.0.0.1:37148`.
|
||||||
|
|
||||||
Upstream URLs and tokens reuse the same environment variables as the rest of the monorepo (`ORCHESTRATOR_*`, `TOOLS_BRIDGE_*`, `LOCAL_OFFICE_URL` / `LOCAL_OFFICE_API_KEY`, etc.). See `src/upstreams.ts`.
|
Micro-service URLs and tokens are configured on **smart-ide-global-api** (`services/smart-ide-global-api/.env.example` or aggregated `config/services.local.env`).
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
- Feature: [`docs/features/sso-gateway-service.md`](../../docs/features/sso-gateway-service.md)
|
- Feature: [`docs/features/sso-gateway-service.md`](../../docs/features/sso-gateway-service.md)
|
||||||
- API: [`docs/API/sso-gateway-api.md`](../../docs/API/sso-gateway-api.md)
|
- API: [`docs/API/sso-gateway-api.md`](../../docs/API/sso-gateway-api.md)
|
||||||
|
- Global API: [`docs/API/global-api.md`](../../docs/API/global-api.md)
|
||||||
|
|||||||
19
services/smart-ide-sso-gateway/src/accessLog.ts
Normal file
19
services/smart-ide-sso-gateway/src/accessLog.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import fs from "node:fs/promises";
|
||||||
|
import path from "node:path";
|
||||||
|
import { repoRoot } from "./repoRoot.js";
|
||||||
|
|
||||||
|
const logFile = async (): Promise<string> => {
|
||||||
|
const dir = path.join(repoRoot(), ".logs", "sso-gateway");
|
||||||
|
await fs.mkdir(dir, { recursive: true });
|
||||||
|
return path.join(dir, "access.log");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const appendSsoAccessLog = async (
|
||||||
|
entry: Record<string, unknown>,
|
||||||
|
): Promise<void> => {
|
||||||
|
const file = await logFile();
|
||||||
|
const line =
|
||||||
|
JSON.stringify({ ts: new Date().toISOString(), service: "smart-ide-sso-gateway", ...entry }) +
|
||||||
|
"\n";
|
||||||
|
await fs.appendFile(file, line, "utf8");
|
||||||
|
};
|
||||||
11
services/smart-ide-sso-gateway/src/repoRoot.ts
Normal file
11
services/smart-ide-sso-gateway/src/repoRoot.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
export const repoRoot = (): string => {
|
||||||
|
const fromEnv = process.env.SMART_IDE_MONOREPO_ROOT?.trim();
|
||||||
|
if (fromEnv) {
|
||||||
|
return path.resolve(fromEnv);
|
||||||
|
}
|
||||||
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
return path.resolve(here, "..", "..", "..");
|
||||||
|
};
|
||||||
@ -1,13 +1,19 @@
|
|||||||
import * as http from "node:http";
|
import * as http from "node:http";
|
||||||
import type { JWTPayload } from "jose";
|
import type { JWTPayload } from "jose";
|
||||||
|
import { appendSsoAccessLog } from "./accessLog.js";
|
||||||
import { discoverJwksUri, createVerify, type VerifyFn } from "./oidc.js";
|
import { discoverJwksUri, createVerify, type VerifyFn } from "./oidc.js";
|
||||||
import { listUpstreamKeys, resolveUpstream, type UpstreamAuth } from "./upstreams.js";
|
import { listUpstreamKeys } from "./upstreams.js";
|
||||||
|
|
||||||
const HOST = process.env.SSO_GATEWAY_HOST ?? "127.0.0.1";
|
const HOST = process.env.SSO_GATEWAY_HOST ?? "127.0.0.1";
|
||||||
const PORT = Number(process.env.SSO_GATEWAY_PORT ?? "37148");
|
const PORT = Number(process.env.SSO_GATEWAY_PORT ?? "37148");
|
||||||
const MAX_BODY_BYTES = Number(process.env.SSO_GATEWAY_MAX_BODY_BYTES ?? "33554432");
|
const MAX_BODY_BYTES = Number(process.env.SSO_GATEWAY_MAX_BODY_BYTES ?? "33554432");
|
||||||
const CORS_ORIGIN = process.env.SSO_CORS_ORIGIN?.trim() ?? "";
|
const CORS_ORIGIN = process.env.SSO_CORS_ORIGIN?.trim() ?? "";
|
||||||
|
|
||||||
|
const trimSlash = (s: string): string => s.replace(/\/+$/, "");
|
||||||
|
const globalApiBase = (): string =>
|
||||||
|
trimSlash(process.env.GLOBAL_API_URL ?? "http://127.0.0.1:37149");
|
||||||
|
const globalApiToken = (): string => process.env.GLOBAL_API_INTERNAL_TOKEN?.trim() ?? "";
|
||||||
|
|
||||||
const corsHeaders = (): Record<string, string> => {
|
const corsHeaders = (): Record<string, string> => {
|
||||||
if (!CORS_ORIGIN) {
|
if (!CORS_ORIGIN) {
|
||||||
return {};
|
return {};
|
||||||
@ -65,9 +71,8 @@ const hopByHop = new Set([
|
|||||||
"host",
|
"host",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const buildForwardHeaders = (
|
const buildForwardHeadersToGlobalApi = (
|
||||||
req: http.IncomingMessage,
|
req: http.IncomingMessage,
|
||||||
serviceAuth: UpstreamAuth,
|
|
||||||
payload: JWTPayload,
|
payload: JWTPayload,
|
||||||
): Headers => {
|
): Headers => {
|
||||||
const out = new Headers();
|
const out = new Headers();
|
||||||
@ -84,12 +89,9 @@ const buildForwardHeaders = (
|
|||||||
}
|
}
|
||||||
out.set(k, Array.isArray(v) ? v.join(", ") : v);
|
out.set(k, Array.isArray(v) ? v.join(", ") : v);
|
||||||
}
|
}
|
||||||
if (serviceAuth.kind === "bearer") {
|
const gToken = globalApiToken();
|
||||||
if (serviceAuth.token) {
|
if (gToken) {
|
||||||
out.set("Authorization", `Bearer ${serviceAuth.token}`);
|
out.set("Authorization", `Bearer ${gToken}`);
|
||||||
}
|
|
||||||
} else if (serviceAuth.value) {
|
|
||||||
out.set(serviceAuth.name, serviceAuth.value);
|
|
||||||
}
|
}
|
||||||
const sub = payload.sub;
|
const sub = payload.sub;
|
||||||
if (typeof sub === "string" && sub.length > 0) {
|
if (typeof sub === "string" && sub.length > 0) {
|
||||||
@ -109,13 +111,13 @@ const responseHopByHop = new Set([
|
|||||||
"content-encoding",
|
"content-encoding",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const proxyToUpstream = async (
|
const proxyToGlobalApi = async (
|
||||||
req: http.IncomingMessage,
|
req: http.IncomingMessage,
|
||||||
res: http.ServerResponse,
|
res: http.ServerResponse,
|
||||||
targetUrl: string,
|
targetUrl: string,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
body: Buffer,
|
body: Buffer,
|
||||||
): Promise<void> => {
|
): Promise<number> => {
|
||||||
const method = req.method ?? "GET";
|
const method = req.method ?? "GET";
|
||||||
const init: RequestInit = {
|
const init: RequestInit = {
|
||||||
method,
|
method,
|
||||||
@ -136,6 +138,7 @@ const proxyToUpstream = async (
|
|||||||
}
|
}
|
||||||
const buf = Buffer.from(await out.arrayBuffer());
|
const buf = Buffer.from(await out.arrayBuffer());
|
||||||
res.end(buf);
|
res.end(buf);
|
||||||
|
return out.status;
|
||||||
};
|
};
|
||||||
|
|
||||||
const publicClaims = (payload: JWTPayload): Record<string, unknown> => {
|
const publicClaims = (payload: JWTPayload): Record<string, unknown> => {
|
||||||
@ -154,6 +157,12 @@ const main = async (): Promise<void> => {
|
|||||||
console.error("smart-ide-sso-gateway: set OIDC_ISSUER (docv / Enso IdP issuer URL).");
|
console.error("smart-ide-sso-gateway: set OIDC_ISSUER (docv / Enso IdP issuer URL).");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
if (!globalApiToken()) {
|
||||||
|
console.error(
|
||||||
|
"smart-ide-sso-gateway: set GLOBAL_API_INTERNAL_TOKEN (must match smart-ide-global-api).",
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
const audience = process.env.OIDC_AUDIENCE?.trim();
|
const audience = process.env.OIDC_AUDIENCE?.trim();
|
||||||
const jwksUri = await discoverJwksUri(issuer);
|
const jwksUri = await discoverJwksUri(issuer);
|
||||||
console.error(`smart-ide-sso-gateway: JWKS URI ${jwksUri}`);
|
console.error(`smart-ide-sso-gateway: JWKS URI ${jwksUri}`);
|
||||||
@ -161,24 +170,32 @@ const main = async (): Promise<void> => {
|
|||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
|
const started = Date.now();
|
||||||
|
const method = req.method ?? "GET";
|
||||||
|
const url = new URL(req.url ?? "/", `http://${HOST}`);
|
||||||
|
const pathname = url.pathname;
|
||||||
|
let logPath = pathname;
|
||||||
|
let upstreamKey = "";
|
||||||
|
let status = 0;
|
||||||
|
let oidcSub: string | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (req.method === "OPTIONS") {
|
if (method === "OPTIONS") {
|
||||||
res.writeHead(204, corsHeaders());
|
res.writeHead(204, corsHeaders());
|
||||||
res.end();
|
res.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(req.url ?? "/", `http://${HOST}`);
|
if (method === "GET" && (pathname === "/health" || pathname === "/health/")) {
|
||||||
const pathname = url.pathname;
|
status = 200;
|
||||||
|
json(res, status, { status: "ok", service: "smart-ide-sso-gateway" });
|
||||||
if (req.method === "GET" && (pathname === "/health" || pathname === "/health/")) {
|
|
||||||
json(res, 200, { status: "ok", service: "smart-ide-sso-gateway" });
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = readBearer(req);
|
const token = readBearer(req);
|
||||||
if (!token) {
|
if (!token) {
|
||||||
json(res, 401, { error: "Missing Authorization: Bearer <access_token>" });
|
status = 401;
|
||||||
|
json(res, status, { error: "Missing Authorization: Bearer <access_token>" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,45 +203,61 @@ const main = async (): Promise<void> => {
|
|||||||
try {
|
try {
|
||||||
payload = await verify(token);
|
payload = await verify(token);
|
||||||
} catch {
|
} catch {
|
||||||
json(res, 401, { error: "Invalid or expired token" });
|
status = 401;
|
||||||
|
json(res, status, { error: "Invalid or expired token" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === "GET" && pathname === "/v1/token/verify") {
|
if (typeof payload.sub === "string") {
|
||||||
json(res, 200, { valid: true, claims: publicClaims(payload) });
|
oidcSub = payload.sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === "GET" && pathname === "/v1/token/verify") {
|
||||||
|
status = 200;
|
||||||
|
json(res, status, { valid: true, claims: publicClaims(payload) });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === "GET" && pathname === "/v1/upstreams") {
|
if (method === "GET" && pathname === "/v1/upstreams") {
|
||||||
json(res, 200, { upstreams: listUpstreamKeys() });
|
status = 200;
|
||||||
|
json(res, status, { upstreams: listUpstreamKeys() });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyMatch = /^\/proxy\/([^/]+)(\/.*)?$/.exec(pathname);
|
const proxyMatch = /^\/proxy\/([^/]+)(\/.*)?$/.exec(pathname);
|
||||||
if (!proxyMatch || !req.method) {
|
if (!proxyMatch || !method) {
|
||||||
json(res, 404, { error: "Not found" });
|
status = 404;
|
||||||
|
json(res, status, { error: "Not found" });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const key = proxyMatch[1];
|
upstreamKey = proxyMatch[1];
|
||||||
const rest = proxyMatch[2] ?? "/";
|
const rest = proxyMatch[2] ?? "/";
|
||||||
const upstream = resolveUpstream(key);
|
logPath = pathname;
|
||||||
if (!upstream) {
|
const targetUrl = `${globalApiBase()}/v1/upstream/${upstreamKey}${rest}${url.search}`;
|
||||||
json(res, 404, { error: `Unknown upstream: ${key}` });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (upstream.auth.kind === "header" && !upstream.auth.value) {
|
|
||||||
json(res, 503, { error: `API key not configured for upstream ${key} (LOCAL_OFFICE_API_KEY)` });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await readBodyBuffer(req);
|
const body = await readBodyBuffer(req);
|
||||||
const targetUrl = `${upstream.baseUrl}${rest}${url.search}`;
|
const headers = buildForwardHeadersToGlobalApi(req, payload);
|
||||||
const headers = buildForwardHeaders(req, upstream.auth, payload);
|
status = await proxyToGlobalApi(req, res, targetUrl, headers, body);
|
||||||
await proxyToUpstream(req, res, targetUrl, headers, body);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = e instanceof Error ? e.message : String(e);
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
if (!res.headersSent) {
|
if (!res.headersSent) {
|
||||||
json(res, 400, { error: msg });
|
status = 400;
|
||||||
|
json(res, status, { error: msg });
|
||||||
|
} else if (status === 0) {
|
||||||
|
status = 500;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
const skipLog =
|
||||||
|
method === "OPTIONS" ||
|
||||||
|
(method === "GET" && (pathname === "/health" || pathname === "/health/"));
|
||||||
|
if (!skipLog) {
|
||||||
|
void appendSsoAccessLog({
|
||||||
|
method,
|
||||||
|
path: logPath,
|
||||||
|
upstream: upstreamKey || undefined,
|
||||||
|
status,
|
||||||
|
durationMs: Date.now() - started,
|
||||||
|
oidcSub,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -1,88 +1,4 @@
|
|||||||
export type UpstreamAuth =
|
/** Upstream keys exposed to OIDC-authenticated clients; traffic is relayed via smart-ide-global-api. */
|
||||||
| { kind: "bearer"; token: string }
|
|
||||||
| { kind: "header"; name: string; value: string };
|
|
||||||
|
|
||||||
export type UpstreamTarget = {
|
|
||||||
baseUrl: string;
|
|
||||||
auth: UpstreamAuth;
|
|
||||||
};
|
|
||||||
|
|
||||||
const trimSlash = (s: string): string => s.replace(/\/+$/, "");
|
|
||||||
|
|
||||||
const env = (k: string, d: string): string => process.env[k]?.trim() ?? d;
|
|
||||||
|
|
||||||
export const resolveUpstream = (key: string): UpstreamTarget | null => {
|
|
||||||
switch (key) {
|
|
||||||
case "orchestrator":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(
|
|
||||||
`http://${env("ORCHESTRATOR_HOST", "127.0.0.1")}:${env("ORCHESTRATOR_PORT", "37145")}`,
|
|
||||||
),
|
|
||||||
auth: { kind: "bearer", token: env("ORCHESTRATOR_TOKEN", "") },
|
|
||||||
};
|
|
||||||
case "repos_devtools":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(
|
|
||||||
`http://${env("REPOS_DEVTOOLS_HOST", "127.0.0.1")}:${env("REPOS_DEVTOOLS_PORT", "37140")}`,
|
|
||||||
),
|
|
||||||
auth: { kind: "bearer", token: env("REPOS_DEVTOOLS_TOKEN", "") },
|
|
||||||
};
|
|
||||||
case "ia_dev_gateway":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(
|
|
||||||
`http://${env("IA_DEV_GATEWAY_HOST", "127.0.0.1")}:${env("IA_DEV_GATEWAY_PORT", "37144")}`,
|
|
||||||
),
|
|
||||||
auth: { kind: "bearer", token: env("IA_DEV_GATEWAY_TOKEN", "") },
|
|
||||||
};
|
|
||||||
case "anythingllm_devtools":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(
|
|
||||||
`http://${env("ANYTHINGLLM_DEVTOOLS_HOST", "127.0.0.1")}:${env("ANYTHINGLLM_DEVTOOLS_PORT", "37146")}`,
|
|
||||||
),
|
|
||||||
auth: { kind: "bearer", token: env("ANYTHINGLLM_DEVTOOLS_TOKEN", "") },
|
|
||||||
};
|
|
||||||
case "tools_bridge":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(
|
|
||||||
`http://${env("TOOLS_BRIDGE_HOST", "127.0.0.1")}:${env("TOOLS_BRIDGE_PORT", "37147")}`,
|
|
||||||
),
|
|
||||||
auth: { kind: "bearer", token: env("TOOLS_BRIDGE_TOKEN", "") },
|
|
||||||
};
|
|
||||||
case "langextract":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(
|
|
||||||
`http://${env("LANGEXTRACT_API_HOST", "127.0.0.1")}:${env("LANGEXTRACT_API_PORT", "37141")}`,
|
|
||||||
),
|
|
||||||
auth: { kind: "bearer", token: env("LANGEXTRACT_SERVICE_TOKEN", "") },
|
|
||||||
};
|
|
||||||
case "regex_search":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(
|
|
||||||
`http://${env("REGEX_SEARCH_HOST", "127.0.0.1")}:${env("REGEX_SEARCH_PORT", "37143")}`,
|
|
||||||
),
|
|
||||||
auth: { kind: "bearer", token: env("REGEX_SEARCH_TOKEN", "") },
|
|
||||||
};
|
|
||||||
case "claw_proxy":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(
|
|
||||||
`http://${env("CLAW_PROXY_HOST", "127.0.0.1")}:${env("CLAW_PROXY_PORT", "37142")}`,
|
|
||||||
),
|
|
||||||
auth: { kind: "bearer", token: env("CLAW_PROXY_TOKEN", "") },
|
|
||||||
};
|
|
||||||
case "local_office":
|
|
||||||
return {
|
|
||||||
baseUrl: trimSlash(env("LOCAL_OFFICE_URL", "http://127.0.0.1:8000")),
|
|
||||||
auth: {
|
|
||||||
kind: "header",
|
|
||||||
name: "X-API-Key",
|
|
||||||
value: env("LOCAL_OFFICE_API_KEY", ""),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const listUpstreamKeys = (): string[] => [
|
export const listUpstreamKeys = (): string[] => [
|
||||||
"orchestrator",
|
"orchestrator",
|
||||||
"repos_devtools",
|
"repos_devtools",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user