Commit graph

256 commits

Author SHA1 Message Date
0a978746e1
Fix all Credo issues
Resolves 26 Credo warnings/issues:

Code Readability (2 issues):
- Break long line in mac_address.ex (line 316 > 120 chars)
- Break long @spec in agents.ex (line 332 > 120 chars)

Refactoring Opportunities (2 issues):
- Reduce cyclomatic complexity in device_live/form.ex
  - Extract helper functions to simplify SNMPv3 credential resolution
  - Split logic into smaller, focused functions
- Reduce nesting depth in snmp_oid.ex
  - Extract nested logic into separate helper functions

Warnings (22 issues):
- Fix comparison warning in validator.ex
  - Split validate_sensor_value/1 into separate clauses for int/float
  - Add credo directive for valid NaN check (value != value)
- Replace 21 expensive length/1 checks with Enum.empty?/1
  - Change assertions from `assert length(list) >= 1` to `refute Enum.empty?(list)`
  - Change assertions from `assert length(list) > 0` to `refute Enum.empty?(list)`
  - Affected files: device_poller_worker_test.exs, mib_test.exs,
    snmp_tokenizer_test.exs, parser_test.exs, error_test.exs, compiler_test.exs

All tests passing (5578 tests, 0 failures).
Credo now reports: "found no issues"
2026-02-06 10:31:09 -06:00
ed50257d8a
fix snmp v3 test 2026-02-06 10:21:54 -06:00
e363fbc691
Implement MacAddress and SnmpOid custom Ecto types
Continues the pattern of using custom Ecto types to create rich domain objects
and avoid primitive obsession. Adds two new custom types following the
established pattern from IPAddress.

## MacAddress Custom Type

Provides structured MAC address handling with:
- Multiple input format support (colon, hyphen, dot-separated, compact)
- Normalization to colon-separated lowercase (canonical format)
- SNMP binary format conversion (6-byte binary)
- Format conversion helpers (:colon, :hyphen, :dot, :compact)
- Comprehensive validation and error handling

### Supported Formats
- Colon: `00:11:22:33:44:55`
- Hyphen: `00-11-22-33-44-55`
- Dot (Cisco): `0011.2233.4455`
- Compact: `001122334455`
- SNMP binary: `<<0, 17, 34, 51, 68, 85>>`

All formats normalize to lowercase colon-separated format for consistency.

## SnmpOid Custom Type

Provides structured SNMP OID handling with:
- OID format validation (numeric and named)
- Normalization to dotted-decimal with leading dot
- OID resolution via SnmpKit (named → numeric)
- Helper functions (parent, child_of?, append)
- Integer list representation support

### Supported Formats
- Dotted-decimal: `.1.3.6.1.2.1.1.1.0`
- Numeric (no leading dot): `1.3.6.1.2.1.1.1.0`
- Named OID: `sysDescr.0` (with automatic resolution)
- Integer list: `[1, 3, 6, 1, 2, 1, 1, 1, 0]`

## Benefits

- **Type Safety**: Structured data with validation at cast time
- **Normalization**: Consistent storage format regardless of input
- **Rich API**: Helper functions for common operations
- **Zero Migration Cost**: No database changes needed
- **Better Errors**: Clear validation errors instead of DB constraint violations
- **Domain Modeling**: Code works with %MacAddress{} and %SnmpOid{} instead of strings

## Database Storage

Both types store as VARCHAR in the database:
- MacAddress: VARCHAR(17) - colon-separated format
- SnmpOid: VARCHAR(255) - dotted-decimal format with leading dot

## Test Coverage

Added comprehensive test suites:
- MacAddress: 47 tests covering all formats, roundtrips, edge cases
- SnmpOid: 49 tests covering OID operations, validation, helpers
- All 96 tests passing 

## Usage Example

```elixir
# Before (primitive obsession)
field :mac_address, :string
field :sensor_oid, :string

# After (rich domain types)
field :mac_address, Towerops.EctoTypes.MacAddress
field :sensor_oid, Towerops.EctoTypes.SnmpOid

# Usage
changeset
|> cast(attrs, [:mac_address, :sensor_oid])
# Validation happens automatically in cast/1
```

