passwordless 0.12.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b90e8f97825d92f0728154737c428d39cfecbedc9e02bbe6948d0861dd5e9c39
4
- data.tar.gz: 2a5b288bf8c16004c6ec8fe7b8937e9f72496475e6e9af4e6a70c32a7a1d05dc
3
+ metadata.gz: 24d02d4dc7676adee968e3f922097f744ee41c2a1826d7622ffac01f3aaf76b8
4
+ data.tar.gz: 03624c6400113e2071eb038cd9406102870b3a56e1f8fa790bb9c190f17b95d5
5
5
  SHA512:
6
- metadata.gz: 2350958cc2cb4628a6242a6c86ef08b3962fef2ba12fd9ed5a1bf8727f9254fc61fb77e9fa946bb015ebfc98eb81563d1e56aeb1c40d1cbc2ef96e66af512de9
7
- data.tar.gz: 5fed7a3d7541a302fa9d6fc802bd530002846ea8cc4dae2057d03fdd799d66263752c766578bd96a441ab584d539739fbe55a63f7e258c5a7e1b16fdecb9eb7a
6
+ metadata.gz: 34e67e3b5a2be5cc657ee7193fa0ea76fc27ae7aad32413c66c4534bd2ce91540d7fe3ccdb2a2816a9d46053fd06d2f9c840d69e14cb7e5c49a45b3933be418e
7
+ data.tar.gz: ed550ba88a988109ad8192120fa25225c2c2fc161caff9beb9f51f5bd54baf72325c5baf02fb42da72a2a23ccb923687959aea517c2ce00879f580f82ab24559
data/README.md CHANGED
@@ -10,42 +10,19 @@ Add authentication to your Rails app without all the icky-ness of passwords.
10
10
 
11
11
  ---
12
12
 
