From fbf692383581bf56af1718024ed4037ec35d90f0 Mon Sep 17 00:00:00 2001
From: david
Date: Sun, 22 Sep 2024 21:57:05 +0200
Subject: [PATCH] Add auth and ruby update
---
.ruby-version | 2 +-
Dockerfile | 2 +-
Gemfile | 3 +-
Gemfile.lock | 28 +++-
app/controllers/application_controller.rb | 28 +++-
app/controllers/backoffice_controller.rb | 6 +
app/controllers/checklists_controller.rb | 2 +
app/controllers/checks_controller.rb | 2 +
app/controllers/concerns/backoffice_menu.rb | 12 ++
app/controllers/home_controller.rb | 4 +
app/controllers/link_categories_controller.rb | 2 +
app/controllers/links_controller.rb | 2 +
app/controllers/rodauth_controller.rb | 37 +++++
app/helpers/backoffice_helper.rb | 2 +
app/helpers/public_helper.rb | 2 +
app/misc/rodauth_app.rb | 25 +++
app/misc/rodauth_main.rb | 154 ++++++++++++++++++
app/models/account.rb | 5 +
app/views/backoffice/show.html.erb | 24 +++
app/views/home/root.html.erb | 2 +
app/views/home/show.html.erb | 21 ---
app/views/layouts/_flash.html.erb | 2 +-
app/views/layouts/_navigation.html.erb | 21 ++-
app/views/layouts/_sidebar.html.erb | 27 +++
app/views/layouts/application.html.erb | 19 ++-
app/views/rodauth/_login_form.html.erb | 26 +++
app/views/rodauth/_login_form_footer.html.erb | 9 +
app/views/rodauth/change_password.html.erb | 28 ++++
app/views/rodauth/login.html.erb | 3 +
app/views/rodauth/logout.html.erb | 14 ++
app/views/rodauth/multi_phase_login.html.erb | 3 +
app/views/rodauth/profile.html.erb | 3 +
app/views/rodauth/remember.html.erb | 22 +++
config/initializers/rodauth.rb | 3 +
config/locales/activerecord.yml | 6 +
config/locales/i11yist.yml | 3 +-
config/routes.rb | 34 ++--
db/migrate/20240922172043_create_rodauth.rb | 45 +++++
db/schema.rb | 15 +-
db/seeds.rb | 4 +-
.../controllers/backoffice_controller_test.rb | 8 +
test/controllers/public_controller_test.rb | 8 +
test/fixtures/accounts.yml | 10 ++
43 files changed, 614 insertions(+), 64 deletions(-)
create mode 100644 app/controllers/backoffice_controller.rb
create mode 100644 app/controllers/concerns/backoffice_menu.rb
create mode 100644 app/controllers/rodauth_controller.rb
create mode 100644 app/helpers/backoffice_helper.rb
create mode 100644 app/helpers/public_helper.rb
create mode 100644 app/misc/rodauth_app.rb
create mode 100644 app/misc/rodauth_main.rb
create mode 100644 app/models/account.rb
create mode 100644 app/views/backoffice/show.html.erb
create mode 100644 app/views/home/root.html.erb
create mode 100644 app/views/rodauth/_login_form.html.erb
create mode 100644 app/views/rodauth/_login_form_footer.html.erb
create mode 100644 app/views/rodauth/change_password.html.erb
create mode 100644 app/views/rodauth/login.html.erb
create mode 100644 app/views/rodauth/logout.html.erb
create mode 100644 app/views/rodauth/multi_phase_login.html.erb
create mode 100644 app/views/rodauth/profile.html.erb
create mode 100644 app/views/rodauth/remember.html.erb
create mode 100644 config/initializers/rodauth.rb
create mode 100644 db/migrate/20240922172043_create_rodauth.rb
create mode 100644 test/controllers/backoffice_controller_test.rb
create mode 100644 test/controllers/public_controller_test.rb
create mode 100644 test/fixtures/accounts.yml
diff --git a/.ruby-version b/.ruby-version
index 6d5369b..f13c6f4 100644
--- a/.ruby-version
+++ b/.ruby-version
@@ -1 +1 @@
-ruby-3.3.4
+ruby-3.3.5
diff --git a/Dockerfile b/Dockerfile
index 949012b..64b137a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@ ARG UID=1000
ARG GID=1000
ARG APP_PORT=3000
ARG INSTALL_DIR=/${NAME}
-ARG RUBY_VERSION=3.3.4
+ARG RUBY_VERSION=3.3.5
FROM ruby:${RUBY_VERSION} AS development
ARG NAME
diff --git a/Gemfile b/Gemfile
index fb12547..cd7a44b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,7 +2,7 @@
source "https://rubygems.org"
-ruby "3.3.4"
+ruby "3.3.5"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.2"
@@ -58,6 +58,7 @@ gem "pagy", "~> 9.0"
gem "pandoc-ruby"
gem "prawn-markup"
gem "prawn-rails"
+gem "rodauth-rails"
gem "sablon"
gem "slim"
diff --git a/Gemfile.lock b/Gemfile.lock
index 975d209..551309d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -76,8 +76,12 @@ GEM
tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
+ after_commit_everywhere (1.4.0)
+ activerecord (>= 4.2)
+ activesupport
ast (2.4.2)
base64 (0.2.0)
+ bcrypt (3.1.20)
bigdecimal (3.1.8)
bindex (0.8.1)
bootsnap (1.18.3)
@@ -267,6 +271,21 @@ GEM
io-console (~> 0.5)
rexml (3.3.2)
strscan
+ roda (3.84.0)
+ rack
+ rodauth (2.36.0)
+ roda (>= 2.6.0)
+ sequel (>= 4)
+ rodauth-model (0.2.1)
+ rodauth (~> 2.0)
+ rodauth-rails (1.15.0)
+ bcrypt
+ railties (>= 5.0, < 8)
+ roda (~> 3.76)
+ rodauth (~> 2.36)
+ rodauth-model (~> 0.2)
+ sequel-activerecord_connection (~> 1.1)
+ tilt
rubocop (1.65.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
@@ -319,6 +338,12 @@ GEM
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
+ sequel (5.84.0)
+ bigdecimal
+ sequel-activerecord_connection (1.4.1)
+ activerecord (>= 5.0, < 8)
+ after_commit_everywhere (~> 1.1)
+ sequel (~> 5.38)
slim (5.2.1)
temple (~> 0.10.0)
tilt (>= 2.1.0)
@@ -396,6 +421,7 @@ DEPENDENCIES
prawn-rails
puma (>= 5.0)
rails (~> 7.2)
+ rodauth-rails
rubocop
rubocop-capybara
rubocop-rails
@@ -413,7 +439,7 @@ DEPENDENCIES
web-console
RUBY VERSION
- ruby 3.3.4p94
+ ruby 3.3.5p100
BUNDLED WITH
2.5.15
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e99f4ce..a94b0a0 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base
include Pagy::Backend
# allow_browser versions: :modern
+ helper_method :sidebar?
before_action :initialize_navbar
@@ -12,15 +13,26 @@ class ApplicationController < ActionController::Base
def initialize_navbar
return unless request.get?
- @navbar_items = [
- { label: "Dashboard", icon: :speedometer2, path: :root },
- { label: Report.model_name.human(count: 2), icon: :'journal-text', path: :reports },
- { label: Checklist.model_name.human(count: 2), icon: :'list-check', path: :checklists },
- { label: Check.model_name.human(count: 2), icon: :check2, path: :checks },
- { label: Link.model_name.human(count: 2), icon: :link, path: :links },
- { label: LinkCategory.model_name.human(count: 2), icon: :folder, path: :link_categories }
- ]
+ @navbar_items = if rodauth.logged_in?
+ [
+ { label: "Dashboard", icon: :speedometer2, path: :root },
+ { label: Report.model_name.human(count: 2), icon: :'journal-text', path: :reports },
+ { label: I18n.t("backoffice"), icon: :gear, path: :backoffice, active: %w[backoffice checklists checks links link_categories].include?(controller_name) },
+ { label: Account.model_name.human, icon: :person, path: profile_path }
+ ]
+ else
+ [ { label: "Login", icon: :'door-closed', path: rodauth.login_path, label_class: "text-info" } ]
+ end
@nav_path = controller_name
@search_url = nil
+ @sidebar_items = initialize_sidebar_items
+ end
+
+ def sidebar?
+ @sidebar_items && @sidebar_items.any?
+ end
+
+ def initialize_sidebar_items
+ []
end
end
diff --git a/app/controllers/backoffice_controller.rb b/app/controllers/backoffice_controller.rb
new file mode 100644
index 0000000..861e030
--- /dev/null
+++ b/app/controllers/backoffice_controller.rb
@@ -0,0 +1,6 @@
+class BackofficeController < ApplicationController
+ include BackofficeMenu
+
+ def show
+ end
+end
diff --git a/app/controllers/checklists_controller.rb b/app/controllers/checklists_controller.rb
index 89f1973..6243166 100644
--- a/app/controllers/checklists_controller.rb
+++ b/app/controllers/checklists_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class ChecklistsController < ApplicationController
+ include BackofficeMenu
+
before_action :set_checklist, only: %i[show edit update destroy]
# GET /checklists
diff --git a/app/controllers/checks_controller.rb b/app/controllers/checks_controller.rb
index 1a36575..fce6a44 100644
--- a/app/controllers/checks_controller.rb
+++ b/app/controllers/checks_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class ChecksController < ApplicationController
+ include BackofficeMenu
+
before_action :set_check, only: %i[show edit update destroy]
# GET /checks or /checks.json
diff --git a/app/controllers/concerns/backoffice_menu.rb b/app/controllers/concerns/backoffice_menu.rb
new file mode 100644
index 0000000..e2f72fb
--- /dev/null
+++ b/app/controllers/concerns/backoffice_menu.rb
@@ -0,0 +1,12 @@
+module BackofficeMenu
+ extend ActiveSupport::Concern
+
+ def initialize_sidebar_items
+ [
+ { label: "Einstellungen", icon: :sliders, path: :backoffice },
+ { label: Checklist.model_name.human(count: 2), icon: :'list-check', path: :checklists },
+ { label: Check.model_name.human(count: 2), icon: :check2, path: :checks },
+ { label: Link.model_name.human(count: 2), icon: :link, path: :links },
+ { label: LinkCategory.model_name.human(count: 2), icon: :folder, path: :link_categories } ]
+ end
+end
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 9ccfc38..f8ddff1 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -2,5 +2,9 @@
class HomeController < ApplicationController
def show
+ if rodauth.logged_in?
+ else
+ render :root
+ end
end
end
diff --git a/app/controllers/link_categories_controller.rb b/app/controllers/link_categories_controller.rb
index cbccdf0..44b4fdc 100644
--- a/app/controllers/link_categories_controller.rb
+++ b/app/controllers/link_categories_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class LinkCategoriesController < ApplicationController
+ include BackofficeMenu
+
before_action :set_link_category, only: %i[show edit update destroy]
# GET /link_categories
diff --git a/app/controllers/links_controller.rb b/app/controllers/links_controller.rb
index 05f502c..8ceb58b 100644
--- a/app/controllers/links_controller.rb
+++ b/app/controllers/links_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class LinksController < ApplicationController
+ include BackofficeMenu
+
before_action :set_link, only: %i[show edit update destroy]
# GET /links
diff --git a/app/controllers/rodauth_controller.rb b/app/controllers/rodauth_controller.rb
new file mode 100644
index 0000000..b477d29
--- /dev/null
+++ b/app/controllers/rodauth_controller.rb
@@ -0,0 +1,37 @@
+class RodauthController < ApplicationController
+ # Used by Rodauth for rendering views, CSRF protection, running any
+ # registered action callbacks and rescue handlers, instrumentation etc.
+
+ # Controller callbacks and rescue handlers will run around Rodauth endpoints.
+ # before_action :verify_captcha, only: :login, if: -> { request.post? }
+ # rescue_from("SomeError") { |exception| ... }
+
+ # Layout can be changed for all Rodauth pages or only certain pages.
+ # layout "authentication"
+ # layout -> do
+ # case rodauth.current_route
+ # when :login, :create_account, :verify_account, :verify_account_resend,
+ # :reset_password, :reset_password_request
+ # "authentication"
+ # else
+ # "application"
+ # end
+ # end
+ #
+ before_action do
+ # Fix encoding in rodauth views.
+ response.headers["Content-Type"] = "text/html; charset=utf-8" if request.format.html?
+ end
+
+ def profile
+ end
+
+ private
+ def initialize_sidebar_items
+ [
+ { label: "Profile", icon: :'person', path: profile_path, active: action_name == "profile" },
+ { label: "Passwort ändern", icon: :'lock', path: rodauth.change_password_path, active: action_name == "change_password" },
+ { label: "Logout", icon: :'box-arrow-right', path: rodauth.logout_path, active: action_name == "logout" }
+ ]
+ end
+end
diff --git a/app/helpers/backoffice_helper.rb b/app/helpers/backoffice_helper.rb
new file mode 100644
index 0000000..f715f71
--- /dev/null
+++ b/app/helpers/backoffice_helper.rb
@@ -0,0 +1,2 @@
+module BackofficeHelper
+end
diff --git a/app/helpers/public_helper.rb b/app/helpers/public_helper.rb
new file mode 100644
index 0000000..0d8e188
--- /dev/null
+++ b/app/helpers/public_helper.rb
@@ -0,0 +1,2 @@
+module PublicHelper
+end
diff --git a/app/misc/rodauth_app.rb b/app/misc/rodauth_app.rb
new file mode 100644
index 0000000..6372422
--- /dev/null
+++ b/app/misc/rodauth_app.rb
@@ -0,0 +1,25 @@
+class RodauthApp < Rodauth::Rails::App
+ # primary configuration
+ configure RodauthMain
+
+ # secondary configuration
+ # configure RodauthAdmin, :admin
+
+ route do |r|
+ rodauth.load_memory # autologin remembered users
+
+ r.rodauth # route rodauth requests
+
+ # ==> Authenticating requests
+ # Call `rodauth.require_account` for requests that you want to
+ # require authentication for. For example:
+ #
+ # # authenticate /dashboard/* and /account/* requests
+ # if r.path.start_with?("/dashboard") || r.path.start_with?("/account")
+ # rodauth.require_account
+ # end
+
+ # ==> Secondary configurations
+ # r.rodauth(:admin) # route admin rodauth requests
+ end
+end
diff --git a/app/misc/rodauth_main.rb b/app/misc/rodauth_main.rb
new file mode 100644
index 0000000..fcfdc17
--- /dev/null
+++ b/app/misc/rodauth_main.rb
@@ -0,0 +1,154 @@
+require "sequel/core"
+
+class RodauthMain < Rodauth::Rails::Auth
+ configure do
+ # List of authentication features that are loaded.
+ # enable :create_account, :verify_account, :verify_account_grace_period,
+ # :login, :logout, :remember,
+ # :reset_password, :change_password, :change_login, :verify_login_change,
+ # :close_account
+ enable :login, :logout, :remember, :change_password
+
+ # See the Rodauth documentation for the list of available config options:
+ # http://rodauth.jeremyevans.net/documentation.html
+
+ # ==> General
+ # Initialize Sequel and have it reuse Active Record's database connection.
+ db Sequel.sqlite(extensions: :activerecord_connection, keep_reference: false)
+ # Avoid DB query that checks accounts table schema at boot time.
+ convert_token_id_to_integer? { Account.columns_hash["id"].type == :integer }
+
+ # Change prefix of table and foreign key column names from default "account"
+ # accounts_table :users
+ # verify_account_table :user_verification_keys
+ # verify_login_change_table :user_login_change_keys
+ # reset_password_table :user_password_reset_keys
+ # remember_table :user_remember_keys
+
+ # The secret key used for hashing public-facing tokens for various features.
+ # Defaults to Rails `secret_key_base`, but you can use your own secret key.
+ # hmac_secret "9180872c271f678dc279aabf6610187b7a11f2d23054ce38ebc66980971e993a25f5613ba255956c9ee7bc4afe724eef16abc411a6e2a8642d53b85531685de1"
+
+ # Use path prefix for all routes.
+ # prefix "/auth"
+
+ # Specify the controller used for view rendering, CSRF, and callbacks.
+ rails_controller { RodauthController }
+
+ # Make built-in page titles accessible in your views via an instance variable.
+ title_instance_variable :@page_title
+
+ # Store account status in an integer column without foreign key constraint.
+ account_status_column :status
+
+ # Store password hash in a column instead of a separate table.
+ account_password_hash_column :password_hash
+
+ # Set password when creating account instead of when verifying.
+ # verify_account_set_password? false
+
+ # Change some default param keys.
+ login_param "email"
+ login_confirm_param "email-confirm"
+ # password_confirm_param "confirm_password"
+
+ # Redirect back to originally requested location after authentication.
+ # login_return_to_requested_location? true
+ # two_factor_auth_return_to_requested_location? true # if using MFA
+
+ # Autologin the user after they have reset their password.
+ # reset_password_autologin? true
+
+ # Delete the account record when the user has closed their account.
+ # delete_account_on_close? true
+
+ # Redirect to the app from login and registration pages if already logged in.
+ # already_logged_in { redirect login_redirect }
+
+ # ==> Emails
+ send_email do |email|
+ # queue email delivery on the mailer after the transaction commits
+ db.after_commit { email.deliver_later }
+ end
+
+ # ==> Flash
+ # Match flash keys with ones already used in the Rails app.
+ # flash_notice_key :success # default is :notice
+ # flash_error_key :error # default is :alert
+
+ # Override default flash messages.
+ # create_account_notice_flash "Your account has been created. Please verify your account by visiting the confirmation link sent to your email address."
+ # require_login_error_flash "Login is required for accessing this page"
+ # login_notice_flash nil
+
+ # ==> Validation
+ # Override default validation error messages.
+ # no_matching_login_message "user with this email address doesn't exist"
+ # already_an_account_with_this_login_message "user with this email address already exists"
+ # password_too_short_message { "needs to have at least #{password_minimum_length} characters" }
+ # login_does_not_meet_requirements_message { "invalid email#{", #{login_requirement_message}" if login_requirement_message}" }
+
+ # Passwords shorter than 8 characters are considered weak according to OWASP.
+ password_minimum_length 8
+ # bcrypt has a maximum input length of 72 bytes, truncating any extra bytes.
+ password_maximum_bytes 72
+
+ # Custom password complexity requirements (alternative to password_complexity feature).
+ # password_meets_requirements? do |password|
+ # super(password) && password_complex_enough?(password)
+ # end
+ # auth_class_eval do
+ # def password_complex_enough?(password)
+ # return true if password.match?(/\d/) && password.match?(/[^a-zA-Z\d]/)
+ # set_password_requirement_error_message(:password_simple, "requires one number and one special character")
+ # false
+ # end
+ # end
+
+ # ==> Remember Feature
+ # Remember all logged in users.
+ after_login { remember_login }
+
+ # Or only remember users that have ticked a "Remember Me" checkbox on login.
+ # after_login { remember_login if param_or_nil("remember") }
+
+ # Extend user's remember period when remembered via a cookie
+ extend_remember_deadline? true
+
+ # ==> Hooks
+ # Validate custom fields in the create account form.
+ # before_create_account do
+ # throw_error_status(422, "name", "must be present") if param("name").empty?
+ # end
+
+ # Perform additional actions after the account is created.
+ # after_create_account do
+ # Profile.create!(account_id: account_id, name: param("name"))
+ # end
+
+ # Do additional cleanup after the account is closed.
+ # after_close_account do
+ # Profile.find_by!(account_id: account_id).destroy
+ # end
+
+ # ==> Redirects
+ # Redirect to home page after logout.
+ logout_redirect "/"
+
+ # Redirect to wherever login redirects to after account verification.
+ # verify_account_redirect { login_redirect }
+
+ # Redirect to login page after password reset.
+ # reset_password_redirect { login_path }
+
+ # Ensure requiring login follows login route changes.
+ require_login_redirect { login_path }
+
+ # ==> Deadlines
+ # Change default deadlines for some actions.
+ # verify_account_grace_period 3.days.to_i
+ # reset_password_deadline_interval Hash[hours: 6]
+ # verify_login_change_deadline_interval Hash[days: 2]
+ # remember_deadline_interval Hash[days: 30]
+ end
+end
diff --git a/app/models/account.rb b/app/models/account.rb
new file mode 100644
index 0000000..ecaffb9
--- /dev/null
+++ b/app/models/account.rb
@@ -0,0 +1,5 @@
+class Account < ApplicationRecord
+ include Rodauth::Rails.model
+
+ enum :status, unverified: 1, verified: 2, closed: 3
+end
diff --git a/app/views/backoffice/show.html.erb b/app/views/backoffice/show.html.erb
new file mode 100644
index 0000000..936bdb1
--- /dev/null
+++ b/app/views/backoffice/show.html.erb
@@ -0,0 +1,24 @@
+Einstellungen
+
+Hier wird es irgendwann mal was einzustellen geben.
+
+
+<%= Checklist.count %>
+<%= link_to Checklist.model_name.human(count: Checklist.count), :checklists %>
+
+
+
+
+<%= Check.count %>
+<%= link_to Check.model_name.human(count: Check.count), :checks %>
+
+
+
+
+<%= Link.count %>
+<%= link_to Link.model_name.human(count: Link.count), :links %>
+
+
+
+ <%= link_to "Backup herunterladen", admin_backup_url, class: "btn btn-secondary", data: { turbo_prefetch: false, frame: "_top", turbo: false } %>
+
\ No newline at end of file
diff --git a/app/views/home/root.html.erb b/app/views/home/root.html.erb
new file mode 100644
index 0000000..f14a0d2
--- /dev/null
+++ b/app/views/home/root.html.erb
@@ -0,0 +1,2 @@
+Public#root
+Find me in app/views/public/root.html.erb
diff --git a/app/views/home/show.html.erb b/app/views/home/show.html.erb
index c72ee9d..117bbf5 100644
--- a/app/views/home/show.html.erb
+++ b/app/views/home/show.html.erb
@@ -7,25 +7,4 @@
<%= Report.count %>
<%= link_to Report.model_name.human(count: Report.count), :reports %>
-
-
-<%= Checklist.count %>
-<%= link_to Checklist.model_name.human(count: Checklist.count), :checklists %>
-
-
-
-<%= Check.count %>
-<%= link_to Check.model_name.human(count: Check.count), :checks %>
-
-
-
-
-<%= Link.count %>
-<%= link_to Link.model_name.human(count: Link.count), :links %>
-
-
-
-
- <%= link_to "Backup herunterladen", admin_backup_url, class: "btn btn-secondary", data: { turbo_prefetch: false, frame: "_top", turbo: false } %>
-
\ No newline at end of file
diff --git a/app/views/layouts/_flash.html.erb b/app/views/layouts/_flash.html.erb
index 486a703..d62a65c 100644
--- a/app/views/layouts/_flash.html.erb
+++ b/app/views/layouts/_flash.html.erb
@@ -1,5 +1,5 @@
<% if flash[:alert] || flash[:notice] %>
-