From 810fb66436453eb3e06b9d8f7154f9c0eca8d1c6 Mon Sep 17 00:00:00 2001
From: Melissa Iori
Date: Wed, 17 Sep 2025 06:35:54 -0400
Subject: [PATCH 1/6] Starting to remove all Authy code
---
network/authy.go | 116 ---------------------
network/authy_test.go | 42 --------
notes.md | 5 +-
pgmodels/user.go | 2 +-
pgmodels/user_view.go | 2 +-
views/users/backup_codes.html | 2 +-
views/users/choose_second_factor.html | 6 --
views/users/init_2fa_setup.html | 6 +-
views/users/my_account.html | 8 +-
views/users/show.html | 8 +-
web/webui/two_factor_controller.go | 123 +----------------------
web/webui/two_factor_controller_test.go | 58 ++---------
web/webui/two_factor_preferences.go | 20 +---
web/webui/two_factor_preferences_test.go | 27 -----
14 files changed, 28 insertions(+), 397 deletions(-)
delete mode 100644 network/authy.go
delete mode 100644 network/authy_test.go
diff --git a/network/authy.go b/network/authy.go
deleted file mode 100644
index 394bfbce..00000000
--- a/network/authy.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package network
-
-import (
- "errors"
- stdlog "log"
- "net/url"
- "os"
- "time"
-
- "github.com/brianvoe/gofakeit/v6"
- "github.com/dcu/go-authy"
- "github.com/rs/zerolog"
-)
-
-// ErrAuthyDisabled means authy isn't enabled here. You can change that
-// in the .env file.
-var ErrAuthyDisabled = errors.New("authy is not enabled in this environment")
-
-var authyLoginMessage = "Log in to the APTrust registry."
-var authyTimeout = (45 * time.Second)
-
-type AuthyClient struct {
- client *authy.Authy
- enabled bool
- log zerolog.Logger
-}
-
-type AuthyClientInterface interface {
- AwaitOneTouch(string, string) (bool, error)
- RegisterUser(string, int, string) (string, error)
-}
-
-func NewAuthyClient(authyEnabled bool, authyAPIKey string, log zerolog.Logger) AuthyClientInterface {
- // Authy library logs to Stderr by default. We want either our logger
- // (which doesn't support the standard go logger interface) or Stdout
- // because Docker gathers Stdout logs.
- authy.Logger = stdlog.New(os.Stdout, "[authy] ", stdlog.LstdFlags)
- return &AuthyClient{
- client: authy.NewAuthyAPI(authyAPIKey),
- enabled: authyEnabled,
- log: log,
- }
-}
-
-// AwaitOneTouch sends a OneTouch login request via Authy and awaits
-// the user's response. Param authyID is the user's AuthyID. Param
-// userEmail is used for logging.
-//
-// This is a blocking request that waits up to 45 seconds for a user
-// to approve the one-touch push notification.
-//
-// If request is approved, this returns true. Otherwise, false.
-func (ac *AuthyClient) AwaitOneTouch(userEmail, authyID string) (bool, error) {
- if !ac.enabled {
- return false, ErrAuthyDisabled
- }
- details := authy.Details{}
- req, err := ac.client.SendApprovalRequest(authyID, authyLoginMessage, details, url.Values{})
- if err != nil {
- ac.log.Error().Msgf("AuthyOneTouch error for %s: %v", userEmail, err)
- return false, err
- }
- ac.log.Info().Msgf("AuthyOneTouch request id for %s: %s", userEmail, req.UUID)
- status, err := ac.client.WaitForApprovalRequest(req.UUID, authyTimeout, url.Values{})
- if status == authy.OneTouchStatusApproved {
- ac.log.Info().Msgf("AuthyOneTouch approved for %s (%s)", userEmail, req.UUID)
- return true, nil
- } else {
- ac.log.Warn().Msgf("AuthyOneTouch %s for %s (%s)", status, userEmail, req.UUID)
- }
- return false, nil
-}
-
-// RegisterUser registers a user with Authy for this app. Note that
-// users need separate registrations for each environment (dev, demo,
-// prod, etc.).
-//
-// On success, this returns the user's new AuthyID. The caller is
-// responsible for attaching that ID to the user object and saving
-// it to the database.
-//
-// Use user.CountryCodeAndPhone() to get country code and phone number,
-// as these need to be separate. Do not pass user.PhoneNumber in format
-// "+" because that won't work.
-func (ac *AuthyClient) RegisterUser(userEmail string, countryCode int, phone string) (string, error) {
- if !ac.enabled {
- return "", ErrAuthyDisabled
- }
- authyUser, err := ac.client.RegisterUser(userEmail, countryCode, phone, url.Values{})
- if err != nil {
- ac.log.Error().Msgf("Can't register user %s (%d %s) with Authy: %v", userEmail, countryCode, phone, err)
- return "", err
- }
- return authyUser.ID, err
-}
-
-// MockAuthyClient is used in testing.
-type MockAuthyClient struct{}
-
-// NewMockAuthyClient returns a mock authy client for testing.
-func NewMockAuthyClient() AuthyClientInterface {
- return &MockAuthyClient{}
-}
-
-// AwaitOneTouch for unit tests. Returns true unless param authyID == "fail".
-func (m *MockAuthyClient) AwaitOneTouch(userEmail, authyID string) (bool, error) {
- if authyID == "fail" {
- return false, nil
- }
- return true, nil
-}
-
-// RegisterUser for unit testing. Always succeeds.
-func (m *MockAuthyClient) RegisterUser(userEmail string, countryCode int, phone string) (string, error) {
- return gofakeit.FarmAnimal(), nil
-}
diff --git a/network/authy_test.go b/network/authy_test.go
deleted file mode 100644
index ccddb853..00000000
--- a/network/authy_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package network_test
-
-import (
- "io/ioutil"
- "testing"
-
- "github.com/APTrust/registry/network"
- "github.com/rs/zerolog"
- "github.com/stretchr/testify/assert"
-)
-
-func TestAuthyClient(t *testing.T) {
- client := network.NewAuthyClient(false, "SecretKey",
- zerolog.New(ioutil.Discard))
- assert.NotNil(t, client)
-
- // Test authy when disabled.
- response, err := client.RegisterUser("homer@example.com", 1, "302-555-1212")
- assert.Empty(t, response)
- assert.Equal(t, network.ErrAuthyDisabled, err)
-
- ok, err := client.AwaitOneTouch("homer@example.com", "no-id")
- assert.False(t, ok)
- assert.Equal(t, network.ErrAuthyDisabled, err)
-
- // Test our mock authy client. This seems superfluous, but
- // we want to make sure it works for tests in web/webui.
- client = network.NewMockAuthyClient()
- assert.NotNil(t, client)
-
- response, err = client.RegisterUser("homer@example.com", 1, "302-555-1212")
- assert.NotEmpty(t, response)
- assert.Nil(t, err)
-
- ok, err = client.AwaitOneTouch("homer@example.com", "no-id")
- assert.True(t, ok)
- assert.Nil(t, err)
-
- ok, err = client.AwaitOneTouch("homer@example.com", "fail")
- assert.False(t, ok)
- assert.Nil(t, err)
-}
diff --git a/notes.md b/notes.md
index 20d34ab4..998d67a4 100644
--- a/notes.md
+++ b/notes.md
@@ -103,12 +103,9 @@ Remember, depdenency hell and mountains of garbage code are only one npm package
* Email/password login
* Two-factor text/sms
-* Two-factor Authy
To ensure users won't have to change their passwords when moving from the Rails app, implement the same password encryption scheme as Devise. The scheme is described [here](https://www.freecodecamp.org/news/how-does-devise-keep-your-passwords-safe-d367f6e816eb/), and the [Go bcrypt library](https://pkg.go.dev/golang.org/x/crypto/bcrypt) should be able to support it.
-For two-factor auth, since we're already using Authy, try the [Go Client for Authy](https://github.com/dcu/go-authy).
-
### Edit
* edit details (phone, etc.)
@@ -345,7 +342,7 @@ The term "items" below refers to Intellectual Objects, Generic Files, Checksums,
# Two Factor Authentication
-Current Pharos users who have enabled two-factor authentication receive one-time passwords through SMS or push notifications through Authy OneTouch. These methods were chosen after long discussion with depositors and we cannot change them without another long discussion. So for now, we're sticking with these two.
+Current Pharos users who have enabled two-factor authentication receive one-time passwords through SMS. This method was chosen after long discussion with depositors and we cannot change this without another long discussion. So for now, we're sticking with these two.
Notes on two-factor setup and workflow have grown large enoug to warrant their own document. See [Two Factor Notes](two_factor_notes.md).
diff --git a/pgmodels/user.go b/pgmodels/user.go
index 0917ced3..f0db7d3d 100644
--- a/pgmodels/user.go
+++ b/pgmodels/user.go
@@ -160,7 +160,7 @@ type User struct {
// push, so they can login with one-touch. Anything else means SMS,
// but call IsTwoFactorUser() to make sure they're actually require
// two-factor auth before trying to text them.
- AuthyStatus string `json:"authy_status" pg:"authy_status"`
+ MFAStatus string `json:"authy_status" pg:"authy_status"`
// EmailVerified will be true once the system has verified that the
// user's email address is correct.
diff --git a/pgmodels/user_view.go b/pgmodels/user_view.go
index 4a1b4e60..ed536605 100644
--- a/pgmodels/user_view.go
+++ b/pgmodels/user_view.go
@@ -37,7 +37,7 @@ type UserView struct {
ConfirmedTwoFactor bool `json:"confirmed_two_factor" pg:"confirmed_two_factor"`
AuthyID string `json:"-" pg:"authy_id"`
LastSignInWithAuthy time.Time `json:"last_sign_in_with_authy" pg:"last_sign_in_with_authy"`
- AuthyStatus string `json:"authy_status" pg:"authy_status"`
+ MFAStatus string `json:"authy_status" pg:"authy_status"`
EmailVerified bool `json:"email_verified" pg:"email_verified"`
InitialPasswordUpdated bool `json:"initial_password_updated" pg:"initial_password_updated"`
ForcePasswordUpdate bool `json:"force_password_update" pg:"force_password_update"`
diff --git a/views/users/backup_codes.html b/views/users/backup_codes.html
index 2c08ff9b..c8f071cb 100644
--- a/views/users/backup_codes.html
+++ b/views/users/backup_codes.html
@@ -15,7 +15,7 @@ Backup Codes
Please copy the backup codes below. You can use these for two-factor login when you don't have access
- to Authy or text messages. These backup codes supersede all previously-generated codes.
+ to text messages. These backup codes supersede all previously-generated codes.
-{{ if not .showAsModal }}
- {{ template "shared/_header.html" .}}
-{{ end }}
-
-
-
-
-{{ if not .showAsModal }}
- {{ template "shared/_footer.html" .}}
-{{ end }}
-
-{{ end }}
diff --git a/views/users/choose_second_factor.html b/views/users/choose_second_factor.html
index 1ebeb3d8..a34dd6bb 100644
--- a/views/users/choose_second_factor.html
+++ b/views/users/choose_second_factor.html
@@ -55,10 +55,6 @@ Multi-Factor Authentication Required
form["csrf_token"] = null
form.method = "get"
form.action = "/users/2fa_backup/"
- } else if (twoFactorMethod == "authy") {
- addCsrf(form, csrfToken)
- form.method = "post"
- form.action = "/users/2fa_push/"
} else if (twoFactorMethod == "sms") {
addCsrf(form, csrfToken)
form.method = "post"
From a7cb1abc29c438cae2ddfb98b52e2136a322f08e Mon Sep 17 00:00:00 2001
From: Melissa Iori
Date: Mon, 6 Apr 2026 02:31:28 -0400
Subject: [PATCH 3/6] Removing Authy parameters and DB fields and all
references except for under legacy
---
.env.dev | 8 ----
.env.docker | 2 -
.env.integration | 9 -----
.env.test | 8 ----
.env.travis | 8 ----
Dockerfile | 3 --
Dockerfile.multi | 3 --
cfn/cfn-registry-cluster.tmpl | 4 --
cfn/cfn-registry-cluster.yml | 4 --
db/fixtures/users.csv | 20 +++++-----
db/migrations/013_remove_authy.sql | 60 ++++++++++++++++++++++++++++++
env-dev | 7 ----
go.mod | 4 --
go.sum | 7 ----
pgmodels/user.go | 2 +-
pgmodels/user_view.go | 2 +-
16 files changed, 72 insertions(+), 79 deletions(-)
create mode 100644 db/migrations/013_remove_authy.sql
diff --git a/.env.dev b/.env.dev
index 3f0bf936..21ff7765 100644
--- a/.env.dev
+++ b/.env.dev
@@ -96,14 +96,6 @@ SNS_ENDPOINT=""
AWS_SNS_USER=""
AWS_SNS_PWD=""
-
-# If you have valid Authy keys, set this to true.
-# You'll need to set the following env variable:
-#
-# AUTHY_API_KEY=
-#
-ENABLE_TWO_FACTOR_AUTHY=false
-
# How long should one-time passwords sent via text/SMS last?
# Use Go duration format. Examples:
# "60s" = 60 seconds
diff --git a/.env.docker b/.env.docker
index 12deb36c..d9af9db9 100644
--- a/.env.docker
+++ b/.env.docker
@@ -13,7 +13,6 @@
# See Parameter Store for a description of each variable.
# --------------------------------------------------------------------------
#
-# AUTHY_API_KEY
# AWS_SES_PWD
# AWS_SES_USER
# AWS_SNS_USER
@@ -33,7 +32,6 @@
# EMAIL_ENABLED
# EMAIL_FROM_ADDRESS
# EMAIL_SERVICE_TYPE
-# ENABLE_TWO_FACTOR_AUTHY
# ENABLE_TWO_FACTOR_SMS
# FLASH_COOKIE_NAME
# HTTPS_COOKIES
diff --git a/.env.integration b/.env.integration
index f6b9a628..a95b0e92 100644
--- a/.env.integration
+++ b/.env.integration
@@ -96,15 +96,6 @@ SNS_ENDPOINT=""
AWS_SNS_USER=""
AWS_SNS_PWD=""
-
-
-# If you have valid Authy keys, set this to true.
-# You'll need to set the following env variable:
-#
-# AUTHY_API_KEY=
-#
-ENABLE_TWO_FACTOR_AUTHY=false
-
# How long should one-time passwords sent via text/SMS last?
# Use Go duration format. Examples:
# "60s" = 60 seconds
diff --git a/.env.test b/.env.test
index 982eea56..cab34085 100644
--- a/.env.test
+++ b/.env.test
@@ -97,14 +97,6 @@ SNS_ENDPOINT=""
AWS_SNS_USER=""
AWS_SNS_PWD=""
-
-# If you have valid Authy keys, set this to true.
-# You'll need to set the following env variable:
-#
-# AUTHY_API_KEY=
-#
-ENABLE_TWO_FACTOR_AUTHY=false
-
# How long should one-time passwords sent via text/SMS last?
# Use Go duration format. Examples:
# "60s" = 60 seconds
diff --git a/.env.travis b/.env.travis
index 0dd7013f..025c4b4d 100644
--- a/.env.travis
+++ b/.env.travis
@@ -96,14 +96,6 @@ SNS_ENDPOINT=""
AWS_SNS_USER=""
AWS_SNS_PWD=""
-
-# If you have valid Authy keys, set this to true.
-# You'll need to set the following env variable:
-#
-# AUTHY_API_KEY=
-#
-ENABLE_TWO_FACTOR_AUTHY=false
-
# How long should one-time passwords sent via text/SMS last?
# Use Go duration format. Examples:
# "60s" = 60 seconds
diff --git a/Dockerfile b/Dockerfile
index 2c903d51..97dd889a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -50,9 +50,6 @@ ENV AWS_REGION=us-east-1
ENV ENABLE_TWO_FACTOR_SMS=true
-ENV ENABLE_TWO_FACTOR_AUTHY=true
-ENV AUTHY_API_KEY=
-
ENV OTP_EXPIRATION="15m"
ENV EMAIL_ENABLED=false
diff --git a/Dockerfile.multi b/Dockerfile.multi
index 0eee29e5..b35a8a82 100644
--- a/Dockerfile.multi
+++ b/Dockerfile.multi
@@ -62,9 +62,6 @@ ENV AWS_REGION=us-east-1
ENV ENABLE_TWO_FACTOR_SMS=true
-ENV ENABLE_TWO_FACTOR_AUTHY=true
-ENV AUTHY_API_KEY=
-
ENV OTP_EXPIRATION="15m"
ENV EMAIL_ENABLED=false
diff --git a/cfn/cfn-registry-cluster.tmpl b/cfn/cfn-registry-cluster.tmpl
index 3b7d0d96..473933d5 100644
--- a/cfn/cfn-registry-cluster.tmpl
+++ b/cfn/cfn-registry-cluster.tmpl
@@ -206,8 +206,6 @@ Resources:
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/APT_ENV'
- Name: LOG_DIR
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/LOG_DIR'
- - Name: AUTHY_API_KEY
- ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/AUTHY_API_KEY'
- Name: AWS_ACCESS_KEY_ID
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/AWS_ACCESS_KEY_ID'
- Name: AWS_REGION
@@ -242,8 +240,6 @@ Resources:
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/EMAIL_ENABLED'
- Name: EMAIL_FROM_ADDRESS
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/EMAIL_FROM_ADDRESS'
- - Name: ENABLE_TWO_FACTOR_AUTHY
- ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/ENABLE_TWO_FACTOR_AUTHY'
- Name: ENABLE_TWO_FACTOR_SMS
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/ENABLE_TWO_FACTOR_SMS'
- Name: FLASH_COOKIE_NAME
diff --git a/cfn/cfn-registry-cluster.yml b/cfn/cfn-registry-cluster.yml
index de5837b5..e3dbd43c 100644
--- a/cfn/cfn-registry-cluster.yml
+++ b/cfn/cfn-registry-cluster.yml
@@ -206,8 +206,6 @@ Resources:
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/APT_ENV'
- Name: LOG_DIR
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/LOG_DIR'
- - Name: AUTHY_API_KEY
- ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/AUTHY_API_KEY'
- Name: AWS_ACCESS_KEY_ID
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/AWS_ACCESS_KEY_ID'
- Name: AWS_REGION
@@ -242,8 +240,6 @@ Resources:
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/EMAIL_ENABLED'
- Name: EMAIL_FROM_ADDRESS
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/EMAIL_FROM_ADDRESS'
- - Name: ENABLE_TWO_FACTOR_AUTHY
- ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/ENABLE_TWO_FACTOR_AUTHY'
- Name: ENABLE_TWO_FACTOR_SMS
ValueFrom: !Sub 'arn:aws:ssm:us-east-1:997427182289:parameter/${Envn}/REGISTRY/ENABLE_TWO_FACTOR_SMS'
- Name: FLASH_COOKIE_NAME
diff --git a/db/fixtures/users.csv b/db/fixtures/users.csv
index 78247fdb..c6ee32b1 100644
--- a/db/fixtures/users.csv
+++ b/db/fixtures/users.csv
@@ -1,10 +1,10 @@
-id,name,email,phone_number,created_at,updated_at,encrypted_password,reset_password_token,reset_password_sent_at,remember_created_at,sign_in_count,current_sign_in_at,last_sign_in_at,current_sign_in_ip,last_sign_in_ip,institution_id,encrypted_api_secret_key,password_changed_at,encrypted_otp_secret,encrypted_otp_secret_iv,encrypted_otp_secret_salt,encrypted_otp_sent_at,consumed_timestep,otp_required_for_login,deactivated_at,enabled_two_factor,confirmed_two_factor,otp_backup_codes,authy_id,last_sign_in_with_authy,authy_status,email_verified,initial_password_updated,force_password_update,account_confirmed,grace_period,awaiting_second_factor,role
-4,Inactive User,inactive@inst1.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,1/15/21 13:49,FALSE,FALSE,"{code1,code2,code3}",,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,none
-5,Inst Two Admin,admin@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin
-7,Inst Two User,user@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user
-2,Inst One Admin,admin@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_admin
-3,Inst One User,user@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$raEJqJ7eRcEwWmeoiJ2vxenR8dqVXCI1SU9zcgkrxeS.6/haWGi4K,,,,1,9/10/21 14:22,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_user
-1,APTrust System,system@aptrust.org,14345551212,1/12/21 17:14,9/10/21 14:24,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,1,9/10/21 14:24,,127.0.0.1,,1,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,admin
-6,Two Factor SMS User,sms_user@example.com,12125551212,9/10/21 14:25,9/10/21 14:25,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,,,,,,,,TRUE,,,,,,,,,,TRUE,TRUE,11/9/21 5:00,FALSE,institutional_user
-8,Test.edu Admin,admin@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin
-9,Test.edu User,user@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user
\ No newline at end of file
+id,name,email,phone_number,created_at,updated_at,encrypted_password,reset_password_token,reset_password_sent_at,remember_created_at,sign_in_count,current_sign_in_at,last_sign_in_at,current_sign_in_ip,last_sign_in_ip,institution_id,encrypted_api_secret_key,password_changed_at,encrypted_otp_secret,encrypted_otp_secret_iv,encrypted_otp_secret_salt,encrypted_otp_sent_at,consumed_timestep,otp_required_for_login,deactivated_at,enabled_two_factor,confirmed_two_factor,otp_backup_codes,mfa_status,email_verified,initial_password_updated,force_password_update,account_confirmed,grace_period,awaiting_second_factor,role
+4,Inactive User,inactive@inst1.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,1/15/21 13:49,FALSE,FALSE,"{code1,code2,code3}",,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,none
+5,Inst Two Admin,admin@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin
+7,Inst Two User,user@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user
+2,Inst One Admin,admin@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_admin
+3,Inst One User,user@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$raEJqJ7eRcEwWmeoiJ2vxenR8dqVXCI1SU9zcgkrxeS.6/haWGi4K,,,,1,9/10/21 14:22,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_user
+1,APTrust System,system@aptrust.org,14345551212,1/12/21 17:14,9/10/21 14:24,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,1,9/10/21 14:24,,127.0.0.1,,1,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,admin
+6,Two Factor SMS User,sms_user@example.com,12125551212,9/10/21 14:25,9/10/21 14:25,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,,,,,,,,TRUE,,,,,,,,TRUE,TRUE,11/9/21 5:00,FALSE,institutional_user
+8,Test.edu Admin,admin@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin
+9,Test.edu User,user@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user
\ No newline at end of file
diff --git a/db/migrations/013_remove_authy.sql b/db/migrations/013_remove_authy.sql
new file mode 100644
index 00000000..08ebd34b
--- /dev/null
+++ b/db/migrations/013_remove_authy.sql
@@ -0,0 +1,60 @@
+-- 013_posix_metadata.sql
+--
+-- This migration removes Authy-related columns and indices from the database.
+
+-- Note that we're starting the migration.
+insert into schema_migrations ("version", started_at) values ('013_remove_authy', now())
+on conflict ("version") do update set started_at = now();
+
+alter table public.users drop column authy_id;
+alter table public.users drop column last_sign_in_with_authy;
+alter table public.users drop column authy_status varchar NULL;
+alter table public.users add column mfa_status varchar NULL;
+
+drop index index_users_on_authy_id on public.users;
+
+CREATE OR REPLACE VIEW public.users_view
+AS SELECT u.id,
+ u.name,
+ u.email,
+ u.phone_number,
+ u.created_at,
+ u.updated_at,
+ u.reset_password_sent_at,
+ u.remember_created_at,
+ u.sign_in_count,
+ u.current_sign_in_at,
+ u.last_sign_in_at,
+ u.current_sign_in_ip,
+ u.last_sign_in_ip,
+ u.institution_id,
+ u.password_changed_at,
+ u.consumed_timestep,
+ u.otp_required_for_login,
+ u.deactivated_at,
+ u.enabled_two_factor,
+ u.confirmed_two_factor,
+ u.mfa_status,
+ u.email_verified,
+ u.initial_password_updated,
+ u.force_password_update,
+ u.account_confirmed,
+ u.grace_period,
+ u.role,
+ i.name AS institution_name,
+ i.identifier AS institution_identifier,
+ i.state AS institution_state,
+ i.type AS institution_type,
+ i.member_institution_id,
+ i2.name AS member_institution_name,
+ i2.identifier AS member_institution_identifier,
+ i2.state AS member_institution_state,
+ i.otp_enabled,
+ i.receiving_bucket,
+ i.restore_bucket
+ FROM users u
+ LEFT JOIN institutions i ON u.institution_id = i.id
+ LEFT JOIN institutions i2 ON i.member_institution_id = i2.id;
+
+-- Now mark the migration as completed.
+update schema_migrations set finished_at = now() where "version" = '013_remove_authy';
diff --git a/env-dev b/env-dev
index 81cf0581..2371928e 100644
--- a/env-dev
+++ b/env-dev
@@ -62,13 +62,6 @@ NSQ_URL='http://localhost:4151'
#
ENABLE_TWO_FACTOR_SMS=true
-# If you have valid Authy keys, set this to true.
-# You'll need to set the following env variable:
-#
-# AUTHY_API_KEY=
-#
-ENABLE_TWO_FACTOR_AUTHY=true
-
# How long should one-time passwords sent via text/SMS last?
# Use Go duration format. Examples:
# "60s" = 60 seconds
diff --git a/go.mod b/go.mod
index eb0fcd7f..2b9244bf 100644
--- a/go.mod
+++ b/go.mod
@@ -8,7 +8,6 @@ require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
github.com/aws/aws-sdk-go v1.49.23
github.com/brianvoe/gofakeit/v6 v6.9.0
- github.com/dcu/go-authy v1.0.1
github.com/gavv/httpexpect/v2 v2.14.0
github.com/gin-contrib/logger v0.0.2
github.com/gin-gonic/gin v1.9.1
@@ -45,8 +44,6 @@ require (
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
- github.com/gojektech/heimdall v5.0.2+incompatible // indirect
- github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 // indirect
github.com/golang/protobuf v1.5.0 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
@@ -70,7 +67,6 @@ require (
github.com/nsqio/go-nsq v1.0.7 // indirect
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
- github.com/pkg/errors v0.8.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/sanity-io/litter v1.5.5 // indirect
github.com/sergi/go-diff v1.0.0 // indirect
diff --git a/go.sum b/go.sum
index 76495f6b..d6afb54e 100644
--- a/go.sum
+++ b/go.sum
@@ -58,8 +58,6 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dcu/go-authy v1.0.1 h1:9LtF0otuGKQOD0AzyAUKzT+etvzGZxpvf4x20/xiW1Y=
-github.com/dcu/go-authy v1.0.1/go.mod h1:SJ8cuAYQ9c9ZGGIGNk5gyH87/Q0uJXzhb1DR8MNnn98=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fasthttp/websocket v1.4.3-rc.6 h1:omHqsl8j+KXpmzRjF8bmzOSYJ8GnS0E3efi1wYT+niY=
@@ -108,10 +106,6 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gojektech/heimdall v5.0.2+incompatible h1:mfGLnHNTKN7b1OMTO4ZvL3oT2P13kqTTV7owK7BZDck=
-github.com/gojektech/heimdall v5.0.2+incompatible/go.mod h1:8hRIZ3+Kz0r3GAFI9QrUuvZht8ypg5Rs8schCXioLOo=
-github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 h1:MO2DsGCZz8phRhLnpFvHEQgTH521sVN/6F2GZTbNO3Q=
-github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45/go.mod h1:tDYRk1s5Pms6XJjj5m2PxAzmQvaDU8GqDf1u6x7yxKw=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@@ -267,7 +261,6 @@ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZ
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
diff --git a/pgmodels/user.go b/pgmodels/user.go
index a3db75b9..f343049f 100644
--- a/pgmodels/user.go
+++ b/pgmodels/user.go
@@ -150,7 +150,7 @@ type User struct {
// MFA. The current only option is SMS,
// but call IsTwoFactorUser() to make sure they're actually require
// two-factor auth before trying to text them.
- MFAStatus string `json:"authy_status" pg:"authy_status"`
+ MFAStatus string `json:"mfa_status" pg:"mfa_status"`
// EmailVerified will be true once the system has verified that the
// user's email address is correct.
diff --git a/pgmodels/user_view.go b/pgmodels/user_view.go
index b7c9e8b1..a90a9375 100644
--- a/pgmodels/user_view.go
+++ b/pgmodels/user_view.go
@@ -35,7 +35,7 @@ type UserView struct {
DeactivatedAt time.Time `json:"deactivated_at" pg:"deactivated_at"`
EnabledTwoFactor bool `json:"enabled_two_factor" pg:"enabled_two_factor"`
ConfirmedTwoFactor bool `json:"confirmed_two_factor" pg:"confirmed_two_factor"`
- MFAStatus string `json:"authy_status" pg:"authy_status"`
+ MFAStatus string `json:"mfa_status" pg:"mfa_status"`
EmailVerified bool `json:"email_verified" pg:"email_verified"`
InitialPasswordUpdated bool `json:"initial_password_updated" pg:"initial_password_updated"`
ForcePasswordUpdate bool `json:"force_password_update" pg:"force_password_update"`
From 0dc47f9ff58278e97bbb49c36658595971128e7a Mon Sep 17 00:00:00 2001
From: Melissa Iori
Date: Mon, 6 Apr 2026 03:02:25 -0400
Subject: [PATCH 4/6] Fix up merge conflicts in go mod
---
go.mod | 6 ------
1 file changed, 6 deletions(-)
diff --git a/go.mod b/go.mod
index df12678a..ab6ad9ff 100644
--- a/go.mod
+++ b/go.mod
@@ -42,12 +42,6 @@ require (
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
-<<<<<<< HEAD
- github.com/golang/protobuf v1.5.0 // indirect
-=======
- github.com/gojektech/heimdall v5.0.2+incompatible // indirect
- github.com/gojektech/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 // indirect
->>>>>>> master
github.com/golang/snappy v0.0.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
From f5b5a6b591a9717b6f9bf16b4852d97805b5426a Mon Sep 17 00:00:00 2001
From: Melissa Iori
Date: Mon, 6 Apr 2026 03:22:09 -0400
Subject: [PATCH 5/6] Fixing issues with Authy migration and Web
---
app/application.go | 1 -
db/fixtures/users.csv | 20 ++++++++++----------
db/migrations/013_remove_authy.sql | 18 ++++++++++++------
3 files changed, 22 insertions(+), 17 deletions(-)
diff --git a/app/application.go b/app/application.go
index 1c670477..dc4d108f 100644
--- a/app/application.go
+++ b/app/application.go
@@ -260,7 +260,6 @@ func initRoutes(router *gin.Engine) {
webRoutes.GET("/users/2fa_backup", webui.UserTwoFactorBackup)
webRoutes.GET("/users/2fa_choose", webui.UserTwoFactorChoose)
webRoutes.POST("/users/2fa_sms", webui.UserTwoFactorGenerateSMS)
- webRoutes.POST("/users/2fa_push", webui.UserTwoFactorPush)
webRoutes.POST("/users/2fa_verify", webui.UserTwoFactorVerify)
// User forgot password
diff --git a/db/fixtures/users.csv b/db/fixtures/users.csv
index c6ee32b1..7541a3b6 100644
--- a/db/fixtures/users.csv
+++ b/db/fixtures/users.csv
@@ -1,10 +1,10 @@
-id,name,email,phone_number,created_at,updated_at,encrypted_password,reset_password_token,reset_password_sent_at,remember_created_at,sign_in_count,current_sign_in_at,last_sign_in_at,current_sign_in_ip,last_sign_in_ip,institution_id,encrypted_api_secret_key,password_changed_at,encrypted_otp_secret,encrypted_otp_secret_iv,encrypted_otp_secret_salt,encrypted_otp_sent_at,consumed_timestep,otp_required_for_login,deactivated_at,enabled_two_factor,confirmed_two_factor,otp_backup_codes,mfa_status,email_verified,initial_password_updated,force_password_update,account_confirmed,grace_period,awaiting_second_factor,role
-4,Inactive User,inactive@inst1.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,1/15/21 13:49,FALSE,FALSE,"{code1,code2,code3}",,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,none
-5,Inst Two Admin,admin@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin
-7,Inst Two User,user@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user
-2,Inst One Admin,admin@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_admin
-3,Inst One User,user@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$raEJqJ7eRcEwWmeoiJ2vxenR8dqVXCI1SU9zcgkrxeS.6/haWGi4K,,,,1,9/10/21 14:22,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_user
-1,APTrust System,system@aptrust.org,14345551212,1/12/21 17:14,9/10/21 14:24,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,1,9/10/21 14:24,,127.0.0.1,,1,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,admin
-6,Two Factor SMS User,sms_user@example.com,12125551212,9/10/21 14:25,9/10/21 14:25,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,,,,,,,,TRUE,,,,,,,,TRUE,TRUE,11/9/21 5:00,FALSE,institutional_user
-8,Test.edu Admin,admin@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin
-9,Test.edu User,user@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user
\ No newline at end of file
+id,name,email,phone_number,created_at,updated_at,encrypted_password,reset_password_token,reset_password_sent_at,remember_created_at,sign_in_count,current_sign_in_at,last_sign_in_at,current_sign_in_ip,last_sign_in_ip,institution_id,encrypted_api_secret_key,password_changed_at,encrypted_otp_secret,encrypted_otp_secret_iv,encrypted_otp_secret_salt,encrypted_otp_sent_at,consumed_timestep,otp_required_for_login,deactivated_at,enabled_two_factor,confirmed_two_factor,otp_backup_codes,email_verified,initial_password_updated,force_password_update,account_confirmed,grace_period,awaiting_second_factor,role,mfa_status
+4,Inactive User,inactive@inst1.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,1/15/21 13:49,FALSE,FALSE,"{code1,code2,code3}",TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,none,
+5,Inst Two Admin,admin@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin,
+7,Inst Two User,user@inst2.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,3,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user,
+2,Inst One Admin,admin@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_admin,
+3,Inst One User,user@inst1.edu,14345551212,1/12/21 17:14,9/10/21 14:22,$2a$10$raEJqJ7eRcEwWmeoiJ2vxenR8dqVXCI1SU9zcgkrxeS.6/haWGi4K,,,,1,9/10/21 14:22,,,,2,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,institutional_user,
+1,APTrust System,system@aptrust.org,14345551212,1/12/21 17:14,9/10/21 14:24,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,1,9/10/21 14:24,,127.0.0.1,,1,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,,,,TRUE,TRUE,,TRUE,12/31/99 23:59,FALSE,admin,
+6,Two Factor SMS User,sms_user@example.com,12125551212,9/10/21 14:25,9/10/21 14:25,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,2,,,,,,,,TRUE,,,,,,,TRUE,TRUE,11/9/21 5:00,FALSE,institutional_user,
+8,Test.edu Admin,admin@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_admin,
+9,Test.edu User,user@test.edu,14345551212,1/12/21 17:14,1/12/21 17:14,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,0,,,,,4,$2a$10$7aoot2KFFqikpTYVEbErYOxZijCHDPvqT4OMoFwdmsYBE9SK2PibC,,,,,,,,,FALSE,FALSE,,TRUE,TRUE,FALSE,TRUE,12/31/99 23:59,FALSE,institutional_user,
\ No newline at end of file
diff --git a/db/migrations/013_remove_authy.sql b/db/migrations/013_remove_authy.sql
index 08ebd34b..d0bf445f 100644
--- a/db/migrations/013_remove_authy.sql
+++ b/db/migrations/013_remove_authy.sql
@@ -6,14 +6,12 @@
insert into schema_migrations ("version", started_at) values ('013_remove_authy', now())
on conflict ("version") do update set started_at = now();
-alter table public.users drop column authy_id;
-alter table public.users drop column last_sign_in_with_authy;
-alter table public.users drop column authy_status varchar NULL;
+-- Add a generic MFA status column to replace the Authy one.
alter table public.users add column mfa_status varchar NULL;
-drop index index_users_on_authy_id on public.users;
-
-CREATE OR REPLACE VIEW public.users_view
+-- Drop and recreate the user view
+drop view public.users_view;
+CREATE VIEW public.users_view
AS SELECT u.id,
u.name,
u.email,
@@ -56,5 +54,13 @@ AS SELECT u.id,
LEFT JOIN institutions i ON u.institution_id = i.id
LEFT JOIN institutions i2 ON i.member_institution_id = i2.id;
+-- Drop Authy columns.
+alter table public.users drop column if exists authy_id;
+alter table public.users drop column if exists last_sign_in_with_authy;
+alter table public.users drop column if exists authy_status;
+
+-- Drop Authy index.
+drop index if exists index_users_on_authy_id;
+
-- Now mark the migration as completed.
update schema_migrations set finished_at = now() where "version" = '013_remove_authy';
From a7b9645b820ce664a8cf8f6d9c0544fd83ffc12a Mon Sep 17 00:00:00 2001
From: Melissa Iori
Date: Mon, 6 Apr 2026 03:28:08 -0400
Subject: [PATCH 6/6] Fix broken test
---
web/webui/two_factor_controller_test.go | 2 --
1 file changed, 2 deletions(-)
diff --git a/web/webui/two_factor_controller_test.go b/web/webui/two_factor_controller_test.go
index 7c900781..ec4ea580 100644
--- a/web/webui/two_factor_controller_test.go
+++ b/web/webui/two_factor_controller_test.go
@@ -173,8 +173,6 @@ func TestUserTwoComplete2FASetup(t *testing.T) {
// These have been manually tested. Automated tests cover only
// a few cases.
- aptContext := common.Context()
-
// Submit with no change
expect := testutil.Inst1UserClient.POST("/users/2fa_setup").
WithHeader("Referer", testutil.BaseURL).