forked from baron/baron-sso
userfront 로그인 후 /dashboard로 이동하게 변경
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
@@ -24,7 +25,7 @@ type AuditLogEntry struct {
|
||||
Origin string
|
||||
Referer string
|
||||
Query map[string]string
|
||||
Headers map[string]string // Core headers like Host, Cookie, Set-Cookie
|
||||
Headers map[string]string // 핵심 헤더(민감 키는 마스킹됨)
|
||||
LoginIDs map[string]string // loginId and loginId_normalized
|
||||
Token string // For reset tokens, magic link tokens
|
||||
ProviderError string
|
||||
@@ -43,8 +44,6 @@ type AuditLogEntry struct {
|
||||
RedirectTo string
|
||||
HasCookieDSRF bool
|
||||
ParsedCookieDSRF string
|
||||
RequestBody string // For complete stage
|
||||
NewPassword string // For complete stage (test only, sensitive)
|
||||
// ... potentially more fields specific to different stages
|
||||
}
|
||||
|
||||
@@ -55,16 +54,14 @@ func NewAuditLogEntry(c *fiber.Ctx, stage string) *AuditLogEntry {
|
||||
// Extract query parameters
|
||||
queryParams := make(map[string]string)
|
||||
c.Context().QueryArgs().VisitAll(func(key, value []byte) {
|
||||
queryParams[string(key)] = string(value)
|
||||
k := string(key)
|
||||
queryParams[k] = maskSensitiveByKey(k, string(value))
|
||||
})
|
||||
|
||||
// Extract relevant headers
|
||||
headers := make(map[string]string)
|
||||
headers["Host"] = c.Get("Host")
|
||||
headers["User-Agent"] = c.Get("User-Agent")
|
||||
if cookie := c.Get("Cookie"); cookie != "" {
|
||||
headers["Cookie"] = cookie
|
||||
}
|
||||
headers["Origin"] = c.Get("Origin")
|
||||
headers["Referer"] = c.Get("Referer")
|
||||
|
||||
@@ -122,14 +119,14 @@ func (ale *AuditLogEntry) Log(level slog.Level, msg string, args ...any) {
|
||||
if len(ale.Query) > 0 {
|
||||
queryGroupArgs := make([]any, 0, len(ale.Query))
|
||||
for k, v := range ale.Query {
|
||||
queryGroupArgs = append(queryGroupArgs, slog.String(k, v))
|
||||
queryGroupArgs = append(queryGroupArgs, slog.String(k, maskSensitiveByKey(k, v)))
|
||||
}
|
||||
attrs = append(attrs, slog.Group("query", queryGroupArgs...))
|
||||
}
|
||||
if len(ale.Headers) > 0 {
|
||||
headersGroupArgs := make([]any, 0, len(ale.Headers))
|
||||
for k, v := range ale.Headers {
|
||||
headersGroupArgs = append(headersGroupArgs, slog.String(k, v))
|
||||
headersGroupArgs = append(headersGroupArgs, slog.String(k, maskSensitiveByKey(k, v)))
|
||||
}
|
||||
attrs = append(attrs, slog.Group("headers", headersGroupArgs...))
|
||||
}
|
||||
@@ -141,7 +138,7 @@ func (ale *AuditLogEntry) Log(level slog.Level, msg string, args ...any) {
|
||||
attrs = append(attrs, slog.Group("login_ids", loginIDGroupArgs...))
|
||||
}
|
||||
if ale.Token != "" {
|
||||
attrs = append(attrs, slog.String("token", ale.Token))
|
||||
attrs = append(attrs, slog.Bool("has_token", true))
|
||||
}
|
||||
if ale.ProviderError != "" {
|
||||
attrs = append(attrs, slog.String("provider_error", ale.ProviderError))
|
||||
@@ -153,13 +150,13 @@ func (ale *AuditLogEntry) Log(level slog.Level, msg string, args ...any) {
|
||||
attrs = append(attrs, slog.String("provider_response_body", ale.ProviderBody))
|
||||
}
|
||||
if ale.RefreshToken != "" {
|
||||
attrs = append(attrs, slog.String("refresh_token", ale.RefreshToken))
|
||||
attrs = append(attrs, slog.Bool("has_refresh_token", true))
|
||||
}
|
||||
if ale.SessionJwt != "" {
|
||||
attrs = append(attrs, slog.String("session_jwt", ale.SessionJwt))
|
||||
attrs = append(attrs, slog.Bool("has_session_jwt", true))
|
||||
}
|
||||
if ale.AccessJwt != "" {
|
||||
attrs = append(attrs, slog.String("access_jwt", ale.AccessJwt))
|
||||
attrs = append(attrs, slog.Bool("has_access_jwt", true))
|
||||
}
|
||||
if ale.UserLoginId != "" {
|
||||
attrs = append(attrs, slog.String("user_login_id", ale.UserLoginId))
|
||||
@@ -175,7 +172,9 @@ func (ale *AuditLogEntry) Log(level slog.Level, msg string, args ...any) {
|
||||
}
|
||||
if ale.SetCookieName != "" {
|
||||
attrs = append(attrs, slog.String("set_cookie_name", ale.SetCookieName))
|
||||
attrs = append(attrs, slog.String("set_cookie_value", ale.SetCookieValue))
|
||||
if ale.SetCookieValue != "" {
|
||||
attrs = append(attrs, slog.Bool("has_set_cookie_value", true))
|
||||
}
|
||||
if len(ale.SetCookieAttrs) > 0 {
|
||||
cookieAttrsGroupArgs := make([]any, 0, len(ale.SetCookieAttrs))
|
||||
for k, v := range ale.SetCookieAttrs {
|
||||
@@ -191,13 +190,7 @@ func (ale *AuditLogEntry) Log(level slog.Level, msg string, args ...any) {
|
||||
attrs = append(attrs, slog.Bool("has_cookie_DSRF", ale.HasCookieDSRF))
|
||||
}
|
||||
if ale.ParsedCookieDSRF != "" {
|
||||
attrs = append(attrs, slog.String("parsed_cookie_DSRF", ale.ParsedCookieDSRF))
|
||||
}
|
||||
if ale.RequestBody != "" {
|
||||
attrs = append(attrs, slog.String("request_body", ale.RequestBody))
|
||||
}
|
||||
if ale.NewPassword != "" { // FOR TEST ONLY - DO NOT LOG IN PRODUCTION
|
||||
attrs = append(attrs, slog.String("new_password", ale.NewPassword))
|
||||
attrs = append(attrs, slog.Bool("has_parsed_cookie_DSRF", true))
|
||||
}
|
||||
|
||||
// Convert variadic args to slog.Attr before appending
|
||||
@@ -212,3 +205,36 @@ func (ale *AuditLogEntry) Log(level slog.Level, msg string, args ...any) {
|
||||
|
||||
slog.Default().LogAttrs(context.Background(), level, msg, attrs...)
|
||||
}
|
||||
|
||||
var sensitiveAuditKeys = map[string]struct{}{
|
||||
"password": {},
|
||||
"currentpassword": {},
|
||||
"newpassword": {},
|
||||
"oldpassword": {},
|
||||
"token": {},
|
||||
"accesstoken": {},
|
||||
"refreshtoken": {},
|
||||
"authorization": {},
|
||||
"cookie": {},
|
||||
"setcookie": {},
|
||||
"verificationcode": {},
|
||||
"code": {},
|
||||
"loginchallenge": {},
|
||||
"loginverifier": {},
|
||||
"sessionjwt": {},
|
||||
"accessjwt": {},
|
||||
"refreshjwt": {},
|
||||
}
|
||||
|
||||
func maskSensitiveByKey(key, value string) string {
|
||||
if value == "" {
|
||||
return value
|
||||
}
|
||||
k := strings.ToLower(key)
|
||||
k = strings.ReplaceAll(k, "-", "")
|
||||
k = strings.ReplaceAll(k, "_", "")
|
||||
if _, ok := sensitiveAuditKeys[k]; ok {
|
||||
return "*****"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
80
backend/internal/logger/audit_logger_test.go
Normal file
80
backend/internal/logger/audit_logger_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAuditLogEntry_RedactsSensitiveFields(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
previous := slog.Default()
|
||||
slog.SetDefault(slog.New(slog.NewJSONHandler(buf, nil)))
|
||||
defer slog.SetDefault(previous)
|
||||
|
||||
ale := &AuditLogEntry{
|
||||
RequestID: "req-1",
|
||||
Stage: "login",
|
||||
Token: "tok-secret",
|
||||
RefreshToken: "refresh-secret",
|
||||
SessionJwt: "session-secret",
|
||||
AccessJwt: "access-secret",
|
||||
SetCookieName: "sid",
|
||||
SetCookieValue: "cookie-secret",
|
||||
ParsedCookieDSRF: "dsrf-secret",
|
||||
LoginIDs: map[string]string{
|
||||
"loginId": "user@example.com",
|
||||
},
|
||||
Query: map[string]string{
|
||||
"token": "query-token",
|
||||
"locale": "ko",
|
||||
},
|
||||
Headers: map[string]string{
|
||||
"Authorization": "Bearer secret",
|
||||
"Cookie": "session=secret",
|
||||
},
|
||||
}
|
||||
|
||||
ale.Log(slog.LevelInfo, "test")
|
||||
|
||||
line := strings.TrimSpace(buf.String())
|
||||
require.NotEmpty(t, line)
|
||||
|
||||
var payload map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(line), &payload))
|
||||
|
||||
assert.NotContains(t, payload, "token")
|
||||
assert.NotContains(t, payload, "refresh_token")
|
||||
assert.NotContains(t, payload, "session_jwt")
|
||||
assert.NotContains(t, payload, "access_jwt")
|
||||
assert.NotContains(t, payload, "set_cookie_value")
|
||||
assert.NotContains(t, payload, "parsed_cookie_DSRF")
|
||||
assert.NotContains(t, payload, "request_body")
|
||||
assert.NotContains(t, payload, "new_password")
|
||||
|
||||
assert.Equal(t, true, payload["has_token"])
|
||||
assert.Equal(t, true, payload["has_refresh_token"])
|
||||
assert.Equal(t, true, payload["has_session_jwt"])
|
||||
assert.Equal(t, true, payload["has_access_jwt"])
|
||||
assert.Equal(t, true, payload["has_set_cookie_value"])
|
||||
assert.Equal(t, true, payload["has_parsed_cookie_DSRF"])
|
||||
|
||||
loginIDs, ok := payload["login_ids"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "user@example.com", loginIDs["loginId"])
|
||||
|
||||
query, ok := payload["query"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "*****", query["token"])
|
||||
assert.Equal(t, "ko", query["locale"])
|
||||
|
||||
headers, ok := payload["headers"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "*****", headers["Authorization"])
|
||||
assert.Equal(t, "*****", headers["Cookie"])
|
||||
}
|
||||
Reference in New Issue
Block a user