Skip to main content
AdRecon uses Supabase Auth for identity management with two authentication methods: Magic Link email sign-in and Google OAuth. Access is controlled through invite-only provisioning via Fanbasis purchases.

Sign-In Methods

The primary authentication method for AdRecon is passwordless Magic Link authentication.
1

Enter your email address

Navigate to the login page and enter the email address associated with your AdRecon account.
2

Submit the form

Click Send Magic Link to request a sign-in link. The system will only send a link if an account exists for that email address.
3

Check your inbox

If your email is registered, you’ll receive a message: “Check your inbox — if there’s an account for this email, a sign-in link is on its way.”
4

Click the magic link

Open the email and click the sign-in link. You’ll be redirected to /app and automatically authenticated.
Magic Links are configured with shouldCreateUser: false, meaning you must already have an account. New users cannot self-register via email.
Implementation details (Auth.tsx:73-79):
const { error: signInError } = await supabase.auth.signInWithOtp({
  email,
  options: {
    emailRedirectTo: redirectTo,
    shouldCreateUser: false,
  },
});

Google OAuth Sign-In

Google OAuth is supported as an alternative authentication provider.
1

Configure OAuth redirect

The OAuth flow redirects to your app origin at /app after successful authentication.
2

New user validation

When a new Google OAuth sign-in occurs, the system checks if the user was created within the last 60 seconds and verifies they have legitimate provenance.
3

Provenance check

Users pass the check if they have app_metadata.user_type set OR user_metadata.source === 'fanbasis'. Without these markers, the session is terminated.
Unauthorized new Google OAuth sign-ups are blocked. The system will sign you out with the message: “No active subscription found. Please purchase access to sign in.”
New user validation logic (App.tsx:218-229):
const ageMs = Date.now() - new Date(user.created_at).getTime();
const isBrandNew = ageMs < 60_000;
const hasLegitProvenance =
  Boolean(user.app_metadata?.user_type) ||
  user.user_metadata?.source === 'fanbasis';

if (isBrandNew && !hasLegitProvenance) {
  await supabase.auth.signOut();
  toast.error('No active subscription found. Please purchase access to sign in.');
  return;
}

Fanbasis Purchase-Based Provisioning

Access to AdRecon is granted exclusively through Fanbasis purchases. The webhook provisioning system creates and manages user accounts automatically.

Purchase Flow

1

Purchase on Fanbasis

When a customer completes a purchase for an enabled AdRecon offer on Fanbasis, a webhook event is fired.
2

Webhook validation

The webhook handler validates the event signature and secret, then extracts the buyer email and offer ID.
3

User creation or update

The system creates a new Supabase auth user (if needed) or updates an existing user’s metadata with source: 'fanbasis' and user_type: 'member'.
4

Magic link email sent

A passwordless sign-in link is automatically emailed to the customer with shouldCreateUser: false for both new and returning users.
Webhook provisioning sets user_metadata.source = 'fanbasis', which grants the user legitimate provenance for authentication.

Access Revocation

If a Fanbasis payment is refunded, disputed, or charged back, the webhook handler revokes access:
  • Sets user_metadata.fanbasis_access_revoked = true
  • Applies a long-duration auth ban
  • Signs the user out on their next session check
Revocation enforcement (App.tsx:150-158):
if (user.user_metadata?.fanbasis_access_revoked === true) {
  await supabase.auth.signOut();
  if (active) {
    setSession(null);
    setIsAdmin(false);
  }
  toast.error('Access revoked due to refund or dispute. Please contact support if this is a mistake.');
  return;
}

Access Control (Admin vs Member)

AdRecon uses role-based access control to gate administrative features.

User Roles

RoleMarkerPermissions
Adminapp_metadata.user_type = 'admin'Full access to dashboard, profile, and admin panel
Memberapp_metadata.user_type = 'member' or not setAccess to dashboard and profile only

Route Protection

Admin routes (/admin, /app/admin, /admin/fanbasis, /app/admin/fanbasis) check the isAdmin state before rendering. Admin route guard (App.tsx:306-316):
route === 'admin' ? (
  isAdmin ? (
    <AdminDashboard currentUserId={session.user.id} />
  ) : (
    <CenteredMessage
      title="Admin access required"
      message="Your account is signed in, but does not have admin permissions for this route."
      actionLabel="Back to app"
      onAction={() => window.location.assign('/app')}
    />
  )
)
Non-admin users attempting to access admin routes see an access-required message instead of the admin controls.

Admin API Protection

Serverless admin endpoints (/api/admin/users, /api/admin/fanbasis) validate the bearer token and check app_metadata.user_type:
  • 200 responses for admin users
  • 403 responses for authenticated non-admin users
  • 401 responses for unauthenticated requests

Session Management

AdRecon bootstraps and maintains authentication state using Supabase session hooks.

Session Bootstrap

1

Initial session fetch

On app load, the client calls supabase.auth.getSession() to retrieve the current session (if any).
2

User metadata fetch

If a session exists, the app calls supabase.auth.getUser() to fetch fresh app_metadata and user_metadata.
3

Admin state sync

The isAdmin state is set based on user.app_metadata.user_type === 'admin'.
4

Access revocation check

If user_metadata.fanbasis_access_revoked === true, the session is immediately terminated.
Session initialization (App.tsx:118-166):
const {
  data: { session: currentSession },
  error,
} = await supabase.auth.getSession();

if (error) {
  throw error;
}

setSession(currentSession);

if (!currentSession) {
  setIsAdmin(false);
} else {
  const {
    data: { user },
    error: userError,
  } = await supabase.auth.getUser();

  if (user.user_metadata?.fanbasis_access_revoked === true) {
    await supabase.auth.signOut();
    // ...
  }
  setIsAdmin(user.app_metadata?.user_type === 'admin');
}

Auth State Listener

The app subscribes to onAuthStateChange to react to session events (sign-in, sign-out, token refresh). Key behaviors:
  • SIGNED_IN events trigger new user validation (60-second provenance check)
  • Access revocation is re-checked on every state change
  • Admin status is re-synced from fresh app_metadata
The auth state listener runs for the entire app lifecycle and cleans up the subscription on unmount.

Sign-Out

Users can sign out from the profile page or admin dashboard header. Sign-out implementation (ProfilePage.tsx:321-332):
const handleSignOut = async () => {
  setSigningOut(true);

  try {
    await getSupabaseClient().auth.signOut();
    window.location.assign('/app');
  } catch (err) {
    toast.error(toUserFacingError(err, 'Sign out failed.'));
  } finally {
    setSigningOut(false);
  }
};
After signing out, users are redirected to /app, which renders the login screen.

Security Notes

  • Invite-only access: Magic Links and OAuth are disabled for new users without Fanbasis provenance.
  • Session validation: Every session includes fresh app_metadata and user_metadata checks.
  • Revocation enforcement: Refunded/disputed purchases result in immediate session termination.
  • Admin route protection: Client-side route guards and server-side API guards prevent unauthorized access.

Build docs developers (and LLMs) love