diff --git a/.github/workflows/brakeman-analysis.yml b/.github/workflows/brakeman-analysis.yml index c91f8355..da55b1cf 100644 --- a/.github/workflows/brakeman-analysis.yml +++ b/.github/workflows/brakeman-analysis.yml @@ -25,13 +25,13 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version: "3.2" + ruby-version: "3.4" - name: Setup Brakeman env: - BRAKEMAN_VERSION: "6.2.1" # SARIF support is provided in Brakeman version 4.10+ + BRAKEMAN_VERSION: "7.1.0" # SARIF support is provided in Brakeman version 4.10+ run: | - gem install brakeman --version $BRAKEMAN_VERSION + gem install brakeman --version $BRAKEMAN_VERSION --no-document # Execute Brakeman CLI and generate a SARIF output with the security issues identified during the analysis - name: Scan diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdd3284f..b164208d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,6 +20,7 @@ jobs: - "3.4" rails: - "7.2" + - "8.0" database: - mysql - postgresql @@ -86,7 +87,7 @@ jobs: run: bundle exec rspec - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 - if: matrix.rails == '7.2' && matrix.ruby == '3.4' + if: matrix.rails == '8.0' && matrix.ruby == '3.4' with: token: ${{ secrets.CODECOV_TOKEN }} slug: AlchemyCMS/alchemy-devise @@ -106,7 +107,7 @@ jobs: - name: Install Ruby and gems uses: ruby/setup-ruby@v1 with: - ruby-version: "3.1" + ruby-version: "3.4" bundler-cache: true - name: Lint Ruby files run: bundle exec standardrb diff --git a/Gemfile b/Gemfile index 54f2237b..517f7972 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source "https://rubygems.org" alchemy_branch = ENV.fetch("ALCHEMY_BRANCH", "main") gem "alchemy_cms", github: "AlchemyCMS/alchemy_cms", branch: alchemy_branch -rails_version = ENV.fetch("RAILS_VERSION", "7.2") +rails_version = ENV.fetch("RAILS_VERSION", "8.0") gem "rails", "~> #{rails_version}.0" gem "listen", "~> 3.8" gem "puma", "~> 7.0" diff --git a/app/assets/builds/alchemy-devise.css b/app/assets/builds/alchemy-devise.css index 9483b2a9..ba0bf5f5 100644 --- a/app/assets/builds/alchemy-devise.css +++ b/app/assets/builds/alchemy-devise.css @@ -1 +1 @@ -body.user_sessions #errors,body.user_sessions .message.info,body.passwords #errors,body.passwords .message.info{border-color:rgba(0,0,0,0)}body.user_sessions #errors,body.passwords #errors{margin-left:157px}.logo-box{width:250px;margin:0 10px 1.5em 200px}.logo-box svg{width:200px;height:auto}.login_signup_box{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}.login_signup_box alchemy-message{margin-left:var(--form-left-column-width)}.no-js .login_signup_box{display:none}.login_signup_box .message{margin-left:157px}.login_signup_box .submit{display:flex;align-items:center;justify-content:space-between;gap:var(--spacing-4);margin-left:var(--form-left-column-width);padding-left:var(--spacing-0)}.login_signup_box .submit>a{display:inline-flex;align-items:center;gap:var(--spacing-0)}.login_signup_box .submit>a:hover alchemy-icon{transform:translateX(-2px);transition:transform var(--transition-duration)} +body.user_sessions #errors,body.user_sessions .message.info,body.passwords #errors,body.passwords .message.info{border-color:rgba(0,0,0,0)}body.user_sessions #errors,body.passwords #errors{margin-left:157px}.logo-box{width:250px;margin:0 10px 1.5em 200px}.logo-box svg{width:200px;height:auto}.login_signup_box{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%)}.login_signup_box alchemy-message{width:300px;margin-left:var(--form-left-column-width)}.no-js .login_signup_box{display:none}.login_signup_box .message{margin-left:157px}.login_signup_box .submit{display:flex;align-items:center;justify-content:space-between;gap:var(--spacing-4);margin-left:var(--form-left-column-width);padding-left:var(--spacing-0)}.login_signup_box .submit.align-right{justify-content:end}.login_signup_box .submit>label{text-align:start}.login_signup_box .submit>label input[type=checkbox]{margin:0 var(--spacing-0) 0 0}.login_signup_box .submit>a{display:inline-flex;align-items:center;gap:var(--spacing-0)}.login_signup_box .submit>a:hover alchemy-icon{transform:translateX(-2px);transition:transform var(--transition-duration)} diff --git a/app/assets/stylesheets/alchemy-devise/login.scss b/app/assets/stylesheets/alchemy-devise/login.scss index abce7588..888dd9b6 100644 --- a/app/assets/stylesheets/alchemy-devise/login.scss +++ b/app/assets/stylesheets/alchemy-devise/login.scss @@ -27,6 +27,7 @@ body.passwords { transform: translate(-50%, -50%); alchemy-message { + width: 300px; margin-left: var(--form-left-column-width); } @@ -46,6 +47,18 @@ body.passwords { margin-left: var(--form-left-column-width); padding-left: var(--spacing-0); + &.align-right { + justify-content: end; + } + + > label { + text-align: start; + + input[type="checkbox"] { + margin: 0 var(--spacing-0) 0 0; + } + } + > a { display: inline-flex; align-items: center; diff --git a/app/controllers/alchemy/admin/users_controller.rb b/app/controllers/alchemy/admin/users_controller.rb index 1e228030..c82035cc 100644 --- a/app/controllers/alchemy/admin/users_controller.rb +++ b/app/controllers/alchemy/admin/users_controller.rb @@ -98,7 +98,7 @@ def signup_admin_or_redirect deliver_welcome_mail redirect_to admin_pages_path else - render :signup + render :signup, status: :unprocessable_entity end end diff --git a/app/models/alchemy/user.rb b/app/models/alchemy/user.rb index f7532182..91e385d3 100644 --- a/app/models/alchemy/user.rb +++ b/app/models/alchemy/user.rb @@ -25,7 +25,7 @@ class User < ActiveRecord::Base has_many :folded_pages - validates_uniqueness_of :login + validates :login, uniqueness: {case_sensitive: false}, presence: :login_required? validates_presence_of :alchemy_roles # Unlock all locked pages before destroy. @@ -140,6 +140,14 @@ def fullname(options = {}) alias_method :name, :fullname alias_method :alchemy_display_name, :fullname + def email_required? + ::Devise.authentication_keys.include?(:email) + end + + def login_required? + ::Devise.authentication_keys.include?(:login) + end + # Returns true if the last request not longer ago then the logged_in_time_out def logged_in? raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at) diff --git a/app/views/alchemy/admin/passwords/edit.html.erb b/app/views/alchemy/admin/passwords/edit.html.erb index f195e3a0..95acba13 100644 --- a/app/views/alchemy/admin/passwords/edit.html.erb +++ b/app/views/alchemy/admin/passwords/edit.html.erb @@ -18,8 +18,10 @@ <% end %> <%= alchemy_form_for resource, as: resource_name, url: admin_update_password_path, method: 'patch' do |f| %> <%= f.hidden_field :reset_password_token %> - <%= f.input :password, autofocus: true, label: Alchemy.t("New password") %> - <%= f.input :password_confirmation, label: Alchemy.t("Confirm new password") %> + <%= f.input :password, autofocus: true, label: Alchemy.t("New password"), + required: true, input_html: {autocomplete: "new-password"} %> + <%= f.input :password_confirmation, label: Alchemy.t("Confirm new password"), + required: true, input_html: {autocomplete: "new-password"} %>
<%= link_to alchemy.admin_login_path do %> <%= render_icon("arrow-left-s", size: "1x") %> diff --git a/app/views/alchemy/admin/passwords/new.html.erb b/app/views/alchemy/admin/passwords/new.html.erb index 08321f13..62a2e718 100644 --- a/app/views/alchemy/admin/passwords/new.html.erb +++ b/app/views/alchemy/admin/passwords/new.html.erb @@ -19,7 +19,11 @@ <%= alchemy_form_for :user, url: admin_reset_password_path, html: {method: 'post'} do |f| %> <%= f.input :email, autofocus: true, - input_html: {value: params[:email]} %> + required: true, + input_html: { + autocomplete: "email", + value: params[:email] + } %>
<%= link_to alchemy.admin_login_path do %> <%= render_icon("arrow-left-s", size: "1x") %> diff --git a/app/views/alchemy/admin/user_sessions/new.html.erb b/app/views/alchemy/admin/user_sessions/new.html.erb index 4fb24c70..82152edf 100644 --- a/app/views/alchemy/admin/user_sessions/new.html.erb +++ b/app/views/alchemy/admin/user_sessions/new.html.erb @@ -11,8 +11,9 @@

