external-rest-api (deployed as image external-rest-api) stores and serves user profile channel identifiers (email, slack username, telegram id), team membership roles, invitation workflows, and RPC token brokerage.
- Node.js + TypeScript
- Express.js
- Internal REST integration with
control-api(through the profile control funnel) - Internal REST integration with
member-registration-servicefor outbound email delivery - Google ID token verification (
google-auth-library)
POST /api/v1/auth/google- login with Google ID tokenGET /api/v1/me- current user + team + profileGET /api/v1/me/contexts- contexts authorized for the authenticated userGET /api/v1/me/agents- agents authorized for the authenticated userPUT /api/v1/me/profile- update current user profile channelsGET /api/v1/team/contexts- contexts authorized for the authenticated teamGET /api/v1/team/agents- agents authorized for the authenticated teamPOST /api/v1/rpc/token- issue short-lived RPC access token forrpc-proxyGET /api/v1/team/members- list team membersPOST /api/v1/team/members/invite- invite member (admin/inviter)DELETE /api/v1/team/members/:userId- remove member (admin)DELETE /api/v1/members/:userId- retire a member account (admin); requires JSON{ "reason": "<non-empty>" }and anIdempotency-Keyheader. Missing fields return400; this is a breaking v1 contract for direct clients.POST /api/v1/invitations/accept- accept invitation after profile-ui sign-inGET /api/v1/directory/search?q=...- lookup directory users for channel mapping
admin: invite and delete membersinviter: invite membersmember: basic profile usage
See .env.example.
Environment variables:
EXTERNAL_REST_API_PORT: HTTP port the service listens on (default8091).EXTERNAL_REST_API_CORS_ORIGIN: Allowed CORS origin for browser requests (*allows all origins).EXTERNAL_REST_API_GOOGLE_CLIENT_ID: Google OAuth client ID used to verify incoming Google ID tokens.EXTERNAL_REST_API_CONTROL_API_BASE_URL: Base URL for internal calls tocontrol-api(typically through the profile control funnel).EXTERNAL_REST_API_CONTROL_API_SERVICE_TOKEN: Shared bearer token used whenexternal-rest-apiauthenticates tocontrol-api.EXTERNAL_REST_API_CONTROL_API_SERVICE_NAME: Service identity sent inx-service-tokenforcontrol-apiinternal auth checks (defaultexternal-rest-api).EXTERNAL_REST_API_MEMBER_REGISTRATION_SERVICE_BASE_URL: Base URL for internal calls tomember-registration-service.EXTERNAL_REST_API_MEMBER_REGISTRATION_SERVICE_SERVICE_TOKEN: Shared bearer token used whenexternal-rest-apiauthenticates tomember-registration-service.EXTERNAL_REST_API_MEMBER_REGISTRATION_SERVICE_SERVICE_NAME: Service identity sent inx-service-tokenformember-registration-serviceinternal auth checks.EXTERNAL_REST_API_JWT_PUBLIC_KEY: RSA public key used to verify session JWTs locally inexternal-rest-api(RS256).EXTERNAL_REST_API_JWT_ISSUER: Expectedissclaim for session JWT verification.EXTERNAL_REST_API_JWT_AUDIENCE: Expectedaudclaim for session JWT verification.
Session JWT issuance is centralized in control-api. external-rest-api verifies session JWTs locally with a public key and still uses control-api for internal profile/team operations.
external-rest-api uses a delegated security model where identity and session authority remain in control-api, while external-rest-api enforces request-level guards for public endpoints.
- Edge identity proof (Google):
POST /api/v1/auth/googleverifies the Google ID token withgoogle-auth-libraryusingEXTERNAL_REST_API_GOOGLE_CLIENT_IDas audience. - Session issuance authority: after Google token verification,
external-rest-apicallscontrol-apiinternal auth endpoints, andcontrol-apimints Clerum session tokens. - Local session verification: protected routes in
external-rest-apivalidate bearer tokens locally usingEXTERNAL_REST_API_JWT_PUBLIC_KEY(RS256) with issuer and audience checks. - Internal service authentication: calls from
external-rest-apitocontrol-apiincludeAuthorization: Bearer <service-token>plusx-service-token: <service-name>, and are checked bycontrol-apiinternal middleware. - Delegated email delivery: invitation email dispatch is forwarded to
member-registration-service, which owns the actual SMTP integration. - Authorization boundary: team membership and role decisions are enforced by
control-apiprofile services;external-rest-apiacts as the external facade and transport boundary. - Reduced machine-to-machine surface: service-token access to
/directory/searchwas removed; directory lookups now require an authenticated user token.
The new access discovery endpoints are forwarding-only endpoints that preserve claim-binding security:
GET /api/v1/me/contextsforwards tocontrol-apiGET /api/v1/external/users/:userId/contexts.GET /api/v1/me/agentsforwards tocontrol-apiGET /api/v1/external/users/:userId/agents.GET /api/v1/team/contextsforwards tocontrol-apiGET /api/v1/external/teams/:teamId/contexts.GET /api/v1/team/agentsforwards tocontrol-apiGET /api/v1/external/teams/:teamId/agents.
Security guarantees:
- Caller must present a valid bearer session token (
requireAuth). external-rest-apiderivesuserIdandteamIdfrom verified token claims, never from user input.external-rest-apiforwards the same session token tocontrol-apiinx-user-session-token.control-apire-validates token and claim-binding at route level (:userId/:teamIdmatch), preventing cross-user or cross-team data access.
- Harden Google claim validation: explicitly require
email_verified=trueand document accepted identity claims. - Keep centralized policy for sensitive operations: use a hybrid model where critical actions still perform live authorization checks in
control-api. - Strengthen transport and network boundaries: enforce TLS everywhere, tighten NetworkPolicies, and limit egress from
external-rest-apito only required services. - Improve credential hygiene: move all service credentials to Kubernetes Secrets, rotate regularly, and avoid inline placeholders in deployment examples.
- Add security-focused tests: extend tests for tampered tokens, disabled users, role downgrades, and authorization regression cases across route handlers.
- Add audit logging: emit structured auth and privileged-action audit events with request correlation IDs across
external-rest-apiandcontrol-api.
cd external-rest-api
npm install
npm run devcd external-rest-api
kubectl apply -f deploy/example.secret.yaml
make deploydeploy/deployment.yaml expects EXTERNAL_REST_API_JWT_PUBLIC_KEY in the external-rest-api-secrets Secret.