1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-06-26 19:37:46 +02:00

form: rework email confirmation

realized half the info from the signup form wasnt being stored in the JWT
used to create the account after email confirmation, and instead of
adding them, the -whole request- from the browser is stored temporarily
by the server, indexed by a smaller JWT that only includes the invite
code. Someone complained on reddit about me storing the password in the
JWT a while back, and although security-wise that isn't an issue (only
the server can decrypt the token), it doesn't happen anymore. Happy?
This commit is contained in:
Harvey Tindall 2023-06-21 21:14:41 +01:00
parent f779f0345e
commit 14c18bd668
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
4 changed files with 59 additions and 39 deletions

View File

@ -242,12 +242,17 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool) (f errorFunc, suc
success = false success = false
return return
} }
inv, _ := app.storage.GetInvitesKey(req.Code) if app.ConfirmationKeys == nil {
if inv.ConfirmationKeys == nil { app.ConfirmationKeys = map[string]map[string]newUserDTO{}
inv.ConfirmationKeys = map[string]newUserDTO{}
} }
inv.ConfirmationKeys[key] = req cKeys, ok := app.ConfirmationKeys[req.Code]
app.storage.SetInvitesKey(req.Code, inv) if !ok {
cKeys = map[string]newUserDTO{}
}
cKeys[key] = req
app.confirmationKeysLock.Lock()
app.ConfirmationKeys[req.Code] = cKeys
app.confirmationKeysLock.Unlock()
f = func(gc *gin.Context) { f = func(gc *gin.Context) {
app.debug.Printf("%s: Email confirmation required", req.Code) app.debug.Printf("%s: Email confirmation required", req.Code)
respond(401, "confirmEmail", gc) respond(401, "confirmEmail", gc)

45
main.go
View File

@ -17,6 +17,7 @@ import (
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings" "strings"
"sync"
"syscall" "syscall"
"time" "time"
@ -90,27 +91,29 @@ type appContext struct {
adminUsers []User adminUsers []User
invalidTokens []string invalidTokens []string
// Keeping jf name because I can't think of a better one // Keeping jf name because I can't think of a better one
jf *mediabrowser.MediaBrowser jf *mediabrowser.MediaBrowser
authJf *mediabrowser.MediaBrowser authJf *mediabrowser.MediaBrowser
ombi *ombi.Ombi ombi *ombi.Ombi
datePattern string datePattern string
timePattern string timePattern string
storage Storage storage Storage
validator Validator validator Validator
email *Emailer email *Emailer
telegram *TelegramDaemon telegram *TelegramDaemon
discord *DiscordDaemon discord *DiscordDaemon
matrix *MatrixDaemon matrix *MatrixDaemon
info, debug, err *logger.Logger info, debug, err *logger.Logger
host string host string
port int port int
version string version string
URLBase string URLBase string
updater *Updater updater *Updater
newUpdate bool // Whether whatever's in update is new. newUpdate bool // Whether whatever's in update is new.
tag Tag tag Tag
update Update update Update
internalPWRs map[string]InternalPWR internalPWRs map[string]InternalPWR
ConfirmationKeys map[string]map[string]newUserDTO // Map of invite code to jwt to request
confirmationKeysLock sync.Mutex
} }
func generateSecret(length int) (string, error) { func generateSecret(length int) (string, error) {

View File

@ -274,12 +274,11 @@ type Invite struct {
UserMinutes int `json:"user-minutes,omitempty"` UserMinutes int `json:"user-minutes,omitempty"`
SendTo string `json:"email"` SendTo string `json:"email"`
// Used to be stored as formatted time, now as Unix. // Used to be stored as formatted time, now as Unix.
UsedBy [][]string `json:"used-by"` UsedBy [][]string `json:"used-by"`
Notify map[string]map[string]bool `json:"notify"` Notify map[string]map[string]bool `json:"notify"`
Profile string `json:"profile"` Profile string `json:"profile"`
Label string `json:"label,omitempty"` Label string `json:"label,omitempty"`
ConfirmationKeys map[string]newUserDTO `json:"-"` // map of JWT confirmation keys to their original requests Captchas map[string]*captcha.Data // Map of Captcha IDs to answers
Captchas map[string]*captcha.Data // Map of Captcha IDs to answers
} }
type Lang struct { type Lang struct {

View File

@ -512,7 +512,6 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
return return
} }
if key := gc.Query("key"); key != "" && app.config.Section("email_confirmation").Key("enabled").MustBool(false) { if key := gc.Query("key"); key != "" && app.config.Section("email_confirmation").Key("enabled").MustBool(false) {
req, ok := inv.ConfirmationKeys[key]
fail := func() { fail := func() {
gcHTML(gc, 404, "404.html", gin.H{ gcHTML(gc, 404, "404.html", gin.H{
"cssClass": app.cssClass, "cssClass": app.cssClass,
@ -520,6 +519,18 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
"contactMessage": app.config.Section("ui").Key("contact_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(),
}) })
} }
var req newUserDTO
if app.ConfirmationKeys == nil {
fail()
return
}
invKeys, ok := app.ConfirmationKeys[code]
if !ok {
fail()
return
}
req, ok = invKeys[key]
if !ok { if !ok {
fail() fail()
return return
@ -537,8 +548,11 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
app.debug.Printf("Invalid key") app.debug.Printf("Invalid key")
return return
} }
_, success := app.newUser(req, true) f, success := app.newUser(req, true)
if !success { if !success {
app.err.Printf("Failed to create new user")
// Not meant for us. Calling this will be a mess, but at least it might give us some information.
f(gc)
fail() fail()
return return
} }
@ -554,11 +568,10 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
"jfLink": jfLink, "jfLink": jfLink,
}) })
} }
inv, ok := app.storage.GetInvitesKey(code) delete(invKeys, key)
if ok { app.confirmationKeysLock.Lock()
delete(inv.ConfirmationKeys, key) app.ConfirmationKeys[code] = invKeys
app.storage.SetInvitesKey(code, inv) app.confirmationKeysLock.Unlock()
}
return return
} }
email := "" email := ""