diff --git a/Gemfile b/Gemfile index 8824e1b..169f39e 100644 --- a/Gemfile +++ b/Gemfile @@ -38,6 +38,7 @@ gem 'net-ldap' # Utilities gem "rqrcode", "~> 2.0" +gem 'rails-settings-cached', '~> 2.8.3' # HTTP requests gem 'faraday' diff --git a/Gemfile.lock b/Gemfile.lock index d80ab81..5a658b5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -206,6 +206,9 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.4.3) loofah (~> 2.3) + rails-settings-cached (2.8.3) + activerecord (>= 5.0.0) + railties (>= 5.0.0) railties (7.0.4) actionpack (= 7.0.4) activesupport (= 7.0.4) @@ -327,6 +330,7 @@ DEPENDENCIES pg (~> 1.2.3) puma (~> 4.1) rails (~> 7.0.2) + rails-settings-cached (~> 2.8.3) rqrcode (~> 2.0) rspec-rails sidekiq (< 7) diff --git a/app/assets/stylesheets/components/forms.css b/app/assets/stylesheets/components/forms.css index 6c68083..61c7b1e 100644 --- a/app/assets/stylesheets/components/forms.css +++ b/app/assets/stylesheets/components/forms.css @@ -1,6 +1,6 @@ @layer components { input[type=text], input[type=email], input[type=password], - input[type=number], select { + input[type=number], select, textarea { @apply mt-1 rounded-md bg-gray-100 focus:bg-white border-transparent focus:border-transparent focus:ring-2 focus:ring-blue-600 focus:ring-opacity-75; diff --git a/app/controllers/admin/donations_controller.rb b/app/controllers/admin/donations_controller.rb index 84d98d9..ffbae14 100644 --- a/app/controllers/admin/donations_controller.rb +++ b/app/controllers/admin/donations_controller.rb @@ -33,7 +33,11 @@ class Admin::DonationsController < Admin::BaseController respond_to do |format| if @donation.save - format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully created.' } + format.html do + redirect_to admin_donation_url(@donation), flash: { + success: 'Donation was successfully created.' + } + end format.json { render :show, status: :created, location: @donation } else format.html { render :new } @@ -47,7 +51,11 @@ class Admin::DonationsController < Admin::BaseController def update respond_to do |format| if @donation.update(donation_params) - format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully updated.' } + format.html do + redirect_to admin_donation_url(@donation), flash: { + success: 'Donation was successfully updated.' + } + end format.json { render :show, status: :ok, location: @donation } else format.html { render :edit } @@ -61,7 +69,10 @@ class Admin::DonationsController < Admin::BaseController def destroy @donation.destroy respond_to do |format| - format.html { redirect_to admin_donations_url, notice: 'Donation was successfully destroyed.' } + format.html do redirect_to admin_donations_url, flash: { + success: 'Donation was successfully destroyed.' + } + end format.json { head :no_content } end end diff --git a/app/controllers/admin/lightning_controller.rb b/app/controllers/admin/lightning_controller.rb index 10e6267..2b3dfe7 100644 --- a/app/controllers/admin/lightning_controller.rb +++ b/app/controllers/admin/lightning_controller.rb @@ -13,7 +13,7 @@ class Admin::LightningController < Admin::BaseController end def check_feature_enabled - if ENV["LNDHUB_ADMIN_UI"].empty? + if !Setting.lndhub_admin_enabled? flash[:alert] = "Lightning Admin UI not enabled" redirect_to admin_root_path and return end diff --git a/app/controllers/admin/settings/registrations_controller.rb b/app/controllers/admin/settings/registrations_controller.rb new file mode 100644 index 0000000..3f2019e --- /dev/null +++ b/app/controllers/admin/settings/registrations_controller.rb @@ -0,0 +1,38 @@ +class Admin::Settings::RegistrationsController < Admin::SettingsController + + def index + end + + def create + @errors = ActiveModel::Errors.new(Setting.new) + + setting_params.keys.each do |key| + next if setting_params[key].nil? + + setting = Setting.new(var: key) + setting.value = setting_params[key].strip + unless setting.valid? + @errors.merge!(setting.errors) + end + end + + if @errors.any? + render :index + end + + setting_params.keys.each do |key| + Setting.send("#{key}=", setting_params[key].strip) unless setting_params[key].nil? + end + + redirect_to admin_settings_registrations_path, flash: { + success: "Settings saved" + } + end + + private + + def setting_params + params.require(:setting).permit(:reserved_usernames) + end + +end diff --git a/app/controllers/admin/settings/services_controller.rb b/app/controllers/admin/settings/services_controller.rb new file mode 100644 index 0000000..ebdad4e --- /dev/null +++ b/app/controllers/admin/settings/services_controller.rb @@ -0,0 +1,9 @@ +class Admin::Settings::ServicesController < Admin::SettingsController + + def index + end + + def update + end + +end diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb new file mode 100644 index 0000000..8353f9b --- /dev/null +++ b/app/controllers/admin/settings_controller.rb @@ -0,0 +1,12 @@ +class Admin::SettingsController < Admin::BaseController + before_action :set_current_section + + def index + end + + private + + def set_current_section + @current_section = :settings + end +end diff --git a/app/controllers/invitations_controller.rb b/app/controllers/invitations_controller.rb index 2bc9d80..3bb038a 100644 --- a/app/controllers/invitations_controller.rb +++ b/app/controllers/invitations_controller.rb @@ -27,7 +27,10 @@ class InvitationsController < ApplicationController respond_to do |format| if @invitation.save - format.html { redirect_to @invitation, notice: 'Invitation was successfully created.' } + format.html do redirect_to @invitation, flash: { + success: 'Invitation was successfully created.' + } + end format.json { render :show, status: :created, location: @invitation } else format.html { render :new } diff --git a/app/models/setting.rb b/app/models/setting.rb new file mode 100644 index 0000000..fb4dae5 --- /dev/null +++ b/app/models/setting.rb @@ -0,0 +1,11 @@ +# RailsSettings Model +class Setting < RailsSettings::Base + cache_prefix { "v1" } + + field :reserved_usernames, type: :array, default: %w[ + account accounts admin donations mail webmaster support + ] + + field :lndhub_enabled, default: (ENV["LNDHUB_API_URL"].present?.to_s || "false"), type: :boolean + field :lndhub_admin_enabled, default: (ENV["LNDHUB_ADMIN_UI"] || "false"), type: :boolean +end diff --git a/app/models/user.rb b/app/models/user.rb index 40d5f20..f12c8ce 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -12,6 +12,15 @@ class User < ApplicationRecord validates_uniqueness_of :cn validates_length_of :cn, :minimum => 3 + validates_format_of :cn, with: /\A([a-z0-9\-])*\z/, + if: Proc.new{ |u| u.cn.present? }, + message: "is invalid. Please use only letters, numbers and -" + validates_format_of :cn, without: /\A-/, + if: Proc.new{ |u| u.cn.present? }, + message: "is invalid. Usernames need to start with a letter." + validates_format_of :cn, without: /\A(#{Setting.reserved_usernames.join('|')})\z/i, + message: "has already been taken" + validates_uniqueness_of :email validates :email, email: true diff --git a/app/views/admin/settings/registrations/index.html.erb b/app/views/admin/settings/registrations/index.html.erb new file mode 100644 index 0000000..2131e41 --- /dev/null +++ b/app/views/admin/settings/registrations/index.html.erb @@ -0,0 +1,37 @@ +<%= render HeaderComponent.new(title: "Settings") %> + +<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %> + <%= form_for(Setting.new, url: admin_settings_registrations_path) do |f| %> + + Registrations + <% if @errors && @errors.any? %> + + + <% @errors.full_messages.each do |msg| %> + <%= msg %> + <% end %> + + + <% end %> + + + Reserved usernames + + These usernames cannot be registered as accounts: + + <%= f.text_area :reserved_usernames, + value: Setting.reserved_usernames.join("\n"), + class: "h-44 mb-2" %> + + One username per line + + + + + + + <%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %> + + + <% end %> +<% end %> diff --git a/app/views/admin/settings/services/index.html.erb b/app/views/admin/settings/services/index.html.erb new file mode 100644 index 0000000..c0dad64 --- /dev/null +++ b/app/views/admin/settings/services/index.html.erb @@ -0,0 +1,39 @@ +<%= render HeaderComponent.new(title: "Settings") %> + +<%= render MainWithSidenavComponent.new(sidenav_partial: 'shared/admin_sidenav_settings') do %> + + Lightning Network + <%= form_for(Setting.new, url: admin_settings_services_path) do |f| %> + <% if @errors && @errors.any? %> + + + <% @errors.full_messages.each do |msg| %> + <%= msg %> + <% end %> + + + <% end %> + + + + + Enable LNDHub integration + LNDHub configuration present and wallet features enabled + + <%= f.check_box :lndhub_enabled, checked: Setting.lndhub_enabled?, + disabled: true, + class: "relative ml-4 inline-flex flex-shrink-0" %> + + + + Enable LNDHub admin panel + LNDHub database configuration present and admin panel enabled + + <%= f.check_box :lndhub_admin_enabled, checked: Setting.lndhub_admin_enabled?, + disabled: true, + class: "relative ml-4 inline-flex flex-shrink-0" %> + + + <% end %> + +<% end %> diff --git a/app/views/icons/_grid.html.erb b/app/views/icons/_grid.html.erb index 8ef2e9d..57f9632 100644 --- a/app/views/icons/_grid.html.erb +++ b/app/views/icons/_grid.html.erb @@ -1 +1 @@ - \ No newline at end of file + diff --git a/app/views/shared/_admin_nav.html.erb b/app/views/shared/_admin_nav.html.erb index 00a0fe8..dd8c8b8 100644 --- a/app/views/shared/_admin_nav.html.erb +++ b/app/views/shared/_admin_nav.html.erb @@ -6,7 +6,9 @@ class: main_nav_class(@current_section, :invitations) %> <%= link_to "Donations", admin_donations_path, class: main_nav_class(@current_section, :donations) %> -<% if ENV["LNDHUB_ADMIN_UI"] %> +<% if Setting.lndhub_admin_enabled? %> <%= link_to "Lightning", admin_lightning_path, class: main_nav_class(@current_section, :lightning) %> <% end %> +<%= link_to "Settings", admin_settings_registrations_path, + class: main_nav_class(@current_section, :settings) %> diff --git a/app/views/shared/_admin_sidenav_settings.html.erb b/app/views/shared/_admin_sidenav_settings.html.erb new file mode 100644 index 0000000..d675242 --- /dev/null +++ b/app/views/shared/_admin_sidenav_settings.html.erb @@ -0,0 +1,11 @@ +<%= render SidenavLinkComponent.new( + name: "Registrations", path: admin_settings_registrations_path, icon: "user", + active: current_page?(admin_settings_registrations_path) +) %> +<%= render SidenavLinkComponent.new( + name: "Services", path: admin_settings_services_path, icon: "grid", + active: current_page?(admin_settings_services_path) +) %> +<%= render SidenavLinkComponent.new( + name: "Security", path: "#", icon: "shield", disabled: true +) %> diff --git a/config/routes.rb b/config/routes.rb index 00475c5..7559393 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -43,6 +43,11 @@ Rails.application.routes.draw do get 'invitations', to: 'invitations#index' resources :donations get 'lightning', to: 'lightning#index' + + namespace :settings do + resources 'registrations', only: ['index', 'create'] + resources 'services', only: ['index', 'create'] + end end authenticate :user, ->(user) { user.is_admin? } do diff --git a/db/migrate/20230217084310_create_settings.rb b/db/migrate/20230217084310_create_settings.rb new file mode 100644 index 0000000..6fc5a0c --- /dev/null +++ b/db/migrate/20230217084310_create_settings.rb @@ -0,0 +1,15 @@ +class CreateSettings < ActiveRecord::Migration[7.0] + def self.up + create_table :settings do |t| + t.string :var, null: false + t.text :value, null: true + t.timestamps + end + + add_index :settings, %i(var), unique: true + end + + def self.down + drop_table :settings + end +end diff --git a/db/schema.rb b/db/schema.rb index 486c465..6b3cf56 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[7.0].define(version: 2023_01_11_113139) do +ActiveRecord::Schema[7.0].define(version: 2023_02_17_084310) do create_table "donations", force: :cascade do |t| t.integer "user_id" t.integer "amount_sats" @@ -34,6 +34,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_11_113139) do t.index ["user_id"], name: "index_invitations_on_user_id" end + create_table "settings", force: :cascade do |t| + t.string "var", null: false + t.text "value" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["var"], name: "index_settings_on_var", unique: true + end + create_table "users", force: :cascade do |t| t.string "cn" t.string "ou" diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb index 3120d5b..f0e7edb 100644 --- a/spec/features/admin/dashboard_spec.rb +++ b/spec/features/admin/dashboard_spec.rb @@ -4,7 +4,9 @@ RSpec.describe 'Admin dashboard', type: :feature do let(:user) { create :user } before do - allow(user).to receive(:is_admin?).and_return(true) + allow(Devise::LDAP::Adapter).to receive(:get_ldap_param) + .with(user.cn, :admin).and_return(["true"]) + login_as user, :scope => :user end diff --git a/spec/features/admin/settings_spec.rb b/spec/features/admin/settings_spec.rb new file mode 100644 index 0000000..bd586c7 --- /dev/null +++ b/spec/features/admin/settings_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +RSpec.describe 'Admin/global settings', type: :feature do + let(:user) { create :user } + + before do + allow(Devise::LDAP::Adapter).to receive(:get_ldap_param) + .with(user.cn, :admin).and_return(["true"]) + + login_as user, :scope => :user + end + + scenario 'Update reserved usernames' do + visit admin_settings_registrations_path + expect(Setting.reserved_usernames).not_to include(['Kosmos', 'Kredits']) + + fill_in 'Reserved usernames', with: "Kosmos\nKredits" + click_button "Save" + expect(Setting.reserved_usernames).to eq(['Kosmos', 'Kredits']) + end +end diff --git a/spec/features/signup_spec.rb b/spec/features/signup_spec.rb index 8b730c2..9959661 100644 --- a/spec/features/signup_spec.rb +++ b/spec/features/signup_spec.rb @@ -71,6 +71,12 @@ RSpec.describe "Signup", type: :feature do fill_in "user_cn", with: "t" click_button "Continue" expect(page).to have_content("Username is too short") + fill_in "user_cn", with: "-tony" + click_button "Continue" + expect(page).to have_content("Username is invalid") + fill_in "user_cn", with: "$atoshi" + click_button "Continue" + expect(page).to have_content("Username is invalid") fill_in "user_cn", with: "jimmy" click_button "Continue" expect(page).to have_content("Username has already been taken") @@ -103,5 +109,11 @@ RSpec.describe "Signup", type: :feature do expect(page).to have_content("confirm your address") end end + + scenario "Reserved usernames" do + fill_in "user_cn", with: "accounts" + click_button "Continue" + expect(page).to have_content("Username has already been taken") + end end end
Reserved usernames
+ These usernames cannot be registered as accounts: +
+ One username per line +
+ <%= f.submit 'Save', class: "btn-md btn-blue w-full md:w-auto" %> +
LNDHub configuration present and wallet features enabled
LNDHub database configuration present and admin panel enabled