authtown 0.4.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5354c22eca5eed6bbf2bdfb9abc22b34681b4eea99ae84a22e692a522b12df64
4
- data.tar.gz: ccf72db36f90a5059724f1df6aaa4c16ac261bf8f7fa27c093b08ffe010c35f5
3
+ metadata.gz: 4ede138d3793ef5ced8186a414504c563f4eeebfb5b9db5a70a6190acaab2f6a
4
+ data.tar.gz: 15886fbb260d0d227c963c699836ed33efe82f197386eec3d5d74c05eb2c42ab
5
5
  SHA512:
6
- metadata.gz: ffa84f2f6d723e4c790ee27a242231fcfcb3c70f4567f163d431a7b76d750911e3b98a4d63a4d40cdcd663da20ac4f7d932d59f0107732077d93cd68307d48af
7
- data.tar.gz: 6444e3cf4af35c02c8c88bf6bd93d17aa2226e81ea193f7353eaae3b8a757d1a201209945a08939604ae5f6586d9f8075edbbf876da5026f31ead98e2bb7b79c
6
+ metadata.gz: 0c295e46ee116cd0d539c95a98fcce53ca3e2eba4910b7f6ff67b0c4613670b6aee9b7d3f053260884ebd9be7941b647fd853a38f227125c197e5de61ab295a9
7
+ data.tar.gz: a89dd959c571fc6e089c7fec8431634c5896cf03ce5a52a89ea4873f1cb9e25d6936c6717eac69cab9a977802405146783a2e4045d05c8f37567e7762f155a74
data/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2025-01-12
11
+
12
+ - Support for production Bridgetown 2.0 release.
13
+
14
+ ## [0.5.0] - 2025-01-12
15
+
16
+ - Fix incorrect Rodauth method override
17
+
10
18
  ## [0.4.0] - 2024-05-06
11
19
 
12
20
  - Another refactor to support latest updates in Bridgetown v2
data/README.md CHANGED
@@ -1,35 +1,394 @@
1
1
  # Authtown: Rodauth integration for Bridgetown
2
2
 
