Skip to main content

API Keys for External Systems

Overview

API keys let a customer's external systems — a POS, a CRM, their own website — call into client-api without a CMS user's JWT. This module is the management surface for those keys: creating them, renaming them, enabling or disabling them, and deleting them.

All the routes sit on the public group combined with JwtLoginAuth and ModuleGate("api-key"). In other words the caller still needs a logged-in token, but is not required to have selected a LINE OA first.

Important caveat: this module was ported one-for-one along with several bugs from the TypeScript original, each marked in the code as not to be fixed. Anyone debugging here should know about them in advance.

Business Flow

  1. GET /api/api-key lists the current OA's keys, paginated in the {data, total} shape.
  2. POST /api/api-key creates a new key.
  3. GET /api/api-key/:id returns a key's details.
  4. PUT /api/api-key/:id edits a key, and PUT /api/api-key/:id/status enables or disables it.
  5. DELETE /api/api-key/:id deletes a key.
  6. The external system attaches the key value to requests it sends to client-api, which validates it against the api_key table.

Behaviours and bugs preserved from the original code

These are all documented in service.go.

  • A POST without a key field in the body produces an INSERT missing the NOT NULL key column, raising a Postgres error and returning 500 with APP_000. Creation only succeeds when the client supplies key itself.
  • findAll searches on the columns api_key.name and api_key.keywords, which do not exist in the table (they were copied from the auto-response module). So a non-empty ?search= or ?orderBy=name returns 500.
  • Omitting ?lineOaId turns the SQL into line_oa_id = NULL, which returns {"data":[],"total":0}.
  • Passing multiple ?status= values compares an array literal against an enum column and returns 500.
  • update() checks for a duplicate name but never uses the result (kept for parity), and a body containing a property that is not a real column returns 500.
  • updateStatus() does not invalidate the find-all cache.

Key Files & Functions

The code lives in internal/modules/apikey/, comprising controller.go, service.go, and dto.go.

MethodRouteHandlerGuard
GET/api/api-keyct.findAllJwtLogin + ModuleGate
GET/api/api-key/:idct.findByIdJwtLogin + ModuleGate
POST/api/api-keyct.createJwtLogin + ModuleGate
PUT/api/api-key/:idct.updateJwtLogin + ModuleGate
PUT/api/api-key/:id/statusct.updateStatusJwtLogin + ModuleGate
DELETE/api/api-key/:idct.deleteByIdJwtLogin + ModuleGate

The middleware order is jwtLogin, then gate, then the handler. The guard has to run first so that selfId and organizationId are present in CLS for the gate to resolve against; without them the gate fails open.

Connections to Other Services

  • Permission — requires JwtLoginAuth plus ModuleGate(d, "api-key"). Customers whose organization has the api-key module disabled get a 403, while onemoby users bypass the gate. PolicyModuleApiKey exists in the enum but is not used to annotate these routes.
  • Tablesapi_key.
  • Redis — caches find-all results (with the known bug that updateStatus does not invalidate it).
  • Key consumer — line-management-client-api-go, a separate service.
  • Companion module without routes — the API Client Registry.