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

Handle db better, fix bug, update db schema, rest

This commit is contained in:
Daniel Oaks 2016-11-06 13:47:13 +10:00
parent 65cb932e46
commit 6d6c1936cc
6 changed files with 141 additions and 28 deletions

View File

@ -11,8 +11,10 @@ New release of Oragono!
### Added ### Added
* Added ability to ban IP addresses and networks from the server with `DLINE`. * Added ability to ban IP addresses and networks from the server with `DLINE`.
* Added REST API for use with web interface to manage accounts, DLINEs, etc.
### Changed ### Changed
* Database upgraded to make handling accounts simpler.
### Removed ### Removed

View File

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"strings"
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
) )
@ -15,6 +16,8 @@ import (
const ( const (
// 'version' of the database schema // 'version' of the database schema
keySchemaVersion = "db.version" keySchemaVersion = "db.version"
// latest schema of the db
latestDbSchema = "2"
// key for the primary salt used by the ircd // key for the primary salt used by the ircd
keySalt = "crypto.salt" keySalt = "crypto.salt"
) )
@ -40,16 +43,68 @@ func InitDB(path string) {
tx.Set(keySalt, encodedSalt, nil) tx.Set(keySalt, encodedSalt, nil)
// set schema version // set schema version
tx.Set(keySchemaVersion, "1", nil) tx.Set(keySchemaVersion, "2", nil)
return nil return nil
}) })
if err != nil { if err != nil {
log.Fatal("Could not save bunt store:", err.Error()) log.Fatal("Could not save datastore:", err.Error())
} }
} }
// UpgradeDB upgrades the datastore to the latest schema. // UpgradeDB upgrades the datastore to the latest schema.
func UpgradeDB(path string) { func UpgradeDB(path string) {
store, err := buntdb.Open(path)
if err != nil {
log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
}
defer store.Close()
err = store.Update(func(tx *buntdb.Tx) error {
version, _ := tx.Get(keySchemaVersion)
// == version 1 -> 2 ==
// account key changes and account.verified key bugfix.
if version == "1" {
log.Println("Updating store v1 to v2")
var keysToRemove []string
newKeys := make(map[string]string)
tx.AscendKeys("account *", func(key, value string) bool {
keysToRemove = append(keysToRemove, key)
splitkey := strings.Split(key, " ")
// work around bug
if splitkey[2] == "exists" {
// manually create new verified key
newVerifiedKey := fmt.Sprintf("%s.verified %s", splitkey[0], splitkey[1])
newKeys[newVerifiedKey] = "1"
} else if splitkey[1] == "%s" {
return true
}
newKey := fmt.Sprintf("%s.%s %s", splitkey[0], splitkey[2], splitkey[1])
newKeys[newKey] = value
return true
})
for _, key := range keysToRemove {
tx.Delete(key)
}
for key, value := range newKeys {
tx.Set(key, value, nil)
}
tx.Set(keySchemaVersion, "2", nil)
}
return nil
})
if err != nil {
log.Fatal("Could not update datastore:", err.Error())
}
return return
} }

View File

@ -17,11 +17,11 @@ import (
) )
const ( const (
keyAccountExists = "account %s exists" keyAccountExists = "account.exists %s"
keyAccountVerified = "account %s verified" keyAccountVerified = "account.verified %s"
keyAccountName = "account %s name" // stores the 'preferred name' of the account, not casemapped keyAccountName = "account.name %s" // stores the 'preferred name' of the account, not casemapped
keyAccountRegTime = "account %s registered.time" keyAccountRegTime = "account.registered.time %s"
keyAccountCredentials = "account %s credentials" keyAccountCredentials = "account.credentials %s"
keyCertToAccount = "account.creds.certfp %s" keyCertToAccount = "account.creds.certfp %s"
) )
@ -80,7 +80,7 @@ func regHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
// removeFailedRegCreateData removes the data created by REG CREATE if the account creation fails early. // removeFailedRegCreateData removes the data created by REG CREATE if the account creation fails early.
func removeFailedRegCreateData(store buntdb.DB, account string) { func removeFailedRegCreateData(store *buntdb.DB, account string) {
// error is ignored here, we can't do much about it anyways // error is ignored here, we can't do much about it anyways
store.Update(func(tx *buntdb.Tx) error { store.Update(func(tx *buntdb.Tx) error {
tx.Delete(fmt.Sprintf(keyAccountExists, account)) tx.Delete(fmt.Sprintf(keyAccountExists, account))
@ -250,7 +250,7 @@ func regCreateHandler(server *Server, client *Client, msg ircmsg.IrcMessage) boo
// automatically complete registration // automatically complete registration
if callbackNamespace == "*" { if callbackNamespace == "*" {
err = server.store.Update(func(tx *buntdb.Tx) error { err = server.store.Update(func(tx *buntdb.Tx) error {
tx.Set(keyAccountVerified, "1", nil) tx.Set(fmt.Sprintf(keyAccountVerified, casefoldedAccount), "1", nil)
// load acct info inside store tx // load acct info inside store tx
account := ClientAccount{ account := ClientAccount{

View File

@ -8,11 +8,13 @@ package irc
import ( import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"strconv"
"time" "time"
"fmt" "fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/tidwall/buntdb"
) )
const restErr = "{\"error\":\"An unknown error occurred\"}" const restErr = "{\"error\":\"An unknown error occurred\"}"
@ -21,7 +23,10 @@ const restErr = "{\"error\":\"An unknown error occurred\"}"
// way to do it, given how HTTP handlers dispatch and work. // way to do it, given how HTTP handlers dispatch and work.
var restAPIServer *Server var restAPIServer *Server
type restVersionResp struct { type restInfoResp struct {
ServerName string `json:"server-name"`
NetworkName string `json:"network-name"`
Version string `json:"version"` Version string `json:"version"`
} }
@ -36,13 +41,13 @@ type restDLinesResp struct {
} }
type restAcct struct { type restAcct struct {
Name string Name string `json:"name"`
RegisteredAt time.Time `json:"registered-at"` RegisteredAt time.Time `json:"registered-at"`
Clients int Clients int `json:"clients"`
} }
type restAccountsResp struct { type restAccountsResp struct {
Accounts map[string]restAcct `json:"accounts"` Verified map[string]restAcct `json:"verified"`
} }
type restRehashResp struct { type restRehashResp struct {
@ -51,9 +56,11 @@ type restRehashResp struct {
Time time.Time `json:"time"` Time time.Time `json:"time"`
} }
func restVersion(w http.ResponseWriter, r *http.Request) { func restInfo(w http.ResponseWriter, r *http.Request) {
rs := restVersionResp{ rs := restInfoResp{
Version: SemVer, Version: SemVer,
ServerName: restAPIServer.name,
NetworkName: restAPIServer.networkName,
} }
b, err := json.Marshal(rs) b, err := json.Marshal(rs)
if err != nil { if err != nil {
@ -91,17 +98,44 @@ func restGetDLines(w http.ResponseWriter, r *http.Request) {
func restGetAccounts(w http.ResponseWriter, r *http.Request) { func restGetAccounts(w http.ResponseWriter, r *http.Request) {
rs := restAccountsResp{ rs := restAccountsResp{
Accounts: make(map[string]restAcct), Verified: make(map[string]restAcct),
} }
// get accts // get accounts
for key, info := range restAPIServer.accounts { err := restAPIServer.store.View(func(tx *buntdb.Tx) error {
rs.Accounts[key] = restAcct{ tx.AscendKeys("account.exists *", func(key, value string) bool {
Name: info.Name, key = key[len("account.exists "):]
RegisteredAt: info.RegisteredAt, _, err := tx.Get(fmt.Sprintf(keyAccountVerified, key))
Clients: len(info.Clients), verified := err == nil
} fmt.Println(fmt.Sprintf(keyAccountVerified, key))
}
// get other details
name, _ := tx.Get(fmt.Sprintf(keyAccountName, key))
regTimeStr, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, key))
regTimeInt, _ := strconv.ParseInt(regTimeStr, 10, 64)
regTime := time.Unix(regTimeInt, 0)
var clients int
acct := restAPIServer.accounts[key]
if acct != nil {
clients = len(acct.Clients)
}
if verified {
rs.Verified[key] = restAcct{
Name: name,
RegisteredAt: regTime,
Clients: clients,
}
} else {
//TODO(dan): Add to unverified list
}
return true // true to continue I guess?
})
return nil
})
b, err := json.Marshal(rs) b, err := json.Marshal(rs)
if err != nil { if err != nil {
@ -139,7 +173,7 @@ func (s *Server) startRestAPI() {
// GET methods // GET methods
rg := r.Methods("GET").Subrouter() rg := r.Methods("GET").Subrouter()
rg.HandleFunc("/version", restVersion) rg.HandleFunc("/info", restInfo)
rg.HandleFunc("/status", restStatus) rg.HandleFunc("/status", restStatus)
rg.HandleFunc("/dlines", restGetDLines) rg.HandleFunc("/dlines", restGetDLines)
rg.HandleFunc("/accounts", restGetAccounts) rg.HandleFunc("/accounts", restGetAccounts)

View File

@ -9,6 +9,7 @@ import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"log" "log"
"net" "net"
@ -32,6 +33,8 @@ var (
bannedFromServerMsg = ircmsg.MakeMessage(nil, "", "ERROR", "You are banned from this server (%s)") bannedFromServerMsg = ircmsg.MakeMessage(nil, "", "ERROR", "You are banned from this server (%s)")
bannedFromServerBytes, _ = bannedFromServerMsg.Line() bannedFromServerBytes, _ = bannedFromServerMsg.Line()
errDbOutOfDate = errors.New("Database schema is old.")
) )
// Limits holds the maximum limits for various things such as topic lengths // Limits holds the maximum limits for various things such as topic lengths
@ -102,7 +105,7 @@ type Server struct {
rehashSignal chan os.Signal rehashSignal chan os.Signal
restAPI *RestAPIConfig restAPI *RestAPIConfig
signals chan os.Signal signals chan os.Signal
store buntdb.DB store *buntdb.DB
whoWas *WhoWasList whoWas *WhoWasList
} }
@ -194,7 +197,22 @@ func NewServer(configFilename string, config *Config) *Server {
if err != nil { if err != nil {
log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error())) log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
} }
server.store = *db server.store = db
// check db version
err = server.store.View(func(tx *buntdb.Tx) error {
version, _ := tx.Get(keySchemaVersion)
if version != latestDbSchema {
log.Println(fmt.Sprintf("Database must be updated. Expected schema v%s, got v%s.", latestDbSchema, version))
return errDbOutOfDate
}
return nil
})
if err != nil {
// close the db
db.Close()
return nil
}
// load dlines // load dlines
server.loadDLines() server.loadDLines()

View File

@ -84,6 +84,10 @@ Options:
} else if arguments["run"].(bool) { } else if arguments["run"].(bool) {
irc.Log.SetLevel(config.Server.Log) irc.Log.SetLevel(config.Server.Log)
server := irc.NewServer(configfile, config) server := irc.NewServer(configfile, config)
if server == nil {
log.Println("Could not load server")
return
}
if !arguments["--quiet"].(bool) { if !arguments["--quiet"].(bool) {
log.Println(fmt.Sprintf("Oragono v%s running", irc.SemVer)) log.Println(fmt.Sprintf("Oragono v%s running", irc.SemVer))
defer log.Println(irc.SemVer, "exiting") defer log.Println(irc.SemVer, "exiting")