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
|
// FIXME: review possible problems due to a large pipeline request
|
||||||
pipe := _redis.Pipeline()
|
pipe := _redis.Pipeline()
|
||||||
for _, guid := range guids {
|
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())
|
panic("Failed to cache terminated session: " + err.Err().Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,25 @@ func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequ
|
|||||||
|
|
||||||
defer helper.RollbackOnError(err)
|
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{
|
if occupationStatus, err = db.TXQueries.CheckUserRegistrationAvailability(db.CTX, database.CheckUserRegistrationAvailabilityParams{
|
||||||
Email: request.Email,
|
Email: request.Email,
|
||||||
@@ -118,7 +136,19 @@ func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequ
|
|||||||
|
|
||||||
} else if occupationStatus.EmailBusy {
|
} else if occupationStatus.EmailBusy {
|
||||||
// Falsely confirm in order to avoid disclosing registered email addresses
|
// 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(
|
a.log.Warn(
|
||||||
"Attempted registration for a taken email",
|
"Attempted registration for a taken email",
|
||||||
zap.String("email", request.Email),
|
zap.String("email", request.Email),
|
||||||
@@ -211,6 +241,19 @@ func (a *authServiceImpl) RegistrationBegin(request models.RegistrationBeginRequ
|
|||||||
zap.String("code", generatedCode))
|
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 {
|
if err = helper.Commit(); err != nil {
|
||||||
a.log.Error(
|
a.log.Error(
|
||||||
"Failed to commit transaction",
|
"Failed to commit transaction",
|
||||||
|
|||||||
Reference in New Issue
Block a user