fix: add MANUAL_USER_MAPPINGS support for users with mismatched emails
JimKarvo's Gitea email (dimitris@jksoftware.gr) differs from Slack email (jimkarvo@gmail.com), so the email-based Slack lookup fails. Add MANUAL_USER_MAPPINGS env var (format: GiteaUsername:SlackID,...) to bypass email lookup when a manual mapping exists. Changes: - cache.go: ParseManualMappings() parses env var format - cache.go: CachedResolver checks manual mappings first (step 0) - main.go: reads MANUAL_USER_MAPPINGS env var, passes to resolver
This commit is contained in:
+9
-2
@@ -67,8 +67,15 @@ func main() {
|
|||||||
slackClient := slacknotifier.New(&cfg.Notification.Slack, logger)
|
slackClient := slacknotifier.New(&cfg.Notification.Slack, logger)
|
||||||
logger.Info().Msg("Slack notifier initialized")
|
logger.Info().Msg("Slack notifier initialized")
|
||||||
|
|
||||||
// Initialize cached identity resolver
|
// Initialize cached identity resolver with optional manual mappings
|
||||||
resolver := cache.NewCachedResolver(repo, emailLookup, slackClient, logger)
|
manualMappings := cache.ParseManualMappings(os.Getenv("MANUAL_USER_MAPPINGS"))
|
||||||
|
if len(manualMappings) > 0 {
|
||||||
|
logger.Info().Int("count", len(manualMappings)).Msg("manual user mappings configured")
|
||||||
|
for username := range manualMappings {
|
||||||
|
logger.Debug().Str("username", username).Msg("manual mapping registered")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolver := cache.NewCachedResolver(repo, emailLookup, slackClient, manualMappings, logger)
|
||||||
logger.Info().Msg("identity resolver initialized")
|
logger.Info().Msg("identity resolver initialized")
|
||||||
|
|
||||||
// Create processor adapter that implements webhook.EventHandler
|
// Create processor adapter that implements webhook.EventHandler
|
||||||
|
|||||||
Vendored
+54
-8
@@ -12,14 +12,40 @@ import (
|
|||||||
"github.com/vincentc-afk/gitea-notification-hub/internal/storage"
|
"github.com/vincentc-afk/gitea-notification-hub/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ParseManualMappings parses a MANUAL_USER_MAPPINGS env var string
|
||||||
|
// Format: "GiteaUsername:SlackID,GiteaUser2:SlackID2"
|
||||||
|
func ParseManualMappings(raw string) map[string]string {
|
||||||
|
mappings := make(map[string]string)
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if raw == "" {
|
||||||
|
return mappings
|
||||||
|
}
|
||||||
|
for _, pair := range strings.Split(raw, ",") {
|
||||||
|
pair = strings.TrimSpace(pair)
|
||||||
|
if pair == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(pair, ":", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
username := strings.TrimSpace(parts[0])
|
||||||
|
slackID := strings.TrimSpace(parts[1])
|
||||||
|
if username != "" && slackID != "" {
|
||||||
|
mappings[username] = slackID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mappings
|
||||||
|
}
|
||||||
|
|
||||||
// CachedResolver implements identity.Resolver with caching
|
// CachedResolver implements identity.Resolver with caching
|
||||||
// It stores resolved identities in the database and only queries
|
// It stores resolved identities in the database and only queries
|
||||||
// external APIs when a user is not found in the cache
|
// external APIs when a user is not found in the cache
|
||||||
type CachedResolver struct {
|
type CachedResolver struct {
|
||||||
repo storage.Repository
|
repo storage.Repository
|
||||||
emailLookup identity.EmailLookup
|
emailLookup identity.EmailLookup
|
||||||
slackLookup identity.SlackLookup
|
slackLookup identity.SlackLookup
|
||||||
logger zerolog.Logger
|
manualMappings map[string]string // GiteaUsername -> SlackID
|
||||||
|
logger zerolog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCachedResolver creates a new cached identity resolver
|
// NewCachedResolver creates a new cached identity resolver
|
||||||
@@ -27,18 +53,24 @@ func NewCachedResolver(
|
|||||||
repo storage.Repository,
|
repo storage.Repository,
|
||||||
emailLookup identity.EmailLookup,
|
emailLookup identity.EmailLookup,
|
||||||
slackLookup identity.SlackLookup,
|
slackLookup identity.SlackLookup,
|
||||||
|
manualMappings map[string]string,
|
||||||
logger zerolog.Logger,
|
logger zerolog.Logger,
|
||||||
) *CachedResolver {
|
) *CachedResolver {
|
||||||
|
if manualMappings == nil {
|
||||||
|
manualMappings = make(map[string]string)
|
||||||
|
}
|
||||||
return &CachedResolver{
|
return &CachedResolver{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
emailLookup: emailLookup,
|
emailLookup: emailLookup,
|
||||||
slackLookup: slackLookup,
|
slackLookup: slackLookup,
|
||||||
logger: logger.With().Str("component", "identity-resolver").Logger(),
|
manualMappings: manualMappings,
|
||||||
|
logger: logger.With().Str("component", "identity-resolver").Logger(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve returns the external identity for a Gitea user
|
// Resolve returns the external identity for a Gitea user
|
||||||
// It follows this strategy:
|
// It follows this strategy:
|
||||||
|
// 0. Check manual mappings (MANUAL_USER_MAPPINGS env var) - highest priority
|
||||||
// 1. Check DB by Gitea username - if found with Slack ID, return cached result
|
// 1. Check DB by Gitea username - if found with Slack ID, return cached result
|
||||||
// 2. If not found, use Gitea API to get the real email (not the webhook email which may be noreply)
|
// 2. If not found, use Gitea API to get the real email (not the webhook email which may be noreply)
|
||||||
// 3. Lookup Slack by email
|
// 3. Lookup Slack by email
|
||||||
@@ -49,6 +81,20 @@ func (r *CachedResolver) Resolve(ctx context.Context, user event.User) (*identit
|
|||||||
Str("webhook_email", user.Email).
|
Str("webhook_email", user.Email).
|
||||||
Logger()
|
Logger()
|
||||||
|
|
||||||
|
// Step 0: Check manual mappings first (highest priority)
|
||||||
|
if user.GiteaUsername != "" {
|
||||||
|
if slackID, ok := r.manualMappings[user.GiteaUsername]; ok {
|
||||||
|
logger.Info().
|
||||||
|
Str("slack_id", slackID).
|
||||||
|
Msg("resolved user via manual mapping")
|
||||||
|
return &identity.ResolvedIdentity{
|
||||||
|
Email: user.Email,
|
||||||
|
SlackID: slackID,
|
||||||
|
SlackName: "",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Step 1: Try to find by Gitea username in cache
|
// Step 1: Try to find by Gitea username in cache
|
||||||
if user.GiteaUsername != "" {
|
if user.GiteaUsername != "" {
|
||||||
dbUser, err := r.repo.GetUserByGiteaUsername(ctx, user.GiteaUsername)
|
dbUser, err := r.repo.GetUserByGiteaUsername(ctx, user.GiteaUsername)
|
||||||
|
|||||||
Reference in New Issue
Block a user