diff --git a/internal/handlers/send_otp.go b/internal/handlers/send_otp.go index fb617a6..b582c46 100644 --- a/internal/handlers/send_otp.go +++ b/internal/handlers/send_otp.go @@ -15,13 +15,13 @@ import ( func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*goth.SendOTPResponse, error) { // OTPPurpose is a enum type with specified values for each type. - // 0 - MfaType (authenticated) - // 1 - Password reset (user should be authenticated, validate email via access token) - // 2 - Email Verification (can accept email from any user in this case). - purpose := req.Purpose + // 0 - MfaType (authenticated user is required). + // 1 - Password reset (user could be auth/unauthenticated). + // 2 - Email Verification (user could be auth/unauthenticated). + - switch purpose { - case 0: + switch req.Purpose { + case goth.OTPPurposeMfa: // get the email of the authenticated user from the access token. claims, ok := interceptors.GetClaims(ctx) if !ok { @@ -71,16 +71,11 @@ func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*go ExpiresInSeconds: 300, }, nil - case 1: - // get the email of the authenticated user from the access token. - claims, ok := interceptors.GetClaims(ctx) - if !ok { - return nil, status.Error(codes.Internal, "failed getting claims") - } + case goth.OTPPurposePasswordReset: - email := claims.Email - userID := claims.UserID + email := req.Email + // generate + hash the otp and then store to redis. otp, err := helpers.GenerateOTP() if err != nil { return nil, status.Error(codes.Internal, "failed generating OTP") @@ -94,7 +89,7 @@ func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*go // store to redis. err = g.rdb.Set( ctx, - "password_reset:email_otp" + userID, + "password_reset:email_otp:" + email, hashedOTP, 5*time.Minute, ).Err() @@ -121,10 +116,25 @@ func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*go ExpiresInSeconds: 300, }, nil - case 3: + case goth.OTPPurposeEmailVerify: // get the email once again. email := req.Email + // check if the user's account is already verified. + var isVerified bool + err := g.db.QueryRow( + ctx, + `SELECT email_verified FROM users + WHERE email = $1`, + email, + ).Scan(&isVerified) + if err != nil { + return nil, status.Error(codes.Internal, "failed sending email to user") + } + if isVerified == true { + return nil, status.Error(codes.AlreadyExists, "your account is already verified") + } + otp, err := helpers.GenerateOTP() if err != nil { return nil, status.Error(codes.Internal, "failed generating OTP") @@ -138,7 +148,7 @@ func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*go // store to redis. err = g.rdb.Set( ctx, - "acc_verify:email_otp" + email, + "acc_verify:email_otp:" + email, hashedOTP, 5*time.Minute, ).Err() diff --git a/internal/helpers/hash_otp.go b/internal/helpers/hash_otp.go index 4f9b2e4..810906b 100644 --- a/internal/helpers/hash_otp.go +++ b/internal/helpers/hash_otp.go @@ -7,7 +7,7 @@ import "golang.org/x/crypto/bcrypt" func GenerateHash(input string) (string, error) { hashedBytes, err := bcrypt.GenerateFromPassword([]byte(input), 10) if err != nil { - return "", nil + return "", err } hashedString := string(hashedBytes) diff --git a/internal/interceptors/auth.go b/internal/interceptors/auth.go index c095327..270ec13 100644 --- a/internal/interceptors/auth.go +++ b/internal/interceptors/auth.go @@ -17,7 +17,6 @@ func AuthInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, h switch info.FullMethod { case "/goth.GothService/Login", "/goth.GothService/Signup", - "/goth.GothService/SendOTP", "/goth.GothService/VerifyOTP": return handler(ctx, req) }