+52
-5
@@ -7,6 +7,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
"vctp/internal/auth"
|
||||
"vctp/server/audit"
|
||||
"vctp/server/middleware"
|
||||
"vctp/server/models"
|
||||
)
|
||||
|
||||
@@ -50,12 +52,14 @@ func (h *Handler) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if h == nil || h.Settings == nil || h.Settings.Values == nil {
|
||||
audit.LogAuthEvent(nil, r, "login", "error", "reason", "settings_not_configured")
|
||||
writeJSONError(w, http.StatusInternalServerError, "authentication is not configured")
|
||||
return
|
||||
}
|
||||
|
||||
cfg := h.Settings.Values.Settings
|
||||
if !cfg.AuthEnabled {
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "deny", "reason", "auth_disabled")
|
||||
writeJSONError(w, http.StatusServiceUnavailable, "authentication is disabled")
|
||||
return
|
||||
}
|
||||
@@ -63,12 +67,14 @@ func (h *Handler) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
var req models.AuthLoginRequest
|
||||
if err := decodeJSONBody(w, r, &req); err != nil {
|
||||
h.Logger.Error("unable to decode auth login request", "error", err)
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "deny", "reason", "invalid_request_json", "error", err)
|
||||
writeJSONError(w, http.StatusBadRequest, "invalid JSON body")
|
||||
return
|
||||
}
|
||||
username := strings.TrimSpace(req.Username)
|
||||
password := req.Password
|
||||
if username == "" || strings.TrimSpace(password) == "" {
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "deny", "reason", "missing_username_or_password", "username", username)
|
||||
writeJSONError(w, http.StatusBadRequest, "username and password are required")
|
||||
return
|
||||
}
|
||||
@@ -83,6 +89,7 @@ func (h *Handler) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
h.Logger.Error("failed to initialize ldap authenticator", "error", err)
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "error", "reason", "ldap_authenticator_init_failed", "username", username, "error", err)
|
||||
writeJSONError(w, http.StatusInternalServerError, "authentication service unavailable")
|
||||
return
|
||||
}
|
||||
@@ -92,23 +99,23 @@ func (h *Handler) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
identity, err := ldapAuth.AuthenticateAndFetchGroups(ctx, username, password)
|
||||
if err != nil {
|
||||
if errors.Is(err, auth.ErrLDAPInvalidCredentials) {
|
||||
h.Logger.Warn("auth login rejected", "username", username, "reason", "invalid_credentials")
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "deny", "reason", "invalid_credentials", "username", username)
|
||||
writeJSONError(w, http.StatusUnauthorized, authLoginFailureMessage)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {
|
||||
h.Logger.Warn("auth login ldap timeout", "username", username, "error", err)
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "deny", "reason", "ldap_timeout", "username", username, "error", err)
|
||||
writeJSONError(w, http.StatusUnauthorized, authLoginFailureMessage)
|
||||
return
|
||||
}
|
||||
h.Logger.Warn("auth login ldap failure", "username", username, "error", err)
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "deny", "reason", "ldap_authentication_failed", "username", username, "error", err)
|
||||
writeJSONError(w, http.StatusUnauthorized, authLoginFailureMessage)
|
||||
return
|
||||
}
|
||||
|
||||
roles := auth.ResolveRoles(identity.Groups, cfg.AuthGroupRoleMappings)
|
||||
if !auth.HasAnyGroup(identity.Groups, cfg.LDAPGroups) || len(roles) == 0 {
|
||||
h.Logger.Warn("auth login rejected", "username", username, "reason", "group_or_role_denied")
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "deny", "reason", "group_or_role_denied", "username", username, "group_count", len(identity.Groups), "resolved_roles", roles)
|
||||
writeJSONError(w, http.StatusUnauthorized, authLoginFailureMessage)
|
||||
return
|
||||
}
|
||||
@@ -122,6 +129,7 @@ func (h *Handler) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
h.Logger.Error("failed to initialize jwt service", "error", err)
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "error", "reason", "jwt_service_init_failed", "username", username, "error", err)
|
||||
writeJSONError(w, http.StatusInternalServerError, "authentication service unavailable")
|
||||
return
|
||||
}
|
||||
@@ -133,14 +141,53 @@ func (h *Handler) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
token, claims, err := jwtSvc.IssueToken(subject, roles, identity.Groups)
|
||||
if err != nil {
|
||||
h.Logger.Error("failed to issue auth token", "username", username, "error", err)
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "error", "reason", "token_issue_failed", "username", username, "error", err)
|
||||
writeJSONError(w, http.StatusInternalServerError, "failed to issue access token")
|
||||
return
|
||||
}
|
||||
|
||||
h.Logger.Info("auth login successful", "username", subject, "roles", roles)
|
||||
audit.LogAuthEvent(h.Logger, r, "login", "allow", "username", subject, "resolved_roles", roles, "expires_at", claims.ExpiresAt)
|
||||
writeJSON(w, http.StatusOK, models.AuthLoginResponse{
|
||||
AccessToken: token,
|
||||
ExpiresAt: claims.ExpiresAt,
|
||||
TokenType: "Bearer",
|
||||
})
|
||||
}
|
||||
|
||||
// AuthMe returns the currently authenticated identity from validated JWT claims.
|
||||
// @Summary Who am I
|
||||
// @Description Returns JWT claims for the currently authenticated bearer token.
|
||||
// @Description Requires Bearer authentication.
|
||||
// @Tags auth
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.AuthMeResponse "Authenticated identity"
|
||||
// @Failure 401 {object} models.ErrorResponse "Missing or invalid authentication context"
|
||||
// @Router /api/auth/me [get]
|
||||
// @Security BearerAuth
|
||||
func (h *Handler) AuthMe(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
writeJSONError(w, http.StatusMethodNotAllowed, "method not allowed")
|
||||
return
|
||||
}
|
||||
|
||||
claims, ok := middleware.ClaimsFromContext(r.Context())
|
||||
if !ok {
|
||||
audit.LogAuthEvent(h.Logger, r, "whoami", "deny", "reason", "missing_auth_context")
|
||||
writeJSONError(w, http.StatusUnauthorized, "missing authentication context")
|
||||
return
|
||||
}
|
||||
|
||||
audit.LogAuthEvent(h.Logger, r, "whoami", "allow", "subject", claims.Subject, "roles", claims.Roles)
|
||||
writeJSON(w, http.StatusOK, models.AuthMeResponse{
|
||||
Status: "OK",
|
||||
Subject: claims.Subject,
|
||||
Roles: claims.Roles,
|
||||
Groups: claims.Groups,
|
||||
Issuer: claims.Issuer,
|
||||
Audience: claims.Audience,
|
||||
IssuedAt: claims.IssuedAt,
|
||||
ExpiresAt: claims.ExpiresAt,
|
||||
NotBefore: claims.NotBefore,
|
||||
TokenID: claims.ID,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user