aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-20 21:58:17 +0900
committernsfisis <nsfisis@gmail.com>2026-02-20 21:58:17 +0900
commit26ead17e4c3682a93f64cca646b291b7ce6b195d (patch)
tree1db444afeba2e65f4217871a20a36b96bb432462
parent85b7a14913c05b88b720fc546eaca5575ffe53fd (diff)
downloadphperkaigi-2026-albatross-26ead17e4c3682a93f64cca646b291b7ce6b195d.tar.gz
phperkaigi-2026-albatross-26ead17e4c3682a93f64cca646b291b7ce6b195d.tar.zst
phperkaigi-2026-albatross-26ead17e4c3682a93f64cca646b291b7ce6b195d.zip
feat(auth): add structured logging for login success and failure
Add ClientIPMiddleware to extract client IP into context.Context, enabling structured log output with username, IP, and failure reason for login attempts. Change failed login log level from Error to Warn as authentication failures are expected events. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-rw-r--r--backend/api/auth_middleware.go21
-rw-r--r--backend/api/handler.go6
-rw-r--r--backend/main.go1
3 files changed, 27 insertions, 1 deletions
diff --git a/backend/api/auth_middleware.go b/backend/api/auth_middleware.go
index 94ef4e4..f2a3987 100644
--- a/backend/api/auth_middleware.go
+++ b/backend/api/auth_middleware.go
@@ -47,3 +47,24 @@ func GetUserFromContext(ctx context.Context) (*db.User, bool) {
func SetUserInContext(ctx context.Context, user *db.User) context.Context {
return context.WithValue(ctx, userContextKey{}, user)
}
+
+type clientIPContextKey struct{}
+
+// ClientIPMiddleware extracts the client IP from echo.Context.RealIP()
+// and stores it in the request's context.Context so that handlers
+// receiving only context.Context (via generated code) can access it.
+func ClientIPMiddleware() echo.MiddlewareFunc {
+ return func(next echo.HandlerFunc) echo.HandlerFunc {
+ return func(c echo.Context) error {
+ ip := c.RealIP()
+ ctx := context.WithValue(c.Request().Context(), clientIPContextKey{}, ip)
+ c.SetRequest(c.Request().WithContext(ctx))
+ return next(c)
+ }
+ }
+}
+
+func GetClientIPFromContext(ctx context.Context) string {
+ ip, _ := ctx.Value(clientIPContextKey{}).(string)
+ return ip
+}
diff --git a/backend/api/handler.go b/backend/api/handler.go
index 74ffcf8..4105efc 100644
--- a/backend/api/handler.go
+++ b/backend/api/handler.go
@@ -51,9 +51,11 @@ func (r postLoginCookieResponse) VisitPostLoginResponse(w http.ResponseWriter) e
func (h *Handler) PostLogin(ctx context.Context, request PostLoginRequestObject) (PostLoginResponseObject, error) {
username := request.Body.Username
password := request.Body.Password
+ ip := GetClientIPFromContext(ctx)
+
userID, err := h.auth.Login(ctx, username, password)
if err != nil {
- slog.Error("login failed", "error", err)
+ slog.Warn("login failed", "username", username, "ip", ip, "reason", err.Error())
var msg string
if errors.Is(err, auth.ErrForteeLoginTimeout) {
msg = "ログインに失敗しました"
@@ -87,6 +89,8 @@ func (h *Handler) PostLogin(ctx context.Context, request PostLoginRequestObject)
return nil, echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
+ slog.Info("login succeeded", "username", username, "user_id", dbUser.UserID, "ip", ip)
+
return postLoginCookieResponse{
cookie: http.Cookie{
Name: "albatross_session",
diff --git a/backend/main.go b/backend/main.go
index 3ea4493..e74a62f 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -99,6 +99,7 @@ func main() {
loginRL := ratelimit.NewIPRateLimiter(rate.Every(time.Minute/5), 5)
apiGroup := e.Group(conf.BasePath + "api")
+ apiGroup.Use(api.ClientIPMiddleware())
apiGroup.Use(ratelimit.LoginRateLimitMiddleware(loginRL))
apiGroup.Use(api.SessionCookieMiddleware(queries))
apiGroup.Use(oapimiddleware.OapiRequestValidator(openAPISpec))