mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-23 03:02:48 +01:00
fix #1274
Enhancements to NS SUSPEND, including stored metadata and the ability to list suspensions
This commit is contained in:
parent
dc456bd6a4
commit
1f6afa31d6
105
irc/accounts.go
105
irc/accounts.go
@ -42,6 +42,7 @@ const (
|
|||||||
keyAccountLastSeen = "account.lastseen %s"
|
keyAccountLastSeen = "account.lastseen %s"
|
||||||
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
keyAccountModes = "account.modes %s" // user modes for the always-on client as a string
|
||||||
keyAccountRealname = "account.realname %s" // client realname stored as string
|
keyAccountRealname = "account.realname %s" // client realname stored as string
|
||||||
|
keyAccountSuspended = "account.suspended %s" // client realname stored as string
|
||||||
|
|
||||||
maxCertfpsPerAccount = 5
|
maxCertfpsPerAccount = 5
|
||||||
)
|
)
|
||||||
@ -117,7 +118,7 @@ func (am *AccountManager) createAlwaysOnClients(config *Config) {
|
|||||||
|
|
||||||
for _, accountName := range accounts {
|
for _, accountName := range accounts {
|
||||||
account, err := am.LoadAccount(accountName)
|
account, err := am.LoadAccount(accountName)
|
||||||
if err == nil && account.Verified &&
|
if err == nil && (account.Verified && account.Suspended == nil) &&
|
||||||
persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, account.Settings.AlwaysOn) {
|
persistenceEnabled(config.Accounts.Multiclient.AlwaysOn, account.Settings.AlwaysOn) {
|
||||||
am.server.AddAlwaysOnClient(
|
am.server.AddAlwaysOnClient(
|
||||||
account,
|
account,
|
||||||
@ -1035,6 +1036,9 @@ func (am *AccountManager) checkPassphrase(accountName, passphrase string) (accou
|
|||||||
if !account.Verified {
|
if !account.Verified {
|
||||||
err = errAccountUnverified
|
err = errAccountUnverified
|
||||||
return
|
return
|
||||||
|
} else if account.Suspended != nil {
|
||||||
|
err = errAccountSuspended
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch account.Credentials.Version {
|
switch account.Credentials.Version {
|
||||||
@ -1230,6 +1234,15 @@ func (am *AccountManager) deserializeRawAccount(raw rawClientAccount, cfName str
|
|||||||
am.server.logger.Warning("internal", "could not unmarshal settings for account", result.Name, e.Error())
|
am.server.logger.Warning("internal", "could not unmarshal settings for account", result.Name, e.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if raw.Suspended != "" {
|
||||||
|
sus := new(AccountSuspension)
|
||||||
|
e := json.Unmarshal([]byte(raw.Suspended), sus)
|
||||||
|
if e != nil {
|
||||||
|
am.server.logger.Error("internal", "corrupt suspension data", result.Name, e.Error())
|
||||||
|
} else {
|
||||||
|
result.Suspended = sus
|
||||||
|
}
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1243,6 +1256,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
|||||||
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
nicksKey := fmt.Sprintf(keyAccountAdditionalNicks, casefoldedAccount)
|
||||||
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
vhostKey := fmt.Sprintf(keyAccountVHost, casefoldedAccount)
|
||||||
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
settingsKey := fmt.Sprintf(keyAccountSettings, casefoldedAccount)
|
||||||
|
suspendedKey := fmt.Sprintf(keyAccountSuspended, casefoldedAccount)
|
||||||
|
|
||||||
_, e := tx.Get(accountKey)
|
_, e := tx.Get(accountKey)
|
||||||
if e == buntdb.ErrNotFound {
|
if e == buntdb.ErrNotFound {
|
||||||
@ -1257,6 +1271,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
|||||||
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
result.AdditionalNicks, _ = tx.Get(nicksKey)
|
||||||
result.VHost, _ = tx.Get(vhostKey)
|
result.VHost, _ = tx.Get(vhostKey)
|
||||||
result.Settings, _ = tx.Get(settingsKey)
|
result.Settings, _ = tx.Get(settingsKey)
|
||||||
|
result.Suspended, _ = tx.Get(suspendedKey)
|
||||||
|
|
||||||
if _, e = tx.Get(verifiedKey); e == nil {
|
if _, e = tx.Get(verifiedKey); e == nil {
|
||||||
result.Verified = true
|
result.Verified = true
|
||||||
@ -1265,20 +1280,44 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) Suspend(accountName string) (err error) {
|
type AccountSuspension struct {
|
||||||
|
AccountName string `json:"AccountName,omitempty"`
|
||||||
|
TimeCreated time.Time
|
||||||
|
Duration time.Duration
|
||||||
|
OperName string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (am *AccountManager) Suspend(accountName string, duration time.Duration, operName, reason string) (err error) {
|
||||||
account, err := CasefoldName(accountName)
|
account, err := CasefoldName(accountName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errAccountDoesNotExist
|
return errAccountDoesNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspension := AccountSuspension{
|
||||||
|
TimeCreated: time.Now().UTC(),
|
||||||
|
Duration: duration,
|
||||||
|
OperName: operName,
|
||||||
|
Reason: reason,
|
||||||
|
}
|
||||||
|
suspensionStr, err := json.Marshal(suspension)
|
||||||
|
if err != nil {
|
||||||
|
am.server.logger.Error("internal", "suspension json unserializable", err.Error())
|
||||||
|
return errAccountDoesNotExist
|
||||||
|
}
|
||||||
|
|
||||||
existsKey := fmt.Sprintf(keyAccountExists, account)
|
existsKey := fmt.Sprintf(keyAccountExists, account)
|
||||||
verifiedKey := fmt.Sprintf(keyAccountVerified, account)
|
suspensionKey := fmt.Sprintf(keyAccountSuspended, account)
|
||||||
|
var setOptions *buntdb.SetOptions
|
||||||
|
if duration != time.Duration(0) {
|
||||||
|
setOptions = &buntdb.SetOptions{Expires: true, TTL: duration}
|
||||||
|
}
|
||||||
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
_, err := tx.Get(existsKey)
|
_, err := tx.Get(existsKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errAccountDoesNotExist
|
return errAccountDoesNotExist
|
||||||
}
|
}
|
||||||
_, err = tx.Delete(verifiedKey)
|
_, _, err = tx.Set(suspensionKey, string(suspensionStr), setOptions)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1293,7 +1332,13 @@ func (am *AccountManager) Suspend(accountName string) (err error) {
|
|||||||
delete(am.accountToClients, account)
|
delete(am.accountToClients, account)
|
||||||
am.Unlock()
|
am.Unlock()
|
||||||
|
|
||||||
am.killClients(clients)
|
// kill clients, sending them the reason
|
||||||
|
suspension.AccountName = accountName
|
||||||
|
for _, client := range clients {
|
||||||
|
client.Logout()
|
||||||
|
client.Quit(suspensionToString(client, suspension), nil)
|
||||||
|
client.destroy(nil)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1312,20 +1357,53 @@ func (am *AccountManager) Unsuspend(account string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
existsKey := fmt.Sprintf(keyAccountExists, cfaccount)
|
existsKey := fmt.Sprintf(keyAccountExists, cfaccount)
|
||||||
verifiedKey := fmt.Sprintf(keyAccountVerified, cfaccount)
|
suspensionKey := fmt.Sprintf(keyAccountSuspended, account)
|
||||||
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
err = am.server.store.Update(func(tx *buntdb.Tx) error {
|
||||||
_, err := tx.Get(existsKey)
|
_, err := tx.Get(existsKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errAccountDoesNotExist
|
return errAccountDoesNotExist
|
||||||
}
|
}
|
||||||
tx.Set(verifiedKey, "1", nil)
|
_, err = tx.Delete(suspensionKey)
|
||||||
|
if err != nil {
|
||||||
|
return errNoop
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
return err
|
||||||
return errAccountDoesNotExist
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
func (am *AccountManager) ListSuspended() (result []AccountSuspension) {
|
||||||
|
var names []string
|
||||||
|
var raw []string
|
||||||
|
|
||||||
|
prefix := fmt.Sprintf(keyAccountSuspended, "")
|
||||||
|
am.server.store.View(func(tx *buntdb.Tx) error {
|
||||||
|
err := tx.AscendGreaterOrEqual("", prefix, func(key, value string) bool {
|
||||||
|
if !strings.HasPrefix(key, prefix) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
raw = append(raw, value)
|
||||||
|
cfname := strings.TrimPrefix(key, prefix)
|
||||||
|
name, _ := tx.Get(fmt.Sprintf(keyAccountName, cfname))
|
||||||
|
names = append(names, name)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
result = make([]AccountSuspension, 0, len(raw))
|
||||||
|
for i := 0; i < len(raw); i++ {
|
||||||
|
var sus AccountSuspension
|
||||||
|
err := json.Unmarshal([]byte(raw[i]), &sus)
|
||||||
|
if err != nil {
|
||||||
|
am.server.logger.Error("internal", "corrupt data for suspension", names[i], err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sus.AccountName = names[i]
|
||||||
|
result = append(result, sus)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (am *AccountManager) Unregister(account string, erase bool) error {
|
func (am *AccountManager) Unregister(account string, erase bool) error {
|
||||||
@ -1351,6 +1429,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
|||||||
unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
|
unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
|
||||||
modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)
|
modesKey := fmt.Sprintf(keyAccountModes, casefoldedAccount)
|
||||||
realnameKey := fmt.Sprintf(keyAccountRealname, casefoldedAccount)
|
realnameKey := fmt.Sprintf(keyAccountRealname, casefoldedAccount)
|
||||||
|
suspendedKey := fmt.Sprintf(keyAccountSuspended, casefoldedAccount)
|
||||||
|
|
||||||
var clients []*Client
|
var clients []*Client
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -1410,6 +1489,7 @@ func (am *AccountManager) Unregister(account string, erase bool) error {
|
|||||||
tx.Delete(lastSeenKey)
|
tx.Delete(lastSeenKey)
|
||||||
tx.Delete(modesKey)
|
tx.Delete(modesKey)
|
||||||
tx.Delete(realnameKey)
|
tx.Delete(realnameKey)
|
||||||
|
tx.Delete(suspendedKey)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@ -1491,6 +1571,9 @@ func (am *AccountManager) AuthenticateByCertificate(client *Client, certfp strin
|
|||||||
} else if !clientAccount.Verified {
|
} else if !clientAccount.Verified {
|
||||||
err = errAccountUnverified
|
err = errAccountUnverified
|
||||||
return
|
return
|
||||||
|
} else if clientAccount.Suspended != nil {
|
||||||
|
err = errAccountSuspended
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// TODO(#1109) clean this check up?
|
// TODO(#1109) clean this check up?
|
||||||
if client.registered {
|
if client.registered {
|
||||||
@ -1882,6 +1965,7 @@ type ClientAccount struct {
|
|||||||
RegisteredAt time.Time
|
RegisteredAt time.Time
|
||||||
Credentials AccountCredentials
|
Credentials AccountCredentials
|
||||||
Verified bool
|
Verified bool
|
||||||
|
Suspended *AccountSuspension
|
||||||
AdditionalNicks []string
|
AdditionalNicks []string
|
||||||
VHost VHostInfo
|
VHost VHostInfo
|
||||||
Settings AccountSettings
|
Settings AccountSettings
|
||||||
@ -1897,4 +1981,5 @@ type rawClientAccount struct {
|
|||||||
AdditionalNicks string
|
AdditionalNicks string
|
||||||
VHost string
|
VHost string
|
||||||
Settings string
|
Settings string
|
||||||
|
Suspended string
|
||||||
}
|
}
|
||||||
|
@ -857,7 +857,7 @@ func schemaChangeV16ToV17(config *Config, tx *buntdb.Tx) error {
|
|||||||
func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
|
func getSchemaChange(initialVersion int) (result SchemaChange, ok bool) {
|
||||||
for _, change := range allChanges {
|
for _, change := range allChanges {
|
||||||
if initialVersion == change.InitialVersion {
|
if initialVersion == change.InitialVersion {
|
||||||
return result, true
|
return change, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -28,6 +28,7 @@ var (
|
|||||||
errAccountAlreadyLoggedIn = errors.New("You're already logged into an account")
|
errAccountAlreadyLoggedIn = errors.New("You're already logged into an account")
|
||||||
errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
|
errAccountTooManyNicks = errors.New("Account has too many reserved nicks")
|
||||||
errAccountUnverified = errors.New(`Account is not yet verified`)
|
errAccountUnverified = errors.New(`Account is not yet verified`)
|
||||||
|
errAccountSuspended = errors.New(`Account has been suspended`)
|
||||||
errAccountVerificationFailed = errors.New("Account verification failed")
|
errAccountVerificationFailed = errors.New("Account verification failed")
|
||||||
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
|
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
|
||||||
errAccountUpdateFailed = errors.New(`Error while updating your account information`)
|
errAccountUpdateFailed = errors.New(`Error while updating your account information`)
|
||||||
|
@ -267,7 +267,7 @@ func authErrorToMessage(server *Server, err error) (msg string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch err {
|
switch err {
|
||||||
case errAccountDoesNotExist, errAccountUnverified, errAccountInvalidCredentials, errAuthzidAuthcidMismatch, errNickAccountMismatch:
|
case errAccountDoesNotExist, errAccountUnverified, errAccountInvalidCredentials, errAuthzidAuthcidMismatch, errNickAccountMismatch, errAccountSuspended:
|
||||||
return err.Error()
|
return err.Error()
|
||||||
default:
|
default:
|
||||||
// don't expose arbitrary error messages to the user
|
// don't expose arbitrary error messages to the user
|
||||||
|
109
irc/nickserv.go
109
irc/nickserv.go
@ -6,12 +6,14 @@ package irc
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goshuirc/irc-go/ircfmt"
|
"github.com/goshuirc/irc-go/ircfmt"
|
||||||
|
|
||||||
|
"github.com/oragono/oragono/irc/custime"
|
||||||
"github.com/oragono/oragono/irc/passwd"
|
"github.com/oragono/oragono/irc/passwd"
|
||||||
"github.com/oragono/oragono/irc/sno"
|
"github.com/oragono/oragono/irc/sno"
|
||||||
"github.com/oragono/oragono/irc/utils"
|
"github.com/oragono/oragono/irc/utils"
|
||||||
@ -333,19 +335,15 @@ example with $bCERT ADD <account> <fingerprint>$b.`,
|
|||||||
},
|
},
|
||||||
"suspend": {
|
"suspend": {
|
||||||
handler: nsSuspendHandler,
|
handler: nsSuspendHandler,
|
||||||
help: `Syntax: $bSUSPEND <nickname>$b
|
help: `Syntax: $bSUSPEND ADD <nickname> [DURATION duration] [reason]$b
|
||||||
|
$bSUSPEND DEL <nickname>$b
|
||||||
|
$bSUSPEND LIST$b
|
||||||
|
|
||||||
SUSPEND disables an account and disconnects the associated clients.`,
|
Suspending an account disables it (preventing new logins) and disconnects
|
||||||
helpShort: `$bSUSPEND$b disables an account and disconnects the clients`,
|
all associated clients. You can specify a time limit or a reason for
|
||||||
minParams: 1,
|
the suspension. The $bDEL$b subcommand reverses a suspension, and the $bLIST$b
|
||||||
capabs: []string{"accreg"},
|
command lists all current suspensions.`,
|
||||||
},
|
helpShort: `$bSUSPEND$b adds or removes an account suspension`,
|
||||||
"unsuspend": {
|
|
||||||
handler: nsUnsuspendHandler,
|
|
||||||
help: `Syntax: $bUNSUSPEND <nickname>$b
|
|
||||||
|
|
||||||
UNSUSPEND reverses a previous SUSPEND, restoring access to the account.`,
|
|
||||||
helpShort: `$bUNSUSPEND$b restores access to a suspended account`,
|
|
||||||
minParams: 1,
|
minParams: 1,
|
||||||
capabs: []string{"accreg"},
|
capabs: []string{"accreg"},
|
||||||
},
|
},
|
||||||
@ -810,6 +808,9 @@ func nsInfoHandler(server *Server, client *Client, command string, params []stri
|
|||||||
for _, channel := range server.accounts.ChannelsForAccount(accountName) {
|
for _, channel := range server.accounts.ChannelsForAccount(accountName) {
|
||||||
nsNotice(rb, fmt.Sprintf(client.t("Registered channel: %s"), channel))
|
nsNotice(rb, fmt.Sprintf(client.t("Registered channel: %s"), channel))
|
||||||
}
|
}
|
||||||
|
if account.Suspended != nil {
|
||||||
|
nsNotice(rb, suspensionToString(client, *account.Suspended))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nsRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func nsRegisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
@ -1276,10 +1277,52 @@ func nsCertHandler(server *Server, client *Client, command string, params []stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nsSuspendHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func nsSuspendHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
err := server.accounts.Suspend(params[0])
|
subCmd := strings.ToLower(params[0])
|
||||||
|
params = params[1:]
|
||||||
|
switch subCmd {
|
||||||
|
case "add":
|
||||||
|
nsSuspendAddHandler(server, client, command, params, rb)
|
||||||
|
case "del", "delete", "remove":
|
||||||
|
nsSuspendRemoveHandler(server, client, command, params, rb)
|
||||||
|
case "list":
|
||||||
|
nsSuspendListHandler(server, client, command, params, rb)
|
||||||
|
default:
|
||||||
|
nsNotice(rb, client.t("Invalid parameters"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nsSuspendAddHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
if len(params) == 0 {
|
||||||
|
nsNotice(rb, client.t("Invalid parameters"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account := params[0]
|
||||||
|
params = params[1:]
|
||||||
|
|
||||||
|
var duration time.Duration
|
||||||
|
if 2 <= len(params) && strings.ToLower(params[0]) == "duration" {
|
||||||
|
var err error
|
||||||
|
cDuration, err := custime.ParseDuration(params[1])
|
||||||
|
if err != nil {
|
||||||
|
nsNotice(rb, client.t("Invalid time duration for NS SUSPEND"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
duration = time.Duration(cDuration)
|
||||||
|
params = params[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
var reason string
|
||||||
|
if len(params) != 0 {
|
||||||
|
reason = strings.Join(params, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := client.Oper().Name
|
||||||
|
|
||||||
|
err := server.accounts.Suspend(account, duration, name, reason)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
nsNotice(rb, fmt.Sprintf(client.t("Successfully suspended account %s"), params[0]))
|
nsNotice(rb, fmt.Sprintf(client.t("Successfully suspended account %s"), account))
|
||||||
case errAccountDoesNotExist:
|
case errAccountDoesNotExist:
|
||||||
nsNotice(rb, client.t("No such account"))
|
nsNotice(rb, client.t("No such account"))
|
||||||
default:
|
default:
|
||||||
@ -1287,14 +1330,50 @@ func nsSuspendHandler(server *Server, client *Client, command string, params []s
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nsUnsuspendHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
func nsSuspendRemoveHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
if len(params) == 0 {
|
||||||
|
nsNotice(rb, client.t("Invalid parameters"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err := server.accounts.Unsuspend(params[0])
|
err := server.accounts.Unsuspend(params[0])
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
nsNotice(rb, fmt.Sprintf(client.t("Successfully un-suspended account %s"), params[0]))
|
nsNotice(rb, fmt.Sprintf(client.t("Successfully un-suspended account %s"), params[0]))
|
||||||
case errAccountDoesNotExist:
|
case errAccountDoesNotExist:
|
||||||
nsNotice(rb, client.t("No such account"))
|
nsNotice(rb, client.t("No such account"))
|
||||||
|
case errNoop:
|
||||||
|
nsNotice(rb, client.t("Account was not suspended"))
|
||||||
default:
|
default:
|
||||||
nsNotice(rb, client.t("An error occurred"))
|
nsNotice(rb, client.t("An error occurred"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort in reverse order of creation time
|
||||||
|
type ByCreationTime []AccountSuspension
|
||||||
|
|
||||||
|
func (a ByCreationTime) Len() int { return len(a) }
|
||||||
|
func (a ByCreationTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a ByCreationTime) Less(i, j int) bool { return a[i].TimeCreated.After(a[j].TimeCreated) }
|
||||||
|
|
||||||
|
func nsSuspendListHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
|
||||||
|
suspensions := server.accounts.ListSuspended()
|
||||||
|
sort.Sort(ByCreationTime(suspensions))
|
||||||
|
nsNotice(rb, fmt.Sprintf(client.t("There are %d active suspensions."), len(suspensions)))
|
||||||
|
for _, suspension := range suspensions {
|
||||||
|
nsNotice(rb, suspensionToString(client, suspension))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func suspensionToString(client *Client, suspension AccountSuspension) (result string) {
|
||||||
|
duration := client.t("indefinite")
|
||||||
|
if suspension.Duration != time.Duration(0) {
|
||||||
|
duration = suspension.Duration.String()
|
||||||
|
}
|
||||||
|
ts := suspension.TimeCreated.Format(time.RFC1123)
|
||||||
|
reason := client.t("No reason given.")
|
||||||
|
if suspension.Reason != "" {
|
||||||
|
reason = fmt.Sprintf(client.t("Reason: %s"), suspension.Reason)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(client.t("Account %s suspended at %s. Duration: %s. %s"), suspension.AccountName, ts, duration, reason)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user