1
0
forked from baron/baron-sso

린트 적용

This commit is contained in:
2026-01-27 15:37:00 +09:00
parent ce86aee5fb
commit c914fad405
5 changed files with 69 additions and 65 deletions

View File

@@ -8,10 +8,10 @@ import (
// BrokerUser is the standard user model used within Baron SSO business logic. // BrokerUser is the standard user model used within Baron SSO business logic.
// It defines the canonical set of fields that must be supported by any underlying IDP. // It defines the canonical set of fields that must be supported by any underlying IDP.
type BrokerUser struct { type BrokerUser struct {
ID string `json:"id" required:"true"` ID string `json:"id" required:"true"`
Email string `json:"email" required:"true"` Email string `json:"email" required:"true"`
Name string `json:"name"` Name string `json:"name"`
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
// Attributes stores custom user attributes. // Attributes stores custom user attributes.
// The "required_keys" tag specifies which keys MUST be present in the IDP's schema support. // The "required_keys" tag specifies which keys MUST be present in the IDP's schema support.
Attributes map[string]interface{} `json:"attributes" required_keys:"grade,department"` Attributes map[string]interface{} `json:"attributes" required_keys:"grade,department"`

View File

@@ -35,14 +35,14 @@ const (
statusSuccess = "success" statusSuccess = "success"
// Durations // Durations
defaultExpiration = 5 * time.Minute defaultExpiration = 5 * time.Minute
signupStateExpiration = 10 * time.Minute signupStateExpiration = 10 * time.Minute
signupBlockDuration = 10 * time.Minute signupBlockDuration = 10 * time.Minute
maxSignupFailures = 5 maxSignupFailures = 5
emailCodeTTL = 5 * time.Minute emailCodeTTL = 5 * time.Minute
smsCodeTTL = 3 * time.Minute smsCodeTTL = 3 * time.Minute
prefixPwdResetToken = "pwdreset_token:" prefixPwdResetToken = "pwdreset_token:"
pwdResetExpiration = 15 * time.Minute pwdResetExpiration = 15 * time.Minute
) )
type AuthHandler struct { type AuthHandler struct {
@@ -165,12 +165,12 @@ func (h *AuthHandler) SendSignupEmailCode(c *fiber.Ctx) error {
Code: code, Code: code,
Verified: false, Verified: false,
FailCount: 0, // Reset fail count on new code generation? Or keep it? FailCount: 0, // Reset fail count on new code generation? Or keep it?
// Requirement says "Auth fail > 5 -> block". New code usually resets or continues? // Requirement says "Auth fail > 5 -> block". New code usually resets or continues?
// Usually getting a new code doesn't reset verify failure count if we want strict blocking. // Usually getting a new code doesn't reset verify failure count if we want strict blocking.
// But for simplicity let's say "fail count" applies to verification attempts. // But for simplicity let's say "fail count" applies to verification attempts.
// If we are issuing a new code, it's a new attempt cycle usually. // If we are issuing a new code, it's a new attempt cycle usually.
// However, spamming "send code" is also an attack. // However, spamming "send code" is also an attack.
// Let's keep FailCount if exists, or 0. // Let's keep FailCount if exists, or 0.
ExpiresAt: time.Now().Add(emailCodeTTL).Unix(), ExpiresAt: time.Now().Add(emailCodeTTL).Unix(),
} }
if state != nil { if state != nil {
@@ -312,10 +312,18 @@ func (h *AuthHandler) Signup(c *fiber.Ctx) error {
} }
// Check complexity (at least 2 types: lower, upper, digit, special) // Check complexity (at least 2 types: lower, upper, digit, special)
types := 0 types := 0
if strings.ContainsAny(req.Password, "abcdefghijklmnopqrstuvwxyz") { types++ } if strings.ContainsAny(req.Password, "abcdefghijklmnopqrstuvwxyz") {
if strings.ContainsAny(req.Password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") { types++ } types++
if strings.ContainsAny(req.Password, "0123456789") { types++ } }
if strings.ContainsAny(req.Password, "!@#$%^&*()_+-=[]{}|;:,.<>?") { types++ } if strings.ContainsAny(req.Password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
types++
}
if strings.ContainsAny(req.Password, "0123456789") {
types++
}
if strings.ContainsAny(req.Password, "!@#$%^&*()_+-=[]{}|;:,.<>?") {
types++
}
if types < 2 { if types < 2 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Password must contain at least 2 types of characters (letters, numbers, symbols)"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Password must contain at least 2 types of characters (letters, numbers, symbols)"})
} }
@@ -1305,8 +1313,6 @@ func (h *AuthHandler) HandleDescopeEmailRelay(c *fiber.Ctx) error {
return c.Status(501).JSON(fiber.Map{"error": "Real email sending not implemented"}) return c.Status(501).JSON(fiber.Map{"error": "Real email sending not implemented"})
} }
// --- User Profile Handlers --- // --- User Profile Handlers ---
func (h *AuthHandler) formatPhoneForDisplay(phone string) string { func (h *AuthHandler) formatPhoneForDisplay(phone string) string {

View File

@@ -28,8 +28,8 @@ type AuditLogEntry struct {
LoginIDs map[string]string // loginId and loginId_normalized LoginIDs map[string]string // loginId and loginId_normalized
Token string // For reset tokens, magic link tokens Token string // For reset tokens, magic link tokens
DescopeError string DescopeError string
DescopeStatus int // Descope HTTP status DescopeStatus int // Descope HTTP status
DescopeBody string // Descope response body (full raw) DescopeBody string // Descope response body (full raw)
RefreshToken string RefreshToken string
SessionJwt string SessionJwt string
AccessJwt string AccessJwt string
@@ -68,7 +68,6 @@ func NewAuditLogEntry(c *fiber.Ctx, stage string) *AuditLogEntry {
headers["Origin"] = c.Get("Origin") headers["Origin"] = c.Get("Origin")
headers["Referer"] = c.Get("Referer") headers["Referer"] = c.Get("Referer")
return &AuditLogEntry{ return &AuditLogEntry{
RequestID: reqID, RequestID: reqID,
Stage: stage, Stage: stage,
@@ -85,7 +84,6 @@ func NewAuditLogEntry(c *fiber.Ctx, stage string) *AuditLogEntry {
} }
} }
// Log emits an audit log entry using slog. // Log emits an audit log entry using slog.
// It includes common fields and allows for additional custom fields. // It includes common fields and allows for additional custom fields.
func (ale *AuditLogEntry) Log(level slog.Level, msg string, args ...any) { func (ale *AuditLogEntry) Log(level slog.Level, msg string, args ...any) {

View File

@@ -70,28 +70,28 @@ func (d *DescopeProvider) GetMetadata() (*domain.IDPMetadata, error) {
} }
func (d *DescopeProvider) InitiatePasswordReset(loginID, redirectUrl string) error { func (d *DescopeProvider) InitiatePasswordReset(loginID, redirectUrl string) error {
ctx := context.Background() ctx := context.Background()
err := d.Client.Auth.Password().SendPasswordReset(ctx, loginID, redirectUrl, nil) err := d.Client.Auth.Password().SendPasswordReset(ctx, loginID, redirectUrl, nil)
if err != nil { if err != nil {
slog.Error("Descope SendPasswordReset failed (raw)", slog.Error("Descope SendPasswordReset failed (raw)",
"loginID", loginID, "loginID", loginID,
"redirectUrl", redirectUrl, "redirectUrl", redirectUrl,
"err", err, "err", err,
"err_type", fmt.Sprintf("%T", err), "err_type", fmt.Sprintf("%T", err),
) )
if de, ok := err.(*descope.Error); ok { if de, ok := err.(*descope.Error); ok {
status := de.Info[descope.ErrorInfoKeys.HTTPResponseStatusCode] // "Status-Code" status := de.Info[descope.ErrorInfoKeys.HTTPResponseStatusCode] // "Status-Code"
slog.Error("Descope error details", slog.Error("Descope error details",
"code", de.Code, "code", de.Code,
"description", de.Description, "description", de.Description,
"message", de.Message, "message", de.Message,
"status_code", status, "status_code", status,
"info", de.Info, "info", de.Info,
) )
} }
} }
return err return err
} }
func (d *DescopeProvider) VerifyPasswordResetToken(token string) (*domain.AuthInfo, error) { func (d *DescopeProvider) VerifyPasswordResetToken(token string) (*domain.AuthInfo, error) {