mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-12-11 18:56:36 +00:00
wip
This commit is contained in:
@@ -31,14 +31,16 @@ type OAuthController struct {
|
||||
router *gin.RouterGroup
|
||||
auth *service.AuthService
|
||||
broker *service.OAuthBrokerService
|
||||
als *service.AccessLogService
|
||||
}
|
||||
|
||||
func NewOAuthController(config OAuthControllerConfig, router *gin.RouterGroup, auth *service.AuthService, broker *service.OAuthBrokerService) *OAuthController {
|
||||
func NewOAuthController(config OAuthControllerConfig, router *gin.RouterGroup, auth *service.AuthService, broker *service.OAuthBrokerService, als *service.AccessLogService) *OAuthController {
|
||||
return &OAuthController{
|
||||
config: config,
|
||||
router: router,
|
||||
auth: auth,
|
||||
broker: broker,
|
||||
als: als,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +63,7 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
service, exists := controller.broker.GetService(req.Provider)
|
||||
svc, exists := controller.broker.GetService(req.Provider)
|
||||
|
||||
if !exists {
|
||||
log.Warn().Msgf("OAuth provider not found: %s", req.Provider)
|
||||
@@ -72,9 +74,9 @@ func (controller *OAuthController) oauthURLHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
service.GenerateVerifier()
|
||||
state := service.GenerateState()
|
||||
authURL := service.GetAuthURL(state)
|
||||
svc.GenerateVerifier()
|
||||
state := svc.GenerateState()
|
||||
authURL := svc.GetAuthURL(state)
|
||||
c.SetCookie(controller.config.CSRFCookieName, state, int(time.Hour.Seconds()), "/", fmt.Sprintf(".%s", controller.config.CookieDomain), controller.config.SecureCookie, true)
|
||||
|
||||
redirectURI := c.Query("redirect_uri")
|
||||
@@ -106,8 +108,16 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
|
||||
state := c.Query("state")
|
||||
csrfCookie, err := c.Cookie(controller.config.CSRFCookieName)
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
if err != nil || state != csrfCookie {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: req.Provider,
|
||||
Username: "",
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "CSRF token mismatch or cookie missing",
|
||||
})
|
||||
log.Warn().Err(err).Msg("CSRF token mismatch or cookie missing")
|
||||
c.SetCookie(controller.config.CSRFCookieName, "", -1, "/", fmt.Sprintf(".%s", controller.config.CookieDomain), controller.config.SecureCookie, true)
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
@@ -117,16 +127,30 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
c.SetCookie(controller.config.CSRFCookieName, "", -1, "/", fmt.Sprintf(".%s", controller.config.CookieDomain), controller.config.SecureCookie, true)
|
||||
|
||||
code := c.Query("code")
|
||||
service, exists := controller.broker.GetService(req.Provider)
|
||||
svc, exists := controller.broker.GetService(req.Provider)
|
||||
|
||||
if !exists {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: req.Provider,
|
||||
Username: "",
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "OAuth provider not found",
|
||||
})
|
||||
log.Warn().Msgf("OAuth provider not found: %s", req.Provider)
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
}
|
||||
|
||||
err = service.VerifyCode(code)
|
||||
err = svc.VerifyCode(code)
|
||||
if err != nil {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: req.Provider,
|
||||
Username: "",
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "Failed to verify OAuth code",
|
||||
})
|
||||
log.Error().Err(err).Msg("Failed to verify OAuth code")
|
||||
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/error", controller.config.AppURL))
|
||||
return
|
||||
@@ -147,6 +171,14 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if !controller.auth.IsEmailWhitelisted(user.Email) {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: req.Provider,
|
||||
Username: user.Email,
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "Email not whitelisted",
|
||||
})
|
||||
|
||||
log.Warn().Str("email", user.Email).Msg("Email not whitelisted")
|
||||
|
||||
queries, err := query.Values(config.UnauthorizedQuery{
|
||||
@@ -189,7 +221,7 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
Email: user.Email,
|
||||
Provider: req.Provider,
|
||||
OAuthGroups: utils.CoalesceToString(user.Groups),
|
||||
OAuthName: service.GetName(),
|
||||
OAuthName: svc.GetName(),
|
||||
}
|
||||
|
||||
log.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
|
||||
@@ -202,6 +234,14 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: req.Provider,
|
||||
Username: user.Email,
|
||||
ClientIP: clientIP,
|
||||
Success: true,
|
||||
Message: "OAuth login successful",
|
||||
})
|
||||
|
||||
redirectURI, err := c.Cookie(controller.config.RedirectCookieName)
|
||||
|
||||
if err != nil || !utils.IsRedirectSafe(redirectURI, controller.config.CookieDomain) {
|
||||
|
||||
@@ -29,13 +29,15 @@ type UserController struct {
|
||||
config UserControllerConfig
|
||||
router *gin.RouterGroup
|
||||
auth *service.AuthService
|
||||
als *service.AccessLogService
|
||||
}
|
||||
|
||||
func NewUserController(config UserControllerConfig, router *gin.RouterGroup, auth *service.AuthService) *UserController {
|
||||
func NewUserController(config UserControllerConfig, router *gin.RouterGroup, auth *service.AuthService, als *service.AccessLogService) *UserController {
|
||||
return &UserController{
|
||||
config: config,
|
||||
router: router,
|
||||
auth: auth,
|
||||
als: als,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +74,13 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
isLocked, remainingTime := controller.auth.IsAccountLocked(rateIdentifier)
|
||||
|
||||
if isLocked {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: req.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "Account is locked due to too many failed login attempts",
|
||||
})
|
||||
log.Warn().Str("username", req.Username).Str("ip", clientIP).Msg("Account is locked due to too many failed login attempts")
|
||||
c.JSON(429, gin.H{
|
||||
"status": 429,
|
||||
@@ -83,6 +92,13 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
userSearch := controller.auth.SearchUser(req.Username)
|
||||
|
||||
if userSearch.Type == "unknown" {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: req.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "User not found",
|
||||
})
|
||||
log.Warn().Str("username", req.Username).Str("ip", clientIP).Msg("User not found")
|
||||
controller.auth.RecordLoginAttempt(rateIdentifier, false)
|
||||
c.JSON(401, gin.H{
|
||||
@@ -93,6 +109,13 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
}
|
||||
|
||||
if !controller.auth.VerifyUser(userSearch, req.Password) {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: req.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "Invalid password",
|
||||
})
|
||||
log.Warn().Str("username", req.Username).Str("ip", clientIP).Msg("Invalid password")
|
||||
controller.auth.RecordLoginAttempt(rateIdentifier, false)
|
||||
c.JSON(401, gin.H{
|
||||
@@ -102,14 +125,18 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Str("username", req.Username).Str("ip", clientIP).Msg("Login successful")
|
||||
|
||||
controller.auth.RecordLoginAttempt(rateIdentifier, true)
|
||||
|
||||
if userSearch.Type == "local" {
|
||||
user := controller.auth.GetLocalUser(userSearch.Username)
|
||||
|
||||
if user.TotpSecret != "" {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: req.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: true,
|
||||
Message: "User has TOTP enabled, requiring TOTP verification",
|
||||
})
|
||||
|
||||
log.Debug().Str("username", req.Username).Msg("User has TOTP enabled, requiring TOTP verification")
|
||||
|
||||
err := controller.auth.CreateSessionCookie(c, &config.SessionCookie{
|
||||
@@ -158,6 +185,18 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: req.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: true,
|
||||
Message: "Login successful",
|
||||
})
|
||||
|
||||
log.Info().Str("username", req.Username).Str("ip", clientIP).Msg("Login successful")
|
||||
|
||||
controller.auth.RecordLoginAttempt(rateIdentifier, true)
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": 200,
|
||||
"message": "Login successful",
|
||||
@@ -167,8 +206,28 @@ func (controller *UserController) loginHandler(c *gin.Context) {
|
||||
func (controller *UserController) logoutHandler(c *gin.Context) {
|
||||
log.Debug().Msg("Logout request received")
|
||||
|
||||
context, err := utils.GetContext(c)
|
||||
|
||||
if err != nil {
|
||||
log.Debug().Msg("Not logged in, nothing to do")
|
||||
c.JSON(200, gin.H{
|
||||
"status": 200,
|
||||
"message": "Not logged in",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
controller.auth.DeleteSessionCookie(c)
|
||||
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: context.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: true,
|
||||
Message: "Logout successful",
|
||||
})
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"status": 200,
|
||||
"message": "Logout successful",
|
||||
@@ -188,6 +247,7 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
context, err := utils.GetContext(c)
|
||||
|
||||
if err != nil {
|
||||
@@ -208,8 +268,6 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
clientIP := c.ClientIP()
|
||||
|
||||
rateIdentifier := context.Username
|
||||
|
||||
if rateIdentifier == "" {
|
||||
@@ -221,6 +279,13 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
isLocked, remainingTime := controller.auth.IsAccountLocked(rateIdentifier)
|
||||
|
||||
if isLocked {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: context.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "Account is locked due to too many failed TOTP attempts",
|
||||
})
|
||||
log.Warn().Str("username", context.Username).Str("ip", clientIP).Msg("Account is locked due to too many failed TOTP attempts")
|
||||
c.JSON(429, gin.H{
|
||||
"status": 429,
|
||||
@@ -234,6 +299,13 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
ok := totp.Validate(req.Code, user.TotpSecret)
|
||||
|
||||
if !ok {
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: context.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: false,
|
||||
Message: "Invalid TOTP code",
|
||||
})
|
||||
log.Warn().Str("username", context.Username).Str("ip", clientIP).Msg("Invalid TOTP code")
|
||||
controller.auth.RecordLoginAttempt(rateIdentifier, false)
|
||||
c.JSON(401, gin.H{
|
||||
@@ -243,6 +315,14 @@ func (controller *UserController) totpHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
controller.als.Log(service.AccessLog{
|
||||
Provider: "username",
|
||||
Username: context.Username,
|
||||
ClientIP: clientIP,
|
||||
Success: true,
|
||||
Message: "TOTP verification successful",
|
||||
})
|
||||
|
||||
log.Info().Str("username", context.Username).Str("ip", clientIP).Msg("TOTP verification successful")
|
||||
|
||||
controller.auth.RecordLoginAttempt(rateIdentifier, true)
|
||||
|
||||
@@ -64,10 +64,18 @@ func setupUserController(t *testing.T, middlewares *[]gin.HandlerFunc) (*gin.Eng
|
||||
SessionCookieName: "tinyauth-session",
|
||||
}, nil, nil, database)
|
||||
|
||||
// Access log service
|
||||
als := service.NewAccessLogService(&service.AccessLogServiceConfig{
|
||||
LogFile: "",
|
||||
LogJson: true,
|
||||
})
|
||||
|
||||
assert.NilError(t, als.Init())
|
||||
|
||||
// Controller
|
||||
ctrl := controller.NewUserController(controller.UserControllerConfig{
|
||||
CookieDomain: "localhost",
|
||||
}, group, authService)
|
||||
}, group, authService, als)
|
||||
ctrl.SetupRoutes()
|
||||
|
||||
return router, recorder
|
||||
|
||||
Reference in New Issue
Block a user