rodauth-tools 0.3.1 → 0.4.1

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: d6b7dce9af93d884748ed19771aff8061295da616488cde6b1d8cd22025f2165
4
- data.tar.gz: b57d1b4d34dc706eb55e2184fee7fced8e7f789037527a5952ce22bfffc3b1ab
3
+ metadata.gz: 39d6a88fc0c69fd330a7ad5fbcce20eef2d1069fc317f9e21d28ef286cbea4d8
4
+ data.tar.gz: 30511219eb536cb7d135706866ba497c5f66b89c124baeeab9f2726021c8eeb1
5
5
  SHA512:
6
- metadata.gz: 9988d318c4ef84a8f350e81ab831ebbeac83072e15a3f76d4bd63a0a297e37ca45af73bfcb65efaab5a1220b719e9fc52e03ca27df4d92f32ede407a87a7c023
7
- data.tar.gz: 45c670a15937b958cdb4c39b50672f6b5de179405777a288fb4893f1f987ad8baee54f88e5ef833541b11d83139bf6230a39aaa2a1afffe951fb29512e311020
6
+ metadata.gz: f33049dc3a878378dc0752173c4bbbf3ce66e278947d5e809a86eff788b78b1f58f556d8a77bb1d90c0395557d43bb8b9fdf2c739ab6e4acd906df523dbc1076
7
+ data.tar.gz: 7a7cff39a3a3ca2eed50a16e5daad470d1e39425f1214d81270fe308a253b24e0e15a1b31c7e1a08f84aab38df3c2a049f3a3480e5040353adc83e0cf72fbf78
data/.rubocop.yml CHANGED
@@ -11,6 +11,10 @@ AllCops:
11
11
  NewCops: enable
12
12
  SuggestExtensions: false
13
13
 
14
+ Style/OneClassPerFile:
15
+ Exclude:
16
+ - 'examples/**/*'
17
+
14
18
  Style/StringLiterals:
15
19
  EnforcedStyle: single_quotes
16
20
 
data/.rubocop_todo.yml CHANGED
@@ -49,7 +49,7 @@ Metrics/AbcSize:
49
49
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode.
50
50
  # AllowedMethods: refine
51
51
  Metrics/BlockLength:
52
- Max: 509
52
+ Max: 532
53
53
 
54
54
  # Offense count: 1
55
55
  # Configuration parameters: CountComments, CountAsOne.
@@ -64,12 +64,12 @@ Metrics/CyclomaticComplexity:
64
64
  # Offense count: 41
65
65
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
66
66
  Metrics/MethodLength:
67
- Max: 64
67
+ Max: 69
68
68
 
69
69
  # Offense count: 3
70
70
  # Configuration parameters: CountComments, CountAsOne.
71
71
  Metrics/ModuleLength:
72
- Max: 511
72
+ Max: 534
73
73
 
74
74
  # Offense count: 10
75
75
  # Configuration parameters: AllowedMethods, AllowedPatterns.
@@ -108,6 +108,8 @@ RSpec/DescribeClass:
108
108
  - '**/spec/views/**/*'
109
109
  - 'spec/rodauth/features/external_identity/external_identity_spec.rb'
110
110
  - 'spec/rodauth/features/external_identity_autocreate_integration_spec.rb'
111
+ - 'spec/rodauth/features/secret_guard_combined_spec.rb'
112
+ - 'spec/rodauth/features/table_guard/table_guard_destructive_modes_spec.rb'
111
113
  - 'spec/rodauth/features/table_guard/table_guard_integration_spec.rb'
112
114
  - 'spec/rodauth/features/table_guard/table_guard_simple_spec.rb'
113
115
  - 'spec/rodauth/features/table_guard/table_guard_spec.rb'
@@ -154,6 +156,7 @@ Style/Documentation:
154
156
  Exclude:
155
157
  - 'spec/**/*'
156
158
  - 'test/**/*'
