password.confirm sends a user to /user/confirm-password unless they entered their password in the last password_timeout (three hours). The kit guards its security page with it, and Fortify 1,761 's confirmPassword option guards enabling 2FA. In the same shell as Resets and Verification, Grace turns on TOTP and signs in again; PHP computes the codes with pragmarx/google2fa, the library Fortify itself uses:
mv grace.jar jar; A='Accept: application/json'; U=$B/user
curl -s $J -o /dev/null -w "$W" $B/settings/security
T=$(tok /user/confirm-password); H="X-CSRF-TOKEN: $T"
curl -s $J -o /dev/null -w "$W" -H "$H" -d "$G" $U/confirm-password
curl -s $J -o /dev/null -w "$W" -H "$H" -H "$A" -X POST $U/two-factor-authentication
S=$(curl -s $J -H "$A" $U/two-factor-secret-key | jq -r .secretKey); echo "$S"
C=$(php -r 'require "vendor/autoload.php";
echo (new PragmaRX\Google2FA\Google2FA)->getCurrentOtp($argv[1]);' "$S")
curl -s $J -o /dev/null -w "$W" -H "$H" -H "$A" -d code=$C $U/confirmed-two-factor-authentication
RC=$(curl -s $J -H "$A" $U/two-factor-recovery-codes | jq -r '.[0]')
curl -s $J -o /dev/null -H "$H" -X POST $B/logout; T=$(tok /login)
curl -s $J -o /dev/null -w "$W" -d "_token=$T&email=grace@example.com&$G" $B/login
T=$(tok /two-factor-challenge)
curl -s $J -o /dev/null -w "$W" -d "_token=$T&code=$C" $B/two-factor-challenge
curl -s $J -o /dev/null -w "$W" -d "_token=$T&recovery_code=$RC" $B/two-factor-challenge302 http://127.0.0.1:8314/user/confirm-password 302 http://127.0.0.1:8314/settings/security 200 3KDUK2XXYBXW6UW3 200 302 http://127.0.0.1:8314/two-factor-challenge 302 http://127.0.0.1:8314/two-factor-challenge 302 http://127.0.0.1:8314/dashboard
Enabling stored an encrypted secret (shown as a QR code on the settings page) and eight recovery codes; only a valid code set two_factor_confirmed_at. The password alone then led only to the challenge. Replaying the setup code failed, because Fortify caches every accepted code; a recovery code works once and is then replaced.