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

Allow override for finding record during session creation #33

Merged
merged 3 commits into from
Jan 4, 2019
Merged
Show file tree
Hide file tree
Changes from 2 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
42 changes: 31 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Add authentication to your Rails app without all the icky-ness of passwords.
* [Usage](#usage)
* [Getting the current user, restricting access, the usual](#getting-the-current-user-restricting-access-the-usual)
* [Providing your own templates](#providing-your-own-templates)
* [Overrides](#overrides)
* [Registering new users](#registering-new-users)
* [Generating tokens](#generating-tokens)
* [Redirecting back after sign-in](#redirecting-back-after-sign-in)
Expand Down Expand Up @@ -51,7 +52,7 @@ Then specify which field on your `User` record is the email field with:
```ruby
class User < ApplicationRecord
validates :email, presence: true, uniqueness: { case_sensitive: false }

passwordless_with :email # <-- here!
end
```
Expand All @@ -73,13 +74,13 @@ Passwordless doesn't give you `current_user` automatically -- it's dead easy to
```ruby
class ApplicationController < ActionController::Base
include Passwordless::ControllerHelpers # <-- This!

# ...

helper_method :current_user

private

def current_user
@current_user ||= authenticate_by_cookie(User)
end
Expand All @@ -96,7 +97,7 @@ Et voilà:
```ruby
class VerySecretThingsController < ApplicationController
before_action :require_user!

def index
@things = current_user.very_secret_things
end
Expand All @@ -118,6 +119,25 @@ app/views/passwordless/mailer/magic_link.text.erb

See [the bundled views](https://github.com/mikker/passwordless/tree/master/app/views/passwordless).

### Overrides

By default `passwordless` uses the `passwordless_with` column you specify in the model to case insensitively fetch the resource during authentication. You can override this and provide your own customer fetcher by defining a class method `fetch_resource_for_passwordless` in your passwordless model. The method will be supplied with the downcased email and should return an `ActiveRecord` instance of the model.

Example time:
mikker marked this conversation as resolved.
Show resolved Hide resolved

Let's say we would like to fetch the record and if it doesn't exist, create automatically.

```ruby
class User < ApplicationRecord
mikker marked this conversation as resolved.
Show resolved Hide resolved
def self.fetch_resource_for_passwordless(email)
record = where("lower(email) = ?", email).first
return record if record.present?

create(email: email)
end
end
```

### Registering new users

Because your `User` record is like any other record, you create one like you normally would. Passwordless provides a helper method you can use to sign in the created user after it is saved like so:
Expand All @@ -129,15 +149,15 @@ class UsersController < ApplicationController

def create
@user = User.new user_params

if @user.save
sign_in @user # <-- And this!
redirect_to @user, flash: {notice: 'Welcome!'}
else
render :new
end
end

# ...
end
```
Expand All @@ -161,9 +181,9 @@ By default Passwordless will redirect back to where the user wanted to go **if**
```ruby
class ApplicationController < ActionController::Base
include Passwordless::ControllerHelpers # <-- Probably already have this!

# ...

def require_user!
return if current_user
save_passwordless_redirect_location!(User) # <-- here we go!
Expand All @@ -181,7 +201,7 @@ By default, Passwordless uses the resource name given to `passwordless_for` to g
```ruby
passwordless_for :users
# <%= users.sign_in_path %> # => /users/sign_in

passwordless_for :users, at: '/', as: :auth
# <%= auth.sign_in_path %> # => /sign_in
```
Expand Down
12 changes: 9 additions & 3 deletions app/controllers/passwordless/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,15 @@ def email_field
end

def find_authenticatable
authenticatable_class.where(
"lower(#{email_field}) = ?", params[:passwordless][email_field].downcase
).first
email = params[:passwordless][email_field].downcase

if authenticatable_class.respond_to?(:fetch_resource_for_passwordless)
authenticatable_class.fetch_resource_for_passwordless(email)
else
authenticatable_class.where(
"lower(#{email_field}) = ?", params[:passwordless][email_field].downcase
).first
end
end

def find_session
Expand Down
20 changes: 19 additions & 1 deletion test/controllers/passwordless/sessions_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ def create_session_for(user)
)
end

def User.fetch_resource_for_passwordless(email)
return if email == 'invalidemail'

User.find_or_create_by(email: email)
end
mikker marked this conversation as resolved.
Show resolved Hide resolved

test 'requesting a magic link as an existing user' do
User.create email: 'a@a'

Expand All @@ -31,13 +37,25 @@ def create_session_for(user)
assert_equal 200, status

post '/users/sign_in',
params: { passwordless: { email: 'something_em@ilish' } },
params: { passwordless: { email: 'invalidemail' } },
headers: { 'User-Agent': 'an actual monkey' }
assert_equal 200, status

assert_equal 0, ActionMailer::Base.deliveries.size
end

test 'requesting a magic link with overridden fetch method' do
get '/users/sign_in'
assert_equal 200, status

post '/users/sign_in',
params: { passwordless: { email: 'overriden_email@example' } },
headers: { 'User-Agent': 'an actual monkey' }
assert_equal 200, status

assert_equal 1, ActionMailer::Base.deliveries.size
end

test 'signing in via a token' do
user = User.create email: 'a@a'
session = create_session_for user
Expand Down