159
+ - 'lib/rodauth/features/account_id_obfuscation.rb'
157
160
  - 'lib/rodauth/features/external_identity.rb'
158
161
  - 'lib/rodauth/features/hmac_secret_guard.rb'
159
162
  - 'lib/rodauth/features/jwt_secret_guard.rb'
@@ -204,6 +207,8 @@ Style/SafeNavigation:
204
207
  - 'spec/rodauth/features/external_identity_autocreate_integration_spec.rb'
205
208
  - 'spec/rodauth/features/hmac_secret_guard/hmac_secret_guard_spec.rb'
206
209
  - 'spec/rodauth/features/jwt_secret_guard/jwt_secret_guard_spec.rb'
210
+ - 'spec/rodauth/features/secret_guard_combined_spec.rb'
211
+ - 'spec/rodauth/features/table_guard/table_guard_destructive_modes_spec.rb'
207
212
  - 'spec/rodauth/features/table_guard/table_guard_simple_spec.rb'
208
213
 
209
214
  # Offense count: 1
@@ -231,6 +236,8 @@ Style/SymbolProc:
231
236
  - 'spec/rodauth/features/external_identity_autocreate_integration_spec.rb'
232
237
  - 'spec/rodauth/features/hmac_secret_guard/hmac_secret_guard_spec.rb'
233
238
  - 'spec/rodauth/features/jwt_secret_guard/jwt_secret_guard_spec.rb'
239
+ - 'spec/rodauth/features/secret_guard_combined_spec.rb'
240
+ - 'spec/rodauth/features/table_guard/table_guard_destructive_modes_spec.rb'
234
241
  - 'spec/rodauth/features/table_guard/table_guard_integration_spec.rb'
235
242
  - 'spec/rodauth/features/table_guard/table_guard_simple_spec.rb'
236
243
  - 'try/features/external_identity_try.rb'
