commit 34a86544b6b3e4d67dea1cb6cea5a1eaa572f911
parent c4bf9831333903b733f9d89fbf3fe68336edba0d
Author: cblgh <cblgh@cblgh.org>
Date: Tue, 27 Sep 2022 11:47:05 +0200
add new password reset util
Diffstat:
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