Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions _example/authgate-cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,11 @@ func exchangeDeviceCode(
}

// Validate token response
if err := validateTokenResponse(tokenResp.AccessToken, tokenResp.TokenType, tokenResp.ExpiresIn); err != nil {
if err := validateTokenResponse(
tokenResp.AccessToken,
tokenResp.TokenType,
tokenResp.ExpiresIn,
); err != nil {
return nil, fmt.Errorf("invalid token response: %w", err)
}

Expand Down Expand Up @@ -781,7 +785,11 @@ func refreshAccessToken(refreshToken string) (*TokenStorage, error) {
}

// Validate token response
if err := validateTokenResponse(tokenResp.AccessToken, tokenResp.TokenType, tokenResp.ExpiresIn); err != nil {
if err := validateTokenResponse(
tokenResp.AccessToken,
tokenResp.TokenType,
tokenResp.ExpiresIn,
); err != nil {
return nil, fmt.Errorf("invalid token response: %w", err)
}

Expand Down
5 changes: 4 additions & 1 deletion internal/auth/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ func (p *LocalAuthProvider) Authenticate(
return nil, ErrInvalidCredentials
}

if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
if err := bcrypt.CompareHashAndPassword(
[]byte(user.PasswordHash),
[]byte(password),
); err != nil {
return nil, ErrInvalidCredentials
}

Expand Down
33 changes: 33 additions & 0 deletions internal/handlers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"time"

"github.com/appleboy/authgate/internal/auth"
"github.com/appleboy/authgate/internal/metrics"
"github.com/appleboy/authgate/internal/middleware"
"github.com/appleboy/authgate/internal/services"
"github.com/appleboy/authgate/internal/templates"
Expand Down Expand Up @@ -97,19 +98,22 @@ type AuthHandler struct {
baseURL string
sessionFingerprintEnabled bool
sessionFingerprintIncludeIP bool
metrics metrics.MetricsRecorder
}

func NewAuthHandler(
us *services.UserService,
baseURL string,
fingerprintEnabled bool,
fingerprintIncludeIP bool,
m metrics.MetricsRecorder,
) *AuthHandler {
return &AuthHandler{
userService: us,
baseURL: baseURL,
sessionFingerprintEnabled: fingerprintEnabled,
sessionFingerprintIncludeIP: fingerprintIncludeIP,
metrics: m,
}
}

Expand Down Expand Up @@ -170,6 +174,7 @@ func (h *AuthHandler) LoginPageWithOAuth(
func (h *AuthHandler) Login(c *gin.Context,
oauthProviders map[string]*auth.OAuthProvider,
) {
start := time.Now()
username := c.PostForm("username")
password := c.PostForm("password")
redirectTo := c.PostForm("redirect")
Expand All @@ -181,6 +186,11 @@ func (h *AuthHandler) Login(c *gin.Context,

user, err := h.userService.Authenticate(c.Request.Context(), username, password)
if err != nil {
// Record failed login
duration := time.Since(start)
h.metrics.RecordLogin("local", false)
h.metrics.RecordAuthAttempt("local", false, duration)

var errorMsg string

// Check for specific error types
Expand Down Expand Up @@ -212,6 +222,15 @@ func (h *AuthHandler) Login(c *gin.Context,
return
}

// Record successful login
duration := time.Since(start)
authSource := user.AuthSource
if authSource == "" {
authSource = "local"
}
h.metrics.RecordLogin(authSource, true)
h.metrics.RecordAuthAttempt(authSource, true, duration)

// Set session
session := sessions.Default(c)
session.Set(SessionUserID, user.ID)
Expand Down Expand Up @@ -249,12 +268,26 @@ func (h *AuthHandler) Login(c *gin.Context,
// Logout clears the session and redirects to login
func (h *AuthHandler) Logout(c *gin.Context) {
session := sessions.Default(c)

// Calculate session duration if available
var sessionDuration time.Duration
if createdAtUnix := session.Get(SessionLastActivity); createdAtUnix != nil {
if createdAtInt64, ok := createdAtUnix.(int64); ok {
createdAt := time.Unix(createdAtInt64, 0)
sessionDuration = time.Since(createdAt)
}
}
Comment on lines +272 to +279
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sessionDuration is computed from SessionLastActivity, but that value is updated on every request by SessionIdleTimeout, so this measures time since last request rather than total session length. If the goal is the session_duration_seconds histogram, consider storing a separate session-created timestamp (set once at login/OAuth callback) and compute duration from that; or rename the metric to reflect idle time.

Copilot uses AI. Check for mistakes.

session.Clear()
if err := session.Save(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to save session",
})
return
}

// Record logout
h.metrics.RecordLogout(sessionDuration)

c.Redirect(http.StatusFound, "/login")
}
10 changes: 10 additions & 0 deletions internal/handlers/oauth_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/appleboy/authgate/internal/auth"
"github.com/appleboy/authgate/internal/metrics"
"github.com/appleboy/authgate/internal/services"
"github.com/appleboy/authgate/internal/templates"

Expand All @@ -38,6 +39,7 @@ type OAuthHandler struct {
httpClient *http.Client // Custom HTTP client for OAuth requests
sessionFingerprintEnabled bool
sessionFingerprintIncludeIP bool
metrics metrics.MetricsRecorder
}

// NewOAuthHandler creates a new OAuth handler
Expand All @@ -47,13 +49,15 @@ func NewOAuthHandler(
httpClient *http.Client,
fingerprintEnabled bool,
fingerprintIncludeIP bool,
m metrics.MetricsRecorder,
) *OAuthHandler {
return &OAuthHandler{
providers: providers,
userService: userService,
httpClient: httpClient,
sessionFingerprintEnabled: fingerprintEnabled,
sessionFingerprintIncludeIP: fingerprintIncludeIP,
metrics: m,
}
}

Expand Down Expand Up @@ -200,6 +204,9 @@ func (h *OAuthHandler) OAuthCallback(c *gin.Context) {
token,
)
if err != nil {
// Record failure
h.metrics.RecordOAuthCallback(provider, false)

log.Printf("[OAuth] Authentication failed: %v", err)

// Handle specific errors
Expand All @@ -225,6 +232,9 @@ func (h *OAuthHandler) OAuthCallback(c *gin.Context) {
return
}

// Record success
h.metrics.RecordOAuthCallback(provider, true)

// Clear OAuth session data
session.Delete("oauth_state")
session.Delete("oauth_provider")
Expand Down
18 changes: 15 additions & 3 deletions internal/handlers/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ func (h *SessionHandler) RevokeSession(c *gin.Context) {
userID, _ := c.Get("user_id")

// Revoke the token
if err := h.tokenService.RevokeTokenByID(c.Request.Context(), tokenID, userID.(string)); err != nil {
if err := h.tokenService.RevokeTokenByID(
c.Request.Context(),
tokenID,
userID.(string),
); err != nil {
templates.RenderTempl(
c,
http.StatusInternalServerError,
Expand Down Expand Up @@ -214,7 +218,11 @@ func (h *SessionHandler) DisableSession(c *gin.Context) {
userID, _ := c.Get("user_id")

// Disable the token
if err := h.tokenService.DisableToken(c.Request.Context(), tokenID, userID.(string)); err != nil {
if err := h.tokenService.DisableToken(
c.Request.Context(),
tokenID,
userID.(string),
); err != nil {
templates.RenderTempl(
c,
http.StatusInternalServerError,
Expand All @@ -238,7 +246,11 @@ func (h *SessionHandler) EnableSession(c *gin.Context) {
userID, _ := c.Get("user_id")

// Enable the token
if err := h.tokenService.EnableToken(c.Request.Context(), tokenID, userID.(string)); err != nil {
if err := h.tokenService.EnableToken(
c.Request.Context(),
tokenID,
userID.(string),
); err != nil {
templates.RenderTempl(
c,
http.StatusInternalServerError,
Expand Down
Loading
Loading