⚠️ PLANNING DOCUMENT - This describes features that are NOT YET IMPLEMENTED.
Builds on: Auth & Roles System — Google SSO patterns, OmniAuth infrastructure, and Pundit policies established there extend to Champion Portal with Apple/Facebook providers.
Authentication for the Champion Portal is separate from the internal Lookup Portal. Champions (external alumni) authenticate through a different system than internal staff users.
| Decision | Answer | Rationale |
|---|---|---|
| Onboarding flow | Progressive Account Creation | Email + name first (feels like “getting started”), password after email verification |
| BUID requirement | Optional + Two-tier verification | Email Verified = basic access; Champion Verified = full access |
| Account timing | Single table + status | champions table with verification_status enum |
| Non-degree alumni | Supported | Can be Champion Verified without degree data |
| Status | How Achieved | Access Level |
|---|---|---|
| Unverified | Account created, email not confirmed | Cannot log in |
| Email Verified | Clicked email verification link, set password | Can log in, edit own profile only |
| Champion Verified | BUID linked by Engagement Team | Full portal access |
| Feature | Unverified | Email Verified | Champion Verified |
|---|---|---|---|
| Log in | ❌ | ✅ | ✅ |
| View/edit own profile | ❌ | ✅ | ✅ |
| Browse directory | ❌ | ❌ | ✅ |
| Appear in directory | ❌ | ❌ | ✅ |
| Post on boards | ❌ | ❌ | ✅ |
| Submit events | ❌ | ❌ | ✅ |
| Contact other Champions | ❌ | ❌ | ✅ |
| See own degree info | ❌ | ❌ | ✅ (if available) |
User Messaging: Email Verified users see clear messaging that verification typically happens within 1 business day.
┌─────────────────────────────────────────────────────────────┐
│ Step 1: Get Started │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ First Name: [________________] │ │
│ │ Last Name: [________________] │ │
│ │ Email: [________________] │ │
│ │ │ │
│ │ [Get Started] │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 2: Check Your Email │
│ │
│ We sent a verification link to [email]. │
│ Click the link to continue setting up your account. │
│ │
│ [Resend Email] │
└─────────────────────────────────────────────────────────────┘
│
▼ (click email link)
┌─────────────────────────────────────────────────────────────┐
│ Step 3: Set Your Password │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Password: [________________] │ │
│ │ Confirm Password: [________________] │ │
│ │ │ │
│ │ [Create Account] │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 4: Complete Your Profile │
│ (Status: Email Verified - awaiting Champion Verification) │
│ │
│ While you wait for verification (usually within 1 business │
│ day), you can complete your profile: │
│ │
│ • Contact information │
│ • Employment │
│ • Affinities │
│ • Privacy settings │
│ │
│ [Continue to Profile] │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 5: Champion Role Quiz (optional, can do later) │
│ │
│ Discover which Champion role fits you best! │
│ │
│ [Take the Quiz] or [Skip for Now] │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Welcome to Belmont Alumni Champions │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Continue with Google │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Sign in with Apple │ │
│ └─────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Continue with Facebook │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ── or ── │
│ │
│ [Create account with email] [Sign in with email] │
└─────────────────────────────────────────────────────────────┘
│
▼ (clicks SSO button)
┌─────────────────────────────────────────────────────────────┐
│ Google/Apple OAuth │
│ │
│ (User authorizes app, redirected back) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 2: Enter Your Location │
│ (Account created as Email Verified - email from SSO) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ZIP Code: [________] │ │
│ │ │ │
│ │ We'll use this to connect you with nearby Champions │ │
│ │ and regional events. │ │
│ │ │ │
│ │ [Continue] │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Step 3: Complete Your Profile │
│ (Status: Email Verified - awaiting Champion Verification) │
│ │
│ (Same as Step 4 in email flow) │
└─────────────────────────────────────────────────────────────┘
SSO Benefits:
SSO Notes:
Some Champions won’t have degree records:
Solution: Champions can be verified (BUID linked) without degree data. Their profile simply won’t display education information.
# Champion can be verified even without degree data
champion.champion_verified? # true
champion.has_degree_info? # false (no degrees in alumni record)
Supported Providers:
Future Consideration: LinkedIn could pull employer/job title, but deferred—2 fields don’t justify the API complexity.
Implementation:
Auth & Roles project installs the OmniAuth foundation for Google. Champion Portal adds Apple and Facebook:
# Gemfile (Apple and Facebook added to existing OmniAuth setup)
gem 'omniauth-apple'
gem 'omniauth-facebook'
# gem 'omniauth-google-oauth2' — already installed by Auth & Roles
# gem 'omniauth-rails_csrf_protection' — already installed by Auth & Roles
SSO Behavior:
| Scenario | Result |
|———-|——–|
| New user via SSO | Account created, email_verified, prompt for ZIP |
| Existing email via SSO | Link provider to existing account |
| Different email via SSO | Reject, show existing account message |
Database Fields (on cp_champions):
t.string :google_uid
t.string :apple_uid
t.string :facebook_uid
t.datetime :google_linked_at
t.datetime :apple_linked_at
t.datetime :facebook_linked_at
Sign-In UI (mobile-first order):
┌──────────────────────────────────────────┐
│ Continue with Google │ ← Most common
├──────────────────────────────────────────┤
│ Sign in with Apple │ ← Apple users
├──────────────────────────────────────────┤
│ Continue with Facebook │ ← Alumni demographic
├──────────────────────────────────────────┤
│ ── or ── │
├──────────────────────────────────────────┤
│ Email: [____________________________] │
│ Password: [_________________________] │ ← Traditional
│ [ Sign In ] or [ Create Account ] │
└──────────────────────────────────────────┘
| Role | Description | Assignment |
|---|---|---|
| Champion | Default role for all Champion Verified users | Automatic on verification |
| CLC (City Leadership Council) | Elevated regional permissions | Manual assignment by Engagement Team |
# app/models/cp_champion.rb
class CpChampion < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:confirmable, # for email verification
:omniauthable, # for SSO
omniauth_providers: [:google_oauth2, :apple, :facebook]
# Link to Alumni record (optional - supports non-degree alumni)
belongs_to :alumni, primary_key: :buid, foreign_key: :buid, optional: true
# Verification status
enum verification_status: {
unverified: 0, # Email not confirmed (can't log in)
email_verified: 1, # Email confirmed, limited access
champion_verified: 2 # BUID linked, full access
}
# Portal role
enum role: { champion: 0, city_leader: 1 }
# Region assignment
belongs_to :region, optional: true
# SSO helper methods
def google_connected?
google_uid.present?
end
def apple_connected?
apple_uid.present?
end
def facebook_connected?
facebook_uid.present?
end
def sso_only?
!encrypted_password.present? && (google_connected? || apple_connected? || facebook_connected?)
end
def has_degree_info?
alumni.present? && alumni.degrees.any?
end
def full_access?
champion_verified?
end
# OmniAuth callbacks
def self.from_omniauth(auth)
where(email: auth.info.email).first_or_initialize.tap do |champion|
champion.email = auth.info.email
champion.first_name ||= auth.info.first_name
champion.last_name ||= auth.info.last_name
case auth.provider
when 'google_oauth2'
champion.google_uid = auth.uid
champion.google_linked_at = Time.current
when 'apple'
champion.apple_uid = auth.uid
champion.apple_linked_at = Time.current
# Apple only sends name on first auth - save it!
champion.first_name ||= auth.info.name&.split&.first
champion.last_name ||= auth.info.name&.split&.last
when 'facebook'
champion.facebook_uid = auth.uid
champion.facebook_linked_at = Time.current
end
# SSO users skip email verification
champion.confirmation_token = nil
champion.confirmed_at ||= Time.current
champion.verification_status = :email_verified if champion.unverified?
champion.save!
end
end
end
# app/controllers/champions/base_controller.rb
class Champions::BaseController < PublicController
before_action :authenticate_champion!
private
def authenticate_champion!
redirect_to champions_login_path unless current_champion
end
def require_full_access!
unless current_champion&.champion_verified?
redirect_to champions_pending_path,
alert: 'Your account is pending verification. This usually takes 1 business day.'
end
end
def require_clc!
redirect_to champions_root_path unless current_champion&.city_leader?
end
end
# app/controllers/champions/registrations_controller.rb
class Champions::RegistrationsController < Devise::RegistrationsController
# Step 1: Collect name + email only (no password yet)
def create
build_resource(sign_up_params.except(:password, :password_confirmation))
resource.skip_password_validation = true
if resource.save
# Send confirmation email with special token
resource.send_confirmation_instructions
redirect_to champions_check_email_path
else
render :new
end
end
# Step 3: Set password after email confirmation
def set_password
@champion = Champion.find_by(confirmation_token: params[:token])
if @champion && request.post?
@champion.password = params[:password]
@champion.password_confirmation = params[:password_confirmation]
@champion.skip_password_validation = false
@champion.confirm # Mark email as confirmed
@champion.email_verified! # Update verification status
if @champion.save
sign_in(@champion)
redirect_to champions_profile_path
end
end
end
end
champion_signups table stores signup data