mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-06 08:01:24 +00:00
183 lines
8.0 KiB
Go
183 lines
8.0 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/warmbly/warmbly/internal/app/organization"
|
|
"github.com/warmbly/warmbly/internal/app/token"
|
|
"github.com/warmbly/warmbly/internal/app/trial"
|
|
"github.com/warmbly/warmbly/internal/app/user"
|
|
"github.com/warmbly/warmbly/internal/config"
|
|
"github.com/warmbly/warmbly/internal/errx"
|
|
"github.com/warmbly/warmbly/internal/infrastructure/cache"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
"github.com/warmbly/warmbly/internal/notify"
|
|
"github.com/warmbly/warmbly/internal/pkg/captcha"
|
|
"github.com/warmbly/warmbly/internal/repository"
|
|
)
|
|
|
|
// TwoFAChallenger issues + checks the 2FA login challenge after the email-code
|
|
// step. Satisfied by *twofa.Service; injected post-construction (WireTwoFA) so
|
|
// the auth package needs no import of twofa (no cycle).
|
|
type TwoFAChallenger interface {
|
|
IsEnabled(ctx context.Context, userID uuid.UUID) (bool, error)
|
|
CreatePendingChallenge(ctx context.Context, userID uuid.UUID) (string, int, *errx.Error)
|
|
}
|
|
|
|
// ReferralAttributor links a brand-new org to the referrer behind its signup
|
|
// code. Satisfied by *referral.Service; injected post-construction (WireReferral)
|
|
// so the auth package needs no import of referral (no cycle).
|
|
type ReferralAttributor interface {
|
|
AttributeSignup(ctx context.Context, code string, inviteeOrgID, inviteeUserID uuid.UUID) *errx.Error
|
|
}
|
|
|
|
// InstanceSettings is the operator-editable half of the signup policy.
|
|
// Satisfied by instancesettings.Service; injected post-construction
|
|
// (WireInstanceSettings) so the auth package needs no import of it (no cycle).
|
|
type InstanceSettings interface {
|
|
AllowInvitedSignup(ctx context.Context) bool
|
|
}
|
|
|
|
type AuthService interface {
|
|
LoginStart(ctx context.Context, data *AuthData, ipaddr, userAgent string) (*models.AuthSession, *errx.Error)
|
|
LoginConfirm(ctx context.Context, data *ConfirmData, session, ipaddr, userAgent string) (*models.LoginResult, *errx.Error)
|
|
// WireTwoFA attaches the 2FA challenger (post-construction; nil = 2FA off).
|
|
WireTwoFA(t TwoFAChallenger)
|
|
|
|
RegistrationStart(ctx context.Context, data *AuthData, ipaddr string) (*models.AuthSession, *errx.Error)
|
|
RegistrationConfirm(ctx context.Context, data *ConfirmData, session, ipaddr string) *errx.Error
|
|
// WireReferral attaches the referral attributor (post-construction; nil = no
|
|
// referral attribution at signup).
|
|
WireReferral(r ReferralAttributor)
|
|
|
|
// WireInstanceSettings attaches the database-backed instance settings
|
|
// (post-construction; nil keeps the permissive defaults).
|
|
WireInstanceSettings(s InstanceSettings)
|
|
|
|
// Native-app social sign-in: the client authenticates with the provider on
|
|
// device and exchanges the resulting ID token for a session. First sign-in
|
|
// provisions the account (org + trial) like password registration.
|
|
AppleIDTokenAuth(ctx context.Context, rawToken, firstName, lastName, ipaddr, userAgent string) (*models.LoginResult, *errx.Error)
|
|
GoogleIDTokenAuth(ctx context.Context, rawToken, ipaddr, userAgent string) (*models.LoginResult, *errx.Error)
|
|
// WireExternalIDTokens attaches the ID-token verifiers (post-construction;
|
|
// a nil verifier disables that provider).
|
|
WireExternalIDTokens(apple, google IDTokenVerifier)
|
|
|
|
ResetPasswordStart(ctx context.Context, data *ResetPasswordStart, ipaddr string) *errx.Error
|
|
ResetPasswordConfirm(ctx context.Context, data *ResetPasswordConfirm, session, ipaddr string) *errx.Error
|
|
|
|
// ChangePassword updates a logged-in user's password after verifying the
|
|
// current one.
|
|
ChangePassword(ctx context.Context, userID, currentSessionID uuid.UUID, data *ChangePassword) *errx.Error
|
|
|
|
// Policy is the resolved per-deployment auth behavior, exposed so the
|
|
// public /auth/config endpoint can report it to the login screen.
|
|
Policy() *config.AuthPolicy
|
|
|
|
// RegistrationMode is Policy().Registration with the first-launch
|
|
// exemption applied, so a brand new instance advertises open signups.
|
|
RegistrationMode(ctx context.Context) string
|
|
|
|
// WireDeployment attaches the deployment-level facts auth decisions depend
|
|
// on: the resolved policy and whether the mail transport actually delivers.
|
|
WireDeployment(policy *config.AuthPolicy, mailDelivers bool)
|
|
|
|
// WireIdentities attaches the federated-identity store, so external
|
|
// sign-in resolves accounts by (issuer, subject) instead of email.
|
|
WireIdentities(r repository.IdentityRepository)
|
|
|
|
// Generic OIDC. WireOIDC attaches the provider (nil = OIDC disabled).
|
|
WireOIDC(p OIDCProvider)
|
|
OIDCBegin(ctx context.Context) (*OIDCRedirect, *errx.Error)
|
|
// OIDCCallback returns a single-use handoff code, not a session: the
|
|
// provider redirects a browser here, so the response must be a redirect.
|
|
OIDCCallback(ctx context.Context, code, state, ipaddr, userAgent string) (string, *errx.Error)
|
|
OIDCExchange(ctx context.Context, code string) (*models.LoginResult, *errx.Error)
|
|
}
|
|
|
|
type authService struct {
|
|
authRepository repository.AuthRepository
|
|
userRepository repository.UserRepository
|
|
tokenService token.TokenService
|
|
userService user.UserService
|
|
trialService trial.TrialService
|
|
organizationService organization.OrganizationService
|
|
emailNotificationService notify.EmailNotificationService
|
|
cache *cache.Cache
|
|
captcha *captcha.Turnstile
|
|
externalAuth *models.ExternalAuth
|
|
appleIDTokens IDTokenVerifier
|
|
googleIDTokens IDTokenVerifier
|
|
twofa TwoFAChallenger
|
|
referral ReferralAttributor
|
|
// settings is the operator-editable settings document, wired after
|
|
// construction because it needs the database pool.
|
|
settings InstanceSettings
|
|
// identities binds federated logins to (issuer, subject). Nil-safe: an
|
|
// unwired repository falls back to the historic email-only matching, which
|
|
// is only safe for Apple and Google.
|
|
identities repository.IdentityRepository
|
|
// oidc is the generic OpenID Connect provider, nil unless configured.
|
|
oidc OIDCProvider
|
|
|
|
// policy and mailDelivers govern whether an emailed code gates a login and
|
|
// whether public signups are open. Defaults are set in NewService so a
|
|
// caller that never wires them still behaves like the historic flow.
|
|
policy *config.AuthPolicy
|
|
mailDelivers bool
|
|
}
|
|
|
|
func (s *authService) WireTwoFA(t TwoFAChallenger) { s.twofa = t }
|
|
|
|
func (s *authService) WireDeployment(policy *config.AuthPolicy, mailDelivers bool) {
|
|
if policy != nil {
|
|
s.policy = policy
|
|
}
|
|
s.mailDelivers = mailDelivers
|
|
}
|
|
|
|
func (s *authService) Policy() *config.AuthPolicy { return s.policy }
|
|
|
|
func (s *authService) WireIdentities(r repository.IdentityRepository) { s.identities = r }
|
|
|
|
func (s *authService) WireOIDC(p OIDCProvider) { s.oidc = p }
|
|
|
|
func (s *authService) WireReferral(r ReferralAttributor) { s.referral = r }
|
|
|
|
// WireInstanceSettings attaches the instance settings document, so the signup
|
|
// knobs on the admin page reach the registration paths.
|
|
func (s *authService) WireInstanceSettings(set InstanceSettings) { s.settings = set }
|
|
|
|
func NewService(
|
|
authRepository repository.AuthRepository,
|
|
cache *cache.Cache,
|
|
captcha *captcha.Turnstile,
|
|
tokenService token.TokenService,
|
|
emailNotificationService notify.EmailNotificationService,
|
|
externalAuthData *models.ExternalAuth,
|
|
trialService trial.TrialService,
|
|
organizationService organization.OrganizationService,
|
|
userRepository repository.UserRepository,
|
|
userService user.UserService,
|
|
) AuthService {
|
|
return &authService{
|
|
authRepository: authRepository,
|
|
tokenService: tokenService,
|
|
emailNotificationService: emailNotificationService,
|
|
cache: cache,
|
|
captcha: captcha,
|
|
externalAuth: externalAuthData,
|
|
trialService: trialService,
|
|
organizationService: organizationService,
|
|
userRepository: userRepository,
|
|
userService: userService,
|
|
// Overwritten by WireDeployment during boot. The default assumes a
|
|
// delivering transport so an unwired service keeps the historic
|
|
// always-email-a-code behavior rather than silently relaxing it.
|
|
policy: config.LoadAuthPolicy(true),
|
|
mailDelivers: true,
|
|
}
|
|
}
|