-
Notifications
You must be signed in to change notification settings - Fork 6
security: auth bypasses, constant-time comparisons, MFA password re-auth, OAuth revocation #1519
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 9 commits
eaaa962
70cafa8
4bdb6d2
21d2f8f
4050623
610533b
2c3a5ea
17b36fc
ce6f4f9
e9404a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -138,6 +138,9 @@ en: | |
| enable_mfa: Enable 2FA | ||
| disable_mfa: Disable 2FA | ||
| disable_mfa_confirm: Are you sure you want to disable two-factor authentication? | ||
| disable_mfa_password_label: Confirm with your password | ||
| disable_mfa_password_placeholder: Enter your password | ||
|
Comment on lines
+141
to
+142
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find sibling locale files for the settings view and check which are missing the new keys.
fd -t f . config/locales/views/settings
for f in $(fd -t f . config/locales/views/settings); do
echo "=== $f ==="
rg -n 'disable_mfa_password_label|disable_mfa_password_placeholder' "$f" || echo " MISSING"
doneRepository: we-promise/sure Length of output: 6401 🏁 Script executed: #!/bin/bash
# Check the YAML structure around lines 141-142
echo "=== Structure of en.yml around lines 141-142 ==="
sed -n '130,150p' config/locales/views/settings/en.yml
echo -e "\n=== Looking for disable_mfa references in views ==="
fd -e erb -e html.erb . app/views | xargs rg -l 'disable.mfa|disable_mfa' 2>/dev/null || echo "No matches found"Repository: we-promise/sure Length of output: 1195 🏁 Script executed: #!/bin/bash
# Check if other securities keys are translated in non-English locales
echo "=== Checking fr.yml for other securities keys ==="
rg -A 5 'securities:' config/locales/views/settings/fr.yml | head -20
echo -e "\n=== Checking es.yml for other securities keys ==="
rg -A 5 'securities:' config/locales/views/settings/es.yml | head -20
echo -e "\n=== Git log to understand PR type ==="
git log --oneline --all -20 | grep -i 'mfa\|security\|disable' || echo "No recent commits with those keywords"Repository: we-promise/sure Length of output: 972 Add parallel keys to all supported locale files for the securities section. The two new keys 🤖 Prompt for AI Agents |
||
| mfa_sso_only: Two-factor authentication is managed through your identity provider. | ||
| sso_title: Connected Accounts | ||
| sso_subtitle: Manage your single sign-on account connections | ||
| sso_disconnect: Disconnect | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -818,4 +818,74 @@ class Api::V1::AuthControllerTest < ActionDispatch::IntegrationTest | |
| assert_equal "AI is not available for your account", response_data["error"] | ||
| assert_not user.reload.ai_enabled | ||
| end | ||
|
|
||
| # Security tests — ported from origin/security/pentest-2026-03-02 | ||
|
|
||
| test "signup is blocked when registration is closed on self-hosted" do | ||
| Rails.application.config.app_mode.stubs(:self_hosted?).returns(true) | ||
| Setting.stubs(:onboarding_state).returns("closed") | ||
|
|
||
| post "/api/v1/auth/signup", params: { | ||
| user: { email: "[email protected]", password: "SecurePass123!", first_name: "New", last_name: "User" }, | ||
| device: @device_info | ||
| } | ||
|
|
||
| assert_response :forbidden | ||
| assert_equal "Registration is currently closed", JSON.parse(response.body)["error"] | ||
| end | ||
|
|
||
| test "login is blocked when local login disabled via AuthConfig" do | ||
| AuthConfig.stubs(:local_login_enabled?).returns(false) | ||
|
|
||
| post "/api/v1/auth/login", params: { | ||
| email: users(:family_admin).email, | ||
| password: user_password_test, | ||
| device: @device_info | ||
| } | ||
|
|
||
| assert_response :forbidden | ||
| assert_equal "Local login is disabled. Please use SSO.", JSON.parse(response.body)["error"] | ||
| end | ||
|
|
||
| test "refresh token is rejected for deactivated user and new token is revoked" do | ||
| user = users(:family_member) | ||
| device = user.mobile_devices.create!(@device_info) | ||
|
|
||
| initial_token = Doorkeeper::AccessToken.create!( | ||
| application: @shared_app, | ||
| resource_owner_id: user.id, | ||
| mobile_device_id: device.id, | ||
| expires_in: 30.days.to_i, | ||
| scopes: "read_write", | ||
| use_refresh_token: true | ||
| ) | ||
|
|
||
| user.update!(active: false) | ||
|
|
||
| post "/api/v1/auth/refresh", params: { | ||
| refresh_token: initial_token.refresh_token, | ||
| device: @device_info | ||
| } | ||
|
|
||
| assert_response :unauthorized | ||
| assert_equal "Account has been deactivated", JSON.parse(response.body)["error"] | ||
|
|
||
| # All tokens for this user must be revoked (including any newly issued one) | ||
| assert Doorkeeper::AccessToken.where(resource_owner_id: user.id).all?(&:revoked?), | ||
| "Expected all tokens to be revoked for deactivated user" | ||
| end | ||
|
|
||
| test "login is rejected for deactivated user" do | ||
| user = users(:family_member) | ||
| user.update!(active: false) | ||
|
|
||
| post "/api/v1/auth/login", params: { | ||
| email: user.email, | ||
| password: user_password_test, | ||
| device: @device_info | ||
| } | ||
|
|
||
| assert_response :unauthorized | ||
| assert_equal "Account has been deactivated", JSON.parse(response.body)["error"] | ||
| end | ||
| end | ||
Uh oh!
There was an error while loading. Please reload this page.