# User Credentials Module

Secure storage and management of user API keys, OAuth tokens, and other credentials.

## Overview

The User Credentials module provides encrypted storage for sensitive user credentials with:

- **Encryption**: OpenBao Transit (preferred) or AES-256-GCM fallback
- **Access Control**: Row-Level Security (RLS) with scope-based isolation
- **Audit Logging**: Automatic tracking of credential access and modifications
- **Multi-tenancy**: USER, WORKSPACE, and SYSTEM scope support

## Database Model

### UserCredential

| Field            | Type            | Description                                     |
| ---------------- | --------------- | ----------------------------------------------- |
| `id`             | UUID            | Primary key                                     |
| `userId`         | UUID            | Owner (FK to users)                             |
| `workspaceId`    | UUID?           | Optional workspace (FK to workspaces)           |
| `name`           | String          | Display name (e.g., "GitHub Personal Token")    |
| `provider`       | String          | Provider identifier (e.g., "github", "openai")  |
| `type`           | CredentialType  | Type of credential (API_KEY, OAUTH_TOKEN, etc.) |
| `scope`          | CredentialScope | Access scope (USER, WORKSPACE, SYSTEM)          |
| `encryptedValue` | Text            | Encrypted credential value                      |
| `maskedValue`    | String?         | Masked value for display (e.g., "\*\*\*\*abcd") |
| `description`    | Text?           | Optional description                            |
| `expiresAt`      | Timestamp?      | Optional expiration date                        |
| `lastUsedAt`     | Timestamp?      | Last access timestamp                           |
| `metadata`       | JSONB           | Provider-specific metadata                      |
| `isActive`       | Boolean         | Soft delete flag (default: true)                |
| `rotatedAt`      | Timestamp?      | Last rotation timestamp                         |
| `createdAt`      | Timestamp       | Creation timestamp                              |
| `updatedAt`      | Timestamp       | Last update timestamp                           |

### Enums

**CredentialType**

- `API_KEY` - API keys (GitHub, GitLab, etc.)
- `OAUTH_TOKEN` - OAuth tokens
- `ACCESS_TOKEN` - Access tokens
- `SECRET` - Generic secrets
- `PASSWORD` - Passwords
- `CUSTOM` - Custom credential types

**CredentialScope**

- `USER` - Personal credentials (visible only to owner)
- `WORKSPACE` - Workspace-scoped credentials (visible to workspace admins)
- `SYSTEM` - System-level credentials (admin access only)

## Security

### Row-Level Security (RLS)

All credential access is enforced via PostgreSQL RLS policies:

```sql
-- USER scope: Owner access only
scope = 'USER' AND user_id = current_user_id()

-- WORKSPACE scope: Workspace admin access
scope = 'WORKSPACE' AND is_workspace_admin(workspace_id, current_user_id())

-- SYSTEM scope: Admin/migration bypass only
current_user_id() IS NULL
```

### Encryption

Credentials are encrypted at rest using:

1. **OpenBao Transit** (preferred)
   - Format: `vault:v1:base64data`
   - Named key: `mosaic-credentials`
   - Handled by `VaultService`

2. **AES-256-GCM** (fallback)
   - Format: `iv:authTag:encrypted`
   - Handled by `CryptoService`
   - Uses `ENCRYPTION_KEY` environment variable

The encryption middleware automatically falls back to AES when OpenBao is unavailable.

### Activity Logging

All credential operations are logged:

- `CREDENTIAL_CREATED` - Credential created
- `CREDENTIAL_ACCESSED` - Credential value decrypted
- `CREDENTIAL_ROTATED` - Credential value rotated
- `CREDENTIAL_REVOKED` - Credential soft-deleted

## Usage

### Creating a Credential

```typescript
const credential = await prisma.userCredential.create({
  data: {
    userId: currentUserId,
    name: "GitHub Personal Token",
    provider: "github",
    type: CredentialType.API_KEY,
    scope: CredentialScope.USER,
    encryptedValue: await vaultService.encrypt(plainValue, TransitKey.CREDENTIALS),
    maskedValue: maskValue(plainValue), // "****abcd"
    metadata: { scopes: ["repo", "user"] },
  },
});
```

### Retrieving a Credential

```typescript
// List credentials (encrypted values NOT returned)
const credentials = await prisma.userCredential.findMany({
  where: { userId: currentUserId, scope: CredentialScope.USER },
  select: {
    id: true,
    name: true,
    provider: true,
    type: true,
    maskedValue: true,
    lastUsedAt: true,
  },
});

// Decrypt a specific credential (audit logged)
const credential = await prisma.userCredential.findUnique({
  where: { id: credentialId },
});

const plainValue = await vaultService.decrypt(credential.encryptedValue, TransitKey.CREDENTIALS);

// Update lastUsedAt
await prisma.userCredential.update({
  where: { id: credentialId },
  data: { lastUsedAt: new Date() },
});

// Log access
await activityLog.log({
  action: ActivityAction.CREDENTIAL_ACCESSED,
  entityType: EntityType.CREDENTIAL,
  entityId: credentialId,
});
```

### Rotating a Credential

```typescript
await prisma.userCredential.update({
  where: { id: credentialId },
  data: {
    encryptedValue: await vaultService.encrypt(newPlainValue, TransitKey.CREDENTIALS),
    maskedValue: maskValue(newPlainValue),
    rotatedAt: new Date(),
  },
});
```

### Soft Deleting (Revoking)

```typescript
await prisma.userCredential.update({
  where: { id: credentialId },
  data: { isActive: false },
});
```

## API Endpoints (Planned - Issue #356)

```
POST   /api/credentials              Create credential
GET    /api/credentials              List credentials (masked values only)
GET    /api/credentials/:id          Get single credential (masked)
GET    /api/credentials/:id/value    Decrypt and return value (audit logged)
PATCH  /api/credentials/:id          Update metadata
POST   /api/credentials/:id/rotate   Rotate credential value
DELETE /api/credentials/:id          Soft-delete (revoke)
```

## Testing

Run tests with:

```bash
pnpm test apps/api/src/credentials/user-credential.model.spec.ts
```

Tests cover:

- Model structure and constraints
- Enum validation
- Unique constraints
- Foreign key relations
- Cascade deletes
- Timestamps
- JSONB metadata

## Related Issues

- **#355** - Create UserCredential Prisma model with RLS policies (this issue)
- **#356** - Build credential CRUD API endpoints
- **#353** - VaultService NestJS module for OpenBao Transit
- **#350** - Add RLS policies to auth tables with FORCE enforcement

## Migration

Migration: `20260207_add_user_credentials`

Apply with:

```bash
cd apps/api
pnpm prisma migrate deploy
```

## References

- Design doc: `docs/design/credential-security.md`
- OpenBao setup: `docs/OPENBAO.md`
- RLS context: `apps/api/src/lib/db-context.ts`
- CryptoService: `apps/api/src/federation/crypto.service.ts`
