2016-09-04 11:25:33 +02:00
// Copyright (c) 2016- Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc
import (
"errors"
"fmt"
"strconv"
"strings"
"time"
"github.com/DanielOaks/girc-go/ircmsg"
"github.com/tidwall/buntdb"
)
var (
errAccountCreation = errors . New ( "Account could not be created" )
)
// AccountRegistration manages the registration of accounts.
type AccountRegistration struct {
2016-09-04 13:15:28 +02:00
Enabled bool
EnabledCallbacks [ ] string
EnabledCredentialTypes [ ] string
2016-09-04 11:25:33 +02:00
}
// NewAccountRegistration returns a new AccountRegistration, configured correctly.
func NewAccountRegistration ( config AccountRegistrationConfig ) ( accountReg AccountRegistration ) {
if config . Enabled {
accountReg . Enabled = true
2016-09-04 12:08:53 +02:00
for _ , name := range config . EnabledCallbacks {
// we store "none" as "*" internally
if name == "none" {
name = "*"
}
2016-09-04 13:15:28 +02:00
accountReg . EnabledCallbacks = append ( accountReg . EnabledCallbacks , name )
}
// no need to make this configurable, right now at least
accountReg . EnabledCredentialTypes = [ ] string {
"passphrase" ,
"certfp" ,
2016-09-04 12:08:53 +02:00
}
2016-09-04 11:25:33 +02:00
}
return accountReg
}
// regHandler parses the REG command.
func regHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
subcommand := strings . ToLower ( msg . Params [ 0 ] )
if subcommand == "create" {
2016-09-04 13:42:19 +02:00
return regCreateHandler ( server , client , msg )
} else if subcommand == "verify" {
client . Notice ( "Parsing VERIFY" )
} else {
client . Send ( nil , server . nameString , ERR_UNKNOWNERROR , client . nickString , "REG" , msg . Params [ 0 ] , "Unknown subcommand" )
}
return false
}
// regCreateHandler parses the REG CREATE command.
func regCreateHandler ( server * Server , client * Client , msg ircmsg . IrcMessage ) bool {
client . Notice ( "Parsing CREATE" )
2016-09-04 11:25:33 +02:00
2016-09-04 13:42:19 +02:00
// get and sanitise account name
account := NewName ( msg . Params [ 1 ] )
if ! account . IsNickname ( ) || msg . Params [ 1 ] == "*" {
client . Send ( nil , server . nameString , ERR_REG_UNSPECIFIED_ERROR , client . nickString , msg . Params [ 1 ] , "Account name is not valid" )
return false
}
accountString := account . String ( )
// check whether account exists
// do it all in one write tx to prevent races
err := server . store . Update ( func ( tx * buntdb . Tx ) error {
accountKey := fmt . Sprintf ( "account %s exists" , accountString )
_ , err := tx . Get ( accountKey )
if err != buntdb . ErrNotFound {
//TODO(dan): if account verified key doesn't exist account is not verified, calc the maximum time without verification and expire and continue if need be
client . Send ( nil , server . nameString , ERR_ACCOUNT_ALREADY_EXISTS , client . nickString , msg . Params [ 1 ] , "Account already exists" )
return errAccountCreation
2016-09-04 11:25:33 +02:00
}
2016-09-04 13:42:19 +02:00
registeredTimeKey := fmt . Sprintf ( "account %s registered.time" , accountString )
2016-09-04 11:25:33 +02:00
2016-09-04 13:42:19 +02:00
tx . Set ( accountKey , "1" , nil )
tx . Set ( registeredTimeKey , strconv . FormatInt ( time . Now ( ) . Unix ( ) , 10 ) , nil )
return nil
} )
2016-09-04 11:25:33 +02:00
2016-09-04 13:42:19 +02:00
// account could not be created and relevant numerics have been dispatched, abort
if err != nil {
return false
}
2016-09-04 11:25:33 +02:00
2016-09-04 13:42:19 +02:00
// account didn't already exist, continue with account creation and dispatching verification (if required)
callback := strings . ToLower ( msg . Params [ 2 ] )
var callbackNamespace , callbackValue string
2016-09-04 12:08:53 +02:00
2016-09-04 13:42:19 +02:00
if callback == "*" {
callbackNamespace = "*"
} else if strings . Contains ( callback , ":" ) {
callbackValues := strings . SplitN ( callback , ":" , 2 )
callbackNamespace , callbackValue = callbackValues [ 0 ] , callbackValues [ 1 ]
} else {
callbackNamespace = server . accountRegistration . EnabledCallbacks [ 0 ]
callbackValue = callback
}
2016-09-04 12:08:53 +02:00
2016-09-04 13:42:19 +02:00
// ensure the callback namespace is valid
// need to search callback list, maybe look at using a map later?
var callbackValid bool
for _ , name := range server . accountRegistration . EnabledCallbacks {
if callbackNamespace == name {
callbackValid = true
2016-09-04 12:08:53 +02:00
}
2016-09-04 13:42:19 +02:00
}
2016-09-04 12:08:53 +02:00
2016-09-04 13:42:19 +02:00
if ! callbackValid {
client . Send ( nil , server . nameString , ERR_REG_INVALID_CALLBACK , client . nickString , msg . Params [ 1 ] , callbackNamespace , "Callback namespace is not supported" )
//TODO(dan): close out failed account reg (remove values from db)
return false
}
2016-09-04 12:08:53 +02:00
2016-09-04 13:42:19 +02:00
// get credential type/value
var credentialType , credentialValue string
2016-09-04 13:15:28 +02:00
2016-09-04 13:42:19 +02:00
if len ( msg . Params ) > 4 {
credentialType = strings . ToLower ( msg . Params [ 3 ] )
credentialValue = msg . Params [ 4 ]
} else if len ( msg . Params ) == 4 {
credentialType = "passphrase" // default from the spec
credentialValue = msg . Params [ 3 ]
} else {
client . Send ( nil , server . nameString , ERR_NEEDMOREPARAMS , client . nickString , msg . Command , "Not enough parameters" )
//TODO(dan): close out failed account reg (remove values from db)
return false
}
2016-09-04 13:15:28 +02:00
2016-09-04 13:42:19 +02:00
// ensure the credential type is valid
var credentialValid bool
for _ , name := range server . accountRegistration . EnabledCredentialTypes {
if credentialType == name {
credentialValid = true
2016-09-04 12:08:53 +02:00
}
2016-09-04 13:42:19 +02:00
}
2016-09-04 12:08:53 +02:00
2016-09-04 13:42:19 +02:00
if ! credentialValid {
client . Send ( nil , server . nameString , ERR_REG_INVALID_CRED_TYPE , client . nickString , credentialType , callbackNamespace , "Credential type is not supported" )
//TODO(dan): close out failed account reg (remove values from db)
return false
}
2016-09-04 11:25:33 +02:00
2016-09-04 13:42:19 +02:00
// dispatch callback
if callbackNamespace != "*" {
client . Notice ( "Account creation was successful!" )
//TODO(dan): close out failed account reg (remove values from db)
return false
2016-09-04 11:25:33 +02:00
}
2016-09-04 13:42:19 +02:00
client . Notice ( fmt . Sprintf ( "We should dispatch an actual callback here to %s:%s" , callbackNamespace , callbackValue ) )
client . Notice ( fmt . Sprintf ( "Primary account credential is with %s:%s" , credentialType , credentialValue ) )
2016-09-04 11:25:33 +02:00
return false
}