Move sessions into their own view for easier management
This commit is contained in:
35
app/controllers/active_sessions_controller.rb
Normal file
35
app/controllers/active_sessions_controller.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
class ActiveSessionsController < ApplicationController
|
||||
def show
|
||||
@user = Current.session.user
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
@connected_applications = @user.oidc_user_consents.includes(:application).order(granted_at: :desc)
|
||||
end
|
||||
|
||||
def revoke_consent
|
||||
@user = Current.session.user
|
||||
application = Application.find(params[:application_id])
|
||||
|
||||
# Check if user has consent for this application
|
||||
consent = @user.oidc_user_consents.find_by(application: application)
|
||||
unless consent
|
||||
redirect_to active_sessions_path, alert: "No consent found for this application."
|
||||
return
|
||||
end
|
||||
|
||||
# Revoke the consent
|
||||
consent.destroy
|
||||
redirect_to active_sessions_path, notice: "Successfully revoked access to #{application.name}."
|
||||
end
|
||||
|
||||
def revoke_all_consents
|
||||
@user = Current.session.user
|
||||
count = @user.oidc_user_consents.count
|
||||
|
||||
if count > 0
|
||||
@user.oidc_user_consents.destroy_all
|
||||
redirect_to active_sessions_path, notice: "Successfully revoked access to #{count} applications."
|
||||
else
|
||||
redirect_to active_sessions_path, alert: "No applications to revoke."
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -76,7 +76,7 @@ module Admin
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email_address, :password, :admin, :status, custom_claims: {})
|
||||
params.require(:user).permit(:email_address, :name, :password, :admin, :status, custom_claims: {})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -291,7 +291,7 @@ class OidcController < ApplicationController
|
||||
email: user.email_address,
|
||||
email_verified: true,
|
||||
preferred_username: user.email_address,
|
||||
name: user.email_address
|
||||
name: user.name.presence || user.email_address
|
||||
}
|
||||
|
||||
# Add groups if user has any
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
class ProfilesController < ApplicationController
|
||||
def show
|
||||
@user = Current.session.user
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
@connected_applications = @user.oidc_user_consents.includes(:application).order(granted_at: :desc)
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -12,7 +10,6 @@ class ProfilesController < ApplicationController
|
||||
# Updating password - requires current password
|
||||
unless @user.authenticate(params[:user][:current_password])
|
||||
@user.errors.add(:current_password, "is incorrect")
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
render :show, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
@@ -20,7 +17,6 @@ class ProfilesController < ApplicationController
|
||||
if @user.update(password_params)
|
||||
redirect_to profile_path, notice: "Password updated successfully."
|
||||
else
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
@@ -28,40 +24,11 @@ class ProfilesController < ApplicationController
|
||||
if @user.update(email_params)
|
||||
redirect_to profile_path, notice: "Email updated successfully."
|
||||
else
|
||||
@active_sessions = @user.sessions.active.order(last_activity_at: :desc)
|
||||
render :show, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def revoke_consent
|
||||
@user = Current.session.user
|
||||
application = Application.find(params[:application_id])
|
||||
|
||||
# Check if user has consent for this application
|
||||
consent = @user.oidc_user_consents.find_by(application: application)
|
||||
unless consent
|
||||
redirect_to profile_path, alert: "No consent found for this application."
|
||||
return
|
||||
end
|
||||
|
||||
# Revoke the consent
|
||||
consent.destroy
|
||||
redirect_to profile_path, notice: "Successfully revoked access to #{application.name}."
|
||||
end
|
||||
|
||||
def revoke_all_consents
|
||||
@user = Current.session.user
|
||||
count = @user.oidc_user_consents.count
|
||||
|
||||
if count > 0
|
||||
@user.oidc_user_consents.destroy_all
|
||||
redirect_to profile_path, notice: "Successfully revoked access to #{count} applications."
|
||||
else
|
||||
redirect_to profile_path, alert: "No applications to revoke."
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def email_params
|
||||
|
||||
@@ -115,7 +115,7 @@ class SessionsController < ApplicationController
|
||||
def destroy_other
|
||||
session = Current.session.user.sessions.find(params[:id])
|
||||
session.destroy
|
||||
redirect_to profile_path, notice: "Session revoked successfully."
|
||||
redirect_to active_sessions_path, notice: "Session revoked successfully."
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -123,8 +123,10 @@ class Application < ApplicationRecord
|
||||
next unless header_name.present? # Skip disabled headers
|
||||
|
||||
case key
|
||||
when :user, :email, :name
|
||||
when :user, :email
|
||||
headers[header_name] = user.email_address
|
||||
when :name
|
||||
headers[header_name] = user.name.presence || user.email_address
|
||||
when :groups
|
||||
headers[header_name] = user.groups.pluck(:name).join(",") if user.groups.any?
|
||||
when :admin
|
||||
|
||||
@@ -13,7 +13,7 @@ class OidcJwtService
|
||||
email: user.email_address,
|
||||
email_verified: true,
|
||||
preferred_username: user.email_address,
|
||||
name: user.email_address
|
||||
name: user.name.presence || user.email_address
|
||||
}
|
||||
|
||||
# Add nonce if provided (OIDC requires this for implicit flow)
|
||||
|
||||
114
app/views/active_sessions/show.html.erb
Normal file
114
app/views/active_sessions/show.html.erb
Normal file
@@ -0,0 +1,114 @@
|
||||
<div class="space-y-8">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-gray-900">Sessions</h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Manage your active sessions and connected applications.</p>
|
||||
</div>
|
||||
|
||||
<!-- Connected Applications -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Connected Applications</h3>
|
||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>These applications have access to your account. You can revoke access at any time.</p>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<% if @connected_applications.any? %>
|
||||
<ul role="list" class="divide-y divide-gray-200">
|
||||
<% @connected_applications.each do |consent| %>
|
||||
<li class="py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm font-medium text-gray-900">
|
||||
<%= consent.application.name %>
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Access to: <%= consent.formatted_scopes %>
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
Authorized <%= time_ago_in_words(consent.granted_at) %> ago
|
||||
</p>
|
||||
</div>
|
||||
<%= button_to "Revoke Access", revoke_consent_active_sessions_path(application_id: consent.application.id), method: :delete,
|
||||
class: "inline-flex items-center rounded-md border border-red-300 bg-white px-3 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2",
|
||||
form: { data: { turbo_confirm: "Are you sure you want to revoke access to #{consent.application.name}? You'll need to re-authorize this application to use it again." } } %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500">No connected applications.</p>
|
||||
<% end %>
|
||||
|
||||
<% if @connected_applications.any? %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<div class="flex justify-end">
|
||||
<div class="inline-block">
|
||||
<%= button_to "Revoke All App Access", revoke_all_consents_active_sessions_path, method: :delete,
|
||||
class: "inline-flex items-center rounded-md border border-red-300 bg-white px-3 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 whitespace-nowrap",
|
||||
form: { data: { turbo_confirm: "This will revoke access from all connected applications. You'll need to re-authorize each application to use them again. Are you sure?" } } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Sessions -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Active Sessions</h3>
|
||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>These devices are currently signed in to your account. Revoke any sessions that you don't recognize.</p>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<% if @active_sessions.any? %>
|
||||
<ul role="list" class="divide-y divide-gray-200">
|
||||
<% @active_sessions.each do |session| %>
|
||||
<li class="py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm font-medium text-gray-900">
|
||||
<%= session.device_name || "Unknown Device" %>
|
||||
<% if session.id == Current.session.id %>
|
||||
<span class="ml-2 inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||||
This device
|
||||
</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
<%= session.ip_address %>
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
Last active <%= time_ago_in_words(session.last_activity_at || session.updated_at) %> ago
|
||||
</p>
|
||||
</div>
|
||||
<% if session.id != Current.session.id %>
|
||||
<%= button_to "Revoke", session_path(session), method: :delete,
|
||||
class: "inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
||||
form: { data: { turbo_confirm: "Are you sure you want to revoke this session?" } } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500">No other active sessions.</p>
|
||||
<% end %>
|
||||
|
||||
<% if @active_sessions.count > 1 %>
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<div class="flex justify-end">
|
||||
<div class="inline-block">
|
||||
<%= button_to "Sign Out Everywhere Else", session_path(Current.session), method: :delete,
|
||||
class: "inline-flex items-center rounded-md border border-orange-300 bg-white px-3 py-2 text-sm font-medium text-orange-700 shadow-sm hover:bg-orange-50 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 whitespace-nowrap",
|
||||
form: { data: { turbo_confirm: "This will sign you out from all other devices except this one. Are you sure?" } } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -65,8 +65,23 @@
|
||||
|
||||
<div>
|
||||
<%= form.label :headers_config, "Custom Headers Configuration (JSON)", class: "block text-sm font-medium text-gray-700" %>
|
||||
<%= form.text_area :headers_config, rows: 8, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono", placeholder: '{"user": "X-Remote-User", "email": "X-Remote-Email"}' %>
|
||||
<p class="mt-1 text-sm text-gray-500">Optional: Override default headers. Leave empty to use defaults: X-Remote-User, X-Remote-Email, X-Remote-Name, X-Remote-Groups, X-Remote-Admin</p>
|
||||
<%= form.text_area :headers_config, rows: 10, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm font-mono", placeholder: '{"user": "Remote-User", "groups": "Remote-Groups"}' %>
|
||||
<div class="mt-2 text-sm text-gray-600 space-y-1">
|
||||
<p class="font-medium">Optional: Customize header names sent to your application.</p>
|
||||
<p><strong>Default headers:</strong> X-Remote-User, X-Remote-Email, X-Remote-Name, X-Remote-Groups, X-Remote-Admin</p>
|
||||
<details class="mt-2">
|
||||
<summary class="cursor-pointer text-blue-600 hover:text-blue-800">Show available header keys and what data they send</summary>
|
||||
<div class="mt-2 ml-4 space-y-1 text-xs">
|
||||
<p><code class="bg-gray-100 px-1 rounded">user</code> - User's email address</p>
|
||||
<p><code class="bg-gray-100 px-1 rounded">email</code> - User's email address</p>
|
||||
<p><code class="bg-gray-100 px-1 rounded">name</code> - User's display name (falls back to email if not set)</p>
|
||||
<p><code class="bg-gray-100 px-1 rounded">groups</code> - Comma-separated list of group names (e.g., "admin,developers")</p>
|
||||
<p><code class="bg-gray-100 px-1 rounded">admin</code> - "true" or "false" indicating admin status</p>
|
||||
<p class="mt-2 italic">Example: <code class="bg-gray-100 px-1 rounded">{"user": "Remote-User", "groups": "Remote-Groups"}</code></p>
|
||||
<p class="italic">Need custom user fields? Add them to user's custom_claims for OIDC tokens</p>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
<%= form.email_field :email_address, required: true, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "user@example.com" %>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.label :name, "Display Name (Optional)", class: "block text-sm font-medium text-gray-700" %>
|
||||
<%= form.text_field :name, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: "John Smith" %>
|
||||
<p class="mt-1 text-sm text-gray-500">Optional: Name shown in applications. Defaults to email address if not set.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= form.label :password, class: "block text-sm font-medium text-gray-700" %>
|
||||
<%= form.password_field :password, class: "mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm", placeholder: user.persisted? ? "Leave blank to keep current password" : "Enter password" %>
|
||||
|
||||
@@ -198,110 +198,4 @@
|
||||
document.getElementById('view-backup-codes-modal').classList.add('hidden');
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Connected Applications -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Connected Applications</h3>
|
||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>These applications have access to your account. You can revoke access at any time.</p>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<% if @connected_applications.any? %>
|
||||
<ul role="list" class="divide-y divide-gray-200">
|
||||
<% @connected_applications.each do |consent| %>
|
||||
<li class="py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm font-medium text-gray-900">
|
||||
<%= consent.application.name %>
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
Access to: <%= consent.formatted_scopes %>
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
Authorized <%= time_ago_in_words(consent.granted_at) %> ago
|
||||
</p>
|
||||
</div>
|
||||
<%= button_to "Revoke Access", revoke_consent_profile_path(application_id: consent.application.id), method: :delete,
|
||||
class: "inline-flex items-center rounded-md border border-red-300 bg-white px-3 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2",
|
||||
form: { data: { turbo_confirm: "Are you sure you want to revoke access to #{consent.application.name}? You'll need to re-authorize this application to use it again." } } %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500">No connected applications.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Sessions -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Active Sessions</h3>
|
||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>These devices are currently signed in to your account. Revoke any sessions that you don't recognize.</p>
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
<% if @active_sessions.any? %>
|
||||
<ul role="list" class="divide-y divide-gray-200">
|
||||
<% @active_sessions.each do |session| %>
|
||||
<li class="py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<p class="text-sm font-medium text-gray-900">
|
||||
<%= session.device_name || "Unknown Device" %>
|
||||
<% if session.id == Current.session.id %>
|
||||
<span class="ml-2 inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800">
|
||||
This device
|
||||
</span>
|
||||
<% end %>
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
<%= session.ip_address %>
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-400">
|
||||
Last active <%= time_ago_in_words(session.last_activity_at || session.updated_at) %> ago
|
||||
</p>
|
||||
</div>
|
||||
<% if session.id != Current.session.id %>
|
||||
<%= button_to "Revoke", session_path(session), method: :delete,
|
||||
class: "inline-flex items-center rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
||||
form: { data: { turbo_confirm: "Are you sure you want to revoke this session?" } } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% else %>
|
||||
<p class="text-sm text-gray-500">No other active sessions.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global Security Actions -->
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900">Security Actions</h3>
|
||||
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||
<p>Use these actions to quickly secure your account. Be careful - these actions cannot be undone.</p>
|
||||
</div>
|
||||
<div class="mt-5 flex flex-wrap gap-4">
|
||||
<% if @active_sessions.count > 1 %>
|
||||
<%= button_to "Sign Out Everywhere Else", session_path(Current.session), method: :delete,
|
||||
class: "inline-flex items-center rounded-md border border-orange-300 bg-white px-4 py-2 text-sm font-medium text-orange-700 shadow-sm hover:bg-orange-50 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2",
|
||||
form: { data: { turbo_confirm: "This will sign you out from all other devices except this one. Are you sure?" } } %>
|
||||
<% end %>
|
||||
|
||||
<% if @connected_applications.any? %>
|
||||
<%= button_to "Revoke All App Access", revoke_all_consents_profile_path, method: :delete,
|
||||
class: "inline-flex items-center rounded-md border border-red-300 bg-white px-4 py-2 text-sm font-medium text-red-700 shadow-sm hover:bg-red-50 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2",
|
||||
form: { data: { turbo_confirm: "This will revoke access from all connected applications. You'll need to re-authorize each application to use them again. Are you sure?" } } %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -78,6 +78,16 @@
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
<!-- Sessions -->
|
||||
<li>
|
||||
<%= link_to active_sessions_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 #{ current_path == '/active_sessions' ? 'bg-gray-50 text-blue-600' : 'text-gray-700 hover:text-blue-600 hover:bg-gray-50' }" do %>
|
||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
|
||||
</svg>
|
||||
Sessions
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
<!-- Sign Out -->
|
||||
<li>
|
||||
<%= link_to signout_path, data: { turbo_method: :delete }, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-red-600 hover:text-red-700 hover:bg-red-50" do %>
|
||||
@@ -169,6 +179,14 @@
|
||||
Profile
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to active_sessions_path, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-gray-700 hover:text-blue-600 hover:bg-gray-50" do %>
|
||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18v-5.25m0 0a6.01 6.01 0 001.5-.189m-1.5.189a6.01 6.01 0 01-1.5-.189m3.75 7.478a12.06 12.06 0 01-4.5 0m3.75 2.383a14.406 14.406 0 01-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 10-7.517 0c.85.493 1.509 1.333 1.509 2.316V18" />
|
||||
</svg>
|
||||
Sessions
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to signout_path, data: { turbo_method: :delete }, class: "group flex gap-x-3 rounded-md p-2 text-sm font-semibold leading-6 text-red-600 hover:text-red-700 hover:bg-red-50" do %>
|
||||
<svg class="h-6 w-6 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
|
||||
@@ -42,6 +42,12 @@ Rails.application.routes.draw do
|
||||
delete :revoke_all_consents
|
||||
end
|
||||
end
|
||||
resource :active_sessions, only: [:show] do
|
||||
member do
|
||||
delete :revoke_consent
|
||||
delete :revoke_all_consents
|
||||
end
|
||||
end
|
||||
resources :sessions, only: [] do
|
||||
member do
|
||||
delete :destroy, action: :destroy_other
|
||||
|
||||
5
db/migrate/20251104022439_add_name_to_users.rb
Normal file
5
db/migrate/20251104022439_add_name_to_users.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddNameToUsers < ActiveRecord::Migration[8.1]
|
||||
def change
|
||||
add_column :users, :name, :string
|
||||
end
|
||||
end
|
||||
3
db/schema.rb
generated
3
db/schema.rb
generated
@@ -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_11_04_015104) do
|
||||
ActiveRecord::Schema[8.1].define(version: 2025_11_04_022439) do
|
||||
create_table "application_groups", force: :cascade do |t|
|
||||
t.integer "application_id", null: false
|
||||
t.datetime "created_at", null: false
|
||||
@@ -128,6 +128,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_11_04_015104) do
|
||||
t.json "custom_claims", default: {}, null: false
|
||||
t.string "email_address", null: false
|
||||
t.datetime "last_sign_in_at"
|
||||
t.string "name"
|
||||
t.string "password_digest", null: false
|
||||
t.integer "status", default: 0, null: false
|
||||
t.boolean "totp_required", default: false, null: false
|
||||
|
||||
Reference in New Issue
Block a user