authtown 0.4.0 → 0.5.0

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