Refactor: extend totp recovery API to recover passkey
This commit is contained in:
parent
fe181fecbe
commit
980ab6c49c
7 changed files with 39 additions and 59 deletions
|
@ -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)
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, "")
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue