From e3c57e185ea380998b71af05f9c9e5a3f8db71f0 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Tue, 1 Sep 2026 23:59:19 +0530 Subject: [PATCH 1/5] fix: switch case now uses string names instead of enum values --- internal/handlers/send_otp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/handlers/send_otp.go b/internal/handlers/send_otp.go index fb617a6..eb133a9 100644 --- a/internal/handlers/send_otp.go +++ b/internal/handlers/send_otp.go @@ -21,7 +21,7 @@ func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*go purpose := req.Purpose switch purpose { - case 0: + case goth.OTPPurposeMfa: // get the email of the authenticated user from the access token. claims, ok := interceptors.GetClaims(ctx) if !ok { @@ -71,7 +71,7 @@ func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*go ExpiresInSeconds: 300, }, nil - case 1: + case goth.OTPPurposePasswordReset: // get the email of the authenticated user from the access token. claims, ok := interceptors.GetClaims(ctx) if !ok { @@ -121,7 +121,7 @@ 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 From a9973ce9f311146ac2c61605cab504fcce11fcd2 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Wed, 2 Sep 2026 00:00:03 +0530 Subject: [PATCH 2/5] fix: remove SendOTP from auth interceptors bypass list --- internal/interceptors/auth.go | 1 - 1 file changed, 1 deletion(-) 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) } From e1533848e753e3eae8852ccce9f630d91b490c04 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Wed, 2 Sep 2026 00:00:25 +0530 Subject: [PATCH 3/5] fix: unmute the silent error drop --- internal/helpers/hash_otp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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) From 7158695dcfe2026886431481cc71bb7e30d79dea Mon Sep 17 00:00:00 2001 From: ayush00git Date: Wed, 2 Sep 2026 00:11:24 +0530 Subject: [PATCH 4/5] fix: password reset has to be a unauth rpc --- internal/handlers/send_otp.go | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/internal/handlers/send_otp.go b/internal/handlers/send_otp.go index eb133a9..1e84080 100644 --- a/internal/handlers/send_otp.go +++ b/internal/handlers/send_otp.go @@ -15,12 +15,12 @@ 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 { + switch req.Purpose { case goth.OTPPurposeMfa: // get the email of the authenticated user from the access token. claims, ok := interceptors.GetClaims(ctx) @@ -72,15 +72,10 @@ func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*go }, nil case goth.OTPPurposePasswordReset: - // 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") - } - 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() From 7584e206e50e336bb72522fc54bb2e6b2f3b08c1 Mon Sep 17 00:00:00 2001 From: ayush00git Date: Wed, 2 Sep 2026 00:31:16 +0530 Subject: [PATCH 5/5] fix: added a few guardrails to the pubilc rpc --- internal/handlers/send_otp.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/handlers/send_otp.go b/internal/handlers/send_otp.go index 1e84080..b582c46 100644 --- a/internal/handlers/send_otp.go +++ b/internal/handlers/send_otp.go @@ -89,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" + email, + "password_reset:email_otp:" + email, hashedOTP, 5*time.Minute, ).Err() @@ -120,6 +120,21 @@ func (g *GothServer) SendOTP(ctx context.Context, req *goth.SendOTPRequest) (*go // 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") @@ -133,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()