package auth import ( "context" "errors" "fmt" "time" "trankilou.fr/lassistanoque/backend/internal/domain" ) var ( ErrRegistrationNotAllowed = errors.New("registration not allowed") ErrMethodUnknown = errors.New("auth method unknown") ErrUnauthorized = errors.New("unauthorized") ) type Service struct { authenticators map[string]Authenticator // "password" -> ..., "oidc" -> ... oidcManager OIDCManager tokenManager TokenManager userRepository domain.UserRepository oidcRepository domain.OIDCProviderRepository settingsRepository domain.SettingsRepository } func NewService( settingsRepository domain.SettingsRepository, userRepository domain.UserRepository, oidcRepository domain.OIDCProviderRepository, tokenManager TokenManager, authenticators map[string]Authenticator, ) *Service { service := &Service{ authenticators: authenticators, tokenManager: tokenManager, userRepository: userRepository, oidcRepository: oidcRepository, settingsRepository: settingsRepository, } if manager, ok := authenticators["oidc"].(OIDCManager); ok { service.oidcManager = manager } return service } func (s *Service) Login(ctx context.Context, creds Credentials) (*Session, error) { authn, ok := s.authenticators[creds.Method] if !ok { return nil, fmt.Errorf("auth method unsupported: %s", creds.Method) } return authn.Authenticate(ctx, creds) } func (s *Service) Register(registration Registration) (*Session, error) { settings, err := s.settingsRepository.GetSettings() if err != nil { return nil, err } if !settings.RegisterEnabled { return nil, ErrRegistrationNotAllowed } if authenticator, ok := s.authenticators[registration.Method]; ok { session, err := authenticator.Register(context.Background(), registration) if err != nil { return nil, err } now := time.Now() _, err = s.userRepository.CreateTeam(session.User.ID, &domain.Team{ Label: "Espace personnel", DateCreated: now, }) if err != nil { return nil, err } return session, nil } else { return nil, ErrMethodUnknown } } func (s *Service) Status(session *Session) (*domain.User, error) { return nil, nil } // ListOIDCProviders retourne les fournisseurs OpenID Connect enregistrés, // sans exposer le secret client (champ marqué json:"-"). func (s *Service) ListOIDCProviders() ([]*domain.OIDCProvider, error) { return s.oidcRepository.ListOIDCProviders() } // OIDCAuthorizeURL construit l'URL d'autorisation du fournisseur, avec un // state signé transportant l'id du fournisseur. func (s *Service) OIDCAuthorizeURL(ctx context.Context, providerID string, redirectURI string) (string, error) { provider, err := s.oidcRepository.FindOIDCProvider(providerID) if err != nil { return "", fmt.Errorf("unknown oidc provider: %s", providerID) } state, err := s.tokenManager.GenerateStateToken(providerID) if err != nil { return "", err } return s.oidcManager.AuthorizeURL(ctx, provider, redirectURI, state) } // OIDCCallback traite le retour du fournisseur: vérifie le state, échange le // code d'autorisation et délivre une session applicative. func (s *Service) OIDCCallback(ctx context.Context, state string, code string, redirectURI string) (*Session, error) { providerID, err := s.tokenManager.ParseStateToken(state) if err != nil { return nil, fmt.Errorf("invalid oidc state: %s", err) } provider, err := s.oidcRepository.FindOIDCProvider(providerID) if err != nil { return nil, fmt.Errorf("unknown oidc provider: %s", providerID) } return s.oidcManager.ExchangeCode(ctx, provider, code, redirectURI) }