rodauth-rails 0.3.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|