The authentication system uses a mode-driven approach with Dart enums and extensions to maintain a single source of truth for auth-related values (routes, text, analytics event names). This document describes the data flow, DRY principles applied, and integration points.
Location: lib/pages/auth_page.dart:14
enum AuthMode { signUp, signIn }Two distinct authentication modes:
AuthMode.signUp— User registration flowAuthMode.signIn— User login flow
Location: lib/pages/auth_page.dart:16–37
Centralizes all AuthMode-dependent values to eliminate duplicated conditionals across the codebase.
extension AuthModeX on AuthMode {
String get routePath => this == AuthMode.signUp ? Routes.signup : Routes.login;
String get title => this == AuthMode.signUp ? 'Create Account' : 'Sign In';
String get buttonText => this == AuthMode.signUp ? 'Sign Up' : 'Sign In';
String get pageViewName => this == AuthMode.signUp ? 'auth_signup' : 'auth_signin';
String get pageSubtitle => this == AuthMode.signUp
? 'Get your API key to access the Integrity API'
: 'Access your account';
String get toggleModePrompt => this == AuthMode.signUp
? "Already have an account? Sign in"
: "Don't have an account? Sign up";
}Benefits:
- Single definition: each value is defined once per mode
- Type-safe: Dart compiler ensures all cases are handled
- Maintainability: update text/routes in one place, changes propagate everywhere
- Reduces cognitive load: intent is clear (
_mode.titlevs_mode == AuthMode.signUp ? 'Create Account' : 'Sign In')
Location: lib/config/content/constants.dart:95–110
abstract final class Routes {
static const String home = '/';
static const String login = '/login';
static const String signup = '/signup';
static const String provision = '/provision';
// ... other routes
}Key changes (as of latest refactor):
/login— formerly/signin; primary sign-in route/signup— user registration route
Location: lib/services/provisioning_service.dart
Handles communication with Auth0 and the provisioning worker.
Auth flows:
signUp(email, password, name?)→ Creates Auth0 user, returns JWTsignIn(email, password)→ Returns JWT for existing user
┌─────────────────────────────────────────────────────────────────┐
│ User navigates to /login │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ GoRouter matches /login → creates AuthPage(mode: AuthMode.signIn)
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ _AuthPageState initializes │
│ • _mode = AuthMode.signIn │
│ • didChangeDependencies fires │
│ AnalyticsService.trackPageView(_mode.pageViewName) │
│ → 'auth_signin' │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Render AuthPage UI using AuthModeX getters │
│ • Page title: _mode.title │
│ → 'Sign In' │
│ • Submit button: _mode.buttonText │
│ → 'Sign In' │
│ • Toggle link: _mode.toggleModePrompt │
│ → "Don't have an account? Sign up" │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ User enters email + password, submits form │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ _submit() → ProvisioningService.signIn(email, password) │
│ • POST to sender-worker /signin │
│ • sender-worker exchanges (email, password) for JWT │
│ via Auth0 Resource Owner Password Credentials grant │
└────────────────────┬────────────────────────────────────────────┘
│
┌───────┴───────┐
│ │
Success Error
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ AuthSuccess │ │ AuthError │
│ jwt: ... │ │ message: .. │
│ email: ... │ └──────────────┘
└──────┬───────┘ │
│ ▼
│ Show error alert
│ (Alert widget)
│
▼
context.go('/provision', extra: AuthSuccess)
↓
ProvisionPage displays dashboard/provisioning UI
(requires valid JWT to proceed)
┌─────────────────────────────────────────────────────────────────┐
│ User navigates to /signup?tier=growth │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ GoRouter matches /signup → creates SignupPage(tier: 'growth') │
│ (SignupPage may redirect to AuthPage with AuthMode.signUp) │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ _AuthPageState initializes │
│ • _mode = AuthMode.signUp │
│ • didChangeDependencies fires │
│ AnalyticsService.trackPageView(_mode.pageViewName) │
│ → 'auth_signup' │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Render AuthPage UI using AuthModeX getters │
│ • Page title: _mode.title │
│ → 'Create Account' │
│ • Submit button: _mode.buttonText │
│ → 'Sign Up' │
│ • Toggle link: _mode.toggleModePrompt │
│ → "Already have an account? Sign in" │
│ • Extra field: password confirmation (signUp-only) │
│ Validation: _confirmPassword == _password │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ User enters email + password + confirm password, submits form │
└────────────────────┬────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ _submit() → ProvisioningService.signUp(email, password, name?) │
│ │
│ Sender-worker executes (in order): │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 1. M2M Token Exchange (client_credentials grant) │ │
│ │ POST /oauth/token │ │
│ │ Using: AUTHO_CLI_ID + AUTHO_CLI_SECRET │ │
│ │ Returns: mgmtToken (for Management API) │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 2. Create Auth0 User (Management API) │ │
│ │ POST /api/v2/users │ │
│ │ Auth: Bearer mgmtToken │ │
│ │ Payload: { email, password, connection, email_verified}
│ │ Returns: { user_id: "auth0|..." } │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 3. ROPC User Sign-In (password grant) │ │
│ │ POST /oauth/token │ │
│ │ Grant: password (Resource Owner Password Credentials) │ │
│ │ Using: AUTH0_CLIENT_ID + AUTH0_CLIENT_SECRET │ │
│ │ Payload: { username, password, audience, scope } │ │
│ │ Returns: { access_token (JWT), token_type: "Bearer" } │ │
│ └───────────────────────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 4. Create Supabase User + Personal Org │ │
│ │ POST /rest/v1/organizations │ │
│ │ POST /rest/v1/users │ │
│ │ POST /rest/v1/organization_memberships │ │
│ │ (using SUPABASE_SERVICE_ROLE_KEY) │ │
│ │ Links auth0_id ↔ supabase user_id │ │
│ └───────────────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────────┘
│
┌───────┴────────────────┐
│ │
Success Error
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ AuthSuccess │ │ AuthError │
│ jwt: JWT │ │ message: "user │
│ email: user@... │ │ already │
│ auth0Sub: .. │ │ exists" | ... │
└────────┬─────────┘ └────────┬─────────┘
│ │
│ ▼
│ Show error alert
│ (Alert widget)
│
│ Auto-redirect?
│ if (error.contains('already exists'))
│ context.go('/login')
│
▼
context.go('/provision', extra: AuthSuccess)
↓
ProvisionPage displays provisioning UI with:
• API key generation form (tier: growth)
• Organization setup
(requires valid JWT + auth0Sub)
// lib/routing/app_router.dart (line 169)
GoRoute(
path: '/login',
builder: (context, state) => AuthPage(
mode: AuthMode.signIn,
onBack: _goHome(context),
),
),
GoRoute(
path: '/signup',
builder: (context, state) => SignupPage(
tier: state.uri.queryParameters['tier'] ?? 'starter',
onBack: _goHome(context),
),
),// test/pages/auth_page_test.dart (line 60)
GoRouter makeAuthRouter(AuthMode mode) => GoRouter(
initialLocation: mode.routePath, // Resolves to Routes.signup or Routes.login
routes: [
GoRoute(path: Routes.signup, builder: (_, __) => const AuthPage(mode: AuthMode.signUp)),
GoRoute(path: Routes.login, builder: (_, __) => const AuthPage(mode: AuthMode.signIn)),
GoRoute(path: Routes.provision, builder: (_, __) => const Scaffold(...)),
],
);// lib/pages/auth_page.dart (line 78)
if (!_pageViewTracked) {
_pageViewTracked = true;
AnalyticsService.trackPageView(_mode.pageViewName);
// Resolves to 'auth_signup' or 'auth_signin'
}To maintain a single source of truth across auth flows (AuthPage and SignupPage), the following shared components are reused:
Location: lib/services/contact_service.dart
static bool isValidEmail(String email) {
return RegExp(r'^[^\s@]+@[^\s@]+\.[^\s@]+$').hasMatch(email);
}Usage:
lib/pages/auth_page.dart— form validation (both signUp and signIn)lib/pages/signup_page.dart— form validation (line 352)
Benefit: Email validation is consistent across all auth forms; single point of update.
Location: lib/utils/security_utils.dart
abstract final class PasswordPolicy {
static const int minLength = 8;
static const int maxLength = 128;
}Usage:
lib/pages/auth_page.dart— validates password length (both signUp and signIn)lib/pages/signup_page.dart— validates password length (line 359)- Signup form error message:
'Password must be at least ${PasswordPolicy.minLength} characters'
Benefit: Password constraints centralized; enforced consistently across all auth flows.
Location: lib/config/content/constants.dart (lines 97–105)
abstract final class Routes {
static const String home = '/';
static const String login = '/login';
static const String signup = '/signup';
static const String provision = '/provision';
static const String checkout = '/checkout';
// ... other routes
}Usage:
lib/routing/app_router.dart— route definitionstest/pages/auth_page_test.dart— test router initialization (line 60)lib/pages/signup_page.dart— post-signup routing (line 404–409)
Benefit: Routes hardcoded in one place; refactoring route paths updates all references automatically.
Location: lib/pages/signup_page.dart (lines 403–410)
/// Route to appropriate page based on tier after successful signup.
void _routeAfterSignup(AuthSuccess result) {
final tierLower = widget.tier.toLowerCase();
if (tierLower == 'growth' || tierLower == 'enterprise') {
context.go(Routes.checkout, extra: CheckoutArgs(email: result.email, tier: widget.tier));
} else {
context.go(Routes.provision, extra: result);
}
}Logic:
growthorenterprisetier → Redirect to Stripe checkoutstartertier → Redirect to provisioning page (API key generation)
Benefit: Tier routing logic isolated in single method; easy to add new tier handling.
Sender Worker (workers/sender-worker/src):
- Requires
AUTHO_CLI_ID,AUTHO_CLI_SECRET,AUTHO_CLI_AUDIENCE - Uses client_credentials grant to obtain management tokens
- Called during
/signupto create users in Auth0
Sender Worker (workers/sender-worker/src):
- Uses
AUTH0_CLIENT_ID,AUTH0_CLIENT_SECRET,AUTH0_AUDIENCE - Exchanges (email, password) for JWT via password grant
- Called during
/signinand immediately after/signup
| Variable | Purpose | Grant Type |
|---|---|---|
AUTH0_DOMAIN |
Auth0 tenant | Both |
AUTH0_CLIENT_ID |
App client ID | ROPC (password) |
AUTH0_CLIENT_SECRET |
App client secret | ROPC (password) |
AUTH0_AUDIENCE |
API audience | Both |
AUTHO_CLI_ID |
CLI app client ID | M2M (client_credentials) |
AUTHO_CLI_SECRET |
CLI app secret | M2M (client_credentials) |
AUTHO_CLI_AUDIENCE |
CLI API audience | M2M (client_credentials) |
If signup returns "user already exists" error, the app automatically redirects to /login:
// lib/pages/request_failure_page.dart (line 33)
if (error?.contains('already exists') ?? false) {
context.go('/login');
}// lib/pages/auth_page.dart
if (result is AuthError) {
setState(() {
_errorMessage = result.message;
_isLoading = false;
});
Alert.show(context, message: _errorMessage!);
}// lib/pages/auth_page.dart (line 121)
void _toggleMode() {
setState(() {
_mode = _mode == AuthMode.signUp ? AuthMode.signIn : AuthMode.signUp;
_password = ''; // Clear password
_confirmPassword = ''; // Clear confirm password
// Email preserved
});
}The toggle link swaps modes while preserving the email field (user often wants to switch between signup/signin with same email).
DRY patterns applied:
-
AuthMode extension — Centralizes mode-dependent text and routes
- Single definition: each value defined once per mode
- Type-safe: Dart compiler ensures all cases handled
- Used in: AuthPage (form titles, buttons) and tests
-
Shared validation — Email and password rules defined once
ContactService.isValidEmail()— reused in AuthPage and SignupPagePasswordPolicy.minLength/maxLength— reused in AuthPage and SignupPage- Ensures consistent validation across all auth flows
-
Routes constants — All auth routes defined in one place
Routes.login,Routes.signup,Routes.provision,Routes.checkout- Eliminates hardcoded path strings
- Single point of update for route refactoring
-
Signup routing logic — Tier-based redirect encapsulated
_routeAfterSignup()method centralizes post-signup routing- Handles: starter →
/provision, growth/enterprise →/checkout - Easy to extend for new tiers
Key files:
lib/pages/auth_page.dart— AuthMode enum + extension, AuthPage widget, form validationlib/pages/signup_page.dart— SignupPage widget, tier-based routing logiclib/config/content/constants.dart— Routes constantslib/utils/security_utils.dart— PasswordPolicylib/services/contact_service.dart— Email validationlib/services/provisioning_service.dart— Auth0 integrationlib/routing/app_router.dart— Route definitionsworkers/sender-worker/src— M2M + ROPC implementations