To gitea and beyond, let's go(-yco)

This commit is contained in:
2025-11-10 19:12:09 +01:00
parent 8f6133392d
commit 71a031342b
245 changed files with 83994 additions and 0 deletions

View File

@@ -0,0 +1,175 @@
package database
import (
"context"
"fmt"
"log"
"os"
"regexp"
"strings"
"time"
"gorm.io/gorm/logger"
)
type SecureLogger struct {
writer logger.Writer
config logger.Config
sensitiveFields []string
sensitivePattern *regexp.Regexp
productionMode bool
}
func NewSecureLogger(writer logger.Writer, config logger.Config, productionMode bool) *SecureLogger {
sensitiveFields := []string{
"password", "token", "secret", "key", "hash", "salt",
"email_verification_token", "password_reset_token",
"token_hash", "jwt_secret", "api_key", "access_token",
"refresh_token", "session_id", "cookie", "auth",
}
sensitivePattern := regexp.MustCompile(`(?i)(password|token|secret|key|hash|salt|email_verification_token|password_reset_token|token_hash|jwt_secret|api_key|access_token|refresh_token|session_id|cookie|auth)`)
return &SecureLogger{
writer: writer,
config: config,
sensitiveFields: sensitiveFields,
sensitivePattern: sensitivePattern,
productionMode: productionMode,
}
}
func (l *SecureLogger) LogMode(level logger.LogLevel) logger.Interface {
newLogger := *l
newLogger.config.LogLevel = level
return &newLogger
}
func (l *SecureLogger) Info(ctx context.Context, msg string, data ...any) {
if l.config.LogLevel >= logger.Info {
l.log(ctx, "info", msg, data...)
}
}
func (l *SecureLogger) Warn(ctx context.Context, msg string, data ...any) {
if l.config.LogLevel >= logger.Warn {
l.log(ctx, "warn", msg, data...)
}
}
func (l *SecureLogger) Error(ctx context.Context, msg string, data ...any) {
if l.config.LogLevel >= logger.Error {
l.log(ctx, "error", msg, data...)
}
}
func (l *SecureLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
if l.config.LogLevel <= logger.Silent {
return
}
elapsed := time.Since(begin)
switch {
case err != nil && l.config.LogLevel >= logger.Error && (!l.config.IgnoreRecordNotFoundError || !IsRecordNotFoundError(err)):
sql, rows := fc()
l.log(ctx, "error", fmt.Sprintf("[%.3fms] [rows:%v] %s", float64(elapsed.Nanoseconds())/1e6, rows, sql))
case elapsed > l.config.SlowThreshold && l.config.SlowThreshold != 0 && l.config.LogLevel >= logger.Warn:
sql, rows := fc()
l.log(ctx, "warn", fmt.Sprintf("[%.3fms] [rows:%v] %s", float64(elapsed.Nanoseconds())/1e6, rows, sql))
case l.config.LogLevel == logger.Info:
sql, rows := fc()
l.log(ctx, "info", fmt.Sprintf("[%.3fms] [rows:%v] %s", float64(elapsed.Nanoseconds())/1e6, rows, sql))
}
}
func (l *SecureLogger) log(_ context.Context, level, msg string, data ...any) {
if l.productionMode {
msg = l.maskSensitiveData(msg)
maskedData := make([]any, len(data))
for i, d := range data {
maskedData[i] = l.maskSensitiveData(fmt.Sprintf("%v", d))
}
data = maskedData
}
formattedMsg := fmt.Sprintf(msg, data...)
l.writer.Printf("[%s] %s", strings.ToUpper(level), formattedMsg)
}
func (l *SecureLogger) maskSensitiveData(data string) string {
if l.productionMode {
data = regexp.MustCompile(`\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b`).ReplaceAllString(data, "[EMAIL_MASKED]")
data = regexp.MustCompile(`\b[A-Za-z0-9]{20,}\b`).ReplaceAllStringFunc(data, func(match string) string {
if l.sensitivePattern.MatchString(match) {
return "[TOKEN_MASKED]"
}
return match
})
data = l.maskSQLValues(data)
}
return data
}
func (l *SecureLogger) maskSQLValues(sql string) string {
paramPattern := regexp.MustCompile(`'([^']*)'`)
return paramPattern.ReplaceAllStringFunc(sql, func(match string) string {
value := strings.Trim(match, "'")
if l.isSensitiveValue(value) {
return "'[MASKED]'"
}
return match
})
}
func (l *SecureLogger) isSensitiveValue(value string) bool {
if regexp.MustCompile(`\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b`).MatchString(value) {
return true
}
if len(value) > 20 && regexp.MustCompile(`^[A-Za-z0-9+/]{20,}={0,2}$`).MatchString(value) {
return true
}
if regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`).MatchString(value) {
return true
}
if regexp.MustCompile(`^[A-Za-z0-9+/]+={0,2}$`).MatchString(value) && len(value) > 10 {
return true
}
return false
}
func IsRecordNotFoundError(err error) bool {
if err == nil {
return false
}
return strings.Contains(strings.ToLower(err.Error()), "record not found") ||
strings.Contains(strings.ToLower(err.Error()), "not found")
}
func CreateSecureLogger(productionMode bool) logger.Interface {
config := logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Info,
IgnoreRecordNotFoundError: true,
Colorful: false,
}
if productionMode {
config.LogLevel = logger.Error
config.SlowThreshold = 2 * time.Second
}
writer := log.New(os.Stdout, "\r\n", log.LstdFlags)
return NewSecureLogger(writer, config, productionMode)
}