passwordless 0.11.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: bfea8774f73a80e003f7257caa02afc7c1475a87077a4881b2c1e85442ebf8e9
4
- data.tar.gz: 4882a066aa2ecc18a4a170e697014b5f09b9bde58c32e821eb60944c9fb90b13
3
+ metadata.gz: '09a1ddce2bcfe831bf08b8e0e2e48ce7d8941ea4a44ed9ea0091de3ece68b9f3'
4
+ data.tar.gz: 35d3d30954025caa77b06a2ff6c51579cbe78135579733cdb92323cd1c140e7f
5
5
  SHA512:
6
- metadata.gz: f71185082eb25883c1a7778276c244cd8e4ada2ee1c76137b2838c08b40ed8687b1db3eae2ed869f376be762d03d0ad7978c481d73f42c2338be0b1d200cb07c
7
- data.tar.gz: 4ad367839721156af66ee6a76786acae842636eda04841ce1a92012dcc245ddd20414d00108908cd27190fa01e4636c88c4668012b893700f6e67ce604ca979f
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
 
@@ -58,7 +59,10 @@ Then specify which field on your `User` record is the email field with:
58
59
 
59
60
  ```ruby
60
61
  class User < ApplicationRecord
61
- validates :email, presence: true, uniqueness: { case_sensitive: false }
62
+ validates :email,
63
+ presence: true,
64
+ uniqueness: { case_sensitive: false },
65
+ format: { with: URI::MailTo::EMAIL_REGEXP }
62
66
 
63
67
  passwordless_with :email # <-- here!
64
68
  end
@@ -94,6 +98,7 @@ class ApplicationController < ActionController::Base
94
98
 
95
99
  def require_user!
96
100
  return if current_user
101
+ save_passwordless_redirect_location!(User) # <-- optional, see below
97
102
  redirect_to root_path, flash: { error: 'You are not worthy!' }
98
103
  end
99
104
  end
@@ -113,30 +118,19 @@ end
113
118
 
114
119
  ### Providing your own templates
115
120
 
116
- 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`.
117
122
 
118
- `passwordless` has 2 action views and 1 mailer view:
123
+ Passwordless has 2 action views and 1 mailer view:
119
124
 
120
125
  ```sh
121
126
  # the form where the user inputs their email address
122
127
  app/views/passwordless/sessions/new.html.erb
123
- # shown after a user requests a magic link
124
- app/views/passwordless/sessions/create.html.erb
125
- # the mail with the magic link that gets sent
126
- app/views/passwordless/mailer/magic_link.text.erb
127
- ```
128
-
129
- 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:
130
- ```erb
131
- <% if @resource.present? %>
132
- <p>User found, check your inbox</p>
133
- <% else %>
134
- <p>No user found with the provided email address</p>
135
- <% 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
136
132
  ```
137
133
 
138
- 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.
139
-
140
134
  See [the bundled views](https://github.com/mikker/passwordless/tree/master/app/views/passwordless).
141
135
 
142
136
  ### Registering new users
@@ -149,13 +143,13 @@ class UsersController < ApplicationController
149
143
  # (unless you already have it in your ApplicationController)
150
144
 
151
145
  def create
152
- @user = User.new user_params
146
+ @user = User.new(user_params)
153
147
 
154
148
  if @user.save
155
- sign_in @user # <-- This!
156
- redirect_to @user, flash: { notice: 'Welcome!' }
149
+ sign_in(build_passwordless_session(@user)) # <-- This!
150
+ redirect_to(@user, flash: { notice: 'Welcome!' })
157
151
  else
158
- render :new
152
+ render(:new)
159
153
  end
160
154
  end
161
155
 
@@ -169,134 +163,90 @@ By default, Passwordless uses the resource name given to `passwordless_for` to g
169
163
 
170
164
  ```ruby
171
165
  passwordless_for :users
172
- # <%= users.sign_in_path %> # => /users/sign_in
166
+ # <%= users_sign_in_path %> # => /users/sign_in
173
167
 
174
168
  passwordless_for :users, at: '/', as: :auth
175
- # <%= auth.sign_in_path %> # => /sign_in
169
+ # <%= auth_sign_in_path %> # => /sign_in
176
170
  ```
177
171
 
178
- 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).
179
174
 
