auto mail generation
This commit is contained in:
@@ -255,7 +255,7 @@ func (ctl *UserController) CreateUser(c *fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// Call service
|
||||
info, err := ctl.userService.CreateUser(user)
|
||||
info, invite, err := ctl.userService.CreateUser(user)
|
||||
if err != nil {
|
||||
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
||||
"code": http.StatusConflict,
|
||||
@@ -264,11 +264,17 @@ func (ctl *UserController) CreateUser(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// The account was created either way. Whether its first-password invitation
|
||||
// was emailed is reported beside it rather than folded into `status`: the
|
||||
// account has no password and the link is the only way to set one, so an
|
||||
// operator who is not told has hired somebody who cannot sign in.
|
||||
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
||||
"code": http.StatusCreated,
|
||||
"status": true,
|
||||
"message": "Success",
|
||||
"details": info,
|
||||
"code": http.StatusCreated,
|
||||
"status": true,
|
||||
"message": "Success",
|
||||
"details": info,
|
||||
"invited": invite.Sent,
|
||||
"invitereason": invite.Reason,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -347,7 +353,9 @@ func (ctl *UserController) DeleteUser(c *fiber.Ctx) error {
|
||||
// what makes it safe to leave open. See the repository for the rest.
|
||||
func (ctl *UserController) SetPassword(c *fiber.Ctx) error {
|
||||
var req struct {
|
||||
Userid int `json:"userid"`
|
||||
// The invitation, exactly as it arrived in the emailed link. The userid
|
||||
// is read out of the signature and never out of the request — see below.
|
||||
Token string `json:"token"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
@@ -356,7 +364,27 @@ func (ctl *UserController) SetPassword(c *fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
if err := ctl.userService.SetInitialPassword(req.Userid, req.Password); err != nil {
|
||||
// ── Why this takes a token and no longer takes a userid ─────────────────
|
||||
//
|
||||
// It used to accept `{userid, password}`, and that was an account takeover
|
||||
// waiting to be noticed. `applogin` answers a POST carrying an email and NO
|
||||
// password with 409 and the userid, for any account that has not set one —
|
||||
// which is how the console's own setup step learned it. So the whole recipe
|
||||
// was: know a merchant's primary email, which is usually printed on their
|
||||
// shopfront, POST it here, receive their userid, then set their password
|
||||
// and own the business's admin account. No guessing at any step.
|
||||
//
|
||||
// The invitation closes it. It is signed with the deployment's key, names
|
||||
// the account in a payload the server produced, and expires. Knowing an
|
||||
// email is no longer enough, and neither is knowing a userid.
|
||||
claims, err := utils.ParseInviteToken(req.Token, time.Now())
|
||||
if err != nil {
|
||||
return c.Status(http.StatusConflict).JSON(fiber.Map{
|
||||
"status": false, "code": http.StatusConflict, "message": err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if err := ctl.userService.SetInitialPassword(claims.Userid, req.Password); err != nil {
|
||||
// 409, not 401. Nothing about this is an authentication failure — the
|
||||
// caller is not supposed to have a session — and answering 401 would
|
||||
// send the console into its sign-out-and-reload path on the one screen
|
||||
|
||||
Reference in New Issue
Block a user