diff --git a/CHANGELOG.md b/CHANGELOG.md index c08371c..3396721 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,25 @@ ### Breaking changes +#### 1. Encrypted tokens + Tokens are now encrypted in the database. If you are upgrading from a previous version, you'll need to add a field to your passwordless table: ```sh $ bin/rails g migration AddTokenDigestToPasswordlessSessions token_digest:string:index ``` +#### 2. Un-isolated namespace + +Passwordless no longer [_isolates namespace_](https://guides.rubyonrails.org/engines.html#routes). + +1. Update all your links with eg. `users.sign_in_path` to `users_sign_in_path` +1. Remove all links with `main_app.whatever_path` to just `whatever_path` + ### Changed - Tokens are now encrypted in the database ([#145](https://github.com/mikker/passwordless/pull/145)) +- Un-isolate namespace ([#146](https://github.com/mikker/passwordless/pull/146)) ## 0.12.0 (2023-06-16) diff --git a/app/controllers/passwordless/sessions_controller.rb b/app/controllers/passwordless/sessions_controller.rb index d69c364..9525aaf 100644 --- a/app/controllers/passwordless/sessions_controller.rb +++ b/app/controllers/passwordless/sessions_controller.rb @@ -7,7 +7,7 @@ module Passwordless class SessionsController < ApplicationController include ControllerHelpers - # get '/sign_in' + # get '/:resource/sign_in' # Assigns an email_field and new Session to be used by new view. # renders sessions/new.html.erb. def new @@ -15,7 +15,7 @@ def new @session = Session.new end - # post '/sign_in' + # post '/:resource/sign_in' # Creates a new Session record then sends the magic link # redirects to sign in page with generic flash message. # @see Mailer#magic_link Mailer#magic_link @@ -32,10 +32,10 @@ def create end flash[:notice] = I18n.t("passwordless.sessions.create.email_sent_if_record_found") - redirect_to(sign_in_path) + redirect_back(fallback_location: root_path) end - # get '/sign_in/:token' + # get '/:resource/sign_in/:token' # Looks up session record by provided token. Signs in user if a match # is found. Redirects to either the user's original destination # or _root_path_ @@ -56,7 +56,7 @@ def show redirect_to(passwordless_failure_redirect_path, redirect_to_options) end - # match '/sign_out', via: %i[get delete]. + # match '/:resource/sign_out', via: %i[get delete]. # Signs user out. Redirects to root_path # @see ControllerHelpers#sign_out def destroy diff --git a/app/mailers/passwordless/mailer.rb b/app/mailers/passwordless/mailer.rb index c56006c..d8864aa 100644 --- a/app/mailers/passwordless/mailer.rb +++ b/app/mailers/passwordless/mailer.rb @@ -10,7 +10,7 @@ class Mailer < Passwordless.parent_mailer.constantize def magic_link(session) @session = session - @magic_link = send(Passwordless.mounted_as).token_sign_in_url(session.token) + @magic_link = send(:"#{session.authenticatable_type.downcase.pluralize}_token_sign_in_url", session.token) email_field = @session.authenticatable.class.passwordless_email_field mail( diff --git a/app/models/passwordless/session.rb b/app/models/passwordless/session.rb index 3912472..8255a3e 100644 --- a/app/models/passwordless/session.rb +++ b/app/models/passwordless/session.rb @@ -4,6 +4,8 @@ module Passwordless # The session responsible for holding the connection between the record # trying to log in and the unique tokens. class Session < ApplicationRecord + self.table_name = "passwordless_sessions" + belongs_to( :authenticatable, polymorphic: true, diff --git a/app/views/passwordless/sessions/new.html.erb b/app/views/passwordless/sessions/new.html.erb index 3fe5fe7..244f786 100644 --- a/app/views/passwordless/sessions/new.html.erb +++ b/app/views/passwordless/sessions/new.html.erb @@ -1,5 +1,7 @@ -<%= form_with model: @session, url: send(Passwordless.mounted_as).sign_in_path, data: { turbo: 'false' } do |f| %> +<%= form_with model: @session, url: send(:"#{params[:resource]}_sign_in_path"), data: { turbo: 'false' } do |f| %> <% email_field_name = :"passwordless[#{@email_field}]" %> - <%= text_field_tag email_field_name, params.fetch(email_field_name, nil), required: true %> - <%= f.submit I18n.t('passwordless.sessions.new.submit') %> + <%= text_field_tag email_field_name, + params.fetch(email_field_name, nil), + required: true %> + <%= f.submit I18n.t("passwordless.sessions.new.submit") %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index e1b8bca..0acdcf8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,8 +1,4 @@ # frozen_string_literal: true Passwordless::Engine.routes.draw do - get("/sign_in", to: "sessions#new", as: :sign_in) - post("/sign_in", to: "sessions#create") - get("/sign_in/:token", to: "sessions#show", as: :token_sign_in) - match("/sign_out", to: "sessions#destroy", via: %i[get delete], as: :sign_out) end diff --git a/lib/passwordless/engine.rb b/lib/passwordless/engine.rb index 8231390..67e64df 100644 --- a/lib/passwordless/engine.rb +++ b/lib/passwordless/engine.rb @@ -3,8 +3,6 @@ module Passwordless # Engine that runs the passwordless gem. class Engine < ::Rails::Engine - isolate_namespace Passwordless - config.to_prepare do require "passwordless/router_helpers" diff --git a/lib/passwordless/router_helpers.rb b/lib/passwordless/router_helpers.rb index dafca84..905d613 100644 --- a/lib/passwordless/router_helpers.rb +++ b/lib/passwordless/router_helpers.rb @@ -16,17 +16,16 @@ module RouterHelpers # helpers (using the above example in a view: # <%= link_to 'Sign in', user_session_things.sign_in_path %>). # (Default: resource.to_s) - def passwordless_for(resource, at: nil, as: nil) - mount_at = at || resource.to_s - mount_as = as || resource.to_s - mount( - Passwordless::Engine, - at: mount_at, - as: mount_as, - defaults: {authenticatable: resource.to_s.singularize} - ) + def passwordless_for(resource, at: :na, as: :na) + at == :na && at = "/#{resource.to_s}" + as == :na && as = "#{resource.to_s}_" - Passwordless.mounted_as = mount_as + scope(defaults: {authenticatable: resource.to_s.singularize, resource: resource}) do + get("#{at}/sign_in", to: "passwordless/sessions#new", as: :"#{as}sign_in") + post("#{at}/sign_in", to: "passwordless/sessions#create") + get("#{at}/sign_in/:token", to: "passwordless/sessions#show", as: :"#{as}token_sign_in") + match("#{at}/sign_out", to: "passwordless/sessions#destroy", via: %i[get delete], as: :"#{as}sign_out") + end end end end diff --git a/passwordless.gemspec b/passwordless.gemspec index 2e8b468..ffaf69d 100644 --- a/passwordless.gemspec +++ b/passwordless.gemspec @@ -1,4 +1,4 @@ -$LOAD_PATH.push File.expand_path("../lib", __FILE__) +$LOAD_PATH.push(File.expand_path("../lib", __FILE__)) # Maintain your gem's version: require "passwordless/version" @@ -16,10 +16,11 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] - s.add_dependency "rails", ">= 5.1.4" - s.add_dependency "bcrypt", "~> 3.1.11" + s.add_dependency("rails", ">= 5.1.4") + s.add_dependency("bcrypt", ">= 3.1.11") - s.add_development_dependency "sqlite3", ">= 1.4.1" - s.add_development_dependency "yard" - s.add_development_dependency "standard" + s.add_development_dependency("sqlite3") + s.add_development_dependency("yard") + s.add_development_dependency("standard") + s.add_development_dependency("minitest") end diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb index a875343..59a9f20 100644 --- a/test/dummy/app/views/layouts/application.html.erb +++ b/test/dummy/app/views/layouts/application.html.erb @@ -7,9 +7,9 @@ <% if current_user %> - <%= link_to "Sign out", users.sign_out_path %> + <%= link_to "Sign out", users_sign_out_path %> <% else %> - <%= link_to "Sign in", users.sign_in_path %> + <%= link_to "Sign in", users_sign_in_path %> <% end %> <%= debug current_user.inspect %> diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb index b4947d4..4945d1f 100644 --- a/test/dummy/config/application.rb +++ b/test/dummy/config/application.rb @@ -24,6 +24,8 @@ 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" diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb index 720e0f5..da90e5c 100644 --- a/test/dummy/config/routes.rb +++ b/test/dummy/config/routes.rb @@ -2,6 +2,7 @@ Rails.application.routes.draw do passwordless_for(:users) + passwordless_for(:admins) resources(:users) resources(:registrations, only: %i[new create]) diff --git a/test/passwordless_for_test.rb b/test/passwordless_for_test.rb index c0885bc..dac9a33 100644 --- a/test/passwordless_for_test.rb +++ b/test/passwordless_for_test.rb @@ -6,27 +6,95 @@ module Passwordless class PasswordlessForTest < ActionDispatch::IntegrationTest test("map sign in for user") do assert_recognizes( - {controller: "passwordless/sessions", action: "new", authenticatable: "user"}, + { + controller: "passwordless/sessions", + action: "new", + authenticatable: "user", + resource: "users" + }, {method: :get, path: "/users/sign_in"}, - {authenticatable: "user"} + {authenticatable: "user", resource: "users"} ) assert_recognizes( - {controller: "passwordless/sessions", action: "create", authenticatable: "user"}, + { + controller: "passwordless/sessions", + action: "create", + authenticatable: "user", + resource: "users" + }, {method: :post, path: "/users/sign_in", params: {passwordless: {email: "a@a"}}}, - {authenticatable: "user"} + {authenticatable: "user", resource: "users"} ) assert_recognizes( - {controller: "passwordless/sessions", action: "show", authenticatable: "user", token: "abc123"}, + { + controller: "passwordless/sessions", + action: "show", + authenticatable: "user", + resource: "users", + token: "abc123" + }, {method: :get, path: "/users/sign_in/abc123", params: {token: "abc123"}}, - {authenticatable: "user"} + {authenticatable: "user", resource: "users"} ) assert_recognizes( - {controller: "passwordless/sessions", action: "destroy", authenticatable: "user"}, + { + controller: "passwordless/sessions", + action: "destroy", + authenticatable: "user", + resource: "users" + }, {method: :delete, path: "/users/sign_out"}, - {authenticatable: "user"} + {authenticatable: "user", resource: "users"} + ) + end + + test("map sign in for admin") do + assert_recognizes( + { + controller: "passwordless/sessions", + action: "new", + authenticatable: "admin", + resource: "admins" + }, + {method: :get, path: "/admins/sign_in"}, + {authenticatable: "admin", resource: "admins"} + ) + + assert_recognizes( + { + controller: "passwordless/sessions", + action: "create", + authenticatable: "admin", + resource: "admins" + }, + {method: :post, path: "/admins/sign_in", params: {passwordless: {email: "a@a"}}}, + {authenticatable: "admin", resource: "admins"} + ) + + assert_recognizes( + { + controller: "passwordless/sessions", + action: "show", + authenticatable: "admin", + resource: "admins", + token: "abc123" + }, + {method: :get, path: "/admins/sign_in/abc123", params: {token: "abc123"}}, + {authenticatable: "admin", resource: "admins"} + ) + + assert_recognizes( + { + controller: "passwordless/sessions", + action: "destroy", + authenticatable: "admin", + resource: "admins" + }, + {method: :delete, path: "/admins/sign_out"}, + {authenticatable: "admin", resource: "admins"} ) end end diff --git a/test/test_helper.rb b/test/test_helper.rb index 4617c99..0b9f82d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "simplecov" +require "minitest/pride" SimpleCov.start do add_filter("test/dummy")