From 980ab6c49c8f58ccc8745d0cfd5846ab00e8d690 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:38:24 +0530 Subject: [PATCH] Refactor: extend totp recovery API to recover passkey --- server/cmd/museum/main.go | 5 +-- server/ente/passkey.go | 6 --- server/ente/user.go | 5 ++- server/pkg/api/user.go | 45 +++++++------------ server/pkg/controller/user/passkey.go | 6 +-- server/pkg/controller/user/twofactor.go | 24 +++++----- .../repo/two_factor_recovery/repository.go | 7 +-- 7 files changed, 39 insertions(+), 59 deletions(-) diff --git a/server/cmd/museum/main.go b/server/cmd/museum/main.go index 17b4fb998f..b240d289b6 100644 --- a/server/cmd/museum/main.go +++ b/server/cmd/museum/main.go @@ -5,7 +5,6 @@ import ( "database/sql" b64 "encoding/base64" "fmt" - "github.com/ente-io/museum/pkg/repo/two_factor_recovery" "net/http" "os" "os/signal" @@ -15,6 +14,8 @@ import ( "syscall" "time" + "github.com/ente-io/museum/pkg/repo/two_factor_recovery" + "github.com/ente-io/museum/pkg/controller/cast" "github.com/ente-io/museum/pkg/controller/commonbilling" @@ -434,8 +435,6 @@ func main() { publicAPI.POST("/users/two-factor/passkeys/finish", userHandler.FinishPasskeyAuthenticationCeremony) privateAPI.GET("/users/two-factor/recovery-status", userHandler.GetTwoFactorRecoveryStatus) privateAPI.POST("/users/two-factor/passkeys/set-skip-challenge", userHandler.ConfigurePassKeySkipChallenge) - publicAPI.GET("/users/two-factor/passkeys/skip-challenge", userHandler.GetPasskeySkipChallenge) - publicAPI.POST("/users/two-factor/passkeys/skip", userHandler.SkipPassKey) 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/ente/passkey.go b/server/ente/passkey.go index b780e8c064..096d0d631c 100644 --- a/server/ente/passkey.go +++ b/server/ente/passkey.go @@ -29,12 +29,6 @@ type TwoFactorRecoveryStatus struct { IsPassKeySkipEnabled bool `json:"isPassKeyResetEnabled" binding:"required"` } -type PasseKeySkipChallengeResponse struct { - // The PassKeyEncSecret has SkipSecret encrypted with the user's recoveryKey - UserSecretCipher string `json:"userSecretCipher" binding:"required"` - UserSecretNonce string `json:"userSecretNonce" binding:"required"` -} - type SkipPassKeyRequest struct { SessionID string `json:"sessionID" binding:"required"` SkipSecret string `json:"resetSecret" binding:"required"` diff --git a/server/ente/user.go b/server/ente/user.go index 5d80dc9836..387d2627b3 100644 --- a/server/ente/user.go +++ b/server/ente/user.go @@ -192,8 +192,9 @@ type TwoFactorRecoveryResponse struct { // TwoFactorRemovalRequest represents the the body of two factor removal request consist of decrypted two factor secret and sessionID type TwoFactorRemovalRequest struct { - Secret string `json:"secret"` - SessionID string `json:"sessionID"` + Secret string `json:"secret"` + SessionID string `json:"sessionID"` + TwoFactorType string `json:"twoFactorType"` } type ProfileData struct { diff --git a/server/pkg/api/user.go b/server/pkg/api/user.go index 8b7c7d11e5..6795acf29a 100644 --- a/server/pkg/api/user.go +++ b/server/pkg/api/user.go @@ -269,34 +269,6 @@ func (h *UserHandler) ConfigurePassKeySkipChallenge(c *gin.Context) { c.JSON(http.StatusOK, gin.H{}) } -func (h *UserHandler) GetPasskeySkipChallenge(c *gin.Context) { - passKeySessionID := c.Query("passKeySessionID") - if passKeySessionID == "" { - c.JSON(http.StatusBadRequest, gin.H{"message": "passKeySessionID is required"}) - return - } - resp, err := h.UserController.GetPasskeySkipChallenge(c, passKeySessionID) - if err != nil { - handler.Error(c, stacktrace.Propagate(err, "")) - return - } - c.JSON(http.StatusOK, resp) -} - -func (h *UserHandler) SkipPassKey(c *gin.Context) { - var req ente.SkipPassKeyRequest - if err := c.ShouldBindJSON(&req); err != nil { - handler.Error(c, stacktrace.Propagate(err, "")) - return - } - resp, err := h.UserController.SkipPassKey(c, &req) - if err != nil { - handler.Error(c, stacktrace.Propagate(err, "")) - return - } - c.JSON(http.StatusOK, resp) -} - // 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) @@ -430,7 +402,14 @@ func (h *UserHandler) DisableTwoFactor(c *gin.Context) { // recoveryKeyEncryptedTwoFactorSecret for the user to decrypt it and make twoFactor removal api call func (h *UserHandler) RecoverTwoFactor(c *gin.Context) { sessionID := c.Query("sessionID") - response, err := h.UserController.RecoverTwoFactor(sessionID) + twoFactorType := c.Query("type") + var response *ente.TwoFactorRecoveryResponse + var err error + if twoFactorType == "passkey" { + response, err = h.UserController.GetPasskeyRecoveryResponse(c, sessionID) + } else { + response, err = h.UserController.RecoverTwoFactor(sessionID) + } if err != nil { handler.Error(c, stacktrace.Propagate(err, "")) return @@ -446,7 +425,13 @@ func (h *UserHandler) RemoveTwoFactor(c *gin.Context) { handler.Error(c, stacktrace.Propagate(err, "")) return } - response, err := h.UserController.RemoveTwoFactor(c, request.SessionID, request.Secret) + var response *ente.TwoFactorAuthorizationResponse + var err error + if request.TwoFactorType == "passkey" { + response, err = h.UserController.SkipPassKey(c, &request) + } else { + response, err = h.UserController.RemoveTwoFactor(c, request.SessionID, request.Secret) + } if err != nil { handler.Error(c, stacktrace.Propagate(err, "")) return diff --git a/server/pkg/controller/user/passkey.go b/server/pkg/controller/user/passkey.go index c1ac639ad7..fe42d5a57e 100644 --- a/server/pkg/controller/user/passkey.go +++ b/server/pkg/controller/user/passkey.go @@ -18,7 +18,7 @@ func (c *UserController) ConfigurePassKeySkip(ctx *gin.Context, req *ente.Config return c.TwoFactorRecoveryRepo.ConfigurePassKeySkipChallenge(ctx, userID, req) } -func (c *UserController) GetPasskeySkipChallenge(ctx *gin.Context, passKeySessionID string) (*ente.PasseKeySkipChallengeResponse, error) { +func (c *UserController) GetPasskeyRecoveryResponse(ctx *gin.Context, passKeySessionID string) (*ente.TwoFactorRecoveryResponse, error) { userID, err := c.PasskeyRepo.GetUserIDWithPasskeyTwoFactorSession(passKeySessionID) if err != nil { return nil, err @@ -41,12 +41,12 @@ func (c *UserController) GetPasskeySkipChallenge(ctx *gin.Context, passKeySessio return result, nil } -func (c *UserController) SkipPassKey(context *gin.Context, req *ente.SkipPassKeyRequest) (*ente.TwoFactorAuthorizationResponse, error) { +func (c *UserController) SkipPassKey(context *gin.Context, req *ente.TwoFactorRemovalRequest) (*ente.TwoFactorAuthorizationResponse, error) { userID, err := c.PasskeyRepo.GetUserIDWithPasskeyTwoFactorSession(req.SessionID) if err != nil { return nil, stacktrace.Propagate(err, "") } - exists, err := c.TwoFactorRecoveryRepo.VerifyPasskeySkipSecret(userID, req.SkipSecret) + exists, err := c.TwoFactorRecoveryRepo.VerifyPasskeySkipSecret(userID, req.Secret) if err != nil { return nil, stacktrace.Propagate(err, "") } diff --git a/server/pkg/controller/user/twofactor.go b/server/pkg/controller/user/twofactor.go index ac5473b06f..92a3a7ae5d 100644 --- a/server/pkg/controller/user/twofactor.go +++ b/server/pkg/controller/user/twofactor.go @@ -131,47 +131,47 @@ func (c *UserController) DisableTwoFactor(userID int64) error { // RecoverTwoFactor handles the two factor recovery request by sending the // recoveryKeyEncryptedTwoFactorSecret for the user to decrypt it and make twoFactor removal api call -func (c *UserController) RecoverTwoFactor(sessionID string) (ente.TwoFactorRecoveryResponse, error) { +func (c *UserController) RecoverTwoFactor(sessionID string) (*ente.TwoFactorRecoveryResponse, error) { userID, err := c.TwoFactorRepo.GetUserIDWithTwoFactorSession(sessionID) if err != nil { - return ente.TwoFactorRecoveryResponse{}, stacktrace.Propagate(err, "") + return nil, stacktrace.Propagate(err, "") } response, err := c.TwoFactorRepo.GetRecoveryKeyEncryptedTwoFactorSecret(userID) if err != nil { - return ente.TwoFactorRecoveryResponse{}, stacktrace.Propagate(err, "") + return nil, stacktrace.Propagate(err, "") } - return response, nil + return &response, nil } // RemoveTwoFactor handles two factor deactivation request if user lost his device // by authenticating him using his twoFactorsessionToken and twoFactor secret -func (c *UserController) RemoveTwoFactor(context *gin.Context, sessionID string, secret string) (ente.TwoFactorAuthorizationResponse, error) { +func (c *UserController) RemoveTwoFactor(context *gin.Context, sessionID string, secret string) (*ente.TwoFactorAuthorizationResponse, error) { userID, err := c.TwoFactorRepo.GetUserIDWithTwoFactorSession(sessionID) if err != nil { - return ente.TwoFactorAuthorizationResponse{}, stacktrace.Propagate(err, "") + return nil, stacktrace.Propagate(err, "") } secretHash, err := crypto.GetHash(secret, c.HashingKey) if err != nil { - return ente.TwoFactorAuthorizationResponse{}, stacktrace.Propagate(err, "") + return nil, stacktrace.Propagate(err, "") } exists, err := c.TwoFactorRepo.VerifyTwoFactorSecret(userID, secretHash) if err != nil { - return ente.TwoFactorAuthorizationResponse{}, stacktrace.Propagate(err, "") + return nil, stacktrace.Propagate(err, "") } if !exists { - return ente.TwoFactorAuthorizationResponse{}, stacktrace.Propagate(ente.ErrPermissionDenied, "") + return nil, stacktrace.Propagate(ente.ErrPermissionDenied, "") } err = c.TwoFactorRepo.UpdateTwoFactorStatus(userID, false) if err != nil { - return ente.TwoFactorAuthorizationResponse{}, stacktrace.Propagate(err, "") + return nil, stacktrace.Propagate(err, "") } response, err := c.GetKeyAttributeAndToken(context, userID) if err != nil { - return ente.TwoFactorAuthorizationResponse{}, stacktrace.Propagate(err, "") + return nil, stacktrace.Propagate(err, "") } - return response, nil + return &response, nil } func (c *UserController) GetKeyAttributeAndToken(context *gin.Context, userID int64) (ente.TwoFactorAuthorizationResponse, error) { diff --git a/server/pkg/repo/two_factor_recovery/repository.go b/server/pkg/repo/two_factor_recovery/repository.go index 02f7c8138c..fcbf586b47 100644 --- a/server/pkg/repo/two_factor_recovery/repository.go +++ b/server/pkg/repo/two_factor_recovery/repository.go @@ -3,6 +3,7 @@ package two_factor_recovery import ( "context" "database/sql" + "github.com/ente-io/museum/ente" "github.com/ente-io/museum/pkg/utils/crypto" "github.com/ente-io/stacktrace" @@ -46,9 +47,9 @@ func (r *Repository) ConfigurePassKeySkipChallenge(ctx context.Context, userID i return err } -func (r *Repository) GetPasskeySkipChallenge(ctx context.Context, userID int64) (*ente.PasseKeySkipChallengeResponse, error) { - var result *ente.PasseKeySkipChallengeResponse - err := r.Db.QueryRowContext(ctx, "SELECT user_passkey_secret_data, user_passkey_secret_nonce FROM two_factor_recovery WHERE user_id= $1", userID).Scan(result.UserSecretCipher, result.UserSecretNonce) +func (r *Repository) GetPasskeySkipChallenge(ctx context.Context, userID int64) (*ente.TwoFactorRecoveryResponse, error) { + var result *ente.TwoFactorRecoveryResponse + err := r.Db.QueryRowContext(ctx, "SELECT user_passkey_secret_data, user_passkey_secret_nonce FROM two_factor_recovery WHERE user_id= $1", userID).Scan(result.EncryptedSecret, result.SecretDecryptionNonce) if err != nil { return nil, err }