Variants are currently broken. So we process the original file with the most common avatar dimensions and stripping metadata, then hash and upload only that version.
229 lines
6.8 KiB
Ruby
229 lines
6.8 KiB
Ruby
require "securerandom"
|
|
require "bcrypt"
|
|
|
|
class SettingsController < ApplicationController
|
|
before_action :authenticate_user!
|
|
before_action :set_main_nav_section
|
|
before_action :set_settings_section, only: [:show, :update, :update_email, :reset_email_password]
|
|
before_action :set_user, only: [:show, :update, :update_email, :reset_email_password]
|
|
|
|
def index
|
|
redirect_to setting_path(:profile)
|
|
end
|
|
|
|
def show
|
|
case @settings_section
|
|
when "lightning"
|
|
@notifications_enabled = @user.preferences[:lightning_notify_sats_received] != "disabled" ||
|
|
@user.preferences[:lightning_notify_zap_received] != "disabled"
|
|
when "nostr"
|
|
session[:shared_secret] ||= SecureRandom.base64(12)
|
|
end
|
|
end
|
|
|
|
# PUT /settings/:section
|
|
def update
|
|
@user.preferences.merge!(user_params[:preferences] || {})
|
|
@user.display_name = user_params[:display_name]
|
|
@user.avatar_new = user_params[:avatar_new]
|
|
@user.pgp_pubkey = user_params[:pgp_pubkey]
|
|
|
|
if @user.save
|
|
if @user.display_name && (@user.display_name != @user.ldap_entry[:display_name])
|
|
LdapManager::UpdateDisplayName.call(dn: @user.dn, display_name: @user.display_name)
|
|
end
|
|
|
|
if @user.avatar_new.present?
|
|
if store_user_avatar
|
|
LdapManager::UpdateAvatar.call(user: @user)
|
|
else
|
|
@validation_errors = @user.errors
|
|
render :show, status: :unprocessable_entity and return
|
|
end
|
|
end
|
|
|
|
if @user.pgp_pubkey && (@user.pgp_pubkey != @user.ldap_entry[:pgp_key])
|
|
UserManager::UpdatePgpKey.call(user: @user)
|
|
end
|
|
|
|
redirect_to setting_path(@settings_section), flash: {
|
|
success: 'Settings saved.'
|
|
}
|
|
else
|
|
@validation_errors = @user.errors
|
|
render :show, status: :unprocessable_entity
|
|
end
|
|
end
|
|
|
|
# POST /settings/update_email
|
|
def update_email
|
|
if @user.valid_ldap_authentication?(security_params[:current_password])
|
|
if @user.update email: email_params[:email]
|
|
redirect_to setting_path(:account), flash: {
|
|
notice: 'Please confirm your new address using the confirmation link we just sent you.'
|
|
}
|
|
else
|
|
@validation_errors = @user.errors
|
|
render :show, status: :unprocessable_entity
|
|
end
|
|
else
|
|
redirect_to setting_path(:account), flash: {
|
|
error: 'Password did not match your current password. Try again.'
|
|
}
|
|
end
|
|
end
|
|
|
|
# POST /settings/reset_email_password
|
|
def reset_email_password
|
|
@user.current_password = security_params[:current_password]
|
|
|
|
if @user.valid_ldap_authentication?(@user.current_password)
|
|
@user.current_password = nil
|
|
session[:new_email_password] = generate_email_password
|
|
hashed_password = hash_email_password(session[:new_email_password])
|
|
LdapManager::UpdateEmailPassword.call(dn: @user.dn, password_hash: hashed_password)
|
|
|
|
if @user.ldap_entry[:email_maildrop] != @user.address
|
|
LdapManager::UpdateEmailMaildrop.call(dn: @user.dn, address: @user.address)
|
|
end
|
|
|
|
redirect_to new_password_services_email_path
|
|
else
|
|
@validation_errors = {
|
|
current_password: [ "Wrong password. Try again!" ]
|
|
}
|
|
render :show, status: :forbidden
|
|
end
|
|
end
|
|
|
|
# POST /settings/reset_password
|
|
def reset_password
|
|
current_user.send_reset_password_instructions
|
|
sign_out current_user
|
|
msg = "We have sent you an email with a link to reset your password."
|
|
redirect_to check_your_email_path, notice: msg
|
|
end
|
|
|
|
# POST /settings/set_nostr_pubkey
|
|
def set_nostr_pubkey
|
|
signed_event = Nostr::Event.new(**nostr_event_from_params)
|
|
|
|
is_valid_sig = signed_event.verify_signature
|
|
is_valid_auth = NostrManager::VerifyAuth.call(
|
|
event: signed_event,
|
|
challenge: session[:shared_secret]
|
|
)
|
|
|
|
unless is_valid_sig && is_valid_auth
|
|
flash[:alert] = "Public key could not be verified"
|
|
http_status :unprocessable_entity and return
|
|
end
|
|
|
|
user_with_pubkey = LdapManager::FetchUserByNostrKey.call(pubkey: signed_event.pubkey)
|
|
|
|
if user_with_pubkey.present? && (user_with_pubkey != current_user)
|
|
flash[:alert] = "Public key already in use for a different account"
|
|
http_status :unprocessable_entity and return
|
|
end
|
|
|
|
LdapManager::UpdateNostrKey.call(dn: current_user.dn, pubkey: signed_event.pubkey)
|
|
session[:shared_secret] = nil
|
|
|
|
flash[:success] = "Public key verification successful"
|
|
http_status :ok
|
|
end
|
|
|
|
# DELETE /settings/nostr_pubkey
|
|
def remove_nostr_pubkey
|
|
# TODO require current pubkey or password to delete
|
|
LdapManager::UpdateNostrKey.call(dn: current_user.dn, pubkey: nil)
|
|
|
|
redirect_to setting_path(:nostr), flash: {
|
|
success: 'Public key removed from account'
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def set_main_nav_section
|
|
@current_section = :settings
|
|
end
|
|
|
|
def set_settings_section
|
|
@settings_section = params[:section]
|
|
allowed_sections = [
|
|
:profile, :account, :xmpp, :email,
|
|
:lightning, :remotestorage, :nostr
|
|
]
|
|
|
|
unless allowed_sections.include?(@settings_section.to_sym)
|
|
redirect_to setting_path(:profile)
|
|
end
|
|
end
|
|
|
|
def set_user
|
|
@user = current_user
|
|
end
|
|
|
|
def user_params
|
|
params.require(:user).permit(
|
|
:display_name, :avatar_new, :pgp_pubkey,
|
|
preferences: UserPreferences.pref_keys
|
|
)
|
|
end
|
|
|
|
def email_params
|
|
params.require(:user).permit(:email)
|
|
end
|
|
|
|
def security_params
|
|
params.require(:user).permit(:current_password)
|
|
end
|
|
|
|
def generate_email_password
|
|
characters = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
|
|
SecureRandom.random_bytes(16).each_byte.map { |b| characters[b % characters.length] }.join
|
|
end
|
|
|
|
def hash_email_password(password)
|
|
salt = BCrypt::Engine.generate_salt
|
|
BCrypt::Engine.hash_secret(password, salt)
|
|
end
|
|
|
|
def store_user_avatar
|
|
io = @user.avatar_new.tempfile
|
|
img_data = process_avatar(io)
|
|
tempfile = Tempfile.create
|
|
tempfile.binmode
|
|
tempfile.write(img_data)
|
|
tempfile.rewind
|
|
|
|
hash = Digest::SHA256.hexdigest(img_data)
|
|
ext = @user.avatar_new.content_type == "image/png" ? "png" : "jpg"
|
|
filename = "#{hash}.#{ext}"
|
|
|
|
if filename == @user.avatar.filename.to_s
|
|
@user.errors.add(:avatar, "must be a new file/picture")
|
|
false
|
|
else
|
|
key = "users/#{@user.cn}/avatars/#{filename}"
|
|
@user.avatar.attach io: tempfile, key: key, filename: filename
|
|
@user.save
|
|
end
|
|
end
|
|
|
|
def process_avatar(io)
|
|
processed = ImageProcessing::Vips
|
|
.source(io)
|
|
.resize_to_fill(400, 400)
|
|
.saver(strip: true)
|
|
.call
|
|
io.rewind
|
|
processed.read
|
|
rescue Vips::Error => e
|
|
Sentry.capture_exception(e) if Setting.sentry_enabled?
|
|
Rails.logger.error { "Image processing failed for avatar: #{e.message}" }
|
|
nil
|
|
end
|
|
end
|