Files
OSIT-AE-App-Svelte/documentation/AE__Permissions_and_Security.md
Scott Idem b8e6bcaf03 fix(idaa): strip API calls from all +page.ts/+layout.ts, gate loading in $effect
SvelteKit load functions fire during link prefetch before Novi auth completes;
`if (browser)` guards do not prevent this. Moving all IDAA data fetching into
$effect hooks gated on `novi_verified || trusted_access` closes the IDB
pre-population race across archives, bb/[post_id], and recovery_meetings/[event_id].

Also documents the Auth-Before-Cache rule and per-route status in
AE__Permissions_and_Security.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 18:49:47 -04:00

9.1 KiB
Raw Blame History

Aether — Permissions and Security

Last updated: 2026-02-27 Source of truth: src/lib/ae_utils/ae_utils__perm_checks.ts, src/lib/stores/ae_stores.ts


Access Level Hierarchy

Highest to lowest. Each level inherits all access from every level below it.

Level access_type string Typical Use
Super super OSIT internal — full system access
Manager manager Account managers
Administrator administrator Event/account admins
Trusted trusted Onsite staff — site passcode or AE login
Public public Site-wide passcode granted
Authenticated authenticated Identity verified (e.g. IDAA Novi UUID)
Anonymous anonymous Default — not signed in

Note on Public vs Authenticated: public is a site-wide unlock (anyone with the passcode). authenticated verifies a specific identity. In the hierarchy, public outranks authenticated because it implies broader site access.


$ae_loc Store — Permission Flags

$ae_loc is a persisted() store (backed by localStorage). Key fields:

$ae_loc.access_type          // string: current access type ('anonymous', 'trusted', etc.)

// Cumulative boolean flags (true = "you have AT LEAST this level")
$ae_loc.anonymous_access     // always true
$ae_loc.authenticated_access // true from authenticated and above
$ae_loc.public_access        // true from public and above
$ae_loc.trusted_access       // true from trusted and above ← most-used gate
$ae_loc.administrator_access // true from administrator and above
$ae_loc.manager_access       // true from manager and above
$ae_loc.super_access         // true only at super

// Exclusive check flags (true = "you are EXACTLY this level")
$ae_loc.trusted_check        // true only if access_type === 'trusted'
$ae_loc.administrator_check  // etc.
// (rarely needed — prefer the _access flags)

// Behavior flags
$ae_loc.edit_mode            // boolean — user preference, see below
$ae_loc.adv_mode             // boolean — advanced mode toggle

Additional intermediate levels (in permission checks, not in hierarchy order)

support, assistant, verified, provisional — appear in _access flags but are not part of the canonical access_level_order. Treat as internal/intermediate.


Edit Mode — Critical Rules

$ae_loc.edit_mode is a user preference, not a permission level.

Rules that must never be broken:

  1. Components must never write to $ae_loc.edit_mode — only the system menu toggle and sign-out/permission-drop handlers may change it.
  2. Edit mode is only available to trusted and above in 95% of modules (the toggle is hidden from lower-access users).
  3. Edit mode persists across navigation — it is NOT reset by page loads or component mounts.
  4. Sign-out and permission drops to below authenticated should reset edit_mode to false.

Background: A bug was fixed (2026-02-27) where ae_comp__badge_obj_view.svelte was writing $ae_loc.edit_mode = false in a data-loading $effect, silently overriding the user's preference on every navigation to the badge print page.


Authentication Methods

Method Grants Used For
Site passcode (site_access_code_kv) trusted, public, or authenticated Onsite staff and event attendees
AE Username + Password trusted and above Staff with AE accounts
Novi UUID authenticated IDAA members (Novi membership system)

Passcodes are stored per-level in $ae_loc.site_access_code_kv:

site_access_code_kv: {
    administrator: null,  // highest passcode tier
    trusted: null,        // onsite staff passcode
    public: 'public1980', // example
    authenticated: 'auth1980'
}

Utility Functions

process_permission_checks(access_type: string)

Returns a full permission object (_check and _access flags) for a given access type string. Used when access type changes to update $ae_loc.

import { process_permission_checks } from '$lib/ae_utils/ae_utils__perm_checks';
const checks = process_permission_checks('trusted');
// checks.trusted_access === true
// checks.administrator_access === false

compare_access_levels(level_a, level_b)

Returns 1 if level_a is higher, -1 if lower, 0 if equal. Useful for threshold comparisons.


Privacy and Security Rules

