Sfoglia il codice sorgente

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

tags/v0.5.0
Daniel Oaks 7 anni fa
parent
commit
6d6c1936cc
6 ha cambiato i file con 141 aggiunte e 28 eliminazioni
  1. 2
    0
      CHANGELOG.md
  2. 57
    2
      irc/database.go
  3. 7
    7
      irc/registration.go
  4. 51
    17
      irc/rest_api.go
  5. 20
    2
      irc/server.go
  6. 4
    0
      oragono.go

+ 2
- 0
CHANGELOG.md Vedi File

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

+ 57
- 2
irc/database.go Vedi File

@@ -8,6 +8,7 @@ import (
8 8
 	"fmt"
9 9
 	"log"
10 10
 	"os"
11
+	"strings"
11 12
 
12 13
 	"github.com/tidwall/buntdb"
13 14
 )
@@ -15,6 +16,8 @@ import (
15 16
 const (
16 17
 	// 'version' of the database schema
17 18
 	keySchemaVersion = "db.version"
19
+	// latest schema of the db
20
+	latestDbSchema = "2"
18 21
 	// key for the primary salt used by the ircd
19 22
 	keySalt = "crypto.salt"
20 23
 )
@@ -40,16 +43,68 @@ func InitDB(path string) {
40 43
 		tx.Set(keySalt, encodedSalt, nil)
41 44
 
42 45
 		// set schema version
43
-		tx.Set(keySchemaVersion, "1", nil)
46
+		tx.Set(keySchemaVersion, "2", nil)
44 47
 		return nil
45 48
 	})
46 49
 
47 50
 	if err != nil {
48
-		log.Fatal("Could not save bunt store:", err.Error())
51
+		log.Fatal("Could not save datastore:", err.Error())
49 52
 	}
50 53
 }
51 54
 
52 55
 // UpgradeDB upgrades the datastore to the latest schema.
53 56
 func UpgradeDB(path string) {
57
+	store, err := buntdb.Open(path)
58
+	if err != nil {
59
+		log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
60
+	}
61
+	defer store.Close()
62
+
63
+	err = store.Update(func(tx *buntdb.Tx) error {
64
+		version, _ := tx.Get(keySchemaVersion)
65
+
66
+		// == version 1 -> 2 ==
67
+		// account key changes and account.verified key bugfix.
68
+		if version == "1" {
69
+			log.Println("Updating store v1 to v2")
70
+
71
+			var keysToRemove []string
72
+			newKeys := make(map[string]string)
73
+
74
+			tx.AscendKeys("account *", func(key, value string) bool {
75
+				keysToRemove = append(keysToRemove, key)
76
+				splitkey := strings.Split(key, " ")
77
+
78
+				// work around bug
79
+				if splitkey[2] == "exists" {
80
+					// manually create new verified key
81
+					newVerifiedKey := fmt.Sprintf("%s.verified %s", splitkey[0], splitkey[1])
82
+					newKeys[newVerifiedKey] = "1"
83
+				} else if splitkey[1] == "%s" {
84
+					return true
85
+				}
86
+
87
+				newKey := fmt.Sprintf("%s.%s %s", splitkey[0], splitkey[2], splitkey[1])
88
+				newKeys[newKey] = value
89
+
90
+				return true
91
+			})
92
+
93
+			for _, key := range keysToRemove {
94
+				tx.Delete(key)
95
+			}
96
+			for key, value := range newKeys {
97
+				tx.Set(key, value, nil)
98
+			}
99
+
100
+			tx.Set(keySchemaVersion, "2", nil)
101
+		}
102
+
103
+		return nil
104
+	})
105
+	if err != nil {
106
+		log.Fatal("Could not update datastore:", err.Error())
107
+	}
108
+
54 109
 	return
55 110
 }

+ 7
- 7
irc/registration.go Vedi File

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

+ 51
- 17
irc/rest_api.go Vedi File

@@ -8,11 +8,13 @@ package irc
8 8
 import (
9 9
 	"encoding/json"
10 10
 	"net/http"
11
+	"strconv"
11 12
 	"time"
12 13
 
13 14
 	"fmt"
14 15
 
15 16
 	"github.com/gorilla/mux"
17
+	"github.com/tidwall/buntdb"
16 18
 )
17 19
 
18 20
 const restErr = "{\"error\":\"An unknown error occurred\"}"
@@ -21,7 +23,10 @@ const restErr = "{\"error\":\"An unknown error occurred\"}"
21 23
 // way to do it, given how HTTP handlers dispatch and work.
22 24
 var restAPIServer *Server
23 25
 
