diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..6bb9e2d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,20 @@ +ANTI-CAPITALIST SOFTWARE LICENSE (v 1.4) + +Copyright © 2022 Travis Shears + +This is anti-capitalist software, released for free use by individuals and organizations that do not operate by capitalist principles. + +Permission is hereby granted, free of charge, to any person or organization (the "User") obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, distribute, and/or sell copies of the Software, subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included in all copies or modified versions of the Software. +2. The User is one of the following: + +- An individual person, laboring for themselves +- A non-profit organization +- An educational institution +- An organization that seeks shared profit for all of its members, and allows non-members to set the cost of their labor + +3. If the User is an organization with owners, then all owners are workers and all workers are owners with equal equity and/or equal vote. +4. If the User is an organization, then the User is not law enforcement or military, or working for or under either. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/internal/guestbook/guestbook.go b/internal/guestbook/guestbook.go index 9054767..79448c7 100644 --- a/internal/guestbook/guestbook.go +++ b/internal/guestbook/guestbook.go @@ -55,6 +55,8 @@ func (book *GuestBook) HandleRequest(w gemini.ResponseWriter, req *gemini.Reques book.serveIndex(w, req) case path == "/guestbook/new": book.serveNewEntry(w, req) + case path == "/guestbook/setname": + book.serveSetName(w, req) case path == "/guestbook/admin": book.serveAdmin(w, req) case strings.HasPrefix(path, "/guestbook/approve"): @@ -92,6 +94,7 @@ func (book *GuestBook) serveDelete(w gemini.ResponseWriter, req *gemini.Request) } entryId := req.URL.Query().Get("id") slog.Info("Deleting", "userId", userId, "entryId", entryId) + _, err = book.db.Exec("DELETE FROM guestbook WHERE id = ?", entryId) if err != nil { slog.Error("Error deleting guestbook entry", "error", err) @@ -101,6 +104,39 @@ func (book *GuestBook) serveDelete(w gemini.ResponseWriter, req *gemini.Request) book.serveAdmin(w, req) } +func (book *GuestBook) serveSetName(w gemini.ResponseWriter, req *gemini.Request) { + if req.Certificate() == nil { + w.WriteStatusMsg(gemini.StatusCertRequired, "Authentication Required") + return + } + if req.URL.RawQuery == "" { + w.WriteStatusMsg(gemini.StatusPlainInput, "Name:") + return + } + userId, err := UserIDFromCert(req.Certificate()) + newName := req.URL.RawQuery + slog.Info("Setting new name for guestbook user", "userId", userId, "newName", newName) + + if err != nil { + slog.Error("Error creating user id from cert", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error generating user ID") + return + } + slog.Info("Setting name", "newName", newName, "userId", userId) + _, err = book.db.Exec("UPDATE guestbook SET name = ? WHERE user_id = ?", newName, userId) + if err != nil { + slog.Error("Error setting name for guestbook entry", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error setting name for guestbook entry") + return + } + + w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") + var content strings.Builder + content.WriteString("# Success!\n") + content.WriteString("Updated the username for you entry / entries if you have mutiple using the same idenitiy\n\n") + content.WriteString("\n\n=> /guestbook Back to guestbook") + w.WriteBody([]byte(content.String())) +} func (book *GuestBook) serveApprove(w gemini.ResponseWriter, req *gemini.Request) { if req.Certificate() == nil { w.WriteStatusMsg(gemini.StatusCertRequired, "Authentication Required") @@ -139,29 +175,46 @@ func (book *GuestBook) serveNewEntry(w gemini.ResponseWriter, req *gemini.Reques // w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") name := req.Certificate().Subject.CommonName userId, err := UserIDFromCert(req.Certificate()) - slog.Info("User ID created", "user_id", userId) if err != nil { slog.Error("Error creating user id from cert", "error", err) w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error generating user ID") return } txt := req.URL.RawQuery - _, err = book.db.Exec("INSERT INTO guestbook(name, user_id, approved, message) VALUES(?, ?, ?, ?)", name, userId, false, txt) + result, err := book.db.Exec("INSERT INTO guestbook(name, user_id, approved, message) VALUES(?, ?, ?, ?)", name, userId, false, txt) if err != nil { slog.Error("Error writing to database", "error", err) w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error writing to database") return } + _, err = result.LastInsertId() + if err != nil { + slog.Error("Error getting last insert ID", "error", err) + w.WriteStatusMsg(gemini.StatusGeneralPermFail, "Error getting inserted ID") + return + } w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") var content strings.Builder content.WriteString("# Success!\n") content.WriteString("Your entry was added to the guestbook\n\n") - content.WriteString(fmt.Sprintf("username: %s\nentry:\n> %s\n\n", name, txt)) + content.WriteString(fmt.Sprintf("username: %s\n", name)) + content.WriteString("This username was taken from your identity.\n=> /guestbook/setname Set custom name\n\n") + content.WriteString(fmt.Sprintf("entry:\n> %s\n\n", txt)) content.WriteString("For now since the entry has not been approved it will appear in the list with xxxxx in place of chars.\n") content.WriteString("\n\n=> /guestbook Back to guestbook") w.WriteBody([]byte(content.String())) } +func censorString(s string) string { + masked := []rune(s) + for i, c := range masked { + if c != ' ' { + masked[i] = 'x' + } + } + return string(masked) +} + func (book *GuestBook) serveIndex(w gemini.ResponseWriter, req *gemini.Request) { w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini") var content strings.Builder @@ -188,14 +241,8 @@ func (book *GuestBook) serveIndex(w gemini.ResponseWriter, req *gemini.Request) var message string rows.Scan(&id, &name, &approved, &createdAt, &message) if !approved { - // Overwrite all non-space characters in message with "x" - masked := []rune(message) - for i, c := range masked { - if c != ' ' { - masked[i] = 'x' - } - } - message = string(masked) + message = censorString(message) + name = censorString(name) } content.WriteString(fmt.Sprintf("%s :: %s\n> %s\n", createdAt.Format("2006-01-02"), name, message)) content.WriteString("\n") diff --git a/pages/guestbook.gmi b/pages/guestbook.gmi index 9d08908..6e33835 100644 --- a/pages/guestbook.gmi +++ b/pages/guestbook.gmi @@ -6,8 +6,12 @@ Hey gemspace! Thanks for visiting my capsule. Why not leave a message? => /guestbook/new Sign the guestbook => /guestbook/admin Admin - +=> /codeview/raw/personal-gemini-capsule/src/branch/main/internal/guestbook/guestbook.go View code for this page Messages like "xxxxx xxxx" mean they are awaiting my approval. Once approved, the actual text will be rendered. +== 2025.10.09 == + +Addressing the issue gdorn raised I've added the option to set a username. + ------------------------