fix: activate-by-code asks the login gates before it signs in
After the mailed code verifies, ActivateByCode consults the login gates with source password, as Login does. A gate answer (for example a second-factor challenge) replaces the token response and sets no session cookie; a gate error answers the opaque 500. The account is activated either way. Without a gate, or when every gate allows, the answer is unchanged. With require_activation off an account can enable a second factor before it activates, so the mailed code no longer stands in for it.
This commit is contained in:
@@ -588,8 +588,15 @@ func Activate(app *backpack.App) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// ActivateByCode activates from a public "{id}!{code}" value and mints a token.
|
||||
// With golem15.user.jwt.token_cookie on, the token goes into the auth.cookie
|
||||
// ActivateByCode activates from a public "{id}!{code}" value, consults the
|
||||
// login gates and mints a token. The gates run as on password Login, with
|
||||
// source password, because the mailed code is the first factor here: with
|
||||
// golem15.user.activation.require_activation off an account can enable a
|
||||
// second factor before it activates, and the code must not stand in for it.
|
||||
// A gate answer replaces the token response with its status and body and a
|
||||
// gate error answers the opaque 500; neither mints a token or sets a
|
||||
// session cookie, and the account stays activated either way. With
|
||||
// golem15.user.jwt.token_cookie on, the token goes into the auth.cookie
|
||||
// session cookie instead of the body.
|
||||
func ActivateByCode(app *backpack.App) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -638,6 +645,28 @@ func ActivateByCode(app *backpack.App) http.HandlerFunc {
|
||||
writeJSON(w, http.StatusUnprocessableEntity, map[string]any{"error": "This activation link is invalid or has expired"})
|
||||
return
|
||||
}
|
||||
// The activation above is committed whatever the gates answer. A
|
||||
// gate answer replaces the token response; a gate error fails
|
||||
// closed.
|
||||
gate, err := CheckLoginGates(r.Context(), app, classes.LoginAttempt{
|
||||
User: user,
|
||||
Source: classes.LoginSourcePassword,
|
||||
Fields: fields,
|
||||
Request: r,
|
||||
})
|
||||
if err != nil {
|
||||
pluginLog.Error("golem15.user: login gate", "user_id", user.ID, "err", err)
|
||||
writeOpaque500(w)
|
||||
return
|
||||
}
|
||||
if gate != nil {
|
||||
status := gate.Status
|
||||
if status == 0 {
|
||||
status = http.StatusOK
|
||||
}
|
||||
writeJSON(w, status, gate.Body)
|
||||
return
|
||||
}
|
||||
token, err := mintFor(app, secret, user, requestURL(r))
|
||||
if err != nil {
|
||||
writeOpaque500(w)
|
||||
|
||||
Reference in New Issue
Block a user