package main import ( "database/sql" "encoding/json" "fmt" "log" "net/http" "os" "strings" "text/template" "time" "github.com/google/uuid" _ "github.com/lib/pq" "golang.org/x/crypto/bcrypt" ) // Varibles, constants and basic structures const ( hashCost = 16 sessionLength = 12000 * time.Second ) var ( db *sql.DB sessions = make(map[string]session) fileUploadPath = "./uploads" templateFilePath = "./editor_templates" templates = template.Must(template.ParseGlob("templates/*.html")) ) type session struct { username string expiry time.Time sessionUUID string } type Credentials struct { Password string `json:"password"` Username string `json:"username"` } type File struct { ID int UserID uuid.UUID Filename string CreationTime int64 LastEdited int64 LastOpened int64 } func main() { // Retrieve Environment variables dbName := "notatio" dbHost := getEnvVariable("DB_HOST") dbPort := getEnvVariable("PGPORT") dbUser := getEnvVariable("DB_USER") dbPassword := getEnvVariable("POSTGRES_PASSWORD") dbSSLMode := getEnvVariable("DB_SSL_MODE") db = connectToDatabase(dbHost, dbPort, dbUser, dbPassword, dbUser, dbSSLMode) defer db.Close() missingParam := "" switch { case dbHost == "": missingParam = "DB_HOST" case dbPort == "": missingParam = "PGPORT" case dbUser == "": missingParam = "DB_USER" case dbPassword == "": missingParam = "POSTGRES_PASSWORD" } if missingParam != "" { log.Printf("Error: Required PostgreSQL connection environment variable '%s' is not provided.\n", missingParam) log.Println("Exiting...") os.Exit(10) } exists := checkDatabaseExists(db, dbName) if !exists { createDatabase(db, dbName) } db = connectToNotatioDatabase(dbHost, dbPort, dbUser, dbPassword, dbName, dbSSLMode) defer db.Close() adminUsername := getEnvVariable("ADMIN_USER") adminPassword := getEnvVariable("ADMIN_PASS") adminName := getEnvVariable("ADMIN_NAME") adminEmail := getEnvVariable("ADMIN_EMAIL") createUserTable() createFilesTable() createUser(adminUsername, adminPassword, adminName, adminEmail) log.Println("Done with database checks, starting webserver!") // Start webserver initHTTPServer() } // Web server initializer func initHTTPServer() { http.HandleFunc("/", AboutPage) http.HandleFunc("/signup", Signup) http.HandleFunc("/login", Login) http.HandleFunc("/home", ListFiles) http.HandleFunc("/refresh", Refresh) http.HandleFunc("/logout", Logout) http.HandleFunc("/upload", UploadFile) http.HandleFunc("/edit", EditFile) http.HandleFunc("/save", SaveFile) http.HandleFunc("/create", createNewFile) http.HandleFunc("/welcome", newUser) http.HandleFunc("/delete", DeleteFiles) http.HandleFunc("/export", exportFiles) http.HandleFunc("/checkusername", handleUsernameCheck) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) // Start the server on port 9991 fmt.Println("Starting HTTP server on port 9991...") log.Fatal(http.ListenAndServe(":9991", nil)) } func renderTemplate(w http.ResponseWriter, templateName string, data interface{}) { err := templates.ExecuteTemplate(w, templateName, data) if err != nil { http.Error(w, "Internal Server Error", http.StatusInternalServerError) log.Printf("Error rendering template %s: %v", templateName, err) } } func handleUsernameCheck(w http.ResponseWriter, r *http.Request) { // Get the username from the query parameter username := r.URL.Query().Get("username") // Check if the username is taken (example function) isTaken, err := isUsernameTaken(username) if err != nil { log.Fatal(err) } // Create a response map to store the availability status response := map[string]bool{ "available": isTaken, } // Convert the response to JSON format jsonResponse, err := json.Marshal(response) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Set the content type as JSON w.Header().Set("Content-Type", "application/json") // Write the JSON response to the response writer w.Write(jsonResponse) } func handleInternalServerError(w http.ResponseWriter, err error) { log.Printf("Error: %v", err) } func AboutPage(w http.ResponseWriter, r *http.Request) { renderTemplate(w, "index.html", nil) } func newUser(w http.ResponseWriter, r *http.Request) { renderTemplate(w, "newuser.html", nil) } func Refresh(w http.ResponseWriter, r *http.Request) { userSession, err := validateSession(w, r) if err != nil { // Handle the error as needed return } // Create a new session token for the current user newSessionToken := uuid.NewString() expiresAt := time.Now().Add(sessionLength) // Store the token in the session map, along with the user whom it represents sessions[newSessionToken] = session{ username: userSession.username, expiry: expiresAt, } // Delete the older session token delete(sessions, userSession.sessionUUID) // Set the new token as the user's `session_token` cookie http.SetCookie(w, &http.Cookie{ Name: "session_token", Value: newSessionToken, Expires: expiresAt, }) log.Printf("Session refreshed for user: %s", userSession.username) // Redirect to the home page http.Redirect(w, r, "/home", http.StatusSeeOther) } func (s session) isSessionExpired() bool { return s.expiry.Before(time.Now()) } func validateSession(w http.ResponseWriter, r *http.Request) (session, error) { sessionCookie, err := r.Cookie("session_token") if err != nil { log.Printf("Error getting session cookie: %v", err) http.Redirect(w, r, "/?login", http.StatusSeeOther) // Redirect to the login page return session{}, err } sessionToken := sessionCookie.Value userSession, exists := sessions[sessionToken] if !exists { log.Printf("Session not found for token: %s", sessionToken) http.Redirect(w, r, "/?login", http.StatusSeeOther) // Redirect to the login page return session{}, fmt.Errorf("session not found") } if userSession.isSessionExpired() { delete(sessions, sessionToken) log.Printf("Session expired for user: %s", userSession.username) http.Redirect(w, r, "/?login", http.StatusSeeOther) // Redirect to the login page return session{}, fmt.Errorf("session expired") } return userSession, nil } func createUserTable() { // Create User Table createUserTable := ` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username VARCHAR(255) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, accounttype VARCHAR(255) NOT NULL, uuid VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL );` _, err := db.Exec(createUserTable) if err != nil { log.Fatal(err) } } func createFilesTable() { // Create File Table createFileTable := ` CREATE TABLE IF NOT EXISTS files ( id SERIAL PRIMARY KEY, user_id UUID NOT NULL, filename VARCHAR(255) NOT NULL, creation_time TIMESTAMP NOT NULL, last_edited TIMESTAMP NOT NULL, last_opened TIMESTAMP NOT NULL );` _, err := db.Exec(createFileTable) if err != nil { log.Fatal(err) } } func getEnvVariable(name string) string { value := os.Getenv(name) if value == "" { log.Printf("Error: Required environment variable '%s' is not provided.\n", name) log.Println("Exiting...") os.Exit(10) } return value } func connectToDatabase(dbHost, dbPort, dbUser, dbPassword, dbName, dbSSLMode string) *sql.DB { dbConnectionString := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", dbHost, dbPort, dbUser, dbPassword, dbName, dbSSLMode) db, err := sql.Open("postgres", dbConnectionString) if err != nil { // Handle the error appropriately panic(err) } return db } func checkDatabaseExists(db *sql.DB, dbName string) bool { var exists bool row := db.QueryRow("SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname = $1)", dbName) if err := row.Scan(&exists); err != nil { log.Fatal(err) } return exists } func createDatabase(db *sql.DB, dbName string) { log.Println("Notatio database does not exist. Creating Database...") _, err := db.Exec("CREATE DATABASE notatio") if err != nil { log.Fatal(err) } } func connectToNotatioDatabase(dbHost, dbPort, dbUser, dbPassword, dbName, dbSSLMode string) *sql.DB { dbConnectionString := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", dbHost, dbPort, dbUser, dbPassword, dbName, dbSSLMode) db, err := sql.Open("postgres", dbConnectionString) if err != nil { // Handle the error appropriately panic(err) } return db } func createUser(adminUsername string, adminPassword string, adminName string, adminEmail string) { adminUsername = strings.ToLower(adminUsername) if adminUsername != "" && adminPassword != "" && adminName != "" && adminEmail != "" { // Check if the admin user already exists in the database _, adminExists := getUserFromDatabase(adminUsername) if !adminExists { // Admin user doesn't exist, create it hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), hashCost) if err != nil { log.Printf("Error hashing admin password: %v", err) // Handle the error appropriately } else { // Generate a UUID for the admin user adminUUID := uuid.New() createUserFolder(adminUsername) // Insert the admin user into the database with the hashed password and UUID if err := insertUserIntoDatabase(adminUsername, string(hashedPassword), "admin", adminUUID.String(), adminName, adminEmail); err != nil { log.Printf("Error inserting admin user into database: %v", err) // Handle the error appropriately } else { log.Printf("Admin user created and added to the database: %s", adminUsername) } } } else { log.Printf("Admin user already exists in the database: %s", adminUsername) } } }