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

Save controller names for url helpers #180

Merged
merged 6 commits into from
Nov 10, 2023
Merged
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
6 changes: 5 additions & 1 deletion app/controllers/passwordless/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ def create
end

redirect_to(
url_for(id: @session.identifier, action: "show"),
Passwordless.context.url_for(
@session,
id: @session.to_param,
action: "show"
),
flash: {notice: I18n.t("passwordless.sessions.create.email_sent")}
)
else
Expand Down
16 changes: 7 additions & 9 deletions app/mailers/passwordless/mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@ class Mailer < Passwordless.config.parent_mailer.constantize
# is still in memory (optional)
def sign_in(session, token = nil)
@token = token || session.token
@magic_link = url_for(
{
controller: "passwordless/sessions",
action: "confirm",
id: session.identifier,
token: @token,
authenticatable: "user",
resource: "users"
}

@magic_link = Passwordless.context.url_for(
session,
action: "confirm",
id: session.to_param,
token: @token
)

email_field = session.authenticatable.class.passwordless_email_field

mail(
Expand Down
9 changes: 9 additions & 0 deletions lib/passwordless.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require "active_support"
require "passwordless/config"
require "passwordless/context"
require "passwordless/errors"
require "passwordless/engine"
require "passwordless/token_digest"
Expand All @@ -10,6 +11,14 @@
module Passwordless
extend Configurable

def self.context
@context ||= Context.new
end

def self.add_resource(resource, controller:, **defaults)
context.resources[resource] = Resource.new(resource, controller: controller)
end

def self.digest(token)
TokenDigest.new(token).digest
end
Expand Down
52 changes: 52 additions & 0 deletions lib/passwordless/context.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# frozen_string_literal: true

module Passwordless
class Resource
def initialize(resource, controller:)
@resource = resource
@authenticatable = resource.to_s.singularize.to_sym
@controller = controller
end

attr_reader :resource, :authenticatable, :controller

def defaults
@defaults ||= {
authenticatable: authenticatable,
resource: resource,
controller: controller
}
end
end

class Context
def initialize
@resources = {}
end

attr_reader :resources

def resource_for(session_or_authenticatable)
if session_or_authenticatable.is_a?(Session)
session_or_authenticatable = session_or_authenticatable.authenticatable.model_name.to_s.tableize.to_sym
end

resources[session_or_authenticatable.to_sym]
end

def url_for(session_or_authenticatable, **options)
unless (resource = resource_for(session_or_authenticatable))
raise ArgumentError, "No resource registered for #{session_or_authenticatable}"
end

Rails.application.routes.url_helpers.url_for(
**resource.defaults,
**options
)
end

def path_for(session_or_authenticatable, **options)
url_for(session_or_authenticatable, only_path: true, **options)
end
end
end
10 changes: 2 additions & 8 deletions lib/passwordless/router_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,9 @@ def passwordless_for(resource, at: :na, as: :na, controller: "passwordless/sessi

as = as.to_s + "_" unless !as || as.to_s.end_with?("_")

plural = resource.to_s
singular = plural.singularize
pwless_resource = Passwordless.add_resource(resource, controller: controller)

defaults = {
authenticatable: singular,
resource: resource
}

scope(defaults: defaults) do
scope(defaults: pwless_resource.defaults) do
get("#{at}/sign_in", to: "#{controller}#new", as: :"#{as}sign_in")
post("#{at}/sign_in", to: "#{controller}#create")
get("#{at}/sign_in/:id", to: "#{controller}#show", as: :"verify_#{as}sign_in")
Expand Down
52 changes: 14 additions & 38 deletions lib/passwordless/test_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,19 @@ module TestHelpers
module TestCase
def passwordless_sign_out(cls = nil)
cls ||= "User".constantize
dest = url_for(
{
controller: "passwordless/sessions",
action: "destroy",
authenticatable: cls.model_name.singular,
resource: cls.model_name.to_s.tableize
}
)
resource = cls.model_name.to_s.tableize
dest = Passwordless.context.path_for(resource, action: "destroy")
delete(dest)
follow_redirect!
end

def passwordless_sign_in(resource)
cls = resource.class
session = Passwordless::Session.create!(authenticatable: resource)
magic_link = url_for(
{
controller: "passwordless/sessions",
action: "confirm",
id: session.to_param,
token: session.token,
authenticatable: cls.model_name.singular,
resource: cls.model_name.to_s.tableize
}
magic_link = Passwordless.context.path_for(
session,
action: "confirm",
id: session.to_param,
token: session.token
)
get(magic_link)
follow_redirect!
Expand All @@ -36,30 +25,17 @@ def passwordless_sign_in(resource)
module SystemTestCase
def passwordless_sign_out(cls = nil)
cls ||= "User".constantize
visit(
url_for(
{
controller: "passwordless/sessions",
action: "destroy",
authenticatable: cls.model_name.singular,
resource: cls.model_name.to_s.tableize
}
)
)
resource = cls.model_name.to_s.tableize
visit(Passwordless.context.url_for(resource, action: "destroy"))
end

def passwordless_sign_in(resource)
cls = resource.class
session = Passwordless::Session.create!(authenticatable: resource)
magic_link = url_for(
{
controller: "passwordless/sessions",
action: "confirm",
id: session.id,
token: session.token,
authenticatable: cls.model_name.singular,
resource: cls.model_name.to_s.tableize
}
magic_link = Passwordless.context.url_for(
session,
action: "confirm",
id: session.id,
token: session.token
)
visit(magic_link)
end
Expand Down
6 changes: 5 additions & 1 deletion test/dummy/app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ class ApplicationController < ActionController::Base

protect_from_forgery with: :exception

helper_method :current_user
helper_method :current_user, :current_admin

private

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

def current_admin
@current_admin ||= authenticate_by_session(Admin)
end

def authenticate_user!
return if current_user

Expand Down
1 change: 0 additions & 1 deletion test/dummy/app/controllers/secrets_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@ class SecretsController < ApplicationController
before_action :authenticate_user!

def index
render(plain: "shhhh! secrets!")
end
end
3 changes: 2 additions & 1 deletion test/dummy/app/models/admin.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: true

class Admin < User
class Admin < ApplicationRecord
passwordless_with :email
end
2 changes: 2 additions & 0 deletions test/dummy/app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<div style="border: 1px solid gray; padding: 10px; margin-bottom: 10px;">
<p><strong>Current user:</strong>
<%= current_user&.email.to_s %></p>
<p><strong>Current admin:</strong>
<%= current_admin&.email.to_s %></p>
</div>

<% flash.each do |kind, msg| %>
Expand Down
1 change: 1 addition & 0 deletions test/dummy/app/views/secrets/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
shhhh! secrets!
3 changes: 1 addition & 2 deletions test/dummy/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ module Dummy
class Application < Rails::Application
config.load_defaults 7.0

routes.default_url_options[:host] = "localhost:3000"
config.action_mailer.default_url_options = {host: "localhost", port: "3000"}
config.hosts << "www.example.com"
routes.default_url_options[:host] = "localhost:3000"
end
end
5 changes: 3 additions & 2 deletions test/dummy/config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,13 @@

# Tell Active Support which deprecation messages to disallow.
config.active_support.disallowed_deprecation_warnings = []

# Raises error for missing translations.
# config.i18n.raise_on_missing_translations = true

# Annotate rendered view with file names.
# config.action_view.annotate_rendered_view_with_filenames = true

config.hosts << "localhost"
host = "www.example.com"
config.action_mailer.default_url_options = {host: host}
routes.default_url_options[:host] = host
end
10 changes: 0 additions & 10 deletions test/dummy/db/migrate/20230621085550_create_users.rb

This file was deleted.

18 changes: 18 additions & 0 deletions test/dummy/db/migrate/20230621085550_create_users_and_admins.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class CreateUsersAndAdmins < ActiveRecord::Migration[7.0]
def change
create_table(:admins) do |t|
t.string(:email)

t.timestamps
end

create_table(:users) do |t|
t.string(:email)

t.timestamps
end

add_index(:admins, :email, unique: true)
add_index(:users, :email, unique: true)
end
end
7 changes: 7 additions & 0 deletions test/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2023_06_21_085550) do
create_table "admins", force: :cascade do |t|
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["email"], name: "index_admins_on_email", unique: true
end

create_table "passwordless_sessions", force: :cascade do |t|
t.string "authenticatable_type"
t.integer "authenticatable_id"
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/admins.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
jerry:
email: [email protected]
2 changes: 1 addition & 1 deletion test/integration/authentication_flow_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class AuthenticationFlowTest < ActionDispatch::SystemTestCase

visit magic_link

assert_equal "/", current_path
assert_equal "/secret", current_path
assert_content "Current user: #{alice.email}"
end
end
22 changes: 20 additions & 2 deletions test/mailers/passwordless/mailer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,27 @@ class Passwordless::MailerTest < ActionMailer::TestCase
assert_match "Signing in ✨", email.subject
assert_match /sign in: hello\n/, email.body.to_s
assert_match %r{/sign_in/#{session.identifier}/hello}, email.body.to_s
end

test("sign_in when no token is passed") do
user = users(:alice)
session = Passwordless::Session.create!(authenticatable: user, token: "hello")

session = Passwordless::Session.create!(authenticatable: user)
email = Passwordless::Mailer.sign_in(session)
assert_equal [user.email], email.to

assert_match %r{/sign_in/#{session.identifier}/hello}, email.body.to_s
end

test("sign in with custom controller") do
admin = admins(:jerry)
session = Passwordless::Session.create!(authenticatable: admin, token: "hello")

email = Passwordless::Mailer.sign_in(session, "hello")

assert_equal [admin.email], email.to

assert_match "Signing in ✨", email.subject
assert_match /sign in: hello\n/, email.body.to_s
assert_match %r{/admins/sign_in/#{session.identifier}/hello}, email.body.to_s
end
end
13 changes: 13 additions & 0 deletions test/passwordless/context_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

require "test_helper"

module Passwordless
class ContextTest < ActiveSupport::TestCase
test("url_for") do
session = Session.create!(authenticatable: users(:alice), token: "hello")
url = Passwordless.context.url_for(session, action: "confirm", id: session.to_param, token: session.token)
assert_match %r{/sign_in/#{session.identifier}/hello}, url
end
end
end
4 changes: 2 additions & 2 deletions test/passwordless/test_helpers_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ class PasswordlessTestHelpersTest < ActiveSupport::TestCase
assert 1, Session.count
assert alice, Session.last!.authenticatable
assert_match(
%r{^http://.*/users/sign_in/[a-z0-9-]+/[a-z0-9]+}i,
%r{/users/sign_in/[a-z0-9-]+/[a-z0-9]+}i,
controller.actions.first.last.first
)

controller.passwordless_sign_out

assert_match(
%r{^http://.*/users/sign_out},
%r{/users/sign_out},
controller.actions[-2].last.first
)
end
Expand Down