mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-28 23:19:30 +01:00
Handle db better, fix bug, update db schema, rest
This commit is contained in:
parent
65cb932e46
commit
6d6c1936cc
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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{
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user