maquina 0.4.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +163 -179
- data/app/controllers/concerns/maquina/authenticate.rb +33 -13
- data/app/controllers/concerns/maquina/resourceful.rb +1 -1
- data/app/controllers/maquina/first_runs_controller.rb +43 -0
- data/app/controllers/maquina/sessions_controller.rb +22 -25
- data/app/helpers/maquina/application_helper.rb +3 -5
- data/app/models/concerns/maquina/searchable.rb +4 -0
- data/app/models/maquina/organization.rb +2 -0
- data/app/models/maquina/user.rb +2 -0
- data/app/views/layouts/maquina/sessions.html.erb +6 -4
- data/app/views/maquina/accept_invitations/new_view.rb +1 -1
- data/app/views/maquina/application/alert.rb +4 -4
- data/app/views/maquina/application/components/action_text_component.rb +1 -1
- data/app/views/maquina/application/components/checkbox_component.rb +1 -1
- data/app/views/maquina/application/components/date_component.rb +1 -1
- data/app/views/maquina/application/components/file_component.rb +1 -1
- data/app/views/maquina/application/components/input_component.rb +1 -1
- data/app/views/maquina/application/components/select_component.rb +1 -1
- data/app/views/maquina/application/components/text_area_component.rb +1 -1
- data/app/views/maquina/application/edit.rb +1 -1
- data/app/views/maquina/application/form.rb +1 -1
- data/app/views/maquina/application/index_header.rb +1 -1
- data/app/views/maquina/application/index_modal.rb +1 -1
- data/app/views/maquina/application/index_table.rb +1 -1
- data/app/views/maquina/application/new.rb +1 -1
- data/app/views/maquina/application/sessions_header.rb +5 -3
- data/app/views/maquina/application_view.rb +1 -1
- data/app/views/maquina/first_runs/form.rb +28 -0
- data/app/views/maquina/first_runs/show.html.erb +5 -0
- data/app/views/maquina/form.rb +44 -0
- data/app/views/maquina/navbar/menu.rb +1 -1
- data/app/views/maquina/navbar/menu_item_link.rb +1 -1
- data/app/views/maquina/navbar/mobile_button.rb +1 -1
- data/app/views/maquina/navbar/mobile_menu.rb +1 -1
- data/app/views/maquina/navbar/notification.rb +1 -1
- data/app/views/maquina/navbar/profile.rb +1 -1
- data/app/views/maquina/navbar/profile_button.rb +1 -1
- data/app/views/maquina/navbar/profile_menu.rb +1 -1
- data/app/views/maquina/navbar/profile_menu_item_link.rb +1 -1
- data/app/views/maquina/navbar/search.rb +1 -1
- data/app/views/maquina/navbar/title.rb +1 -1
- data/app/views/maquina/sessions/form.rb +3 -4
- data/app/views/maquina/sessions/new.html.erb +1 -4
- data/config/importmap.rb +7 -6
- data/config/locales/flash.es.yml +14 -5
- data/config/locales/forms.es.yml +13 -1
- data/config/locales/models.es.yml +1 -0
- data/config/locales/views.es.yml +8 -0
- data/config/routes.rb +1 -0
- data/lib/generators/maquina/install_stimulus_controllers/install_stimulus_controllers_generator.rb +23 -0
- data/lib/maquina/version.rb +1 -1
- data/lib/maquina.rb +2 -2
- metadata +31 -74
- data/app/views/maquina/sessions/create.turbo_stream.erb +0 -11
@@ -2,55 +2,52 @@
|
|
2
2
|
|
3
3
|
module Maquina
|
4
4
|
class SessionsController < ApplicationController
|
5
|
+
allow_unauthenticated_access only: [:new, :create]
|
6
|
+
|
5
7
|
layout "maquina/sessions"
|
6
8
|
|
9
|
+
before_action :ensure_user_exists, only: :new
|
10
|
+
|
7
11
|
def new
|
8
|
-
@
|
9
|
-
@return_to = nil if @return_to == "/" || @return_to == "%2F"
|
12
|
+
@user = Maquina::User.new
|
10
13
|
end
|
11
14
|
|
12
15
|
def create
|
13
|
-
|
14
|
-
|
15
|
-
@return_to = params.dig(:return_to)
|
16
|
-
result = create_session(user, @return_to)
|
17
|
-
return redirect_to(result, status: :see_other, format: :html) if result.present?
|
16
|
+
reset_session
|
18
17
|
|
19
|
-
|
20
|
-
flash.now.alert = t("flash.sessions.create.alert")
|
18
|
+
@user = Maquina::User.authenticate_by(email: params.dig(:email), password: params.dig(:password))
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
if @user.present?
|
21
|
+
active_session = start_new_session_for(@user)
|
22
|
+
if active_session.present?
|
23
|
+
return redirect_to(calculate_redirect_path(active_session), notice: t("flash.sessions.create.notice"), status: :see_other)
|
24
|
+
end
|
25
25
|
end
|
26
|
+
|
27
|
+
@user = Maquina::User.new(email: params.dig(:email))
|
28
|
+
flash.now.alert = t("flash.sessions.create.alert")
|
29
|
+
render :new, status: :unprocessable_entity
|
26
30
|
end
|
27
31
|
|
28
32
|
def destroy
|
29
|
-
|
33
|
+
reset_session
|
30
34
|
Maquina::Current.reset
|
31
35
|
|
32
36
|
flash.notice = t("flash.sessions.destroy.notice")
|
33
|
-
redirect_to main_app.
|
37
|
+
redirect_to main_app.sign_in_path, status: :see_other
|
34
38
|
end
|
35
39
|
|
36
40
|
private
|
37
41
|
|
42
|
+
def ensure_user_exists
|
43
|
+
redirect_to maquina.first_run_url if Maquina::User.none?
|
44
|
+
end
|
45
|
+
|
38
46
|
def calculate_redirect_path(active_session)
|
39
47
|
return maquina.new_multifactor_path if active_session.user.multifactor?
|
40
48
|
return active_session.return_url if active_session.return_url.present?
|
41
49
|
|
42
50
|
active_session.user.management? ? maquina.root_path : main_app.root_path
|
43
51
|
end
|
44
|
-
|
45
|
-
def create_session(user, return_to)
|
46
|
-
return nil if user.blank?
|
47
|
-
|
48
|
-
active_session = ActiveSession.create(user: user, user_agent: request.user_agent, remote_addr: request.remote_ip, return_url: return_to)
|
49
|
-
return nil if !active_session.persisted?
|
50
|
-
|
51
|
-
session["--active_session"] = active_session.id
|
52
|
-
flash.notice = t("flash.sessions.create.notice")
|
53
|
-
calculate_redirect_path(active_session)
|
54
|
-
end
|
55
52
|
end
|
56
53
|
end
|
@@ -2,12 +2,10 @@
|
|
2
2
|
|
3
3
|
module Maquina
|
4
4
|
module ApplicationHelper
|
5
|
-
def maquina_importmap_tags(entry_point = "application",
|
5
|
+
def maquina_importmap_tags(entry_point = "application", importmap: Maquina.configuration.importmap)
|
6
6
|
safe_join [
|
7
|
-
javascript_inline_importmap_tag(
|
8
|
-
javascript_importmap_module_preload_tags(
|
9
|
-
(javascript_importmap_shim_nonce_configuration_tag if shim),
|
10
|
-
(javascript_importmap_shim_tag if shim),
|
7
|
+
javascript_inline_importmap_tag(importmap.to_json(resolver: self)),
|
8
|
+
javascript_importmap_module_preload_tags(importmap),
|
11
9
|
javascript_import_module_tag(entry_point)
|
12
10
|
].compact, "\n"
|
13
11
|
end
|
data/app/models/maquina/user.rb
CHANGED
@@ -6,11 +6,13 @@ module Maquina
|
|
6
6
|
include Maquina::AuthenticateBy
|
7
7
|
include Maquina::Blockeable
|
8
8
|
include Maquina::Multifactor
|
9
|
+
# include Maquina::Searchable
|
9
10
|
|
10
11
|
PASSWORD_COMPLEXITY_REGEX = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#-=+])[A-Za-z\d@$!%*?&#-=+]{8,}\z/
|
11
12
|
has_secure_password
|
12
13
|
|
13
14
|
has_many :memberships, class_name: "Maquina::Membership", foreign_key: :maquina_user_id, inverse_of: :user
|
15
|
+
has_many :active_sessions, class_name: "Maquina::ActiveSession", foreign_key: :maquina_user_id, dependent: :destroy
|
14
16
|
|
15
17
|
validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
|
16
18
|
validates :password, format: {with: PASSWORD_COMPLEXITY_REGEX}, unless: ->(user) { user.password.blank? }
|
@@ -3,18 +3,20 @@
|
|
3
3
|
<head>
|
4
4
|
<title><%= t("application_name") %></title>
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
6
|
+
<meta name="apple-mobile-web-app-capable" content="yes">
|
6
7
|
<%= csrf_meta_tags %>
|
7
8
|
<%= csp_meta_tag %>
|
8
9
|
|
9
|
-
<%=
|
10
|
+
<%= yield :head %>
|
10
11
|
|
12
|
+
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
|
11
13
|
<%= maquina_importmap_tags %>
|
14
|
+
|
15
|
+
<%= turbo_refreshes_with method: :morph, scroll: :preserve %>
|
12
16
|
</head>
|
13
17
|
|
14
18
|
<body class="h-full">
|
15
|
-
<%=
|
16
|
-
<%= render Maquina::Application::Alert.new(flash) %>
|
17
|
-
<% end %>
|
19
|
+
<%= render Maquina::Application::Alert.new(flash) %>
|
18
20
|
|
19
21
|
<main class="container mx-auto">
|
20
22
|
<%= yield %>
|
@@ -12,7 +12,7 @@ module Maquina
|
|
12
12
|
@resource = resource
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
15
|
+
def view_template
|
16
16
|
div(class: "mt-8 sm:mx-auto sm:w-full sm:max-w-md") do
|
17
17
|
div(class: "bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10") do
|
18
18
|
@resource.new_record? ? invalid_invitation : build_form
|
@@ -6,17 +6,17 @@ module Maquina
|
|
6
6
|
include Maquina::ApplicationView
|
7
7
|
|
8
8
|
def initialize(flash)
|
9
|
-
notice = flash.notice
|
10
|
-
alert = flash.alert
|
9
|
+
notice = flash.notice || flash.now[:notice]
|
10
|
+
alert = flash.alert || flash.now[:alert]
|
11
11
|
|
12
12
|
@flash = notice || alert
|
13
13
|
@success = notice.present?
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def view_template
|
17
17
|
return if @flash.blank?
|
18
18
|
|
19
|
-
div(aria_live: "assertive", class: "pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-30", data: {controller: "alert"
|
19
|
+
div(aria_live: "assertive", class: "pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-30", data: {controller: "alert"}) do
|
20
20
|
div(class: "flex w-full flex-col items-center space-y-4 sm:items-end hidden", data: transition_attributes) do
|
21
21
|
div(class: "pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5") do
|
22
22
|
div(class: "p-4") do
|
@@ -6,7 +6,7 @@ module Maquina
|
|
6
6
|
include ApplicationView
|
7
7
|
register_element :turbo_frame
|
8
8
|
|
9
|
-
def
|
9
|
+
def view_template
|
10
10
|
div(data_controller: "modal", class: "modal", data_modal_backdrop_outlet: ".modal-backdrop") do
|
11
11
|
div(class: "hidden fixed inset-0 z-30 overflow-y-auto", aria_labelledby: "modal-title", role: "dialog",
|
12
12
|
aria_modal: "true", data_modal_target: "container") do
|
@@ -13,7 +13,7 @@ module Maquina
|
|
13
13
|
@pagination = pagination
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def view_template
|
17
17
|
div(class: "mt-8 flex flex-col") do
|
18
18
|
div(class: "-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8") do
|
19
19
|
div(class: "inline-block min-w-full py-2 align-middle md:px-6 lg:px-8") do
|
@@ -5,15 +5,17 @@ module Maquina
|
|
5
5
|
class SessionsHeader < Phlex::HTML
|
6
6
|
include Maquina::ApplicationView
|
7
7
|
include Phlex::DeferredRender
|
8
|
+
include Phlex::Rails::Helpers::Translate
|
8
9
|
|
9
|
-
def initialize(brand_icon:)
|
10
|
+
def initialize(brand_icon:, translation_key: "maquina.application.sessions_header")
|
10
11
|
@brand_icon = brand_icon
|
12
|
+
@translation_key = translation_key
|
11
13
|
end
|
12
14
|
|
13
|
-
def
|
15
|
+
def view_template
|
14
16
|
div(class: "sm:mx-auto sm:w-full sm:max-w-md") do
|
15
17
|
image_tag(@brand_icon, class: "mx-auto h-12 w-auto", alt: t("application_name"))
|
16
|
-
h2(class: "mt-6 text-center text-3xl font-bold tracking-tight text-skin-base") { t(".title") }
|
18
|
+
h2(class: "mt-6 text-center text-3xl font-bold tracking-tight text-skin-base") { t("#{@translation_key}.title") }
|
17
19
|
|
18
20
|
if @description.present?
|
19
21
|
p(class: "mt-2 text-center text-sm text-skin-dimmed") do
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Maquina
|
4
4
|
module ApplicationView
|
5
5
|
include Maquina::Engine.routes.url_helpers
|
6
|
-
include Phlex::Rails::Helpers::
|
6
|
+
include Phlex::Rails::Helpers::Translate
|
7
7
|
|
8
8
|
delegate :resource_class, :l, :default_url_options, :policy_class, :show_link, :allowed_to?, to: :helpers
|
9
9
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Maquina
|
2
|
+
module FirstRuns
|
3
|
+
class Form < Phlex::HTML
|
4
|
+
include Maquina::ApplicationView
|
5
|
+
include Maquina::Form
|
6
|
+
|
7
|
+
def initialize(resource)
|
8
|
+
@resource = resource
|
9
|
+
@scope = "first_runs"
|
10
|
+
end
|
11
|
+
|
12
|
+
def view_template
|
13
|
+
div(class: "mt-8 sm:mx-auto sm:w-full sm:max-w-md") do
|
14
|
+
div(class: "bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10") do
|
15
|
+
p(class: "text-center text-skin-muted") { t("maquina.application.first_runs_header.description") }
|
16
|
+
form_with(model: @resource, url: first_run_path, method: :post, class: "space-y-6") do |form|
|
17
|
+
text_field(form, field_name: :email, required: true)
|
18
|
+
password_field(form, field_name: :password, required: true)
|
19
|
+
div do
|
20
|
+
form.submit t("helpers.submit.first_runs.create"), class: "flex w-full justify-center button button-accented"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<div class="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
|
2
|
+
|
3
|
+
<%= render Maquina::Application::SessionsHeader.new(brand_icon: brand_icon, translation_key: "maquina.application.first_runs_header") %>
|
4
|
+
<%= render Maquina::FirstRuns::Form.new(@user) %>
|
5
|
+
</div>
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Maquina
|
2
|
+
module Form
|
3
|
+
include Phlex::Rails::Helpers::FormWith
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def text_field(form, field_name:, required: false, label_class: "block label", input_class: "w-full block input", **options)
|
8
|
+
div do
|
9
|
+
form.label field_name, class: label_class
|
10
|
+
div(class: "mt-1") do
|
11
|
+
form.text_field field_name, required: required, class: input_class,
|
12
|
+
**options.merge(field_attributes(field_name))
|
13
|
+
field_help(field_name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def password_field(form, field_name:, required: false, label_class: "block label", input_class: "w-full block input", **options)
|
19
|
+
div do
|
20
|
+
form.label field_name, class: label_class
|
21
|
+
div(class: "mt-1") do
|
22
|
+
form.password_field field_name, required: required, class: input_class,
|
23
|
+
**options.merge(field_attributes(field_name))
|
24
|
+
field_help(field_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def field_attributes(field_name)
|
30
|
+
@scope ||= ""
|
31
|
+
{
|
32
|
+
maxlength: t("helpers.maxlength.#{@scope}.#{field_name}", default: t("helpers.maxlength.default")),
|
33
|
+
placeholder: t("placeholder.#{@scope}.#{field_name}", default: "")
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def field_help(field_name)
|
38
|
+
help = t("help.#{@scope}.#{field_name}", default: "")
|
39
|
+
if help.present?
|
40
|
+
div(class: "mt-2 text-sm text-skin-dimmed") { help }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -5,7 +5,7 @@ module Maquina
|
|
5
5
|
class MobileButton < Phlex::HTML
|
6
6
|
include ApplicationView
|
7
7
|
|
8
|
-
def
|
8
|
+
def view_template(&block)
|
9
9
|
div class: "flex items-center lg:hidden" do
|
10
10
|
button type: "button", class: "mobile-button", "aria-controls": "mobile-menu", "aria-expanded": false, "data-action": "mobile-menu#toggle" do
|
11
11
|
span(class: "sr-only") { "Open main menu" }
|
@@ -5,7 +5,7 @@ module Maquina
|
|
5
5
|
class Profile < Phlex::HTML
|
6
6
|
include ApplicationView
|
7
7
|
|
8
|
-
def
|
8
|
+
def view_template
|
9
9
|
div class: "ml-4 relative flex-shrink-0", "data-controller": "popup-menu" do
|
10
10
|
render Maquina::Navbar::ProfileButton.new
|
11
11
|
render Maquina::Navbar::ProfileMenu.new
|
@@ -5,7 +5,7 @@ module Maquina
|
|
5
5
|
class ProfileButton < Phlex::HTML
|
6
6
|
include ApplicationView
|
7
7
|
|
8
|
-
def
|
8
|
+
def view_template
|
9
9
|
div do
|
10
10
|
button type: "button", class: "bg-white rounded-full hidden lg:flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", id: "user-menu-button", "aria-expanded": false, "aria-haspopup": true, "data-action": "popup-menu#toggleTransition" do
|
11
11
|
span(class: "sr-only") { "Open user menu" }
|
@@ -13,7 +13,7 @@ module Maquina
|
|
13
13
|
@query = query
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
16
|
+
def view_template
|
17
17
|
div class: "flex-1 flex items-center justify-center px-2 lg:ml-6 lg:justify-end" do
|
18
18
|
div class: "max-w-lg w-full lg:max-w-xs" do
|
19
19
|
form_with(url: @url, method: :get, data: {controller: "submit-form"}) do |form|
|
@@ -10,11 +10,10 @@ module Maquina
|
|
10
10
|
@resource = resource
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def view_template
|
14
14
|
div(class: "mt-8 sm:mx-auto sm:w-full sm:max-w-md") do
|
15
15
|
div(class: "bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10") do
|
16
|
-
form_with(url: sessions_path, method: :post, class: "space-y-6"
|
17
|
-
form.hidden_field :return_to, value: @resource.return_to
|
16
|
+
form_with(url: sessions_path, method: :post, class: "space-y-6") do |form|
|
18
17
|
div do
|
19
18
|
form.label t("form.sessions.email"), for: :email, class: "block label"
|
20
19
|
div(class: "mt-1") do
|
@@ -31,7 +30,7 @@ module Maquina
|
|
31
30
|
div do
|
32
31
|
end
|
33
32
|
div(class: "text-sm") do
|
34
|
-
a(href: "#", class: "link"
|
33
|
+
a(href: "#", class: "link") { t("form.sessions.forgot_password") }
|
35
34
|
end
|
36
35
|
end
|
37
36
|
div do
|
@@ -2,8 +2,5 @@
|
|
2
2
|
|
3
3
|
<%= render Maquina::Application::SessionsHeader.new(brand_icon: brand_icon) %>
|
4
4
|
|
5
|
-
<%=
|
6
|
-
<%= render Maquina::Sessions::Form.new(OpenStruct.new(params.slice(:email, :password, :return_to))) %>
|
7
|
-
<% end %>
|
8
|
-
|
5
|
+
<%= render Maquina::Sessions::Form.new(@user) %>
|
9
6
|
</div>
|
data/config/importmap.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
pin "application" # , to: "maquina/application.js", preload: true
|
4
|
+
|
3
5
|
# Stimulus & Turbo
|
4
|
-
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
5
|
-
pin "@hotwired/stimulus", to: "stimulus.
|
6
|
-
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
7
|
-
pin "stimulus-use", to: "https://ga.jspm.io/npm:stimulus-use@0.52.2/dist/index.js"
|
6
|
+
pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
7
|
+
pin "@hotwired/stimulus", to: "@hotwired--stimulus.js" # @3.2.2
|
8
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
|
9
|
+
pin "stimulus-use" # , to: "https://ga.jspm.io/npm:stimulus-use@0.52.2/dist/index.js"
|
8
10
|
|
9
11
|
# Maquina entrypoint
|
10
|
-
pin "application", to: "maquina/application.js", preload: true
|
11
12
|
|
12
|
-
pin_all_from Maquina::Engine.root.join("app/assets/javascripts/maquina/controllers"), under: "controllers", to: "maquina/controllers"
|
13
|
+
pin_all_from Maquina::Engine.root.join("app/assets/javascripts/maquina/controllers"), under: "controllers" # , to: "maquina/controllers"
|