feat(sso-gateway): add OIDC JWT gateway and proxy to micro-services
- New service smart-ide-sso-gateway (port 37148): JWKS verify, /health, /v1/token/verify, /v1/upstreams, /proxy/<key>/... - CORS on JSON responses when SSO_CORS_ORIGIN is set; optional empty bearer for langextract upstream - Docs: feature, API, repo index; wire sso-docv-enso and services scope - Extend config/services.local.env.example with OIDC and gateway vars
This commit is contained in:
parent
f482b0e2b8
commit
68cb5737c5
@ -56,6 +56,15 @@ REPOS_DEVTOOLS_URL=http://127.0.0.1:37140
|
|||||||
# Orchestrator → tools bridge (for intent resolution hints)
|
# Orchestrator → tools bridge (for intent resolution hints)
|
||||||
TOOLS_BRIDGE_URL=http://127.0.0.1:37147
|
TOOLS_BRIDGE_URL=http://127.0.0.1:37147
|
||||||
|
|
||||||
|
# --- smart-ide-sso-gateway (OIDC user token → proxy to micro-services) ---
|
||||||
|
SSO_GATEWAY_HOST=127.0.0.1
|
||||||
|
SSO_GATEWAY_PORT=37148
|
||||||
|
# SSO_CORS_ORIGIN=
|
||||||
|
# Required when running the gateway: docv / Enso issuer URL
|
||||||
|
OIDC_ISSUER=
|
||||||
|
# OIDC_AUDIENCE=
|
||||||
|
# OIDC_JWKS_URI=
|
||||||
|
|
||||||
# Ollama / AnythingLLM (orchestrator)
|
# Ollama / AnythingLLM (orchestrator)
|
||||||
OLLAMA_URL=http://127.0.0.1:11434
|
OLLAMA_URL=http://127.0.0.1:11434
|
||||||
ANYTHINGLLM_BASE_URL=
|
ANYTHINGLLM_BASE_URL=
|
||||||
|
|||||||
@ -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-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) |
|
||||||
|
|
||||||
**OpenAPI** : FastAPI expose une spec interactive pour **langextract-api** (`/docs`) et **local-office** (`/docs`) une fois le service démarré.
|
**OpenAPI** : FastAPI expose une spec interactive pour **langextract-api** (`/docs`) et **local-office** (`/docs`) une fois le service démarré.
|
||||||
|
|||||||
52
docs/API/sso-gateway-api.md
Normal file
52
docs/API/sso-gateway-api.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# API — smart-ide-sso-gateway
|
||||||
|
|
||||||
|
Écoute par défaut : **`127.0.0.1:37148`**. Configuration : `services/smart-ide-sso-gateway/.env.example`, agrégat [config/services.local.env.example](../../config/services.local.env.example).
|
||||||
|
|
||||||
|
## Authentification
|
||||||
|
|
||||||
|
| Route | Auth utilisateur |
|
||||||
|
|-------|------------------|
|
||||||
|
| `GET /health` | Aucune |
|
||||||
|
| `OPTIONS *` | Aucune (préflight CORS si `SSO_CORS_ORIGIN` défini) |
|
||||||
|
| Toutes les autres | `Authorization: Bearer <access_token>` OIDC (docv / Enso) |
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### `GET /health`
|
||||||
|
|
||||||
|
Réponse `200` : `{ "status": "ok", "service": "smart-ide-sso-gateway" }`.
|
||||||
|
|
||||||
|
### `GET /v1/token/verify`
|
||||||
|
|
||||||
|
Vérifie le Bearer utilisateur. Réponse `200` : `{ "valid": true, "claims": { ... } }` avec un sous-ensemble des claims (`sub`, `iss`, `aud`, `exp`, `iat`, `email`, `name`, `preferred_username`).
|
||||||
|
|
||||||
|
### `GET /v1/upstreams`
|
||||||
|
|
||||||
|
Liste les clés de proxy disponibles : `{ "upstreams": [ "orchestrator", ... ] }`.
|
||||||
|
|
||||||
|
### Proxy — `ANY /proxy/<upstream_key>/<path>`
|
||||||
|
|
||||||
|
- **`<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/...`).
|
||||||
|
- **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`.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
## Variables d’environnement (passerelle)
|
||||||
|
|
||||||
|
| Variable | Rôle |
|
||||||
|
|----------|------|
|
||||||
|
| `OIDC_ISSUER` | Obligatoire — URL de l’issuer OpenID |
|
||||||
|
| `OIDC_AUDIENCE` | Optionnel — audience attendue du JWT |
|
||||||
|
| `OIDC_JWKS_URI` | Optionnel — URI JWKS explicite |
|
||||||
|
| `SSO_GATEWAY_HOST` / `SSO_GATEWAY_PORT` | Bind HTTP |
|
||||||
|
| `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 |
|
||||||
|
|
||||||
|
Les jetons et hôtes des micro-services : mêmes noms que dans `config/services.local.env.example`.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [sso-docv-enso.md](../features/sso-docv-enso.md)
|
||||||
|
- [README du service](../../services/smart-ide-sso-gateway/README.md)
|
||||||
@ -33,6 +33,7 @@ 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-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,6 +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) |
|
||||||
|
|
||||||
## Flux (authorization code + PKCE)
|
## Flux (authorization code + PKCE)
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ Un **client OAuth par env** (test / pprod / prod) ou un seul client avec **claim
|
|||||||
|
|
||||||
## Références internes
|
## Références internes
|
||||||
|
|
||||||
|
- [sso-gateway-service.md](./sso-gateway-service.md) — passerelle OIDC → API internes
|
||||||
- [platform-target.md](../platform-target.md) — matrice test / pprod / prod
|
- [platform-target.md](../platform-target.md) — matrice test / pprod / prod
|
||||||
- [deployment-target.md](../deployment-target.md) — TLS, pas de HTTP de contournement
|
- [deployment-target.md](../deployment-target.md) — TLS, pas de HTTP de contournement
|
||||||
|
|
||||||
|
|||||||
32
docs/features/sso-gateway-service.md
Normal file
32
docs/features/sso-gateway-service.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Passerelle SSO — accès utilisateur aux API `smart_ide`
|
||||||
|
|
||||||
|
## 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 **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.
|
||||||
|
|
||||||
|
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 ».
|
||||||
|
|
||||||
|
## Lien avec docv / Enso
|
||||||
|
|
||||||
|
Le flux **authorization code + PKCE** et le rôle d’**IdP** restent décrits dans [sso-docv-enso.md](./sso-docv-enso.md). La passerelle ne remplace pas docv : elle **consomme** les `access_token` déjà obtenus par le front ou un back de confiance.
|
||||||
|
|
||||||
|
## 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)).
|
||||||
|
|
||||||
|
## En-têtes vers l’amont
|
||||||
|
|
||||||
|
En plus de l’authentification de service, la passerelle peut envoyer :
|
||||||
|
|
||||||
|
- `X-OIDC-Sub` — claim `sub` du JWT utilisateur ;
|
||||||
|
- `X-OIDC-Email` — claim `email` si présent.
|
||||||
|
|
||||||
|
Les services amont peuvent s’en servir pour du **journal** ou des **règles fines** ; la **politique d’autorisation** métier reste de leur responsabilité.
|
||||||
|
|
||||||
|
## Documentation détaillée
|
||||||
|
|
||||||
|
- [API/sso-gateway-api.md](../API/sso-gateway-api.md)
|
||||||
|
- [services/smart-ide-sso-gateway/README.md](../../services/smart-ide-sso-gateway/README.md)
|
||||||
@ -41,6 +41,7 @@ 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 |
|
||||||
| [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
docs/repo/service-smart-ide-sso-gateway.md
Normal file
16
docs/repo/service-smart-ide-sso-gateway.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
- Service : **`services/smart-ide-sso-gateway/.env.example`**.
|
||||||
|
|
||||||
|
## Exploitation
|
||||||
|
|
||||||
|
Voir **[`services/smart-ide-sso-gateway/README.md`](../../services/smart-ide-sso-gateway/README.md)**, **[`docs/features/sso-gateway-service.md`](../features/sso-gateway-service.md)** et **[`docs/API/sso-gateway-api.md`](../API/sso-gateway-api.md)**.
|
||||||
|
|
||||||
|
## Voir aussi
|
||||||
|
|
||||||
|
- [sso-docv-enso.md](../features/sso-docv-enso.md)
|
||||||
@ -27,6 +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) |
|
||||||
|
|
||||||
## 3. Outils CLI et sous-modules (sans listener HTTP dédié)
|
## 3. Outils CLI et sous-modules (sans listener HTTP dédié)
|
||||||
|
|
||||||
@ -53,12 +54,13 @@ 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, gateway, 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-sso-gateway** (OIDC utilisateur → 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-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,6 +44,8 @@ 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).
|
||||||
|
|
||||||
**Configuration locale** : [config/services.local.env.example](../config/services.local.env.example).
|
**Configuration locale** : [config/services.local.env.example](../config/services.local.env.example).
|
||||||
|
|
||||||
## Documentation liée
|
## Documentation liée
|
||||||
|
|||||||
51
services/smart-ide-sso-gateway/.env.example
Normal file
51
services/smart-ide-sso-gateway/.env.example
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
# 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)
|
||||||
|
|
||||||
|
SSO_GATEWAY_HOST=127.0.0.1
|
||||||
|
SSO_GATEWAY_PORT=37148
|
||||||
|
# Optional: browser SPA origin for CORS on JSON and proxied responses
|
||||||
|
# SSO_CORS_ORIGIN=https://app.example.test
|
||||||
|
# SSO_GATEWAY_MAX_BODY_BYTES=33554432
|
||||||
|
|
||||||
|
# Required: docv / Enso OpenID issuer URL (no trailing slash issues tolerated)
|
||||||
|
OIDC_ISSUER=https://docv.example.test
|
||||||
|
# Optional: validate access_token audience
|
||||||
|
# OIDC_AUDIENCE=smart-ide-gateway
|
||||||
|
# Optional: override JWKS URL (otherwise discovery or {issuer}/.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
|
||||||
|
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-sso-gateway/.gitignore
vendored
Normal file
2
services/smart-ide-sso-gateway/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
30
services/smart-ide-sso-gateway/README.md
Normal file
30
services/smart-ide-sso-gateway/README.md
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# 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`).
|
||||||
|
|
||||||
|
## Responsibilities
|
||||||
|
|
||||||
|
- Verify `Authorization: Bearer <access_token>` with JWKS (`OIDC_ISSUER`, optional `OIDC_AUDIENCE`, optional `OIDC_JWKS_URI`).
|
||||||
|
- Expose `GET /health` without auth.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd services/smart-ide-sso-gateway
|
||||||
|
cp .env.example .env # edit OIDC_ISSUER and service tokens
|
||||||
|
set -a && source .env && set +a
|
||||||
|
npm ci
|
||||||
|
npm run build
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- 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)
|
||||||
63
services/smart-ide-sso-gateway/package-lock.json
generated
Normal file
63
services/smart-ide-sso-gateway/package-lock.json
generated
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"name": "@4nk/smart-ide-sso-gateway",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@4nk/smart-ide-sso-gateway",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jose": "^5.9.6"
|
||||||
|
},
|
||||||
|
"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/jose": {
|
||||||
|
"version": "5.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
|
||||||
|
"integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
services/smart-ide-sso-gateway/package.json
Normal file
23
services/smart-ide-sso-gateway/package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "@4nk/smart-ide-sso-gateway",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "OIDC JWT validation (docv/Enso IdP) and authenticated proxy to smart_ide micro-services.",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/server.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc -p ./",
|
||||||
|
"start": "node dist/server.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jose": "^5.9.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
services/smart-ide-sso-gateway/src/oidc.ts
Normal file
35
services/smart-ide-sso-gateway/src/oidc.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import * as jose from "jose";
|
||||||
|
|
||||||
|
export const discoverJwksUri = async (issuer: string): Promise<string> => {
|
||||||
|
const explicit = process.env.OIDC_JWKS_URI?.trim();
|
||||||
|
if (explicit) {
|
||||||
|
return explicit;
|
||||||
|
}
|
||||||
|
const base = issuer.replace(/\/$/, "");
|
||||||
|
const r = await fetch(`${base}/.well-known/openid-configuration`);
|
||||||
|
if (r.ok) {
|
||||||
|
const j = (await r.json()) as { jwks_uri?: string };
|
||||||
|
if (j.jwks_uri) {
|
||||||
|
return j.jwks_uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${base}/.well-known/jwks.json`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type VerifyFn = (token: string) => Promise<jose.JWTPayload>;
|
||||||
|
|
||||||
|
export const createVerify = (
|
||||||
|
jwksUri: string,
|
||||||
|
issuer: string,
|
||||||
|
audience: string | undefined,
|
||||||
|
): VerifyFn => {
|
||||||
|
const JWKS = jose.createRemoteJWKSet(new URL(jwksUri));
|
||||||
|
return async (token: string) => {
|
||||||
|
const { payload } = await jose.jwtVerify(token, JWKS, {
|
||||||
|
issuer,
|
||||||
|
audience: audience ? audience : undefined,
|
||||||
|
clockTolerance: 30,
|
||||||
|
});
|
||||||
|
return payload;
|
||||||
|
};
|
||||||
|
};
|
||||||
238
services/smart-ide-sso-gateway/src/server.ts
Normal file
238
services/smart-ide-sso-gateway/src/server.ts
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import * as http from "node:http";
|
||||||
|
import type { JWTPayload } from "jose";
|
||||||
|
import { discoverJwksUri, createVerify, type VerifyFn } from "./oidc.js";
|
||||||
|
import { listUpstreamKeys, resolveUpstream, type UpstreamAuth } from "./upstreams.js";
|
||||||
|
|
||||||
|
const HOST = process.env.SSO_GATEWAY_HOST ?? "127.0.0.1";
|
||||||
|
const PORT = Number(process.env.SSO_GATEWAY_PORT ?? "37148");
|
||||||
|
const MAX_BODY_BYTES = Number(process.env.SSO_GATEWAY_MAX_BODY_BYTES ?? "33554432");
|
||||||
|
const CORS_ORIGIN = process.env.SSO_CORS_ORIGIN?.trim() ?? "";
|
||||||
|
|
||||||
|
const corsHeaders = (): Record<string, string> => {
|
||||||
|
if (!CORS_ORIGIN) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"Access-Control-Allow-Origin": CORS_ORIGIN,
|
||||||
|
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
||||||
|
"Access-Control-Allow-Headers": "Authorization, Content-Type",
|
||||||
|
"Access-Control-Max-Age": "86400",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyCors = (res: http.ServerResponse): void => {
|
||||||
|
const h = corsHeaders();
|
||||||
|
for (const [k, v] of Object.entries(h)) {
|
||||||
|
res.setHeader(k, v);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const json = (res: http.ServerResponse, status: number, body: unknown): void => {
|
||||||
|
applyCors(res);
|
||||||
|
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,
|
||||||
|
payload: JWTPayload,
|
||||||
|
): 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);
|
||||||
|
}
|
||||||
|
const sub = payload.sub;
|
||||||
|
if (typeof sub === "string" && sub.length > 0) {
|
||||||
|
out.set("X-OIDC-Sub", sub);
|
||||||
|
}
|
||||||
|
const email = payload.email;
|
||||||
|
if (typeof email === "string" && email.length > 0) {
|
||||||
|
out.set("X-OIDC-Email", email);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseHopByHop = new Set([
|
||||||
|
"connection",
|
||||||
|
"keep-alive",
|
||||||
|
"transfer-encoding",
|
||||||
|
"content-encoding",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const proxyToUpstream = async (
|
||||||
|
req: http.IncomingMessage,
|
||||||
|
res: http.ServerResponse,
|
||||||
|
targetUrl: string,
|
||||||
|
headers: Headers,
|
||||||
|
body: Buffer,
|
||||||
|
): Promise<void> => {
|
||||||
|
const method = req.method ?? "GET";
|
||||||
|
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);
|
||||||
|
applyCors(res);
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
const publicClaims = (payload: JWTPayload): Record<string, unknown> => {
|
||||||
|
const out: Record<string, unknown> = {};
|
||||||
|
for (const k of ["sub", "iss", "aud", "exp", "iat", "email", "name", "preferred_username"]) {
|
||||||
|
if (payload[k] !== undefined) {
|
||||||
|
out[k] = payload[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const main = async (): Promise<void> => {
|
||||||
|
const issuer = process.env.OIDC_ISSUER?.trim();
|
||||||
|
if (!issuer) {
|
||||||
|
console.error("smart-ide-sso-gateway: set OIDC_ISSUER (docv / Enso IdP issuer URL).");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
const audience = process.env.OIDC_AUDIENCE?.trim();
|
||||||
|
const jwksUri = await discoverJwksUri(issuer);
|
||||||
|
console.error(`smart-ide-sso-gateway: JWKS URI ${jwksUri}`);
|
||||||
|
const verify: VerifyFn = createVerify(jwksUri, issuer, audience || undefined);
|
||||||
|
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
if (req.method === "OPTIONS") {
|
||||||
|
res.writeHead(204, corsHeaders());
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(req.url ?? "/", `http://${HOST}`);
|
||||||
|
const pathname = url.pathname;
|
||||||
|
|
||||||
|
if (req.method === "GET" && (pathname === "/health" || pathname === "/health/")) {
|
||||||
|
json(res, 200, { status: "ok", service: "smart-ide-sso-gateway" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = readBearer(req);
|
||||||
|
if (!token) {
|
||||||
|
json(res, 401, { error: "Missing Authorization: Bearer <access_token>" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload: JWTPayload;
|
||||||
|
try {
|
||||||
|
payload = await verify(token);
|
||||||
|
} catch {
|
||||||
|
json(res, 401, { error: "Invalid or expired token" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "GET" && pathname === "/v1/token/verify") {
|
||||||
|
json(res, 200, { valid: true, claims: publicClaims(payload) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.method === "GET" && pathname === "/v1/upstreams") {
|
||||||
|
json(res, 200, { upstreams: listUpstreamKeys() });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyMatch = /^\/proxy\/([^/]+)(\/.*)?$/.exec(pathname);
|
||||||
|
if (!proxyMatch || !req.method) {
|
||||||
|
json(res, 404, { error: "Not found" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const key = proxyMatch[1];
|
||||||
|
const rest = proxyMatch[2] ?? "/";
|
||||||
|
const upstream = resolveUpstream(key);
|
||||||
|
if (!upstream) {
|
||||||
|
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 targetUrl = `${upstream.baseUrl}${rest}${url.search}`;
|
||||||
|
const headers = buildForwardHeaders(req, upstream.auth, payload);
|
||||||
|
await proxyToUpstream(req, res, targetUrl, headers, body);
|
||||||
|
} catch (e) {
|
||||||
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
json(res, 400, { error: msg });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(PORT, HOST, () => {
|
||||||
|
console.error(`smart-ide-sso-gateway listening on http://${HOST}:${PORT}`);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
void main();
|
||||||
96
services/smart-ide-sso-gateway/src/upstreams.ts
Normal file
96
services/smart-ide-sso-gateway/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-sso-gateway/tsconfig.json
Normal file
16
services/smart-ide-sso-gateway/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"]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user