feat: add registration attempt rate limiting with Redis
feat: prevent email enumeration by caching registration state fix: correct Redis key formatting for session termination cache refactor: improve registration flow with Redis cooldown checks chore: add Redis caching for registration in-progress state
This commit is contained in:
@@ -68,7 +68,7 @@ func NewAuthService(_log *zap.Logger, _dbctx database.DbContext, _redis *redis.C
|
||||
// FIXME: review possible problems due to a large pipeline request
|
||||
pipe := _redis.Pipeline()
|
||||
for _, guid := range guids {
|
||||
if err := pipe.Set(ctx, fmt.Sprint("session:%s:is_terminated", guid), true, 0); err != nil {
|
||||
if err := pipe.Set(ctx, fmt.Sprintf("session::%s::is_terminated", guid), true, 0); err != nil {
|
||||
panic("Failed to cache terminated session: " + err.Err().Error())
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,25 @@ func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequ
|
||||
|
||||
defer helper.RollbackOnError(err)
|
||||
|
||||
// TODO: check occupation with redis
|
||||
if isInProgress, err := a.redis.Get(
|
||||
context.TODO(),
|
||||
fmt.Sprintf("email::%s::registration_in_progress",
|
||||
request.Email),
|
||||
).Bool(); err != nil {
|
||||
if err != redis.Nil {
|
||||
a.log.Error(
|
||||
"Failed to look up cached registration_in_progress state of email as part of registration procedure",
|
||||
zap.String("email", request.Email),
|
||||
zap.Error(err))
|
||||
return false, errs.ErrServerError
|
||||
}
|
||||
isInProgress = false
|
||||
} else if isInProgress {
|
||||
a.log.Warn(
|
||||
"Attempted to begin registration on email that is in progress of registration or on cooldown",
|
||||
zap.String("email", request.Email))
|
||||
return false, errs.ErrTooManyRequests
|
||||
}
|
||||
|
||||
if occupationStatus, err = db.TXQueries.CheckUserRegistrationAvailability(db.CTX, database.CheckUserRegistrationAvailabilityParams{
|
||||
Email: request.Email,
|
||||
@@ -118,7 +136,19 @@ func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequ
|
||||
|
||||
} else if occupationStatus.EmailBusy {
|
||||
// Falsely confirm in order to avoid disclosing registered email addresses
|
||||
// TODO: save this email into redis
|
||||
if err := a.redis.Set(
|
||||
context.TODO(),
|
||||
fmt.Sprintf("email::%s::registration_in_progress", request.Email),
|
||||
true,
|
||||
time.Duration(10 * time.Minute), // XXX: magic number
|
||||
).Err(); err != nil {
|
||||
a.log.Error(
|
||||
"Failed to falsely set cache registration_in_progress state for email as a measure to prevent email enumeration",
|
||||
zap.String("email", request.Email),
|
||||
zap.Error(err))
|
||||
return false, errs.ErrServerError
|
||||
}
|
||||
|
||||
a.log.Warn(
|
||||
"Attempted registration for a taken email",
|
||||
zap.String("email", request.Email),
|
||||
@@ -211,6 +241,19 @@ func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequ
|
||||
zap.String("code", generatedCode))
|
||||
}
|
||||
|
||||
if err := a.redis.Set(
|
||||
context.TODO(),
|
||||
fmt.Sprintf("email::%s::registration_in_progress", request.Email),
|
||||
true,
|
||||
time.Duration(10 * time.Minute), // XXX: magic number
|
||||
).Err(); err != nil {
|
||||
a.log.Error(
|
||||
"Failed to cache registration_in_progress state for email",
|
||||
zap.String("email", request.Email),
|
||||
zap.Error(err))
|
||||
return false, errs.ErrServerError
|
||||
}
|
||||
|
||||
if err = helper.Commit(); err != nil {
|
||||
a.log.Error(
|
||||
"Failed to commit transaction",
|
||||
|
||||
Reference in New Issue
Block a user