impress/app/controllers/application_controller.rb
Emi Matchu 604a8667cf feat: add write lock for AuthUser migration preparation
This commit implements a temporary write lock on the AuthUser model to
prepare for consolidating the openneo_id database into the main database.

Changes:
- AuthUser: Added before_save/before_destroy callbacks that raise
  TemporarilyReadOnly exception to prevent any writes
- AuthUser: Override update_tracked_fields! to silently skip Devise login
  tracking (prevents crashes during login while maintaining read access)
- ApplicationController: Added rescue_from handler for TemporarilyReadOnly
  that redirects to root with a friendly maintenance message
- Migration: Created CopyAuthUsersTableToMainDatabase to copy
  openneo_id.users → openneo_impress.auth_users (preserves all IDs)

This allows us to run the data copy migration in production while:
- Keeping existing users able to log in and use the site
- Blocking new registrations, settings changes, and NeoPass connections
- Losing some login tracking data (acceptable tradeoff)

Next step: Deploy this, then run the migration in production while the
table is stable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 06:50:55 +00:00

131 lines
3.7 KiB
Ruby

require 'async'
require 'async/container'
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :current_user, :support_staff?, :user_signed_in?
before_action :set_locale
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :save_return_to_path,
if: ->(c) { c.controller_name == 'sessions' && c.action_name == 'new' }
# Enable profiling tools if logged in as admin.
before_action do
if current_user && current_user.admin?
Rack::MiniProfiler.authorize_request
end
end
class AccessDenied < StandardError; end
rescue_from AccessDenied, with: :on_access_denied
rescue_from Async::Stop, Async::Container::Terminate,
with: :on_request_stopped
rescue_from ActiveRecord::ConnectionTimeoutError, with: :on_db_timeout
# TEMPORARY: Rescue handler for database migration write lock
# This catches attempts to modify AuthUser records during the migration.
rescue_from AuthUser::TemporarilyReadOnly, with: :on_auth_user_read_only
def authenticate_user!
redirect_to(new_auth_user_session_path) unless user_signed_in?
end
def authorize_user!
raise AccessDenied unless user_signed_in? && current_user.id == params[:user_id].to_i
end
def current_user
if auth_user_signed_in?
User.where(remote_id: current_auth_user.id).first
else
nil
end
end
def user_signed_in?
auth_user_signed_in?
end
def infer_locale
return params[:locale] if valid_locale?(params[:locale])
return cookies[:locale] if valid_locale?(cookies[:locale])
Rails.logger.debug "Preferred languages: #{http_accept_language.user_preferred_languages}"
http_accept_language.language_region_compatible_from(I18n.available_locales.map(&:to_s)) ||
I18n.default_locale
end
def not_found(record_name='record')
raise ActionController::RoutingError.new("#{record_name} not found")
end
def on_access_denied
render file: 'public/403.html', layout: false, status: :forbidden
end
def on_request_stopped
render file: 'public/stopped.html', layout: false,
status: :internal_server_error
end
def on_db_timeout
render file: 'public/503.html', layout: false,
status: :service_unavailable
end
def on_auth_user_read_only
flash[:alert] = "Account changes are temporarily unavailable during maintenance. Please try again shortly."
redirect_to root_path
end
def redirect_back!(default=:back)
redirect_to(params[:return_to] || default)
end
def set_locale
I18n.locale = infer_locale || I18n.default_locale
end
def valid_locale?(locale)
locale && I18n.available_locales.include?(locale.to_sym)
end
def configure_permitted_parameters
# Devise will automatically permit the authentication key (username) and
# the password, but we need to let the email field through ourselves.
devise_parameter_sanitizer.permit(:sign_up, keys: [:email])
devise_parameter_sanitizer.permit(:account_update, keys: [:email])
end
def save_return_to_path
if params[:return_to]
Rails.logger.debug "Saving return_to path: #{params[:return_to].inspect}"
session[:devise_return_to] = params[:return_to]
end
end
def after_sign_in_path_for(user)
return_to = session.delete(:devise_return_to)
Rails.logger.debug "Using return_to path: #{return_to.inspect}"
return_to || root_path
end
def after_sign_out_path_for(user)
return_to = params[:return_to]
Rails.logger.debug "Using return_to path: #{return_to.inspect}"
return_to || root_path
end
def support_staff?
current_user&.support_staff?
end
def support_staff_only
raise AccessDenied, "Support staff only" unless support_staff?
end
end