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 +4 -4
- data/.rubocop.yml +4 -0
- data/.rubocop_todo.yml +10 -3
- data/AGENTS.md +317 -0
- data/CHANGELOG.md +56 -0
- data/Gemfile +5 -5
- data/Gemfile.lock +130 -127
- data/README.md +33 -1
- data/lib/rodauth/features/account_id_obfuscation.rb +317 -0
- data/lib/rodauth/features/hmac_secret_guard.rb +55 -47
- data/lib/rodauth/features/jwt_secret_guard.rb +55 -45
- data/lib/rodauth/features/table_guard.rb +222 -77
- data/lib/rodauth/secret_guard.rb +137 -0
- data/lib/rodauth/sequel_generator.rb +17 -5
- data/lib/rodauth/table_inspector.rb +1 -1
- data/lib/rodauth/tools/account_id_cipher.rb +150 -0
- data/lib/rodauth/tools/migration.rb +3 -4
- data/lib/rodauth/tools/version.rb +1 -1
- data/lib/rodauth/tools.rb +2 -0
- data/package-lock.json +944 -77
- data/package.json +1 -1
- metadata +6 -3
- data/CLAUDE.md +0 -262
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 39d6a88fc0c69fd330a7ad5fbcce20eef2d1069fc317f9e21d28ef286cbea4d8
|
|
4
|
+
data.tar.gz: 30511219eb536cb7d135706866ba497c5f66b89c124baeeab9f2726021c8eeb1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f33049dc3a878378dc0752173c4bbbf3ce66e278947d5e809a86eff788b78b1f58f556d8a77bb1d90c0395557d43bb8b9fdf2c739ab6e4acd906df523dbc1076
|
|
7
|
+
data.tar.gz: 7a7cff39a3a3ca2eed50a16e5daad470d1e39425f1214d81270fe308a253b24e0e15a1b31c7e1a08f84aab38df3c2a049f3a3480e5040353adc83e0cf72fbf78
|
data/.rubocop.yml
CHANGED
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:
|
|
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:
|
|
67
|
+
Max: 69
|
|
68
68
|
|
|
69
69
|
# Offense count: 3
|
|
70
70
|
# Configuration parameters: CountComments, CountAsOne.
|
|
71
71
|
Metrics/ModuleLength:
|
|
72
|
-
Max:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
31
|
-
gem 'tilt', '~> 2.
|
|
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'
|