You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* fix: resolve OIDC provider conflict and add auto-link
OmniAuth returns auth.provider as symbol (:oidc) but DB
stores string ("oidc"). Comparison always triggered
ProviderConflictError. rescue_from ordering hid the
actionable message behind generic "unexpected error."
- Provider+uid-first lookup in User.from_omniauth
- .to_s coercion for type-safe comparison
- Fix rescue_from ordering (StandardError first)
- Add VULCAN_AUTO_LINK_USER global setting
- email_verified check for auto-link security
- just_auto_linked? flag (no duplicate lookup)
- Genericize oauth_error flash message
- Replace gitlab_omniauth-ldap with omniauth-ldap
- Remove nkf gem, lazy-load LDAP
- 75 backend auth specs
Authored by: Aaron Lippold<lippold@gmail.com>
* feat: session auth tracking, profile UX, unlink
- session[:auth_method] tracks HOW user signed in
- Profile shows "Signed in via X" + "Linked to Y"
- POST /users/unlink_identity with password check
- Unlink button with confirmation modal
- 15 backend + 29 frontend tests
Authored by: Aaron Lippold<lippold@gmail.com>
* fix: pre-existing bugs + infrastructure hardening
Bug fixes found during auth code review:
- vulcan_audit.rb: bitwise & vs && crash on nil rule
- users_controller: Slack fires on every update
(now gated on saved_change_to_admin?)
- History.vue: suppress raw field changes when
audit has a comment (link/unlink UX)
- registrations: polymorphic audit + user_type
Infrastructure:
- Ruby 3.4.8 -> 3.4.9
- parallel_sync.rake: recursion guard
- docker-compose.dev.yml: trust auth for VPN
Authored by: Aaron Lippold<lippold@gmail.com>
* docs: add what's-left tracking and beads export
- WHATS-LEFT.md: pending bug fixes, future features
- beads-export.jsonl: full task board for handoff
Authored by: Aaron Lippold<lippold@gmail.com>
* chore: fix Gemfile extra blank line (rubocop)
Authored by: Aaron Lippold<lippold@gmail.com>
* test: add TDD tests for Slack notification gate and polymorphic audit filter
71q.1: Verify Slack notification only fires on admin flag changes,
not on name/email updates. Tests promotion, demotion, and no-op cases.
71q.2: Verify registrations#edit filters audits by user_type: 'User',
excluding rogue audits with matching user_id but different user_type.
Signed-off-by: Will Dower <will@dower.dev>
* fix: restore prev_unconfirmed_email for reconfirmation flash (71q.3)
The update action read resource.unconfirmed_email but discarded the
value (no assignment). After email change, users got generic "Profile
updated" instead of "confirmation link sent to new address".
Fix: capture prev_unconfirmed_email, compare after update, show
appropriate flash message per Devise stock behavior.
TDD: red (email-change test failed with generic flash) → green.
Signed-off-by: Will Dower <will@dower.dev>
* fix: use update_columns for reset token to bypass validations (71q.4)
generate_reset_url used update! which runs full validations. If a
user has pre-existing validation failures (e.g., name exceeds a
tightened limit), the admin's reset link generation fails with
RecordInvalid — blocking an unrelated recovery flow.
Fix: use update_columns (matches Devise's save(validate: false)
pattern). Token writes carry no business logic.
TDD: red (user with 500-char name → validation error) → green.
Signed-off-by: Will Dower <will@dower.dev>
* fix: stop leaking exception.message in users_controller rescues (71q.5)
Both send_password_reset and set_password rescue blocks echoed
e.message directly to the client, leaking SMTP hostnames, DB
connection strings, or other internal details.
Fix: log full exception with backtrace server-side via
Rails.logger.error, return generic message to client.
TDD:
- send_password_reset: red (response contained 'smtp.internal.corp')
→ green (generic message returned)
- set_password: source inspection test verifies rescue block does
not interpolate e.message (Rails test mode re-raises before
controller rescue runs, preventing request-level testing)
Signed-off-by: Will Dower <will@dower.dev>
* fix: remove dead authProvider computed and add visibility guard (71q.6, 71q.7)
71q.6: Add source-inspection test ensuring no bare 'public' keyword
exists between private and protected sections in registrations
controller (issue was already fixed, test prevents regression).
71q.7: Remove dead authProvider computed property from UserProfile.vue.
Nothing in the template or script references it — the refactored
linkedProvider + currentSessionMethod properties cover all use cases.
TDD: red (authProvider still in computed options) → green (removed).
Signed-off-by: Will Dower <will@dower.dev>
* fix: P3/P4 hardening — backtraces, email_verified, null guard, constants, docs
71q.8: Log OmniAuth exception backtraces in all environments (was
dev-only). Use error level with first 10 frames.
71q.9: Cast email_verified OIDC claim via ActiveModel::Type::Boolean
to catch providers sending "false" (string) instead of false.
71q.10: Use falsy check in UsersTable typeColumn to handle both
null and undefined provider gracefully.
71q.11: Normalize PROJECT_MEMBER_ADMINS to array for consistency
with VIEWERS/AUTHORS/REVIEWERS. Add ROLE_ADMIN scalar constant
for attribute assignment. Update call sites.
71q.12: Document valid_password? hidden bcrypt→PBKDF2 rehash
side-effect at the unlink call site.
Signed-off-by: Will Dower <will@dower.dev>
* fix: use fake ID in vulcan_audit destroy action test
Test referenced undefined `rule` variable. Use a fake ID like the
adjacent nil-rule test — the destroy action guard skips before any
DB lookup so the ID value doesn't matter.
Signed-off-by: Will Dower <will@dower.dev>
* fix: address Copilot review — v-for key, remove dev artifacts
- Fix History.vue v-for key: changes.id is undefined, use
changes.field with index fallback for stable Vue diffing
- Remove WHATS-LEFT.md and beads-export.jsonl (branch-specific
tracking docs, not for the repo)
Signed-off-by: Will Dower <will@dower.dev>
* fix: properly exercise RecordNotUnique retry path in race condition test
Test claimed to verify retry-on-RecordNotUnique but never stubbed
save! to raise. Now stubs save! to raise once, pre-creates the user
with matching provider+uid so retry lookup succeeds, and asserts
save! was called exactly once before the retry found the existing user.
Signed-off-by: Will Dower <will@dower.dev>
---------
Signed-off-by: Will Dower <will@dower.dev>
Co-authored-by: Aaron Lippold <lippold@gmail.com>
|`VULCAN_ENABLE_REMEMBER_ME`| Show "Remember Me" checkbox on login forms |`true`|`false` for DoD |
60
60
|`VULCAN_REMEMBER_ME_DURATION`| How long Remember Me keeps session alive. Same format as session timeout. |`8h`|`1d`, `28800`|
61
61
62
+
### Account Linking
63
+
| Variable | Description | Default | Example |
64
+
|----------|-------------|---------|---------|
65
+
|`VULCAN_AUTO_LINK_USER`| Automatically link external identities (OIDC, LDAP, GitHub) to existing local accounts with the same email. Only enable when all configured identity providers verify email ownership (e.g., enterprise Okta, corporate LDAP). When `false`, users see a clear error directing them to sign in with their existing method or contact an administrator. |`false`|`true` or `false`|
0 commit comments