built out the passrecovery servlet
This commit is contained in:
+33
-1
@@ -52,6 +52,20 @@ func AmNewPasswordChangeRequest(uid int32, username string, email string) *Passw
|
|||||||
return &rc
|
return &rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AmGetPasswordChangeRequest retrieves the password change request for a UID.
|
||||||
|
* Parameters:
|
||||||
|
* uid - The UID to retrieve the request for.
|
||||||
|
* Returns:
|
||||||
|
* The PasswordChangeRequest pointer, or nil.
|
||||||
|
*/
|
||||||
|
func AmGetPasswordChangeRequest(uid int32) *PasswordChangeRequest {
|
||||||
|
rc := passwordRequests[uid]
|
||||||
|
if rc != nil {
|
||||||
|
delete(passwordRequests, uid)
|
||||||
|
}
|
||||||
|
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
|
||||||
@@ -167,13 +181,31 @@ func (u *User) NewEmailConfirmationNumber() error {
|
|||||||
u.Mutex.Lock()
|
u.Mutex.Lock()
|
||||||
defer u.Mutex.Unlock()
|
defer u.Mutex.Unlock()
|
||||||
newnum := util.GenerateRandomConfirmationNumber()
|
newnum := util.GenerateRandomConfirmationNumber()
|
||||||
_, err := amdb.Exec("UPDATE user SET email_confnum = ? WHERE uid = ?", newnum, u.Uid)
|
_, err := amdb.Exec("UPDATE users SET email_confnum = ? WHERE uid = ?", newnum, u.Uid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
u.EmailConfNum = newnum
|
u.EmailConfNum = newnum
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChangePassword resets a user's password.
|
||||||
|
func (u *User) ChangePassword(password string, remoteIP string) error {
|
||||||
|
var ar *AuditRecord = nil
|
||||||
|
defer func() {
|
||||||
|
AmStoreAudit(ar)
|
||||||
|
}()
|
||||||
|
|
||||||
|
u.Mutex.Lock()
|
||||||
|
defer u.Mutex.Unlock()
|
||||||
|
pval := hashPassword(password)
|
||||||
|
_, err := amdb.Exec("UPDATE users SET passhash = ? WHERE uid = ?", pval, u.Uid)
|
||||||
|
if err == nil {
|
||||||
|
u.Passhash = pval
|
||||||
|
ar = AmNewAudit(AuditChangePassword, u.Uid, remoteIP, "via password change request")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
/* AmGetUser returns a reference to the specified user.
|
/* AmGetUser returns a reference to the specified user.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* uid - The UID of the user.
|
* uid - The UID of the user.
|
||||||
|
|||||||
@@ -13,10 +13,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"git.erbosoft.com/amy/amsterdam/email"
|
"git.erbosoft.com/amy/amsterdam/email"
|
||||||
"git.erbosoft.com/amy/amsterdam/ui"
|
"git.erbosoft.com/amy/amsterdam/ui"
|
||||||
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/labstack/gommon/log"
|
"github.com/labstack/gommon/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -394,3 +396,44 @@ func NewAccount(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PasswordRecovery(ctxt ui.AmContext) (string, any, error) {
|
||||||
|
var emailaddy string
|
||||||
|
uid, err := ctxt.URLParamInt("uid")
|
||||||
|
if err == nil {
|
||||||
|
auth, err := ctxt.URLParamInt("auth")
|
||||||
|
if err == nil {
|
||||||
|
pchange := database.AmGetPasswordChangeRequest(int32(uid))
|
||||||
|
if pchange == nil {
|
||||||
|
return ui.ErrorPage(ctxt, errors.New("password change request not found"))
|
||||||
|
}
|
||||||
|
if auth != int(pchange.Authentication) {
|
||||||
|
return ui.ErrorPage(ctxt, errors.New("invalid password change request"))
|
||||||
|
}
|
||||||
|
if time.Now().Compare(pchange.Expires) > 0 {
|
||||||
|
return ui.ErrorPage(ctxt, errors.New("password change request has expired"))
|
||||||
|
}
|
||||||
|
emailaddy = pchange.Email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
user, err := database.AmGetUser(int32(uid))
|
||||||
|
if err == nil {
|
||||||
|
newpass := util.GenerateRandomPassword()
|
||||||
|
err = user.ChangePassword(newpass, ctxt.RemoteIP())
|
||||||
|
if err == nil {
|
||||||
|
// send the password change message
|
||||||
|
msg := email.AmNewEmailMessage(user.Uid, ctxt.RemoteIP())
|
||||||
|
msg.AddTo(emailaddy, "")
|
||||||
|
msg.SetTemplate("pass_change.jet")
|
||||||
|
msg.AddVariable("username", user.Username)
|
||||||
|
msg.AddVariable("password", newpass)
|
||||||
|
msg.Send()
|
||||||
|
ctxt.VarMap().Set("amsterdam_pageTitle", "Your Password Has Been Changed")
|
||||||
|
return "framed_template", "password_changed.jet", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ui.ErrorPage(ctxt, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ func setupEcho() *echo.Echo {
|
|||||||
e.GET("/newacct2", ui.AmWrap(NewAccountForm))
|
e.GET("/newacct2", ui.AmWrap(NewAccountForm))
|
||||||
e.GET("/verify", ui.AmWrap(VerifyEmailForm))
|
e.GET("/verify", ui.AmWrap(VerifyEmailForm))
|
||||||
e.POST("/verify", ui.AmWrap(VerifyEMail))
|
e.POST("/verify", ui.AmWrap(VerifyEMail))
|
||||||
|
e.GET("/passrecovery/:uid/:auth", ui.AmWrap(PasswordRecovery))
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ type AmContext interface {
|
|||||||
SetRC(int)
|
SetRC(int)
|
||||||
GetScratch(string) any
|
GetScratch(string) any
|
||||||
SetScratch(string, any)
|
SetScratch(string, any)
|
||||||
|
URLParam(string) string
|
||||||
|
URLParamInt(string) (int, error)
|
||||||
URLPath() string
|
URLPath() string
|
||||||
VarMap() jet.VarMap
|
VarMap() jet.VarMap
|
||||||
}
|
}
|
||||||
@@ -228,6 +230,7 @@ func (c *amContext) SetRC(rc int) {
|
|||||||
c.httprc = rc
|
c.httprc = rc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetScratch returns a value in the per-request scratchpad.
|
||||||
func (c *amContext) GetScratch(name string) any {
|
func (c *amContext) GetScratch(name string) any {
|
||||||
if c.scratchpad == nil {
|
if c.scratchpad == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -235,6 +238,7 @@ func (c *amContext) GetScratch(name string) any {
|
|||||||
return c.scratchpad[name]
|
return c.scratchpad[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetScratch sets a value in the per-request scratchpad.
|
||||||
func (c *amContext) SetScratch(name string, val any) {
|
func (c *amContext) SetScratch(name string, val any) {
|
||||||
if c.scratchpad == nil {
|
if c.scratchpad == nil {
|
||||||
c.scratchpad = make(map[string]any)
|
c.scratchpad = make(map[string]any)
|
||||||
@@ -242,6 +246,16 @@ func (c *amContext) SetScratch(name string, val any) {
|
|||||||
c.scratchpad[name] = val
|
c.scratchpad[name] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// URLParam returns the value of a URL parameter.
|
||||||
|
func (c *amContext) URLParam(name string) string {
|
||||||
|
return c.echoContext.Param(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLParamINt returns the value of a URL parameter parsed as an integer.
|
||||||
|
func (c *amContext) URLParamInt(name string) (int, error) {
|
||||||
|
return strconv.Atoi(c.echoContext.Param(name))
|
||||||
|
}
|
||||||
|
|
||||||
// URLPath returns the path component of the request URL.
|
// URLPath returns the path component of the request URL.
|
||||||
func (c *amContext) URLPath() string {
|
func (c *amContext) URLPath() string {
|
||||||
return c.echoContext.Request().URL.Path
|
return c.echoContext.Request().URL.Path
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{*
|
||||||
|
* Amsterdam Web Communities System
|
||||||
|
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*}
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-1 p-4">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-blue-800 text-4xl font-bold mb-2">Your Password Has Been Changed</h1>
|
||||||
|
<hr class="border-2 border-gray-400 w-4/5 mb-4">
|
||||||
|
The password for your account has been changed, and a new password has been E-mailed to you at
|
||||||
|
your account's defined E-mail address. (Check your E-mail again!). After getting the new password,
|
||||||
|
please log in and change your password (yes, again!) as soon as possible.
|
||||||
|
<p class="text-black text-sm mb-4">
|
||||||
|
<a href="/">Click here</a> to return to the home page.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
crand "crypto/rand"
|
crand "crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
mrand "math/rand"
|
mrand "math/rand"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// authAlphabet is the set of characters from which we generate auth strings.
|
// authAlphabet is the set of characters from which we generate auth strings.
|
||||||
@@ -22,6 +23,32 @@ const authAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv
|
|||||||
// authStringLen is the standard lengtth of authentication strings.
|
// authStringLen is the standard lengtth of authentication strings.
|
||||||
const authStringLen = 32
|
const authStringLen = 32
|
||||||
|
|
||||||
|
// syllabary is used to generate random passwords.
|
||||||
|
var syllabary = [...]string{
|
||||||
|
"ba",
|
||||||
|
"be",
|
||||||
|
"bi", "bo", "bu",
|
||||||
|
"da", "de", "di", "do", "du",
|
||||||
|
"cha", "chi", "cho", "chu",
|
||||||
|
"fa", "fe", "fi", "fo", "fu",
|
||||||
|
"ga", "ge", "gi", "go", "gu",
|
||||||
|
"ha", "he", "hi", "ho", "hu",
|
||||||
|
"ja", "je", "ji", "jo", "ju",
|
||||||
|
"ka", "ke", "ki", "ko", "ku",
|
||||||
|
"la", "le", "li", "lo", "lu",
|
||||||
|
"ma", "me", "mi", "mo", "mu",
|
||||||
|
"na", "ne", "ni", "no", "nu",
|
||||||
|
"pa", "pe", "pi", "po", "pu",
|
||||||
|
"ra", "re", "ri", "ro", "ru",
|
||||||
|
"sa", "se", "si", "so", "su",
|
||||||
|
"sha", "she", "sho", "shu",
|
||||||
|
"ta", "te", "ti", "to", "tu",
|
||||||
|
"va", "ve", "vi", "vo", "vu",
|
||||||
|
"wa", "we", "wi", "wo", "wu",
|
||||||
|
"ya", "ye", "yi", "yo", "yu",
|
||||||
|
"za", "ze", "zi", "zo", "zu",
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
@@ -39,3 +66,20 @@ func GenerateRandomAuthString() string {
|
|||||||
func GenerateRandomConfirmationNumber() int32 {
|
func GenerateRandomConfirmationNumber() int32 {
|
||||||
return mrand.Int31n(9000000) + 1000000
|
return mrand.Int31n(9000000) + 1000000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenerateRandomPassword generates a random password string.
|
||||||
|
func GenerateRandomPassword() string {
|
||||||
|
var b strings.Builder
|
||||||
|
rd := make([]byte, 7)
|
||||||
|
if _, err := io.ReadFull(crand.Reader, rd); err != nil {
|
||||||
|
// can't happen (at least on a modern OS)
|
||||||
|
panic("failed to read random: " + err.Error())
|
||||||
|
}
|
||||||
|
for i := 0; i < 4; i++ { // add random syllables
|
||||||
|
b.WriteString(syllabary[int(rd[i])%len(syllabary)])
|
||||||
|
}
|
||||||
|
for i := 4; i < 7; i++ { // add random digits
|
||||||
|
b.WriteByte(byte('0' + int(rd[i])%10))
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user