introduce the password reminder infrastructure

This commit is contained in:
2025-10-06 16:42:23 -06:00
parent 33c2fcc471
commit fe360e23d3
4 changed files with 41 additions and 12 deletions
+30 -8
View File
@@ -15,7 +15,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash/crc32" "hash/crc32"
"math/rand"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -26,6 +25,33 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
// PasswordChangeRequest represents a temporary password change request.
type PasswordChangeRequest struct {
Uid int32
Username string
Email string
Authentication int32
Expires time.Time
}
// passwordRequests contains a map of password change requests currently managed.
var passwordRequests map[int32]*PasswordChangeRequest = make(map[int32]*PasswordChangeRequest)
/* AmNewPasswordChangeRequest creates a new password change request and enrolls it.
* Parameters:
* uid - The UID of the user.
* username - The user name of the user.
* email - The E-mail address of the user.
* Returns:
* Pointer to the new PasswordChangeRequest.
*/
func AmNewPasswordChangeRequest(uid int32, username string, email string) *PasswordChangeRequest {
rc := PasswordChangeRequest{Uid: uid, Username: username, Email: email,
Authentication: util.GenerateRandomConfirmationNumber(), Expires: time.Now().Add(time.Hour)}
passwordRequests[uid] = &rc
return &rc
}
// User represents a user in the Amsterdam database. // User represents a user in the Amsterdam database.
type User struct { type User struct {
Mutex sync.RWMutex Mutex sync.RWMutex
@@ -140,7 +166,7 @@ func (u *User) ConfirmEMailAddress(confnum int32, remoteIP string) error {
func (u *User) NewEmailConfirmationNumber() error { func (u *User) NewEmailConfirmationNumber() error {
u.Mutex.Lock() u.Mutex.Lock()
defer u.Mutex.Unlock() defer u.Mutex.Unlock()
newnum := newEmailConfirmationNumber() newnum := util.GenerateRandomConfirmationNumber()
_, err := amdb.Exec("UPDATE user SET email_confnum = ? WHERE uid = ?", newnum, u.Uid) _, err := amdb.Exec("UPDATE user SET email_confnum = ? WHERE uid = ?", newnum, u.Uid)
if err != nil { if err != nil {
u.EmailConfNum = newnum u.EmailConfNum = newnum
@@ -384,11 +410,6 @@ func AmAuthenticateUserByToken(authString string, remoteIP string) (*User, error
return user, nil return user, nil
} }
// newEmailConfirmationNumber returns a new E-mail confirmation number.
func newEmailConfirmationNumber() int32 {
return rand.Int31n(9000000) + 1000000
}
/* AmCreateNewUser creates a new user record in the database. /* AmCreateNewUser creates a new user record in the database.
* Parameters: * Parameters:
* username - New user name. * username - New user name.
@@ -421,7 +442,8 @@ func AmCreateNewUser(username string, password string, reminder string, dob *tim
// Insert the user record. // Insert the user record.
_, err2 := amdb.Exec(`INSERT INTO users (username, passhash, verify_email, lockout, email_confnum, _, err2 := amdb.Exec(`INSERT INTO users (username, passhash, verify_email, lockout, email_confnum,
base_lvl, created, lastaccess, passreminder, description, dob) VALUES (?, ?, 0, 0, ?, ?, NOW(), NOW(), ?, '', ?)`, base_lvl, created, lastaccess, passreminder, description, dob) VALUES (?, ?, 0, 0, ?, ?, NOW(), NOW(), ?, '', ?)`,
username, hashPassword(password), newEmailConfirmationNumber(), AmDefaultRole("Global.NewUser").Level(), reminder, *dob) username, hashPassword(password), util.GenerateRandomConfirmationNumber(), AmDefaultRole("Global.NewUser").Level(),
reminder, *dob)
if err2 != nil { if err2 != nil {
return nil, err2 return nil, err2
} }
+1 -1
View File
@@ -7,7 +7,7 @@ If this reminder is not sufficient for you to remember what your password is,
then the system can change your password for you. To do so, please visit then the system can change your password for you. To do so, please visit
the following URL: the following URL:
http://example.com/passrecovery/{{ change_uid }}.{{ change_auth }} http://example.com/passrecovery/{{ change_uid }}/{{ change_auth }}
Your password will be changed and a new password will be E-mailed to you Your password will be changed and a new password will be E-mailed to you
at this address. at this address.
+2 -1
View File
@@ -80,13 +80,14 @@ func Login(ctxt ui.AmContext) (string, any, error) {
ci, uerr = user.ContactInfo() ci, uerr = user.ContactInfo()
if uerr == nil { if uerr == nil {
if ci != nil && ci.Email != nil && *ci.Email != "" { if ci != nil && ci.Email != nil && *ci.Email != "" {
pchange := database.AmNewPasswordChangeRequest(user.Uid, user.Username, *ci.Email)
msg := email.AmNewEmailMessage(ctxt.CurrentUserId(), ctxt.RemoteIP()) msg := email.AmNewEmailMessage(ctxt.CurrentUserId(), ctxt.RemoteIP())
msg.AddTo(*ci.Email, "") msg.AddTo(*ci.Email, "")
msg.SetTemplate("pass_remind.jet") msg.SetTemplate("pass_remind.jet")
msg.AddVariable("username", user.Username) msg.AddVariable("username", user.Username)
msg.AddVariable("reminder", user.PassReminder) msg.AddVariable("reminder", user.PassReminder)
msg.AddVariable("change_uid", user.Uid) msg.AddVariable("change_uid", user.Uid)
msg.AddVariable("change_auth", "TODO") // TODO: add change auth link msg.AddVariable("change_auth", pchange.Authentication)
msg.Send() msg.Send()
} else { } else {
uerr = errors.New("cannot find email address") uerr = errors.New("cannot find email address")
+8 -2
View File
@@ -11,8 +11,9 @@
package util package util
import ( import (
"crypto/rand" crand "crypto/rand"
"io" "io"
mrand "math/rand"
) )
// authAlphabet is the set of characters from which we generate auth strings. // authAlphabet is the set of characters from which we generate auth strings.
@@ -24,7 +25,7 @@ const authStringLen = 32
// GenerateRandomAuthString generates a random authentication string. // GenerateRandomAuthString generates a random authentication string.
func GenerateRandomAuthString() string { func GenerateRandomAuthString() string {
b := make([]byte, authStringLen) b := make([]byte, authStringLen)
if _, err := io.ReadFull(rand.Reader, b); err != nil { if _, err := io.ReadFull(crand.Reader, b); err != nil {
// can't happen (at least on a modern OS) // can't happen (at least on a modern OS)
panic("failed to read random: " + err.Error()) panic("failed to read random: " + err.Error())
} }
@@ -33,3 +34,8 @@ func GenerateRandomAuthString() string {
} }
return string(b) return string(b)
} }
// GenerateRandomConfirmationNumber generates a random 7-digit confirmation number.
func GenerateRandomConfirmationNumber() int32 {
return mrand.Int31n(9000000) + 1000000
}