Skip to content

Commit

Permalink
Un-isolate namespace (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikker committed Jun 16, 2023
1 parent a64d5f9 commit ac267ff
Show file tree
Hide file tree
Showing 14 changed files with 121 additions and 41 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 5 additions & 5 deletions app/controllers/passwordless/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ 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
@email_field = email_field
@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
Expand All @@ -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_
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/mailers/passwordless/mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions app/models/passwordless/session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 5 additions & 3 deletions app/views/passwordless/sessions/new.html.erb
Original file line number Diff line number Diff line change
@@ -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 %>
4 changes: 0 additions & 4 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -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
2 changes: 0 additions & 2 deletions lib/passwordless/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
19 changes: 9 additions & 10 deletions lib/passwordless/router_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 7 additions & 6 deletions passwordless.gemspec
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
4 changes: 2 additions & 2 deletions test/dummy/app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

<body>
<% 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 %>
Expand Down
2 changes: 2 additions & 0 deletions test/dummy/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions test/dummy/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

Rails.application.routes.draw do
passwordless_for(:users)
passwordless_for(:admins)

resources(:users)
resources(:registrations, only: %i[new create])
Expand Down
84 changes: 76 additions & 8 deletions test/passwordless_for_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "simplecov"
require "minitest/pride"

SimpleCov.start do
add_filter("test/dummy")
Expand Down

0 comments on commit ac267ff

Please sign in to comment.