Skip to content

Commit 1a0e2db

Browse files
committed
Added Magic Sign-up/-in Links
1 parent 3de6ba8 commit 1a0e2db

File tree

10 files changed

+133
-0
lines changed

10 files changed

+133
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class MagicSessionsController < ApplicationController
2+
allow_unauthenticated_access only: %w[show new create]
3+
before_action :set_user_by_token, only: %i[ show ]
4+
5+
def show
6+
start_new_session_for @user
7+
8+
redirect_to root_path
9+
end
10+
11+
def new
12+
@signin = MagicSignin.new
13+
end
14+
15+
def create
16+
MagicSignin.new(signin_params).save
17+
18+
redirect_to new_magic_session_path
19+
end
20+
21+
private
22+
23+
def signin_params
24+
params.expect(magic_signin: [ :email_address ])
25+
end
26+
27+
def set_user_by_token
28+
@user = User.find_by_token_for(:signin, params[:token])
29+
rescue ActiveSupport::MessageVerifier::InvalidSignature
30+
redirect_to new_magic_signup_path, alert: "Link is invalid or has expired."
31+
end
32+
end
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class MagicSignupsController < ApplicationController
2+
allow_unauthenticated_access only: %w[new create]
3+
invisible_captcha only: %w[create], timestamp_enabled: false
4+
5+
def new
6+
@signup = MagicSignin.new
7+
end
8+
9+
def create
10+
MagicSignin.new(signup_params).save
11+
12+
redirect_to new_magic_signup_path
13+
end
14+
15+
private
16+
17+
def signup_params
18+
params.expect(magic_signin: [ :email_address ])
19+
end
20+
end

app/mailers/magic_signup_mailer.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class MagicSignupMailer < ApplicationMailer
2+
def magic_link(user)
3+
@user = user
4+
mail to: @user.email_address
5+
end
6+
end

app/models/magic_signin.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
class MagicSignin
2+
AUTO_GENERATED_PASSWORD = SecureRandom.hex(32)
3+
4+
include ActiveModel::Model
5+
include ActiveModel::Attributes
6+
7+
attribute :email_address, :string
8+
9+
validates :email_address, presence: true
10+
11+
validates :email_address, is_not_spam: true
12+
13+
def save
14+
return unless valid?
15+
16+
User.where(email_address: email_address).first_or_create.tap do |user|
17+
if user.new_record?
18+
user.password = AUTO_GENERATED_PASSWORD
19+
user.save
20+
21+
create_workspace_for user
22+
end
23+
24+
send_magic_link_to user
25+
end
26+
end
27+
28+
def model_name
29+
ActiveModel::Name.new(self, nil, self.class.name)
30+
end
31+
32+
private
33+
34+
def create_workspace_for(user)
35+
Workspace.create(name: "New Workspace").tap do |workspace|
36+
workspace.users << user
37+
end
38+
end
39+
40+
def send_magic_link_to(user)
41+
MagicSignupMailer.magic_link(user).deliver_later
42+
end
43+
end

app/models/user.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ class User < ApplicationRecord
55
belongs_to :workspace
66

77
normalizes :email_address, with: ->(e) { e.strip.downcase }
8+
9+
generates_token_for :signin, expires_in: 5.minutes
810
end

app/views/magic_sessions/new.html.erb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
2+
<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>
3+
4+
<%= form_with model: @signin, url: magic_sessions_path do |form| %>
5+
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
6+
7+
<%= form.submit "Sign in" %>
8+
<% end %>
9+
<br>
10+
11+
<%= link_to "Don't have an account?", new_magic_signup_path %>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<p>
2+
Here is your magic link: <%= magic_session_url(@user.generate_token_for(:signin)) %>
3+
</p>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Here is your magic link: <%= magic_session_url(@user.generate_token_for(:signin)) %>

app/views/magic_signups/new.html.erb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<%= tag.div(flash[:alert], style: "color:red") if flash[:alert] %>
2+
<%= tag.div(flash[:notice], style: "color:green") if flash[:notice] %>
3+
4+
<%= form_with model: @signup, url: magic_signups_path do |form| %>
5+
<%= form.email_field :email_address, required: true, autofocus: true, autocomplete: "username", placeholder: "Enter your email address", value: params[:email_address] %><br>
6+
7+
<%= invisible_captcha %>
8+
9+
<%= form.submit "Sign up" %>
10+
11+
<%= link_to "Already have an account?", new_magic_session_path %>
12+
<% end %>

config/routes.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
resources :passwords, param: :token
66
resources :invitations, only: %w[index new create]
77
resources :accept_invitations, only: %w[new create]
8+
9+
resources :magic_signups, path: "magic-signup", only: %w[new create]
10+
resources :magic_sessions, path: "magic-session", param: :token
811
# Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
912

1013
# Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500.

0 commit comments

Comments
 (0)