From 09a7d557d21637d3514f17555efa41d3c3455cc4 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:43:25 +0530 Subject: [PATCH] Add API to get account two recovery status --- server/cmd/museum/main.go | 7 +++++ server/pkg/api/user.go | 31 +++++++++++++++++++ server/pkg/controller/user/passkey.go | 13 ++++++++ server/pkg/controller/user/user.go | 4 +++ server/pkg/repo/accountrecovery/repository.go | 29 +++++++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 server/pkg/controller/user/passkey.go create mode 100644 server/pkg/repo/accountrecovery/repository.go diff --git a/server/cmd/museum/main.go b/server/cmd/museum/main.go index fb5b36fdb..6f660b217 100644 --- a/server/cmd/museum/main.go +++ b/server/cmd/museum/main.go @@ -5,6 +5,7 @@ import ( "database/sql" b64 "encoding/base64" "fmt" + "github.com/ente-io/museum/pkg/repo/accountrecovery" "net/http" "os" "os/signal" @@ -137,6 +138,7 @@ func main() { twoFactorRepo := &repo.TwoFactorRepository{DB: db, SecretEncryptionKey: secretEncryptionKeyBytes} userAuthRepo := &repo.UserAuthRepository{DB: db} + accountRecoveryRepo := &accountrecovery.Repository{Db: db} billingRepo := &repo.BillingRepository{DB: db} userEntityRepo := &userEntityRepo.Repository{DB: db} locationTagRepository := &locationtagRepo.Repository{DB: db} @@ -304,6 +306,7 @@ func main() { usageRepo, userAuthRepo, twoFactorRepo, + accountRecoveryRepo, passkeysRepo, storagBonusRepo, fileRepo, @@ -429,6 +432,10 @@ func main() { publicAPI.POST("/users/two-factor/remove", userHandler.RemoveTwoFactor) publicAPI.POST("/users/two-factor/passkeys/begin", userHandler.BeginPasskeyAuthenticationCeremony) publicAPI.POST("/users/two-factor/passkeys/finish", userHandler.FinishPasskeyAuthenticationCeremony) + privateAPI.GET("/users/two-factor/account-recovery-status", userHandler.GetAccountRecoveryStatus) + privateAPI.POST("/users/two-factor/passkeys/set-reset-challenge", userHandler.ConfigurePassKeyRecovery) + publicAPI.GET("/users/two-factor/passkeys/reset-challenge", userHandler.GetPasskeyResetChallenge) + publicAPI.POST("/users/two-factor/passkeys/reset", userHandler.ResetPasskey) privateAPI.GET("/users/two-factor/status", userHandler.GetTwoFactorStatus) privateAPI.POST("/users/two-factor/setup", userHandler.SetupTwoFactor) privateAPI.POST("/users/two-factor/enable", userHandler.EnableTwoFactor) diff --git a/server/pkg/api/user.go b/server/pkg/api/user.go index 554f6654b..5461c69af 100644 --- a/server/pkg/api/user.go +++ b/server/pkg/api/user.go @@ -244,6 +244,27 @@ func (h *UserHandler) GetTwoFactorStatus(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": status}) } +func (h *UserHandler) GetAccountRecoveryStatus(c *gin.Context) { + res, err := h.UserController.GetAccountRecoveryStatus(c) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, res) +} + +func (h *UserHandler) ConfigurePassKeyRecovery(c *gin.Context) { + c.JSON(http.StatusNotImplemented, gin.H{"message": "Not implemented"}) +} + +func (h *UserHandler) GetPasskeyResetChallenge(c *gin.Context) { + c.JSON(http.StatusNotImplemented, gin.H{"message": "Not implemented"}) +} + +func (h *UserHandler) ResetPasskey(c *gin.Context) { + c.JSON(http.StatusNotImplemented, gin.H{"message": "Not implemented"}) +} + // SetupTwoFactor generates a two factor secret and sends it to user to setup his authenticator app with func (h *UserHandler) SetupTwoFactor(c *gin.Context) { userID := auth.GetUserID(c.Request.Header) @@ -352,6 +373,16 @@ func (h *UserHandler) FinishPasskeyAuthenticationCeremony(c *gin.Context) { c.JSON(http.StatusOK, response) } +func (h *UserHandler) IsPasskeyRecoveryEnabled(c *gin.Context) { + userID := auth.GetUserID(c.Request.Header) + response, err := h.UserController.GetKeyAttributeAndToken(c, userID) + if err != nil { + handler.Error(c, stacktrace.Propagate(err, "")) + return + } + c.JSON(http.StatusOK, response) +} + // DisableTwoFactor disables the two factor authentication for a user func (h *UserHandler) DisableTwoFactor(c *gin.Context) { userID := auth.GetUserID(c.Request.Header) diff --git a/server/pkg/controller/user/passkey.go b/server/pkg/controller/user/passkey.go new file mode 100644 index 000000000..075fa056d --- /dev/null +++ b/server/pkg/controller/user/passkey.go @@ -0,0 +1,13 @@ +package user + +import ( + "github.com/ente-io/museum/ente" + "github.com/ente-io/museum/pkg/utils/auth" + "github.com/gin-gonic/gin" +) + +// GetAccountRecoveryStatus returns a user's passkey reset status +func (c *UserController) GetAccountRecoveryStatus(ctx *gin.Context) (*ente.AccountRecoveryStatus, error) { + userID := auth.GetUserID(ctx.Request.Header) + return c.AccountRecoveryRepo.GetAccountRecoveryStatus(userID) +} diff --git a/server/pkg/controller/user/user.go b/server/pkg/controller/user/user.go index 4be02b24f..0e2b6fe7e 100644 --- a/server/pkg/controller/user/user.go +++ b/server/pkg/controller/user/user.go @@ -3,6 +3,7 @@ package user import ( "errors" "fmt" + "github.com/ente-io/museum/pkg/repo/accountrecovery" "strings" cache2 "github.com/ente-io/museum/ente/cache" @@ -30,6 +31,7 @@ import ( // UserController exposes request handlers for all user related requests type UserController struct { UserRepo *repo.UserRepository + AccountRecoveryRepo *accountrecovery.Repository UsageRepo *repo.UsageRepository UserAuthRepo *repo.UserAuthRepository TwoFactorRepo *repo.TwoFactorRepository @@ -99,6 +101,7 @@ func NewUserController( usageRepo *repo.UsageRepository, userAuthRepo *repo.UserAuthRepository, twoFactorRepo *repo.TwoFactorRepository, + accountRecoveryRepo *accountrecovery.Repository, passkeyRepo *passkey.Repository, storageBonusRepo *storageBonusRepo.Repository, fileRepo *repo.FileRepository, @@ -121,6 +124,7 @@ func NewUserController( return &UserController{ UserRepo: userRepo, UsageRepo: usageRepo, + AccountRecoveryRepo: accountRecoveryRepo, UserAuthRepo: userAuthRepo, StorageBonusRepo: storageBonusRepo, TwoFactorRepo: twoFactorRepo, diff --git a/server/pkg/repo/accountrecovery/repository.go b/server/pkg/repo/accountrecovery/repository.go new file mode 100644 index 000000000..b04909f28 --- /dev/null +++ b/server/pkg/repo/accountrecovery/repository.go @@ -0,0 +1,29 @@ +package accountrecovery + +import ( + "database/sql" + "github.com/ente-io/museum/ente" +) + +type Repository struct { + Db *sql.DB +} + +// GetAccountRecoveryStatus returns `ente.AccountRecoveryStatus` for a user +func (r *Repository) GetAccountRecoveryStatus(userID int64) (*ente.AccountRecoveryStatus, error) { + var isAdminResetEnabled bool + var resetKey sql.NullString + row := r.Db.QueryRow("SELECT enable_admin_mfa_reset, pass_key_reset_key FROM account_recovery WHERE user_id = $1", userID) + err := row.Scan(&isAdminResetEnabled, &resetKey) + if err != nil { + if err == sql.ErrNoRows { + // by default, admin + return &ente.AccountRecoveryStatus{ + AllowAdminReset: true, + IsPassKeyResetEnabled: false, + }, nil + } + return nil, err + } + return &ente.AccountRecoveryStatus{AllowAdminReset: isAdminResetEnabled, IsPassKeyResetEnabled: resetKey.Valid}, nil +}