passwordless 0.12.0 → 1.0.0.beta1

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: '09a1ddce2bcfe831bf08b8e0e2e48ce7d8941ea4a44ed9ea0091de3ece68b9f3'
4
+ data.tar.gz: 35d3d30954025caa77b06a2ff6c51579cbe78135579733cdb92323cd1c140e7f
5
5
  SHA512:
6
- metadata.gz: 2350958cc2cb4628a6242a6c86ef08b3962fef2ba12fd9ed5a1bf8727f9254fc61fb77e9fa946bb015ebfc98eb81563d1e56aeb1c40d1cbc2ef96e66af512de9
7
- data.tar.gz: 5fed7a3d7541a302fa9d6fc802bd530002846ea8cc4dae2057d03fdd799d66263752c766578bd96a441ab584d539739fbe55a63f7e258c5a7e1b16fdecb9eb7a
6
+ metadata.gz: 16be37d7458a6749f1df567fa15b7e480913a21a85ec3fab9dfabc737e4ab85308a93f387c663939b863967968e9e26c9e3bdfb1ec1b26e957570a109bd170ea
7
+ data.tar.gz: 5715a453783257aa2f3065f85e9a2b3a71079fca9519ff7fbc92e0b5dfe491b5b8846f7dfd24e458b15589746be0c5fd6d5996ec7c3feebc2ed9bf6c3aa1836a
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ **NOTE:** Passwordless is currently going through some breaking changes. Be aware that the docs in `master` aren't necessarily the same as for you installed version.
2
+
3
+ ---
4
+
1
5
  <p align='center'>
2
6
  <img src='https://s3.brnbw.com/Passwordless-title-gaIVkX0sPg.svg' alt='Passwordless' />
3
7
  <br />
@@ -12,37 +16,34 @@ Add authentication to your Rails app without all the icky-ness of passwords.
12
16
 
13
17
  ## Table of Contents