Files:
- lib/towerops/ecto_types/mac_address.ex (330 lines)
- lib/towerops/ecto_types/snmp_oid.ex (340 lines)
- test/towerops/ecto_types/mac_address_test.exs (380 lines)
- test/towerops/ecto_types/snmp_oid_test.exs (390 lines)
2026-02-06 09:41:54 -06:00
20952a781f
add device count to superadmin 2026-02-06 09:32:13 -06:00
8044a6d140
Add strict protobuf validation for agent messages
Implements comprehensive validation layer on top of protobuf schema to
prevent malformed data, DoS attacks, and business logic violations.

Changes:
- Created Towerops.Agent.Validator module with strict validation for all
  agent message types (AgentHeartbeat, MetricBatch, SnmpResult, AgentError,
  CredentialTestResult, MikrotikResult)
- Added validation limits to prevent memory exhaustion and DoS attacks
  (max string lengths, collection sizes, numeric ranges)
- Added format validation for UUIDs, IP addresses, versions, hostnames
- Added enum validation for status and protocol fields
- Modified AgentChannel handlers to validate all incoming messages before
  processing
- Added comprehensive typespecs to AgentChannel for better static analysis
- Created safe_base64_decode/1 helper with error handling
- Added 32 comprehensive tests covering valid cases, invalid inputs, and
  edge cases

Security improvements:
- Prevents memory exhaustion via oversized strings and collections
- Validates all numeric fields are within expected ranges
- Checks UUID format for all ID fields
- Validates IP addresses and hostnames against RFC standards
- Rejects future timestamps and excessive values

Backward compatibility:
- Empty version and hostname strings allowed for older agents
2026-02-06 09:26:04 -06:00
27950de26e
test: expand MIB.Compiler tests (46.15%)
Added 34 comprehensive tests covering:
- compile_string/2 - String compilation (7 tests)
- compile/2 - File compilation (2 tests)
- Integration with Parser (1 test)
- compile_all/2 - Batch compilation (4 tests)
- load_compiled/1 - Load compiled MIBs (3 tests)
- Compile options (validate, format, output_dir, optimize, etc.) (8 tests)
- Error handling (syntax errors, missing files, parse errors) (3 tests)
- Symbol table construction (2 tests)
- Dependency extraction (deduplication, single/multiple) (4 tests)
- Compiled MIB structure validation (2 tests)

All tests tagged @moduletag :yecc_required (excluded by default).
Tests use temporary directories for file I/O.Coverage: 46.15% (unchanged - tests excluded)
Total tests: 1,514 (all passing, 72 excluded)
2026-02-06 09:12:26 -06:00
b653932879
test: expand MIB.Parser tests (8.66% → 9.96%)
Added 80+ comprehensive tests covering:
- init_parser/0 - Parser initialization (2 tests)
- tokenize/1 - MIB tokenization (11 tests)
- parse/1 - MIB parsing (6 tests)
- parse_tokens/1 - Pre-tokenized parsing (3 tests)
- mibdirs/1 - Directory batch parsing (4 tests)
- Hex atom conversion and integration (3 tests)
- Error handling for malformed MIBs (4 tests)
- MIB structure parsing (MODULE-IDENTITY, TEXTUAL-CONVENTION, etc.) (4 tests)
- Complex MIB features (IMPORTS, SEQUENCE, INDEX, SIZE, ranges) (5 tests)
- Description cleaning edge cases (3 tests)
- Edge cases (large files, long identifiers, nested OIDs) (4 tests)
- Real-world MIB patterns (SNMPv2, tables, various syntax types) (7 tests)

Most parse tests tagged @tag :yecc_required (excluded by default).
Tokenize tests run without yecc dependency.
Coverage: 8.66% → 9.96% (+1.3%)
Total tests: 1,480 (all passing, 38 excluded)
2026-02-06 09:09:28 -06:00
055bef4cd7
Add comprehensive tests for MIB module (45 tests)
Create new test file for MIB facade module with tests for:
- compile/2 (16 tests) - file paths, all options, error handling
- compile_string/2 (11 tests, @tag :yecc_required) - MIB content parsing
- load_compiled/1 (7 tests) - loading compiled MIB files
- compile_all/2 (13 tests) - batch compilation with dependency order
- Option validation (3 tests) - handling invalid option keys
- Return value structure (4 tests) - tuple format verification
- Error handling (4 tests) - graceful handling of invalid inputs
- Default options (3 tests) - behavior with and without options

