cerca

lean forum software (pmc local branch)
Log | Files | Refs | README | LICENSE

commit 1257d66c5d9e851c555e38ac56cbd4d169b58708
parent a192f455eef06015114d3b1d99dd584f6da483c9
Author: cblgh <cblgh@cblgh.org>
Date:   Tue,  5 Dec 2023 20:47:08 +0100

remove public key concept and password tool

cleaning up ResetPasswordRoute, at least for the interim

Diffstat:
M.gitignore | 1+
Mcmd/admin-add-user/main.go | 24++----------------------
Dcmd/pwtool/main.go | 28----------------------------
Dcmd/pwtool/main_test.go | 19-------------------
Mcrypto/crypto.go | 132+------------------------------------------------------------------------------
Mdatabase/database.go | 64----------------------------------------------------------------
Mhtml/change-password.html | 4----
Mhtml/password-reset.html | 18+-----------------
Mi18n/i18n.go | 65+++--------------------------------------------------------------
Mserver/server.go | 154++++---------------------------------------------------------------------------
10 files changed, 15 insertions(+), 494 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -3,4 +3,5 @@ data/ data/.gitkeep pwtool +admin-reset *.json diff --git a/cmd/admin-add-user/main.go b/cmd/admin-add-user/main.go @@ -10,7 +10,7 @@ import ( ) type UserInfo struct { - Username, Password, Keypair string + Username, Password string } func createUser (username, password string, db *database.DB) UserInfo { @@ -35,22 +35,7 @@ func createUser (username, password string, db *database.DB) UserInfo { if err = ed.Eout(err, "add registration"); err != nil { complain("Database had a problem saving user registration location") } - // generate and pass public keypair - keypair, err := crypto.GenerateKeypair() - ed.Check(err, "generate keypair") - // record generated pubkey in database for eventual later use - pub, err := keypair.PublicString() - if err = ed.Eout(err, "convert pubkey to string"); err != nil { - complain("Can't convert pubkey to string") - } - ed.Check(err, "stringify pubkey") - err = db.AddPubkey(userID, pub) - if err = ed.Eout(err, "insert pubkey in db"); err != nil { - complain("Database had a problem saving user registration") - } - kpJson, err := keypair.Marshal() - ed.Check(err, "marshal keypair") - return UserInfo{Username: username, Password: password, Keypair: string(kpJson)} + return UserInfo{Username: username, Password: password} } func inform(msg string, args ...interface{}) { @@ -73,9 +58,7 @@ func complain(msg string, args ...interface{}) { func main() { var username string var forumDomain string - var keypairFlag bool var dbPath string - flag.BoolVar(&keypairFlag, "keypair", false, "output the keypair") flag.StringVar(&forumDomain, "url", "https://forum.merveilles.town", "root url to forum, referenced in output") flag.StringVar(&username, "username", "", "username whose credentials should be reset") flag.StringVar(&dbPath, "database", "./data/forum.db", "full path to the forum database; e.g. ./data/forum.db") @@ -104,9 +87,6 @@ func main() { inform("[user]\n%s", username) inform("[password]\n%s", newPassword) - if keypairFlag { - inform("[keypair]\n%s", info.Keypair) - } inform("Please login at %s\n", loginRoute) inform("After logging in, visit %s to reset your password", resetRoute) } diff --git a/cmd/pwtool/main.go b/cmd/pwtool/main.go @@ -1,28 +0,0 @@ -package main - -import ( - "cerca/crypto" - "flag" - "fmt" - "os" -) - -func main() { - var kpPath string - var payload string - flag.StringVar(&kpPath, "keypair", "", "path to your account-securing public-keypair (the thing you got during registering and was told to save:)") - flag.StringVar(&payload, "payload", "", "the payload presented on the restore password page") - flag.Parse() - - if kpPath == "" { - fmt.Println(`usage: - tool --keypair <path-to-keypair.json> --payload <payload from website> - tool --help for more information`) - os.Exit(0) - } - kp, _ := crypto.ReadKeypair(kpPath) - proof := crypto.CreateProof(kp, []byte(payload)) - fmt.Println("your proof:") - fmt.Println(fmt.Sprintf("%x", proof)) - fmt.Println("\nplease paste the proof in the proof box of the restore password page, thank you!") -} diff --git a/cmd/pwtool/main_test.go b/cmd/pwtool/main_test.go @@ -1,19 +0,0 @@ -package main_test - -import ( - "cerca/crypto" - "github.com/stretchr/testify/assert" - "testing" -) - -func TestMain(t *testing.T) { - a := assert.New(t) - - kp, err := crypto.GenerateKeypair() - a.NoError(err) - msg := []byte("hi") - proof := crypto.CreateProof(kp, msg) - a.NotZero(len(proof), "proof length greater than zero") - proofVerificationCorrect := crypto.VerifyProof(kp.Public, msg, proof) - a.True(proofVerificationCorrect) -} diff --git a/crypto/crypto.go b/crypto/crypto.go @@ -2,139 +2,17 @@ package crypto import ( "cerca/util" - "crypto/ed25519" crand "crypto/rand" - "encoding/base64" "encoding/binary" - "encoding/json" - "fmt" "github.com/synacor/argon2id" "math/big" rand "math/rand" - "os" "strings" - "time" ) -type Keypair struct { - Public ed25519.PublicKey `json:"public"` - Private ed25519.PrivateKey `json:"private"` -} - -func GenerateKeypair() (Keypair, error) { - ed := util.Describe("generate public keypair") - pub, priv, err := ed25519.GenerateKey(nil) - if err != nil { - return Keypair{}, ed.Eout(err, "generating key") - } - return Keypair{Public: pub, Private: priv}, nil -} - -func ReadKeypair(kpPath string) (Keypair, error) { - var kp Keypair - ed := util.Describe("read keypair from disk") - b, err := os.ReadFile(kpPath) - if err != nil { - return kp, ed.Eout(err, "read file") - } - err = kp.Unmarshal(b) - if err != nil { - return kp, ed.Eout(err, "unmarshal kp") - } - return kp, nil -} - -func CreateProof(kp Keypair, payload []byte) []byte { - return ed25519.Sign(kp.Private, payload) -} - -func VerifyProof(public ed25519.PublicKey, payload, proof []byte) bool { - return ed25519.Verify(public, payload, proof) -} - -/* kinda cludgy oh well */ -func (kp *Keypair) PublicString() (string, error) { - b, err := json.Marshal(kp.Public) - if err != nil { - return "", util.Eout(err, "marshal public key") - } - return string(b), nil -} - -func (kp *Keypair) PrivateString() (string, error) { - b, err := json.Marshal(kp.Private) - if err != nil { - return "", util.Eout(err, "marshal private key") - } - return string(b), nil -} - -func (kp *Keypair) Marshal() ([]byte, error) { - jason, err := json.MarshalIndent(kp, "", " ") - if err != nil { - return []byte{}, util.Eout(err, "marshal keypair") - } - return jason, nil -} - -func (kp *Keypair) Unmarshal(input []byte) error { - ed := util.Describe("unmarshal keypair") - type stringKp struct { - Public string `json:"public"` - Private string `json:"private"` - } - var m stringKp - err := json.Unmarshal(input, &m) - if err != nil { - return ed.Eout(err, "unmarshal into string struct") - } - - // handle the unfortunate case that the first generated keypairs were all in hex :') - // meaning: convert them from hex to base64 (the format expected by crypto/ed25519) - if len(m.Private) == 128 { - convertedVal, err := util.Hex2Base64(m.Private) - if err != nil { - return ed.Eout(err, "failed to convert privkey hex to base64") - } - m.Private = convertedVal - convertedVal, err = util.Hex2Base64(m.Public) - if err != nil { - return ed.Eout(err, "failed to convert pubkey hex to base64") - } - m.Public = convertedVal - - // marshal the corrected version to a slice of bytes, so that we can pretend this debacle never happened - input, err = json.Marshal(m) - if err != nil { - return ed.Eout(err, "failed to marshal converted hex") - } - } - - err = json.Unmarshal(input, kp) - if err != nil { - return ed.Eout(err, "unmarshal keypair") - } - return nil -} - -func PublicKeyFromString(s string) ed25519.PublicKey { - ed := util.Describe("public key from string") - var err error - // handle legacy case of some pubkeys being stored in wrong column due to faulty query <.< - s = strings.ReplaceAll(s, `"`, "") - // handle legacy case of some pubkeys being stored as hex - if len(s) == 64 { - s, err = util.Hex2Base64(s) - ed.Check(err, "convert hex to base64") - } - b, err := base64.StdEncoding.DecodeString(s) - ed.Check(err, "decode base64 string") - pub := (ed25519.PublicKey)(b) - return pub -} - func HashPassword(s string) (string, error) { ed := util.Describe("hash password") + // TODO (2023-12-05): see about argon2id replacement (or getting 32 bit bug fixed) hash, err := argon2id.DefaultHashPassword(s) if err != nil { return "", ed.Eout(err, "hashing with argon2id") @@ -156,14 +34,6 @@ func GenerateVerificationCode() int { return rnd.Intn(999999) } -func GenerateNonce() string { - const MaxUint = ^uint(0) - const MaxInt = int(MaxUint >> 1) - var src cryptoSource - rnd := rand.New(src) - return fmt.Sprintf("%d%d", time.Now().Unix(), rnd.Intn(MaxInt)) -} - // used for generating a random reset password const characterSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" const pwlength = 20 diff --git a/database/database.go b/database/database.go @@ -63,20 +63,6 @@ func createTables(db *sql.DB) { ); `, ` - CREATE TABLE IF NOT EXISTS nonces ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - nonce TEXT NOT NULL UNIQUE - ); - `, - ` - CREATE TABLE IF NOT EXISTS pubkeys ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - pubkey TEXT NOT NULL UNIQUE, - userid integer NOT NULL UNIQUE, - FOREIGN KEY (userid) REFERENCES users(id) - ); - `, - ` CREATE TABLE IF NOT EXISTS registrations ( id INTEGER PRIMARY KEY AUTOINCREMENT, userid INTEGER, @@ -387,11 +373,6 @@ func (d DB) CheckUserExists(userid int) (bool, error) { return d.existsQuery(stmt, userid) } -func (d DB) CheckNonceExists(nonce string) (bool, error) { - stmt := `SELECT 1 FROM nonces WHERE nonce = ?` - return d.existsQuery(stmt, nonce) -} - func (d DB) CheckUsernameExists(username string) (bool, error) { stmt := `SELECT 1 FROM users WHERE name = ?` return d.existsQuery(stmt, username) @@ -415,51 +396,6 @@ func (d DB) DeleteUser(userid int) { util.Check(err, "deleting user %d", userid) } -func (d DB) AddPubkey(userid int, pubkey string) error { - ed := util.Describe("add pubkey") - // TODO (2022-02-03): the insertion order is wrong >.< - stmt := `INSERT INTO pubkeys (pubkey, userid) VALUES (?, ?)` - _, err := d.Exec(stmt, userid, pubkey) - if err = ed.Eout(err, "inserting record"); err != nil { - return err - } - return nil -} - -func (d DB) SetPubkey(userid int, pubkey string) error { - ed := util.Describe("set pubkey") - // TODO (2022-09-27): the insertion order is still wrong >.< - stmt := `UPDATE pubkeys SET pubkey = ? WHERE userid = ? ` - _, err := d.Exec(stmt, userid, pubkey) - if err = ed.Eout(err, "updating record"); err != nil { - return err - } - return nil -} - -func (d DB) GetPubkey(userid int) (pubkey string, err error) { - ed := util.Describe("get pubkey") - // due to a mishap in the query in AddPubkey the column `pubkey` contains the userid - // and the column `userid` contains the pubkey - // :')))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) - // TODO (2022-02-03): when we have migration logic, fix this mishap - stmt := `SELECT userid from pubkeys where pubkey = ?` - err = d.db.QueryRow(stmt, userid).Scan(&pubkey) - err = ed.Eout(err, "query & scan") - return -} - -// TODO (2022-02-04): extend mrv verification code length and reuse nonce scheme to fix registration bug? -func (d DB) AddNonce(nonce string) error { - ed := util.Describe("add nonce") - stmt := `INSERT INTO nonces (nonce) VALUES (?)` - _, err := d.Exec(stmt, nonce) - if err != nil { - return ed.Eout(err, "insert nonce") - } - return nil -} - func (d DB) AddRegistration(userid int, verificationLink string) error { ed := util.Describe("add registration") stmt := `INSERT INTO registrations (userid, host, link, time) VALUES (?, ?, ?, ?)` diff --git a/html/change-password.html b/html/change-password.html @@ -12,10 +12,6 @@ <div><small id="password-help">{{ "PasswordMin" | translate }}.</small></div> </div> <div> - <input type="checkbox" value="true" name ="reset-keypair" id="reset-keypair"> - <label for="reset-keypair" style="display: inline-block;">{{ "GenerateNewKeypair" | translate }}</label> - </div> - <div> <input type="submit" value="Submit"> </div> </form> diff --git a/html/password-reset.html b/html/password-reset.html @@ -1,4 +1,5 @@ {{ template "head" . }} +<p>This page is currently being reconstructed; if you can't log in, contact admin for help to reset your password :)</p> <p>{{ "PasswordResetDescription" | translate }}</p> <p>{{ "PasswordResetUsernameQuestion" | translate }}</p> {{ if eq .Data.Action "/reset/generate" }} @@ -6,7 +7,6 @@ <label type="text" for="username">{{ "Username" | translate | capitalize }}:</label> <input required id="username" name="username"> <div> - <input type="submit" value='{{ "GeneratePayload" | translate | capitalize }}'> </div> </form> {{ end }} @@ -18,22 +18,6 @@ <pre style="user-select: all;"> <code>{{ .Data.Payload }}</code> </pre> -<p> {{ "PasswordResetFollowToolInstructions" | translate | tohtml }}</p> -<details> - <summary>{{ "ToolInstructions" | translate | capitalize }}</summary> - {{ "PasswordResetToolInstructions" | translate | tohtml }} -</details> -<form method="post" action="{{.Data.Action}}"> - <input type="hidden" required id="username" name="username" value="{{ .Data.Username }}"> - <input type="hidden" required id="payload" name="payload" value="{{ .Data.Payload }}"> - <label for="proof">{{ "Proof" | translate | capitalize }}</label> - <input type="text" required id="proof" name="proof"> - <label for="password">{{ "NewPassword" | translate | capitalize }}</label> - <input type="password" minlength="9" required id="password" name="password" aria-describedby="password-help"> - <div> - <input type="submit" value='{{ "ChangePassword" | translate | capitalize }}'> - </div> -</form> {{ end }} {{ template "footer" . }} diff --git a/i18n/i18n.go b/i18n/i18n.go @@ -34,8 +34,7 @@ var English = map[string]string{ "Username": "username", "Current": "current", "New": "new", - "GenerateNewKeypair": "I also want to generate a new keypair", - "ChangePasswordDescription": "Use this page to change your password. If needed, you can also regenerate your password reset keypair—used to reset a forgotten password without admin help.", + "ChangePasswordDescription": "Use this page to change your password.", "Password": "password", "PasswordMin": "Must be at least 9 characters long", "PasswordForgot": "Forgot your password?", @@ -52,7 +51,6 @@ var English = map[string]string{ "TextareaPlaceholder": "Tabula rasa", "PasswordReset": "reset password", - "PasswordResetMessage": "You are logged in, log out to reset password using proof", "PasswordResetSuccess": "Reset password—success!", "PasswordResetSuccessMessage": "You reset your password!", "PasswordResetSuccessLinkMessage": "Give it a try and", @@ -86,9 +84,6 @@ var English = map[string]string{ "ThreadStartNew": "Start a new thread", "RegisterHTMLMessage": `You now have an account! Welcome. Visit the <a href="/">index</a> to read and reply to threads, or start a new one.`, - "RegisterKeypairExplanationStart": `There's just one more thing: <b>save the key displayed below</b>. It is a <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">keypair</a> describing your forum identity, with a private part that only you know; the forum only stores the public portion.`, - "RegisterViewKeypairExplanationEnd": `With this keypair you will be able to reset your account if you ever lose your password—and without having to share your email (or require email infrastructure on the forum's part).`, - "RegisterKeypairWarning": "This keypair will only be displayed once", "RegisterVerificationCode": "Your verification code is", "RegisterVerificationInstructionsTitle": "Verification instructions", @@ -96,22 +91,6 @@ var English = map[string]string{ "RegisterConductCodeBoxOne": `I have refreshed my memory of the <a target="_blank" href="{{ .Data.Link }}">{{ .Data.Name }} Code of Conduct</a>`, "RegisterConductCodeBoxTwo": `Yes, I have actually <a target="_blank" href="{{ .Data.Link }}">read it</a>`, - "PasswordResetDescription": "On this page we'll go through a few steps to securely reset your password—without resorting to any emails!", - "PasswordResetUsernameQuestion": "First up: what was your username?", - "PasswordResetCopyPayload": `Now, first copy the snippet (aka <i>proof payload</i>) below`, - "PasswordResetFollowToolInstructions": `Follow the <b>tool instructions</b> to finalize the password reset.`, - "ToolInstructions": `tool instructions`, - "PasswordResetToolInstructions": fmt.Sprintf(` - <ul> - <li><a href="%s">Download the tool</a></li> - <li>Run as:<br><code>pwtool --payload &lt;proof payload from above&gt; --keypair &lt;path to file with yr keypair from registration&gt;</code> - </li> - <li>Copy the generated proof and paste below</li> - <li>(Remember to save your password :)</li> - </ul> - `, toolURL), - "GeneratePayload": "generate payload", - "Proof": "proof", "NewPassword": "new password", "ChangePassword": "change password", } @@ -140,8 +119,7 @@ var Swedish = map[string]string{ "Username": "användarnamn", "Current": "nuvarande", "New": "nytt", - "GenerateNewKeypair": "Jag vill också generera ett nytt nyckelpar", - "ChangePasswordDescription": "På den här sidan kan du ändra ditt lösenord. Vid behov kan du också regenerera ditt nyckelpar—används för att nollställa ditt lösenord utan att be admin om hjälp.", + "ChangePasswordDescription": "På den här sidan kan du ändra ditt lösenord.", "Password": "lösenord", "PasswordMin": "Måste vara minst 9 karaktärer långt", "PasswordForgot": "Glömt lösenordet?", @@ -158,7 +136,6 @@ var Swedish = map[string]string{ "TextareaPlaceholder": "Tabula rasa", "PasswordReset": "nollställ lösenord", - "PasswordResetMessage": "Du är inloggad, logga ut för att nollställga lösenordet med skapat lösenordsbevis", "PasswordResetSuccess": "Nollställning av lösenord—lyckades!", "PasswordResetSuccessMessage": "Du har nollställt ditt lösenord!", "PasswordResetSuccessLinkMessage": "Ge det ett försök och", @@ -192,9 +169,6 @@ var Swedish = map[string]string{ "ThreadStartNew": "Starta ny tråd", "RegisterHTMLMessage": `Du har nu ett konto! Välkommen. Besök <a href="/">trådindexet</a> för att läsa och svara på trådar, eller för att starta en ny.`, - "RegisterKeypairExplanationStart": `En grej till: <b>spara nyckeln du ser nedan</b>. Det är ett <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">nyckelpar</a> som tillhandahåller din forumidentitet, och inkluderar en hemlig del som bara du vet om och endast visas nu; forumdatabasen kommer enbart ihåg den publika delen.`, - "RegisterViewKeypairExplanationEnd": `Med detta nyckelpar kan du återställa ditt lösenord om du skulle tappa bort det—och detta utan att behöva samla in din email (eller kräva emailinfrastruktur på forumets sida).`, - "RegisterKeypairWarning": "Detta nyckelpar visas enbart denna gång", "RegisterVerificationCode": "Din verifikationskod är", "RegisterVerificationInstructionsTitle": "Verification instructions", @@ -204,20 +178,6 @@ var Swedish = map[string]string{ "PasswordResetDescription": "På denna sida går vi igenom ett par steg för att säkert nollställa ditt lösenord—utan att behöva ta till mejl!", "PasswordResetUsernameQuestion": "För de första: hur löd användarnamnet?", - "PasswordResetCopyPayload": `Kopiera nu textsnutten nedan (aka <i>beviset</i>)`, - "PasswordResetFollowToolInstructions": `Följ <b>verktygsinstruktionerna</b> för att finalisera nollställningen.`, - "ToolInstructions": `verktygsinstruktionerna`, - "PasswordResetToolInstructions": fmt.Sprintf(` - <ul> - <li><a href="%s">Ladda ned verktyget</a></li> - <li>Kör det så hör:<br><code>pwtool --payload &lt;payload från ovan&gt; --keypair &lt;filvägen innehållandes ditt nyckelpar från när du registrerade dig&gt;</code> - </li> - <li>Kopiera det genererade beviset och klistra in nedan</li> - <li>(Kom ihåg att spara ditt lösenord:)</li> - </ul> - `, toolURL), - "GeneratePayload": "skapa payload", - "Proof": "bevis", "NewPassword": "nytt lösenord", "ChangePassword": "ändra lösenord", } @@ -246,8 +206,7 @@ var EspanolMexicano = map[string]string{ "Username": "usuarie", "Current": "current", "New": "new", - "GenerateNewKeypair": "I also want to generate a new keypair", - "ChangePasswordDescription": "Use this page to change your password. If needed, you can also regenerate your password reset keypair—used to reset a forgotten password without admin help.", + "ChangePasswordDescription": "Use this page to change your password.", "Password": "contraseña", "PasswordMin": "Debe tener por lo menos 9 caracteres.", "PasswordForgot": "Olvidaste tu contraseña?", @@ -264,7 +223,6 @@ var EspanolMexicano = map[string]string{ "TextareaPlaceholder": "Tabula rasa", "PasswordReset": "reset password", - "PasswordResetMessage": "You are logged in, log out to reset password using proof", "PasswordResetSuccess": "Reset password—success!", "PasswordResetSuccessMessage": "You reset your password!", "PasswordResetSuccessLinkMessage": "Give it a try and", @@ -297,9 +255,6 @@ var EspanolMexicano = map[string]string{ "YourAnswer": "Your answer", "RegisterHTMLMessage": `You now have an account! Welcome. Visit the <a href="/">index</a> to read and reply to threads, or start a new one.`, - "RegisterKeypairExplanationStart": `There's just one more thing: <b>save the key displayed below</b>. It is a <a href="https://en.wikipedia.org/wiki/Public-key_cryptography">keypair</a> describing your forum identity, with a private part that only you know; the forum only stores the public portion.`, - "RegisterViewKeypairExplanationEnd": `With this keypair you will be able to reset your account if you ever lose your password—and without having to share your email (or require email infrastructure on the forum's part).`, - "RegisterKeypairWarning": "This keypair will only be displayed once", "RegisterVerificationCode": "Your verification code is", "RegisterVerificationInstructionsTitle": "Verification instructions", @@ -309,20 +264,6 @@ var EspanolMexicano = map[string]string{ "PasswordResetDescription": "On this page we'll go through a few steps to securely reset your password—without resorting to any emails!", "PasswordResetUsernameQuestion": "First up: what was your username?", - "PasswordResetCopyPayload": `Now, first copy the snippet (aka <i>proof payload</i>) below`, - "PasswordResetFollowToolInstructions": `Follow the <b>tool instructions</b> to finalize the password reset.`, - "ToolInstructions": `tool instructions`, - "PasswordResetToolInstructions": fmt.Sprintf(` - <ul> - <li><a href="%s">Download the tool</a></li> - <li>Run as:<br><code>pwtool --payload &lt;proof payload from above&gt; --keypair &lt;path to file with yr keypair from registration&gt;</code> - </li> - <li>Copy the generated proof and paste below</li> - <li>(Remember to save your password :)</li> - </ul> - `, toolURL), - "GeneratePayload": "generate payload", - "Proof": "proof", "NewPassword": "new password", "ChangePassword": "change password", } diff --git a/server/server.go b/server/server.go @@ -2,7 +2,6 @@ package server import ( "context" - "encoding/hex" "errors" "fmt" "html/template" @@ -51,7 +50,6 @@ type PasswordResetData struct { type ChangePasswordData struct { Action string - Keypair string } type IndexData struct { @@ -74,10 +72,6 @@ type RegisterData struct { ConductLink string } -type RegisterSuccessData struct { - Keypair string -} - type LoginData struct { FailedAttempt bool } @@ -480,23 +474,6 @@ func (h RequestHandler) handleChangePassword(res http.ResponseWriter, req *http. case "/reset/submit": oldPassword := req.PostFormValue("password-old") newPassword := req.PostFormValue("password-new") - resetKeypair := (req.PostFormValue("reset-keypair") == "true") - var keypairString string - - // check if we're resetting keypair - if resetKeypair { - // if so: generate new keypair - kp, err := crypto.GenerateKeypair() - ed.Check(err, "generate keypair") - kpBytes, err := kp.Marshal() - ed.Check(err, "marshal keypair") - pubkey, err := kp.PublicString() - ed.Check(err, "get pubkey string") - // and set it in db - err = h.db.SetPubkey(uid, pubkey) - ed.Check(err, "set new pubkey in database") - keypairString = string(kpBytes) - } // check that the submitted, old password is valid username, err := h.db.GetUsername(uid) @@ -526,7 +503,7 @@ func (h RequestHandler) handleChangePassword(res http.ResponseWriter, req *http. // then save the hash h.db.UpdateUserPasswordHash(uid, pwhashNew) // render a success message & show a link to the login page :') - h.renderView(res, "change-password-success", TemplateData{HasRSS: h.config.RSS.URL != "", LoggedIn: true, Data: ChangePasswordData{Keypair: keypairString}}) + h.renderView(res, "change-password-success", TemplateData{HasRSS: h.config.RSS.URL != "", LoggedIn: true, Data: ChangePasswordData{}}) default: fmt.Printf("unsupported POST route (%s), redirecting to /\n", req.URL.Path) IndexRedirect(res, req) @@ -538,130 +515,28 @@ func (h RequestHandler) handleChangePassword(res http.ResponseWriter, req *http. } func (h RequestHandler) ResetPasswordRoute(res http.ResponseWriter, req *http.Request) { - ed := util.Describe("password proof route") loggedIn, _ := h.IsLoggedIn(req) title := util.Capitalize(h.translator.Translate("PasswordReset")) - // change password functionality, handle this in another function + // the user was logged in, let them change their password themselves :) if loggedIn { h.handleChangePassword(res, req) return } - renderErr := func(errFmt string, args ...interface{}) { + renderPlaceholder := func(errFmt string, args ...interface{}) { errMessage := fmt.Sprintf(errFmt, args...) fmt.Println(errMessage) data := GenericMessageData{ Title: title, Message: errMessage, - Link: "/reset", + Link: "/", LinkText: h.translator.Translate("GoBack"), } h.renderView(res, "generic-message", TemplateData{Data: data, Title: title}) } - - switch req.Method { - case "GET": - switch req.URL.Path { - case "/reset/submit": - params := req.URL.Query() - getParam := func(key string) string { - if q, exists := params[key]; exists { - return q[0] - } - fmt.Println("can't find param", key) - return "" - } - username := getParam("username") - payload := getParam("payload") - h.renderView(res, "password-reset", TemplateData{Data: PasswordResetData{Action: "/reset/submit", Username: username, Payload: payload}}) - default: - h.renderView(res, "password-reset", TemplateData{Data: PasswordResetData{Action: "/reset/generate"}}) - } - case "POST": - username := req.PostFormValue("username") - switch req.URL.Path { - case "/reset/generate": - constructProofPayload := func() string { - return fmt.Sprintf("%s::%s", username, crypto.GenerateNonce()) - } - payload := constructProofPayload() - params := fmt.Sprintf("?payload=%s&username=%s", payload, username) - http.Redirect(res, req, "/reset/submit"+params, http.StatusSeeOther) - case "/reset/submit": - password := req.PostFormValue("password") - proofString := req.PostFormValue("proof") - payload := req.PostFormValue("payload") - - // make sure the user exists - userid, err := h.db.GetUserID(username) - if err != nil { - renderErr("Wrong username, or a non-existent user") - return - } - - // make sure the nonce / payload is not being reused - nonceExisted, err := h.db.CheckNonceExists(payload) - if err != nil { - dump(ed.Eout(err, "check nonce existed")) - return - } - if nonceExisted { - renderErr("This payload has already been used, please generate a new one") - return - } - - // get the pubkey, as it is saved in the database for the corresponding user - pubkeyString, err := h.db.GetPubkey(userid) - if err != nil { - renderErr("No matching pubkey found") - return - } - // convert to ed25519.PublicKey - pubkey := crypto.PublicKeyFromString(pubkeyString) - - proof, err := hex.DecodeString(proofString) - if err != nil { - renderErr("The proof format was incorrect") - return - } - - correct := crypto.VerifyProof(pubkey, []byte(payload), proof) - if !correct { - renderErr("The proof was incorrect") - return - } - // proof was correct! - // save the nonce, so it's not reused - err = h.db.AddNonce(payload) - if err != nil { - dump(ed.Eout(err, "insert nonce into database")) - return - } - // let's set the new password in the database. first, hash it - pwhash, err := crypto.HashPassword(password) - if err != nil { - dump(ed.Eout(err, "hash password during reset")) - return - } - h.db.UpdateUserPasswordHash(userid, pwhash) - // render a success message & show a link to the login page :') - data := GenericMessageData{ - Title: h.translator.Translate("PasswordResetSuccess"), - Message: h.translator.Translate("PasswordResetSuccessMessage"), - Link: "/login", - LinkMessage: h.translator.Translate("PasswordResetSuccessLinkMessage"), - LinkText: h.translator.Translate("Login"), - } - h.renderView(res, "generic-message", TemplateData{Data: data, Title: title}) - default: - fmt.Printf("unsupported POST route (%s), redirecting to /\n", req.URL.Path) - IndexRedirect(res, req) - } - default: - fmt.Println("non get/post method, redirecting to index") - IndexRedirect(res, req) - } + renderPlaceholder("Password reset under construction: please contact admin if you need help resetting yr pw :)") + return } func (h RequestHandler) RegisterRoute(res http.ResponseWriter, req *http.Request) { @@ -764,22 +639,7 @@ func (h RequestHandler) RegisterRoute(res http.ResponseWriter, req *http.Request if err = ed.Eout(err, "add registration"); err != nil { dump(err) } - // generate and pass public keypair - keypair, err := crypto.GenerateKeypair() - ed.Check(err, "generate keypair") - // record generated pubkey in database for eventual later use - pub, err := keypair.PublicString() - if err = ed.Eout(err, "convert pubkey to string"); err != nil { - dump(err) - } - ed.Check(err, "stringify pubkey") - err = h.db.AddPubkey(userID, pub) - if err = ed.Eout(err, "insert pubkey in db"); err != nil { - dump(err) - } - kpJson, err := keypair.Marshal() - ed.Check(err, "marshal keypair") - h.renderView(res, "register-success", TemplateData{Data: RegisterSuccessData{string(kpJson)}, HasRSS: h.config.RSS.URL != "", LoggedIn: loggedIn, Title: h.translator.Translate("RegisterSuccess")}) + h.renderView(res, "register-success", TemplateData{HasRSS: h.config.RSS.URL != "", LoggedIn: loggedIn, Title: h.translator.Translate("RegisterSuccess")}) default: fmt.Println("non get/post method, redirecting to index") IndexRedirect(res, req)