IDAA — International Doctors in Alcoholics Anonymous

  • ALL IDAA content is private. Always. No exceptions.
  • BB (Bulletin Board / Posts), Archives, Recovery Meetings — all require authentication.
  • IDAA users authenticate via Novi UUID at authenticated level or higher.
  • A prior agent accidentally exposed IDAA BB data publicly — treat any IDAA exposure as Sev-1.

IDAA IndexedDB (IDB) Caching — Auth-Before-Cache Rule

Root cause discovered 2026-04: SvelteKit +page.ts/+layout.ts load functions run before layout $effect hooks and fire during link prefetch (hover). if (browser) guards do NOT prevent this — they only prevent SSR. This means API calls inside these files execute before Novi auth completes, writing private IDAA data to the user's IndexedDB even for unauthenticated sessions.

The fix — established pattern for all IDAA routes:

  1. Load/layout .ts files = thin shells. Pass URL params only. No API calls. No if (browser) data fetching.
  2. Data loading = $effect in .svelte files, gated on:
    if (!$idaa_loc.novi_verified && !$ae_loc.trusted_access) return;
    
  3. Three IDB purge paths in (idaa)/+layout.svelte (auth failure, anonymous no-UUID, Reset & Retry button) clear db_posts, db_archives, and db_events tables.

Auth path matrix:

User type novi_verified trusted_access Can load data? Purge fires?
Anonymous / unauthenticated false false No Yes (Case 1)
Novi-verified IDAA member true false Yes No
Manager / trusted access false true Yes No (Case 3 exemption)

Applied to routes (as of 2026-04-19):

  • idaa/bb/+page.svelte$effect gate added; bb/+page.ts stripped
  • idaa/bb/[post_id]/+page.ts — stripped; loading handled by trigger in bb/+layout.svelte
  • idaa/archives/+page.svelte$effect gate added; archives/+layout.ts stripped
  • idaa/archives/[archive_id]/+page.svelte$effect gate added; [archive_id]/+page.ts stripped
  • idaa/recovery_meetings/+page.svelte$effect gate already present; +layout.ts stripped
  • idaa/recovery_meetings/[event_id]/+page.svelte$effect gate added; +page.ts stripped

When adding a new IDAA route: never put API calls in +page.ts/+layout.ts. Always gate data fetching with the $effect pattern above.

Journals

  • Private personal data. Always authenticated. Passcode/encryption features exist.
  • Never expose journal content publicly.

PUBLIC_AE_API_SECRET_KEY

  • Audit closed 2026-03-11. PUBLIC_* prefix is by design — key is always in the client bundle.
  • Anonymous site-domain lookup uses the limited-permission PUBLIC_AE_BOOTSTRAP_KEY instead.
  • Security model: API key is one layer; JWT + x-account-id scoping provides the primary auth.
  • Do not introduce new usages. Prefer PUBLIC_AE_BOOTSTRAP_KEY for unauthenticated lookups.

Email Display

Non-trusted users must never see a full email address. Obscure using:

// joh***@example.com
function obscure_email(email: string): string {
    const at = email.indexOf('@');
    if (at < 0) return email;
    return `${email.slice(0, Math.min(3, at))}***${email.slice(at)}`;
}

This pattern lives in ae_comp__badge_obj_li.svelte — move to ae_utils if needed elsewhere.


Module-Specific Permission Patterns

Events — Badges

Scenario Visibility Print Action Review Actions
Anonymous / below trusted Unprinted only None (name display only) Email Review Link button (→ email API)
Trusted, not Edit Mode Unprinted only Clickable (first print) Email Review Link button
Trusted, Edit Mode All non-hidden Clickable incl. reprint; shows Nx count Email Review Link + direct Review Link (clipboard)
  • Print count badge: shown as Nx (e.g. 2×) next to the printer icon when print_count >= 1
  • Edit mode for badges: limited to trusted_access users (toggle hidden from lower levels)
  • person_passcode field (for attendee-gated review URL): not yet in DB as of 2026-02-27

IDAA

  • Auth gate test must be the first test in any test file — privacy enforcement is a hard requirement.
  • Default required permission: trusted_access or higher for module access.

Common Template Patterns

<!-- Gate on trusted access -->
{#if $ae_loc.trusted_access}

<!-- Gate on edit mode (always check trusted too — edit mode alone is insufficient) -->
{#if $ae_loc.trusted_access && $ae_loc.edit_mode}

<!-- Gate on administrator -->
{#if $ae_loc.administrator_access}

<!-- Show full vs obscured email -->
{$ae_loc.trusted_access ? email : obscure_email(email)}

Never gate purely on $ae_loc.edit_mode without also checking a permission level. Edit mode is a UI preference, not a permission grant.