13
- ## Table of Contents
14
-
15
- * [Installation](#installation)
16
- * [Usage](#usage)
17
- * [Getting the current user, restricting access, the usual](#getting-the-current-user-restricting-access-the-usual)
18
- * [Providing your own templates](#providing-your-own-templates)
19
- * [Registering new users](#registering-new-users)
20
- * [URLs and links](#urls-and-links)
21
- * [Customize the way to send magic link](#customize-the-way-to-send-magic-link)
22
- * [Generate your own magic links](#generate-your-own-magic-links)
23
- * [Overrides](#overrides)
24
- * [Configuration](#configuration)
25
- * [Customising token generation](#generating-tokens)
26
- * [Token and Session Expiry](#token-and-session-expiry)
27
- * [Redirecting back after sign-in](#redirecting-back-after-sign-in)
28
- * [Claiming tokens](#claiming-tokens)
29
- * [Supporting UUID primary keys](#supporting-uuid-primary-keys)
30
- * [Testing helpers](#testing-helpers)
31
- * [E-mail security](#e-mail-security)
32
- * [License](#license)
33
-
34
13
  ## Installation
35
14
 
36
- Add the `passwordless` gem to your `Gemfile`:
37
-
38
- ```ruby
39
- gem 'passwordless'
40
- ```
41
-
42
- Install it and copy over the migrations:
15
+ Add to your bundle and copy over the migrations:
43
16
 
44
17
  ```sh
45
- $ bundle
18
+ $ bundle add passwordless
46
19
  $ bin/rails passwordless:install:migrations
47
20
  ```
48
21
 
22
+ ### Upgrading
23
+
24
+ See [Upgrading to Passwordless 1.0](docs/upgrading_to_1_0.md) for more details.
25
+
49
26
  ## Usage
50
27
 
51
28
  Passwordless creates a single model called `Passwordless::Session`. It doesn't come with its own `User` model, it expects you to create one:
@@ -97,6 +74,7 @@ class ApplicationController < ActionController::Base
97
74
 
98
75
  def require_user!
99
76
  return if current_user
77
+ save_passwordless_redirect_location!(User) # <-- optional, see below
100
78
  redirect_to root_path, flash: { error: 'You are not worthy!' }
101
79
  end
102
80
  end
@@ -116,30 +94,19 @@ end
116
94
 
117
95
  ### Providing your own templates
118
96
 
119
- Override `passwordless`' bundled views by adding your own. You can manually copy the specific views that you need or copy them to your application with `rails generate passwordless:views`.
97
+ To make Passwordless look like your app, override the bundled views by adding your own. You can manually copy the specific views that you need or copy them to your application with `rails generate passwordless:views`.
120
98
 
121
- `passwordless` has 2 action views and 1 mailer view:
99
+ Passwordless has 2 action views and 1 mailer view:
122
100
 
123
101
  ```sh
124
102
  # the form where the user inputs their email address
125
103
  app/views/passwordless/sessions/new.html.erb
126
- # shown after a user requests a magic link
127
- app/views/passwordless/sessions/create.html.erb
128
- # the mail with the magic link that gets sent
129
- app/views/passwordless/mailer/magic_link.text.erb
130
- ```
131
-
132
- If you'd like to let the user know whether or not a record was found, `@resource` is provided to the view. You may override `app/views/passwordless/session/create.html.erb` for example like so:
133
- ```erb
134
- <% if @resource.present? %>
135
- <p>User found, check your inbox</p>
136
- <% else %>
137
- <p>No user found with the provided email address</p>
138
- <% end %>
104
+ # the form where the user inputs their just received token
105
+ app/views/passwordless/sessions/show.html.erb
106
+ # the email with the token and magic link
107
+ app/views/passwordless/mailer/sign_in.text.erb
139
108
  ```
140
109
 
141
- Please note that, from a security standpoint, this is a **bad practice** because you'd be giving information about which users are registered on your system. It is recommended to use a single message similar to the default one: "If we found you in the system, we've sent you an email". The **best practice** is to never expose which emails are registered on your system.
142
-
143
110
  See [the bundled views](https://github.com/mikker/passwordless/tree/master/app/views/passwordless).
144
111
 
145
112
  ### Registering new users
@@ -152,13 +119,13 @@ class UsersController < ApplicationController
152
119
  # (unless you already have it in your ApplicationController)
153
120
 
154
121
  def create
155
- @user = User.new user_params
122
+ @user = User.new(user_params)
156
123
 
157
124
  if @user.save
158
- sign_in @user # <-- This!
159
- redirect_to @user, flash: { notice: 'Welcome!' }
125
+ sign_in(build_passwordless_session(@user)) # <-- This!
126
+ redirect_to(@user, flash: { notice: 'Welcome!' })
160
127
  else
161
- render :new
128
+ render(:new)
162
129
  end
163
130
  end
164
131
 
@@ -172,137 +139,90 @@ By default, Passwordless uses the resource name given to `passwordless_for` to g
172
139
 
173
140
  ```ruby
174
141
  passwordless_for :users
175
- # <%= users.sign_in_path %> # => /users/sign_in
142
+ # <%= users_sign_in_path %> # => /users/sign_in
176
143
 
177
144
  passwordless_for :users, at: '/', as: :auth
178
- # <%= auth.sign_in_path %> # => /sign_in
145
+ # <%= auth_sign_in_path %> # => /sign_in
179
146
  ```
180
147
 
181
- Also be sure to [specify ActionMailer's `default_url_options.host`](http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views).
148
+ Also be sure to
149
+ [specify ActionMailer's `default_url_options.host`](http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views).
182
150
 
183
- ### Customize the way to send magic link
151
+ ## Configuration
184
152
 
185
- By default, magic link will send by email. You can customize this method. For example, you can send magic link via SMS.
153
+ To customize Passwordless, create a file `config/initializers/passwordless.rb`.
186
154
 
187
- config/initializers/passwordless.rb
155
+ The default values are shown below. It's recommended to only include the ones that you specifically want to modify.
188
156
 
189
157
  ```ruby
190
- Passwordless.after_session_save = lambda do |session, request|
191
- # Default behavior is
192
- # Passwordless::Mailer.magic_link(session).deliver_now
193
-
194
- # You can change behavior to do something with session model. For example,
195
- # session.authenticatable.send_sms
158
+ Passwordless.configure do |config|
159
+ config.default_from_address = "CHANGE_ME@example.com"
160
+ config.parent_mailer = "ActionMailer::Base"
161
+ config.restrict_token_reuse = false # Can a token/link be used multiple times?
162
+ config.token_generator = Passwordless::ShortTokenGenerator.new # Used to generate magic link tokens.
163
+
164
+ config.expires_at = lambda { 1.year.from_now } # How long until a signed in session expires.
165
+ config.timeout_at = lambda { 10.minutes.from_now } # How long until a token/magic link times out.
166
+
167
+ config.redirect_back_after_sign_in = true # When enabled the user will be redirected to their previous page, or a page specified by the `destination_path` query parameter, if available.
168
+ config.redirect_to_response_options = {} # Additional options for redirects.
169
+ config.success_redirect_path = '/' # After a user successfully signs in
170
+ config.failure_redirect_path = '/' # After a sign in fails
171
+ config.sign_out_redirect_path = '/' # After a user signs out
196
172
  end
197
173
  ```
198
174
 
199
- You can access user model through authenticatable.
175
+ ### Delivery method
200
176
 
201
- ### Generate your own magic links
177
+ By default, Passwordless sends emails. See [Providing your own templates](#providing-your-own-templates). If you need to customize this further, you can do so in the `after_session_save` callback.
202
178
 
203
- Currently there is not an officially supported way to generate your own magic links to send in your own mailers.
204
-
205
- However, you can accomplish this with the following snippet of code.
179
+ In `config/initializers/passwordless.rb`:
206
180
 
207
181
  ```ruby
208
- session = Passwordless::Session.new({
209
- authenticatable: @manager,
210
- user_agent: 'Command Line',
211
- remote_addr: 'unknown',
212
- })
213
- session.save!
214
- @magic_link = send(Passwordless.mounted_as).token_sign_in_url(session.token)
215
- ```
182
+ Passwordless.configure do |config|
183
+ config.after_session_save = lambda do |session, request|
184
+ # Default behavior is
185
+ # Passwordless::Mailer.sign_in(session).deliver_now
216
186
 
217
- You can further customize this URL by specifying the destination path to be redirected to after the user has logged in. You can do this by adding the `destination_path` query parameter to the end of the URL. For example
218
- ```
219
- @magic_link = "#{@magic_link}?destination_path=/your-custom-path"
220
- ```
221
-
222
- ### Overrides
223
-
224
- By default `passwordless` uses the `passwordless_with` column to _case insensitively_ fetch the resource.
225
-
226
- 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 called with the downcased email and should return an `ActiveRecord` instance of the model.
227
-
228
- Example time:
229
-
230
- Let's say we would like to fetch the record and if it doesn't exist, create automatically.
231
-
232
- ```ruby
233
- class User < ApplicationRecord
234
- def self.fetch_resource_for_passwordless(email)
235
- find_or_create_by(email: email)
187
+ # You can change behavior to do something with session model. For example,
188
+ # SmsApi.send_sms(session.authenticatable.phone_number, session.token)
236
189
  end
237
190
  end
238
191
  ```
239
192
 
240
- ## Configuration
241
-
242
- The following configuration parameters are supported. You can override these for example in `initializers/passwordless.rb`.
193
+ ### Token generation
243
194
 
244
- The default values are shown below. It's recommended to only include the ones that you specifically want to override.
195
+ By default Passwordless generates short, 6-digit, alpha numeric tokens. You can change the generator using `Passwordless.config.token_generator` to something else that responds to `call(session)` eg.:
245
196
 
246
197
  ```ruby
247
- Passwordless.default_from_address = "CHANGE_ME@example.com"
248
- Passwordless.parent_mailer = "ActionMailer::Base"
249
- Passwordless.token_generator = Passwordless::UrlSafeBase64Generator.new # Used to generate magic link tokens.
250
- Passwordless.restrict_token_reuse = false # By default a magic link token can be used multiple times.
251
- Passwordless.redirect_back_after_sign_in = true # When enabled the user will be redirected to their previous page, or a page specified by the `destination_path` query parameter, if available.
252
-
253
- Passwordless.expires_at = lambda { 1.year.from_now } # How long until a passwordless session expires.
254
- Passwordless.timeout_at = lambda { 1.hour.from_now } # How long until a magic link expires.
255
-
256
- # redirection session behavior
257
- Passwordless.redirect_to_response_options = {} # any allowed response_options for redirect_to can go in here
258
-
259
- # Default redirection paths
260
- Passwordless.success_redirect_path = '/' # When a user succeeds in logging in.
261
- Passwordless.failure_redirect_path = '/' # When a a login is failed for any reason.
262
- Passwordless.sign_out_redirect_path = '/' # When a user logs out.
263
- ```
264
-
265
- ### Customizing token generation
266
-
267
- By default Passwordless generates tokens using `SecureRandom.urlsafe_base64` but you can change that by setting `Passwordless.token_generator` to something else that responds to `call(session)` eg.:
268
-
269
- ```ruby
270
- Passwordless.token_generator = -> (session) {
271
- "probably-stupid-token-#{session.user_agent}-#{Time.current}"
272
- }
198
+ Passwordless.configure do |config|
199
+ config.token_generator = lambda do |session|
200
+ "probably-stupid-token-#{session.user_agent}-#{Time.current}"
201
+ end
202
+ end
273
203
  ```
274
204
 
275
- Session is going to keep generating tokens until it finds one that hasn't been used yet. So be sure to use some kind of method where matches are unlikely.
205
+ Passwordless will keep generating tokens until it finds one that hasn't been used yet. So be sure to use some kind of method where matches are unlikely.
276
206
 
277
- ### Token and Session Expiry
207
+ ### Timeout and Expiry
278
208
 
279
- Token timeout is the time by which the sign in token is invalidated. Post the timeout, the token cannot be used to sign-in to the app and the user would need to request it again.
209
+ The _timeout_ is the time by which the generated token and magic link is invalidated. After this the token cannot be used to sign in to your app and the user will need to request a new token.
280
210
 
281
- Session expiry is the expiration time of the session of a logged in user. Once this is expired, user would need to log back in to create a new session.
211
+ The _expiry_ is the expiration time of the session of a logged in user. Once this is expired, the user is signed out.
282
212
 
283
- #### Token timeout
213
+ **Note:** Passwordless' session relies on Rails' own session and so will never live longer than that.
284
214
 
285
- By default, sign in tokens generated by Passwordless are made invalid after `1.hour` from the time they are generated. If you wish you can override this and supply your custom Proc function that will return a valid datetime object. Make sure the generated time is in the future.
286
-
287
- > Make sure to use a `.call`able object, like a proc or lambda as it will be called everytime a session is created.
215
+ To configure your Rails session, in `config/initializers/session_store.rb`:
288
216
 
289
217
  ```ruby
290
- Passwordless.timeout_at = lambda { 2.hours.from_now }
291
- ```
292
-
293
- #### Session Expiry
294
-
295
- Session expiry is the time when the actual session is itself expired, i.e. users will be logged out and has to sign back in post this expiry time. By default, sessions are valid for `1.year` from the time they are generated. You can override by providing your custom Proc function that returns a datetime object.
296
-
297
- > Make sure to use a `.call`able object, like a proc or lambda as it will be called everytime a session is created.
298
-
299
- ```ruby
300
- Passwordless.expires_at = lambda { 24.hours.from_now }
218
+ Rails.application.config.session_store :cookie_store,
219
+ expire_after: 1.year,
220
+ # ...
301
221
  ```
302
222
 
303
- ### Redirecting back after sign-in
223
+ ### Redirection after sign-in
304
224
 
305
- By default Passwordless will redirect back to where the user wanted to go **if** it knows where that is, so you'll have to help it. `Passwordless::ControllerHelpers` provide a method for this:
225
+ By default Passwordless will redirect back to where the user wanted to go _if_ it knows where that is -- so you'll have to help it. `Passwordless::ControllerHelpers` provide a method:
306
226
 
307
227
  ```ruby
308
228
  class ApplicationController < ActionController::Base
@@ -312,71 +232,45 @@ class ApplicationController < ActionController::Base
312
232
 
313
233
  def require_user!
314
234
  return if current_user
315
- save_passwordless_redirect_location!(User) # <-- here we go!
235
+ save_passwordless_redirect_location!(User) # <-- this one!
316
236
  redirect_to root_path, flash: {error: 'You are not worthy!'}
317
237
  end
318
238
  end
319
239
  ```
320
240
 
321
- This can be turned off with `Passwordless.redirect_back_after_sign_in = false` but if you just don't save the previous destination, you'll be fine.
322
-
323
- ### Claiming tokens
324
-
325
- Opt-in for marking tokens as `claimed` so they can only be used once.
326
-
327
- config/initializers/passwordless.rb
328
-
329
- ```ruby
330
- # Default is `false`
331
- Passwordless.restrict_token_reuse = true
332
- ```
333
-
334
- #### Upgrading an existing Rails app to use claim token
241
+ This can also be turned off with `Passwordless.config.redirect_back_after_sign_in = false`.
335
242
 
336
- The simplest way to update your sessions table is with a single migration:
243
+ ### Looking up the user
337
244
 
338
- <details>
339
- <summary>Example migration</summary>
245
+ By default Passwordless uses the `passwordless_with` column to _case insensitively_ fetch the user resource.
340
246
 
341
- ```bash
342
- bin/rails generate migration add_claimed_at_to_passwordless_sessions
343
- ```
247
+ You can override this by defining a class method `fetch_resource_for_passwordless` in your user model. This method will be called with the down-cased, stripped `email` and should return an `ActiveRecord` instance.
344
248
 
345
249
  ```ruby
346
- class AddClaimedAtToPasswordlessSessions < ActiveRecord::Migration[5.2]
347
- def change
348
- add_column :passwordless_sessions, :claimed_at, :datetime
250
+ class User < ApplicationRecord
251
+ def self.fetch_resource_for_passwordless(email)
252
+ find_or_create_by(email: email)
349
253
  end
350
254
  end
351
-
352
255
  ```
353
- </details>
354
256
 
355
- ### Supporting UUID primary keys
257
+ ### Claiming tokens
258
+
259
+ By default, a token/magic link **can** be used more than once.
356
260
 
357
- If your `users` table uses UUIDs for its primary keys, you will need to add a migration
358
- to change the type of `passwordless`' `authenticatable_id` field to match your primary key type (this will also involve dropping and recreating associated indices).
261
+ To change, in `config/initializers/passwordless.rb`:
359
262
 
360
- Here is an example migration you can use:
361
263
  ```ruby
362
- class SupportUuidInPasswordlessSessions < ActiveRecord::Migration[6.0]
363
- def change
364
- remove_index :passwordless_sessions, column: [:authenticatable_type, :authenticatable_id] if index_exists? :authenticatable_type, :authenticatable_id
365
- remove_column :passwordless_sessions, :authenticatable_id
366
- add_column :passwordless_sessions, :authenticatable_id, :uuid
367
- add_index :passwordless_sessions, [:authenticatable_type, :authenticatable_id], name: 'authenticatable'
368
- end
264
+ Passwordless.configure do |config|
265
+ config.restrict_token_reuse = true
369
266
  end
370
267
  ```
371
268
 
372
- Alternatively, you can use `add_reference` with `type: :uuid` in your migration (see docs [here](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference)).
373
-
374
- ## Testing helpers
269
+ ## Test helpers
375
270
 
376
271
  To help with testing, a set of test helpers are provided.
377
272
 
378
- If you are using RSpec, add the following line to your `spec/rails_helper.rb` or
379
- `spec/spec_helper.rb` if `rails_helper.rb` does not exist:
273
+ If you are using RSpec, add the following line to your `spec/rails_helper.rb`:
380
274
 
381
275
  ```ruby
382
276
  require "passwordless/test_helpers"
@@ -388,7 +282,6 @@ If you are using TestUnit, add this line to your `test/test_helper.rb`:
388
282
  require "passwordless/test_helpers"
389
283
  ```
390
284
 
391
-
392
285
  Then in your controller, request, and system tests/specs, you can utilize the following methods:
393
286
 
394
287
  ```ruby
@@ -396,18 +289,18 @@ passwordless_sign_in(user) # signs you in as a user
396
289
  passwordless_sign_out # signs out user
397
290
  ```
398
291
 
399
- ## E-mail security
292
+ ## Security considerations
400
293
 
401
- There's no reason that this approach should be less secure than the usual username/password combo. In fact this is most often a more secure option, as users don't get to choose the weak passwords they still use. In a way this is just the same as having each user go through "Forgot password" on every login.
294
+ There's no reason that this approach should be less secure than the usual username/password combo. In fact this is most often a more secure option, as users don't get to choose the horrible passwords they can't seem to stop using. In a way, this is just the same as having each user go through "Forgot password" on every login.
402
295
 
403
- But be aware that when everyone authenticates via emails you send, the way you send those mails becomes a weak spot. Email services usually provide a log of all the mails you send so if your app's account is compromised, every user in the system is as well. (This is the same for "Forgot password".) [Reddit was compromised](https://thenextweb.com/hardfork/2018/01/05/reddit-bitcoin-cash-stolen-hack/) using this method.
296
+ But be aware that when everyone authenticates via emails, the way you send those mails becomes a weak spot. Email services usually provide a log of all the mails you send so if your email delivery provider account is compromised, every user in the system is as well. (This is the same for "Forgot password".) [Reddit was once compromised](https://thenextweb.com/hardfork/2018/01/05/reddit-bitcoin-cash-stolen-hack/) using this method.
404
297
 
405
- Ideally you should set up your email provider to not log these mails. And be sure to turn on 2-factor auth if your provider supports it.
298
+ Ideally you should set up your email provider to not log these mails. And be sure to turn on non-SMS 2-factor authentication if your provider supports it.
406
299
 
407
- # Alternatives
300
+ ## Alternatives
408
301
 
409
302
  - [OTP JWT](https://github.com/stas/otp-jwt) -- Passwordless JSON Web Tokens
410
303
 
411
- # License
304
+ ## License
412
305
 
413
306
  MIT
data/Rakefile CHANGED
@@ -2,17 +2,19 @@
2
2
 
3
3
  begin
4
4
  require "bundler/setup"
5
+
5
6
  rescue LoadError
6
- puts "You must `gem install bundler` and `bundle install` to run rake tasks"
7
+ puts("You must `gem install bundler` and `bundle install` to run rake tasks")
7
8
  end
8
9
 
9
10
  require "yard"
11
+
10
12
  YARD::Rake::YardocTask.new
11
- task docs: :yard
13
+ task(docs: :yard)
12
14
 
13
15
  APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
14
- load "rails/tasks/engine.rake"
15
- load "rails/tasks/statistics.rake"
16
+ load("rails/tasks/engine.rake")
17
+ load("rails/tasks/statistics.rake")
16
18
 
17
19
  require "bundler/gem_tasks"
18
20
 
@@ -24,6 +26,4 @@ Rake::TestTask.new(:test) do |t|
24
26
  t.verbose = false
25
27
  end
26
28
 
27
- task default: :test
28
-
29
- require "standard/rake"
29
+ task(default: :test)