[FEAT]: IDIR Self-Serve Account API Key Management
- Dominant language
- No language data
- Stars
- 0
- Forks
- 4
- Avg merge
- 1m
- Merged PRs (30d)
- 2
Description
# Technical Specification: IDIR Self-Serve Account API Key Management
## 1. Overview & Context
In the ServiceBC Connect G2G Portal ecosystem, authenticated IDIR users manage digital services, register backend APIs, and access protected government microservices. To call these microservices securely, ministry teams require **API Keys** issued against their active organization/account (`GOVM`).
Currently:
1. API key generation is largely mediated by Ops or accessed through legacy developer portals (`setup-account`/auth-web).
2. Users need a streamlined, in-portal interface to provision, view, and revoke API keys directly for their active account.
3. Keys need clear operational traceability. When generated, keys must incorporate the account identifier in their name prefix (`{account_id}-{descriptive_name}`) so Ops and Apigee gateway logs can rapidly identify tenant ownership.
4. Key duplication must be prevented within an account.
5. Strict account isolation must be enforced: a user in Account A must never be able to view, generate, or revoke keys belonging to Account B.
This document specifies the technical architecture, security boundaries, API contracts, and implementation plan for self-serve IDIR account API Key Management.
---
## 2. Business Requirements & User Stories
### User Story 1: Listing Account API Keys
> **As an** authenticated IDIR user managing a ministry team account,
> **I want** to view all active API keys associated with my current account,
> **So that** I can audit credentials, check key statuses, and reference keys for service integration.
### User Story 2: Provisioning a New Descriptive API Key
> **As an** authenticated IDIR user,
> **I want** to create a new API key with a descriptive custom label and an enforced `{account_id}-` prefix,
> **So that** my credentials are readily identifiable in operational logs and unique within my account.
### User Story 3: One-Time Key Secret Reveal & Copy
> **As an** authenticated IDIR user,
> **I want** to immediately see and copy the newly generated raw API key in a modal upon creation,
> **So that** I can store it in my application's vault/secret manager before closing the dialog.
### User Story 4: Key Revocation & Risk Management
> **As an** authenticated IDIR user,
> **I want** to revoke an exposed or deprecated API key at any time,
> **So that** I immediately cut off compromised credentials and eliminate billing/security liability for my team.
---
## 3. Architecture & Security Model
```
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ CLIENT (Nuxt 4 SPA) │
│ │
│ • Left Nav Sidebar: "API Keys" -> /gov-user/api-keys │
│ • Intent Flow: "Add / View API Keys" -> /gov-user/api-keys │
│ • useConnectAccountStore(): Resolves active account ID dynamically │
│ • useConnectAuth(): IDIR Bearer JWT │
└───────────────────────────────────────────┬────────────────────────────────────────────┘
│
│ /api/accounts/{accountId}/api-keys
│ Headers: Bearer
▼
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ NUXT NITRO SERVER LAYER │
│ │
│ 1. validateIdirUser(event): Verify IDIR JWT, active signature & token expiration │
│ 2. Tenant Boundary Guard: Verify user belongs to {accountId} │
│ 3. Admin Service Account Token Exchange (sbc-auth-admin client credentials) │
│ 4. Enforce Name Prefix: Normalize keyName to `${accountId}-${sanitizedName}` │
│ 5. Uniqueness Guard: Pre-check GET /orgs/{accountId}/api-keys for name conflicts │
│ 6. Upstream Proxy: │
│ - GET /api/v1/orgs/{accountId}/api-keys │
│ - POST /api/v1/orgs/{accountId}/api-keys │
│ - DELETE /api/v1/orgs/{accountId}/api-keys/{apiKey} │
└───────────────────────────────────────────┬────────────────────────────────────────────┘
│
│ Headers: Bearer , x-apikey:
▼
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ UPSTREAM CONNECT AUTH API │
│ │
│ • /api/v1/orgs/{account_number}/api-keys │
│ • Gateway provisions API key in Apigee and registers consumer credential (future:Kong)│
└────────────────────────────────────────────────────────────────────────────────────────┘
```
### Security Guardrails
1. **Multi-Tenant Isolation**: The Nitro server validates that the authenticated IDIR user is an affiliated member/admin of `{accountId}` (or has administrative roles). Cross-account access attempts reject immediately with `403 Forbidden`.
2. **Zero Client Secret Exposure**: Upstream service account credentials (`AUTH_SA_CLIENT_ID`, `AUTH_SA_CLIENT_SECRET`, `API_GW_KEY`) reside exclusively in server-side environment secrets. The browser client never handles service account tokens.
3. **Prefix Enforcement**: Both client UI and server route strictly enforce that all keys are created with the prefix `${accountId}-`. Users cannot create unprefixed keys or spoof other accounts' prefixes.
4. **Uniqueness Enforcement**: The server route checks existing keys for the account before upstream submission; duplicate names reject with `409 Conflict`.
5. **No Hardcoded Values**: In strict accordance with Project Constraints Rule 3, no hardcoded account numbers (e.g., `3139`) exist in frontend or backend logic. All account context is resolved dynamically.
---
## 4. API Endpoints Specification
### 4.1. `GET /api/accounts/:accountId/api-keys`
Retrieves all API keys associated with the specified account.
#### Request Headers
| Header | Type | Description |
|---|---|---|
| `Authorization` | `string` | `Bearer ` (Required) |
#### Response (`200 OK`)
```json
{
"success": true,
"data": [
{
"apiKey": "ts...B",
"apiKeyName": "3156-core-api-sandbox",
"environment": "sandbox",
"keyStatus": "approved",
"keyExpiryDate": "never",
"email": "user@gov.bc.ca",
"apiAccess": ["ALL_API"]
}
]
}
```
---
### 4.2. `POST /api/accounts/:accountId/api-keys`
Creates a new API key for the account.
#### Request Headers
| Header | Type | Description |
|---|---|---|
| `Authorization` | `string` | `Bearer ` (Required) |
#### Request Body
```json
{
"keyName": "core-api-sandbox"
}
```
*Note*: If the user provides `"core-api-sandbox"`, the server automatically prepends `${accountId}-` resulting in `"3156-core-api-sandbox"`. If the user already typed the prefix `"3156-core-api-sandbox"`, the server avoids double-prefixing.
#### Upstream Request
```
POST https://{AUTH_API_URL}/api/v1/orgs/{accountId}/api-keys
Headers:
Authorization: Bearer {SA_TOKEN}
x-apikey: {API_GW_KEY}
Content-Type: application/json
Body:
{ "keyName": "3156-core-api-sandbox" }
```
#### Response (`201 Created`)
```json
{
"success": true,
"data": {
"apiKey": "ts...Q1hB",
"apiKeyName": "3156-core-api-sandbox",
"environment": "sandbox",
"keyStatus": "approved",
"keyExpiryDate": "never",
"email": "user@gov.bc.ca",
"apiAccess": ["ALL_API"]
}
}
```
---
### 4.3. `DELETE /api/accounts/:accountId/api-keys/:apiKey`
Revokes an active API key from the account and API Gateway.
#### Request Headers
| Header | Type | Description |
|---|---|---|
| `Authorization` | `string` | `Bearer ` (Required) |
#### Upstream Request
```
DELETE https://{AUTH_API_URL}/api/v1/orgs/{accountId}/api-keys/{apiKey}
Headers:
Authorization: Bearer {SA_TOKEN}
x-apikey: {API_GW_KEY}
Content-Type: application/json
Body: {}
```
#### Response (`200 OK`)
```json
{
"success": true,
"message": "API key revoked successfully."
}
```
---
## 5. Client Flow & UI Architecture
### 5.1. Navigation Entry Points
1. **Dashboard Left Sidebar (`hub/app/pages/gov-user/dashboard.vue`)**:
- Add a navigation item: **`API Keys`** (icon: `i-lucide-key-round`) linking to `/gov-user/api-keys`.
2. **Dedicated Page (`hub/app/pages/gov-user/api-keys.vue`)**:
- Adheres to Corporate Nuxt 4 Architecture Rules: utilizes `@sbc-connect/nuxt-pay` / `@sbc-connect/nuxt-base` layout (`connect-auth`), Nuxt UI primitives, and design system variables.
3. **Intent Page Integration (`hub/app/pages/intent.vue`)**:
- Secondary action button / card linking to `/gov-user/api-keys` for existing service account holders configuring integration credentials.
### 5.2. Page Components & UX
- **Header & Stats**:
- Displays current active account name (`orgName`) and account ID badge.
- Primary button: `+ Provision New API Key` (opens `ModalCreateApiKey`).
- **Keys Table (`UTable`)**:
- Columns:
- `Key Name` (bold, e.g. `3156-core-sandbox`)
- `API Key Secret` (masked by default: `tsSPL••••••••Q1hB`, with toggle/unmask and copy button)
- `Status` (badge: `approved` / `active`)
- `Environment` (badge: `sandbox` / `production`)
- `Expiry` (`never` or formatted date)
- `Actions` (Revoke button with confirmation dialog)
- **Modal: Create API Key (`ModalCreateApiKey.vue`)**:
- Form field for Key Name with fixed prefix addon:
`[ 3156- ] [ Enter descriptive name... ]`
- Validation with Zod: alphanumeric, dashes, underscores, max 50 chars, uniqueness check.
- Submits to `POST /api/accounts/:accountId/api-keys`.
- **Modal: One-Time Key Reveal**:
- Prominently displays the full raw key with one-click copy.
- Caution alert: *"Copy your key now. While available in your account, treat this credential like a password. Anyone with this key can incur charges on your account."*
- **Modal: Revoke Key Confirmation**:
- Danger dialog: *"Are you sure you want to revoke key `{keyName}`? Any applications using this key will immediately lose access to G2G APIs."*
---
## 6. Monorepo File System Boundaries
### Allowed Edits (Target Files)
* `hub/server/api/accounts/[accountId]/api-keys/index.get.ts` (List keys)
* `hub/server/api/accounts/[accountId]/api-keys/index.post.ts` (Create key)
* `hub/server/api/accounts/[accountId]/api-keys/[key].delete.ts` (Revoke key)
* `hub/app/pages/gov-user/api-keys.vue` (Main API Key management view)
* `hub/app/components/ModalCreateApiKey.vue` (Creation and one-time reveal dialog)
* `hub/app/pages/gov-user/dashboard.vue` (Sidebar link to API Keys)
* `hub/i18n/locales/en-CA.ts` & `hub/i18n/locales/fr-CA.ts` (Localization strings)
* `hub/e2e/api-keys.spec.ts` (Playwright E2E & isolation tests)
* `hub/e2e/contracts/auth-api.oas.yaml` (Prism mock contract additions)
### Strictly Prohibited (Do Not Modify)
* `hub/server/db/schema.ts` (Keys are stored upstream in Auth API / Kong, not in Hub PostgreSQL DB)
* `@sbc-connect/*` packages in `node_modules`
* Layouts / global authentication core in layer packages
---
## 7. Implementation Plan & Agent Tasks
| Phase | Description | Deliverables |
|---|---|---|
| **Phase 1: OpenAPI Mock Contract** | Add `/orgs/{account_id}/api-keys` endpoints to Stoplight Prism OpenAPI contract for mock testing. | `hub/e2e/contracts/auth-api.oas.yaml` |
| **Phase 2: Nitro Server Endpoints** | Implement GET, POST, and DELETE endpoints with identity validation, tenant isolation check, prefix enforcement, and upstream token proxying. | `hub/server/api/accounts/[accountId]/api-keys/*` |
| **Phase 3: UI Implementation** | Build `/gov-user/api-keys.vue`, `ModalCreateApiKey.vue`, and connect sidebar navigation. | `hub/app/pages/gov-user/api-keys.vue`, `hub/app/components/ModalCreateApiKey.vue` |
| **Phase 4: Verification & Isolation Tests** | Implement Playwright tests verifying prefixing, duplicate rejection, cross-account isolation, and key revocation. | `hub/e2e/api-keys.spec.ts` |
---
## 8. Definition of Done (Automated Verification)
Downstream coding agents must successfully run and verify:
1. `pnpm --filter hub exec vue-tsc --noEmit` -> 0 type errors
2. `pnpm --filter hub exec vue-tsc --noEmit -p .nuxt/tsconfig.server.json` -> 0 server type errors
3. `pnpm --filter hub exec eslint .` -> 0 lint errors
4. `npx playwright test e2e/api-keys.spec.ts` -> All tests pass (including multi-tenant isolation tests)
Contributor guide
Assessment
This issue has not been assessed yet.