Files
daily-notification-plugin/docs/security-boundaries.md
Jose Olarte III 4027bb0c37 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.
2026-05-20 22:35:13 +08:00

8.8 KiB
Raw Blame History

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.

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.

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

┌─────────────────────────────────────────────────────────────┐
│  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.


  • 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.