Skip to content

v0.22.0 — Production Security Suite + Test Infrastructure Overhaul

Latest

Choose a tag to compare

@cevheri cevheri released this 19 Jun 07:07
b255990

v0.22.0 — Production Security Suite + Test Infrastructure Overhaul

A month-long hardening sprint that turned the template into a security-conscious, enterprise-grade base: tokens moved off plaintext storage into the platform keystore, six opt-in production-security features added, the entire test infrastructure rebuilt around a deterministic global bootstrap with golden + e2e coverage, global mutable config replaced with immutable DI, and the toolchain upgraded to Flutter 3.44.2 / Dart 3.12.2.

By the numbers — 47 PRs merged · ~105 commits · 350 files changed (+17,816 / −2,397) · 1,626 tests passing (up from 1,417) · May 20 → Jun 19, 2026


Production Security Suite (#129#143)

The headline of this release. A full pass to make the template safe to ship to production.

Secure token storage + JWT sanitization (#129)

The largest single effort — JWT access/refresh tokens no longer live in plaintext SharedPreferences.

  • Tokens persisted via flutter_secure_storage (iOS Keychain, Android AES-GCM ciphers); keychain accessibility pinned to first_unlock_this_device (no iCloud sync). Non-secret session fields (username, roles) stay in SharedPreferences for synchronous access.
  • A single ISecureStorage instance is threaded through DI (AppDependenciesAppScope → interceptors) — no ad-hoc instantiation.
  • One-shot SessionMigration copies legacy plaintext keys into secure storage on first launch after upgrade, then cleans up lingering plaintext.
  • LogSanitizer.maskToken + masked toString() on AuthSession/JWTToken; OTP debug logs sanitized — no secret ever reaches a log sink.
  • Single-flight refresh: concurrent 401s coalesce into one /api/token/refresh call; refresh-loop guard via RequestOptions.extra marker; symmetric rollback on partial-write failure; all UI logout sites now wipe secure storage.
  • Hardened across ~40 commits and 21 rounds of independent/Copilot PR review.

Six opt-in production-security features

Feature Issue / PR What it does
Screen-capture protection #134 / #138 Blocks screenshots/recording on sensitive screens (opt-in)
Idempotency-Key header #136 / #139 Opt-in Idempotency-Key on POST/PUT/PATCH to defeat duplicate writes
Inactivity auto-logout #130 / #140 Configurable idle timeout (15 min prod default, off in dev); background-aware via wall-clock check
Certificate pinning #133 / #141 Configurable SHA-256 pins for production HTTPS; default off; web is a documented no-op
Sentry crash reporting #131 / #142 Opt-in production backend with conservative PII/token scrubber; no DSN committed
Role-based route guards #132 / #143 Admin/User route protection + returnUrl propagation through the auth flow

Test Infrastructure Overhaul (#135, #147#155, #168)

The test suite was rebuilt from scattered per-file setup into a coherent, deterministic harness.

  • Global bootstrap + TestEnv (#150)flutter_test_config.dart runs once per isolate (binding init, logger/Equatable config, mocktail fallbacks) and resets state per test. Tests need zero manual storage/router/secure-storage setup.
  • Deterministic time (#148)fakeAsync everywhere; no real-time await Future.delayed waits in tests.
  • Golden tests (#135, 4 phases)alchemist harness → component goldens → shared-widget goldens → screen goldens; relocated to a feature-first mirror.
  • E2E smoke test (#152) — full-app boot → login → dashboard flow.
  • dart_test.yaml + test tags (#154), test-double convention docs (#155) (mocktail mock / hand-fake / Dio handler stub), pumpAndSettle hygiene (#151).
  • Screen-test quality epic (#168)whenListen-based bloc stubbing, verify-by-event-type, dead-mock and brittle-text-assert cleanup.
  • Coverage gate (#149) excludes generated/bootstrap code; new docs/testing-architecture.md.
  • 1,417 → 1,626 tests.

Architecture & Configuration

  • Immutable AppConfig + DI-injected ApiClient (#144 / #146) — replaced the global mutable config map (a ~2019 pattern) with a typed, immutable AppConfig injected through DI. No global state.
  • dynamic_forms relocated features/shared/ (#121) — the forms engine has no standalone domain entity, so it belongs in shared/. Added FormBundleEntity + a {schema, values} bundle endpoint, native pathParams support, and a User Extended-Info page that renders a dynamic schema with prefilled values (/user/:id/extended-info).

Tooling & Developer Experience

  • Dev console — opt-in verbose request/response body logging with a Network-tab toggle; mock mode now feeds the DevConsole capture + verbose logging.
  • Fix — Environment tab no longer crashes with a GoRouterState error.

Toolchain & Dependencies (#173)

  • Flutter 3.44.0 → 3.44.2, Dart 3.12.0 → 3.12.2.
  • Bumps: go_router 17.2.3 → 17.3.0, flutter_secure_storage 10.2.0 → 10.3.1, sentry_flutter 9.20.0 → 9.22.0.
  • Documented pins (with inline rationale): test 1.31.0 (SDK pins test_api 0.7.11), intl_utils 2.8.15 (analyzer cap), screen_protector 1.5.1 (1.5.2 dropped its Kotlin jvmTarget pin → Android release-build JVM-target mismatch under JDK 21+).
  • Verified across dart analyze, 1,626 tests, web + APK + appbundle release builds, and a Chrome mock-login smoke run.

Documentation

  • README.md + CLAUDE.md synced to current architecture; secure-storage design + wiring docs; dynamic-forms engine docs; web/llms*.txt metadata reconciled with pubspec.yaml.

Full changelog: v0.21.0...v0.22.0