bullet_train 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/controllers/concerns/registrations/controller_base.rb +39 -0
- data/app/controllers/concerns/sessions/controller_base.rb +15 -0
- data/app/controllers/registrations_controller.rb +9 -0
- data/app/controllers/sessions_controller.rb +3 -0
- data/app/helpers/account/buttons_helper.rb +12 -0
- data/app/helpers/account/dates_helper.rb +38 -0
- data/app/helpers/account/forms_helper.rb +67 -0
- data/app/helpers/account/locale_helper.rb +51 -0
- data/app/helpers/account/markdown_helper.rb +5 -0
- data/app/helpers/account/role_helper.rb +5 -0
- data/app/helpers/attributes_helper.rb +32 -0
- data/app/helpers/email_helper.rb +7 -0
- data/app/helpers/images_helper.rb +7 -0
- data/app/mailers/concerns/mailers/base.rb +16 -0
- data/app/mailers/devise_mailer.rb +10 -0
- data/app/mailers/user_mailer.rb +24 -0
- data/app/views/devise/confirmations/new.html.erb +16 -0
- data/app/views/devise/mailer/confirmation_instructions.html.erb +5 -0
- data/app/views/devise/mailer/password_change.html.erb +3 -0
- data/app/views/devise/mailer/reset_password_instructions.html.erb +30 -0
- data/app/views/devise/mailer/unlock_instructions.html.erb +7 -0
- data/app/views/devise/passwords/edit.html.erb +17 -0
- data/app/views/devise/passwords/new.html.erb +20 -0
- data/app/views/devise/registrations/_two_factor.html.erb +42 -0
- data/app/views/devise/registrations/edit.html.erb +43 -0
- data/app/views/devise/registrations/new.html.erb +30 -0
- data/app/views/devise/sessions/new.html.erb +59 -0
- data/app/views/devise/sessions/pre_otp.js.erb +11 -0
- data/app/views/devise/shared/_links.html.erb +21 -0
- data/app/views/devise/shared/_oauth.html.erb +9 -0
- data/app/views/devise/unlocks/new.html.erb +16 -0
- data/config/locales/en/devise.en.yml +110 -0
- data/lib/bullet_train/version.rb +1 -1
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edaadd5d50965bf9b15be0e2c69ead2e4180ab34a1cba61035546546dd00bb87
|
4
|
+
data.tar.gz: 0b41c024ef616cd3a33b114da7dca2f0c0161713003269569a210e2937814004
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c599a9871fdf7b746ac62452489554e271c62a8384ca6f747bb512f0334bdd22d6ec78e9fb6218d2a78a17f26afe343a78e70aff0fd4c9d7d5992f8a2a322fe
|
7
|
+
data.tar.gz: ca2f4b81605161e94f3a704d63bdcbcbced15228688f2ee454e1db9876d1d198e56e9ba9f3b5f34f2c7b1841fa5ac655bba5dedd1e5bf6cd7016a6d1c19414cf
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Registrations::ControllerBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
include do
|
5
|
+
def new
|
6
|
+
if invitation_only?
|
7
|
+
unless session[:invitation_uuid] || session[:invitation_key]
|
8
|
+
return redirect_to root_path
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# do all the regular devise stuff.
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
# do all the regular devise stuff first.
|
18
|
+
super
|
19
|
+
|
20
|
+
# if current_user is defined, that means they were successful registering.
|
21
|
+
if current_user
|
22
|
+
|
23
|
+
# TODO i think this might be redundant. we've added a hook into `session["user_return_to"]` in the
|
24
|
+
# `invitations#accept` action and that might be enough to get them where they're supposed to be after
|
25
|
+
# either creating a new account or signing into an existing account.
|
26
|
+
handle_outstanding_invitation
|
27
|
+
|
28
|
+
# if the user doesn't have a team at this point, create one.
|
29
|
+
unless current_user.teams.any?
|
30
|
+
current_user.create_default_team
|
31
|
+
end
|
32
|
+
|
33
|
+
# send the welcome email.
|
34
|
+
current_user.send_welcome_email unless current_user.email_is_oauth_placeholder?
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Sessions::ControllerBase
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
include do
|
5
|
+
def pre_otp
|
6
|
+
if (@email = params["user"]["email"].downcase.strip.presence)
|
7
|
+
@user = User.find_by(email: @email)
|
8
|
+
end
|
9
|
+
|
10
|
+
respond_to do |format|
|
11
|
+
format.js
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# i really, really wanted this controller in a namespace, but devise doesn't
|
2
|
+
# appear to support it. instead, i got the following error:
|
3
|
+
#
|
4
|
+
# 'Authentication::RegistrationsController' is not a supported controller name.
|
5
|
+
# This can lead to potential routing problems.
|
6
|
+
|
7
|
+
class RegistrationsController < Devise::RegistrationsController
|
8
|
+
include Registrations::ControllerBase
|
9
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Account::DatesHelper
|
2
|
+
# e.g. October 11, 2018
|
3
|
+
def display_date(timestamp)
|
4
|
+
return nil unless timestamp
|
5
|
+
if local_time(timestamp).year == local_time(Time.now).year
|
6
|
+
local_time(timestamp).strftime("%B %-d")
|
7
|
+
else
|
8
|
+
local_time(timestamp).strftime("%B %-d, %Y")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# e.g. October 11, 2018 at 4:22 PM
|
13
|
+
# e.g. Yesterday at 2:12 PM
|
14
|
+
# e.g. April 24 at 7:39 AM
|
15
|
+
def display_date_and_time(timestamp)
|
16
|
+
return nil unless timestamp
|
17
|
+
|
18
|
+
# today?
|
19
|
+
if local_time(timestamp).to_date == local_time(Time.now).to_date
|
20
|
+
"Today at #{display_time(timestamp)}"
|
21
|
+
# yesterday?
|
22
|
+
elsif (local_time(timestamp).to_date) == (local_time(Time.now).to_date - 1.day)
|
23
|
+
"Yesterday at #{display_time(timestamp)}"
|
24
|
+
else
|
25
|
+
"#{display_date(timestamp)} at #{display_time(timestamp)}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# e.g. 4:22 PM
|
30
|
+
def display_time(timestamp)
|
31
|
+
local_time(timestamp).strftime("%l:%M %p")
|
32
|
+
end
|
33
|
+
|
34
|
+
def local_time(time)
|
35
|
+
return time if current_user.time_zone.nil?
|
36
|
+
time.in_time_zone(current_user.time_zone)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Account::FormsHelper
|
2
|
+
PRESENCE_VALIDATORS = [ActiveRecord::Validations::PresenceValidator, ActiveModel::Validations::PresenceValidator]
|
3
|
+
|
4
|
+
def presence_validated?(object, attribute)
|
5
|
+
validators = object.class.validators
|
6
|
+
validators.select! do |validator|
|
7
|
+
PRESENCE_VALIDATORS.include?(validator.class) && validator.attributes.include?(attribute)
|
8
|
+
end
|
9
|
+
validators.any?
|
10
|
+
end
|
11
|
+
|
12
|
+
def flush_content_for(name)
|
13
|
+
content_for name, flush: true do
|
14
|
+
""
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def options_with_labels(options, namespace)
|
19
|
+
hash = {}
|
20
|
+
options.each do |option|
|
21
|
+
hash[option] = t([namespace, option].join("."))
|
22
|
+
end
|
23
|
+
hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def if_present(string)
|
27
|
+
string.present? ? string : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def id_for(form, method)
|
31
|
+
[form.object.class.name, form.index, method].compact.join("_").underscore
|
32
|
+
end
|
33
|
+
|
34
|
+
def model_key(form)
|
35
|
+
form.object.class.name.pluralize.underscore
|
36
|
+
end
|
37
|
+
|
38
|
+
def labels_for(form, method)
|
39
|
+
keys = [:placeholder, :label, :help, :options_help]
|
40
|
+
path = [model_key(form), (current_fields_namespace || :fields), method].compact
|
41
|
+
Struct.new(*keys).new(*keys.map { |key| t((path + [key]).join("."), default: "").presence })
|
42
|
+
end
|
43
|
+
|
44
|
+
def options_for(form, method)
|
45
|
+
# e.g. "scaffolding/completely_concrete/tangible_things.fields.text_area_value.options"
|
46
|
+
path = [model_key(form), (current_fields_namespace || :fields), method, :options]
|
47
|
+
t(path.compact.join("."))
|
48
|
+
end
|
49
|
+
|
50
|
+
def legacy_label_for(form, method)
|
51
|
+
# e.g. 'scaffolding/things.labels.name'
|
52
|
+
key = "#{model_key(form)}.labels.#{method}"
|
53
|
+
# e.g. 'scaffolding/things.labels.name' or 'scaffolding.things.labels.name' or nil
|
54
|
+
t(key, default: "").presence || t(key.tr("/", "."), default: "").presence
|
55
|
+
end
|
56
|
+
|
57
|
+
def within_fields_namespace(namespace)
|
58
|
+
@fields_namespaces ||= []
|
59
|
+
@fields_namespaces << namespace
|
60
|
+
yield
|
61
|
+
@fields_namespaces.pop
|
62
|
+
end
|
63
|
+
|
64
|
+
def current_fields_namespace
|
65
|
+
@fields_namespaces&.last
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Account::LocaleHelper
|
2
|
+
def current_locale
|
3
|
+
current_user.locale || current_team.locale || "en"
|
4
|
+
end
|
5
|
+
|
6
|
+
# as of now, we only calculate a possessive version of nouns in english.
|
7
|
+
# if you're aware of another language where we can do this, please don't hesitate to reach out!
|
8
|
+
def possessive_string(string)
|
9
|
+
[:en].include?(I18n.locale) ? string.possessive : string
|
10
|
+
end
|
11
|
+
|
12
|
+
def model_locales(model)
|
13
|
+
name = model.label_string.presence
|
14
|
+
return {} unless name
|
15
|
+
|
16
|
+
hash = {}
|
17
|
+
prefix = model.class.name.split("::").last.underscore
|
18
|
+
hash[:"#{prefix}_name"] = name
|
19
|
+
hash[:"#{prefix.pluralize}_possessive"] = possessive_string(name)
|
20
|
+
|
21
|
+
hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def models_locales(*models)
|
25
|
+
hash = {}
|
26
|
+
models.compact.each do |model|
|
27
|
+
hash.merge! model_locales(model)
|
28
|
+
end
|
29
|
+
hash
|
30
|
+
end
|
31
|
+
|
32
|
+
# this is a bit scary, no?
|
33
|
+
def account_controller?
|
34
|
+
controller.class.name.match(/^Account::/)
|
35
|
+
end
|
36
|
+
|
37
|
+
def t(key, options = {})
|
38
|
+
if account_controller?
|
39
|
+
# give preference to the options they've passed in.
|
40
|
+
options = models_locales(@child_object, @parent_object).merge(options)
|
41
|
+
end
|
42
|
+
super(key, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
# like 't', but if the key isn't found, it returns nil.
|
46
|
+
def ot(key, options = {})
|
47
|
+
t(key, options)
|
48
|
+
rescue I18n::MissingTranslationData => _
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module AttributesHelper
|
2
|
+
def current_attributes_object
|
3
|
+
@_attributes_helper_objects ? @_attributes_helper_objects.last : nil
|
4
|
+
end
|
5
|
+
|
6
|
+
def current_attributes_strategy
|
7
|
+
@_attributes_helper_strategies ? @_attributes_helper_strategies.last : nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_attribute_settings(options)
|
11
|
+
@_attributes_helper_objects ||= []
|
12
|
+
@_attributes_helper_strategies ||= []
|
13
|
+
|
14
|
+
if options[:object]
|
15
|
+
@_attributes_helper_objects << options[:object]
|
16
|
+
end
|
17
|
+
|
18
|
+
if options[:strategy]
|
19
|
+
@_attributes_helper_strategies << options[:strategy]
|
20
|
+
end
|
21
|
+
|
22
|
+
yield
|
23
|
+
|
24
|
+
if options[:strategy]
|
25
|
+
@_attributes_helper_strategies.pop
|
26
|
+
end
|
27
|
+
|
28
|
+
if options[:object]
|
29
|
+
@_attributes_helper_objects.pop
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
module EmailHelper
|
2
|
+
def email_image_tag(image, **options)
|
3
|
+
image_underscore = image.tr("-", "_")
|
4
|
+
attachments.inline[image_underscore] = File.read(Rails.root.join("app/javascript/images/#{image}"))
|
5
|
+
image_tag attachments.inline[image_underscore].url, **options
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Mailers::Base
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
default from: "#{I18n.t("application.name")} <#{I18n.t("application.support_email")}>"
|
6
|
+
layout "mailer"
|
7
|
+
|
8
|
+
helper :email
|
9
|
+
helper :application
|
10
|
+
helper :images
|
11
|
+
helper "account/teams"
|
12
|
+
helper "account/users"
|
13
|
+
helper "account/locale"
|
14
|
+
helper "fields/trix_editor"
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class UserMailer < ApplicationMailer
|
2
|
+
def welcome(user)
|
3
|
+
@user = user
|
4
|
+
@cta_url = account_dashboard_url
|
5
|
+
@values = {
|
6
|
+
# are there any substitution values you want to include?
|
7
|
+
}
|
8
|
+
mail(to: @user.email, subject: I18n.t("user_mailer.welcome.subject", **@values))
|
9
|
+
end
|
10
|
+
|
11
|
+
# technically not a 'user' email, but they'll be a user soon.
|
12
|
+
# didn't seem worth creating an entirely new mailer for.
|
13
|
+
def invited(uuid)
|
14
|
+
@invitation = Invitation.find_by_uuid uuid
|
15
|
+
return if @invitation.nil?
|
16
|
+
@cta_url = accept_account_invitation_url(@invitation.uuid)
|
17
|
+
@values = {
|
18
|
+
# Just in case the inviting user has been removed from the team...
|
19
|
+
inviter_name: @invitation.from_membership&.user&.full_name || @invitation.from_membership.name,
|
20
|
+
team_name: @invitation.team.name,
|
21
|
+
}
|
22
|
+
mail(to: @invitation.email, subject: I18n.t("user_mailer.invited.subject", **@values))
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<h2><%= t('device.headers.resend_confirm') %></h2>
|
2
|
+
|
3
|
+
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
|
4
|
+
<%= devise_error_messages! %>
|
5
|
+
|
6
|
+
<div class="field">
|
7
|
+
<%= f.label :email %><br />
|
8
|
+
<%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="actions">
|
12
|
+
<%= f.submit t('devise.buttons.resend_confirm') %>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<%= render "devise/shared/links" %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<p><%= t('.hello', email: @resource.first_name || @resource.email) %></p>
|
2
|
+
|
3
|
+
<p><%= t('.requested_change') %></p>
|
4
|
+
|
5
|
+
<!-- Action -->
|
6
|
+
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
|
7
|
+
<tr>
|
8
|
+
<td align="center">
|
9
|
+
<!-- Border based button https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
|
10
|
+
<table width="100%" border="0" cellspacing="0" cellpadding="0">
|
11
|
+
<tr>
|
12
|
+
<td align="center">
|
13
|
+
<table border="0" cellspacing="0" cellpadding="0">
|
14
|
+
<tr>
|
15
|
+
<td>
|
16
|
+
<%= link_to t('.change_password'), edit_password_url(@resource, reset_password_token: @token), class: 'button button--', target: '_blank' %>
|
17
|
+
</td>
|
18
|
+
</tr>
|
19
|
+
</table>
|
20
|
+
</td>
|
21
|
+
</tr>
|
22
|
+
</table>
|
23
|
+
</td>
|
24
|
+
</tr>
|
25
|
+
</table>
|
26
|
+
|
27
|
+
<p><%= t('.ignore') %></p>
|
28
|
+
<p><%= t('.access_change') %></p>
|
29
|
+
|
30
|
+
<%= t('user_mailer.signature.html', support_email: t('application.support_email')) %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% @title = t('devise.headers.change_password') %>
|
2
|
+
<%= render 'account/shared/workflow/box' do |p| %>
|
3
|
+
<% p.content_for :title, @title %>
|
4
|
+
<% p.content_for :body do %>
|
5
|
+
<% within_fields_namespace(:update_self) do %>
|
6
|
+
<%= form_for(resource, as: resource_name, url: password_path(resource_name), class: 'form', html: { method: :put }) do |f| %>
|
7
|
+
<%= f.hidden_field :reset_password_token %>
|
8
|
+
<%= render 'account/shared/forms/errors', form: f, attributes: [:reset_password_token] %>
|
9
|
+
<%= render 'shared/fields/password_field', form: f, method: :password, options: {autofocus: true, autocomplete: "off"} %>
|
10
|
+
<%= render 'shared/fields/password_field', form: f, method: :password_confirmation, options: {autocomplete: "off"} %>
|
11
|
+
<div class="buttons">
|
12
|
+
<%= f.submit t('devise.buttons.change_password'), class: "button" %>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
15
|
+
<% end %>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<%= render 'account/shared/workflow/box' do |p| %>
|
2
|
+
<% p.content_for :title, t('devise.titles.reset_password') %>
|
3
|
+
<% p.content_for :body do %>
|
4
|
+
<% within_fields_namespace(:sign_up) do %>
|
5
|
+
<%= form_for resource, as: resource_name, url: password_path(resource_name), html: {method: :post, class: 'form'} do |f| %>
|
6
|
+
<%= render 'account/shared/forms/errors', form: f %>
|
7
|
+
|
8
|
+
<%= render 'shared/fields/email_field', form: f, method: :email, options: {autofocus: true} do %>
|
9
|
+
<% if show_sign_up_options? %>
|
10
|
+
<% content_for :help do %>
|
11
|
+
<%= link_to t('devise.links.account'), new_user_registration_path %>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<%= f.submit t('devise.buttons.reset_password'), class: 'button full' %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
20
|
+
<% end %>
|
@@ -0,0 +1,42 @@
|
|
1
|
+
<%= render 'account/shared/box', divider: @backup_codes do |p| %>
|
2
|
+
<% p.content_for :title, t("users.edit.two_factor.header") %>
|
3
|
+
<% p.content_for :description, t("users.edit.two_factor.description_#{@user.otp_required_for_login? ? 'enabled' : 'disabled'}") %>
|
4
|
+
<% p.content_for :body do %>
|
5
|
+
<% if current_user.otp_required_for_login? %>
|
6
|
+
<% if @backup_codes %>
|
7
|
+
|
8
|
+
<%= render 'account/shared/alert' do %>
|
9
|
+
<%= t('users.edit.two_factor.warning').html_safe %>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<p><%= t('users.edit.two_factor.instructions').html_safe %></p>
|
13
|
+
|
14
|
+
<center class="py-4">
|
15
|
+
<%= current_user.otp_qr_code.as_svg(
|
16
|
+
offset: 0,
|
17
|
+
color: '000',
|
18
|
+
shape_rendering: 'crispEdges',
|
19
|
+
module_size: 4,
|
20
|
+
standalone: true
|
21
|
+
).html_safe %>
|
22
|
+
</center>
|
23
|
+
|
24
|
+
<p><%= t('users.edit.two_factor.recovery_codes').html_safe %></p>
|
25
|
+
|
26
|
+
<center>
|
27
|
+
<% @backup_codes.each do |code| %>
|
28
|
+
<p><code><%= code %></code></p>
|
29
|
+
<% end %>
|
30
|
+
</center>
|
31
|
+
|
32
|
+
<% end %>
|
33
|
+
<% end %>
|
34
|
+
<% end %>
|
35
|
+
<% p.content_for :actions do %>
|
36
|
+
<% if current_user.otp_required_for_login? %>
|
37
|
+
<%= link_to t('users.edit.two_factor.buttons.disable'), account_two_factor_path, method: :delete, remote: true, class: "button" %>
|
38
|
+
<% else %>
|
39
|
+
<%= link_to t('users.edit.two_factor.buttons.enable'), account_two_factor_path, method: :post, remote: true, class: "button" %>
|
40
|
+
<% end %>
|
41
|
+
<% end %>
|
42
|
+
<% end %>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<h2><%= t('devise.headers.edit_registration', resource_name: resource_name.to_s.humanize) %></h2>
|
2
|
+
|
3
|
+
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
|
4
|
+
<%= devise_error_messages! %>
|
5
|
+
|
6
|
+
<div class="field">
|
7
|
+
<%= f.label :email %><br />
|
8
|
+
<%= f.email_field :email, autofocus: true %>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
|
12
|
+
<div><%= t('devise.labels.wait_confirm', unconfirmed_email: resource.unconfirmed_email) %></div>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<div class="field">
|
16
|
+
<%= f.label :password %> <i><%= t('devise.hints.leave_blank') %></i><br />
|
17
|
+
<%= f.password_field :password, autocomplete: "off" %>
|
18
|
+
<% if @minimum_password_length %>
|
19
|
+
<br />
|
20
|
+
<em><%= t('devise.hints.password_length', length: @minimum_password_length) %></em>
|
21
|
+
<% end %>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<div class="field">
|
25
|
+
<%= f.label :password_confirmation %><br />
|
26
|
+
<%= f.password_field :password_confirmation, autocomplete: "off" %>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div class="field">
|
30
|
+
<%= f.label :current_password %> <i><%= t('devise.hints.need_password') %></i><br />
|
31
|
+
<%= f.password_field :current_password, autocomplete: "off" %>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<div class="actions">
|
35
|
+
<%= f.submit t('global.buttons.update') %>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
38
|
+
|
39
|
+
<h3><%= t('devise.headers.cancel_account') %></h3>
|
40
|
+
|
41
|
+
<p>Unhappy? <%= button_to t('devise.buttons.cancel_account'), registration_path(resource_name), data: { confirm: t('global.confirm_message') }, method: :delete %></p>
|
42
|
+
|
43
|
+
<%= link_to t('global.buttons.back'), :back %>
|
@@ -0,0 +1,30 @@
|
|
1
|
+
<%= render 'account/shared/workflow/box' do |p| %>
|
2
|
+
<% p.content_for :title, t('devise.headers.create_account') %>
|
3
|
+
<% p.content_for :body do %>
|
4
|
+
<% within_fields_namespace(:sign_up) do %>
|
5
|
+
<%= form_for resource, as: resource_name, url: registration_path(resource_name), html: {class: 'form'} do |f| %>
|
6
|
+
<%= render 'account/shared/notices' %>
|
7
|
+
<%= render 'account/shared/forms/errors', form: f %>
|
8
|
+
|
9
|
+
<%= render 'shared/fields/email_field', form: f, method: :email, options: {autofocus: true} do %>
|
10
|
+
<% content_for :help do %>
|
11
|
+
<%= link_to t('devise.links.have_account'), new_user_session_path %>
|
12
|
+
<% end %>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<div class="grid grid-cols-2 gap-5">
|
16
|
+
<div>
|
17
|
+
<%= render 'shared/fields/password_field', form: f, method: :password %>
|
18
|
+
</div>
|
19
|
+
<div>
|
20
|
+
<%= render 'shared/fields/password_field', form: f, method: :password_confirmation, other_options: {error: f.object.errors.full_messages_for(:password).first, hide_custom_error: true} %>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
<%= f.submit t('global.buttons.sign_up'), class: 'button full' %>
|
25
|
+
|
26
|
+
<%= render 'devise/shared/oauth', verb: 'Sign Up' %>
|
27
|
+
<% end %>
|
28
|
+
<% end %>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
<%= render 'account/shared/workflow/box' do |p| %>
|
4
|
+
<% p.content_for :title, t('devise.headers.sign_in') %>
|
5
|
+
<% p.content_for :body do %>
|
6
|
+
<% within_fields_namespace(:self) do %>
|
7
|
+
<%= form_for resource, as: resource_name, url: two_factor_authentication_enabled? ? users_pre_otp_path : session_path(resource_name), remote: two_factor_authentication_enabled?, html: {class: 'form'}, authenticity_token: true do |form| %>
|
8
|
+
<% with_field_settings form: form do %>
|
9
|
+
<%= render 'account/shared/notices', form: form %>
|
10
|
+
<%= render 'account/shared/forms/errors', form: form %>
|
11
|
+
|
12
|
+
<% email_field = capture do %>
|
13
|
+
<%= render 'shared/fields/email_field', method: :email, options: {autofocus: true} do %>
|
14
|
+
<% if show_sign_up_options? %>
|
15
|
+
<% content_for :help do %>
|
16
|
+
<%= link_to t('devise.links.account'), new_user_registration_path %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
20
|
+
<% end %>
|
21
|
+
|
22
|
+
<% if two_factor_authentication_enabled? %>
|
23
|
+
<div id="step-1" class="space-y">
|
24
|
+
<%= email_field %>
|
25
|
+
<%= form.submit t('global.buttons.next'), class: 'button full' %>
|
26
|
+
</div>
|
27
|
+
<% else %>
|
28
|
+
<%= email_field %>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<div id="step-2" class="<%= 'hidden' if two_factor_authentication_enabled? %> space-y">
|
32
|
+
<%= render 'shared/fields/password_field', method: :password do %>
|
33
|
+
<% content_for :help do %>
|
34
|
+
<%= link_to t('devise.links.forgot_password'), new_user_password_path %>
|
35
|
+
<% end %>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
<% if two_factor_authentication_enabled? %>
|
39
|
+
<div id="step-2-otp" class="hidden">
|
40
|
+
<%= render 'shared/fields/text_field', method: :otp_attempt %>
|
41
|
+
</div>
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<%= form.submit t('global.buttons.sign_in'), class: 'button full' %>
|
45
|
+
</div>
|
46
|
+
|
47
|
+
<div class="flex items-center">
|
48
|
+
<input id="remember_me" name="remember_me" type="checkbox" class="h-4 w-4 text-blue focus:ring-blue-dark border-gray-300 rounded">
|
49
|
+
<label for="remember_me" class="ml-2 block">
|
50
|
+
Remember me
|
51
|
+
</label>
|
52
|
+
</div>
|
53
|
+
<% end %>
|
54
|
+
<% end %>
|
55
|
+
<% end %>
|
56
|
+
|
57
|
+
<%= render 'devise/shared/oauth' %>
|
58
|
+
<% end %>
|
59
|
+
<% end %>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<% if @email %>
|
2
|
+
$("#step-1").addClass("hidden");
|
3
|
+
$("#step-2").removeClass("hidden");
|
4
|
+
<% if @user&.otp_required_for_login %>
|
5
|
+
$("#step-2-otp").removeClass("hidden");
|
6
|
+
<% end %>
|
7
|
+
setTimeout(function() {
|
8
|
+
$("#user_password").focus();
|
9
|
+
$("#new_user").attr('action', '/users/sign_in').attr('data-remote', 'false');
|
10
|
+
}, 1);
|
11
|
+
<% end %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<%- if controller_name != 'sessions' %>
|
2
|
+
<%= link_to t('devise.links.log_in'), new_session_path(resource_name) %><br />
|
3
|
+
<% end -%>
|
4
|
+
|
5
|
+
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
|
6
|
+
<%= link_to t('devise.links.sign_up'), new_registration_path(resource_name) %><br />
|
7
|
+
<% end -%>
|
8
|
+
|
9
|
+
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
|
10
|
+
<%= link_to t('devise.links.new_confirm'), new_confirmation_path(resource_name) %><br />
|
11
|
+
<% end -%>
|
12
|
+
|
13
|
+
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
|
14
|
+
<%= link_to t('devise.links.new_unlock'), new_unlock_path(resource_name) %><br />
|
15
|
+
<% end -%>
|
16
|
+
|
17
|
+
<%- if devise_mapping.omniauthable? %>
|
18
|
+
<%- resource_class.omniauth_providers.each do |provider| %>
|
19
|
+
<%= link_to t('devise.links.sign_in_with', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider) %><br />
|
20
|
+
<% end -%>
|
21
|
+
<% end -%>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
<% if any_oauth_enabled? %>
|
2
|
+
<% verb ||= 'Sign In' %>
|
3
|
+
|
4
|
+
<%= render 'account/shared/decision_line' %>
|
5
|
+
<div class="grid grid-cols-1 gap-2">
|
6
|
+
<%= render 'devise/shared/oauth/stripe', verb: verb if stripe_enabled? %>
|
7
|
+
<%# 🚅 super scaffolding will insert new oauth providers above this line. %>
|
8
|
+
</div>
|
9
|
+
<% end %>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<h2><%= t('devise.headers.resend_unlock') %></h2>
|
2
|
+
|
3
|
+
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
|
4
|
+
<%= devise_error_messages! %>
|
5
|
+
|
6
|
+
<div class="field">
|
7
|
+
<%= f.label :email %><br />
|
8
|
+
<%= f.email_field :email, autofocus: true %>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="actions">
|
12
|
+
<%= f.submit t('devise.buttons.resend_unlock') %>
|
13
|
+
</div>
|
14
|
+
<% end %>
|
15
|
+
|
16
|
+
<%= render "devise/shared/links" %>
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# Additional translations at https://github.com/plataformatec/devise/wiki/I18n
|
2
|
+
|
3
|
+
en:
|
4
|
+
devise:
|
5
|
+
titles:
|
6
|
+
reset_password: Reset Your Password
|
7
|
+
sign_in: Sign In
|
8
|
+
headers:
|
9
|
+
resend_confirm: Resend Confirmation Instructions
|
10
|
+
change_password: Change Your Password
|
11
|
+
reset_password: Reset Password
|
12
|
+
edit_registration: Edit %{resource_name}
|
13
|
+
cancel_account: Cancel My Account
|
14
|
+
create_account: Create Your Account
|
15
|
+
sign_in: Sign In
|
16
|
+
resend_unlock: Resend Unlock Instructions
|
17
|
+
labels:
|
18
|
+
wait_confirm: "Currently waiting confirmation for: %{unconfirmed_email}"
|
19
|
+
hints:
|
20
|
+
leave_blank: (leave blank if you don't want to change it)
|
21
|
+
length: '%{password_length} characters minimum'
|
22
|
+
need_password: (we need your current password to confirm your changes)
|
23
|
+
buttons:
|
24
|
+
resend_confirm: Resend Confirmation Instructions
|
25
|
+
change_password: Change My Password
|
26
|
+
reset_password: Reset Password by Email
|
27
|
+
cancel_account: Cancel My Account
|
28
|
+
resend_unlock: Resend Unlock Instructions
|
29
|
+
links:
|
30
|
+
account: Don't have an account?
|
31
|
+
have_account: Already have an account?
|
32
|
+
forgot_password: Forgot your password?
|
33
|
+
log_in: Log In
|
34
|
+
sign_up: Sign Up
|
35
|
+
new_confirm: Didn't receive confirmation instructions?
|
36
|
+
new_unlock: Didn't receive unlock instructions?
|
37
|
+
sign_in_with: Sign in with %{provider}
|
38
|
+
|
39
|
+
confirmations:
|
40
|
+
confirmed: "Your email address has been successfully confirmed."
|
41
|
+
send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
|
42
|
+
send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
|
43
|
+
failure:
|
44
|
+
already_authenticated: "You are already signed in."
|
45
|
+
inactive: "Your account is not activated yet."
|
46
|
+
invalid: "Invalid %{authentication_keys} or Password."
|
47
|
+
locked: "Your account is locked."
|
48
|
+
last_attempt: "You have one more attempt before your account is locked."
|
49
|
+
not_found_in_database: "Invalid %{authentication_keys} or Password."
|
50
|
+
timeout: "Your session expired. Please sign in again to continue."
|
51
|
+
unauthenticated: "You need to sign in or sign up before continuing."
|
52
|
+
unconfirmed: "You have to confirm your email address before continuing."
|
53
|
+
mailer:
|
54
|
+
confirmation_instructions:
|
55
|
+
subject: "Confirmation instructions"
|
56
|
+
welcome: Welcome %{email}!
|
57
|
+
description: 'You can confirm your account email through the link below:'
|
58
|
+
confirm_account: Confirm my account
|
59
|
+
reset_password_instructions:
|
60
|
+
subject: "Reset password instructions"
|
61
|
+
hello: Hello %{email}!
|
62
|
+
requested_change: Someone has requested a link to change your password. You can do this through the link below.
|
63
|
+
ignore: If you didn't request this, please ignore this email.
|
64
|
+
access_change: Your password won't change until you access the link above and create a new one.
|
65
|
+
change_password: Change my password
|
66
|
+
unlock_instructions:
|
67
|
+
subject: "Unlock instructions"
|
68
|
+
hello: Hello %{email}!
|
69
|
+
account_blocked: Your account has been locked due to an excessive number of unsuccessful sign in attempts.
|
70
|
+
click_link: 'Click the link below to unlock your account:'
|
71
|
+
unlock: Unlock my account
|
72
|
+
password_change:
|
73
|
+
subject: "Password Changed"
|
74
|
+
hello: Hello %{email}!
|
75
|
+
description: We're contacting you to notify you that your password has been changed.
|
76
|
+
omniauth_callbacks:
|
77
|
+
failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
|
78
|
+
success: "Successfully authenticated from %{kind} account."
|
79
|
+
passwords:
|
80
|
+
no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
|
81
|
+
send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
|
82
|
+
send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
|
83
|
+
updated: "Your password has been changed successfully. You are now signed in."
|
84
|
+
updated_not_active: "Your password has been changed successfully."
|
85
|
+
registrations:
|
86
|
+
destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
|
87
|
+
signed_up: "Welcome! You have signed up successfully."
|
88
|
+
signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
|
89
|
+
signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
|
90
|
+
signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
|
91
|
+
update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address."
|
92
|
+
updated: "Your account has been updated successfully."
|
93
|
+
sessions:
|
94
|
+
signed_in: "Signed in successfully."
|
95
|
+
signed_out: "Signed out successfully."
|
96
|
+
already_signed_out: "Signed out successfully."
|
97
|
+
unlocks:
|
98
|
+
send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
|
99
|
+
send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
|
100
|
+
unlocked: "Your account has been unlocked successfully. Please sign in to continue."
|
101
|
+
errors:
|
102
|
+
messages:
|
103
|
+
already_confirmed: "was already confirmed, please try signing in"
|
104
|
+
confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
|
105
|
+
expired: "has expired, please request a new one"
|
106
|
+
not_found: "not found"
|
107
|
+
not_locked: "was not locked"
|
108
|
+
not_saved:
|
109
|
+
one: "1 error prohibited this %{resource} from being saved:"
|
110
|
+
other: "%{count} errors prohibited this %{resource} from being saved:"
|
data/lib/bullet_train/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bullet_train
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Culver
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-01-
|
11
|
+
date: 2022-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -47,12 +47,28 @@ files:
|
|
47
47
|
- app/controllers/concerns/account/onboarding/user_email/controller_base.rb
|
48
48
|
- app/controllers/concerns/account/teams/controller_base.rb
|
49
49
|
- app/controllers/concerns/account/users/controller_base.rb
|
50
|
+
- app/controllers/concerns/registrations/controller_base.rb
|
51
|
+
- app/controllers/concerns/sessions/controller_base.rb
|
52
|
+
- app/controllers/registrations_controller.rb
|
53
|
+
- app/controllers/sessions_controller.rb
|
54
|
+
- app/helpers/account/buttons_helper.rb
|
55
|
+
- app/helpers/account/dates_helper.rb
|
56
|
+
- app/helpers/account/forms_helper.rb
|
50
57
|
- app/helpers/account/invitations_helper.rb
|
58
|
+
- app/helpers/account/locale_helper.rb
|
59
|
+
- app/helpers/account/markdown_helper.rb
|
51
60
|
- app/helpers/account/memberships_helper.rb
|
61
|
+
- app/helpers/account/role_helper.rb
|
52
62
|
- app/helpers/account/teams_helper.rb
|
53
63
|
- app/helpers/account/users_helper.rb
|
64
|
+
- app/helpers/attributes_helper.rb
|
65
|
+
- app/helpers/email_helper.rb
|
66
|
+
- app/helpers/images_helper.rb
|
54
67
|
- app/helpers/invitation_only_helper.rb
|
55
68
|
- app/helpers/invitations_helper.rb
|
69
|
+
- app/mailers/concerns/mailers/base.rb
|
70
|
+
- app/mailers/devise_mailer.rb
|
71
|
+
- app/mailers/user_mailer.rb
|
56
72
|
- app/models/concerns/invitations/base.rb
|
57
73
|
- app/models/concerns/memberships/base.rb
|
58
74
|
- app/models/concerns/teams/base.rb
|
@@ -97,6 +113,22 @@ files:
|
|
97
113
|
- app/views/account/users/_form.html.erb
|
98
114
|
- app/views/account/users/edit.html.erb
|
99
115
|
- app/views/account/users/show.html.erb
|
116
|
+
- app/views/devise/confirmations/new.html.erb
|
117
|
+
- app/views/devise/mailer/confirmation_instructions.html.erb
|
118
|
+
- app/views/devise/mailer/password_change.html.erb
|
119
|
+
- app/views/devise/mailer/reset_password_instructions.html.erb
|
120
|
+
- app/views/devise/mailer/unlock_instructions.html.erb
|
121
|
+
- app/views/devise/passwords/edit.html.erb
|
122
|
+
- app/views/devise/passwords/new.html.erb
|
123
|
+
- app/views/devise/registrations/_two_factor.html.erb
|
124
|
+
- app/views/devise/registrations/edit.html.erb
|
125
|
+
- app/views/devise/registrations/new.html.erb
|
126
|
+
- app/views/devise/sessions/new.html.erb
|
127
|
+
- app/views/devise/sessions/pre_otp.js.erb
|
128
|
+
- app/views/devise/shared/_links.html.erb
|
129
|
+
- app/views/devise/shared/_oauth.html.erb
|
130
|
+
- app/views/devise/unlocks/new.html.erb
|
131
|
+
- config/locales/en/devise.en.yml
|
100
132
|
- config/locales/en/invitations.en.yml
|
101
133
|
- config/locales/en/memberships.en.yml
|
102
134
|
- config/locales/en/onboarding/user_details.en.yml
|