180
- ### Customize the way to send magic link
175
+ ## Configuration
181
176
 
182
- 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`.
183
178
 
184
- 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.
185
180
 
186
181
  ```ruby
187
- Passwordless.after_session_save = lambda do |session, request|
188
- # Default behavior is
189
- # Passwordless::Mailer.magic_link(session).deliver_now
190
-
191
- # You can change behavior to do something with session model. For example,
192
- # 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
193
196
  end
194
197
  ```
195
198
 
196
- You can access user model through authenticatable.
199
+ ### Delivery method
197
200
 
198
- ### 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.
199
202
 
200
- Currently there is not an officially supported way to generate your own magic links to send in your own mailers.
201
-
202
- However, you can accomplish this with the following snippet of code.
203
+ In `config/initializers/passwordless.rb`:
203
204
 
204
205
  ```ruby
205
- session = Passwordless::Session.new({
206
- authenticatable: @manager,
207
- user_agent: 'Command Line',
208
- remote_addr: 'unknown',
209
- })
210
- session.save!
211
- @magic_link = send(Passwordless.mounted_as).token_sign_in_url(session.token)
212
- ```
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
213
210
 
214
- 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
215
- ```
216
- @magic_link = "#{@magic_link}?destination_path=/your-custom-path"
217
- ```
218
-
219
- ### Overrides
220
-
221
- By default `passwordless` uses the `passwordless_with` column to _case insensitively_ fetch the resource.
222
-
223
- 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.
224
-
225
- Example time:
226
-
227
- Let's say we would like to fetch the record and if it doesn't exist, create automatically.
228
-
229
- ```ruby
230
- class User < ApplicationRecord
231
- def self.fetch_resource_for_passwordless(email)
232
- 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)
233
213
  end
234
214
  end
235
215
  ```
236
216
 
237
- ## Configuration
238
-
239
- The following configuration parameters are supported. You can override these for example in `initializers/passwordless.rb`.
217
+ ### Token generation
240
218
 
241
- 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.:
242
220
 
243
221
  ```ruby
244
- Passwordless.default_from_address = "CHANGE_ME@example.com"
245
- Passwordless.parent_mailer = "ActionMailer::Base"
246
- Passwordless.token_generator = Passwordless::UrlSafeBase64Generator.new # Used to generate magic link tokens.
247
- Passwordless.restrict_token_reuse = false # By default a magic link token can be used multiple times.
248
- 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.
249
-
250
- Passwordless.expires_at = lambda { 1.year.from_now } # How long until a passwordless session expires.
251
- Passwordless.timeout_at = lambda { 1.hour.from_now } # How long until a magic link expires.
252
-
253
- # Default redirection paths
254
- Passwordless.success_redirect_path = '/' # When a user succeeds in logging in.
255
- Passwordless.failure_redirect_path = '/' # When a a login is failed for any reason.
256
- Passwordless.sign_out_redirect_path = '/' # When a user logs out.
257
- ```
258
-
259
- ### Customizing token generation
260
-
261
- 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.:
262
-
263
- ```ruby
264
- Passwordless.token_generator = -> (session) {
265
- "probably-stupid-token-#{session.user_agent}-#{Time.current}"
266
- }
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
267
227
  ```
268
228
 
269
- 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.
270
230
 
271
- ### Token and Session Expiry
231
+ ### Timeout and Expiry
272
232
 
273
- 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.
274
234
 
275
- 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.
276
236
 
277
- #### Token timeout
237
+ **Note:** Passwordless' session relies on Rails' own session and so will never live longer than that.
278
238
 
279
- 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.
280
-
281
- > 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`:
282
240
 
283
241
  ```ruby
284
- Passwordless.timeout_at = lambda { 2.hours.from_now }
285
- ```
286
-
287
- #### Session Expiry
288
-
289
- 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.
290
-
291
- > Make sure to use a `.call`able object, like a proc or lambda as it will be called everytime a session is created.
292
-
293
- ```ruby
294
- Passwordless.expires_at = lambda { 24.hours.from_now }
242
+ Rails.application.config.session_store :cookie_store,
243
+ expire_after: 1.year,
244
+ # ...
295
245
  ```
