diff --git a/go.mod b/go.mod index ab9904b..37a0038 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module hectabit.org/burgernotes go 1.22 require ( + github.com/catalinc/hashcash v1.0.0 github.com/gin-contrib/sessions v1.0.1 github.com/gin-gonic/gin v1.10.0 github.com/mattn/go-sqlite3 v1.14.22 diff --git a/go.sum b/go.sum index f9a0ffa..51f0fc3 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/catalinc/hashcash v1.0.0 h1:DiI2kBNCczy7y3xJnLddIl7KGx0yP4B7irFZZ+yzzwc= +github.com/catalinc/hashcash v1.0.0/go.mod h1:ldWL6buwYCK4VqIkLbZuFbGUoJceSafm8duCEQYw9Jw= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= diff --git a/main.go b/main.go index 7a88ae3..cefee81 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/catalinc/hashcash" "log" "os" "regexp" @@ -24,6 +25,7 @@ import ( var ( conn *sql.DB + mem *sql.DB host string port int secretKey string @@ -133,7 +135,7 @@ func checkUsernameTaken(username string) (int, bool, error) { func getSession(session string) (int, int, error) { var id int var sessionId int - err := conn.QueryRow("SELECT sessionid, id FROM sessions WHERE session = ? LIMIT 1", session).Scan(&sessionId, &id) + err := mem.QueryRow("SELECT sessionid, id FROM sessions WHERE session = ? LIMIT 1", session).Scan(&sessionId, &id) if err != nil { return 0, 0, err } @@ -143,7 +145,7 @@ func getSession(session string) (int, int, error) { func getSessionFromId(sessionId int) (string, int, error) { var id int var session string - err := conn.QueryRow("SELECT session, id FROM sessions WHERE sessionid = ? LIMIT 1", sessionId).Scan(&session, &id) + err := mem.QueryRow("SELECT session, id FROM sessions WHERE sessionid = ? LIMIT 1", sessionId).Scan(&session, &id) if err != nil { return "", 0, err } @@ -208,11 +210,22 @@ func migrateDb() { if strings.ToLower(answer) == "y" { _, err = conn.Exec("ALTER TABLE users DROP COLUMN versionTwoLegacyPassword") if err != nil { - log.Println("[WARN] Unknown while migrating database (1/2):", err) + log.Println("[WARN] Unknown while migrating database (1/3):", err) log.Println("[INFO] This is likely because your database is already migrated. This is not a problem, and Burgernotes does not need this removed - it is just for cleanup") return } _, err = conn.Exec("CREATE TABLE oauth (id INTEGER NOT NULL, oauthProvider TEXT NOT NULL, encryptedPasswd TEXT NOT NULL)") + if err != nil { + log.Println("[WARN] Unknown while migrating database (2/3):", err) + log.Println("[INFO] This is likely because your database is already migrated. This is not a problem, but if it is not, it may cause issues with OAuth2") + return + } + _, err = conn.Exec("DROP TABLE sessions") + if err != nil { + log.Println("[WARN] Unknown while migrating database (3/3):", err) + log.Println("[INFO] This is likely because your database is already migrated. This is not a problem, and Burgernotes does not need this removed - it is just for cleanup") + return + } } else if answer == ":3" { log.Println("[:3] :3") } else { @@ -269,10 +282,30 @@ func main() { defer func(conn *sql.DB) { err := conn.Close() if err != nil { - log.Println("[ERROR] Unknown in main() defer:", err) + log.Println("[ERROR] Unknown in main() conn defer:", err) } }(conn) + mem, err = sql.Open("sqlite3", ":memory: cache=shared") + if err != nil { + log.Fatalln("[FATAL] Cannot open session database:", err) + } + defer func(mem *sql.DB) { + err := mem.Close() + if err != nil { + log.Println("[ERROR] Unknown in main() mem defer:", err) + } + }(mem) + + _, err = mem.Exec("CREATE TABLE sessions (sessionid INTEGER PRIMARY KEY AUTOINCREMENT, session TEXT NOT NULL, id INTEGER NOT NULL, device TEXT NOT NULL DEFAULT '?')") + if err != nil { + if err.Error() == "table sessions already exists" { + log.Println("[INFO] Session table already exists") + } else { + log.Fatalln("[FATAL] Cannot create session table:", err) + } + } + if len(os.Args) > 1 { if os.Args[1] == "init_db" { initDb() @@ -327,6 +360,18 @@ func main() { c.JSON(400, gin.H{"error": "Invalid JSON"}) return } + stamp, ok := data["stamp"].(string) + if !ok { + c.JSON(400, gin.H{"error": "Invalid JSON"}) + return + } + + pow := hashcash.New(20, 16, "I love burgernotes!") + ok = pow.Check(stamp) + if !ok { + c.JSON(400, gin.H{"error": "Invalid hashcash stamp"}) + return + } if username == "" || password == "" || len(username) > 20 || !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(username) { c.JSON(422, gin.H{"error": "Invalid username or password"}) @@ -379,7 +424,7 @@ func main() { c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-SESSIONSALT"}) return } - _, err = conn.Exec("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)", token, userid, c.Request.Header.Get("User-Agent")) + _, err = mem.Exec("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)", token, userid, c.Request.Header.Get("User-Agent")) if err != nil { log.Println("[ERROR] Unknown in /api/signup session Exec():", err) c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-SESSIONINSERT"}) @@ -448,7 +493,7 @@ func main() { return } - _, err = conn.Exec("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)", token, userid, c.Request.Header.Get("User-Agent")) + _, err = mem.Exec("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)", token, userid, c.Request.Header.Get("User-Agent")) if err != nil { log.Println("[ERROR] Unknown in /api/login session Exec():", err) c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LOGIN-SESSIONINSERT"}) @@ -1121,7 +1166,7 @@ func main() { return } - _, err = conn.Exec("DELETE FROM sessions WHERE id = ?", userid) + _, err = mem.Exec("DELETE FROM sessions WHERE id = ?", userid) if err != nil { log.Println("[ERROR] Unknown in /api/deleteaccount session Exec():", err) c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-DELETEACCOUNT-SESSIONDELETE"}) @@ -1151,7 +1196,7 @@ func main() { return } - rows, err := conn.Query("SELECT sessionid, session, device FROM sessions WHERE id = ? ORDER BY id DESC", userid) + rows, err := mem.Query("SELECT sessionid, session, device FROM sessions WHERE id = ? ORDER BY id DESC", userid) if err != nil { if errors.Is(err, sql.ErrNoRows) { c.JSON(200, []map[string]interface{}{}) @@ -1236,7 +1281,7 @@ func main() { c.JSON(403, gin.H{"error": "Session does not belong to user"}) return } else { - _, err := conn.Exec("DELETE FROM sessions WHERE sessionid = ?", sessionId) + _, err := mem.Exec("DELETE FROM sessions WHERE sessionid = ?", sessionId) if err != nil { log.Println("[ERROR] Unknown in /api/sessions/remove Exec():", err) c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SESSIONS-REMOVE-DBDELETE"}) diff --git a/schema.sql b/schema.sql index cb8378d..2ed6fe1 100644 --- a/schema.sql +++ b/schema.sql @@ -1,6 +1,6 @@ DROP TABLE IF EXISTS users; DROP TABLE IF EXISTS notes; -DROP TABLE IF EXISTS sessions; +DROP TABLE IF EXISTS oauth; CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -18,13 +18,6 @@ CREATE TABLE notes ( title TEXT NOT NULL ); -CREATE TABLE sessions ( - sessionid INTEGER PRIMARY KEY AUTOINCREMENT, - session TEXT NOT NULL, - id INTEGER NOT NULL, - device TEXT NOT NULL DEFAULT '?' -); - CREATE TABLE oauth ( id INTEGER NOT NULL, oauthProvider TEXT NOT NULL,