Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add passwordless for authentication #586

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ gem 'rails_admin'
gem 'redcarpet'
gem 'erubis'
gem 'rack-attack'
gem 'passwordless'

group :development, :test do
gem 'rspec-rails', '6.1.2'
gem 'byebug', platform: :mri
gem 'factory_bot_rails'
gem 'dotenv-rails'
gem 'faker'
gem 'letter_opener'
end

group :development do
Expand Down
17 changes: 16 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ GEM
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
base64 (0.2.0)
bcrypt (3.1.20)
bigdecimal (3.1.8)
bindex (0.8.1)
builder (3.2.4)
Expand All @@ -97,6 +98,7 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
childprocess (5.0.0)
concurrent-ruby (1.2.3)
connection_pool (2.4.1)
crass (1.0.6)
Expand Down Expand Up @@ -176,6 +178,11 @@ GEM
activerecord
kaminari-core (= 1.2.2)
kaminari-core (1.2.2)
launchy (3.0.1)
addressable (~> 2.8)
childprocess (~> 5.0)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
Expand Down Expand Up @@ -204,10 +211,15 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.5-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.5-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.5-x86_64-linux)
racc (~> 1.4)
passwordless (1.7.0)
bcrypt (>= 3.1.11)
rails (>= 5.1.4)
pg (1.5.6)
psych (5.1.2)
stringio
Expand Down Expand Up @@ -350,6 +362,7 @@ GEM
zeitwerk (2.6.14)

PLATFORMS
arm64-darwin-22
x86_64-darwin-21
x86_64-linux

Expand All @@ -363,7 +376,9 @@ DEPENDENCIES
factory_bot_rails
faker
jquery-rails
letter_opener
listen
passwordless
pg (= 1.5.6)
puma
rack-attack
Expand All @@ -384,7 +399,7 @@ DEPENDENCIES
web-console

RUBY VERSION
ruby 3.1.2p20
ruby 3.2.2p53

BUNDLED WITH
2.3.24
16 changes: 16 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,20 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception

include Passwordless::ControllerHelpers

helper_method :current_user

private

def current_user
@current_user ||= authenticate_by_session(User)
end

def require_user!
return if current_user
save_passwordless_redirect_location!(User)
redirect_to users_sign_in_path
end
end
23 changes: 12 additions & 11 deletions app/controllers/proposals_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
class ProposalsController < ApplicationController
include ApplicationHelper

before_action :require_user!, only: [:new, :create, :edit, :update]
before_action :set_proposal, only: [:edit, :update]

def index
@proposals = Proposal.order('title ASC')
if params[:search].present?
Expand All @@ -13,47 +16,45 @@ def show
end

def new
@speakers = speakers
preselected_speaker = @speakers.where(id: params[:speaker_id]).first
@proposal = Proposal.new(speaker_id: preselected_speaker&.id)
@proposal = Proposal.new
end

def create
@proposal = Proposal.new(proposal_params)
if verify_recaptcha(model: @proposal) && @proposal.save

if @proposal.save
flash[:notice] = 'Proposal created successfully!'

redirect_to proposal_path(@proposal)
else
@speakers = speakers
render 'new'
end
end

def edit
@proposal = Proposal.find(params[:id])
@speakers = speakers
end

def update
@proposal = Proposal.find(params[:id])
if verify_recaptcha(model: @proposal) && @proposal.update(proposal_params)
if @proposal.update(proposal_params)
flash[:notice] = 'Proposal updated successfully!'

redirect_to proposal_path(@proposal)
else
@speakers = speakers
render 'edit'
end
end

private

def proposal_params
params.require(:proposal).permit(:title, :body, :tag_list, :speaker_id).to_h
params.require(:proposal).permit(:title, :body, :tag_list).
merge(speaker_id: current_user.speaker.id).to_h
end

def speakers
Speaker.all.order('name ASC')
def set_proposal
@proposal = current_user.proposals.where(id: params[:id]).first
redirect_to proposals_path if @proposal.blank?
end
end
21 changes: 0 additions & 21 deletions app/controllers/speakers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,4 @@ def index
def show
@speaker = Speaker.find(params[:id])
end

def new
@speaker = Speaker.new
end

def create
@speaker = Speaker.new(speaker_params)
if verify_recaptcha(model: @speaker) && @speaker.save
flash[:notice] = 'Speaker created successfully!'

redirect_to speakers_path
else
render :new
end
end

private

def speaker_params
params.require(:speaker).permit(:name).to_h
end
end
17 changes: 17 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class UsersController < ApplicationController

def new
@user = User.new(speaker: Speaker.new)
end

def create
speaker = Speaker.new(name: params[:name])
@user = User.new(email: params[:email], speaker: speaker)
if @user.save
sign_in(create_passwordless_session(@user))
redirect_to root_path
else
render :new
end
end
end
4 changes: 4 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ def markdown(text)
markdown = Redcarpet::Markdown.new(renderer, options)
markdown.render(text).html_safe
end

def current_user_owns?(proposal)
@editable ||= current_user && proposal.speaker_id == current_user.speaker.id
end
end
1 change: 1 addition & 0 deletions app/models/speaker.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Speaker < ApplicationRecord
has_many :proposals
belongs_to :user, optional: true

validates_presence_of :name
validates_uniqueness_of :name
Expand Down
10 changes: 10 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class User < ApplicationRecord
has_one :speaker
delegate :proposals, to: :speaker

validates :email, presence: true,
uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }

