3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-29 23:49:25 +01:00

Merge pull request #890 from slingamn/permanent_accounts.2

make account names permanent identifiers
This commit is contained in:
Shivaram Lingamneni 2020-03-21 21:20:18 -07:00 committed by GitHub
commit 48f6bb79c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 84 additions and 36 deletions

View File

@ -24,6 +24,7 @@ import (
const ( const (
keyAccountExists = "account.exists %s" keyAccountExists = "account.exists %s"
keyAccountVerified = "account.verified %s" keyAccountVerified = "account.verified %s"
keyAccountUnregistered = "account.unregistered %s"
keyAccountCallback = "account.callback %s" keyAccountCallback = "account.callback %s"
keyAccountVerificationCode = "account.verificationcode %s" keyAccountVerificationCode = "account.verificationcode %s"
keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
@ -363,6 +364,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
} }
accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount) accountKey := fmt.Sprintf(keyAccountExists, casefoldedAccount)
unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount) accountNameKey := fmt.Sprintf(keyAccountName, casefoldedAccount)
callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount) callbackKey := fmt.Sprintf(keyAccountCallback, casefoldedAccount)
registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount) registeredTimeKey := fmt.Sprintf(keyAccountRegTime, casefoldedAccount)
@ -401,7 +403,11 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
} }
return am.server.store.Update(func(tx *buntdb.Tx) error { return am.server.store.Update(func(tx *buntdb.Tx) error {
_, err := am.loadRawAccount(tx, casefoldedAccount) if _, err := tx.Get(unregisteredKey); err == nil {
return errAccountAlreadyUnregistered
}
_, err = am.loadRawAccount(tx, casefoldedAccount)
if err != errAccountDoesNotExist { if err != errAccountDoesNotExist {
return errAccountAlreadyRegistered return errAccountAlreadyRegistered
} }
@ -432,7 +438,7 @@ func (am *AccountManager) Register(client *Client, account string, callbackNames
code, err := am.dispatchCallback(client, casefoldedAccount, callbackNamespace, callbackValue) code, err := am.dispatchCallback(client, casefoldedAccount, callbackNamespace, callbackValue)
if err != nil { if err != nil {
am.Unregister(casefoldedAccount) am.Unregister(casefoldedAccount, true)
return errCallbackFailed return errCallbackFailed
} else { } else {
return am.server.store.Update(func(tx *buntdb.Tx) error { return am.server.store.Update(func(tx *buntdb.Tx) error {
@ -1063,7 +1069,7 @@ func (am *AccountManager) loadRawAccount(tx *buntdb.Tx, casefoldedAccount string
return return
} }
func (am *AccountManager) Unregister(account string) error { func (am *AccountManager) Unregister(account string, erase bool) error {
config := am.server.Config() config := am.server.Config()
casefoldedAccount, err := CasefoldName(account) casefoldedAccount, err := CasefoldName(account)
if err != nil { if err != nil {
@ -1084,6 +1090,7 @@ func (am *AccountManager) Unregister(account string) error {
channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount) channelsKey := fmt.Sprintf(keyAccountChannels, casefoldedAccount)
joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount) joinedChannelsKey := fmt.Sprintf(keyAccountJoinedChannels, casefoldedAccount)
lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount) lastSeenKey := fmt.Sprintf(keyAccountLastSeen, casefoldedAccount)
unregisteredKey := fmt.Sprintf(keyAccountUnregistered, casefoldedAccount)
var clients []*Client var clients []*Client
@ -1091,7 +1098,10 @@ func (am *AccountManager) Unregister(account string) error {
// on our way out, unregister all the account's channels and delete them from the db // on our way out, unregister all the account's channels and delete them from the db
defer func() { defer func() {
for _, channelName := range registeredChannels { for _, channelName := range registeredChannels {
am.server.channels.SetUnregistered(channelName, casefoldedAccount) err := am.server.channels.SetUnregistered(channelName, casefoldedAccount)
if err != nil {
am.server.logger.Error("internal", "couldn't unregister channel", channelName, err.Error())
}
} }
}() }()
@ -1104,6 +1114,13 @@ func (am *AccountManager) Unregister(account string) error {
var accountName string var accountName string
var channelsStr string var channelsStr string
am.server.store.Update(func(tx *buntdb.Tx) error { am.server.store.Update(func(tx *buntdb.Tx) error {
if erase {
tx.Delete(unregisteredKey)
} else {
if _, err := tx.Get(accountKey); err == nil {
tx.Set(unregisteredKey, "1", nil)
}
}
tx.Delete(accountKey) tx.Delete(accountKey)
accountName, _ = tx.Get(accountNameKey) accountName, _ = tx.Get(accountNameKey)
tx.Delete(accountNameKey) tx.Delete(accountNameKey)
@ -1129,7 +1146,7 @@ func (am *AccountManager) Unregister(account string) error {
if err == nil { if err == nil {
var creds AccountCredentials var creds AccountCredentials
if err = json.Unmarshal([]byte(credText), &creds); err == nil { if err := json.Unmarshal([]byte(credText), &creds); err == nil {
for _, cert := range creds.Certfps { for _, cert := range creds.Certfps {
certFPKey := fmt.Sprintf(keyCertToAccount, cert) certFPKey := fmt.Sprintf(keyCertToAccount, cert)
am.server.store.Update(func(tx *buntdb.Tx) error { am.server.store.Update(func(tx *buntdb.Tx) error {
@ -1169,7 +1186,7 @@ func (am *AccountManager) Unregister(account string) error {
} }
} }
if err != nil { if err != nil && !erase {
return errAccountDoesNotExist return errAccountDoesNotExist
} }

View File

@ -226,7 +226,13 @@ func (cm *ChannelManager) SetUnregistered(channelName string, account string) (e
return err return err
} }
var info RegisteredChannel info, err := cm.server.channelRegistry.LoadChannel(cfname)
if err != nil {
return err
}
if info.Founder != account {
return errChannelNotOwnedByAccount
}
defer func() { defer func() {
if err == nil { if err == nil {
@ -237,18 +243,13 @@ func (cm *ChannelManager) SetUnregistered(channelName string, account string) (e
cm.Lock() cm.Lock()
defer cm.Unlock() defer cm.Unlock()
entry := cm.chans[cfname] entry := cm.chans[cfname]
if entry == nil { if entry != nil {
return errNoSuchChannel
}
info = entry.channel.ExportRegistration(0)
if info.Founder != account {
return errChannelNotOwnedByAccount
}
entry.channel.SetUnregistered(account) entry.channel.SetUnregistered(account)
delete(cm.registeredChannels, cfname) delete(cm.registeredChannels, cfname)
if skel, err := Skeleton(entry.channel.Name()); err == nil { if skel, err := Skeleton(entry.channel.Name()); err == nil {
delete(cm.registeredSkeletons, skel) delete(cm.registeredSkeletons, skel)
} }
}
return nil return nil
} }

View File

@ -231,6 +231,7 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC
info = RegisteredChannel{ info = RegisteredChannel{
Name: name, Name: name,
NameCasefolded: nameCasefolded,
RegisteredAt: time.Unix(regTimeInt, 0).UTC(), RegisteredAt: time.Unix(regTimeInt, 0).UTC(),
Founder: founder, Founder: founder,
Topic: topic, Topic: topic,

View File

@ -13,6 +13,7 @@ import (
// Runtime Errors // Runtime Errors
var ( var (
errAccountAlreadyRegistered = errors.New(`Account already exists`) errAccountAlreadyRegistered = errors.New(`Account already exists`)
errAccountAlreadyUnregistered = errors.New(`That account name was registered previously and can't be reused`)
errAccountAlreadyVerified = errors.New(`Account is already verified`) errAccountAlreadyVerified = errors.New(`Account is already verified`)
errAccountCantDropPrimaryNick = errors.New("Can't unreserve primary nickname") errAccountCantDropPrimaryNick = errors.New("Can't unreserve primary nickname")
errAccountCreation = errors.New("Account could not be created") errAccountCreation = errors.New("Account could not be created")

View File

@ -66,7 +66,7 @@ func registrationErrorToMessageAndCode(err error) (message, code string) {
case errAccountBadPassphrase: case errAccountBadPassphrase:
code = "REG_INVALID_CREDENTIAL" code = "REG_INVALID_CREDENTIAL"
message = err.Error() message = err.Error()
case errAccountAlreadyRegistered, errAccountAlreadyVerified: case errAccountAlreadyRegistered, errAccountAlreadyVerified, errAccountAlreadyUnregistered:
message = err.Error() message = err.Error()
case errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled: case errAccountCreation, errAccountMustHoldNick, errAccountBadPassphrase, errCertfpAlreadyExists, errFeatureDisabled:
message = err.Error() message = err.Error()

View File

@ -164,6 +164,20 @@ a code will display the necessary code.`,
enabled: servCmdRequiresAuthEnabled, enabled: servCmdRequiresAuthEnabled,
minParams: 1, minParams: 1,
}, },
"erase": {
handler: nsUnregisterHandler,
help: `Syntax: $bERASE <username> [code]$b
ERASE deletes all records of an account, allowing it to be re-registered.
This should be used with caution, because it violates an expectation that
account names are permanent identifiers. Typically, UNREGISTER should be
used instead. A confirmation code is required; invoking the command
without a code will display the necessary code.`,
helpShort: `$bERASE$b erases all records of an account, allowing reuse.`,
enabled: servCmdRequiresAuthEnabled,
capabs: []string{"accreg"},
minParams: 1,
},
"verify": { "verify": {
handler: nsVerifyHandler, handler: nsVerifyHandler,
help: `Syntax: $bVERIFY <username> <code>$b help: `Syntax: $bVERIFY <username> <code>$b
@ -815,6 +829,8 @@ func nsSaregisterHandler(server *Server, client *Client, command string, params
} }
func nsUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { func nsUnregisterHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
erase := command == "erase"
username := params[0] username := params[0]
var verificationCode string var verificationCode string
if len(params) > 1 { if len(params) > 1 {
@ -826,6 +842,12 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params
return return
} }
var accountName string
var registeredAt time.Time
if erase {
// account may not be in a loadable state, e.g., if it was unregistered
accountName = username
} else {
account, err := server.accounts.LoadAccount(username) account, err := server.accounts.LoadAccount(username)
if err == errAccountDoesNotExist { if err == errAccountDoesNotExist {
nsNotice(rb, client.t("Invalid account name")) nsNotice(rb, client.t("Invalid account name"))
@ -834,31 +856,37 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params
nsNotice(rb, client.t("Internal error")) nsNotice(rb, client.t("Internal error"))
return return
} }
accountName = account.Name
registeredAt = account.RegisteredAt
}
cfname, _ := CasefoldName(username) if !(accountName == client.AccountName() || client.HasRoleCapabs("accreg")) {
if !(cfname == client.Account() || client.HasRoleCapabs("accreg")) {
nsNotice(rb, client.t("Insufficient oper privs")) nsNotice(rb, client.t("Insufficient oper privs"))
return return
} }
expectedCode := utils.ConfirmationCode(account.Name, account.RegisteredAt) expectedCode := utils.ConfirmationCode(accountName, registeredAt)
if expectedCode != verificationCode { if expectedCode != verificationCode {
if erase {
nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: erasing this account will allow it to be re-registered; consider UNREGISTER instead.$b")))
nsNotice(rb, fmt.Sprintf(client.t("To confirm account erase, type: /NS ERASE %[1]s %[2]s"), accountName, expectedCode))
} else {
nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b"))) nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b")))
nsNotice(rb, fmt.Sprintf(client.t("To confirm account unregistration, type: /NS UNREGISTER %[1]s %[2]s"), cfname, expectedCode)) nsNotice(rb, fmt.Sprintf(client.t("To confirm account unregistration, type: /NS UNREGISTER %[1]s %[2]s"), accountName, expectedCode))
}
return return
} }
err = server.accounts.Unregister(cfname) err := server.accounts.Unregister(accountName, erase)
if err == errAccountDoesNotExist { if err == errAccountDoesNotExist {
nsNotice(rb, client.t(err.Error())) nsNotice(rb, client.t(err.Error()))
} else if err != nil { } else if err != nil {
nsNotice(rb, client.t("Error while unregistering account")) nsNotice(rb, client.t("Error while unregistering account"))
} else { } else {
nsNotice(rb, fmt.Sprintf(client.t("Successfully unregistered account %s"), cfname)) nsNotice(rb, fmt.Sprintf(client.t("Successfully unregistered account %s"), accountName))
server.logger.Info("accounts", "client", client.Nick(), "unregistered account", cfname) server.logger.Info("accounts", "client", client.Nick(), "unregistered account", accountName)
client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] unregistered account $c[grey][$r%s$c[grey]]"), client.NickMaskString(), accountName))
} }
client.server.snomasks.Send(sno.LocalAccounts, fmt.Sprintf(ircfmt.Unescape("Client $c[grey][$r%s$c[grey]] unregistered account $c[grey][$r%s$c[grey]]"), client.NickMaskString(), account.Name))
} }
func nsVerifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) { func nsVerifyHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {