Add users system, required for uploading new pastes
This commit is contained in:
		
							parent
							
								
									fbab9028f4
								
							
						
					
					
						commit
						788e75c4c1
					
				@ -32,6 +32,10 @@ func main() {
 | 
				
			|||||||
		log.Fatal(errors.Wrap(err, "migrating database"))
 | 
							log.Fatal(errors.Wrap(err, "migrating database"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := db.CreateAdminUser(database, "admin"); err != nil {
 | 
				
			||||||
 | 
						  log.Fatalln(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	go rushlink.StartMetricsServer(*metricsListen, database, filestore)
 | 
						go rushlink.StartMetricsServer(*metricsListen, database, filestore)
 | 
				
			||||||
	rushlink.StartMainServer(*httpListen, database, filestore, *rootURL)
 | 
						rushlink.StartMainServer(*httpListen, database, filestore, *rootURL)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										178
									
								
								handlers.go
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								handlers.go
									
									
									
									
									
								
							@ -8,6 +8,7 @@ import (
 | 
				
			|||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -244,6 +245,23 @@ func (rl *rushlink) viewActionSuccess(w http.ResponseWriter, r *http.Request, p
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (rl *rushlink) newPasteHandler(w http.ResponseWriter, r *http.Request) {
 | 
					func (rl *rushlink) newPasteHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						// Check if the user is authenticated
 | 
				
			||||||
 | 
						username, password, ok := r.BasicAuth()
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							// User is not authenticated, return a 401 Unauthorized response
 | 
				
			||||||
 | 
							w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Authenticate the user
 | 
				
			||||||
 | 
						user, err := db.Authenticate(rl.db, username, password)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// Authentication failed, return a 401 Unauthorized response
 | 
				
			||||||
 | 
							w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if err := r.ParseMultipartForm(formParseMaxMemory); err != nil {
 | 
						if err := r.ParseMultipartForm(formParseMaxMemory); err != nil {
 | 
				
			||||||
		msg := fmt.Sprintf("could not parse form: %v\n", err)
 | 
							msg := fmt.Sprintf("could not parse form: %v\n", err)
 | 
				
			||||||
		rl.renderError(w, r, http.StatusBadRequest, msg)
 | 
							rl.renderError(w, r, http.StatusBadRequest, msg)
 | 
				
			||||||
@ -262,7 +280,7 @@ func (rl *rushlink) newPasteHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if shortensPrs {
 | 
						if shortensPrs {
 | 
				
			||||||
		rl.newRedirectPasteHandler(w, r, shortens[0])
 | 
							rl.newRedirectPasteHandler(w, r, user, shortens[0])
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if fileHeadersPrs {
 | 
						if fileHeadersPrs {
 | 
				
			||||||
@ -272,17 +290,152 @@ func (rl *rushlink) newPasteHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
			rl.renderInternalServerError(w, r, err)
 | 
								rl.renderInternalServerError(w, r, err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		rl.newFileUploadPasteHandler(w, r, file, *fileHeader)
 | 
							rl.newFileUploadPasteHandler(w, r, user, file, *fileHeader)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					func (rl *rushlink) createUserHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						// Check if the user is authenticated as an admin
 | 
				
			||||||
 | 
						user := rl.authenticateUser(w, r, true, nil)
 | 
				
			||||||
 | 
						if user == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Request, file multipart.File, header multipart.FileHeader) {
 | 
						if err := r.ParseMultipartForm(formParseMaxMemory); err != nil {
 | 
				
			||||||
 | 
							msg := fmt.Sprintf("could not parse form: %v\n", err)
 | 
				
			||||||
 | 
							rl.renderError(w, r, http.StatusBadRequest, msg)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						new_username := r.FormValue("username")
 | 
				
			||||||
 | 
						new_password := r.FormValue("password")
 | 
				
			||||||
 | 
						new_admin, _ := strconv.ParseBool(r.FormValue("admin"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create the user
 | 
				
			||||||
 | 
						if err := db.NewUser(rl.db, new_username, new_password, new_admin); err != nil {
 | 
				
			||||||
 | 
							// Failed to create the user, return a 500 Internal Server Error response
 | 
				
			||||||
 | 
							http.Error(w, "Failed to create user", http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Return a 201 Created response
 | 
				
			||||||
 | 
						w.WriteHeader(http.StatusCreated)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (rl *rushlink) authenticateUser(w http.ResponseWriter, r *http.Request, shouldBeAdmin bool, canAlsoBe *string) *db.User {
 | 
				
			||||||
 | 
						// Check if the user is authenticated
 | 
				
			||||||
 | 
						username, password, ok := r.BasicAuth()
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							// User is not authenticated, return a 401 Unauthorized response
 | 
				
			||||||
 | 
							w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Authenticate the user
 | 
				
			||||||
 | 
						user, err := db.Authenticate(rl.db, username, password)
 | 
				
			||||||
 | 
						if err != nil || (shouldBeAdmin && !user.Admin && (canAlsoBe == nil || *canAlsoBe != user.User)) {
 | 
				
			||||||
 | 
							// Authentication failed, return a 401 Unauthorized response
 | 
				
			||||||
 | 
							w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusUnauthorized)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return user
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (rl *rushlink) deleteUserHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						// Parse the user ID from the request URL
 | 
				
			||||||
 | 
						vars := mux.Vars(r)
 | 
				
			||||||
 | 
						userName := vars["user"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if the user is authenticated as an admin or self
 | 
				
			||||||
 | 
						user := rl.authenticateUser(w, r, true, &userName)
 | 
				
			||||||
 | 
						if user == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Delete the user
 | 
				
			||||||
 | 
						if err := db.DeleteUser(rl.db, userName); err != nil {
 | 
				
			||||||
 | 
							// Failed to delete the user, return a 500 Internal Server Error response
 | 
				
			||||||
 | 
							http.Error(w, "Failed to delete user", http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Return a 204 No Content response
 | 
				
			||||||
 | 
						w.WriteHeader(http.StatusNoContent)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (rl *rushlink) changeUserHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						// Parse the user ID from the request URL
 | 
				
			||||||
 | 
						vars := mux.Vars(r)
 | 
				
			||||||
 | 
						userName := vars["user"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if the user is authenticated as an admin or self
 | 
				
			||||||
 | 
						user := rl.authenticateUser(w, r, true, &userName)
 | 
				
			||||||
 | 
						if user == nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse the request multipart form
 | 
				
			||||||
 | 
						if err := r.ParseMultipartForm(formParseMaxMemory); err != nil {
 | 
				
			||||||
 | 
							msg := fmt.Sprintf("could not parse form: %v\n", err)
 | 
				
			||||||
 | 
							rl.renderError(w, r, http.StatusBadRequest, msg)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get the user record from the database
 | 
				
			||||||
 | 
						updatedUser := new(db.User)
 | 
				
			||||||
 | 
						if err := rl.db.Where("user = ?", userName).First(&updatedUser).Error; err != nil {
 | 
				
			||||||
 | 
							// User record not found, return a 404 Not Found response
 | 
				
			||||||
 | 
							http.NotFound(w, r)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get the updated user data from the form
 | 
				
			||||||
 | 
						if newUsername := r.FormValue("new_username"); newUsername != "" {
 | 
				
			||||||
 | 
							updatedUser.User = newUsername
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if newPassword := r.FormValue("new_password"); newPassword != "" {
 | 
				
			||||||
 | 
							hashedPassword, err := db.HashPassword(newPassword)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								msg := fmt.Sprintf("could not hash password: %v\n", err)
 | 
				
			||||||
 | 
								rl.renderError(w, r, http.StatusInternalServerError, msg)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							updatedUser.Password = hashedPassword
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if newAdminStr := r.FormValue("new_admin"); newAdminStr != "" {
 | 
				
			||||||
 | 
							newAdmin, err := strconv.ParseBool(newAdminStr)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								msg := fmt.Sprintf("could not parse admin status: %v\n", err)
 | 
				
			||||||
 | 
								rl.renderError(w, r, http.StatusBadRequest, msg)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							updatedUser.Admin = newAdmin
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if the user is trying to update their own admin status
 | 
				
			||||||
 | 
						if updatedUser.Admin && !user.Admin {
 | 
				
			||||||
 | 
							// User is trying to update their own admin status, return a 403 Forbidden response
 | 
				
			||||||
 | 
							http.Error(w, "You do not have permission to update your own admin status", http.StatusForbidden)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update the user in the database
 | 
				
			||||||
 | 
						if err := db.ChangeUser(rl.db, userName, updatedUser); err != nil {
 | 
				
			||||||
 | 
							// Failed to update the user, return a 500 Internal Server Error response
 | 
				
			||||||
 | 
							http.Error(w, "Failed to update user", http.StatusInternalServerError)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Return a 200 OK response
 | 
				
			||||||
 | 
						w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Request, user *db.User, file multipart.File, header multipart.FileHeader) {
 | 
				
			||||||
	var fu *db.FileUpload
 | 
						var fu *db.FileUpload
 | 
				
			||||||
	var paste *db.Paste
 | 
						var paste *db.Paste
 | 
				
			||||||
	if err := rl.db.Transaction(func(tx *db.Database) error {
 | 
						if err := rl.db.Transaction(func(tx *db.Database) error {
 | 
				
			||||||
		var err error
 | 
							var err error
 | 
				
			||||||
		fu, err = db.NewFileUpload(rl.fs, file, header.Filename)
 | 
							fu, err = db.NewFileUpload(rl.fs, file, user, header.Filename)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			panic(errors.Wrap(err, "creating fileUpload"))
 | 
								panic(errors.Wrap(err, "creating fileUpload"))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -290,7 +443,7 @@ func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Req
 | 
				
			|||||||
			panic(errors.Wrap(err, "saving fileUpload in db"))
 | 
								panic(errors.Wrap(err, "saving fileUpload in db"))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		paste, err = shortenFileUploadID(tx, fu.PubID)
 | 
							paste, err = shortenFileUploadID(tx, user, fu.PubID)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
@ -298,7 +451,7 @@ func (rl *rushlink) newFileUploadPasteHandler(w http.ResponseWriter, r *http.Req
 | 
				
			|||||||
	rl.viewActionSuccess(w, r, paste, fu)
 | 
						rl.viewActionSuccess(w, r, paste, fu)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (rl *rushlink) newRedirectPasteHandler(w http.ResponseWriter, r *http.Request, rawurl string) {
 | 
					func (rl *rushlink) newRedirectPasteHandler(w http.ResponseWriter, r *http.Request, user *db.User, rawurl string) {
 | 
				
			||||||
	userURL, err := url.Parse(rawurl)
 | 
						userURL, err := url.Parse(rawurl)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		msg := fmt.Sprintf("invalid url (%v): %v", err, rawurl)
 | 
							msg := fmt.Sprintf("invalid url (%v): %v", err, rawurl)
 | 
				
			||||||
@ -317,7 +470,7 @@ func (rl *rushlink) newRedirectPasteHandler(w http.ResponseWriter, r *http.Reque
 | 
				
			|||||||
	var paste *db.Paste
 | 
						var paste *db.Paste
 | 
				
			||||||
	if err := rl.db.Transaction(func(tx *db.Database) error {
 | 
						if err := rl.db.Transaction(func(tx *db.Database) error {
 | 
				
			||||||
		var err error
 | 
							var err error
 | 
				
			||||||
		paste, err = shortenURL(tx, userURL)
 | 
							paste, err = shortenURL(tx, user, userURL)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}); err != nil {
 | 
						}); err != nil {
 | 
				
			||||||
		panic(err)
 | 
							panic(err)
 | 
				
			||||||
@ -370,19 +523,19 @@ func (rl *rushlink) deletePasteHandler(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
// Returns the new paste key if the fileUpload was successfully added to the
 | 
					// Returns the new paste key if the fileUpload was successfully added to the
 | 
				
			||||||
// database
 | 
					// database
 | 
				
			||||||
func shortenFileUploadID(tx *db.Database, id uuid.UUID) (*db.Paste, error) {
 | 
					func shortenFileUploadID(tx *db.Database, user *db.User, id uuid.UUID) (*db.Paste, error) {
 | 
				
			||||||
	return shorten(tx, db.PasteTypeFileUpload, id[:])
 | 
						return shorten(tx, user, db.PasteTypeFileUpload, id[:])
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add a new URL to the database
 | 
					// Add a new URL to the database
 | 
				
			||||||
//
 | 
					//
 | 
				
			||||||
// Returns the new paste key if the url was successfully shortened
 | 
					// Returns the new paste key if the url was successfully shortened
 | 
				
			||||||
func shortenURL(tx *db.Database, userURL *url.URL) (*db.Paste, error) {
 | 
					func shortenURL(tx *db.Database, user *db.User, userURL *url.URL) (*db.Paste, error) {
 | 
				
			||||||
	return shorten(tx, db.PasteTypeRedirect, []byte(userURL.String()))
 | 
						return shorten(tx, user, db.PasteTypeRedirect, []byte(userURL.String()))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Add a paste (of any kind) to the database with arbitrary content.
 | 
					// Add a paste (of any kind) to the database with arbitrary content.
 | 
				
			||||||
func shorten(tx *db.Database, ty db.PasteType, content []byte) (*db.Paste, error) {
 | 
					func shorten(tx *db.Database, user *db.User, ty db.PasteType, content []byte) (*db.Paste, error) {
 | 
				
			||||||
	// Generate the paste key
 | 
						// Generate the paste key
 | 
				
			||||||
	var keyEntropy int
 | 
						var keyEntropy int
 | 
				
			||||||
	if ty == db.PasteTypeFileUpload || ty == db.PasteTypePaste {
 | 
						if ty == db.PasteTypeFileUpload || ty == db.PasteTypePaste {
 | 
				
			||||||
@ -403,6 +556,7 @@ func shorten(tx *db.Database, ty db.PasteType, content []byte) (*db.Paste, error
 | 
				
			|||||||
	p := db.Paste{
 | 
						p := db.Paste{
 | 
				
			||||||
		Type:        ty,
 | 
							Type:        ty,
 | 
				
			||||||
		State:       db.PasteStatePresent,
 | 
							State:       db.PasteStatePresent,
 | 
				
			||||||
 | 
							CreatedBy:   user.ID,
 | 
				
			||||||
		Content:     content,
 | 
							Content:     content,
 | 
				
			||||||
		Key:         pasteKey,
 | 
							Key:         pasteKey,
 | 
				
			||||||
		DeleteToken: deleteToken,
 | 
							DeleteToken: deleteToken,
 | 
				
			||||||
 | 
				
			|||||||
@ -38,6 +38,9 @@ type FileUpload struct {
 | 
				
			|||||||
	// UUID publically identifies this FileUpload.
 | 
						// UUID publically identifies this FileUpload.
 | 
				
			||||||
	PubID uuid.UUID `gorm:"uniqueIndex"`
 | 
						PubID uuid.UUID `gorm:"uniqueIndex"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// User ID that created this file
 | 
				
			||||||
 | 
						CreatedBy   uint `gorm:"index"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// FileName contains the original filename of this FileUpload.
 | 
						// FileName contains the original filename of this FileUpload.
 | 
				
			||||||
	FileName string
 | 
						FileName string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -126,7 +129,7 @@ func (fs *FileStore) filePath(pubID uuid.UUID, fileName string) string {
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
// Internally, this function detects the type of the file stored in `r` using
 | 
					// Internally, this function detects the type of the file stored in `r` using
 | 
				
			||||||
// `http.DetectContentType`.
 | 
					// `http.DetectContentType`.
 | 
				
			||||||
func NewFileUpload(fs *FileStore, r io.Reader, fileName string) (*FileUpload, error) {
 | 
					func NewFileUpload(fs *FileStore, r io.Reader, user *User, fileName string) (*FileUpload, error) {
 | 
				
			||||||
	// Generate a file ID
 | 
						// Generate a file ID
 | 
				
			||||||
	pubID, err := uuid.NewRandom()
 | 
						pubID, err := uuid.NewRandom()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@ -168,6 +171,7 @@ func NewFileUpload(fs *FileStore, r io.Reader, fileName string) (*FileUpload, er
 | 
				
			|||||||
	fu := &FileUpload{
 | 
						fu := &FileUpload{
 | 
				
			||||||
		State:       FileUploadStatePresent,
 | 
							State:       FileUploadStatePresent,
 | 
				
			||||||
		PubID:       pubID,
 | 
							PubID:       pubID,
 | 
				
			||||||
 | 
							CreatedBy:   user.ID,
 | 
				
			||||||
		FileName:    baseName,
 | 
							FileName:    baseName,
 | 
				
			||||||
		ContentType: contentType,
 | 
							ContentType: contentType,
 | 
				
			||||||
		Checksum:    hash.Sum32(),
 | 
							Checksum:    hash.Sum32(),
 | 
				
			||||||
 | 
				
			|||||||
@ -42,5 +42,56 @@ func Gormigrate(db *gorm.DB) *gormigrate.Gormigrate {
 | 
				
			|||||||
				return tx.Migrator().DropTable(&FileUpload{}, &Paste{})
 | 
									return tx.Migrator().DropTable(&FileUpload{}, &Paste{})
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								ID: "202304301337",
 | 
				
			||||||
 | 
								Migrate: func(tx *gorm.DB) error {
 | 
				
			||||||
 | 
									type User struct {
 | 
				
			||||||
 | 
										ID        uint   `gorm:"primaryKey"`
 | 
				
			||||||
 | 
										User      string `gorm:"uniqueIndex"`
 | 
				
			||||||
 | 
										Password  string
 | 
				
			||||||
 | 
										Admin     bool
 | 
				
			||||||
 | 
										CreatedAt time.Time
 | 
				
			||||||
 | 
										UpdatedAt time.Time
 | 
				
			||||||
 | 
										DeletedAt gorm.DeletedAt
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									type FileUpload struct {
 | 
				
			||||||
 | 
										ID          uint            `gorm:"primaryKey"`
 | 
				
			||||||
 | 
										State       FileUploadState `gorm:"index"`
 | 
				
			||||||
 | 
										PubID       uuid.UUID       `gorm:"uniqueIndex"`
 | 
				
			||||||
 | 
										CreatedBy   uint            `gorm:"index"`
 | 
				
			||||||
 | 
										FileName    string
 | 
				
			||||||
 | 
										ContentType string
 | 
				
			||||||
 | 
										Checksum    uint32
 | 
				
			||||||
 | 
										CreatedAt   time.Time
 | 
				
			||||||
 | 
										UpdatedAt   time.Time
 | 
				
			||||||
 | 
										DeletedAt   gorm.DeletedAt
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									type Paste struct {
 | 
				
			||||||
 | 
										ID          uint       `gorm:"primaryKey"`
 | 
				
			||||||
 | 
										Type        PasteType  `gorm:"index"`
 | 
				
			||||||
 | 
										State       PasteState `gorm:"index"`
 | 
				
			||||||
 | 
										CreatedBy   uint       `gorm:"index"`
 | 
				
			||||||
 | 
										Content     []byte
 | 
				
			||||||
 | 
										Key         string `gorm:"uniqueIndex"`
 | 
				
			||||||
 | 
										DeleteToken string
 | 
				
			||||||
 | 
										CreatedAt   time.Time
 | 
				
			||||||
 | 
										UpdatedAt   time.Time
 | 
				
			||||||
 | 
										DeletedAt   gorm.DeletedAt
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return tx.AutoMigrate(&User{}, &FileUpload{}, &Paste{})
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Rollback: func(tx *gorm.DB) error {
 | 
				
			||||||
 | 
									if err := tx.Migrator().DropTable(&User{}); err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if err := tx.Migrator().DropColumn(&FileUpload{}, "CreatedBy"); err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if err := tx.Migrator().DropColumn(&Paste{}, "CreatedBy"); err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -24,6 +24,7 @@ type Paste struct {
 | 
				
			|||||||
	ID          uint       `gorm:"primaryKey"`
 | 
						ID          uint       `gorm:"primaryKey"`
 | 
				
			||||||
	Type        PasteType  `gorm:"index"`
 | 
						Type        PasteType  `gorm:"index"`
 | 
				
			||||||
	State       PasteState `gorm:"index"`
 | 
						State       PasteState `gorm:"index"`
 | 
				
			||||||
 | 
					  CreatedBy   uint       `gorm:"index"`
 | 
				
			||||||
	Content     []byte
 | 
						Content     []byte
 | 
				
			||||||
	Key         string `gorm:"uniqueIndex"`
 | 
						Key         string `gorm:"uniqueIndex"`
 | 
				
			||||||
	DeleteToken string
 | 
						DeleteToken string
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										153
									
								
								internal/db/user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								internal/db/user.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,153 @@
 | 
				
			|||||||
 | 
					package db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"encoding/base64"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/uuid"
 | 
				
			||||||
 | 
						"golang.org/x/crypto/argon2"
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type User struct {
 | 
				
			||||||
 | 
						ID        uint   `gorm:"primaryKey"`
 | 
				
			||||||
 | 
						User      string `gorm:"uniqueIndex"`
 | 
				
			||||||
 | 
						Password  string
 | 
				
			||||||
 | 
						Admin     bool
 | 
				
			||||||
 | 
						CreatedAt time.Time
 | 
				
			||||||
 | 
						UpdatedAt time.Time
 | 
				
			||||||
 | 
						DeletedAt gorm.DeletedAt
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewUser(db *gorm.DB, username string, password string, admin bool) error {
 | 
				
			||||||
 | 
						// Generate a new UUID for the user
 | 
				
			||||||
 | 
						id, err := uuid.NewRandom()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Hash the password using argon2id
 | 
				
			||||||
 | 
						hashedPassword, err := HashPassword(password)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create a new user record
 | 
				
			||||||
 | 
						user := &User{
 | 
				
			||||||
 | 
							ID:       uint(id.ID()),
 | 
				
			||||||
 | 
							User:     username,
 | 
				
			||||||
 | 
							Password: hashedPassword,
 | 
				
			||||||
 | 
							Admin:    admin,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return db.Create(user).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Authenticate(db *gorm.DB, username string, password string) (*User, error) {
 | 
				
			||||||
 | 
						// Get the user record by username
 | 
				
			||||||
 | 
						var user User
 | 
				
			||||||
 | 
						if err := db.Where("user = ?", username).First(&user).Error; err != nil {
 | 
				
			||||||
 | 
							return nil, errors.New("user not found")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Compare the hashed password with the provided password
 | 
				
			||||||
 | 
						if !comparePassword(user.Password, password) {
 | 
				
			||||||
 | 
							return nil, errors.New("invalid password")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &user, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func HashPassword(password string) (string, error) {
 | 
				
			||||||
 | 
						// Generate a salt for the password hash
 | 
				
			||||||
 | 
						salt := make([]byte, 16)
 | 
				
			||||||
 | 
						if _, err := rand.Read(salt); err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Hash the password using argon2id
 | 
				
			||||||
 | 
						hash := argon2.IDKey([]byte(password), salt, 2, 64*1024, 1, 64)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Encode the salt and hash as a string
 | 
				
			||||||
 | 
						return string(salt) + string(hash), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func comparePassword(hashedPassword string, password string) bool {
 | 
				
			||||||
 | 
						// Decode the salt and hash from the hashed password string
 | 
				
			||||||
 | 
						salt := []byte(hashedPassword)[:16]
 | 
				
			||||||
 | 
						hash := []byte(hashedPassword)[16:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Hash the password using the same salt and parameters
 | 
				
			||||||
 | 
						computedHash := argon2.IDKey([]byte(password), salt, 2, 64*1024, 1, 64)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Compare the computed hash with the stored hash
 | 
				
			||||||
 | 
						return bytes.Equal(hash, computedHash)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DeleteUser deletes a user with the specified username from the database.
 | 
				
			||||||
 | 
					func DeleteUser(db *gorm.DB, username string) error {
 | 
				
			||||||
 | 
						// Find the user by username
 | 
				
			||||||
 | 
						var user User
 | 
				
			||||||
 | 
						if err := db.Where("user = ?", username).First(&user).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Delete the user
 | 
				
			||||||
 | 
						if err := db.Delete(&user).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ChangeUser(db *gorm.DB, username string, updatedUser *User) error {
 | 
				
			||||||
 | 
						// Retrieve the existing user
 | 
				
			||||||
 | 
						var existingUser User
 | 
				
			||||||
 | 
						if err := db.Where("user = ?", username).First(&existingUser).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update the user fields
 | 
				
			||||||
 | 
						existingUser.User = updatedUser.User
 | 
				
			||||||
 | 
						existingUser.Password = updatedUser.Password
 | 
				
			||||||
 | 
						existingUser.Admin = updatedUser.Admin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Save the updated user to the database
 | 
				
			||||||
 | 
						if err := db.Save(&existingUser).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CreateAdminUser(db *gorm.DB, adminUsername string) error {
 | 
				
			||||||
 | 
						// Check if the admin user already exists
 | 
				
			||||||
 | 
						var admins []User
 | 
				
			||||||
 | 
						if err := db.Unscoped().Limit(1).Where("user = ?", adminUsername).Find(&admins).Error; err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(admins) > 0 {
 | 
				
			||||||
 | 
							// already exists
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generate a random 24-char password
 | 
				
			||||||
 | 
						passwordBytes := make([]byte, 24)
 | 
				
			||||||
 | 
						if _, err := rand.Read(passwordBytes); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						password := base64.URLEncoding.EncodeToString(passwordBytes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create the admin user
 | 
				
			||||||
 | 
						if err := NewUser(db, adminUsername, password, true); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Print out the generated password
 | 
				
			||||||
 | 
						fmt.Printf("Generated password for admin user %s: %s\n", adminUsername, password)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -106,6 +106,9 @@ func InitMainRouter(r *mux.Router, rl *rushlink) {
 | 
				
			|||||||
	r.HandleFunc("/{path:js/"+staticFilenameExpr+"}", rl.staticGetHandler).Methods("GET", "HEAD")
 | 
						r.HandleFunc("/{path:js/"+staticFilenameExpr+"}", rl.staticGetHandler).Methods("GET", "HEAD")
 | 
				
			||||||
	r.HandleFunc("/", rl.indexGetHandler).Methods("GET", "HEAD")
 | 
						r.HandleFunc("/", rl.indexGetHandler).Methods("GET", "HEAD")
 | 
				
			||||||
	r.HandleFunc("/", rl.newPasteHandler).Methods("POST")
 | 
						r.HandleFunc("/", rl.newPasteHandler).Methods("POST")
 | 
				
			||||||
 | 
						r.HandleFunc("/users", rl.createUserHandler).Methods("POST")
 | 
				
			||||||
 | 
						r.HandleFunc("/users/{user}", rl.changeUserHandler).Methods("POST")
 | 
				
			||||||
 | 
						r.HandleFunc("/users/{user}", rl.deleteUserHandler).Methods("DELETE")
 | 
				
			||||||
	r.HandleFunc("/"+urlKeyExpr, rl.viewPasteHandler).Methods("GET", "HEAD")
 | 
						r.HandleFunc("/"+urlKeyExpr, rl.viewPasteHandler).Methods("GET", "HEAD")
 | 
				
			||||||
	r.HandleFunc("/"+urlKeyWithExtExpr, rl.viewPasteHandler).Methods("GET", "HEAD")
 | 
						r.HandleFunc("/"+urlKeyWithExtExpr, rl.viewPasteHandler).Methods("GET", "HEAD")
 | 
				
			||||||
	r.HandleFunc("/"+urlKeyExpr+"/nr", rl.viewPasteHandlerNoRedirect).Methods("GET", "HEAD")
 | 
						r.HandleFunc("/"+urlKeyExpr+"/nr", rl.viewPasteHandlerNoRedirect).Methods("GET", "HEAD")
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user