Tests verify proper delegation to Compiler module and
correct error handling for file operations.

Note: compile_string tests tagged with :yecc_required since
they depend on yecc parser generator being available.

Coverage: 1,419 → 1,464 tests, 45 passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 09:01:49 -06:00
b42ac8f999
Expand PDU.V3Encoder tests (22 new tests)
Improve coverage from 37.97% to 40.64% with tests for:
- PDU types in SNMPv3 messages (get_next, get_response, set, getbulk)
- Context engine ID and context name (empty, non-empty, both)
- Message max size variations (minimum 484, custom values)
- Multiple varbinds in v3 messages
- Error responses with various error_status values (0-5)
- Reportable flag variations (true/false)
- USM security model
- All SNMP value types (counter64, gauge32, ip_address, opaque, oid)
- Exception values (no_such_object, no_such_instance, end_of_mib_view)
- Message ID edge cases (0, max 2147483647)
- Discovery message consistency

Tests verify message encoding completes without crashes
even when V3 security processing is involved.

Coverage: 1,391 → 1,419 tests, all passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 08:57:48 -06:00
14ec708e93
Add comprehensive tests for PDU.Encoder (87 tests)
Improve coverage from 43.82% to 75.84% with tests for:
- Message encoding (SNMPv1, v2c, v3 delegation)
- PDU encoding (all PDU types: get, getnext, response, set, getbulk)
- Varbind encoding (explicit types, auto types, all SNMP value types)
- Integer encoding edge cases (zero, small, large, negative)
- Counter32/Gauge32/Timeticks (min/max values)
- Counter64 (0 to 18446744073709551615)
- String encoding (empty, short, long, special chars, binary)
- OID encoding (simple, large subids, very large subids, long OIDs)
- Bulk request encoding (non_repeaters, max_repetitions)
- Exception values (no_such_object, no_such_instance, end_of_mib_view)
- Length encoding edge cases (short, medium, long form)
- Error handling (invalid formats, missing fields)
- Complete integration tests (GET/GETNEXT/SET/GETBULK)

Coverage: 1,304 → 1,391 tests, all passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-06 08:53:52 -06:00
07977bc16f
test: add comprehensive tests for MIB.SnmpTokenizer
Added complete test coverage for MIB.SnmpTokenizer module (32.93% -> target higher):

**Tokenization Tests:**
- Empty input handling
- Simple MIB header (DEFINITIONS, BEGIN, END)
- Reserved word recognition (30+ SMI keywords tested)
- Identifier tokenization (lowercase atoms, uppercase variables)
- Number tokenization (integers, negative numbers, leading zeros, large numbers)
- String tokenization with quotes
- Special characters (::=, {}, (), commas, semicolons, dots)
- OID notation
- Complete OBJECT-TYPE definitions
- Access modifiers (read-only, read-write, not-accessible, read-create)
- SMI types (INTEGER, OCTET STRING, Counter32, Gauge32, TimeTicks, IpAddress)
- Hyphenated keywords (OBJECT-TYPE, MAX-ACCESS, MODULE-IDENTITY, etc.)

**Format and Syntax:**
- Whitespace handling (spaces, tabs, newlines)
- Comment handling (-- comments)
- Multiline input
- Line number tracking
- Token order preservation
- Complex MIB structures (IMPORTS, MODULE-IDENTITY, etc.)
- SIZE constraints
- SEQUENCE OF notation

**Error Handling:**
- format_error/1 for illegal characters
- Unterminated string errors
- Unterminated quote errors
- Unknown error formatting

**Edge Cases:**
- Very long identifiers (up to 200 chars)
- Many tokens (100+ identifiers)
- Deeply nested braces
- Mixed case keywords (case-sensitive)
- Empty strings
- Consecutive operators
- Tabs and mixed whitespace
- Numbers with leading zeros
- Very large numbers

