@@ -25,6 +25,7 @@ func initArgs() { | |||
flag.StringVar(&sqlServer, "sql", sqlServerDefault, "SQL Database type") | |||
flag.BoolVar(&buildTables, "build-db", false, "Build database tables on startup") | |||
flag.BoolVar(&populateTables, "populate-db", false, "Populate database with test data") | |||
flag.BoolVar(&randomizeTokens, "random-tokens", false, "Generate tokens with some random bytes") | |||
} | |||
func parseArgs() { | |||
@@ -9,6 +9,7 @@ import ( | |||
"net/http" | |||
"net/url" | |||
"strconv" | |||
"strings" | |||
) | |||
func boardHandler(w http.ResponseWriter, r *http.Request) { | |||
@@ -136,6 +137,22 @@ func newEntryHandler(w http.ResponseWriter, r *http.Request) { | |||
errorResponse(405, "Non-POST method not allowed at this endpoint", w, r) | |||
return | |||
} | |||
// check for API token | |||
tokenHeader := r.Header.Get("Authorization") | |||
tokenSplit := strings.Split(tokenHeader, " ") | |||
if tokenHeader == "" || len(tokenSplit) != 2 || tokenSplit[1] == "" { | |||
errorResponse(401, "Missing or invalid authorization header", w, r) | |||
return | |||
} | |||
key, keyErr := keyByToken(tokenSplit[1]) | |||
if keyErr != nil || key.ID == 0 { | |||
errorResponse(401, "Invalid token", w, r) | |||
return | |||
} | |||
if !key.IsEnabled() { | |||
errorResponse(403, "Invalid token", w, r) | |||
return | |||
} | |||
data, readErr := ioutil.ReadAll(r.Body) | |||
if readErr != nil { | |||
fmt.Println(readErr) | |||
@@ -149,6 +166,10 @@ func newEntryHandler(w http.ResponseWriter, r *http.Request) { | |||
errorResponse(400, "Unable to convert request to JSON: "+jsonErr.Error(), w, r) | |||
return | |||
} | |||
if newEntry.PlayerID != key.Player && !key.IsMultiuser() { | |||
errorResponse(403, "Invalid authorization for player", w, r) | |||
return | |||
} | |||
sqlErr := newEntrySql(newEntry.Score, newEntry.PlayerID, newEntry.BoardID) | |||
if sqlErr != nil { | |||
fmt.Println(sqlErr) | |||
@@ -160,6 +181,43 @@ func newEntryHandler(w http.ResponseWriter, r *http.Request) { | |||
errorResponse(200, "New entry created", w, r) | |||
} | |||
func newKeyHandler(w http.ResponseWriter, r *http.Request) { | |||
w.Header().Add("Content-Type", "application/json") | |||
w.Header().Add("Access-Control-Allow-Origin", "*") | |||
if r.Method != "POST" { | |||
errorResponse(405, "Non-POST method not allowed at this endpoint", w, r) | |||
return | |||
} | |||
data, readErr := ioutil.ReadAll(r.Body) | |||
if readErr != nil { | |||
fmt.Println(readErr) | |||
errorResponse(500, "Unable to read HTTP request body: "+readErr.Error(), w, r) | |||
return | |||
} | |||
result := NewResult("", r.URL.String()) | |||
newKey, jsonErr := UnmarshalNewKeyJSON(data) | |||
if jsonErr != nil { | |||
errorResponse(400, "Unable to convert request to JSON: "+jsonErr.Error(), w, r) | |||
return | |||
} | |||
key, sqlErr := newKeySql(newKey.PlayerID) | |||
if sqlErr != nil { | |||
fmt.Println(sqlErr) | |||
errorResponse(500, "Key could not be created: "+sqlErr.Error(), w, r) | |||
return | |||
} | |||
jsonObj, _ := key.JsonObject() | |||
result.Items = []interface{}{jsonObj} | |||
result.Query = fmt.Sprintf("new key[player id: %d]", newKey.PlayerID) | |||
result.Complete() | |||
data, err := json.Marshal(result) | |||
if err != nil { | |||
errorResponse(500, "Unable convert result to JSON: "+err.Error(), w, r) | |||
return | |||
} | |||
w.Write(data) | |||
} | |||
func exampleHandler(w http.ResponseWriter, r *http.Request) { | |||
// useless function please ignore | |||
} | |||
@@ -46,6 +46,26 @@ func UnmarshalNewEntryJSON(data []byte) (NewEntryJSON, error) { | |||
return neJson, nil | |||
} | |||
// KeyJSON an API key for making new entry requests | |||
type KeyJSON struct { | |||
Token string | |||
PlayerID int64 | |||
} | |||
// NewKeyJSON a new API key to be generated | |||
type NewKeyJSON struct { | |||
PlayerID int64 | |||
} | |||
func UnmarshalNewKeyJSON(data []byte) (NewKeyJSON, error) { | |||
var nkJson NewKeyJSON | |||
jsonErr := json.Unmarshal(data, &nkJson) | |||
if jsonErr != nil { | |||
return nkJson, jsonErr | |||
} | |||
return nkJson, nil | |||
} | |||
// PlayerJSON a player | |||
type PlayerJSON struct { | |||
ID int64 | |||
@@ -69,7 +89,7 @@ type ErrorJSON struct { | |||
type Result struct { | |||
StatusCode int | |||
Items []interface{} | |||
Elapsed int64 // query time (ms) | |||
Elapsed int64 // query time (ns) | |||
Count int | |||
Query string | |||
URL string | |||
@@ -88,5 +108,5 @@ func NewResult(q string, url string) (r Result) { | |||
// Complete finish the result | |||
func (r *Result) Complete() { | |||
r.Count = len(r.Items) | |||
r.Elapsed = time.Since(r.Start).Milliseconds() | |||
r.Elapsed = time.Since(r.Start).Nanoseconds() | |||
} |
@@ -31,6 +31,7 @@ func init() { | |||
serverMux.HandleFunc("/board", boardHandler) | |||
serverMux.HandleFunc("/player", playerHandler) | |||
serverMux.HandleFunc("/record", newEntryHandler) | |||
serverMux.HandleFunc("/token", newKeyHandler) | |||
handler = serverMux | |||
} | |||
@@ -55,17 +55,17 @@ func sqlClose() error { | |||
func boardByName(name string) (*Board, error) { | |||
b := &Board{} | |||
return b, db.QueryRow("SELECT * FROM Boards WHERE name=? LIMIT 1", name).Scan(b.Intake()...) | |||
return b, db.QueryRow("SELECT * FROM Boards WHERE name=? LIMIT 1;", name).Scan(b.Intake()...) | |||
} | |||
func playerByName(name string) (*Player, error) { | |||
p := &Player{} | |||
return p, db.QueryRow("SELECT * FROM Players WHERE name=? LIMTI 1", name).Scan(p.Intake()...) | |||
return p, db.QueryRow("SELECT * FROM Players WHERE name=? LIMTI 1;", name).Scan(p.Intake()...) | |||
} | |||
func newEntrySql(score, player, board int64) error { | |||
tx, _ := db.Begin() | |||
stmt, _ := tx.Prepare("INSERT INTO Entries(score, player, board, time) VALUES (?, ?, ?, ?)") | |||
stmt, _ := tx.Prepare("INSERT INTO Entries(score, player, board, time) VALUES (?, ?, ?, ?);") | |||
_, err := stmt.Exec(score, player, board, time.Now().Unix()) | |||
if err != nil { | |||
tx.Rollback() | |||
@@ -75,6 +75,29 @@ func newEntrySql(score, player, board int64) error { | |||
return nil | |||
} | |||
func newKeySql(player int64) (*Key, error) { | |||
tx, _ := db.Begin() | |||
newKey := &Key{Token: "new", Player: player, Time: time.Now().Unix()} | |||
stmt, _ := tx.Prepare("INSERT INTO Keys(token, player, time) VALUES (?, ?, ?);") | |||
_, err := stmt.Exec(newKey.Token, newKey.Player, newKey.Time) | |||
if err != nil { | |||
tx.Rollback() | |||
return nil, err | |||
} | |||
tx.Commit() | |||
db.QueryRow("SELECT * FROM Keys WHERE token=? AND player=? AND time=?", newKey.Token, newKey.Player, newKey.Time).Scan(newKey.Intake()...) | |||
tokenErr := newKey.GenerateToken() | |||
if tokenErr != nil { | |||
return nil, tokenErr | |||
} | |||
return newKey, newKey.Commit() | |||
} | |||
func keyByToken(token string) (*Key, error) { | |||
k := &Key{} | |||
return k, db.QueryRow("SELECT * FROM Keys WHERE token=? LIMIT 1;", token).Scan(k.Intake()...) | |||
} | |||
// internal operations | |||
func sqlBuildTables() { | |||
transaction, txErr := db.Begin() | |||
@@ -87,7 +110,8 @@ func sqlBuildTables() { | |||
// build real tables | |||
transaction.Exec("CREATE TABLE IF NOT EXISTS Players (id INTEGER PRIMARY KEY, name TEXT NOT NULL);") | |||
transaction.Exec("CREATE TABLE IF NOT EXISTS Boards (id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL);") | |||
transaction.Exec("CREATE TABLE IF NOT EXISTS Entries (id INTEGER PRIMARY KEY, rank INTEGER NOT NULL DEFAULT -1, score INTEGER NOT NULL, player INTEGER NOT NULL, board INTEGER NOT NULL, time INTEGER NOT NULL, metadata BLOB NOT NULL DEFAULT 0, FOREIGN KEY(player) REFERENCES Players(id), FOREIGN KEY(board) REFERENCES Boards(id));") | |||
transaction.Exec("CREATE TABLE IF NOT EXISTS Entries (id INTEGER PRIMARY KEY, rank INTEGER NOT NULL DEFAULT -1, score INTEGER NOT NULL, player INTEGER NOT NULL, board INTEGER NOT NULL, time INTEGER NOT NULL, metadata BLOB NOT NULL DEFAULT x'FF', FOREIGN KEY(player) REFERENCES Players(id), FOREIGN KEY(board) REFERENCES Boards(id));") | |||
transaction.Exec("CREATE TABLE IF NOT EXISTS Keys (id INTEGER PRIMARY KEY, token TEXT NOT NULL, player INTEGER NOT NULL, time INTEGER NOT NULL, metadata BLOB NOT NULL DEFAULT x'FF', FOREIGN KEY(player) REFERENCES Players(id));") | |||
transaction.Commit() | |||
} | |||
@@ -125,19 +149,20 @@ func sqlPopulateTables() { | |||
} | |||
} | |||
now := time.Now().Unix() | |||
metadata := []byte{0x00, 0x00, 0x00, 0x00} | |||
entries := []*Entry{ | |||
&Entry{ID: 1, Rank: 1, Score: 1000, Player: players[0].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 2, Rank: 2, Score: 900, Player: players[1].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 3, Rank: 3, Score: 400, Player: players[2].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 4, Rank: 4, Score: 350, Player: players[3].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 5, Rank: 5, Score: 350, Player: players[4].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 6, Rank: 6, Score: 250, Player: players[5].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 7, Rank: 7, Score: 200, Player: players[6].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 8, Rank: 8, Score: 175, Player: players[7].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 9, Rank: 9, Score: 150, Player: players[8].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 10, Rank: 10, Score: 140, Player: players[9].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 11, Rank: 11, Score: 10, Player: players[10].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 12, Rank: 12, Score: 60, Player: players[11].ID, Board: boards[0].ID, Time: now}, | |||
&Entry{ID: 1, Rank: 1, Score: 1000, Player: players[0].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 2, Rank: 2, Score: 900, Player: players[1].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 3, Rank: 3, Score: 400, Player: players[2].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 4, Rank: 4, Score: 350, Player: players[3].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 5, Rank: 5, Score: 350, Player: players[4].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 6, Rank: 6, Score: 250, Player: players[5].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 7, Rank: 7, Score: 200, Player: players[6].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 8, Rank: 8, Score: 175, Player: players[7].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 9, Rank: 9, Score: 150, Player: players[8].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 10, Rank: 10, Score: 140, Player: players[9].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 11, Rank: 11, Score: 10, Player: players[10].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 12, Rank: 12, Score: 60, Player: players[11].ID, Board: boards[0].ID, Time: now, Metadata: metadata}, | |||
&Entry{ID: 13, Rank: 13, Score: 13, Player: players[12].ID, Board: boards[0].ID, Time: now}, | |||
} | |||
for _, e := range entries { | |||
@@ -146,4 +171,22 @@ func sqlPopulateTables() { | |||
fmt.Printf("Error creating entry %d: %s\n", e.ID, err) | |||
} | |||
} | |||
metadata = []byte{0x7E} | |||
keys := []*Key{ | |||
&Key{ID: 1, Token: "", Player: players[0].ID, Time: now, Metadata: metadata}, | |||
&Key{ID: 2, Token: "", Player: players[1].ID, Time: now, Metadata: metadata}, | |||
} | |||
for _, k := range keys { | |||
genErr := k.GenerateToken() | |||
if genErr != nil { | |||
fmt.Printf("Error generating token for key %d, %s\n", k.ID, genErr) | |||
} | |||
//fmt.Printf("Key %d token: %s\n", k.ID, k.Token) | |||
err := k.Commit() | |||
if err != nil { | |||
fmt.Printf("Error creating key %d, %s\n", k.ID, err) | |||
} | |||
// bitwise op test | |||
//fmt.Printf("Key &d is superuser:%t, enabled:%t\n", k.ID, k.IsSuperuser(), k.IsEnabled()) | |||
} | |||
} |
@@ -5,10 +5,18 @@ package main | |||
import ( | |||
"encoding/json" | |||
"strconv" | |||
"crypto/sha512" | |||
"crypto/rand" | |||
"encoding/binary" | |||
"math/big" | |||
// test | |||
//"fmt" | |||
) | |||
var ( | |||
randomizeTokens bool | |||
) | |||
type Jsonable interface { | |||
Json() ([]byte, error) | |||
JsonPretty() ([]byte, error) | |||
@@ -306,3 +314,123 @@ func (e *Entry) Intake() []interface{} { | |||
func (e *Entry) Output() []interface{} { | |||
return []interface{}{e.ID, e.Rank, e.Score, e.Player, e.Board, e.Time, e.Metadata} | |||
} | |||
type Key struct { | |||
ID int64 | |||
Token string | |||
Player int64 | |||
Time int64 // Created time (seconds since Unix epoch) | |||
Metadata []byte | |||
} | |||
func LoadKey(id int64) *Key { | |||
k := &Key{ID: id} | |||
loadErr := k.Load() | |||
if loadErr != nil { | |||
return nil | |||
} | |||
return k | |||
} | |||
func (k *Key) Load() error { | |||
return db.QueryRow("SELECT * FROM Keys WHERE id=? LIMIT 1;", k.ID).Scan(k.Intake()...) | |||
} | |||
func (k *Key) Commit() error { | |||
tx, _ := db.Begin() | |||
statement, _ := tx.Prepare("INSERT OR REPLACE INTO Keys(id, token, player, time, metadata) VALUES (?, ?, ?, ?, ?);") | |||
_, err := statement.Exec(k.Output()...) | |||
if err != nil { | |||
tx.Rollback() | |||
return err | |||
} | |||
return tx.Commit() | |||
} | |||
func (k *Key) IsEnabled() bool { | |||
return (k.Metadata[0] & 0b00000001) == 0 | |||
} | |||
func (k *Key) Disable() { | |||
k.Metadata[0] = k.Metadata[0] | 0b00000001 | |||
} | |||
func (k *Key) IsSuperuser() bool { | |||
return (k.Metadata[0] & 0b00000100) == 0 | |||
} | |||
func (k *Key) Promote() { | |||
k.Metadata[0] = k.Metadata[0] | 0b00000100 | |||
} | |||
func (k *Key) IsMultiuser() bool { | |||
return (k.Metadata[0] & 0b00010000) == 0 | |||
} | |||
func (k *Key) Develop() { | |||
k.Metadata[0] = k.Metadata[0] | 0b00010000 | |||
} | |||
func (k *Key) GenerateToken() error { | |||
buf_int64 := make([]byte, 8) // 8 bytes = 64 bits | |||
input := []byte{} | |||
if randomizeTokens { | |||
max := big.NewInt(2^16-1) | |||
for i := 0; i < 512; i++ { | |||
// generate randomness | |||
num, _ := rand.Int(rand.Reader, max) | |||
input = append(input, num.Bytes()...) | |||
} | |||
} | |||
binary.PutVarint(buf_int64, k.ID) | |||
input = append(input, buf_int64...) | |||
binary.PutVarint(buf_int64, k.Player) | |||
input = append(input, buf_int64...) | |||
binary.PutVarint(buf_int64, k.Time) | |||
input = append(input, buf_int64...) | |||
bToken := sha512.Sum512(input) | |||
//k.Token = string(bToken) | |||
k.Token = "" | |||
for _, b := range bToken { | |||
tmp_b := b & 0b01111111 // Valid 7-bit ASCII values only | |||
for !((tmp_b > 47 && tmp_b < 58) || (tmp_b > 64 && tmp_b < 91) || (tmp_b > 96 && tmp_b < 123)) { | |||
tmp_b = sha512.Sum512([]byte{tmp_b})[1] | |||
tmp_b = tmp_b & 0b01111111 | |||
} | |||
k.Token += string(tmp_b) | |||
} | |||
return nil | |||
} | |||
// Implementation of Jsonable | |||
func (k *Key) Json() ([]byte, error) { | |||
var data []byte | |||
jsonObj, err := k.JsonObject() | |||
if err != nil { | |||
return data, err | |||
} | |||
return json.Marshal(jsonObj) | |||
} | |||
func (k *Key) JsonPretty() ([]byte, error) { | |||
var data []byte | |||
jsonObj, err := k.JsonObject() | |||
if err != nil { | |||
return data, err | |||
} | |||
return json.MarshalIndent(jsonObj, "", " ") | |||
} | |||
func (k *Key) JsonObject() (interface{}, error) { | |||
jsonObj := KeyJSON{Token: k.Token, PlayerID: k.Player} | |||
return jsonObj, nil | |||
} | |||
// Implementation of Rower | |||
func (k *Key) Intake() []interface{} { | |||
return []interface{}{&k.ID, &k.Token, &k.Player, &k.Time, &k.Metadata} | |||
} | |||
func (k *Key) Output() []interface{} { | |||
return []interface{}{k.ID, k.Token, k.Player, k.Time, k.Metadata} | |||
} |