@leumas/connectors
Connectors — logged-in users link third-party accounts (databases, social/OAuth, SSH/SFTP). A per-provider connector registry + createConnectorsRouter (CRUD connections + test-connection). Absorbs...
@leumas/connectors
Logged-in users link third-party accounts — databases (Mongo, SQL, Redis, Firebase), media (Cloudinary), social/OAuth (Google, GitHub, Facebook, LinkedIn, YouTube, TikTok), and SSH/SFTP hosts. One connector registry powers the API, the connect form, and the Studio page.
Absorbs (DRY-consolidated from): leumas-middleware/database-connector (Mongo/SQL/Redis/Firebase/ Cloudinary adapters) + leumas-cursor-agent-mvp (databasesController, socialConnectionsController = OAuth, sshProfilesController = SSH/SFTP).
The model
A Connector describes one integration a user can link — its provider id, family (database | media | social | ssh), the form fields, which fields are secret, and a test(config) probe. Drivers (mongodb, redis, ssh2, …) are lazy-loaded inside test() so this engine has no hard driver dependency.
A Connection is a saved link: { id, owner, provider, name, config, lastTest }. Secrets are never returned to the client (masked as ••••••).
Usage (in leumas-api)
import { createConnectorsRouter } from '@leumas/connectors';
import { requireAuth } from '@leumas/auth';
// `store` persists the per-user `connections` collection (back it with @leumas/middleware).
app.use('/api/connectors', createConnectorsRouter({ store, requireAuth }));
The store contract
store.list({ owner }) → Connection[]
store.get(id, { owner }) → Connection | null
store.create(connection, { owner }) → Connection
store.update(id, changes, { owner }) → Connection
store.remove(id, { owner }) → void
createMemoryStore() is a zero-dep default for dev/tests.
Routes
| Method | Path | Purpose |
|---|---|---|
| GET | /providers | connector manifests (fields, family, auth) |
| GET | / | this user's connections (secrets masked) |
| POST | / | create { provider, name, config } |
| GET/PUT/DELETE | /:id | read / update / remove a connection |
| POST | /:id/test | test a saved connection |
| POST | /test | test an unsaved { provider, config } |
| GET | /:provider/authorize | OAuth authorize URL (social connectors) |
Add a provider
import { defineConnector } from '@leumas/connectors';
export const stripe = defineConnector({
provider: 'stripe', family: 'media', name: 'Stripe',
fields: [{ name: 'secretKey', type: 'password', required: true }],
async test(config) { /* probe */ return { ok: true }; },
});
Pass it in connectors: [...allConnectors, stripe] to createConnectorsRouter, or add it to src/providers/index.js.
Operations — what a connection can actually DO
A connector carries an .integration payload ({ baseUrl, auth, operations }) and that one property is what makes it callable: executeOperation, createIntegrationRunner, the /:id/operations route, the grid's integration: cell and @leumas/invoke's integration kind all look for it and nothing else.
api-family connectors get it fromdefineApiIntegration(src/providers/api/catalog.js).- OAuth connectors get it from
src/providers/operations/<provider>.js— one pure-data file per
provider, assembled by operations/index.js and attached in providers/social.js. 156 operations across 27 providers.
- A provider with no catalog is listed in
UNCATALOGUEDwith the sentence saying why, which the
Studio panel shows instead of an empty list. The rule: a provider gets a catalog when a bearer token is enough to call it; when it is not, it gets a sentence.
Declare parameters with the compact spec in operations/_shared.js:
params: args({
'*id': 'string|Required — the star means required.',
part: 'string=snippet|A default, applied before the call.',
mine: 'boolean=true|Typed: this is a real boolean, not "true".',
})
An operation may also declare scopes: [...] (needs more than the read-ish defaults), bodyStyle: 'json' | 'form' | 'multipart', and uploadStyle: 'google-resumable'.
[warning] A declared scope must be in the provider descriptor'sscopesoroptionalScopes. If it is in neither, the authorize URL never asks for it — so it can never be granted and the operation can never work, on any account, with no error anywhere.pnpm smoke:connector-opsasserts this.
Environment
| Variable | What it does |
|---|---|
LEUMAS_SECRET_KEY | Seals every credential at rest (aes-256-gcm, lsx1: prefix). Without it tokens and database passwords are stored in clear text, and the Connections page says so on the page rather than leaving you to infer it. |
LEUMAS_OAUTH_<PROVIDER>_CLIENT_ID / _SECRET | This deployment's registered OAuth app. <PROVIDER> is the provider id uppercased with dashes as underscores — google-sheets → LEUMAS_OAUTH_GOOGLE_SHEETS_CLIENT_ID. The Google family falls back to the plain GOOGLE pair, so one registered Google app serves Docs, Drive, Sheets, Gmail, Calendar and YouTube. |
LEUMAS_OAUTH_SHARED_APPS | 1 makes those env apps shared defaults a tenant may override with their own, instead of pinned (the default, where env wins and the UI locks the form). See below. |
LEUMAS_OAUTH_REDIRECT_URI | Pins the callback URI. Otherwise it is derived from the address the browser is at — env → X-Forwarded-Host → Origin → Referer → Host — never from the listening socket, because in dev the browser is on Vite and 127.0.0.1 and localhost are different origins to Google. |
Pinned vs shared OAuth apps
Two modes, opposite failure modes, both real — so it is a flag rather than a flip:
- Pinned (default). Env wins outright and the provider shows as
locked. An operator must not be
able to silently repoint a fleet's or a vendor's Google app by saving a row over it.
- Shared (
LEUMAS_OAUTH_SHARED_APPS=1). Env is Leumas's own registered app, offered so a new
Studio can connect an account with no OAuth paperwork — and a tenant that registers their own app beats it. status() reports shared: true (works today, not locked) or overrides: 'shared' (your own app is the one in use).
A tenant's own app is the better one whenever it exists: their name on the consent screen, their quota, and no shared-app suspension taking them down with it.