3
- A Bridgetown plugin which provides authentication and account management via database access and SSR routes.
3
+ A Bridgetown plugin which provides authentication and account management via database access and SSR routes, powered by [Rodauth](https://rodauth.jeremyevans.net).
4
4
 
5
- **WIP: not yet for public use!**
5
+ **WIP public 1.0 release coming soon once Bridgetown 2.0 ships!**
6
6
 
7
7
  ## Installation
8
8
 
9
+ As a prerequisite to installing Authtown, make sure you've set up Sequel database access via the [bridgetown_sequel](https://github.com/bridgetownrb/bridgetown_sequel) plugin. You'll also need a way to send mail (Authtown will install the `mail` gem automatically) via a service like Sendgrid, Mailgun, etc. In addition, if you haven't already, you will need to add the `dotenv` and `bridgetown-routes` plugins as described in your `Gemfile` and `config/initializers.rb` file. Finally, for an easy way to generate account forms, install the [Lifeform](https://github.com/bridgetownrb/lifeform) plugin.
10
+
9
11
  Run this command to add this plugin to your site's Gemfile:
10
12
 
11
13
  ```shell
12
14
  bundle add authtown
13
15
  ```
14
16
 
15
- Then add the initializer to your configuration in `config/initializers.rb`:
17
+ You'll also need the lifeform gem to build the login form:
18
+ ```shell
19
+ bundle add lifeform
20
+ ```
21
+
22
+ Then set up a mail initializer file (in `config/mail.rb`):
23
+
24
+ ```ruby
25
+ Bridgetown.initializer :mail do |password:|
26
+ # set up options below for your particular email service
27
+ Mail.defaults do
28
+ delivery_method :smtp,
29
+ address: "smtp.sendgrid.net",
30
+ port: 465,
31
+ user_name: "apikey",
32
+ password:,
33
+ authentication: :plain,
34
+ tls: true
35
+ end
36
+ end
37
+ ```
38
+
39
+ And call that from your configuration in `config/initializers.rb`:
40
+
41
+ ```ruby
42
+ only :server do
43
+ init :mail, password: ENV.fetch("SERVICE_API_KEY", nil) # can come from .env file or hosting environment
44
+ end
45
+ ```
46
+
47
+ Next, add Authtown's initialization for your configuration. Here's a basic set of options:
48
+
49
+ ```ruby
50
+ init :lifeform
51
+
52
+ init :authtown do
53
+ # Defaults, uncomment to modify:
54
+ #
55
+ # account_landing_page "/account/profile"
56
+ # user_class_resolver -> { User }
57
+ # user_name_field :first_name
58
+
59
+ rodauth_config -> do
60
+ email_from "Your Name <youremail@example.com>"
61
+
62
+ reset_password_email_body do
63
+ "Howdy! You or somebody requested a password reset for your account.\n" \
64
+ "If that's legit, here's the link:\n#{reset_password_email_link}\n\n" \
65
+ "Otherwise, you may safely ignore this message.\n\nThanks!\n–You @ Company"
66
+ end
67
+
68
+ enable :http_basic_auth if Bridgetown.env.test?
69
+
70
+ # You can define additional options here as provided by Rodauth directly
71
+ end
72
+ end
73
+ ```
74
+
75
+ You will need to generate a secret key for Roda's session handling. Run `bin/bridgetown secret` to copy that into your .env file.
76
+
77
+ ```env
78
+ RODA_SECRET_KEY=1f8dbd0da3a4...
79
+ ```
80
+
81
+ You will also need to generate a Sequel migration for your user accounts. Here is an example, you can tweak as necessary. Run `bin/bridgetown db:migrations:new filename=create_users`, then edit the file:
82
+
83
+ ```ruby
84
+ Sequel.migration do
85
+ change do
86
+ extension :date_arithmetic
87
+
88
+ create_table(:users) do
89
+ primary_key :id, type: :Bignum
90
+ citext :email, null: false
91
+ # Not available on SQLite
92
+ constraint :valid_email, email: /^[^,;@ \r\n]+@[^,@; \r\n]+\.[^,@; \r\n]+$/
93
+ String :first_name
94
+ String :password_hash, null: false
95
+ index :email, unique: true
96
+
97
+ DateTime :created_at
98
+ DateTime :updated_at
99
+ end
100
+
101
+ # Used by the remember me feature
102
+ create_table(:account_remember_keys) do
103
+ foreign_key :id, :users, primary_key: true, type: :Bignum
104
+ String :key, null: false
105
+ DateTime :deadline, { null: false, default: Sequel.date_add(Sequel::CURRENT_TIMESTAMP, days: 30) }
106
+ end
107
+
108
+ create_table(:account_password_reset_keys) do
109
+ foreign_key :id, :users, primary_key: true, type: :Bignum
110
+ String :key, null: false
111
+ DateTime :deadline, { null: false, default: Sequel.date_add(Sequel::CURRENT_TIMESTAMP, days: 1) }
112
+ DateTime :email_last_sent, null: false, default: Sequel::CURRENT_TIMESTAMP
113
+ end
114
+ end
115
+ end
116
+ ```
117
+
118
+ Also create your User model:
16
119
 
17
120
  ```ruby
18
- init :authtown
121
+ # ./models/user.rb
122
+
123
+ require "bcrypt"
124
+
125
+ class User < Sequel::Model
126
+ def self.password_for_string(str) # helper method
127
+ BCrypt::Password.create(str).to_s
128
+ end
129
+ end
130
+ ```
131
+
132
+ And then run `bin/bridgetown db:migrate`.
133
+
134
+ Now, let's set up our forms and auth pages. We'll begin by creating an Account form:
135
+
136
+ ```ruby
137
+ # ./models/forms/account.rb
138
+
139
+ require "lifeform" # Needed because Zeitwerk will try loading Forms::Lifeform and then fail.
140
+
141
+ module Forms
142
+ class Account < Lifeform::Form
143
+ fields do
144
+ field rodauth.login_param.to_sym,
145
+ type: :email,
146
+ label: rodauth.login_label,
147
+ required: true,
148
+ autocomplete: "username",
149
+ autofocus: true
150
+ field :first_name,
151
+ label: "Your Name",
152
+ autocomplete: "name",
153
+ required: true
154
+ field rodauth.password_param.to_sym,
155
+ type: :password,
156
+ label: rodauth.password_label,
157
+ required: true,
158
+ autocomplete: rodauth.password_field_autocomplete_value
159
+ field :submit, type: :submit_button, label: rodauth.login_button
160
+ end
161
+ end
162
+ end
163
+ ```
164
+
165
+ Next, let's create the pages for logging in or creating an account.
166
+
167
+ **src/_routes/auth/login.erb**
168
+
169
+ ```erb
170
+ ---<%
171
+ render_with do
172
+ layout :page
173
+ title "Sign In"
174
+ end
175
+ %>---
176
+
177
+ <% if rodauth.logged_in? %>
178
+ <p style="text-align:center">
179
+ It looks like you're already signed in. Would you like to <a href="/account/profile">view your profile?</a>
180
+ </p>
181
+ <% end %>
182
+
183
+ <article>
184
+ <%= render Forms::Account.new(id: "login-form", url: rodauth.login_path, class: "centered") do |f| %>
185
+ <%= render "form_errors" %>
186
+ <%=
187
+ render f.field(
188
+ rodauth.login_param,
189
+ value: r.params[rodauth.login_param],
190
+ aria: { invalid: rodauth.field_error(rodauth.login_param).present? }
191
+ )
192
+ %>
193
+ <%=
194
+ render f.field(
195
+ rodauth.password_param,
196
+ aria: { invalid: rodauth.field_error(rodauth.password_param).present? }
197
+ ) %>
198
+ <%=
199
+ render f.field(:submit)
200
+ %>
201
+ <% end %>
202
+ </article>
203
+
204
+ <p>Need to reset password? <a href="<%= rodauth.reset_password_request_path %>">Guess so ➞</a></p>
205
+
206
+ <hr />
207
+
208
+ <p>Don't have an account yet? <a href="<%= rodauth.create_account_path %>">Sign up today!</a></p>
209
+ ```
210
+
211
+ **src/_routes/auth/create-account.erb**
212
+
213
+ ```erb
214
+ ---<%
215
+ render_with do
216
+ layout :page
217
+ title "Sign Up"
218
+ end
219
+ %>---
220
+
221
+ <% if rodauth.logged_in? %>
222
+ <p style="text-align:center">
223
+ It looks like you're already signed in. Would you like to <a href="/account/profile">view your profile?</a>
224
+ </p>
225
+ <% end %>
226
+
227
+ <article>
228
+ <%= render Forms::Account.new(url: rodauth.create_account_path, class: "centered") do |f| %>
229
+ <%= render "form_errors" %>
230
+ <%=
231
+ render f.field(
232
+ rodauth.login_param,
233
+ value: r.params[rodauth.login_param],
234
+ aria: { invalid: rodauth.field_error(rodauth.login_param).present? }
235
+ )
236
+ %>
237
+ <%=
238
+ render f.field(
239
+ :first_name,
240
+ value: r.params[:first_name]
241
+ )
242
+ %>
243
+ <%=
244
+ render f.field(
245
+ rodauth.password_param,
246
+ aria: { invalid: rodauth.field_error(rodauth.password_param).present? }
247
+ ) %>
248
+ <%=
249
+ render f.field(:submit, label: rodauth.create_account_button)
250
+ %>
251
+ <% end %>
252
+ </article>
253
+
254
+ <% unless rodauth.logged_in? %>
255
+ <p style="text-align:center">Have an account already? <a href="/auth/login">Sign in here</a>.</p>
256
+ <% end %>
257
+ ```
258
+
259
+ **src/_partials/form_errors.erb**
260
+
261
+ ```erb
262
+ <form-errors>
263
+ <p aria-live="assertive">
264
+ <% if flash[:error] || rodauth.field_error(rodauth.login_param) || rodauth.field_error(rodauth.password_param) %>
265
+ <%= flash[:error] %>:
266
+ <br/>
267
+ <small>
268
+ <% if rodauth.field_error(rodauth.login_param) %>
269
+ <%= rodauth.field_error(rodauth.login_param) %>
270
+ <% end %>
271
+ <% if rodauth.field_error(rodauth.password_param) %>
272
+ <%= rodauth.field_error(rodauth.password_param) %>
273
+ <% end %>
274
+ </small>
275
+ <% end %>
276
+ </p>
277
+ </form-errors>
278
+ ```
279
+
280
+ We'll still need ones for password reset, but let's hold off for the moment. We're almost ready to test this out, but you'll also need an account profile page for when the user's successfully signed in:
281
+
282
+ **src/_routes/account/profile.erb**
283
+
284
+ ```erb
285
+ ---<%
286
+ rodauth.require_authentication # always include this before logged-in only routes
287
+
288
+ render_with do
289
+ layout :page
290
+ title "Your Account"
291
+ end
292
+ %>---
293
+
294
+ <%= markdownify do %>
295
+
296
+ Welcome back, **<%= current_user.first_name %>**.
297
+
298
+ (other content here)
299
+
300
+ <% end %>
301
+
302
+ <hr />
303
+
304
+ <form method="post" action="<%= rodauth.logout_path %>">
305
+ <%= csrf_tag(rodauth.logout_path) %>
306
+ <p>You’re logged in as: <strong><%= current_user.email %></strong>.</p>
307
+ <button type="submit"><%= rodauth.logout_button %></button>
308
+ </form>
19
309
  ```
20
310
 
21
- ## Usage
311
+ At this point, you should be able to start up your Bridgetown site, navigate to `/auth/create-account`, and test creating an account, logging out, logging back in, etc.
312
+
313
+ As a final step, we'll need to handle password reset. Add the following pages:
314
+
315
+ **src/_routes/auth/reset-password-request.erb**
316
+
317
+ ```erb
318
+ ---<%
319
+ render_with do
320
+ layout :page
321
+ title "Reset Your Password"
322
+ end
323
+ %>---
324
+
325
+ <article>
326
+ <%= render Forms::Account.new(url: rodauth.reset_password_request_path, class: "centered") do |f| %>
327
+ <%= render "form_errors" %>
328
+ <%=
329
+ render f.field(
330
+ rodauth.login_param,
331
+ value: r.params[rodauth.login_param],
332
+ aria: { invalid: rodauth.field_error(rodauth.login_param).present? }
333
+ )
334
+ %>
335
+ <%=
336
+ render f.field(:submit, label: rodauth.reset_password_request_button)
337
+ %>
338
+ <% end %>
339
+ </article>
340
+ ```
341
+
342
+
343
+ **src/_routes/auth/reset-password.erb**
344
+
345
+ ```erb
346
+ ---<%
347
+ render_with do
348
+ layout :page
349
+ title "Save New Password"
350
+ end
351
+ %>---
352
+
353
+ <article>
354
+ <%= render Forms::Account.new(url: rodauth.reset_password_path, class: "centered") do |f| %>
355
+ <%= render "form_errors" %>
356
+ <%=
357
+ render f.field(
358
+ rodauth.password_param,
359
+ aria: { invalid: rodauth.field_error(rodauth.password_param).present? }
360
+ )
361
+ %>
362
+ <%=
363
+ render f.field(:submit, label: rodauth.reset_password_button)
364
+ %>
365
+ <% end %>
366
+ </article>
367
+ ```
368
+
369
+ **src/_routes/account/reset-link-sent.erb**
370
+
371
+ ```erb
372
+ ---<%
373
+ render_with do
374
+ layout :page
375
+ title "Reset Link Sent"
376
+ end
377
+ %>---
378
+
379
+ <p style="text-align: center">Check your email, it should be arriving in just a moment.</p>
380
+ ```
22
381
 
23
- The plugin will…
382
+ Now when you navigate to `/auth/reset-password-request`, you should be able to get a link for saving a new password.
24
383
 
25
- ## Testing
384
+ ## Testing the Gem
26
385
 
27
386
  * Run `bundle exec rake test` to run the test suite
28
387
  * Or run `script/cibuild` to validate with Rubocop and Minitest together.
29
388
 
30
389
  ## Contributing
31
390
 
32
- 1. Fork it (https://github.com/username/my-awesome-plugin/fork)
391
+ 1. Fork it (https://github.com/bridgetownrb/authtown)
33
392
  2. Clone the fork using `git clone` to your local development machine.
34
393
  3. Create your feature branch (`git checkout -b my-new-feature`)
35
394
  4. Commit your changes (`git commit -am 'Add some feature'`)
data/authtown.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
 
18
18
  spec.required_ruby_version = ">= 3.1"
19
19
 
20
- spec.add_dependency "bridgetown", ">= 1.3.0", "< 2.0"
20
+ spec.add_dependency "bridgetown", ">= 2.0"
21
21
  spec.add_dependency "rodauth", ">= 2.30"
22
22
  spec.add_dependency "bcrypt", ">= 3.1"
23
23
  spec.add_dependency "mail", ">= 2.8"
@@ -19,6 +19,7 @@ Bridgetown.initializer :authtown do |
19
19
 
20
20
  config.only :server do
21
21
  require "authtown/routes/rodauth"
22
+ require "authtown/rodauth_mixin"
22
23
 
23
24
  # @param app [Class<Roda>]
24
25
  config.roda do |app|
@@ -78,6 +79,8 @@ Bridgetown.initializer :authtown do |
78
79
 
79
80
  instance_exec(&rodauth_config) if rodauth_config
80
81
  end
82
+
83
+ app.rodauth.prepend Authtown::RodauthMixin
81
84
  end
82
85
  end
83
86
 
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This gets prepended into the RodAuth class after plugin load
4
+ module Authtown::RodauthMixin
5
+ def login_failed_reset_password_request_form
6
+ # part of reset_password internals…no-op since we're rendering our own forms
7
+ end
8
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Authtown
4
- VERSION = "0.4.0"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # This gets prepended into the RodaApp class
3
4
  module Authtown::ViewMixin
4
5
  def locals = @_route_locals
5
6
 
data/lib/authtown.rb CHANGED
@@ -5,7 +5,6 @@ require "mail"
5
5
  require "authtown/builder"
6
6
  require "authtown/view_mixin"
7
7
 
8
- # rubocop:disable Layout/LineLength
9
8
  ### Simple migration strategy:
10
9
  #
11
10
  # Sequel.migration do
@@ -36,7 +35,6 @@ require "authtown/view_mixin"
36
35
  # end
37
36
  # end
38
37
  # end
39
- # rubocop:enable Layout/LineLength
40
38
 
41
39
  Thread.attr_accessor :authtown_state
42
40
  class Authtown::Current
metadata CHANGED
@@ -1,23 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: authtown
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bridgetown Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-06 00:00:00.000000000 Z
11
+ date: 2025-09-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bridgetown
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
- - !ruby/object:Gem::Version
19
- version: 1.3.0
20
- - - "<"
21
18
  - !ruby/object:Gem::Version
22
19
  version: '2.0'
23
20
  type: :runtime
@@ -25,9 +22,6 @@ dependencies:
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - ">="
28
- - !ruby/object:Gem::Version
29
- version: 1.3.0
30
- - - "<"
31
25
  - !ruby/object:Gem::Version
32
26
  version: '2.0'
33
27
  - !ruby/object:Gem::Dependency
@@ -133,6 +127,7 @@ files:
133
127
  - lib/authtown.rb
134
128
  - lib/authtown/builder.rb
135
129
  - lib/authtown/initializer.rb
130
+ - lib/authtown/rodauth_mixin.rb
136
131
  - lib/authtown/routes/rodauth.rb
137
132
  - lib/authtown/version.rb
138
133
  - lib/authtown/view_mixin.rb
@@ -155,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
150
  - !ruby/object:Gem::Version
156
151
  version: '0'
157
152
  requirements: []
158
- rubygems_version: 3.5.3
153
+ rubygems_version: 3.5.16
159
154
  signing_key:
160
155
  specification_version: 4
161
156
  summary: Rodauth integration for Bridgetown