mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-22 11:59:40 +01:00
Merge pull request #744 from slingamn/hostserv_offer.1
add support for offering vhosts
This commit is contained in:
commit
b329cf430e
@ -1129,6 +1129,7 @@ func (am *AccountManager) ModifyAccountSettings(account string, munger settingsM
|
|||||||
type VHostInfo struct {
|
type VHostInfo struct {
|
||||||
ApprovedVHost string
|
ApprovedVHost string
|
||||||
Enabled bool
|
Enabled bool
|
||||||
|
Forbidden bool
|
||||||
RequestedVHost string
|
RequestedVHost string
|
||||||
RejectedVHost string
|
RejectedVHost string
|
||||||
RejectionReason string
|
RejectionReason string
|
||||||
@ -1141,12 +1142,40 @@ type PendingVHostRequest struct {
|
|||||||
Account string
|
Account string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type vhostThrottleExceeded struct {
|
||||||
|
timeRemaining time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vhe *vhostThrottleExceeded) Error() string {
|
||||||
|
return fmt.Sprintf("Wait at least %v and try again", vhe.timeRemaining)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vh *VHostInfo) checkThrottle(cooldown time.Duration) (err error) {
|
||||||
|
if cooldown == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().UTC()
|
||||||
|
elapsed := now.Sub(vh.LastRequestTime)
|
||||||
|
if elapsed > cooldown {
|
||||||
|
// success
|
||||||
|
vh.LastRequestTime = now
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return &vhostThrottleExceeded{timeRemaining: cooldown - elapsed}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// callback type implementing the actual business logic of vhost operations
|
// callback type implementing the actual business logic of vhost operations
|
||||||
type vhostMunger func(input VHostInfo) (output VHostInfo, err error)
|
type vhostMunger func(input VHostInfo) (output VHostInfo, err error)
|
||||||
|
|
||||||
func (am *AccountManager) VHostSet(account string, vhost string) (result VHostInfo, err error) {
|
func (am *AccountManager) VHostSet(account string, vhost string, cooldown time.Duration) (result VHostInfo, err error) {
|
||||||
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
||||||
output = input
|
output = input
|
||||||
|
err = output.checkThrottle(cooldown)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
output.Enabled = true
|
output.Enabled = true
|
||||||
output.ApprovedVHost = vhost
|
output.ApprovedVHost = vhost
|
||||||
return
|
return
|
||||||
@ -1155,9 +1184,17 @@ func (am *AccountManager) VHostSet(account string, vhost string) (result VHostIn
|
|||||||
return am.performVHostChange(account, munger)
|
return am.performVHostChange(account, munger)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) VHostRequest(account string, vhost string) (result VHostInfo, err error) {
|
func (am *AccountManager) VHostRequest(account string, vhost string, cooldown time.Duration) (result VHostInfo, err error) {
|
||||||
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
||||||
output = input
|
output = input
|
||||||
|
// you can update your existing request, but if you were approved or rejected,
|
||||||
|
// you can't spam a new request
|
||||||
|
if output.RequestedVHost == "" {
|
||||||
|
err = output.checkThrottle(cooldown)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
output.RequestedVHost = vhost
|
output.RequestedVHost = vhost
|
||||||
output.RejectedVHost = ""
|
output.RejectedVHost = ""
|
||||||
output.RejectionReason = ""
|
output.RejectionReason = ""
|
||||||
@ -1207,6 +1244,16 @@ func (am *AccountManager) VHostSetEnabled(client *Client, enabled bool) (result
|
|||||||
return am.performVHostChange(client.Account(), munger)
|
return am.performVHostChange(client.Account(), munger)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) VHostForbid(account string, forbid bool) (result VHostInfo, err error) {
|
||||||
|
munger := func(input VHostInfo) (output VHostInfo, err error) {
|
||||||
|
output = input
|
||||||
|
output.Forbidden = forbid
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return am.performVHostChange(account, munger)
|
||||||
|
}
|
||||||
|
|
||||||
func (am *AccountManager) performVHostChange(account string, munger vhostMunger) (result VHostInfo, err error) {
|
func (am *AccountManager) performVHostChange(account string, munger vhostMunger) (result VHostInfo, err error) {
|
||||||
account, err = CasefoldName(account)
|
account, err = CasefoldName(account)
|
||||||
if err != nil || account == "" {
|
if err != nil || account == "" {
|
||||||
@ -1322,7 +1369,7 @@ func (am *AccountManager) applyVHostInfo(client *Client, info VHostInfo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vhost := ""
|
vhost := ""
|
||||||
if info.Enabled {
|
if info.Enabled && !info.Forbidden {
|
||||||
vhost = info.ApprovedVHost
|
vhost = info.ApprovedVHost
|
||||||
}
|
}
|
||||||
oldNickmask := client.NickMaskString()
|
oldNickmask := client.NickMaskString()
|
||||||
|
@ -117,6 +117,7 @@ type VHostConfig struct {
|
|||||||
Channel string
|
Channel string
|
||||||
Cooldown time.Duration
|
Cooldown time.Duration
|
||||||
} `yaml:"user-requests"`
|
} `yaml:"user-requests"`
|
||||||
|
OfferList []string `yaml:"offer-list"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NickEnforcementMethod int
|
type NickEnforcementMethod int
|
||||||
@ -796,6 +797,12 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||||||
config.Accounts.VHosts.ValidRegexp = defaultValidVhostRegex
|
config.Accounts.VHosts.ValidRegexp = defaultValidVhostRegex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, vhost := range config.Accounts.VHosts.OfferList {
|
||||||
|
if !config.Accounts.VHosts.ValidRegexp.MatchString(vhost) {
|
||||||
|
return nil, fmt.Errorf("invalid offered vhost: %s", vhost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !config.Accounts.LoginThrottling.Enabled {
|
if !config.Accounts.LoginThrottling.Enabled {
|
||||||
config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled
|
config.Accounts.LoginThrottling.MaxAttempts = 0 // limit of 0 means disabled
|
||||||
}
|
}
|
||||||
|
137
irc/hostserv.go
137
irc/hostserv.go
@ -7,7 +7,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed
|
const hostservHelp = `HostServ lets you manage your vhost (i.e., the string displayed
|
||||||
@ -121,6 +120,51 @@ for the rejection.`,
|
|||||||
maxParams: 2,
|
maxParams: 2,
|
||||||
unsplitFinalParam: true,
|
unsplitFinalParam: true,
|
||||||
},
|
},
|
||||||
|
"forbid": {
|
||||||
|
handler: hsForbidHandler,
|
||||||
|
help: `Syntax: $bFORBID <user>$b
|
||||||
|
|
||||||
|
FORBID prevents a user from using any vhost, including ones on the offer list.`,
|
||||||
|
helpShort: `$bFORBID$b prevents a user from using vhosts.`,
|
||||||
|
capabs: []string{"vhosts"},
|
||||||
|
enabled: hostservEnabled,
|
||||||
|
minParams: 1,
|
||||||
|
maxParams: 1,
|
||||||
|
},
|
||||||
|
"permit": {
|
||||||
|
handler: hsForbidHandler,
|
||||||
|
help: `Syntax: $bPERMIT <user>$b
|
||||||
|
|
||||||
|
PERMIT undoes FORBID, allowing the user to TAKE vhosts again.`,
|
||||||
|
helpShort: `$bPERMIT$b allows a user to use vhosts again.`,
|
||||||
|
capabs: []string{"vhosts"},
|
||||||
|
enabled: hostservEnabled,
|
||||||
|
minParams: 1,
|
||||||
|
maxParams: 1,
|
||||||
|
},
|
||||||
|
"offerlist": {
|
||||||
|
handler: hsOfferListHandler,
|
||||||
|
help: `Syntax: $bOFFERLIST$b
|
||||||
|
|
||||||
|
OFFERLIST lists vhosts that can be chosen without requiring operator approval;
|
||||||
|
to use one of the listed vhosts, take it with /HOSTSERV TAKE.`,
|
||||||
|
helpShort: `$bOFFERLIST$b lists vhosts that can be taken without operator approval.`,
|
||||||
|
enabled: hostservEnabled,
|
||||||
|
minParams: 0,
|
||||||
|
maxParams: 0,
|
||||||
|
},
|
||||||
|
"take": {
|
||||||
|
handler: hsTakeHandler,
|
||||||
|
help: `Syntax: $bTAKE$b <vhost>
|
||||||
|
|
||||||
|
TAKE sets your vhost to one of the vhosts in the server's offer list; to see
|
||||||
|
the offered vhosts, use /HOSTSERV OFFERLIST.`,
|
||||||
|
helpShort: `$bTAKE$b sets your vhost to one of the options from the offer list.`,
|
||||||
|
enabled: hostservEnabled,
|
||||||
|
authRequired: true,
|
||||||
|
minParams: 1,
|
||||||
|
maxParams: 1,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -168,23 +212,13 @@ func hsRequestHandler(server *Server, client *Client, command string, params []s
|
|||||||
}
|
}
|
||||||
|
|
||||||
accountName := client.Account()
|
accountName := client.Account()
|
||||||
account, err := server.accounts.LoadAccount(client.Account())
|
_, err := server.accounts.VHostRequest(accountName, vhost, server.Config().Accounts.VHosts.UserRequests.Cooldown)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hsNotice(rb, client.t("An error occurred"))
|
if throttled, ok := err.(*vhostThrottleExceeded); ok {
|
||||||
return
|
hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before making another request"), throttled.timeRemaining))
|
||||||
}
|
} else {
|
||||||
elapsed := time.Since(account.VHost.LastRequestTime)
|
hsNotice(rb, client.t("An error occurred"))
|
||||||
remainingTime := server.AccountConfig().VHosts.UserRequests.Cooldown - elapsed
|
}
|
||||||
// you can update your existing request, but if you were rejected,
|
|
||||||
// you can't spam a replacement request
|
|
||||||
if account.VHost.RequestedVHost == "" && remainingTime > 0 {
|
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before making another request"), remainingTime))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = server.accounts.VHostRequest(accountName, vhost)
|
|
||||||
if err != nil {
|
|
||||||
hsNotice(rb, client.t("An error occurred"))
|
|
||||||
} else {
|
} else {
|
||||||
hsNotice(rb, client.t("Your vhost request will be reviewed by an administrator"))
|
hsNotice(rb, client.t("Your vhost request will be reviewed by an administrator"))
|
||||||
chanMsg := fmt.Sprintf("Account %s requests vhost %s", accountName, vhost)
|
chanMsg := fmt.Sprintf("Account %s requests vhost %s", accountName, vhost)
|
||||||
@ -218,6 +252,11 @@ func hsStatusHandler(server *Server, client *Client, command string, params []st
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if account.VHost.Forbidden {
|
||||||
|
hsNotice(rb, client.t("An administrator has denied you the ability to use vhosts"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if account.VHost.ApprovedVHost != "" {
|
if account.VHost.ApprovedVHost != "" {
|
||||||
hsNotice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost))
|
hsNotice(rb, fmt.Sprintf(client.t("Account %[1]s has vhost: %[2]s"), accountName, account.VHost.ApprovedVHost))
|
||||||
if !account.VHost.Enabled {
|
if !account.VHost.Enabled {
|
||||||
@ -259,7 +298,7 @@ func hsSetHandler(server *Server, client *Client, command string, params []strin
|
|||||||
}
|
}
|
||||||
// else: command == "del", vhost == ""
|
// else: command == "del", vhost == ""
|
||||||
|
|
||||||
_, err := server.accounts.VHostSet(user, vhost)
|
_, err := server.accounts.VHostSet(user, vhost, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hsNotice(rb, client.t("An error occurred"))
|
hsNotice(rb, client.t("An error occurred"))
|
||||||
} else if vhost != "" {
|
} else if vhost != "" {
|
||||||
@ -316,3 +355,65 @@ func hsRejectHandler(server *Server, client *Client, command string, params []st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hsForbidHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
user := params[0]
|
||||||
|
forbidden := command == "forbid"
|
||||||
|
|
||||||
|
_, err := server.accounts.VHostForbid(user, forbidden)
|
||||||
|
if err == errAccountDoesNotExist {
|
||||||
|
hsNotice(rb, client.t("No such account"))
|
||||||
|
} else if err != nil {
|
||||||
|
hsNotice(rb, client.t("An error occurred"))
|
||||||
|
} else {
|
||||||
|
if forbidden {
|
||||||
|
hsNotice(rb, fmt.Sprintf(client.t("User %s is no longer allowed to use vhosts"), user))
|
||||||
|
} else {
|
||||||
|
hsNotice(rb, fmt.Sprintf(client.t("User %s is now allowed to use vhosts"), user))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hsOfferListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
vhostConfig := server.Config().Accounts.VHosts
|
||||||
|
if len(vhostConfig.OfferList) == 0 {
|
||||||
|
if vhostConfig.UserRequests.Enabled {
|
||||||
|
hsNotice(rb, client.t("The server does not offer any vhosts, but you can request one with /HOSTSERV REQUEST"))
|
||||||
|
} else {
|
||||||
|
hsNotice(rb, client.t("The server does not offer any vhosts"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hsNotice(rb, client.t("The following vhosts are available and can be chosen with /HOSTSERV TAKE:"))
|
||||||
|
for _, vhost := range vhostConfig.OfferList {
|
||||||
|
hsNotice(rb, vhost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hsTakeHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
config := server.Config()
|
||||||
|
vhost := params[0]
|
||||||
|
found := false
|
||||||
|
for _, offered := range config.Accounts.VHosts.OfferList {
|
||||||
|
if offered == vhost {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
hsNotice(rb, client.t("That vhost isn't being offered by the server"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := server.accounts.VHostSet(client.Account(), vhost, config.Accounts.VHosts.UserRequests.Cooldown)
|
||||||
|
if err != nil {
|
||||||
|
if throttled, ok := err.(*vhostThrottleExceeded); ok {
|
||||||
|
hsNotice(rb, fmt.Sprintf(client.t("You must wait an additional %v before taking a vhost"), throttled.timeRemaining))
|
||||||
|
} else {
|
||||||
|
hsNotice(rb, client.t("An error occurred"))
|
||||||
|
}
|
||||||
|
} else if vhost != "" {
|
||||||
|
hsNotice(rb, client.t("Successfully set vhost"))
|
||||||
|
} else {
|
||||||
|
hsNotice(rb, client.t("Successfully cleared vhost"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -380,6 +380,10 @@ accounts:
|
|||||||
# before they can request a new one.
|
# before they can request a new one.
|
||||||
cooldown: 168h
|
cooldown: 168h
|
||||||
|
|
||||||
|
# vhosts that users can take without approval, using `/HS TAKE`
|
||||||
|
offer-list:
|
||||||
|
#- "oragono.test"
|
||||||
|
|
||||||
# channel options
|
# channel options
|
||||||
channels:
|
channels:
|
||||||
# modes that are set when new channels are created
|
# modes that are set when new channels are created
|
||||||
|
Loading…
Reference in New Issue
Block a user