rodauth-rails 0.3.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +169 -69
- data/lib/generators/rodauth/install_generator.rb +34 -17
- data/lib/generators/rodauth/migration/account_expiration.erb +7 -0
- data/lib/generators/rodauth/migration/active_sessions.erb +7 -0
- data/lib/generators/rodauth/migration/audit_logging.erb +16 -0
- data/lib/generators/rodauth/migration/base.erb +19 -0
- data/lib/generators/rodauth/migration/disallow_password_reuse.erb +5 -0
- data/lib/generators/rodauth/migration/email_auth.erb +7 -0
- data/lib/generators/rodauth/migration/jwt_refresh.erb +7 -0
- data/lib/generators/rodauth/migration/lockout.erb +11 -0
- data/lib/generators/rodauth/migration/otp.erb +7 -0
- data/lib/generators/rodauth/migration/password_expiration.erb +5 -0
- data/lib/generators/rodauth/migration/recovery_codes.erb +6 -0
- data/lib/generators/rodauth/migration/remember.erb +6 -0
- data/lib/generators/rodauth/migration/reset_password.erb +7 -0
- data/lib/generators/rodauth/migration/single_session.erb +5 -0
- data/lib/generators/rodauth/migration/sms_codes.erb +8 -0
- data/lib/generators/rodauth/migration/verify_account.erb +7 -0
- data/lib/generators/rodauth/migration/verify_login_change.erb +7 -0
- data/lib/generators/rodauth/migration/webauthn.erb +12 -0
- data/lib/generators/rodauth/migration_generator.rb +32 -0
- data/lib/generators/rodauth/migration_helpers.rb +69 -0
- data/lib/generators/rodauth/templates/{lib → app/lib}/rodauth_app.rb +26 -2
- data/lib/generators/rodauth/templates/config/initializers/sequel.rb +1 -5
- data/lib/generators/rodauth/templates/db/migrate/create_rodauth.rb +2 -167
- data/lib/rodauth/rails.rb +24 -5
- data/lib/rodauth/rails/app.rb +5 -4
- data/lib/rodauth/rails/feature.rb +69 -13
- data/lib/rodauth/rails/flash.rb +48 -0
- data/lib/rodauth/rails/railtie.rb +11 -0
- data/lib/rodauth/rails/tasks.rake +28 -0
- data/lib/rodauth/rails/version.rb +5 -0
- data/rodauth-rails.gemspec +6 -4
- metadata +31 -9
- data/lib/rodauth/rails/app/flash.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 00d7ab9dd749cbae17cddc2788005d8570d8d46f89f427ad624c7e61fe177665
|
4
|
+
data.tar.gz: d62aed32823b0be9c74d7281de80650b8faf600c01db22ad941a35f30bfeb002
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ab0afe5e95fab1af706b64ef5c494252fac49481d49dad1c2ef3510c17ca96050c58bf0832a36af761673137f2fd63d7d49a692656bcf06748840de96f60875
|
7
|
+
data.tar.gz: 729bf3b5887647c23f4b11d821d3829c4b4d290c546d2f19ba6b393dd47bf0b357d128577e877ac3e0a4352972b79325d4217d62935d4a683e78ff8e910d13a2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,45 @@
|
|
1
|
+
## 0.6.0 (2020-11-22)
|
2
|
+
|
3
|
+
* Add `Rodauth::Rails.rodauth` method for retrieving Rodauth instance outside of request context (@janko)
|
4
|
+
|
5
|
+
* Add default Action Dispatch response headers in Rodauth responses (@janko)
|
6
|
+
|
7
|
+
* Run controller rescue handlers around Rodauth actions (@janko)
|
8
|
+
|
9
|
+
* Run controller action callbacks around Rodauth actions (@janko)
|
10
|
+
|
11
|
+
## 0.5.0 (2020-11-16)
|
12
|
+
|
13
|
+
* Support more Active Record adapters in `rodauth:install` generator (@janko)
|
14
|
+
|
15
|
+
* Add `rodauth:migration` generator for creating tables of specified features (@janko)
|
16
|
+
|
17
|
+
* Use UUIDs for primary keys if so configured in Rails generators (@janko)
|
18
|
+
|
19
|
+
* Add `rodauth:routes` rake task for printing routes handled by Rodauth middleware (@janko)
|
20
|
+
|
21
|
+
## 0.4.2 (2020-11-08)
|
22
|
+
|
23
|
+
* Drop support for Ruby 2.2 (@janko)
|
24
|
+
|
25
|
+
* Bump `sequel-activerecord_connection` dependency to 1.1+ (@janko)
|
26
|
+
|
27
|
+
* Set default bcrypt hash cost to `1` in tests (@janko)
|
28
|
+
|
29
|
+
* Call `AR::Base.connection_db_config` on Rails 6.1+ in `rodauth:install` generator (@janko)
|
30
|
+
|
31
|
+
## 0.4.1 (2020-11-02)
|
32
|
+
|
33
|
+
* Don't generate `RodauthController` in API-only mode (@janko)
|
34
|
+
|
35
|
+
* Pass `test: false` to Sequel in the `sequel.rb` initializer (@janko)
|
36
|
+
|
37
|
+
## 0.4.0 (2020-11-02)
|
38
|
+
|
39
|
+
* Support Rails API-only mode (@janko)
|
40
|
+
|
41
|
+
* Make `rodauth:install` create `rodauth_app.rb` in `app/lib/` directory (@janko)
|
42
|
+
|
1
43
|
## 0.3.1 (2020-10-25)
|
2
44
|
|
3
45
|
* Depend on sequel-activerecord_connection 1.0+ (@janko)
|
data/README.md
CHANGED
@@ -4,16 +4,27 @@ Provides Rails integration for the [Rodauth] authentication framework.
|
|
4
4
|
|
5
5
|
## Resources
|
6
6
|
|
7
|
+
Useful links:
|
8
|
+
|
7
9
|
* [Rodauth documentation](http://rodauth.jeremyevans.net/documentation.html)
|
8
|
-
* [rodauth-rails wiki](https://github.com/janko/rodauth-rails/wiki)
|
9
10
|
* [Rails demo](https://github.com/janko/rodauth-demo-rails)
|
10
11
|
|
12
|
+
Articles:
|
13
|
+
|
14
|
+
* [Rodauth: A Refreshing Authentication Solution for Ruby](https://janko.io/rodauth-a-refreshing-authentication-solution-for-ruby/)
|
15
|
+
* [Adding Authentication in Rails 6 with Rodauth](https://janko.io/adding-authentication-in-rails-with-rodauth/)
|
16
|
+
|
11
17
|
## Installation
|
12
18
|
|
13
19
|
Add the gem to your Gemfile:
|
14
20
|
|
15
21
|
```rb
|
16
|
-
gem "rodauth-rails", "~> 0.
|
22
|
+
gem "rodauth-rails", "~> 0.6"
|
23
|
+
|
24
|
+
# gem "jwt", require: false # for JWT feature
|
25
|
+
# gem "rotp", require: false # for OTP feature
|
26
|
+
# gem "rqrcode", require: false # for OTP feature
|
27
|
+
# gem "webauthn", require: false # for WebAuthn feature
|
17
28
|
```
|
18
29
|
|
19
30
|
Then run `bundle install`.
|
@@ -29,7 +40,7 @@ The generator will create the following files:
|
|
29
40
|
* Rodauth migration at `db/migrate/*_create_rodauth.rb`
|
30
41
|
* Rodauth initializer at `config/initializers/rodauth.rb`
|
31
42
|
* Sequel initializer at `config/initializers/sequel.rb` for ActiveRecord integration
|
32
|
-
* Rodauth app at `lib/rodauth_app.rb`
|
43
|
+
* Rodauth app at `app/lib/rodauth_app.rb`
|
33
44
|
* Rodauth controller at `app/controllers/rodauth_controller.rb`
|
34
45
|
* Account model at `app/models/account.rb`
|
35
46
|
|
@@ -49,7 +60,6 @@ class CreateRodauth < ActiveRecord::Migration
|
|
49
60
|
create_table :account_verification_keys do |t| ... end
|
50
61
|
create_table :account_login_change_keys do |t| ... end
|
51
62
|
create_table :account_remember_keys do |t| ... end
|
52
|
-
# ...
|
53
63
|
end
|
54
64
|
end
|
55
65
|
```
|
@@ -83,17 +93,17 @@ ActiveRecord connection.
|
|
83
93
|
require "sequel/core"
|
84
94
|
|
85
95
|
# initialize Sequel and have it reuse Active Record's database connection
|
86
|
-
DB = Sequel.
|
96
|
+
DB = Sequel.connect("postgresql://", extensions: :activerecord_connection)
|
87
97
|
```
|
88
98
|
|
89
99
|
### Rodauth app
|
90
100
|
|
91
|
-
Your Rodauth app is created in the `lib/` directory,
|
92
|
-
set of authentication features enabled, as well as extensive examples
|
93
|
-
you can configure authentication behaviour.
|
101
|
+
Your Rodauth app is created in the `app/lib/` directory, and comes with a
|
102
|
+
default set of authentication features enabled, as well as extensive examples
|
103
|
+
on ways you can configure authentication behaviour.
|
94
104
|
|
95
105
|
```rb
|
96
|
-
# lib/rodauth_app.rb
|
106
|
+
# app/lib/rodauth_app.rb
|
97
107
|
class RodauthApp < Rodauth::Rails::App
|
98
108
|
configure do
|
99
109
|
# authentication configuration
|
@@ -105,23 +115,11 @@ class RodauthApp < Rodauth::Rails::App
|
|
105
115
|
end
|
106
116
|
```
|
107
117
|
|
108
|
-
Note that Rails doesn't autoload files in the `lib/` directory by default, so
|
109
|
-
make sure to add `lib/` to your `config.autoload_paths`:
|
110
|
-
|
111
|
-
```rb
|
112
|
-
# config/application.rb
|
113
|
-
module YourApp
|
114
|
-
class Application < Rails::Application
|
115
|
-
# ...
|
116
|
-
config.autoload_paths += %W[#{config.root}/lib]
|
117
|
-
end
|
118
|
-
end
|
119
|
-
```
|
120
|
-
|
121
118
|
### Controller
|
122
119
|
|
123
|
-
Your Rodauth app will by default use `RodauthController` for view rendering
|
124
|
-
and
|
120
|
+
Your Rodauth app will by default use `RodauthController` for view rendering,
|
121
|
+
CSRF protection, and running controller callbacks and rescue handlers around
|
122
|
+
Rodauth actions.
|
125
123
|
|
126
124
|
```rb
|
127
125
|
# app/controllers/rodauth_controller.rb
|
@@ -129,7 +127,7 @@ class RodauthController < ApplicationController
|
|
129
127
|
end
|
130
128
|
```
|
131
129
|
|
132
|
-
### Account
|
130
|
+
### Account model
|
133
131
|
|
134
132
|
Rodauth stores user accounts in the `accounts` table, so the generator will
|
135
133
|
also create an `Account` model for custom use.
|
@@ -140,10 +138,34 @@ class Account < ApplicationRecord
|
|
140
138
|
end
|
141
139
|
```
|
142
140
|
|
143
|
-
##
|
141
|
+
## Usage
|
142
|
+
|
143
|
+
### Routes
|
144
|
+
|
145
|
+
We can see the list of routes our Rodauth middleware handles:
|
146
|
+
|
147
|
+
```sh
|
148
|
+
$ rails rodauth:routes
|
149
|
+
```
|
150
|
+
```
|
151
|
+
Routes handled by RodauthApp:
|
152
|
+
|
153
|
+
/login rodauth.login_path
|
154
|
+
/create-account rodauth.create_account_path
|
155
|
+
/verify-account-resend rodauth.verify_account_resend_path
|
156
|
+
/verify-account rodauth.verify_account_path
|
157
|
+
/change-password rodauth.change_password_path
|
158
|
+
/change-login rodauth.change_login_path
|
159
|
+
/logout rodauth.logout_path
|
160
|
+
/remember rodauth.remember_path
|
161
|
+
/reset-password-request rodauth.reset_password_request_path
|
162
|
+
/reset-password rodauth.reset_password_path
|
163
|
+
/verify-login-change rodauth.verify_login_change_path
|
164
|
+
/close-account rodauth.close_account_path
|
165
|
+
```
|
144
166
|
|
145
|
-
|
146
|
-
|
167
|
+
Using this information, we could add some basic authentication links to our
|
168
|
+
navigation header:
|
147
169
|
|
148
170
|
```erb
|
149
171
|
<ul>
|
@@ -156,43 +178,48 @@ page:
|
|
156
178
|
</ul>
|
157
179
|
```
|
158
180
|
|
159
|
-
These
|
181
|
+
These routes are fully functional, feel free to visit them and interact with the
|
160
182
|
pages. The templates that ship with Rodauth aim to provide a complete
|
161
183
|
authentication experience, and the forms use [Bootstrap] markup.
|
162
184
|
|
163
|
-
|
164
|
-
|
185
|
+
### Current account
|
186
|
+
|
187
|
+
To be able to fetch currently authenticated account, let's define a
|
188
|
+
`#current_account` method that fetches the account id from session and
|
189
|
+
retrieves the corresponding account record:
|
165
190
|
|
166
191
|
```rb
|
167
192
|
# app/controllers/application_controller.rb
|
168
193
|
class ApplicationController < ActionController::Base
|
169
|
-
before_action :
|
194
|
+
before_action :current_account, if: -> { rodauth.authenticated? }
|
170
195
|
|
171
196
|
private
|
172
197
|
|
173
|
-
def
|
174
|
-
@current_account
|
198
|
+
def current_account
|
199
|
+
@current_account ||= Account.find(rodauth.session_value)
|
175
200
|
rescue ActiveRecord::RecordNotFound
|
176
201
|
rodauth.logout
|
177
202
|
rodauth.login_required
|
178
203
|
end
|
179
|
-
|
180
|
-
attr_reader :current_account
|
181
204
|
helper_method :current_account
|
182
205
|
end
|
183
206
|
```
|
207
|
+
|
208
|
+
This allows us to access the current account in controllers and views:
|
209
|
+
|
184
210
|
```erb
|
185
211
|
<p>Authenticated as: <%= current_account.email %></p>
|
186
212
|
```
|
187
213
|
|
188
214
|
### Requiring authentication
|
189
215
|
|
190
|
-
|
191
|
-
|
192
|
-
the authentication logic
|
216
|
+
We'll likely want to require authentication for certain parts of our app,
|
217
|
+
redirecting the user to the login page if they're not logged in. We can do this
|
218
|
+
in our Rodauth app's routing block, which helps keep the authentication logic
|
219
|
+
encapsulated:
|
193
220
|
|
194
221
|
```rb
|
195
|
-
# lib/rodauth_app.rb
|
222
|
+
# app/lib/rodauth_app.rb
|
196
223
|
class RodauthApp < Rodauth::Rails::App
|
197
224
|
# ...
|
198
225
|
route do |r|
|
@@ -247,9 +274,9 @@ end
|
|
247
274
|
|
248
275
|
### Views
|
249
276
|
|
250
|
-
The templates built into Rodauth are useful when getting started, but
|
251
|
-
|
252
|
-
|
277
|
+
The templates built into Rodauth are useful when getting started, but soon
|
278
|
+
you'll want to start editing the markup. You can run the following command to
|
279
|
+
copy Rodauth templates into your Rails app:
|
253
280
|
|
254
281
|
```sh
|
255
282
|
$ rails generate rodauth:views
|
@@ -273,7 +300,7 @@ $ rails generate rodauth:views --all
|
|
273
300
|
```
|
274
301
|
|
275
302
|
You can also tell the generator to create views into another directory (in this
|
276
|
-
case
|
303
|
+
case make sure to rename the Rodauth controller accordingly):
|
277
304
|
|
278
305
|
```sh
|
279
306
|
# generates views into app/views/authentication
|
@@ -308,11 +335,11 @@ end
|
|
308
335
|
|
309
336
|
### Mailer
|
310
337
|
|
311
|
-
Rodauth may send emails as part of
|
312
|
-
can be customized:
|
338
|
+
Depending on the features you've enabled, Rodauth may send emails as part of
|
339
|
+
the authentication flow. Most email settings can be customized:
|
313
340
|
|
314
341
|
```rb
|
315
|
-
# lib/rodauth_app.rb
|
342
|
+
# app/lib/rodauth_app.rb
|
316
343
|
class RodauthApp < Rodauth::Rails::App
|
317
344
|
# ...
|
318
345
|
configure do
|
@@ -353,11 +380,11 @@ end
|
|
353
380
|
```
|
354
381
|
|
355
382
|
You can then uncomment the lines in your Rodauth configuration to have it call
|
356
|
-
your mailer. If you've enabled additional authentication features
|
357
|
-
override their `send_*_email` methods as well.
|
383
|
+
your mailer. If you've enabled additional authentication features that send
|
384
|
+
emails, make sure to override their `send_*_email` methods as well.
|
358
385
|
|
359
386
|
```rb
|
360
|
-
# lib/rodauth_app.rb
|
387
|
+
# app/lib/rodauth_app.rb
|
361
388
|
class RodauthApp < Rodauth::Rails::App
|
362
389
|
# ...
|
363
390
|
configure do
|
@@ -393,6 +420,76 @@ class RodauthApp < Rodauth::Rails::App
|
|
393
420
|
end
|
394
421
|
```
|
395
422
|
|
423
|
+
### Migrations
|
424
|
+
|
425
|
+
The install generator will create a migration for tables used by the Rodauth
|
426
|
+
features enabled by default. For any additional features, you can use the
|
427
|
+
migration generator to create the corresponding tables:
|
428
|
+
|
429
|
+
```sh
|
430
|
+
$ rails generate rodauth:migration otp sms_codes recovery_codes
|
431
|
+
```
|
432
|
+
```rb
|
433
|
+
# db/migration/*_create_rodauth_otp_sms_codes_recovery_codes.rb
|
434
|
+
class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
|
435
|
+
def change
|
436
|
+
create_table :account_otp_keys do |t| ... end
|
437
|
+
create_table :account_sms_codes do |t| ... end
|
438
|
+
create_table :account_recovery_codes do |t| ... end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
```
|
442
|
+
|
443
|
+
### JSON API
|
444
|
+
|
445
|
+
JSON API support in Rodauth is provided by the [JWT feature]. First you'll need
|
446
|
+
to add the [JWT gem] to your Gemfile:
|
447
|
+
|
448
|
+
```rb
|
449
|
+
gem "jwt"
|
450
|
+
```
|
451
|
+
|
452
|
+
The following configuration will enable the Rodauth endpoints to be accessed
|
453
|
+
via JSON requests (in addition to HTML requests):
|
454
|
+
|
455
|
+
```rb
|
456
|
+
# app/lib/rodauth_app.rb
|
457
|
+
class RodauthApp < Rodauth::Rails::App
|
458
|
+
configure(json: true) do
|
459
|
+
# ...
|
460
|
+
enable :jwt
|
461
|
+
jwt_secret "...your secret key..."
|
462
|
+
# ...
|
463
|
+
end
|
464
|
+
end
|
465
|
+
```
|
466
|
+
|
467
|
+
If you want the endpoints to be only accessible via JSON requests, or if your
|
468
|
+
Rails app is in API-only mode, instead of `json: true` pass `json: :only` to
|
469
|
+
the configure method.
|
470
|
+
|
471
|
+
Make sure to store the `jwt_secret` in a secure place, such as Rails
|
472
|
+
credentials or environment variables.
|
473
|
+
|
474
|
+
### Rodauth instance
|
475
|
+
|
476
|
+
In some cases you might need to use Rodauth more programmatically, and perform
|
477
|
+
Rodauth operations outside of the request context. rodauth-rails gives you the
|
478
|
+
ability to retrieve the Rodauth instance:
|
479
|
+
|
480
|
+
```rb
|
481
|
+
rodauth = Rodauth::Rails.rodauth # or Rodauth::Rails.rodauth(:secondary)
|
482
|
+
|
483
|
+
rodauth.login_url #=> "https://example.com/login"
|
484
|
+
rodauth.account_from_login("user@example.com") # loads user by email
|
485
|
+
rodauth.password_match?("secret") #=> true
|
486
|
+
rodauth.setup_account_verification
|
487
|
+
rodauth.close_account
|
488
|
+
```
|
489
|
+
|
490
|
+
This Rodauth instance will be initialized with basic Rack env that allows is it
|
491
|
+
to generate URLs, using `config.action_mailer.default_url_options` options.
|
492
|
+
|
396
493
|
## How it works
|
397
494
|
|
398
495
|
### Middleware
|
@@ -437,11 +534,12 @@ end
|
|
437
534
|
The `Rodauth::Rails::App` class is a [Roda] subclass that provides Rails
|
438
535
|
integration for Rodauth:
|
439
536
|
|
440
|
-
* uses
|
441
|
-
* uses
|
537
|
+
* uses Action Dispatch flash instead of Roda's
|
538
|
+
* uses Action Dispatch CSRF protection instead of Roda's
|
442
539
|
* sets [HMAC] secret to Rails' secret key base
|
443
|
-
* uses
|
444
|
-
*
|
540
|
+
* uses Action Controller for rendering templates
|
541
|
+
* runs Action Controller callbacks & rescue handlers around Rodauth actions
|
542
|
+
* uses Action Mailer for sending emails
|
445
543
|
|
446
544
|
The `configure { ... }` method wraps configuring the Rodauth plugin, forwarding
|
447
545
|
any additional [plugin options].
|
@@ -498,20 +596,6 @@ Rodauth::Rails.configure do |config|
|
|
498
596
|
end
|
499
597
|
```
|
500
598
|
|
501
|
-
## Working with JWT
|
502
|
-
|
503
|
-
To use Rodauth's [JWT feature], you'll need to load Roda's JSON support:
|
504
|
-
|
505
|
-
```rb
|
506
|
-
# lib/rodauth_app.rb
|
507
|
-
class RodauthApp < Rodauth::Rails::App
|
508
|
-
configure(json: true) do
|
509
|
-
enable :jwt
|
510
|
-
# your configuration
|
511
|
-
end
|
512
|
-
end
|
513
|
-
```
|
514
|
-
|
515
599
|
## Testing
|
516
600
|
|
517
601
|
If you're writing system tests, it's generally better to go through the actual
|
@@ -579,6 +663,22 @@ disables the use of database functions, though you can always turn it back on.
|
|
579
663
|
use_database_authentication_functions? true
|
580
664
|
```
|
581
665
|
|
666
|
+
To create the database functions, pass the Sequel database object into the
|
667
|
+
Rodauth method for creating database functions:
|
668
|
+
|
669
|
+
```rb
|
670
|
+
# db/migrate/*_create_rodauth_database_functions.rb
|
671
|
+
class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
|
672
|
+
def up
|
673
|
+
Rodauth.create_database_authentication_functions(DB)
|
674
|
+
end
|
675
|
+
|
676
|
+
def down
|
677
|
+
Rodauth.drop_database_authentication_functions(DB)
|
678
|
+
end
|
679
|
+
end
|
680
|
+
```
|
681
|
+
|
582
682
|
### Account statuses
|
583
683
|
|
584
684
|
The recommended [Rodauth migration] stores possible account status values in a
|
@@ -631,9 +731,9 @@ conduct](https://github.com/janko/rodauth-rails/blob/master/CODE_OF_CONDUCT.md).
|
|
631
731
|
|
632
732
|
[Rodauth]: https://github.com/jeremyevans/rodauth
|
633
733
|
[Sequel]: https://github.com/jeremyevans/sequel
|
634
|
-
[rendering views outside of controllers]: https://blog.bigbinary.com/2016/01/08/rendering-views-outside-of-controllers-in-rails-5.html
|
635
734
|
[feature documentation]: http://rodauth.jeremyevans.net/documentation.html
|
636
735
|
[JWT feature]: http://rodauth.jeremyevans.net/rdoc/files/doc/jwt_rdoc.html
|
736
|
+
[JWT gem]: https://github.com/jwt/ruby-jwt
|
637
737
|
[Bootstrap]: https://getbootstrap.com/
|
638
738
|
[Roda]: http://roda.jeremyevans.net/
|
639
739
|
[HMAC]: http://rodauth.jeremyevans.net/rdoc/files/README_rdoc.html#label-HMAC
|