14
18
 
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)
19
+ <!--toc:start-->
20
+
21
+ - [Table of Contents](#table-of-contents)
22
+ - [Installation](#installation)
23
+ - [Usage](#usage)
24
+ - [Getting the current user, restricting access, the usual](#getting-the-current-user-restricting-access-the-usual)
25
+ - [Providing your own templates](#providing-your-own-templates)
26
+ - [Registering new users](#registering-new-users)
27
+ - [URLs and links](#urls-and-links)
28
+ - [Configuration](#configuration)
29
+ - [Delivery method](#delivery-method)
30
+ - [Token generation](#token-generation)
31
+ - [Timeout and Expiry](#timeout-and-expiry)
32
+ - [Redirection after sign-in](#redirection-after-sign-in)
33
+ - [Looking up the user](#looking-up-the-user)
34
+ - [Claiming tokens](#claiming-tokens)
35
+ - [Test helpers](#test-helpers)
36
+ - [Security considerations](#security-considerations)
37
+ - [Alternatives](#alternatives)
38
+ - [License](#license)
39
+ <!--toc:end-->
33
40
 
34
41
  ## Installation
35
42
 
36
- Add the `passwordless` gem to your `Gemfile`:
37
-
38
- ```ruby
39
- gem 'passwordless'
40
- ```
41
-
42
- Install it and copy over the migrations:
43
+ Add to your bundle and copy over the migrations:
43
44
 
44
45
  ```sh
45
- $ bundle
46
+ $ bundle add passwordless
46
47
  $ bin/rails passwordless:install:migrations
47
48
  ```
48
49
 
@@ -97,6 +98,7 @@ class ApplicationController < ActionController::Base
97
98
 
98
99
  def require_user!
99
100
  return if current_user
101
+ save_passwordless_redirect_location!(User) # <-- optional, see below
100
102
  redirect_to root_path, flash: { error: 'You are not worthy!' }
101
103
  end
102
104
  end
@@ -116,30 +118,19 @@ end
116
118
 
117
119
  ### Providing your own templates
118
120
 
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`.
121
+ 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
122
 
121
- `passwordless` has 2 action views and 1 mailer view:
123
+ Passwordless has 2 action views and 1 mailer view:
122
124
 
123
125
  ```sh
124
126
  # the form where the user inputs their email address
125
127
  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 %>
128
+ # the form where the user inputs their just received token
129
+ app/views/passwordless/sessions/show.html.erb
130
+ # the email with the token and magic link
131
+ app/views/passwordless/mailer/sign_in.text.erb
139
132
  ```
140
133
 
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
134
  See [the bundled views](https://github.com/mikker/passwordless/tree/master/app/views/passwordless).
144
135
 
145
136
  ### Registering new users
@@ -152,13 +143,13 @@ class UsersController < ApplicationController
152
143
  # (unless you already have it in your ApplicationController)
153
144
 
154
145
  def create
155
- @user = User.new user_params
146
+ @user = User.new(user_params)
156
147
 
157
148
  if @user.save
158
- sign_in @user # <-- This!
159
- redirect_to @user, flash: { notice: 'Welcome!' }
149
+ sign_in(build_passwordless_session(@user)) # <-- This!
150
+ redirect_to(@user, flash: { notice: 'Welcome!' })
160
151
  else
161
- render :new
152
+ render(:new)
162
153
  end
163
154
  end
164
155
 
@@ -172,137 +163,90 @@ By default, Passwordless uses the resource name given to `passwordless_for` to g
172
163
 
173
164
  ```ruby
174
165
  passwordless_for :users
175
- # <%= users.sign_in_path %> # => /users/sign_in
166
+ # <%= users_sign_in_path %> # => /users/sign_in
176
167
 
177
168
  passwordless_for :users, at: '/', as: :auth
178
- # <%= auth.sign_in_path %> # => /sign_in
169
+ # <%= auth_sign_in_path %> # => /sign_in
179
170
  ```
180
171
 
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).
172
+ Also be sure to
173
+ [specify ActionMailer's `default_url_options.host`](http://guides.rubyonrails.org/action_mailer_basics.html#generating-urls-in-action-mailer-views).
182
174
 
183
- ### Customize the way to send magic link
175
+ ## Configuration
184
176
 
185
- By default, magic link will send by email. You can customize this method. For example, you can send magic link via SMS.
177
+ To customize Passwordless, create a file `config/initializers/passwordless.rb`.
186
178
 
187
- config/initializers/passwordless.rb
179
+ The default values are shown below. It's recommended to only include the ones that you specifically want to modify.
188
180
 
189
181
  ```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
182
+ Passwordless.configure do |config|
183
+ config.default_from_address = "CHANGE_ME@example.com"
184
+ config.parent_mailer = "ActionMailer::Base"
185
+ config.restrict_token_reuse = false # Can a token/link be used multiple times?
186
+ config.token_generator = Passwordless::ShortTokenGenerator.new # Used to generate magic link tokens.
187
+
188
+ config.expires_at = lambda { 1.year.from_now } # How long until a signed in session expires.
189
+ config.timeout_at = lambda { 10.minutes.from_now } # How long until a token/magic link times out.
190
+
191
+ 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.
192
+ config.redirect_to_response_options = {} # Additional options for redirects.
193
+ config.success_redirect_path = '/' # After a user successfully signs in
194
+ config.failure_redirect_path = '/' # After a sign in fails
195
+ config.sign_out_redirect_path = '/' # After a user signs out
196
196
  end
197
197
  ```
198
198
 
199
- You can access user model through authenticatable.
199
+ ### Delivery method
200
200
 
201
- ### Generate your own magic links
201
+ 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
202
 
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.
203
+ In `config/initializers/passwordless.rb`:
206
204
 
207
205
  ```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
- ```
206
+ Passwordless.configure do |config|
207
+ config.after_session_save = lambda do |session, request|
208
+ # Default behavior is
209
+ # Passwordless::Mailer.sign_in(session).deliver_now
216
210
 
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)
211
+ # You can change behavior to do something with session model. For example,
212
+ # SmsApi.send_sms(session.authenticatable.phone_number, session.token)
236
213
  end
237
214
  end
238
215
  ```
239
216
 
240
- ## Configuration
241
-
242
- The following configuration parameters are supported. You can override these for example in `initializers/passwordless.rb`.
217
+ ### Token generation
243
218
 
244
- The default values are shown below. It's recommended to only include the ones that you specifically want to override.
219
+ 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
220
 
246
221
  ```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
- }
222
+ Passwordless.configure do |config|
223
+ config.token_generator = lambda do |session|
224
+ "probably-stupid-token-#{session.user_agent}-#{Time.current}"
225
+ end
226
+ end
273
227
  ```
274
228
 
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.
229
+ 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
230
 
277
- ### Token and Session Expiry
231
+ ### Timeout and Expiry
278
232
 
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.
233
+ 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
234
 
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.
235
+ The _expiry_ is the expiration time of the session of a logged in user. Once this is expired, the user is signed out.
282
236
 
283
- #### Token timeout
237
+ **Note:** Passwordless' session relies on Rails' own session and so will never live longer than that.
284
238
 
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.
239
+ To configure your Rails session, in `config/initializers/session_store.rb`:
288
240
 
289
241
  ```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 }
242
+ Rails.application.config.session_store :cookie_store,
243
+ expire_after: 1.year,
244
+ # ...
301
245
  ```
302
246
 
303
- ### Redirecting back after sign-in
247
+ ### Redirection after sign-in
304
248
 
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:
249
+ 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
250
 
307
251
  ```ruby
308
252
  class ApplicationController < ActionController::Base
@@ -312,71 +256,45 @@ class ApplicationController < ActionController::Base
312
256
 
313
257
  def require_user!
314
258
  return if current_user
315
- save_passwordless_redirect_location!(User) # <-- here we go!
259
+ save_passwordless_redirect_location!(User) # <-- this one!
316
260
  redirect_to root_path, flash: {error: 'You are not worthy!'}
317
261
  end
318
262
  end
319
263
  ```
320
264
 
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
265
+ This can also be turned off with `Passwordless.config.redirect_back_after_sign_in = false`.
335
266
 
336
- The simplest way to update your sessions table is with a single migration:
267
+ ### Looking up the user
337
268
 
338
- <details>
339
- <summary>Example migration</summary>
269
+ By default Passwordless uses the `passwordless_with` column to _case insensitively_ fetch the user resource.
340
270
 
341
- ```bash
342
- bin/rails generate migration add_claimed_at_to_passwordless_sessions
343
- ```
271
+ 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
272
 
345
273
  ```ruby
346
- class AddClaimedAtToPasswordlessSessions < ActiveRecord::Migration[5.2]
347
- def change
348
- add_column :passwordless_sessions, :claimed_at, :datetime
274
+ class User < ApplicationRecord
275
+ def self.fetch_resource_for_passwordless(email)
276
+ find_or_create_by(email: email)
349
277
  end
350
278
  end
351
-
352
279
  ```
353
- </details>
354
280
 
355
- ### Supporting UUID primary keys
281
+ ### Claiming tokens
282
+
283
+ By default, a token/magic link **can** be used more than once.
356
284
 
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).
285
+ To change, in `config/initializers/passwordless.rb`:
359
286
 
360
- Here is an example migration you can use:
361
287
  ```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
288
+ Passwordless.configure do |config|
289
+ config.restrict_token_reuse = true
369
290
  end
370
291
  ```
371
292
 
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
293
+ ## Test helpers
375
294
 
376
295
  To help with testing, a set of test helpers are provided.
377
296
 
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:
297
+ If you are using RSpec, add the following line to your `spec/rails_helper.rb`:
380
298
 
381
299
  ```ruby
382
300
  require "passwordless/test_helpers"
@@ -388,7 +306,6 @@ If you are using TestUnit, add this line to your `test/test_helper.rb`:
388
306
  require "passwordless/test_helpers"
389
307
  ```
390
308
 
391
-
392
309
  Then in your controller, request, and system tests/specs, you can utilize the following methods:
393
310
 
394
311
  ```ruby
@@ -396,18 +313,18 @@ passwordless_sign_in(user) # signs you in as a user
396
313
  passwordless_sign_out # signs out user
397
314
  ```
398
315
 
399
- ## E-mail security
316
+ ## Security considerations
400
317
 
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.
318
+ 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
319
 
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.
320
+ 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
321
 
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.
322
+ 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
323
 
407
- # Alternatives
324
+ ## Alternatives
408
325
 
409
326
  - [OTP JWT](https://github.com/stas/otp-jwt) -- Passwordless JSON Web Tokens
410
327
 
411
- # License
328
+ ## License
412
329
 
413
330
  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)