diff --git a/bridgev2/bridgeconfig/permissions.go b/bridgev2/bridgeconfig/permissions.go index 610051e0..898bf58a 100644 --- a/bridgev2/bridgeconfig/permissions.go +++ b/bridgev2/bridgeconfig/permissions.go @@ -24,6 +24,7 @@ type Permissions struct { DoublePuppet bool `yaml:"double_puppet"` Admin bool `yaml:"admin"` ManageRelay bool `yaml:"manage_relay"` + MaxLogins int `yaml:"max_logins"` } type PermissionConfig map[string]*Permissions diff --git a/bridgev2/commands/login.go b/bridgev2/commands/login.go index a18564c2..0f7bd821 100644 --- a/bridgev2/commands/login.go +++ b/bridgev2/commands/login.go @@ -70,6 +70,15 @@ func fnLogin(ce *Event) { } ce.Args = ce.Args[1:] } + if reauth == nil && ce.User.HasTooManyLogins() { + ce.Reply( + "You have reached the maximum number of logins (%d). "+ + "Please logout from an existing login before creating a new one. "+ + "If you want to re-authenticate an existing login, use the `$cmdprefix relogin` command.", + ce.User.Permissions.MaxLogins, + ) + return + } flows := ce.Bridge.Network.GetLoginFlows() var chosenFlowID string if len(ce.Args) > 0 { diff --git a/bridgev2/matrix/provisioning.go b/bridgev2/matrix/provisioning.go index 61aad869..43d19380 100644 --- a/bridgev2/matrix/provisioning.go +++ b/bridgev2/matrix/provisioning.go @@ -367,17 +367,19 @@ func (prov *ProvisioningAPI) GetCapabilities(w http.ResponseWriter, r *http.Requ } var ErrNilStep = errors.New("bridge returned nil step with no error") +var ErrTooManyLogins = bridgev2.RespError{ErrCode: "FI.MAU.BRIDGE.TOO_MANY_LOGINS", Err: "Maximum number of logins exceeded"} func (prov *ProvisioningAPI) PostLoginStart(w http.ResponseWriter, r *http.Request) { overrideLogin, failed := prov.GetExplicitLoginForRequest(w, r) if failed { return } - login, err := prov.net.CreateLogin( - r.Context(), - prov.GetUser(r), - r.PathValue("flowID"), - ) + user := prov.GetUser(r) + if overrideLogin == nil && user.HasTooManyLogins() { + ErrTooManyLogins.AppendMessage(" (%d)", user.Permissions.MaxLogins).Write(w) + return + } + login, err := prov.net.CreateLogin(r.Context(), user, r.PathValue("flowID")) if err != nil { zerolog.Ctx(r.Context()).Err(err).Msg("Failed to create login process") RespondWithError(w, err, "Internal error creating login process") diff --git a/bridgev2/user.go b/bridgev2/user.go index 87ced1d7..af9e9694 100644 --- a/bridgev2/user.go +++ b/bridgev2/user.go @@ -176,6 +176,10 @@ func (user *User) GetUserLogins() []*UserLogin { return maps.Values(user.logins) } +func (user *User) HasTooManyLogins() bool { + return user.Permissions.MaxLogins > 0 && len(user.GetUserLoginIDs()) >= user.Permissions.MaxLogins +} + func (user *User) GetFormattedUserLogins() string { user.Bridge.cacheLock.Lock() logins := make([]string, len(user.logins))