package auth import ( "crypto/rand" "encoding/hex" "errors" "os" "strings" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" "server/internal/models" ) const staffCtxKey = "staffId" type StaffClaims struct { StaffID uint `json:"staffId"` Email string `json:"email"` jwt.RegisteredClaims } func JWTSecret() string { if s := os.Getenv("JWT_SECRET"); s != "" { return s } return "simple-care-staff-jwt-change-me-in-production" } func HashPassword(password string) (string, error) { b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) return string(b), err } func CheckPassword(hash, password string) bool { return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil } func RandomPassword(n int) string { if n < 8 { n = 8 } b := make([]byte, n) _, _ = rand.Read(b) return hex.EncodeToString(b)[:n] } func RandomToken(n int) string { b := make([]byte, n) _, _ = rand.Read(b) return hex.EncodeToString(b) } func IssueToken(staff *models.StaffAccount) (string, error) { claims := StaffClaims{ StaffID: staff.ID, Email: staff.Email, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), }, } t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return t.SignedString([]byte(JWTSecret())) } func ParseToken(tokenStr string) (*StaffClaims, error) { t, err := jwt.ParseWithClaims(tokenStr, &StaffClaims{}, func(t *jwt.Token) (any, error) { return []byte(JWTSecret()), nil }) if err != nil { return nil, err } claims, ok := t.Claims.(*StaffClaims) if !ok || !t.Valid { return nil, errors.New("invalid token") } return claims, nil } func EmailDomainPart(email string) string { parts := strings.Split(strings.ToLower(strings.TrimSpace(email)), "@") if len(parts) != 2 { return "" } return parts[1] } func IsEmailDomainAllowed(db *gorm.DB, email string) bool { domain := EmailDomainPart(email) if domain == "" { return false } var count int64 db.Model(&models.EmailDomain{}).Where("is_active = ?", true).Count(&count) if count == 0 { return true } var found int64 db.Model(&models.EmailDomain{}).Where("domain = ? AND is_active = ?", domain, true).Count(&found) return found > 0 }