Skip to content

Commit c7bb6d6

Browse files
authored
Merge branch 'main' into refactor/tests
2 parents fa25740 + d3cda06 commit c7bb6d6

1 file changed

Lines changed: 51 additions & 1 deletion

File tree

internal/service/auth_service.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ import (
2121
"golang.org/x/oauth2"
2222
)
2323

24+
// hard-defaults, may make configurable in the future if needed,
25+
// but for now these are just safety limits to prevent unbounded memory usage
2426
const MaxOAuthPendingSessions = 256
2527
const OAuthCleanupCount = 16
28+
const MaxLoginAttemptRecords = 256
2629

2730
type OAuthPendingSession struct {
2831
State string
@@ -43,6 +46,11 @@ type LoginAttempt struct {
4346
LockedUntil time.Time
4447
}
4548

49+
type Lockdown struct {
50+
Active bool
51+
ActiveUntil time.Time
52+
}
53+
4654
type AuthServiceConfig struct {
4755
Users []config.User
4856
OauthWhitelist []string
@@ -69,6 +77,7 @@ type AuthService struct {
6977
ldap *LdapService
7078
queries *repository.Queries
7179
oauthBroker *OAuthBrokerService
80+
lockdown *Lockdown
7281
}
7382

7483
func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, queries *repository.Queries, oauthBroker *OAuthBrokerService) *AuthService {
@@ -202,6 +211,11 @@ func (auth *AuthService) IsAccountLocked(identifier string) (bool, int) {
202211
auth.loginMutex.RLock()
203212
defer auth.loginMutex.RUnlock()
204213

214+
if auth.lockdown != nil && auth.lockdown.Active {
215+
remaining := int(time.Until(auth.lockdown.ActiveUntil).Seconds())
216+
return true, remaining
217+
}
218+
205219
if auth.config.LoginMaxRetries <= 0 || auth.config.LoginTimeout <= 0 {
206220
return false, 0
207221
}
@@ -227,6 +241,14 @@ func (auth *AuthService) RecordLoginAttempt(identifier string, success bool) {
227241
auth.loginMutex.Lock()
228242
defer auth.loginMutex.Unlock()
229243

244+
if len(auth.loginAttempts) >= MaxLoginAttemptRecords {
245+
if auth.lockdown != nil && auth.lockdown.Active {
246+
return
247+
}
248+
go auth.lockdownMode()
249+
return
250+
}
251+
230252
attempt, exists := auth.loginAttempts[identifier]
231253
if !exists {
232254
attempt = &LoginAttempt{}
@@ -747,9 +769,37 @@ func (auth *AuthService) ensureOAuthSessionLimit() {
747769
}
748770
}
749771

772+
func (auth *AuthService) lockdownMode() {
773+
auth.loginMutex.Lock()
774+
775+
tlog.App.Warn().Msg("Multiple login attempts detected, possibly DDOS attack. Activating temporary lockdown.")
776+
777+
auth.lockdown = &Lockdown{
778+
Active: true,
779+
ActiveUntil: time.Now().Add(time.Duration(auth.config.LoginTimeout) * time.Second),
780+
}
781+
782+
// At this point all login attemps will also expire so,
783+
// we might as well clear them to free up memory
784+
auth.loginAttempts = make(map[string]*LoginAttempt)
785+
786+
timer := time.NewTimer(time.Until(auth.lockdown.ActiveUntil))
787+
defer timer.Stop()
788+
789+
auth.loginMutex.Unlock()
790+
791+
<-timer.C
792+
793+
auth.loginMutex.Lock()
794+
795+
tlog.App.Info().Msg("Lockdown period ended, resuming normal operation")
796+
auth.lockdown = nil
797+
auth.loginMutex.Unlock()
798+
}
799+
750800
// Function only used for testing - do not use in prod!
751801
func (auth *AuthService) ClearRateLimitsTestingOnly() {
752802
auth.loginMutex.Lock()
753803
auth.loginAttempts = make(map[string]*LoginAttempt)
754-
auth.loginMutex.Unlock()
804+
auth.loginMutex.Unlock()
755805
}

0 commit comments

Comments
 (0)