<%= Alchemy.t('welcome_please_identify_notice') %>

<% end %> <%= alchemy_form_for :user, url: {action: 'create'}, id: 'login', data: { turbo: false } do |f| %> - <%= f.input Devise.authentication_keys.first, autofocus: true %> - <%= f.input :password %> + <%= f.input Devise.authentication_keys.first, autofocus: true, required: true, + input_html: {autocomplete: Devise.authentication_keys.first == :email ? "email" : "username"} %> + <%= f.input :password, required: true, input_html: {autocomplete: "current-password"} %>
<%= link_to Alchemy.t('Forgot your password?'), admin_new_password_path %> diff --git a/app/views/alchemy/admin/users/_fields.html.erb b/app/views/alchemy/admin/users/_fields.html.erb index d895074b..0d94a45e 100644 --- a/app/views/alchemy/admin/users/_fields.html.erb +++ b/app/views/alchemy/admin/users/_fields.html.erb @@ -1,13 +1,15 @@ -<%= f.input :firstname %> -<%= f.input :lastname %> -<%= f.input :login, autofocus: true %> -<%= f.input :email %> -<%= f.input :language, - collection: translations_for_select, - include_blank: false, - input_html: {class: 'alchemy_selectbox'} %> -<%= f.input :password %> -<%= f.input :password_confirmation %> +<%= f.input :firstname, input_html: {autocomplete: "given-name"} %> +<%= f.input :lastname, input_html: {autocomplete: "family-name"} %> +<%= f.input :login, autofocus: true, required: @user.login_required?, input_html: {autocomplete: "username"} %> +<%= f.input :email, required: @user.email_required?, input_html: {autocomplete: "email"} %> +<% if Alchemy::I18n.available_locales.many? %> +
+ <%= f.label(:language) %> + <%= render Alchemy::Admin::LocaleSelect.new(f.field_name(:language)) %> +
+<% end %> +<%= f.input :password, required: while_signup?, input_html: {autocomplete: "new-password"} %> +<%= f.input :password_confirmation, required: while_signup?, input_html: {autocomplete: "new-password"} %> <% if can_update_role? %> <%= f.input :alchemy_roles, collection: @user_roles, @@ -27,4 +29,8 @@
<% end %> <%= f.input :send_credentials, as: 'boolean' %> -<%= f.submit Alchemy.t(:save) %> +
+ +
diff --git a/app/views/alchemy/admin/users/_resource_table.html.erb b/app/views/alchemy/admin/users/_resource_table.html.erb index 3bbbdc57..afb9504b 100644 --- a/app/views/alchemy/admin/users/_resource_table.html.erb +++ b/app/views/alchemy/admin/users/_resource_table.html.erb @@ -8,7 +8,7 @@ alchemy.edit_admin_user_path(user), { title: Alchemy.t(:edit_user), overflow: true, - size: "430x560" + size: "430x500" }, title: Alchemy.t(:edit_user) %> <% else %> @@ -28,7 +28,7 @@ <%= user.human_roles_string %> <% end %> <% table.delete_button tooltip: Alchemy.t(:delete_user), confirm_message: Alchemy.t(:confirm_to_delete_user) %> - <% table.edit_button tooltip: Alchemy.t(:edit_user), dialog_size: "430x560" %> + <% table.edit_button tooltip: Alchemy.t(:edit_user), dialog_size: "430x500" %> <% end %> <%= paginate @users, theme: "alchemy" %> diff --git a/app/views/alchemy/admin/users/_table.html.erb b/app/views/alchemy/admin/users/_table.html.erb deleted file mode 100644 index 31cad890..00000000 --- a/app/views/alchemy/admin/users/_table.html.erb +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - <%= render partial: "user", collection: @users %> - -
- <%= sort_link @query, :firstname, hide_indicator: true %> - - <%= sort_link @query, :lastname, hide_indicator: true %> - <%= Alchemy::User.human_attribute_name('language') %> - <%= sort_link @query, :last_sign_in_at, hide_indicator: true %> - <%= Alchemy::User.human_attribute_name('roles') %>
diff --git a/app/views/alchemy/admin/users/_user.html.erb b/app/views/alchemy/admin/users/_user.html.erb deleted file mode 100644 index 4ed75e0a..00000000 --- a/app/views/alchemy/admin/users/_user.html.erb +++ /dev/null @@ -1,41 +0,0 @@ - - - <%= render_icon(:user, style: user.logged_in? ? 'solid' : 'regular') %> - - - <% if can?(:edit, user) %> - <%= link_to_dialog user.login, - alchemy.edit_admin_user_path(user), { - title: Alchemy.t(:edit_user), - overflow: true, - size: '430x560' - }, - title: Alchemy.t(:edit_user) %> - <% end %> - - <%= user.firstname %> - <%= user.lastname %> - <%= user.email %> - <%= Alchemy.t(user.language, scope: 'translations', default: Alchemy.t(:unknown)) %> - <%= user.last_sign_in_at.present? ? l(user.last_sign_in_at, format: 'alchemy.default'.to_sym) : Alchemy.t(:unknown) %> - <%= user.human_roles_string %> - - <% if can?(:destroy, user) %> - <%= delete_button alchemy.admin_user_path(user), { - message: Alchemy.t(:confirm_to_delete_user), - title: Alchemy.t(:delete_user), - icon: :minus - }, - title: Alchemy.t(:delete_user) %> - <% end %> - <% if can?(:edit, user) %> - <%= link_to_dialog render_icon(:edit), - alchemy.edit_admin_user_path(user), { - title: Alchemy.t(:edit_user), - overflow: true, - size: '430x560' - }, - title: Alchemy.t(:edit_user) %> - <% end %> - - diff --git a/app/views/alchemy/admin/users/index.html.erb b/app/views/alchemy/admin/users/index.html.erb index 5b65124a..b445a09a 100644 --- a/app/views/alchemy/admin/users/index.html.erb +++ b/app/views/alchemy/admin/users/index.html.erb @@ -12,7 +12,7 @@ tooltip_placement: "top-start", dialog_options: { title: Alchemy.t(:create_user), - size: "430x560" + size: "430x500" }, if_permitted_to: [:create, Alchemy::User] ) %> @@ -22,12 +22,7 @@
<% if @users.any? %> <%= render "alchemy/admin/resources/table_header" %> - - <% if Alchemy::Admin.const_defined?(:Resource) %> - <%= render "resource_table" %> - <% else %> - <%= render "table" %> - <% end %> + <%= render "resource_table" %> <%= paginate @users, theme: 'alchemy' %> diff --git a/app/views/alchemy/admin/users/signup.html.erb b/app/views/alchemy/admin/users/signup.html.erb index b87dc83d..15247c88 100644 --- a/app/views/alchemy/admin/users/signup.html.erb +++ b/app/views/alchemy/admin/users/signup.html.erb @@ -3,9 +3,11 @@ <% end %>