mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
Adjusted internal documentation links and updated references within various Markdown files to reflect a new directory structure for audit and meta documents. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 4dee684a-a4b9-4c32-a818-3a27dae1b027 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 971b8c75-c322-4ede-98ea-eb5ae31d4e5c Replit-Helium-Checkpoint-Created: true
508 lines
18 KiB
Markdown
508 lines
18 KiB
Markdown
# Authorization and Security Requirements Specification
|
|
|
|
**Document Version:** 1.0
|
|
**Last Updated:** January 2026
|
|
**Status:** Draft
|
|
**Related Documents:** [Security Audit](../audits/security-audit.md), [API Layer Audit](../audits/api-layer-audit.md)
|
|
|
|
---
|
|
|
|
## 1. Purpose
|
|
|
|
This document formally specifies the authentication and authorization requirements for Nightscout's API system. It serves as a contract for:
|
|
|
|
1. **Client developers** - Understanding how to authenticate with Nightscout
|
|
2. **Maintainers** - Preserving security behavior during refactoring
|
|
3. **Testers** - Validating security behavior against formal requirements
|
|
4. **Security auditors** - Understanding the expected security posture
|
|
|
|
---
|
|
|
|
## 2. Terminology
|
|
|
|
| Term | Definition |
|
|
|------|------------|
|
|
| **API_SECRET** | A shared secret (minimum 12 characters) used for admin-level authentication |
|
|
| **API_SECRET Hash** | The SHA-1 or SHA-512 digest of the API_SECRET, transmitted instead of the raw secret |
|
|
| **Access Token** | A subject-specific token derived from the subject name and a digest of the subject ID |
|
|
| **JWT** | JSON Web Token signed with a dedicated JWT signing key for time-limited authentication |
|
|
| **JWT Signing Key** | A separate key (loaded from `randomString` file or set via `setJWTKey`) used to sign/verify JWTs |
|
|
| **Subject** | An entity (user, device, application) that can authenticate |
|
|
| **Role** | A named collection of permissions that can be assigned to subjects |
|
|
| **Permission** | An Apache Shiro-style string defining allowed actions (e.g., `api:entries:read`) |
|
|
| **Shiro Trie** | A data structure for efficient wildcard permission matching |
|
|
|
|
---
|
|
|
|
## 3. Authentication Requirements
|
|
|
|
### 3.1 API_SECRET Authentication
|
|
|
|
#### REQ-AUTH-001: API_SECRET Minimum Length
|
|
|
|
The API_SECRET environment variable MUST be at least 12 characters long.
|
|
|
|
| Input | Expected Behavior | Requirement ID |
|
|
|-------|-------------------|----------------|
|
|
| `API_SECRET` ≥ 12 chars | Server starts, secret is valid | REQ-AUTH-001a |
|
|
| `API_SECRET` < 12 chars | Server logs error, secret is null | REQ-AUTH-001b |
|
|
| `API_SECRET` not set | Server runs with limited functionality | REQ-AUTH-001c |
|
|
|
|
**Implementation Reference:** `lib/server/env.js`
|
|
|
|
```javascript
|
|
if (readENV('API_SECRET').length < consts.MIN_PASSPHRASE_LENGTH) {
|
|
env.err.push({desc: 'API_SECRET should be at least ' + consts.MIN_PASSPHRASE_LENGTH + ' characters'});
|
|
}
|
|
```
|
|
|
|
#### REQ-AUTH-002: API_SECRET Transmission
|
|
|
|
The API_SECRET MUST be transmitted as a hash (SHA-1 or SHA-512), never in plaintext.
|
|
|
|
| Method | Header/Parameter | Format | Requirement ID |
|
|
|--------|------------------|--------|----------------|
|
|
| Header | `api-secret` | SHA-1 (40 chars) or SHA-512 (128 chars) hex string | REQ-AUTH-002a |
|
|
| Query | `?secret=` | SHA-1 (40 chars) or SHA-512 (128 chars) hex string | REQ-AUTH-002b |
|
|
| Body | `body.secret` | SHA-1 (40 chars) or SHA-512 (128 chars) hex string | REQ-AUTH-002c |
|
|
|
|
**Example (SHA-1):**
|
|
```
|
|
API_SECRET: "this is my long pass phrase"
|
|
SHA-1 Hash: "b723e97aa97846eb92d5264f084b2823f57c4aa1"
|
|
```
|
|
|
|
**Note:** Either SHA-1 or SHA-512 hashes are accepted per REQ-AUTH-003.
|
|
|
|
#### REQ-AUTH-003: API_SECRET Hash Algorithms
|
|
|
|
The system MUST accept both SHA-1 and SHA-512 hashes of the API_SECRET.
|
|
|
|
| Algorithm | Hash Length | Requirement ID |
|
|
|-----------|-------------|----------------|
|
|
| SHA-1 | 40 hex chars | REQ-AUTH-003a |
|
|
| SHA-512 | 128 hex chars | REQ-AUTH-003b |
|
|
|
|
**Rationale:** SHA-512 support provides a migration path to stronger hashing.
|
|
|
|
#### REQ-AUTH-004: API_SECRET Authorization Level
|
|
|
|
A valid API_SECRET grants full admin permissions (`*`).
|
|
|
|
**Shiro Permission:** `*` (all permissions)
|
|
|
|
---
|
|
|
|
### 3.2 Access Token Authentication
|
|
|
|
#### REQ-AUTH-010: Access Token Format
|
|
|
|
Access tokens MUST be derived from the subject name and a digest computed by the enclave.
|
|
|
|
**Format:** `{abbreviation}-{digest_prefix}`
|
|
|
|
| Component | Description | Requirement ID |
|
|
|-----------|-------------|----------------|
|
|
| Abbreviation | First 10 alphanumeric chars of subject name, lowercase | REQ-AUTH-010a |
|
|
| Digest Prefix | First 16 chars of digest from `enclave.getSubjectHash(subject._id)` | REQ-AUTH-010b |
|
|
|
|
**Implementation Detail:** The digest is computed as SHA-1 of `apiKeySHA1 + subject._id`, where `apiKeySHA1` is the SHA-1 hash of the API_SECRET. This double-hashing provides an additional layer of indirection.
|
|
|
|
**Reference:** `lib/server/enclave.js:getSubjectHash()`
|
|
|
|
**Example:**
|
|
```
|
|
Subject Name: "Loop App"
|
|
Access Token: "loopapp-a1b2c3d4e5f6g7h8"
|
|
```
|
|
|
|
#### REQ-AUTH-011: Access Token Locations
|
|
|
|
Access tokens MAY be provided in the following locations:
|
|
|
|
| Location | Priority | Requirement ID |
|
|
|----------|----------|----------------|
|
|
| `api-secret` header | 1 (checked if not API_SECRET hash) | REQ-AUTH-011a |
|
|
| `?token=` query parameter | 2 | REQ-AUTH-011b |
|
|
| `body.token` | 3 | REQ-AUTH-011c |
|
|
|
|
#### REQ-AUTH-012: Access Token Resolution
|
|
|
|
When a valid access token is provided, the system MUST:
|
|
|
|
1. Locate the corresponding subject
|
|
2. Retrieve the subject's roles
|
|
3. Merge subject roles with default roles
|
|
4. Return combined permissions
|
|
|
|
---
|
|
|
|
### 3.3 JWT Authentication
|
|
|
|
#### REQ-AUTH-020: JWT Generation
|
|
|
|
The system MUST generate JWTs when a valid access token is provided to the authorization endpoint.
|
|
|
|
| Endpoint | Method | Input | Output | Requirement ID |
|
|
|----------|--------|-------|--------|----------------|
|
|
| `/api/v2/authorization/request/{token}` | GET | Access token | JWT | REQ-AUTH-020a |
|
|
|
|
**JWT Payload:**
|
|
```json
|
|
{
|
|
"accessToken": "subject-access-token",
|
|
"iat": 1705000000,
|
|
"exp": 1705003600
|
|
}
|
|
```
|
|
|
|
#### REQ-AUTH-021: JWT Signature
|
|
|
|
JWTs MUST be signed using HMAC-SHA256 with a dedicated JWT signing key.
|
|
|
|
**Implementation Detail:** The signing key is stored in `secrets[jwtKey]` and is loaded from a `randomString` file in the cache directory, or can be set via `env.enclave.setJWTKey()`. This is separate from the API_SECRET.
|
|
|
|
**Reference:** `lib/server/enclave.js:signJWT()`, `lib/server/enclave.js:readKey()`
|
|
|
|
#### REQ-AUTH-022: JWT Expiration
|
|
|
|
JWTs MUST have an expiration time. Default: 8 hours.
|
|
|
|
**Implementation Detail:** The default lifetime is `'8h'` as defined in `enclave.signJWT()`. This can be overridden by passing a custom lifetime parameter.
|
|
|
|
**Reference:** `lib/server/enclave.js:58`
|
|
|
|
#### REQ-AUTH-023: JWT Validation
|
|
|
|
When a JWT is provided, the system MUST:
|
|
|
|
1. Verify the signature using the JWT signing key (same key used in REQ-AUTH-021)
|
|
2. Check expiration time
|
|
3. Extract the access token from payload
|
|
4. Resolve permissions via access token
|
|
|
|
**Reference:** `lib/server/enclave.js:verifyJWT()`
|
|
|
|
#### REQ-AUTH-024: JWT Transmission
|
|
|
|
JWTs MUST be transmitted via the `Authorization` header.
|
|
|
|
**Format:** `Authorization: Bearer {jwt}`
|
|
|
|
| Input | Expected Behavior | Requirement ID |
|
|
|-------|-------------------|----------------|
|
|
| Valid JWT | Extract access token, resolve permissions | REQ-AUTH-024a |
|
|
| Expired JWT | Return 401 Unauthorized | REQ-AUTH-024b |
|
|
| Invalid signature | Return 401 Unauthorized | REQ-AUTH-024c |
|
|
| Malformed JWT | Return 401 Unauthorized | REQ-AUTH-024d |
|
|
|
|
---
|
|
|
|
## 4. Authorization Requirements
|
|
|
|
### 4.1 Role-Based Access Control
|
|
|
|
#### REQ-AUTHZ-001: Default Roles
|
|
|
|
The system MUST provide the following built-in roles:
|
|
|
|
| Role Name | Permissions | Description | Requirement ID |
|
|
|-----------|-------------|-------------|----------------|
|
|
| `admin` | `*` | Full access | REQ-AUTHZ-001a |
|
|
| `denied` | (none) | No permissions | REQ-AUTHZ-001b |
|
|
| `status-only` | `api:status:read` | Read status only | REQ-AUTHZ-001c |
|
|
| `readable` | `*:*:read` | Read all data | REQ-AUTHZ-001d |
|
|
| `careportal` | `api:treatments:create` | Create treatments | REQ-AUTHZ-001e |
|
|
| `devicestatus-upload` | `api:devicestatus:create` | Upload device status | REQ-AUTHZ-001f |
|
|
| `activity` | `api:activity:create` | Create activity records | REQ-AUTHZ-001g |
|
|
|
|
#### REQ-AUTHZ-002: Custom Roles
|
|
|
|
Administrators MUST be able to create custom roles with arbitrary permission sets.
|
|
|
|
**Storage:** MongoDB collection `auth_roles`
|
|
|
|
#### REQ-AUTHZ-003: Default Permissions
|
|
|
|
Unauthenticated requests MUST receive permissions based on `AUTH_DEFAULT_ROLES` environment variable.
|
|
|
|
| Setting | Effect | Requirement ID |
|
|
|---------|--------|----------------|
|
|
| `readable` | Unauthenticated can read all data | REQ-AUTHZ-003a |
|
|
| `denied` | Unauthenticated have no permissions | REQ-AUTHZ-003b |
|
|
| Comma-separated roles | Merge permissions from listed roles | REQ-AUTHZ-003c |
|
|
|
|
### 4.2 Shiro Permission Model
|
|
|
|
#### REQ-AUTHZ-010: Permission Format
|
|
|
|
Permissions MUST follow the Apache Shiro format: `domain:action:instance`
|
|
|
|
**Examples:**
|
|
```
|
|
api:entries:read - Read entries via API
|
|
api:treatments:create - Create treatments
|
|
api:*:* - All API operations
|
|
* - Full admin access
|
|
```
|
|
|
|
#### REQ-AUTHZ-011: Wildcard Matching
|
|
|
|
The permission system MUST support wildcard matching at any level.
|
|
|
|
| Pattern | Matches | Requirement ID |
|
|
|---------|---------|----------------|
|
|
| `*` | All permissions | REQ-AUTHZ-011a |
|
|
| `api:*:*` | All API operations | REQ-AUTHZ-011b |
|
|
| `api:entries:*` | All entry operations | REQ-AUTHZ-011c |
|
|
| `*:*:read` | All read operations | REQ-AUTHZ-011d |
|
|
|
|
#### REQ-AUTHZ-012: Permission Checking
|
|
|
|
Permission checks MUST use the Shiro Trie data structure for efficient wildcard matching.
|
|
|
|
---
|
|
|
|
## 5. Brute-Force Protection Requirements
|
|
|
|
### 5.1 IP-Based Delay List
|
|
|
|
#### REQ-BRUTE-001: Failed Authentication Tracking
|
|
|
|
The system MUST track failed authentication attempts by IP address.
|
|
|
|
**Implementation Reference:** `lib/authorization/delaylist.js`
|
|
|
|
#### REQ-BRUTE-002: Progressive Delay
|
|
|
|
After a failed authentication attempt, subsequent requests from the same IP MUST be delayed.
|
|
|
|
| Parameter | Default Value | Configurable | Requirement ID |
|
|
|-----------|---------------|--------------|----------------|
|
|
| Delay per failure | 5000ms | Yes (`settings.authFailDelay`) | REQ-BRUTE-002a |
|
|
| Delay accumulation | Cumulative | No | REQ-BRUTE-002b |
|
|
| Max delay | No limit | No | REQ-BRUTE-002c |
|
|
|
|
**Behavior:**
|
|
```
|
|
1st failure: 5 second delay
|
|
2nd failure: 10 second delay (cumulative)
|
|
3rd failure: 15 second delay (cumulative)
|
|
...
|
|
```
|
|
|
|
#### REQ-BRUTE-003: Delay Expiration
|
|
|
|
Failed request entries SHOULD be cleaned up after a period of inactivity.
|
|
|
|
| Parameter | Value | Requirement ID |
|
|
|-----------|-------|----------------|
|
|
| Expiration age | 60 seconds after last delay (`FAIL_AGE`) | REQ-BRUTE-003a |
|
|
| Cleanup mechanism | One-shot setTimeout after 30 seconds | REQ-BRUTE-003b |
|
|
|
|
**Implementation Note:** The current implementation uses a single `setTimeout(30000)` call at module initialization to clean up entries older than `FAIL_AGE` (60 seconds). This is a one-shot cleanup, not a recurring interval. Entries created after the cleanup runs may persist until server restart. This is a known limitation.
|
|
|
|
**Reference:** `lib/authorization/delaylist.js:45-53`
|
|
|
|
#### REQ-BRUTE-004: Successful Authentication Clears Delay
|
|
|
|
A successful authentication MUST immediately clear the delay for that IP.
|
|
|
|
#### REQ-BRUTE-005: Failed Authentication Notification
|
|
|
|
Failed authentication attempts MUST trigger an admin notification.
|
|
|
|
**Notification Content:**
|
|
- Title: "Failed authentication"
|
|
- Message: IP address and warning about potential misconfiguration
|
|
|
|
---
|
|
|
|
## 6. Subject Management Requirements
|
|
|
|
### 6.1 Subject CRUD Operations
|
|
|
|
#### REQ-SUBJ-001: Subject Creation
|
|
|
|
Subjects MUST be creatable via the admin API.
|
|
|
|
**Required Fields:**
|
|
| Field | Type | Description | Requirement ID |
|
|
|-------|------|-------------|----------------|
|
|
| `name` | String | Display name for the subject | REQ-SUBJ-001a |
|
|
| `roles` | Array | List of role names assigned | REQ-SUBJ-001b |
|
|
|
|
**Auto-generated Fields:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `_id` | ObjectID | Unique identifier |
|
|
| `created_at` | ISO 8601 | Creation timestamp |
|
|
| `accessToken` | String | Generated access token |
|
|
| `digest` | String | Token digest for matching |
|
|
|
|
#### REQ-SUBJ-002: Subject Modification
|
|
|
|
Subjects MUST be modifiable via the admin API.
|
|
|
|
#### REQ-SUBJ-003: Subject Deletion
|
|
|
|
Subjects MUST be deletable via the admin API.
|
|
|
|
**Behavior:** Once a subject is deleted, its access token becomes unusable because the subject lookup will fail. There is no explicit token revocation mechanism; invalidation occurs because the subject record no longer exists in the database.
|
|
|
|
**Note:** Existing JWTs containing the deleted subject's access token will fail on the next permission resolution when the subject cannot be found.
|
|
|
|
### 6.2 Role Management
|
|
|
|
#### REQ-ROLE-001: Role Creation
|
|
|
|
Custom roles MUST be creatable via the admin API.
|
|
|
|
**Required Fields:**
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `name` | String | Unique role name |
|
|
| `permissions` | Array | List of Shiro-format permissions |
|
|
|
|
#### REQ-ROLE-002: Built-in Role Behavior
|
|
|
|
Built-in roles (admin, denied, readable, etc.) are defined in code and merged with database roles at runtime.
|
|
|
|
**Note:** The current implementation does not explicitly protect built-in roles from deletion attempts via the admin API. Deleting a built-in role from the database has no effect since the role is re-added from `storage.defaultRoles` on reload. This behavior is implementation-specific and may change.
|
|
|
|
---
|
|
|
|
## 7. Socket.IO Authentication Requirements
|
|
|
|
### 7.1 Storage Namespace
|
|
|
|
#### REQ-SOCK-001: Subscription Authentication
|
|
|
|
The `/storage` WebSocket namespace MUST require authentication for data subscriptions.
|
|
|
|
**Message Format:**
|
|
```javascript
|
|
socket.emit('subscribe', {
|
|
accessToken: 'subject-access-token',
|
|
collections: ['entries', 'treatments']
|
|
});
|
|
```
|
|
|
|
#### REQ-SOCK-002: Per-Collection Authorization
|
|
|
|
Access to collections via WebSocket MUST respect the subject's permissions.
|
|
|
|
| Permission | Required For |
|
|
|------------|--------------|
|
|
| `api:entries:read` | Subscribe to entries |
|
|
| `api:treatments:read` | Subscribe to treatments |
|
|
| `api:treatments:create` | dbAdd to treatments |
|
|
|
|
### 7.2 Alarm Namespace
|
|
|
|
#### REQ-SOCK-010: Alarm Subscription
|
|
|
|
The `/alarm` namespace MUST require a valid access token for subscription.
|
|
|
|
---
|
|
|
|
## 8. Error Handling Requirements
|
|
|
|
### 8.1 Authentication Errors
|
|
|
|
#### REQ-ERR-001: Unauthorized Response
|
|
|
|
Invalid authentication MUST return HTTP 401 Unauthorized.
|
|
|
|
**Response Format:**
|
|
```json
|
|
{
|
|
"status": 401,
|
|
"message": "Unauthorized"
|
|
}
|
|
```
|
|
|
|
#### REQ-ERR-002: Forbidden Response
|
|
|
|
Valid authentication with insufficient permissions MUST return HTTP 403 Forbidden.
|
|
|
|
---
|
|
|
|
## 9. Traceability Matrix
|
|
|
|
| Requirement | Test File | Test Case | Status |
|
|
|-------------|-----------|-----------|--------|
|
|
| REQ-AUTH-001a | `security.test.js` | "should work fine set" | Covered |
|
|
| REQ-AUTH-001b | `security.test.js` | "should not work short" | Covered |
|
|
| REQ-AUTH-002a | `security.test.js` | "should work fine set" | Covered |
|
|
| REQ-AUTH-003a | `verifyauth.test.js` | SHA-1 verification | Covered |
|
|
| REQ-AUTH-003b | `verifyauth.test.js` | SHA-512 verification | Covered |
|
|
| REQ-AUTH-011a | `api.security.test.js` | "Data load should succeed with token in place of a secret" | Covered |
|
|
| REQ-AUTH-011b | `api.security.test.js` | "Data load should succeed with GET token" | Covered |
|
|
| REQ-AUTH-020a | `api.security.test.js` | "Should return a JWT on token" | Covered |
|
|
| REQ-AUTH-024a | `api.security.test.js` | "Data load should succeed with a bearer token" | Covered |
|
|
| REQ-AUTH-024c | `api.security.test.js` | "Data load fail succeed with a false bearer token" | Covered |
|
|
| REQ-AUTHZ-003b | `api.security.test.js` | "Data load should fail unauthenticated" | Covered |
|
|
| REQ-BRUTE-002 | `verifyauth.test.js` | "should fail unauthorized and delay subsequent attempts" | Covered |
|
|
| REQ-BRUTE-004 | Implicit in `verifyauth.test.js` | Successful auth clears delay | Implicit |
|
|
| REQ-SOCK-001 | N/A | WebSocket subscription auth | Not Covered |
|
|
| REQ-SOCK-002 | N/A | Per-collection authorization | Not Covered |
|
|
| REQ-SUBJ-001 | N/A | Subject creation | Not Covered |
|
|
| REQ-ROLE-001 | N/A | Role creation | Not Covered |
|
|
|
|
---
|
|
|
|
## 10. Coverage Gaps and Recommendations
|
|
|
|
### 10.1 Identified Gaps
|
|
|
|
| Gap | Priority | Recommendation |
|
|
|-----|----------|----------------|
|
|
| WebSocket authentication testing | High | Add tests for `/storage` and `/alarm` subscription auth |
|
|
| Subject/Role CRUD testing | Medium | Add API tests for admin tools endpoints |
|
|
| JWT expiration testing | Medium | Add test for expired JWT rejection |
|
|
| Permission wildcard testing | Low | Add comprehensive Shiro pattern tests |
|
|
|
|
### 10.2 Future Enhancements
|
|
|
|
| Enhancement | Description | Priority |
|
|
|-------------|-------------|----------|
|
|
| Token expiration | Add expiration to access tokens | Medium |
|
|
| Refresh tokens | Add JWT refresh mechanism | Low |
|
|
| Audit logging | Log all auth events for compliance | Medium |
|
|
| **OIDC Actor Identity** | External identity provider integration with verified actor tracking | **High** |
|
|
|
|
#### OIDC Actor Identity Proposal
|
|
|
|
A comprehensive RFC has been created for integrating OpenID Connect and OAuth 2.0 identity management into Nightscout Core. This enables:
|
|
|
|
- **Verified actor tracking** - Replace freeform `enteredBy` with cryptographically-verified identities
|
|
- **Care coordination** - Know exactly who performed each action (Mom, Dad, school nurse)
|
|
- **Delegation support** - Track when actions are performed on behalf of others
|
|
- **Audit trails** - HIPAA-grade compliance for clinical settings
|
|
- **Automation safety** - Distinguish human decisions from automated actions (Loop, OpenAPS)
|
|
|
|
See [OIDC Actor Identity Proposal](../proposals/oidc-actor-identity-proposal.md) for:
|
|
- Full architecture and protocol flows
|
|
- JWT claims specification
|
|
- Actor lookup collection schema
|
|
- Migration path for `enteredBy`
|
|
- Test plan and implementation readiness
|
|
|
|
---
|
|
|
|
## 11. Version History
|
|
|
|
| Version | Date | Author | Changes |
|
|
|---------|------|--------|---------|
|
|
| 1.0 | January 2026 | Nightscout Team | Initial specification |
|
|
|
|
---
|
|
|
|
## 12. References
|
|
|
|
- [Security Audit](../audits/security-audit.md) - Security analysis and recommendations
|
|
- [API Layer Audit](../audits/api-layer-audit.md) - API endpoint inventory
|
|
- [Modernization Roadmap](../meta/modernization-roadmap.md) - OIDC/OAuth2 plans
|
|
- `lib/authorization/` - Implementation source code
|