go-mongo/services/auth/auth.go

70 lines
1.9 KiB
Go

package authService
import (
"context"
"fmt"
"time"
"git.farahty.com/nimer/go-mongo/app"
"git.farahty.com/nimer/go-mongo/models"
"github.com/redis/go-redis/v9"
"go.mongodb.org/mongo-driver/bson"
)
const maxAttempts = 5
const lockoutDuration = 15 * time.Minute
func Login(ctx context.Context, loginInput *models.LoginInput, redisClient redis.UniversalClient) (*models.LoginResponse, error) {
if locked, _ := isLockedOut(loginInput.Identity, redisClient); locked {
return nil, fmt.Errorf("too many failed attempts, please try again later")
}
filter := bson.D{
{
Key: "$or",
Value: bson.A{
bson.D{{Key: "phone", Value: loginInput.Identity}},
bson.D{{Key: "email", Value: loginInput.Identity}},
},
},
}
user, err := app.FindOne[models.User](ctx, "users", filter)
if err != nil {
logFailedLoginAttempt(ctx, loginInput.Identity, redisClient) // optional
return nil, err
}
if !user.CheckPassword(loginInput.Password) {
logFailedLoginAttempt(ctx, loginInput.Identity, redisClient) // optional
return nil, fmt.Errorf("invalid identity or password")
}
clearFailedAttempts(ctx, loginInput.Identity, redisClient)
return successLogin(ctx, user)
}
func isLockedOut(identity string, redisClient redis.UniversalClient) (bool, error) {
key := fmt.Sprintf("login:fail:%s", identity)
attempts, err := redisClient.Get(context.Background(), key).Int()
if err != nil && err != redis.Nil {
return false, err
}
return attempts >= maxAttempts, nil
}
func logFailedLoginAttempt(ctx context.Context, identity string, redisClient redis.UniversalClient) {
key := fmt.Sprintf("login:fail:%s", identity)
redisClient.Incr(ctx, key)
redisClient.Expire(ctx, key, lockoutDuration)
}
func clearFailedAttempts(ctx context.Context, identity string, redisClient redis.UniversalClient) {
key := fmt.Sprintf("login:fail:%s", identity)
redisClient.Del(ctx, key)
}