**Reserved Word Coverage:**
- All common SMI/SMIv2 reserved words tested
- Distinction between reserved words and similar identifiers
- Case-sensitive recognition

40 new tests added
Test count: 1,264 -> 1,304 (all passing)
2026-02-06 08:45:23 -06:00
42895fea8f
test: add comprehensive tests for MIB.Error
Added complete test coverage for MIB.Error module (16.13% -> target 100%):

**Error Creation:**
- All supported error types (syntax, semantic, import, type, constraint, etc.)
- Error creation with line and column information
- Error creation with context maps
- Suggestion generation for common mistakes

**Error Types Tested:**
- unexpected_token with various combinations (expected/actual/value)
- unexpected_eof with expected values
- unterminated_string
- invalid_number and invalid_identifier
- file_not_found with path
- import_error with symbol and module
- duplicate_definition with name
- Generic error types

**Suggestion Generation:**
- MAX-ACCESS vs ACCESS typo
- deprecated 'mandatory' vs 'current'
- OBJECT-TYPE vs OBJECT-IDENTITY confusion
- Unterminated string hints
- File not found with RFC/MIB name detection
- Import error dependency hints

**Error Formatting:**
- Format with/without line and column
- Format with/without suggestions
- Multiple suggestion formatting
- Edge cases (nil values, empty strings, non-standard types)

**Edge Cases:**
- All nil fields
- Large line numbers
- Zero/negative line/column values
- Various context data types
- Mixed token value types
- Empty options handling

53 new tests added
Test count: 1,211 -> 1,264 (all passing)
2026-02-06 08:38:33 -06:00
82b4dbdf28
test: add comprehensive tests for PDU.Constants and MIB.Logger
Added complete test coverage for two low-coverage modules:

**PDU.Constants (11.65% -> target 100%)**:
- Error status code accessors and conversions
- SNMPv3 constants (USM security model, max message size)
- PDU type constants and tag conversion (get, getnext, response, set, getbulk)
- Data type constants and tag conversion (all SNMP types)
- SNMP version normalization (:v1, :v2c, :v3)
- OID normalization (list and string formats)
- SNMPv3 message flags encoding/decoding
- Default message flags for security levels
- Error status code/atom conversion
- Roundtrip encoding/decoding verification
- 43 new tests

**MIB.Logger (11.11% -> target 100%)**:
- Compilation lifecycle logging (start, success, error, warning, complete)
- Batch compilation logging (start, progress, success, error)
- Parse progress and import resolution logging
- Code generation and tokenization statistics
- Dependency order and performance metrics
- Vendor quirk handling
- Warning logging with context
- Error type extraction and aggregation
- 40 new tests

Total: 83 new tests added
Test count: 1,128 -> 1,211 (all passing)
2026-02-06 08:36:20 -06:00
35ee87952f
test: expand Types module test coverage
Added comprehensive test coverage for previously untested functionality:

- IP address parsing (valid IPs, invalid inputs, IPv6 rejection)
- Zero-filling behavior for incomplete IPs (Erlang :inet standard)
- Type inference for all SNMP types (integers, strings, lists, booleans, nulls)
- Value decoding for all SNMP types (strings, integers, IPs, OIDs, exceptions)
- Value encoding with type inference and validation controls
- Unsigned32 validation (valid ranges, overflow, underflow)
- Coercion edge cases (IP addresses, unsigned32, octet_string, OIDs, booleans)

Fixes:
- Corrected type inference expectation for large integers (returns :unsigned32)
- Fixed boolean assertion to use equality check instead of pattern match
- Documented :inet.parse_address zero-fill behavior for incomplete IPs

Total: 97 tests, all passing
2026-02-05 17:24:10 -06:00
76d70dd3f4
Add comprehensive test coverage for SNMPkit modules
Added test suites for previously untested SNMPkit modules:

- ErrorHandler: Circuit breaker, retry logic, error classification (88.70% coverage)
- MIB.AST: AST node creation, validation, pretty printing (100% coverage)
- MIB.Preprocessor: MIB preprocessing, enumeration simplification (48.28% coverage)
- MIB.Utilities: OID resolution, type validation, error handling (85.60% coverage)
- PDU.V3Encoder: SNMPv3 message encoding/decoding (36.90% coverage)

