package auth import ( "context" "fmt" "strings" "chat-agent-system/internal/config" "github.com/jackc/pgx/v5/pgxpool" ) // Stores 认证账户存储与会话存储组合。 type Stores struct { Auth AuthStore Sessions SessionStore } func allowInMemoryAuthFallback(cfg *config.Config) bool { if cfg == nil || !cfg.AllowInMemory { return false } env := strings.ToLower(strings.TrimSpace(cfg.Env)) return env == "dev" || env == "test" } // NewStores 根据数据库与 IAM 迁移状态选择 PG 或内存实现。 // 仅当环境明确为 dev/test 且 AllowInMemory 时允许 MemoryStore; // 环境空值、未知值、main/prod、pool 缺失或 IAM 表未就绪时 fail closed。 func NewStores(ctx context.Context, pool *pgxpool.Pool, cfg *config.Config) (*Stores, error) { if pool != nil && IAMTablesReady(ctx, pool) { pg, err := NewPgStore(pool, cfg) if err != nil { return nil, err } if err := BootstrapIAM(ctx, pool, pg, cfg); err != nil { return nil, err } return &Stores{Auth: pg, Sessions: pg}, nil } if allowInMemoryAuthFallback(cfg) { mem := NewMemoryStore(cfg) return &Stores{Auth: mem, Sessions: NewMemorySessionStore()}, nil } env := "" if cfg != nil { env = strings.TrimSpace(cfg.Env) } reason := "IAM tables not ready or database pool unavailable" if pool == nil { reason = "database pool is nil" } else if !IAMTablesReady(ctx, pool) { reason = "IAM tables not ready" } return nil, fmt.Errorf("auth store fail-closed: %s (env=%q); memory IAM fallback requires env=dev|test and ALLOW_IN_MEMORY=true", reason, env) }