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.
8.8 KiB
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
NativeNotificationContentFetcherin native code and implementfetchContent()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.
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:
- User authentication — no login UI, no credential storage for platform identity, no session store tied to wakeup-service.
- Direct backend auth traffic — the plugin must not implement
clients for
notification-wakeup-serviceauth/login/refresh routes. - Backend business logic — plans, offers, projects, and community rules live in app + backend, not in the plugin.
The plugin’s job is local reliability: prefetch → cache → schedule → display.
Warning — Do Not Add Backend Auth to the Plugin
┌─────────────────────────────────────────────────────────────┐
│ 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 fetchdoc/integration/REFACTOR_NOTES_QUICK_START.md— “JWT generation in host app only”android/.../NativeNotificationContentFetcher.java— SPI contractARCHITECTURE.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.urlHTTP 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.