mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-26 13:59:44 +01:00
Merge pull request #890 from slingamn/permanent_accounts.2
make account names permanent identifiers
This commit is contained in:
commit
48f6bb79c8
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,17 +243,12 @@ 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
|
entry.channel.SetUnregistered(account)
|
||||||
}
|
delete(cm.registeredChannels, cfname)
|
||||||
info = entry.channel.ExportRegistration(0)
|
if skel, err := Skeleton(entry.channel.Name()); err == nil {
|
||||||
if info.Founder != account {
|
delete(cm.registeredSkeletons, skel)
|
||||||
return errChannelNotOwnedByAccount
|
}
|
||||||
}
|
|
||||||
entry.channel.SetUnregistered(account)
|
|
||||||
delete(cm.registeredChannels, cfname)
|
|
||||||
if skel, err := Skeleton(entry.channel.Name()); err == nil {
|
|
||||||
delete(cm.registeredSkeletons, skel)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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")
|
||||||
|
@ -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()
|
||||||
|
@ -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,39 +842,51 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := server.accounts.LoadAccount(username)
|
var accountName string
|
||||||
if err == errAccountDoesNotExist {
|
var registeredAt time.Time
|
||||||
nsNotice(rb, client.t("Invalid account name"))
|
if erase {
|
||||||
return
|
// account may not be in a loadable state, e.g., if it was unregistered
|
||||||
} else if err != nil {
|
accountName = username
|
||||||
nsNotice(rb, client.t("Internal error"))
|
} else {
|
||||||
return
|
account, err := server.accounts.LoadAccount(username)
|
||||||
|
if err == errAccountDoesNotExist {
|
||||||
|
nsNotice(rb, client.t("Invalid account name"))
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
nsNotice(rb, client.t("Internal error"))
|
||||||
|
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 {
|
||||||
nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b")))
|
if erase {
|
||||||
nsNotice(rb, fmt.Sprintf(client.t("To confirm account unregistration, type: /NS UNREGISTER %[1]s %[2]s"), cfname, expectedCode))
|
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, 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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user