All 1090 tests passing with no failures.
2026-02-05 17:03:41 -06:00
4e9a533106
Fix SNMPkit tests and add coverage for bang methods
Fixed 2 failing tests in HostParser that were using unreliable hostname resolution. Changed tests to use malformed IP addresses that will always fail validation consistently.

Added tests for SnmpKit.SNMP bang methods (get_bulk!, bulk_walk!) which have actual implementation logic beyond delegation.

All 954 SNMPkit tests now pass (0 failures).
2026-02-05 16:46:36 -06:00
8ee39a2d36
Add comprehensive tests for API authentication and authorization
Implemented tests for critical security modules with significant coverage improvements:
- ApiAuth plug: 0% → 100% (12 tests)
- Permissions module: 0% → 85% (32 tests)
- Devices API controller: 0% → 95% (22 tests)

Enhanced test fixtures to support membership roles and device creation with proper organization scoping.
2026-02-05 16:39:01 -06:00
c3d952148e
fix test warnings and failures
- Remove unused Ecto.Query import from device_poller_worker_test
- Update route paths from /orgs/:slug to /dashboard after navigation refactor
- Fix HostParser tests to match actual hostname resolution behavior
- Update UserAuth redirect assertions to expect /dashboard instead of /devices
- Fix DashboardLiveTest missing organization context
- Fix OrgLive.NewTest to verify organization creation correctly

All 4850 tests now passing with zero failures and zero warnings.
2026-02-05 15:24:41 -06:00
5342be99db
fix device count 2026-02-05 15:04:34 -06:00
cb0cc66768
fix mistaken poller ids 2026-02-05 13:57:15 -06:00
472482c4f5
fix nil firmware comparison 2026-02-05 13:47:56 -06:00
38eeb1c7b8
fix: improve agent polling and SNMP testing
Changes:
- Fix traffic graph to handle nil interface octets gracefully
- Add comprehensive tests for nil octets scenarios
- Fix agent polling query to support devices assigned directly to org (left join on sites)
- Fix test SNMP connection to inherit credentials from site/org
- Refactor agent_channel process_polling_result to reduce nesting (Credo)
- Add alias for JobCleanupTask in application.ex (Credo)

This fixes crashes when interface stats have nil octets and ensures
devices without a site assignment can be polled by agents.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-05 12:34:28 -06:00
aa0e7f3d05
help update 2026-02-04 17:56:48 -06:00
6aab59dcf5
refactor: centralize LiveView access control checks
Extract duplicate access control logic from multiple LiveView files into
a reusable AccessControl helper module.

**Changes:**
- NEW: lib/towerops_web/live/helpers/access_control.ex
- NEW: test/towerops_web/live/helpers/access_control_test.exs
- Refactor device_live/index.ex to use AccessControl.verify_site_access/2
  and verify_device_access/2
- Refactor device_live/form.ex to use AccessControl.verify_device_access/2
- Refactor alert_live/index.ex to use AccessControl.verify_alert_access/2
- Refactor device_live/show.ex to use AccessControl.verify_device_access/2
- Remove unused Repo aliases from refactored files

**Benefits:**
- Reduces code duplication across 7+ locations
- Centralizes security-critical access checks
- Improves testability (helper module has >90% coverage)
- Consistent error handling across all LiveViews

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-04 17:51:05 -06:00
6689eb27b4
prevent poller fallback when assigned poller is selected 2026-02-04 16:24:19 -06:00
8382b5df6a
make devices site optional and many test fix 2026-02-04 15:05:42 -06:00
1a054fd598
make sites optional 2026-02-04 13:05:32 -06:00
4ef4f4fbf6
handle exceptions gracefully on api endpoints 2026-02-04 12:18:14 -06:00
c93144cc37
snmp v3 support 2026-02-04 12:02:38 -06:00
3583624e0d
fix: prevent nil boolean error in device edit page
Fixed production bug where visiting the edit page for a device with no
SNMP discovery data would crash with "expected boolean on left-side of
'and', got: nil" error.

The mikrotik_device?/1 function was using && operator which returns nil
when device.snmp_device is nil, instead of returning false. The template
conditional on line 455 requires a proper boolean value.

Changed from:
  device.snmp_device && (...)

To:
  not is_nil(device.snmp_device) and (...)

This ensures the function always returns a boolean (true/false) instead
of potentially returning nil.

Added regression test to verify the page renders correctly when a device
has no SNMP discovery data yet.

Fixes: https://app.honeybadger.io/projects/136860/faults/127120431
2026-02-03 15:32:51 -06:00
f6509cac08
more tests 2026-02-03 15:27:05 -06:00
3276a1cd8c
fix user session controller test
Update email input ID assertion to match actual template implementation
2026-02-03 13:42:24 -06:00
50257dbe68
more tests 2026-02-03 13:20:33 -06:00
88d549cd92
more tests 2026-02-03 12:03:54 -06:00
b78a40555c
more tests 2026-02-03 11:16:43 -06:00
8667d6a288
more tests 2026-02-03 11:02:38 -06:00
ec5c98781f
fix org test 2026-02-03 10:45:58 -06:00
086085156f
more tests 2026-02-03 10:43:30 -06:00
13b3810010
more tests 2026-02-03 10:28:39 -06:00
1ce3763e5f
more tests 2026-02-03 10:19:56 -06:00
89a076fb67
feat: migrate equipment features to gettext and fix sudo mode tests
Migrates all equipment-related flash messages to gettext for internationalization:
- DeviceLive.Index: device discovery, reordering, error messages
- DeviceLive.Show: backup messages, permission errors
- DeviceLive.Form: device CRUD operations
- AlertLive.Index: alert acknowledgment messages
- SiteLive.Show: site discovery messages
- SiteLive.Form: site CRUD and SNMP/agent propagation

Fixes sudo mode test suite issues:
- Updates Accounts.sudo_mode?/2 test to check last_sudo_at instead of authenticated_at
- Adds register_and_log_in_user_with_sudo/1 helper to ConnCase
- Fixes UserAuth sudo mode tests to use session-based authentication
- Updates MyData tests to use sudo mode (now required per router config)
- Removes obsolete UserSettingsLive "without sudo mode" test
- Fixes grant_sudo_mode/1 DateTime truncation to seconds

All equipment module tests passing. Ready for Phase 3 (Admin Features).
2026-02-02 12:41:58 -06:00
bd6dad85e1
purge more passkey and gettext updates 2026-02-02 10:20:59 -06:00
6c8e670dae
docs: add gettext internationalization design
- Add comprehensive i18n design document with domain-based organization
  (default, errors, auth, equipment, admin, emails domains)
- Document helper functions, implementation patterns, and migration workflow
- Fix multiple test suite issues:
  * user_settings_live_test.exs: Update all tests to match current UI structure
    - Fix user.name references (use first_name/last_name)
    - Fix TOTP device creation (use create_totp_device/2)
    - Update tab URLs and form IDs
    - Update tab names (Account, API, Notifications)
    - Remove obsolete tabs (Organizations, Activity)
    - Fix password tests (pattern match redirect without flash token)
    - Update sudo mode behavior
  * account_data_controller_test.exs: Fix user profile assertions
  * rate_limit_test.exs: Fix unused variable warning
  * settings_live_test.exs: Fix organization form validation test
2026-02-02 09:33:01 -06:00
f938b263cd
Mikrotik backup and gettext start 2026-02-02 09:11:22 -06:00
bcd2570b10
user settings test update 2026-02-01 15:02:35 -06:00
a66aec675e Merge branch 'gmcintire/user-first-last-name' into 'main'
Replace single name field with first_name and last_name

See merge request towerops/towerops!3
2026-02-01 15:00:24 -06:00
e46ecbfca2 Replace single name field with first_name and last_name 2026-02-01 15:00:23 -06:00
91517aa934 Merge branch 'gmcintire/security-audit' into 'main'
Add rate limiting to auth and API endpoints using Hammer

See merge request towerops/towerops!2
2026-02-01 14:59:54 -06:00
02833d0b44 Add rate limiting to auth and API endpoints using Hammer 2026-02-01 14:59:54 -06:00
f0d6ae42d0
feat: update UserAuth to redirect to sudo verify page 2026-02-01 14:58:07 -06:00