some tweaks to account/channel unregistration

This commit is contained in:
Shivaram Lingamneni 2018-06-19 04:03:40 -04:00
parent f4a284675d
commit 02a4aaf583
4 changed files with 45 additions and 25 deletions

View File

@ -10,6 +10,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/goshuirc/irc-go/ircfmt" "github.com/goshuirc/irc-go/ircfmt"
"github.com/oragono/oragono/irc/modes" "github.com/oragono/oragono/irc/modes"
@ -287,15 +288,10 @@ func csUnregisterHandler(server *Server, client *Client, command, params string,
} }
info := channel.ExportRegistration(0) info := channel.ExportRegistration(0)
// verification code is the crc32 of the name, plus the registration time expectedCode := unregisterConfirmationCode(info.Name, info.RegisteredAt)
var codeInput bytes.Buffer if expectedCode != verificationCode {
codeInput.WriteString(info.Name) csNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b")))
codeInput.WriteString(strconv.FormatInt(info.RegisteredAt.Unix(), 16)) csNotice(rb, fmt.Sprintf(client.t("To confirm channel unregistration, type: /CS UNREGISTER %s %s"), channelKey, expectedCode))
expectedCode := int(crc32.ChecksumIEEE(codeInput.Bytes()))
receivedCode, err := strconv.Atoi(verificationCode)
if err != nil || expectedCode != receivedCode {
csNotice(rb, client.t("$bWarning:$b Unregistering this channel will remove all stored channel attributes."))
csNotice(rb, fmt.Sprintf(client.t("To confirm channel unregistration, type: /CS UNREGISTER %s %d"), channelKey, expectedCode))
return return
} }
@ -303,3 +299,11 @@ func csUnregisterHandler(server *Server, client *Client, command, params string,
go server.channelRegistry.Delete(channelKey, info) go server.channelRegistry.Delete(channelKey, info)
csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey)) csNotice(rb, fmt.Sprintf(client.t("Channel %s is now unregistered"), channelKey))
} }
// deterministically generates a confirmation code for unregistering a channel / account
func unregisterConfirmationCode(name string, registeredAt time.Time) (code string) {
var codeInput bytes.Buffer
codeInput.WriteString(name)
codeInput.WriteString(strconv.FormatInt(registeredAt.Unix(), 16))
return strconv.Itoa(int(crc32.ChecksumIEEE(codeInput.Bytes())))
}

View File

@ -255,7 +255,10 @@ func umodeGreaterThan(l modes.Mode, r modes.Mode) bool {
// ProcessAccountToUmodeChange processes Add/Remove/List operations for channel persistent usermodes. // ProcessAccountToUmodeChange processes Add/Remove/List operations for channel persistent usermodes.
func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes.ModeChange) (results []modes.ModeChange, err error) { func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes.ModeChange) (results []modes.ModeChange, err error) {
umodeGEQ := func(l modes.Mode, r modes.Mode) bool { hasPrivsOver := func(l modes.Mode, r modes.Mode) bool {
if l == modes.ChannelAdmin {
return umodeGreaterThan(l, r)
}
return l == r || umodeGreaterThan(l, r) return l == r || umodeGreaterThan(l, r)
} }
@ -275,9 +278,9 @@ func (channel *Channel) ProcessAccountToUmodeChange(client *Client, change modes
// operators and founders can do anything // operators and founders can do anything
hasPrivs := isOperChange || (account != "" && account == channel.registeredFounder) hasPrivs := isOperChange || (account != "" && account == channel.registeredFounder)
// halfop and up can list, and do add/removes at levels <= their own // halfop and up can list, and do add/removes at levels <= their own
if change.Op == modes.List && umodeGEQ(clientMode, modes.Halfop) { if change.Op == modes.List && hasPrivsOver(clientMode, modes.Halfop) {
hasPrivs = true hasPrivs = true
} else if umodeGEQ(clientMode, modes.Halfop) && umodeGEQ(clientMode, targetModeNow) && umodeGEQ(clientMode, targetModeAfter) { } else if hasPrivsOver(clientMode, modes.Halfop) && hasPrivsOver(clientMode, targetModeNow) && hasPrivsOver(clientMode, targetModeAfter) {
hasPrivs = true hasPrivs = true
} }
if !hasPrivs { if !hasPrivs {

View File

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/oragono/oragono/irc/utils" "github.com/oragono/oragono/irc/utils"
) )
@ -98,10 +99,12 @@ SADROP forcibly de-links the given nickname from the attached user account.`,
}, },
"unregister": { "unregister": {
handler: nsUnregisterHandler, handler: nsUnregisterHandler,
help: `Syntax: $bUNREGISTER [username]$b help: `Syntax: $bUNREGISTER <username> [code]$b
UNREGISTER lets you delete your user account (or the given one, if you're an UNREGISTER lets you delete your user account (or someone else's, if you're an
IRC operator with the correct permissions).`, IRC operator with the correct permissions). To prevent accidental
unregistrations, a verification code is required; invoking the command without
a code will display the necessary code.`,
helpShort: `$bUNREGISTER$b lets you delete your user account.`, helpShort: `$bUNREGISTER$b lets you delete your user account.`,
}, },
"verify": { "verify": {
@ -316,7 +319,7 @@ func nsRegisterHandler(server *Server, client *Client, command, params string, r
} }
func nsUnregisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) { func nsUnregisterHandler(server *Server, client *Client, command, params string, rb *ResponseBuffer) {
username, _ := utils.ExtractParam(params) username, verificationCode := utils.ExtractParam(params)
if !server.AccountConfig().Registration.Enabled { if !server.AccountConfig().Registration.Enabled {
nsNotice(rb, client.t("Account registration has been disabled")) nsNotice(rb, client.t("Account registration has been disabled"))
@ -324,22 +327,32 @@ func nsUnregisterHandler(server *Server, client *Client, command, params string,
} }
if username == "" { if username == "" {
username = client.Account() nsNotice(rb, client.t("You must specify an account"))
}
if username == "" {
nsNotice(rb, client.t("You're not logged into an account"))
return return
} }
cfname, err := CasefoldName(username)
if err != nil { account, err := server.accounts.LoadAccount(username)
nsNotice(rb, client.t("Invalid username")) if err == errAccountDoesNotExist {
nsNotice(rb, client.t("Invalid account name"))
return
} else if err != nil {
nsNotice(rb, client.t("Internal error"))
return return
} }
if !(cfname == client.Account() || client.HasRoleCapabs("unregister")) {
cfname, _ := CasefoldName(username)
if !(cfname == client.Account() || client.HasRoleCapabs("accreg")) {
nsNotice(rb, client.t("Insufficient oper privs")) nsNotice(rb, client.t("Insufficient oper privs"))
return return
} }
expectedCode := unregisterConfirmationCode(account.Name, account.RegisteredAt)
if expectedCode != verificationCode {
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 %s %s"), cfname, expectedCode))
return
}
if cfname == client.Account() { if cfname == client.Account() {
client.server.accounts.Logout(client) client.server.accounts.Logout(client)
} }

View File

@ -280,7 +280,7 @@ oper-classes:
capabilities: capabilities:
- "oper:rehash" - "oper:rehash"
- "oper:die" - "oper:die"
- "unregister" - "accreg"
- "sajoin" - "sajoin"
- "samode" - "samode"
- "vhosts" - "vhosts"