cerca

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

commit 34a86544b6b3e4d67dea1cb6cea5a1eaa572f911
parent c4bf9831333903b733f9d89fbf3fe68336edba0d
Author: cblgh <cblgh@cblgh.org>
Date:   Tue, 27 Sep 2022 11:47:05 +0200

add new password reset util

Diffstat:
Acmd/admin-reset/main.go | 100+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrypto/crypto.go | 19+++++++++++++++++++
Mdatabase/database.go | 21++++++++++++++++++++-
3 files changed, 139 insertions(+), 1 deletion(-)

diff --git a/cmd/admin-reset/main.go b/cmd/admin-reset/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "cerca/crypto" + "cerca/database" + "cerca/util" + "flag" + "fmt" + "os" +) + +func inform(msg string, args ...interface{}) { + if len(args) > 0 { + fmt.Printf("admin-reset: %s\n", fmt.Sprintf(msg, args...)) + } else { + fmt.Printf("admin-reset: %s\n", msg) + } +} + +func complain(msg string, args ...interface{}) { + if len(args) > 0 { + inform(msg, args) + } else { + inform(msg) + } + os.Exit(0) +} + +func main() { + var keypairFlag bool + var passwordFlag bool + var username string + var dbPath string + 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") + flag.BoolVar(&keypairFlag, "keypair", false, "reset the keypair") + flag.BoolVar(&passwordFlag, "password", false, "reset the password. if true generates a random new password") + flag.Parse() + + usage := `usage + admin-reset --database ./data/forum.db --username <username to reset> [--keypair, --password] + admin-reset --help for more information + + examples: + # only reset the keypair, leaving the password intact + ./admin-reset --database ../../testdata/forum.db --username bambas --keypair + + # reset password only + ./admin-reset --database ../../testdata/forum.db --username bambas --password + + # reset both password and keypair + ./admin-reset --database ../../testdata/forum.db --username bambas --password --keypair + ` + + if username == "" { + complain(usage) + } + if !keypairFlag && !passwordFlag { + complain("nothing to reset, exiting") + } + + // check if database exists! we dont wanna create a new db in this case ':) + if !database.CheckExists(dbPath) { + complain("couldn't find database at %s", dbPath) + } + + db := database.InitDB(dbPath) + ed := util.Describe("admin reset") + + userid, err := db.GetUserID(username) + if err != nil { + complain("username %s not in database", username) + } + + // generate new password for user and set it in the database + if passwordFlag { + newPassword := crypto.GeneratePassword() + passwordHash, err := crypto.HashPassword(newPassword) + ed.Check(err, "hash new password") + db.UpdateUserPasswordHash(userid, passwordHash) + + inform("successfully updated %s's password hash", username) + inform("new temporary password %s", newPassword) + } + + // generate a new keypair for user and update user's pubkey record with new pubkey + if keypairFlag { + 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") + err = db.SetPubkey(userid, pubkey) + ed.Check(err, "set new pubkey in database") + + inform("successfully changed %s's stored public key", username) + inform("new keypair\n%s", string(kpBytes)) + } +} diff --git a/crypto/crypto.go b/crypto/crypto.go @@ -9,6 +9,7 @@ import ( "encoding/json" "fmt" "github.com/synacor/argon2id" + "math/big" rand "math/rand" "os" "strings" @@ -163,6 +164,24 @@ func GenerateNonce() string { return fmt.Sprintf("%d%d", time.Now().Unix(), rnd.Intn(MaxInt)) } +// used for generating a random reset password +const characterSet = "abcdedfghijklmnopqrstABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +const pwlength = 20 + +func GeneratePassword() string { + var password strings.Builder + const maxChar = int64(len(characterSet)) + + for i := 0; i < pwlength; i++ { + max := big.NewInt(maxChar) + bigN, err := crand.Int(crand.Reader, max) + util.Check(err, "randomly generate int") + n := bigN.Int64() + password.WriteString(string(characterSet[n])) + } + return password.String() +} + type cryptoSource struct{} func (s cryptoSource) Seed(seed int64) {} diff --git a/database/database.go b/database/database.go @@ -20,8 +20,16 @@ type DB struct { db *sql.DB } -func InitDB(filepath string) DB { +func CheckExists(filepath string) bool { if _, err := os.Stat(filepath); errors.Is(err, os.ErrNotExist) { + return false + } + return true +} + +func InitDB(filepath string) DB { + exists := CheckExists(filepath) + if !exists { file, err := os.Create(filepath) if err != nil { log.Fatal(err) @@ -405,6 +413,17 @@ func (d DB) AddPubkey(userid int, pubkey string) error { 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