diff --git a/main.go b/main.go index 3ec95cf..cb17786 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,8 @@ package main import ( - library "git.ailur.dev/ailur/fg-library/v3" - "errors" + library "git.ailur.dev/ailur/fg-library/v3" "io" "log" "mime" @@ -393,7 +392,17 @@ func newFileServer(root string, directoryListing bool, path string) http.Handler } } - file, err := os.Open(filepath.Join(root, filepath.FromSlash(r.URL.Path))) + absolutePath, err := filepath.Abs(filepath.Join(root, filepath.FromSlash(r.URL.Path))) + if err != nil { + serverError(w, 500) + } + + if !strings.HasPrefix(absolutePath, root) { + serverError(w, 403) + return + } + + file, err := os.Open(absolutePath) if err != nil { serverError(w, 500) return @@ -561,6 +570,12 @@ func svInit(message library.InterServiceMessage) { return } + _, err = pluginConn.Exec("PRAGMA journal_mode=WAL") + if err != nil { + message.Respond(library.InternalError, err, dummyInfo) + return + } + db = library.Database{ DB: pluginConn, DBType: library.Sqlite, diff --git a/services-src/auth/main.go b/services-src/auth/main.go index c78c894..861f64b 100644 --- a/services-src/auth/main.go +++ b/services-src/auth/main.go @@ -266,10 +266,6 @@ func Main(information *library.ServiceInitializationInformation) { if err != nil { logFunc(err.Error(), 3, information) } - _, err = mem.Exec("DROP TABLE IF EXISTS challengeResponse") - if err != nil { - logFunc(err.Error(), 3, information) - } // Create the sessions table _, err = mem.Exec("CREATE TABLE sessions (id BLOB NOT NULL, session TEXT NOT NULL, device TEXT NOT NULL DEFAULT '?')") if err != nil { @@ -285,11 +281,6 @@ func Main(information *library.ServiceInitializationInformation) { if err != nil { logFunc(err.Error(), 3, information) } - // Create the challenge-response table - _, err = mem.Exec("CREATE TABLE challengeResponse (challenge TEXT NOT NULL UNIQUE, userId BLOB NOT NULL, expires INTEGER NOT NULL)") - if err != nil { - logFunc(err.Error(), 3, information) - } // Set up the signing keys // Check if the global table has the keys @@ -632,30 +623,6 @@ func Main(information *library.ServiceInitializationInformation) { }) router.Post("/api/loginChallenge", func(w http.ResponseWriter, r *http.Request) { - type login struct { - Username string `json:"username"` - } - - var data login - err = json.NewDecoder(r.Body).Decode(&data) - if err != nil { - renderJSON(400, w, map[string]interface{}{"error": "Invalid JSON"}, information) - return - } - - // Get the id for the user - var userId []byte - err = conn.DB.QueryRow("SELECT id FROM users WHERE username = $1", data.Username).Scan(&userId) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information) - } else { - renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "12"}, information) - logFunc(err.Error(), 2, information) - } - return - } - // Generate a new challenge challenge, err := randomChars(512) if err != nil { @@ -664,22 +631,27 @@ func Main(information *library.ServiceInitializationInformation) { return } - // Insert the challenge with one minute expiration - _, err = mem.Exec("INSERT INTO challengeResponse (challenge, userId, expires) VALUES (?, ?, ?)", challenge, userId, time.Now().Unix()+60) + // Issue a new JWT token with the challenge + token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, jwt.MapClaims{ + "challenge": challenge, + "exp": time.Now().Add(time.Second * 20).Unix(), + }) + tokenString, err := token.SignedString(privateKey) if err != nil { - renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, information) + renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "51"}, information) logFunc(err.Error(), 2, information) return } // Return the challenge - renderJSON(200, w, map[string]interface{}{"challenge": challenge}, information) + renderJSON(200, w, map[string]interface{}{"challenge": challenge, "verifier": tokenString}, information) }) router.Post("/api/login", func(w http.ResponseWriter, r *http.Request) { type login struct { Username string `json:"username"` Signature string `json:"signature"` + Verifier string `json:"verifier"` } var data login @@ -691,8 +663,8 @@ func Main(information *library.ServiceInitializationInformation) { // Try to select the user var userId []byte - var publicKey []byte - err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &publicKey) + var userPublicKey []byte + err = conn.DB.QueryRow("SELECT id, publicKey FROM users WHERE username = $1", data.Username).Scan(&userId, &userPublicKey) if err != nil { if errors.Is(err, sql.ErrNoRows) { renderJSON(401, w, map[string]interface{}{"error": "Invalid username"}, information) @@ -711,33 +683,43 @@ func Main(information *library.ServiceInitializationInformation) { } // Verify the challenge - // Select the current challenge from the database - var challenge string - err = mem.QueryRow("SELECT challenge FROM challengeResponse WHERE userId = ?", userId).Scan(&challenge) + token, err := jwt.Parse(data.Verifier, func(token *jwt.Token) (interface{}, error) { + return publicKey, nil + }) if err != nil { - if errors.Is(err, sql.ErrNoRows) { - renderJSON(401, w, map[string]interface{}{"error": "Invalid challenge"}, information) - } else { - renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "52"}, information) - logFunc(err.Error(), 2, information) - } + renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier"}, information) + return + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier: no claims"}, information) + return + } + + expired, err := claims.GetExpirationTime() + if err != nil { + renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier: no expiry"}, information) + return + } + + if expired.Before(time.Now()) { + renderJSON(401, w, map[string]interface{}{"error": "Expired verifier"}, information) + return + } + + challenge, ok := claims["challenge"].(string) + if !ok { + renderJSON(401, w, map[string]interface{}{"error": "Invalid verifier: no challenge"}, information) return } // Check if the challenge is correct by verifying the signature - if !ed25519.Verify(publicKey, []byte(challenge), signature) { + if !ed25519.Verify(userPublicKey, []byte(challenge), signature) { renderJSON(401, w, map[string]interface{}{"error": "Invalid signature"}, information) return } - // Delete the challenge - _, err = mem.Exec("DELETE FROM challengeResponse WHERE userId = ?", userId) - if err != nil { - renderJSON(500, w, map[string]interface{}{"error": "Internal server error", "code": "53"}, information) - logFunc(err.Error(), 2, information) - return - } - // Create a new session // We want the session token to be somewhat legible, so we use randomChars // As a trade-off for this, we use a longer session token @@ -1573,17 +1555,7 @@ func Main(information *library.ServiceInitializationInformation) { if err != nil { logFunc(err.Error(), 1, information) } else { - affected, err := mem.Exec("DELETE FROM challengeResponse WHERE expires < ?", time.Now().Unix()) - if err != nil { - logFunc(err.Error(), 1, information) - } else { - affectedCount2, err := affected.RowsAffected() - if err != nil { - logFunc(err.Error(), 1, information) - } else { - logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount+affectedCount2, 10)+" entries", 0, information) - } - } + logFunc("Cleanup complete, deleted "+strconv.FormatInt(affectedCount, 10)+" entries", 0, information) } } } diff --git a/services-src/auth/resources/wasm/login/main.go b/services-src/auth/resources/wasm/login/main.go index af3a389..4a561a7 100644 --- a/services-src/auth/resources/wasm/login/main.go +++ b/services-src/auth/resources/wasm/login/main.go @@ -192,6 +192,7 @@ func main() { signupBody := map[string]interface{}{ "username": username, "signature": base64.StdEncoding.EncodeToString(signature), + "verifier": responseMap["verifier"].(string), } // Marshal the body diff --git a/services-src/storage/main.go b/services-src/storage/main.go index 81a6cc2..06077bd 100644 --- a/services-src/storage/main.go +++ b/services-src/storage/main.go @@ -29,7 +29,6 @@ var ServiceInformation = library.Service{ } var ( - conn library.Database loggerService = uuid.MustParse("00000000-0000-0000-0000-000000000002") ) @@ -50,7 +49,7 @@ func respondError(message library.InterServiceMessage, err error, information *l message.Respond(errCode, err, information) } -func checkUserExists(userID uuid.UUID) bool { +func checkUserExists(userID uuid.UUID, conn library.Database) bool { // Check if a user exists in the database var userCheck []byte err := conn.DB.QueryRow("SELECT id FROM users WHERE id = $1", userID[:]).Scan(&userCheck) @@ -66,10 +65,10 @@ func checkUserExists(userID uuid.UUID) bool { } // addQuota can be used with a negative quota to remove quota from a user -func addQuota(information *library.ServiceInitializationInformation, message library.InterServiceMessage) { +func addQuota(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) { // Add more quota to a user userID := message.Message.(nucleusLibrary.Quota).User - if checkUserExists(userID) { + if checkUserExists(userID, conn) { _, err := conn.DB.Exec("UPDATE users SET quota = quota + $1 WHERE id = $2", message.Message.(nucleusLibrary.Quota).Bytes, message.Message.(nucleusLibrary.Quota).User) if err != nil { respondError(message, err, information, true) @@ -86,16 +85,16 @@ func addQuota(information *library.ServiceInitializationInformation, message lib } // And so does addReserved -func addReserved(information *library.ServiceInitializationInformation, message library.InterServiceMessage) { +func addReserved(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) { // Add more reserved space to a user userID := message.Message.(nucleusLibrary.Quota).User - if checkUserExists(userID) { + if checkUserExists(userID, conn) { // Check if the user has enough space - quota, err := getQuota(userID) + quota, err := getQuota(information, userID, conn) if err != nil { respondError(message, err, information, true) } - used, err := getUsed(userID, information) + used, err := getUsed(userID, information, conn) if err != nil { respondError(message, err, information, true) } @@ -123,17 +122,25 @@ func addReserved(information *library.ServiceInitializationInformation, message message.Respond(library.Success, nil, information) } -func getQuota(userID uuid.UUID) (int64, error) { +func getQuota(information *library.ServiceInitializationInformation, userID uuid.UUID, conn library.Database) (int64, error) { // Get the quota for a user var quota int64 err := conn.DB.QueryRow("SELECT quota FROM users WHERE id = $1", userID[:]).Scan("a) if err != nil { - return 0, err + if errors.Is(err, sql.ErrNoRows) { + _, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int))) + if err != nil { + return 0, err + } + return int64(information.Configuration["defaultQuota"].(int)), nil + } else { + return 0, err + } } return quota, nil } -func getUsed(userID uuid.UUID, information *library.ServiceInitializationInformation) (int64, error) { +func getUsed(userID uuid.UUID, information *library.ServiceInitializationInformation, conn library.Database) (int64, error) { // Get the used space for a user by first getting the reserved space from file storage _, err := os.Stat(filepath.Join(information.Configuration["path"].(string), userID.String())) if os.IsNotExist(err) { @@ -162,13 +169,21 @@ func getUsed(userID uuid.UUID, information *library.ServiceInitializationInforma var reserved int64 err = conn.DB.QueryRow("SELECT reserved FROM users WHERE id = $1", userID[:]).Scan(&reserved) if err != nil { - return 0, err + if errors.Is(err, sql.ErrNoRows) { + _, err := conn.DB.Exec("INSERT INTO users (id, quota, reserved) VALUES ($1, $2, 0)", userID[:], int64(information.Configuration["defaultQuota"].(int))) + if err != nil { + return 0, err + } + return 0, nil + } else { + return 0, err + } } return used + reserved, nil } -func modifyFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage) { +func modifyFile(information *library.ServiceInitializationInformation, message library.InterServiceMessage, conn library.Database) { // Check if the file already exists path := filepath.Join(information.Configuration["path"].(string), message.Message.(nucleusLibrary.File).User.String(), message.Message.(nucleusLibrary.File).Name) @@ -186,11 +201,11 @@ func modifyFile(information *library.ServiceInitializationInformation, message l } // Check if the user has enough space - quota, err := getQuota(message.Message.(nucleusLibrary.File).User) + quota, err := getQuota(information, message.Message.(nucleusLibrary.File).User, conn) if err != nil { respondError(message, err, information, true) } - used, err := getUsed(message.Message.(nucleusLibrary.File).User, information) + used, err := getUsed(message.Message.(nucleusLibrary.File).User, information, conn) if err != nil { respondError(message, err, information, true) } @@ -227,6 +242,7 @@ func getFile(information *library.ServiceInitializationInformation, message libr _, err := os.Stat(path) if os.IsNotExist(err) { + println("file not found: " + path) respondError(message, errors.New("file not found"), information, false) return } @@ -263,20 +279,20 @@ func deleteFile(information *library.ServiceInitializationInformation, message l } // processInterServiceMessages listens for incoming messages and processes them -func processInterServiceMessages(information *library.ServiceInitializationInformation) { +func processInterServiceMessages(information *library.ServiceInitializationInformation, conn library.Database) { // Listen for incoming messages for { message := information.AcceptMessage() switch message.MessageType { case 1: // Add quota - addQuota(information, message) + addQuota(information, message, conn) case 2: // Add reserved - addReserved(information, message) + addReserved(information, message, conn) case 3: // Modify file - modifyFile(information, message) + modifyFile(information, message, conn) case 4: // Get file getFile(information, message) @@ -313,5 +329,5 @@ func Main(information *library.ServiceInitializationInformation) { } // Listen for incoming messages - go processInterServiceMessages(information) + go processInterServiceMessages(information, conn) }