296
246
 
297
- ### Redirecting back after sign-in
247
+ ### Redirection after sign-in
298
248
 
299
- 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:
300
250
 
301
251
  ```ruby
302
252
  class ApplicationController < ActionController::Base
@@ -306,71 +256,45 @@ class ApplicationController < ActionController::Base
306
256
 
307
257
  def require_user!
308
258
  return if current_user
309
- save_passwordless_redirect_location!(User) # <-- here we go!
259
+ save_passwordless_redirect_location!(User) # <-- this one!
310
260
  redirect_to root_path, flash: {error: 'You are not worthy!'}
311
261
  end
312
262
  end
313
263
  ```
314
264
 
315
- 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.
316
-
317
- ### Claiming tokens
318
-
319
- Opt-in for marking tokens as `claimed` so they can only be used once.
320
-
321
- config/initializers/passwordless.rb
322
-
323
- ```ruby
324
- # Default is `false`
325
- Passwordless.restrict_token_reuse = true
326
- ```
327
-
328
- #### 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`.
329
266
 
330
- The simplest way to update your sessions table is with a single migration:
267
+ ### Looking up the user
331
268
 
332
- <details>
333
- <summary>Example migration</summary>
269
+ By default Passwordless uses the `passwordless_with` column to _case insensitively_ fetch the user resource.
334
270
 
335
- ```bash
336
- bin/rails generate migration add_claimed_at_to_passwordless_sessions
337
- ```
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.
338
272
 
339
273
  ```ruby
340
- class AddClaimedAtToPasswordlessSessions < ActiveRecord::Migration[5.2]
341
- def change
342
- 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)
343
277
  end
344
278
  end
345
-
346
279
  ```
347
- </details>
348
280
 
349
- ### Supporting UUID primary keys
281
+ ### Claiming tokens
282
+
283
+ By default, a token/magic link **can** be used more than once.
350
284
 
351
- If your `users` table uses UUIDs for its primary keys, you will need to add a migration
352
- 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`:
353
286
 
354
- Here is an example migration you can use:
355
287
  ```ruby
356
- class SupportUuidInPasswordlessSessions < ActiveRecord::Migration[6.0]
357
- def change
358
- remove_index :passwordless_sessions, column: [:authenticatable_type, :authenticatable_id] if index_exists? :authenticatable_type, :authenticatable_id
359
- remove_column :passwordless_sessions, :authenticatable_id
360
- add_column :passwordless_sessions, :authenticatable_id, :uuid
361
- add_index :passwordless_sessions, [:authenticatable_type, :authenticatable_id], name: 'authenticatable'
362
- end
288
+ Passwordless.configure do |config|
289
+ config.restrict_token_reuse = true
363
290
  end
364
291
  ```
365
292
 
366
- 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)).
367
-
368
- ## Testing helpers
293
+ ## Test helpers
369
294
 
370
295
  To help with testing, a set of test helpers are provided.
371
296
 
372
- If you are using RSpec, add the following line to your `spec/rails_helper.rb` or
373
- `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`:
374
298
 
375
299
  ```ruby
376
300
  require "passwordless/test_helpers"
@@ -382,7 +306,6 @@ If you are using TestUnit, add this line to your `test/test_helper.rb`:
382
306
  require "passwordless/test_helpers"
383
307
  ```
384
308
 
385
-
386
309
  Then in your controller, request, and system tests/specs, you can utilize the following methods:
387
310
 
388
311
  ```ruby
@@ -390,18 +313,18 @@ passwordless_sign_in(user) # signs you in as a user
390
313
  passwordless_sign_out # signs out user
391
314
  ```
392
315
 
393
- ## E-mail security
316
+ ## Security considerations
394
317
 
395
- 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.
396
319
 
397
- 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.
398
321
 
399
- 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.
400
323
 
401
- # Alternatives
324
+ ## Alternatives
402
325
 
403
326
  - [OTP JWT](https://github.com/stas/otp-jwt) -- Passwordless JSON Web Tokens
404
327
 
405
- # License
328
+ ## License
406
329
 
407
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)