24
-type restVersionResp struct {
26
+type restInfoResp struct {
27
+	ServerName  string `json:"server-name"`
28
+	NetworkName string `json:"network-name"`
29
+
25 30
 	Version string `json:"version"`
26 31
 }
27 32
 
@@ -36,13 +41,13 @@ type restDLinesResp struct {
36 41
 }
37 42
 
38 43
 type restAcct struct {
39
-	Name         string
44
+	Name         string    `json:"name"`
40 45
 	RegisteredAt time.Time `json:"registered-at"`
41
-	Clients      int
46
+	Clients      int       `json:"clients"`
42 47
 }
43 48
 
44 49
 type restAccountsResp struct {
45
-	Accounts map[string]restAcct `json:"accounts"`
50
+	Verified map[string]restAcct `json:"verified"`
46 51
 }
47 52
 
48 53
 type restRehashResp struct {
@@ -51,9 +56,11 @@ type restRehashResp struct {
51 56
 	Time       time.Time `json:"time"`
52 57
 }
53 58
 
54
-func restVersion(w http.ResponseWriter, r *http.Request) {
55
-	rs := restVersionResp{
56
-		Version: SemVer,
59
+func restInfo(w http.ResponseWriter, r *http.Request) {
60
+	rs := restInfoResp{
61
+		Version:     SemVer,
62
+		ServerName:  restAPIServer.name,
63
+		NetworkName: restAPIServer.networkName,
57 64
 	}
58 65
 	b, err := json.Marshal(rs)
59 66
 	if err != nil {
@@ -91,17 +98,44 @@ func restGetDLines(w http.ResponseWriter, r *http.Request) {
91 98
 
92 99
 func restGetAccounts(w http.ResponseWriter, r *http.Request) {
93 100
 	rs := restAccountsResp{
94
-		Accounts: make(map[string]restAcct),
101
+		Verified: make(map[string]restAcct),
95 102
 	}
96 103
 
97
-	// get accts
98
-	for key, info := range restAPIServer.accounts {
99
-		rs.Accounts[key] = restAcct{
100
-			Name:         info.Name,
101
-			RegisteredAt: info.RegisteredAt,
102
-			Clients:      len(info.Clients),
103
-		}
104
-	}
104
+	// get accounts
105
+	err := restAPIServer.store.View(func(tx *buntdb.Tx) error {
106
+		tx.AscendKeys("account.exists *", func(key, value string) bool {
107
+			key = key[len("account.exists "):]
108
+			_, err := tx.Get(fmt.Sprintf(keyAccountVerified, key))
109
+			verified := err == nil
110
+			fmt.Println(fmt.Sprintf(keyAccountVerified, key))
111
+
112
+			// get other details
113
+			name, _ := tx.Get(fmt.Sprintf(keyAccountName, key))
114
+			regTimeStr, _ := tx.Get(fmt.Sprintf(keyAccountRegTime, key))
115
+			regTimeInt, _ := strconv.ParseInt(regTimeStr, 10, 64)
116
+			regTime := time.Unix(regTimeInt, 0)
117
+
118
+			var clients int
119
+			acct := restAPIServer.accounts[key]
120
+			if acct != nil {
121
+				clients = len(acct.Clients)
122
+			}
123
+
124
+			if verified {
125
+				rs.Verified[key] = restAcct{
126
+					Name:         name,
127
+					RegisteredAt: regTime,
128
+					Clients:      clients,
129
+				}
130
+			} else {
131
+				//TODO(dan): Add to unverified list
132
+			}
133
+
134
+			return true // true to continue I guess?
135
+		})
136
+
137
+		return nil
138
+	})
105 139
 
106 140
 	b, err := json.Marshal(rs)
107 141
 	if err != nil {
@@ -139,7 +173,7 @@ func (s *Server) startRestAPI() {
139 173
 
140 174
 	// GET methods
141 175
 	rg := r.Methods("GET").Subrouter()
142
-	rg.HandleFunc("/version", restVersion)
176
+	rg.HandleFunc("/info", restInfo)
143 177
 	rg.HandleFunc("/status", restStatus)
144 178
 	rg.HandleFunc("/dlines", restGetDLines)
145 179
 	rg.HandleFunc("/accounts", restGetAccounts)

+ 20
- 2
irc/server.go Vedi File

@@ -9,6 +9,7 @@ import (
9 9
 	"bufio"
10 10
 	"crypto/tls"
11 11
 	"encoding/base64"
12
+	"errors"
12 13
 	"fmt"
13 14
 	"log"
14 15
 	"net"
@@ -32,6 +33,8 @@ var (
32 33
 
33 34
 	bannedFromServerMsg      = ircmsg.MakeMessage(nil, "", "ERROR", "You are banned from this server (%s)")
34 35
 	bannedFromServerBytes, _ = bannedFromServerMsg.Line()
36
+
37
+	errDbOutOfDate = errors.New("Database schema is old.")
35 38
 )
36 39
 
37 40
 // Limits holds the maximum limits for various things such as topic lengths
@@ -102,7 +105,7 @@ type Server struct {
102 105
 	rehashSignal          chan os.Signal
103 106
 	restAPI               *RestAPIConfig
104 107
 	signals               chan os.Signal
105
-	store                 buntdb.DB
108
+	store                 *buntdb.DB
106 109
 	whoWas                *WhoWasList
107 110
 }
108 111
 
@@ -194,7 +197,22 @@ func NewServer(configFilename string, config *Config) *Server {
194 197
 	if err != nil {
195 198
 		log.Fatal(fmt.Sprintf("Failed to open datastore: %s", err.Error()))
196 199
 	}
197
-	server.store = *db
200
+	server.store = db
201
+
202
+	// check db version
203
+	err = server.store.View(func(tx *buntdb.Tx) error {
204
+		version, _ := tx.Get(keySchemaVersion)
205
+		if version != latestDbSchema {
206
+			log.Println(fmt.Sprintf("Database must be updated. Expected schema v%s, got v%s.", latestDbSchema, version))
207
+			return errDbOutOfDate
208
+		}
209
+		return nil
210
+	})
211
+	if err != nil {
212
+		// close the db
213
+		db.Close()
214
+		return nil
215
+	}
198 216
 
199 217
 	// load dlines
200 218
 	server.loadDLines()

+ 4
- 0
oragono.go Vedi File

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

Loading…
Annulla
Salva