docs: document plugin vs backend authentication boundaries

Clarify that the native Daily Notification Plugin does not
authenticate users or call notification-wakeup-service directly.
Authentication belongs in the host app; the plugin only schedules
local notifications and orchestrates host-provided content fetch.

Add docs/security-boundaries.md with responsibility matrix, auth
flow diagram, SPI boundary notes, and a warning against adding backend
auth logic to the plugin layer.
This commit is contained in:
Jose Olarte III
2026-05-20 22:35:13 +08:00
parent 750343ddb9
commit 4027bb0c37

228
docs/security-boundaries.md Normal file
View File

@@ -0,0 +1,228 @@
# Security Boundaries — Native Plugin vs Backend Auth
**Purpose:** Document intentional architectural boundaries so
contributors do not add backend authentication into the plugin layer.
**Owner:** Development Team
**Last Updated:** 2026-05-20
**Status:** active
---
## Summary
The **Daily Notification Plugin** is a **local notification
orchestration layer**. It schedules alarms, manages permissions, caches
content for offline delivery, and invokes **host-provided** fetch logic
when configured.
It is **not** an authentication client and **not** a backend service
client for TimeSafari identity or session management.
**Authentication belongs in the host app**, which talks to
**notification-wakeup-service**. The plugin must never own that flow.
---
## Responsibility Matrix
| Concern | Plugin | Host app | Backend
(notification-wakeup-service) |
| --- | --- | --- | --- |
| User login / session | No | Yes | Yes |
| JWT / token issuance | No | Yes (or via backend) | Yes |
| Token refresh | No | Yes | Yes |
| DID / identity selection | No | Yes | May validate |
| Schedule local notifications | Yes | Configures via API | No |
| OS permission prompts | Yes | May explain UX | No |
| Local SQLite / cache | Yes | No | No |
| Prefetch timing (WorkManager, BG tasks) | Yes | No | No |
| Fetch notification **content** | Orchestrates only | Implements fetcher | Serves API data |
| Business API contracts | No | Yes | Yes |
---
## Layer Responsibilities
### Plugin (`@timesafari/daily-notification-plugin`)
The plugin **does**:
- Schedule and cancel **local** notifications (AlarmManager,
UNUserNotificationCenter, etc.).
- Persist schedules, cached content, and delivery metadata locally.
- Run background **prefetch orchestration** (when to fetch, retries,
TTL-at-fire).
- Request and track **notification permission** status.
- Expose a **Service Provider Interface (SPI)** so the host app can
supply content in native code (`NativeNotificationContentFetcher`).
- Forward **already-obtained** credentials to a registered native
fetcher via `configureNativeFetcher()` (passthrough only; see below).
- Perform **generic HTTP GET** only when a schedule supplies an
explicit `contentFetch.url` (no login, no token refresh, no
TimeSafari-specific auth).
The plugin **does not**:
- Authenticate users.
- Communicate with **notification-wakeup-service** or other backend
auth endpoints **as an auth client**.
- Sign JWTs, manage refresh tokens, or implement OAuth/OIDC flows.
- Encode TimeSafari-specific identity or API contracts in plugin code.
### Backend (`notification-wakeup-service` and related APIs)
The backend **does**:
- Authenticate users and establish sessions (exact mechanism is owned
by the TimeSafari platform).
- Issue, validate, and refresh credentials used for API access.
- Expose wakeup / notification-related APIs consumed by the **app**,
not by the plugin directly.
The backend **does not**:
- Depend on the Capacitor plugin for login or session lifecycle.
- Assume the plugin will call auth endpoints on its own.
### Host app (TimeSafari / consuming Capacitor application)
The app **does**:
- Authenticate the user against **notification-wakeup-service**
(and any related identity services).
- Obtain and refresh tokens **before** background fetch runs.
- Register `NativeNotificationContentFetcher` in native code and
implement `fetchContent()` using app-owned HTTP clients and auth.
- Call plugin APIs to configure schedules, permissions, and optional
`configureNativeFetcher()` with **pre-generated** tokens from app code.
- Handle auth failures (401/403), logout, and identity changes without
expecting the plugin to recover sessions.
The app **does not**:
- Assume the plugin will log in or refresh credentials on its behalf.
---
## Authentication Flow (Authoritative)
Authentication **does not** go through the plugin.
```mermaid
sequenceDiagram
participant User
participant App as Host app
participant Auth as notification-wakeup-service
participant Plugin as Daily Notification Plugin
participant OS as OS notification APIs
User->>App: Sign in / unlock identity
App->>Auth: Authenticate (credentials, DID, etc.)
Auth-->>App: Session / tokens
App->>App: Build or obtain JWT for API fetch
App->>Plugin: configureNativeFetcher(tokens from app)
App->>Plugin: registerNativeFetcher(app implementation)
Plugin->>Plugin: Schedule prefetch / notify alarms
Plugin->>App: NativeNotificationContentFetcher.fetchContent()
Note over App: App uses tokens from Auth;<br/>not plugin auth logic
App-->>Plugin: NotificationContent list
Plugin->>OS: Show local notification
```
**Rule:** `App → notification-wakeup-service` for authentication.
The plugin sits **below** that boundary and only receives outputs the
app chooses to pass in (e.g. a JWT string for a registered fetcher).
---
## Native Content Fetcher (SPI Boundary)
Background workers **cannot** rely on JavaScript bridges. The plugin
calls **`NativeNotificationContentFetcher`**, which the **host app
implements and registers**.
- **Plugin:** When to fetch, timeouts, retries, cache write, notify
scheduling.
- **Host fetcher:** How to call APIs, which headers to send, how to
handle 401 and refresh **in app code**.
Example pattern (conceptual): the fetcher uses a token the app already
minted; the plugin never signs JWTs. See
`android/.../NativeNotificationContentFetcher.java` Javadoc.
`configureNativeFetcher()` stores or forwards `apiBaseUrl`, `activeDid`,
and `jwtToken` supplied by the app. It is **configuration passthrough**,
not an authentication implementation. Tokens are **not** persisted by
default (`persistToken` defaults to `false`).
---
## Explicit Non-Goals (Plugin)
The following are **intentionally out of scope** for this repository:
1. **User authentication** — no login UI, no credential storage for
platform identity, no session store tied to wakeup-service.
2. **Direct backend auth traffic** — the plugin must not implement
clients for `notification-wakeup-service` auth/login/refresh routes.
3. **Backend business logic** — plans, offers, projects, and community
rules live in app + backend, not in the plugin.
The plugins job is **local reliability**: prefetch → cache → schedule →
display.
---
## Warning — Do Not Add Backend Auth to the Plugin
```text
┌─────────────────────────────────────────────────────────────┐
│ DO NOT add backend authentication logic to the plugin │
│ layer (TypeScript bridge, Android Kotlin/Java, iOS Swift). │
│ │
│ Wrong: JWT signing, refresh flows, or HTTP clients to │
│ notification-wakeup-service auth endpoints inside │
│ DailyNotificationPlugin / FetchWorker / iOS plugin│
│ │
│ Right: Implement auth in the host app; register a native │
│ fetcher; pass short-lived tokens from app code. │
└─────────────────────────────────────────────────────────────┘
```
**Why this matters:**
- **Security:** Auth secrets and refresh logic stay in one place (the
app), subject to app review and platform keychains.
- **Coupling:** The plugin stays reusable across apps and test harnesses.
- **Background limits:** OS background tasks must stay short; auth
refresh belongs in app lifecycle, not plugin workers.
- **Audit clarity:** Security reviewers can treat wakeup-service + app
as the trust boundary; the plugin is untrusted for identity.
If integration docs elsewhere mention “plugin provides authentication,”
treat that as **orchestration of fetch timing**, not **identity
authentication**. This document is the security boundary reference.
---
## Related Documentation
- `doc/integration/REFACTOR_NOTES.md` — SPI refactor; host-owned fetch
- `doc/integration/REFACTOR_NOTES_QUICK_START.md` — “JWT generation in
host app only”
- `android/.../NativeNotificationContentFetcher.java` — SPI contract
- `ARCHITECTURE.md` — broader system design (encryption, local storage)
---
## Assumptions & Limits
- **notification-wakeup-service** is named as the TimeSafari auth/wakeup
entry point per platform architecture; endpoint details live in app
and backend repos, not here.
- Generic `contentFetch.url` HTTP GET in the plugin is a **legacy /
optional** path for URL-configured schedules; new integrations should
prefer the native fetcher SPI with app-owned auth.
- This document does not change runtime behavior; it records existing
intent for reviewers and implementers.