passwordless_with :email
end
1 change: 1 addition & 0 deletions app/views/passwordless/mailer/sign_in.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= t("passwordless.mailer.sign_in.body", token: @token, magic_link: @magic_link) %>
16 changes: 16 additions & 0 deletions app/views/passwordless/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

<h2>Sign in</h2>
<%= form_with(model: @session, url: url_for(action: 'new'), data: { turbo: 'false' }) do |f| %>
<% email_field_name = :"passwordless[#{email_field}]" %>
<%= f.label email_field_name,
t("passwordless.sessions.new.email.label"),
for: "passwordless_#{email_field}" %>
<%= email_field_tag email_field_name,
params.fetch(email_field_name, nil),
required: true,
autofocus: true,
placeholder: t("passwordless.sessions.new.email.placeholder") %>
<div><%= f.submit t("passwordless.sessions.new.submit") %></div>
<% end %>

Or <%= link_to "click here", new_user_path %> to register.
8 changes: 8 additions & 0 deletions app/views/passwordless/sessions/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<%= form_with(model: @session, url: url_for(action: 'update'), scope: 'passwordless', method: 'patch', data: { turbo: false }) do |f| %>
<%= f.label :token, t(".token") %>
<%= f.text_field :token,
required: true,
autofocus: true,
autocomplete: "one-time-code" %>
<%= f.submit t(".confirm") %>
<% end %>
2 changes: 0 additions & 2 deletions app/views/proposals/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
<% end %>

<%= form_for @proposal do |f| %>
<%= f.collection_select(:speaker_id, @speakers, :id, :name) %>
<%= f.label(:title) %>
<%= f.text_field(:title, class: "u-full-width medium-height") %>
<%= f.label(:tags, "Tags (comma-separated, max 3)", for: :proposal_tag_list) %>
<%= f.text_field(:tag_list, value: @proposal.tag_list.join(", ")) %>
<%= f.label(:body) %>
<%= f.text_area(:body, class: "u-full-width medium-height", placeholder: "For formatting, you can use Markdown! :-)") %>
<%= recaptcha_tags %>
<%= f.submit "Save proposal" %>
<% end %>
14 changes: 10 additions & 4 deletions app/views/proposals/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
<% proposals_columns = @proposals.in_groups(4, false) %>
<% if current_user %>
<%= link_to new_proposal_path do %>
<button class="button-add">Add proposal</button>
<% end %>
<% else %>
<%= link_to users_sign_in_path do %>
<button class="button-add">Sign in to manage your Proposals</button>
<% end %>
<% end %>

<form method="GET">
<div class="row">
<div class="six columns">
<input class="u-full-width" type="text" placeholder="Title or Body" id="search" name="search" value="<%= params[:search] %>">
</div>
<input class="button-primary two columns" type="submit" value="Search">

<%= link_to new_proposal_path, class: "two columns" do %>
<button class="button-add">Add proposal</button>
<% end %>
</div>
</form>

Expand Down
18 changes: 13 additions & 5 deletions app/views/proposals/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
</h4>

<p><%= markdown(@proposal.body) %></p>
<p>
<%= link_to 'Edit proposal', edit_proposal_path(@proposal) %>
</p>

<% if current_user_owns?(@proposal) %>
<p>
<%= link_to 'Edit proposal', edit_proposal_path(@proposal) %>
</p>
<% end %>
</div>

<div class="one-half column">
Expand All @@ -21,9 +24,14 @@
<p>
<%= link_to submission.event_instance.name_and_year, submission.event_instance %> -
<%= submission.result.capitalize %>
<%= link_to '[Edit]', edit_submission_path(submission), id: 'edit-submission' %>
<% if current_user_owns?(@proposal) %>
<%= link_to '[Edit]', edit_submission_path(submission), id: 'edit-submission' %>
<% end %>
</p>
<% end %>
<%= link_to 'Add submission', new_submission_path(proposal: @proposal.id) %>

<% if current_user_owns?(@proposal) %>
<%= link_to 'Add submission', new_submission_path(proposal: @proposal.id) %>
<% end %>
</div>
</div>
7 changes: 5 additions & 2 deletions app/views/speakers/index.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<% speakers_columns = @speakers.in_groups(4, false) %>
<%= link_to new_speaker_path do %>
<button class="button-add">Add a speaker</button>

<% unless current_user %>
<%= link_to users_sign_in_path do %>
<button class="button-add">Sign up to add yourself as a Speaker</button>
<% end %>
<% end %>

<div class="row">
Expand Down
14 changes: 0 additions & 14 deletions app/views/speakers/new.html.erb

This file was deleted.

12 changes: 12 additions & 0 deletions app/views/users/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<h2>Register</h2>
<%= form_with(model: @user, data: { turbo: 'false' }) do |f| %>
<%= f.label :email %>
<%= email_field_tag :email, @user.email, required: true, autofocus: true %>

<%= f.label :name %>
<%= text_field_tag :name, @user.speaker.name, required: true %>

<div><%= f.submit "Save" %></div>
<% end %>

Or <%= link_to "click here", users_sign_in_path %> to sign in.
5 changes: 5 additions & 0 deletions config/environments/development.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@

config.action_mailer.perform_caching = false

config.action_mailer.delivery_method = :letter_opener
config.action_mailer.perform_deliveries = true
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
routes.default_url_options[:host] ||= "localhost:3000"

# Print deprecation notices to the Rails logger.
config.active_support.deprecation = :log

Expand Down
Loading