data/AGENTS.md ADDED
@@ -0,0 +1,317 @@
1
+ # AGENTS.md
2
+
3
+ This file provides guidance to coding agents working in this repository.
4
+
5
+ ## Project Purpose
6
+
7
+ Framework-agnostic utilities for Rodauth authentication:
8
+
9
+ 1. **External Rodauth Features** - `table_guard` for database validation, `external_identity` for external service IDs, `hmac_secret_guard` for HMAC secret validation, `jwt_secret_guard` for JWT secret validation, `account_id_obfuscation` for hiding numeric account ids in email links and cookies
10
+ 2. **Sequel Migration Generator** - Generate migrations for 19 Rodauth features
11
+
12
+ **Not a framework adapter.** For Rails integration, use rodauth-rails. This project demonstrates Rodauth's extensibility and provides reference implementations.
13
+
14
+ **Status:** Experimental. The gemspec says it plainly — "experimental stuff that may come and go" — so treat the API as unstable. It *is* published to RubyGems (`rodauth-tools`), and at least one production app depends on it, so a breaking change is a real cost to someone: bump the version and write the CHANGELOG entry.
15
+
16
+ **Namespace history (2025-10):** Namespace changed from `Rodauth::Rack::Generators::Migration` to `Rodauth::Tools::Migration`. This reflects the project's evolution away from being a Rack adapter toward being a collection of framework-agnostic utilities. The migration generator is now deprecated in favor of the `table_guard` feature with `sequel_mode`.
17
+
18
+ ## Development Commands
19
+
20
+ ```bash
21
+ # Run all tests
22
+ bundle exec rspec # or: bundle exec rake
23
+
24
+ # Tryouts suite (documentation-style tests; auto-discovers try/, NOT run by CI)
25
+ bundle exec try
26
+
27
+ # Lint (config in .rubocop.yml, with .rubocop_todo.yml exclusions)
28
+ bundle exec rubocop lib/ spec/
29
+
30
+ # Interactive console with helpers
31
+ bin/console
32
+ ```
33
+
34
+ ## Architecture Overview
35
+
36
+ ### Core Components
37
+
38
+ **lib/rodauth/features/table_guard.rb** - External Rodauth feature
39
+
40
+ - Uses `Rodauth::Feature.define(:table_guard, :TableGuard)` pattern
41
+ - Validates database tables exist for enabled features at `post_configure` time
42
+ - Provides introspection methods: `missing_tables`, `table_status`, `list_all_required_tables`
43
+ - Configurable modes: `:warn`, `:error`, `:silent`, or custom block handler
44
+ - Demonstrates proper feature lifecycle hooks and configuration DSL
45
+
46
+ **lib/rodauth/features/external_identity.rb** - External Rodauth feature
47
+
48
+ - Uses `Rodauth::Feature.define(:external_identity, :ExternalIdentity)` pattern
49
+ - Declares `accounts` columns holding IDs from external services (`external_identity_column :stripe_customer_id`) and generates a reader per column
50
+ - Layer 1 is the bare column plus conflict policy (`external_identity_on_conflict`: `:error`, `:warn`, `:skip`) and column presence checking (`external_identity_check_columns`: `true`, `false`, `:autocreate`)
51
+ - Layer 2 hangs lifecycle callables off the same declaration: `before_create_account`, `formatter`, `validator`, `verifier`, `handshake`
52
+ - Introspection: `external_identity_column_list`, `external_identity_column_config`, `external_identity_status`
53
+
54
+ **lib/rodauth/features/hmac_secret_guard.rb** - External Rodauth feature
55
+
56
+ - Uses `Rodauth::Feature.define(:hmac_secret_guard, :HmacSecretGuard)` pattern
57
+ - Automatically loads HMAC secret from environment variable (defaults to `HMAC_SECRET`)
58
+ - Validates secret is configured at application startup via `post_configure` hook
59
+ - Production mode: Raises `ConfigurationError` if secret missing, blank, or (when `minimum_secret_length` is set) too short
60
+ - Development mode: Logs warning and uses a fallback secret (random per-process by default)
61
+ - Deletes secret from ENV after loading for security (strips whitespace; blank values treated as absent)
62
+ - Provides `production?`, `validate_hmac_secret!`, and (aliased) `validate_secrets!` public methods
63
+
64
+ **lib/rodauth/features/jwt_secret_guard.rb** - External Rodauth feature
65
+
66
+ - Uses `Rodauth::Feature.define(:jwt_secret_guard, :JwtSecretGuard)` pattern
67
+ - Automatically loads JWT secret from environment variable (defaults to `JWT_SECRET`)
68
+ - Validates secret is configured at application startup via `post_configure` hook
69
+ - Production mode: Raises `ConfigurationError` if secret missing, blank, or (when `minimum_secret_length` is set) too short
70
+ - Development mode: Logs warning and uses a fallback secret (random per-process by default)
71
+ - Deletes secret from ENV after loading for security (strips whitespace; blank values treated as absent)
72
+ - Provides `production?`, `validate_jwt_secret!`, and (aliased) `validate_secrets!` public methods
73
+ - Defines `jwt_secret` configuration method for standalone use
74
+
75
+ **lib/rodauth/features/account_id_obfuscation.rb** - External Rodauth feature
76
+
77
+ - Uses `Rodauth::Feature.define(:account_id_obfuscation, :AccountIdObfuscation)` pattern
78
+ - Obfuscates `account_id` in email-link tokens and the remember cookie via scoped `token_param_value`/`account_from_key` (and conditional `_set_/_get_remember_cookie`) overrides
79
+ - Never touches the global `split_token`/`convert_token_id`, so `jwt_refresh` and other token consumers are unaffected
80
+ - Loads a dedicated `ACCOUNT_ID_SECRET` like the secret guards; `production?`/`validate_secrets!` lifecycle
81
+ - Non-digit version tag makes legacy-vs-obfuscated deterministic and drives config-driven secret rotation
82
+ - Delegates the crypto to the standalone `Rodauth::Tools::AccountIdCipher`
83
+
84
+ **lib/rodauth/tools/account_id_cipher.rb** - Framework-agnostic utility
85
+
86
+ - Keyed format-preserving obfuscation of a 64-bit integer id (4-round Feistel network, HMAC-SHA256 round function)
87
+ - Pure `Integer <-> 13-char Crockford Base32` bijection; stdlib `openssl` only, independently testable
88
+ - `decode` returns `nil` on malformed input so callers can pass legacy/foreign values through
89
+
90
+ **lib/rodauth/secret_guard.rb** - Shared support module (`Rodauth::SecretGuard`)
91
+
92
+ - Kind-parameterized (`:hmac`/`:jwt`) logic behind both secret-guard features
93
+ - Plain module functions taking the Rodauth instance explicitly — no mixed-in
94
+ method names, so both guards can be enabled together without one shadowing the
95
+ other (each feature's `post_configure` validates its own secret via `kind`)
96
+ - Handles ENV loading (`load_from_env!`), validation (`validate!`), blank/whitespace
97
+ detection, production detection, and minimum-length enforcement
98
+
99
+ **lib/rodauth/table_inspector.rb** - Shared support module (`Rodauth::TableInspector`)
100
+
101
+ - Discovers the tables an enabled feature set requires by inspecting a live Rodauth instance, rather than from a static table
102
+ - `discover_tables(rodauth)` returns `{ accounts_table: "accounts", ... }`; `table_information(rodauth)` adds the owning feature and column list per table
103
+ - This is what backs `table_guard`'s `_table_configuration`, so it is the source of the `*_table` method enumeration described under Hidden Tables below
104
+
105
+ **lib/rodauth/tools/migration.rb** - Sequel migration generator
106
+
107
+ - Generates database migrations for 19 Rodauth features
108
+ - Uses ERB templates in `lib/rodauth/tools/migration/sequel/`
109
+ - Provides `generate()` for migration content and `migration_name()` for filename
110
+ - Uses dry-inflector gem for robust table name pluralization
111
+ - Mock database adapter pattern when no real DB connection provided
112
+ - Deprecated in favor of table_guard feature with sequel_mode
113
+
114
+ ### How Rodauth Features Work
115
+
116
+ Rodauth features are modules that mix into `Rodauth::Auth` instances:
117
+
118
+ ```ruby
119
+ Feature.define(:feature_name, :FeatureName) do
120
+ # Configuration methods (overridable by users)
121
+ auth_value_method :setting_name, 'default_value'
122
+
123
+ # Public methods (overridable by users)
124
+ auth_methods :public_method
125
+
126
+ # Private methods (not overridable)
127
+ auth_private_methods :internal_helper
128
+
129
+ # Lifecycle hook - runs after configuration
130
+ def post_configure
131
+ super if defined?(super)
132
+ # Initialization code
133
+ end
134
+ end
135
+ ```
136
+
137
+ **Key Pattern:** Methods defined in features become part of the Rodauth instance. Users override them in configuration blocks:
138
+
139
+ ```ruby
140
+ plugin :rodauth do
141
+ enable :feature_name
142
+
143
+ setting_name 'custom_value' # Override auth_value_method
144
+
145
+ public_method do # Override auth_methods
146
+ # Custom implementation
147
+ end
148
+ end
149
+ ```
150
+
151
+ ### Table Guard Implementation Details
152
+
153
+ **Existence Checks (no logger suppression):** The `table_exists?` method matches names against the database's table/view list (`db.tables` + `db.views`) rather than probing each table with a SELECT. An earlier implementation used Sequel's `table_exists?` probe and suppressed the logger around it (clearing/restoring the shared `db.loggers` array) to hide the "no such table" ERROR that Sequel logs before catching internally — but that mutation of shared connection state was not thread-safe. Listing names avoids the failed probe entirely, so no logger suppression (and no shared-state mutation) is needed. Schema-qualified identifiers (a `Sequel::SQL::QualifiedIdentifier` or a `:schema__table` Symbol) are not present in the unqualified list, so they take a separate schema-aware `db.table_exists?` probe path.
154
+
155
+ **Cached-Method Backing Visibility:** `auth_cached_method :foo` registers `foo` via `auth_private_methods`, so the backing `_foo` must be defined **private**. Rodauth 2.45.0 audits this at feature-definition time with `private_method_defined?` and warns (`RODAUTH3: raise instead of warn`) when it isn't — a publicly-defined `_foo` trips the audit even though it exists. `_table_configuration` and `_column_requirements` therefore live below the feature's `private` keyword, alongside the equivalent backing methods in `account_id_obfuscation` and `external_identity`.
156
+
157
+ `spec/rodauth/feature_configuration_spec.rb` holds the line. Two things about it are load-bearing, so preserve them when editing:
158
+
159
+ - It **derives** its feature list from `lib/rodauth/features/*.rb` and requires each file. A hardcoded list would leave a new feature silently uncovered — and the require is what makes the audit run at all, since rodauth audits inside `Feature.define` and never sees a feature nothing loaded.
160
+ - Per feature it **re-runs rodauth's own** `def_configuration_methods` with stderr captured and asserts silence, as well as mirroring what that method checks. The re-run cannot drift as upstream tightens the audit and honours `allowed_undefined_configuration_methods` (rodauth's opt-out) for free; the mirrored checks are what name the offending method when it fails.
161
+
162
+ **Configuration Storage:** Uses instance variables set by `auth_value_method`:
163
+
164
+ - Block configs stored as Procs in `@table_guard_mode`
165
+ - Symbol configs stored directly as `:warn`, `:error`, `:silent`
166
+
167
+ **Check Strategy:**
168
+
169
+ 1. `should_check_tables?` examines `@table_guard_mode` to decide if checking is needed
170
+ 2. Returns `true` if mode is a Proc (block), enabling custom handlers
171
+ 3. Returns `true` if mode is `:warn` or `:error`, `false` for `:silent`
172
+
173
+ **Execution Flow:**
174
+
175
+ 1. `post_configure` hook calls `check_required_tables!` if `should_check_tables?` returns true
176
+ 2. `check_required_tables!` gets missing tables via `missing_tables`
177
+ 3. For symbol modes (`:warn`, `:error`), handles directly
178
+ 4. For block modes, calls block with missing tables, handles return value (`:error`, `:continue`, String)
179
+
180
+ **Introspection Methods:**
181
+
182
+ - `all_table_methods` - Finds all methods ending in `_table` using Ruby reflection
183
+ - `missing_tables` - Checks each required table against the existing table/view name set (fetched once per pass to avoid an N+1 of catalog queries)
184
+ - `table_status` - Returns array of hashes with method, table name, and existence status
185
+
186
+ ### Migration Generator Architecture
187
+
188
+ **Note:** The Migration class is deprecated. For new code, use the `table_guard` feature with `sequel_mode` instead.
189
+
190
+ **Template System:**
191
+
192
+ - Each feature has ERB template in `lib/rodauth/tools/migration/sequel/`
193
+ - Templates use binding from Migration instance for variables like `table_prefix`
194
+ - `generate()` loads, evaluates, and concatenates all feature templates
195
+
196
+ **Pluralization:**
197
+
198
+ - Uses `dry-inflector` gem for intelligent pluralization (e.g., "status" → "statuses")
199
+ - Helper method `pluralize(str)` available in templates via ERB binding
200
+ - Removed Rails/ActiveRecord dependencies (68 lines of cruft eliminated)
201
+
202
+ **Database Adapter Pattern:**
203
+
204
+ - `MockSequelDatabase` simulates database when no real connection provided
205
+ - Allows template generation without active database
206
+ - Real `Sequel::Database` object can be passed for actual migrations
207
+ - Supports PostgreSQL, MySQL, and SQLite database types
208
+
209
+ ### Hidden Tables Architecture
210
+
211
+ **Problem:** Some tables are created in ERB templates without corresponding `*_table` methods in Rodauth features.
212
+
213
+ **Example from base.erb:**
214
+
215
+ ```ruby
216
+ # base.erb creates THREE tables:
217
+ create_table(:account_statuses) # NO METHOD - Hidden!
218
+ create_table(:account_password_hashes) # NO METHOD - Hidden!
219
+ create_table(:accounts) # Has accounts_table method ✓
220
+ ```
221
+
222
+ **Why This Happens:**
223
+
224
+ - `account_statuses` - Lookup table for status values (Unverified=1, Verified=2, Closed=3). No method because users configure status IDs directly via `account_open_status_value`, etc.
225
+ - `account_password_hashes` - Separate table for security. Method is `account_password_hash_table` (singular), but ERB uses pluralized form based on `table_prefix`.
226
+
227
+ #### Solution: TemplateInspector Module
228
+
229
+ `lib/rodauth/template_inspector.rb` extracts table names directly from ERB templates by:
230
+
231
+ 1. Creating minimal binding context with `table_prefix`, `pluralize`, and mock `db`
232
+ 2. Evaluating ERB templates to render actual Ruby code
233
+ 3. Parsing rendered code for `create_table()` calls using regex
234
+ 4. Returning complete list of tables, including hidden ones
235
+
236
+ **Usage:**
237
+
238
+ ```ruby
239
+ # Extract all tables for a feature
240
+ tables = TemplateInspector.extract_tables_from_template(
241
+ :base,
242
+ table_prefix: 'account'
243
+ )
244
+ # => [:account_statuses, :account_password_hashes, :accounts]
245
+
246
+ # Get tables for multiple features
247
+ all_tables = TemplateInspector.all_tables_for_features(
248
+ [:base, :verify_account, :lockout],
249
+ table_prefix: 'account'
250
+ )
251
+ ```
252
+
253
+ **Impact on DROP Operations:**
254
+
255
+ Before TemplateInspector, `generate_drop_statements` only dropped dynamically discovered tables, missing hidden ones. Now it extracts the complete table list from ERB templates, ensuring all tables are properly dropped in correct dependency order.
256
+
257
+ **Key Insight:** ERB templates are the single source of truth for table schemas. By extracting information FROM templates instead of duplicating it in Ruby constants, we maintain consistency and eliminate hardcoded mappings.
258
+
259
+ ## Testing Patterns
260
+
261
+ **RSpec Structure:**
262
+
263
+ - `spec/spec_helper.rb` - Minimal configuration; requires `rodauth/tools` and `rack/test`. It does **not** require `hmac_secret_guard` or `jwt_secret_guard` — those feature files are not loaded by `rodauth/tools`, so a spec that needs them must require them itself
264
+ - Feature specs test both behavior and configuration
265
+ - Migration generator specs verify template output and configuration
266
+ - `spec/rodauth/feature_configuration_spec.rb` - Guards every feature against rodauth's definition-time audit (see Cached-Method Backing Visibility above)
267
+
268
+ **Tryouts:** `try/features/*_try.rb` holds documentation-style tests, run separately from RSpec (`bundle exec try`). Only `external_identity` has one today. CI runs `bundle exec rake`, whose default task is `spec` alone — so **tryouts is not gated**, and it has drifted red: 5 of 50 fail as of 0.4.1. Don't read a red tryouts run as damage you just caused; check against the base branch first.
269
+
270
+ **Console Helpers:**
271
+
272
+ - `setup_test_db` - Creates in-memory SQLite database with tables
273
+ - `create_app(db, features: [...])` - Creates Roda app with Rodauth configured
274
+ - Useful for interactive testing of table_guard and migration generator
275
+
276
+ ## Documentation Reference
277
+
278
+ **docs/rodauth-features-api.md** - Complete reference for feature development DSL methods
279
+
280
+ **docs/rodauth-integration.md** - Integrating Rodauth into a Rack app, and where this library fits
281
+
282
+ **docs/sequel-migrations.md** - Migration generator usage
283
+
284
+ **docs/rodauth-mail.md** - Email/SMTP configuration patterns
285
+
286
+ **docs/unresolved-bugs.md** - Known defects with analysis, not yet fixed. Read before "fixing" something surprising — it may already be written up here, with the reasoning for why it was left alone
287
+
288
+ **docs/features/** and **docs/examples/** - Per-feature documentation and runnable examples
289
+
290
+ Rodauth's own object model (`Rodauth::Auth`, `Rodauth::Configuration`, `Rodauth::Feature`, `Rodauth::FeatureConfiguration`) is documented upstream in `doc/guides/internals.rdoc` in the rodauth gem.
291
+
292
+ ## Integration Pattern
293
+
294
+ Rodauth integrates with any Rack app via Roda middleware (NOT via this library):
295
+
296
+ ```ruby
297
+ # Create Roda app with Rodauth
298
+ class RodauthApp < Roda
299
+ plugin :middleware
300
+ plugin :rodauth do
301
+ enable :login, :logout
302
+ enable :table_guard # ← Feature from this library
303
+ db DB
304
+ end
305
+
306
+ route do |r|
307
+ r.rodauth
308
+ env['rodauth'] = rodauth
309
+ end
310
+ end
311
+
312
+ # Mount as Rack middleware
313
+ use RodauthApp
314
+ run MyApp
315
+ ```
316
+
317
+ Access in your app: `request.env['rodauth']` provides all authentication methods.
data/CHANGELOG.md CHANGED
@@ -5,6 +5,59 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.1] - 2026-08-10
9
+
10
+ ### Fixed
11
+
12
+ - **`table_guard`: silence rodauth 2.45.0 feature-definition warnings.**
13
+ `_table_configuration` and `_column_requirements` — the backing methods behind
14
+ the `table_configuration` / `column_requirements` `auth_cached_method`
15
+ declarations — were defined publicly. Rodauth registers those via
16
+ `auth_private_methods` and, as of 2.45.0, audits each backing method with
17
+ `private_method_defined?` at feature-definition time, so loading the feature
18
+ printed two `"Bug in Rodauth table_guard feature definition..."` warnings on
19
+ stderr. Both methods moved into the feature's `private` section (matching
20
+ `account_id_obfuscation` and `external_identity`). Upstream marks the warning
21
+ `RODAUTH3: raise instead of warn`, so this would have become a load-time error.
22
+ Added `spec/rodauth/feature_configuration_spec.rb`, which mirrors rodauth's
23
+ audit across all five features so a regression fails the suite.
24
+
25
+ ### Added
26
+
27
+ - **Feature-definition audit coverage** (`spec/rodauth/feature_configuration_spec.rb`).
28
+ The feature list is derived from `lib/rodauth/features/*.rb` rather than
29
+ hand-maintained, so a newly added feature is covered as soon as its file lands
30
+ — and requiring those files is what makes the audit run at all, since rodauth
31
+ audits inside `Feature.define` and never sees a feature nothing requires.
32
+ Alongside the mirrored checks, each feature re-runs rodauth's own
33
+ `def_configuration_methods` with stderr captured and asserts it is silent, so
34
+ the coverage cannot drift as upstream tightens the audit and honours
35
+ `allowed_undefined_configuration_methods` (2.45.0's opt-out) without
36
+ reimplementing it.
37
+
38
+ ## [0.4.0] - 2026-07-05
39
+
40
+ ### Added
41
+
42
+ - **Account ID Obfuscation feature** (`account_id_obfuscation`) - Keyed, reversible
43
+ obfuscation of the numeric `account_id` that leaks into email-link tokens (e.g.
44
+ `/verify-account?key=2_...`) and the remember-me cookie, with no database schema
45
+ change. Wraps the two `email_base` chokepoints (`token_param_value` /
46
+ `account_from_key`), so a single `enable` covers verify_account, reset_password,
47
+ email_auth, verify_login_change and lockout/unlock; also obfuscates the remember
48
+ cookie when `remember` is enabled. Loads a dedicated `ACCOUNT_ID_SECRET` following
49
+ the `hmac_secret_guard` pattern. Backward compatible with in-flight numeric links
50
+ and legacy cookies, with config-driven secret rotation via a version tag.
51
+ - **`Rodauth::Tools::AccountIdCipher`** - Framework-agnostic 4-round Feistel
52
+ format-preserving encryption utility (stdlib `openssl` only, no new dependencies).
53
+
54
+ ## [0.3.1] - 2026-01-13
55
+
56
+ ### Changed
57
+
58
+ - Dependency and development-tooling maintenance (bundled `rodauth`, `sequel`,
59
+ `rubocop`, and related updates).
60
+
8
61
  ## [0.3.0] - 2025-11-25
9
62
 
10
63
  ### Added
@@ -76,6 +129,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
76
129
  - Namespace changed from `Rodauth::Rack::Generators::Migration` to `Rodauth::Tools::Migration`
77
130
  - Evolution from Rack adapter to framework-agnostic utilities
78
131
 
132
+ [0.4.1]: https://github.com/delano/rodauth-tools/compare/v0.4.0...v0.4.1
133
+ [0.4.0]: https://github.com/delano/rodauth-tools/compare/v0.3.1...v0.4.0
134
+ [0.3.1]: https://github.com/delano/rodauth-tools/compare/v0.3.0...v0.3.1
79
135
  [0.3.0]: https://github.com/delano/rodauth-tools/compare/v0.2.0...v0.3.0
80
136
  [0.2.0]: https://github.com/delano/rodauth-tools/compare/v0.1.0...v0.2.0
81
137
  [0.1.0]: https://github.com/delano/rodauth-tools/releases/tag/v0.1.0
data/Gemfile CHANGED
@@ -8,12 +8,12 @@ gemspec
8
8
 
9
9
  gem 'dry-inflector'
10
10
  gem 'irb'
11
- gem 'rake', '~> 13.3'
11
+ gem 'rake', '~> 13.4'
12
12
 
13
13
  group :development do
14
14
  gem 'bundler-audit'
15
15
  gem 'rspec', '~> 3.0'
16
- gem 'rubocop', '~> 1.81'
16
+ gem 'rubocop', '~> 1.88'
17
17
  gem 'rubocop-rake'
18
18
  gem 'rubocop-rspec'
19
19
  end
@@ -21,14 +21,14 @@ end
21
21
  group :test do
22
22
  gem 'bcrypt', '~> 3.1'
23
23
  gem 'capybara'
24
- gem 'jwt', '~> 3.1'
24
+ gem 'jwt', '~> 3.2'
25
25
  gem 'rack-test', '~> 2.1'
26
26
  gem 'rails', '>= 6.0'
27
27
  gem 'rotp'
28
28
  gem 'rqrcode'
29
29
  gem 'sequel-activerecord_connection', '~> 2.0'
30
- gem 'sqlite3', '~> 2.8'
31
- gem 'tilt', '~> 2.4'
30
+ gem 'sqlite3', '~> 2.9'
31
+ gem 'tilt', '~> 2.8'
32
32
  gem 'tryouts', '~> 3.0'
33
33
  gem 'warning'
34
34
  gem 'webauthn' unless RUBY_ENGINE == 'jruby'