From dc8a638814ac911a16db5999d1d3ad420c86ab03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 5 Aug 2025 22:58:36 +0200 Subject: [PATCH] Initial commit --- db.go | 31 +++++++++++ go.mod | 9 ++++ go.sum | 12 +++++ main.go | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 db.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/db.go b/db.go new file mode 100644 index 0000000..cd4576f --- /dev/null +++ b/db.go @@ -0,0 +1,31 @@ +package main + +import ( + // External + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" + + // Standard + "fmt" +) + +var ( + db *sqlx.DB +) + +func initDB(host string, port int, dbName, username, password string) (err error) { // {{{ + dbConn := fmt.Sprintf( + "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", + host, + port, + username, + password, + dbName, + ) + + if db, err = sqlx.Connect("postgres", dbConn); err != nil { + return + } + + return +} // }}} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6ddf924 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module nodebb_invite_link + +go 1.24.2 + +require ( + github.com/google/uuid v1.6.0 + github.com/jmoiron/sqlx v1.4.0 + github.com/lib/pq v1.10.9 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..cc996a4 --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f161df2 --- /dev/null +++ b/main.go @@ -0,0 +1,157 @@ +package main + +import ( + // External + "github.com/google/uuid" + + // Standard + "encoding/json" + "flag" + "fmt" + "net/http" + "os" + "strconv" + "strings" + "time" +) + +const VERSION = "v1" + +var ( + flagVersion bool + flagHost string + flagPort int + flagUsername string + flagPassword string + flagDatabase string + flagListenPort int + flagSequenceFilename string + flagDomain string +) + +func init() { + flag.BoolVar(&flagVersion, "version", false, "Display version and exit") + flag.StringVar(&flagHost, "host", "", "Database host") + flag.StringVar(&flagUsername, "username", "", "Database username") + flag.StringVar(&flagPassword, "password", "", "Database password") + flag.StringVar(&flagDatabase, "database", "", "Database name") + flag.IntVar(&flagPort, "port", 5432, "Database port") + flag.IntVar(&flagListenPort, "listen", 9876, "Web server listen port") + flag.StringVar(&flagSequenceFilename, "seq", "sequence", "Sequence filename") + flag.StringVar(&flagDomain, "domain", "", "Domain FQDN") + flag.Parse() +} + +func main() { + err := initDB(flagHost, flagPort, flagDatabase, flagUsername, flagPassword) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + listenOn := fmt.Sprintf("[::]:%d", flagListenPort) + http.HandleFunc("/invite", createInvite) + fmt.Printf("Listen on %s\n", listenOn) + err = http.ListenAndServe(listenOn, nil) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func sequenceNext() (seq int, err error) { + var contents []byte + var oldSequence int + contents, err = os.ReadFile(flagSequenceFilename) + if err != nil && !os.IsNotExist(err) { + return + } + + if err != nil && os.IsNotExist(err) { + oldSequence = 0 + } else { + fixedContents := strings.TrimSpace(string(contents)) + oldSequence, err = strconv.Atoi(fixedContents) + if err != nil { + return + } + } + + seq = oldSequence + 1 + err = os.WriteFile(flagSequenceFilename, []byte(strconv.Itoa(seq)), 0644) + return +} + +func createInvite(w http.ResponseWriter, r *http.Request) { + seq, err := sequenceNext() + if err != nil { + w.Write([]byte(err.Error())) + return + } + + newUUID := uuid.NewString() + email := fmt.Sprintf("%08d@example.com", seq) + expire := time.Now().Add(time.Hour * 24 * 7) + + // legacy_object + key := fmt.Sprintf("invitation:uid:2:invited:%s", email) + _, err = db.Exec(` INSERT INTO public.legacy_object(_key, "type", "expireAt") VALUES($1, 'string', null)`, key) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + key = fmt.Sprintf("invitation:invited:%s", email) + _, err = db.Exec(` INSERT INTO public.legacy_object(_key, "type", "expireAt") VALUES($1, 'set', null)`, key) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + key = fmt.Sprintf("invitation:token:%s", newUUID) + _, err = db.Exec(` INSERT INTO public.legacy_object(_key, "type", "expireAt") VALUES($1, 'hash', $2)`, key, expire) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + // legacy_hash + key = fmt.Sprintf("invitation:token:%s", newUUID) + data := fmt.Sprintf(`{"email": "%s", "token": "%s", "inviter": 2, "groupsToJoin": "[]"}`, email, newUUID) + _, err = db.Exec(` INSERT INTO public.legacy_hash(_key, "data", "type") VALUES($1, $2, 'hash')`, key, data) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + // legacy_set + key = fmt.Sprintf("invitation:uid:2") + _, err = db.Exec(` INSERT INTO public.legacy_set(_key, "member", "type") VALUES($1, $2, 'set')`, key, email) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + key = fmt.Sprintf("invitation:invited:%s", email) + _, err = db.Exec(` INSERT INTO public.legacy_set(_key, "member", "type") VALUES($1, $2, 'set')`, key, newUUID) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + key = fmt.Sprintf("invitation:uid:2:invited:%s", email) + _, err = db.Exec(` INSERT INTO public.legacy_string(_key, "data", "type") VALUES($1, $2, 'string')`, key, newUUID) + if err != nil { + w.Write([]byte(err.Error())) + return + } + + j, _ := json.Marshal(struct { + Link string + }{ + fmt.Sprintf("https://%s/register?token=%s", flagDomain, newUUID), + }) + w.Write(j) + + return +}