Show connected OAuth provider on the settings page

Settings previously had no signal of how an account signs in. Records which provider (GitHub/Google) a brokered OAuth sign-in used on the User record, and shows it under a new "Connected accounts" card; falls back to a "no OAuth connection" message for email/password accounts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Seto Elkahfi committed Jul 5, 2026 at 10:40 UTC a0cb76ee96230acfd6693d65dfc3acf350339c2a
7 files changed +63 -5
app/controllers/oauth/github_controller.rb
+4
index 5080f48..dc11ae3 100644 --- a/app/controllers/oauth/github_controller.rb +++ b/app/controllers/oauth/github_controller.rb @@ -9,6 +9,10 @@ module Oauth "GitHub" end + def provider_key + "github" + end + def authorize_url SmbcloudAuthService.github_authorize_url(redirect_uri: github_auth_callback_url) end
app/controllers/oauth/google_controller.rb
+4
index 8cf98ff..3987a69 100644 --- a/app/controllers/oauth/google_controller.rb +++ b/app/controllers/oauth/google_controller.rb @@ -9,6 +9,10 @@ module Oauth "Google" end + def provider_key + "google" + end + def authorize_url SmbcloudAuthService.google_authorize_url(redirect_uri: google_auth_callback_url) end
app/controllers/oauth/provider_controller.rb
+6 -1
index 5a8e6e6..d7a759c 100644 --- a/app/controllers/oauth/provider_controller.rb +++ b/app/controllers/oauth/provider_controller.rb @@ -10,6 +10,7 @@ module Oauth # # Subclasses supply the provider specifics: # provider_label — human name used in flash/log messages ("GitHub") + # provider_key — lowercase key stored on User#oauth_provider ("github") # authorize_url — the smbCloud authorize URL carrying our callback class ProviderController < ApplicationController before_action :redirect_if_signed_in @@ -32,7 +33,11 @@ module Oauth end profile = SmbcloudAuthService.me(access_token: access_token) - user = User.find_or_create_from_smbcloud(profile.merge(access_token: access_token)) + user = User.find_or_create_from_smbcloud( + profile, + access_token: access_token, + oauth_provider: provider_key + ) reset_session session[:user_id] = user.id
app/models/user.rb
+16 -3
index 9bfbaa1..14ac8ed 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -84,6 +84,14 @@ class User < ApplicationRecord "https://ui-avatars.com/api/?name=#{encoded_name}&background=0057B8&color=fff&size=128" end + OAUTH_PROVIDER_LABELS = { "github" => "GitHub", "google" => "Google" }.freeze + + # Human label for the OAuth provider this account last signed in with, or + # nil if it has only ever used email/password sign-in. + def oauth_provider_label + OAUTH_PROVIDER_LABELS[oauth_provider] + end + # --------------------------------------------------------------------------- # smbCloud Auth integration # --------------------------------------------------------------------------- @@ -92,11 +100,15 @@ class User < ApplicationRecord # 1. SmbcloudAuthService.me(access_token:) # → { id: Integer, email: String, created_at: String, updated_at: String } # 2. The access_token passed through from login. + # 3. `oauth_provider`, when this upsert followed a brokered "Continue with + # <provider>" sign-in (e.g. "github", "google") — nil for plain + # email/password sign-in. Recorded so Settings can show which OAuth + # connection, if any, the account last used. # # Accepts either string or symbol keys. # # Returns the persisted User. Raises ActiveRecord::RecordInvalid on failure. - def self.find_or_create_from_smbcloud(userinfo, access_token: nil) + def self.find_or_create_from_smbcloud(userinfo, access_token: nil, oauth_provider: nil) smbcloud_id = Integer(userinfo[:id] || userinfo["id"]) email = (userinfo[:email] || userinfo["email"]).to_s.strip.downcase @@ -118,8 +130,9 @@ class User < ApplicationRecord end # Always sync email and token in case they changed on the auth server. - user.email = email if email.present? - user.access_token = access_token if access_token.present? + user.email = email if email.present? + user.access_token = access_token if access_token.present? + user.oauth_provider = oauth_provider if oauth_provider.present? # Derive a username only for new records. user.username = generate_username_from_email(email) if user.new_record?
app/views/users/settings.html.erb
+21
index 92f3b25..e704110 100644 --- a/app/views/users/settings.html.erb +++ b/app/views/users/settings.html.erb @@ -32,6 +32,27 @@ </div> </div> + <div class="card mt-6"> + <div class="px-6 py-4 border-b border-surface-600"> + <h2 class="text-sm font-semibold text-gray-100">Connected accounts</h2> + </div> + <div class="px-6 py-6"> + <% if @user.oauth_provider_label %> + <div class="flex items-center justify-between"> + <div class="flex items-center gap-3"> + <span class="inline-flex h-2 w-2 rounded-full bg-green-500"></span> + <p class="text-sm text-gray-300"> + Signed in with <span class="font-medium text-gray-100"><%= @user.oauth_provider_label %></span> + </p> + </div> + <span class="text-xs text-gray-500">Connected</span> + </div> + <% else %> + <p class="text-sm text-gray-400">No OAuth connection. You're signing in with email and password.</p> + <% end %> + </div> + </div> + <div class="card mt-6"> <div class="px-6 py-4 border-b border-surface-600"> <h2 class="text-sm font-semibold text-gray-100">Billing</h2>
db/migrate/20250101000012_add_oauth_provider_to_users.rb
+10
new file mode 100644 index 0000000..17c16af --- /dev/null +++ b/db/migrate/20250101000012_add_oauth_provider_to_users.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# Records which OAuth provider (if any) a user last signed in with, so +# Settings can show "Connected via GitHub/Google" for accounts that used the +# brokered social sign-in flow. Left blank for email/password-only accounts. +class AddOauthProviderToUsers < ActiveRecord::Migration[8.1] + def change + add_column :users, :oauth_provider, :string + end +end
db/schema.rb
+2 -1
index 51e8b3f..f57e441 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_01_01_000011) do +ActiveRecord::Schema[8.1].define(version: 2025_01_01_000012) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -146,6 +146,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_01_01_000011) do t.datetime "created_at", null: false t.string "display_name" t.string "email", null: false + t.string "oauth_provider" t.integer "smbcloud_id", null: false t.datetime "updated_at", null: false t.string "username", null: false