Skip to content

Commit

Permalink
implement password forgot endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
EnricoSchw committed Oct 27, 2024
1 parent cff002b commit ed046de
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 20 deletions.
25 changes: 24 additions & 1 deletion internal/auth/account/account_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
)

const activateAccountURLPath = "activateAccount"
const passwordForgetURLPath = "newPassword"

var ErrInvalidCredentials = errors.New("invalid credentials")
var ErrAccountAlreadyExists = errors.New("account already exists")
Expand Down Expand Up @@ -62,7 +63,7 @@ func (s *AccountService) CreateAccount(ctx context.Context, account *Account) er
}

if existAccount != nil && !existAccount.Active {
// we delete previous created account if were not activated
// we delete a previous created account if were not activated
if err := s.repo.deleteByUuid(ctx, existAccount.UUID); err != nil {
return fmt.Errorf("deleting existing inactive account: %w", err)
}
Expand Down Expand Up @@ -116,6 +117,28 @@ func (s *AccountService) VerifyAccount(ctx context.Context, token string) error
return nil
}

func (s *AccountService) CreateForgotPasswordToken(ctx context.Context, email string) error {
account, err := s.repo.findActiveByEmail(ctx, email)
if err != nil && !errors.Is(err, ErrAccountNotFound) {
return fmt.Errorf("serching for pass forgot accoun: %w", err)
}

token := NewPasswordVerificationToken(account)

if err := s.repo.AddVerificationToken(ctx, token); err != nil {
return fmt.Errorf("creating verify token for new password: %w", err)
}

link := s.instanceUrl.String() + "/" + passwordForgetURLPath + "/" + token.UUID

if err := s.mailSender.SendActivateAccountMail(account.User, account.Email, link); err != nil {
return fmt.Errorf("sending verify email for new password: %w", err)
}

return nil

}

func (s *AccountService) CreateAccountByActor(ctx context.Context, actor *models.Actor) error {
return nil
}
Expand Down
29 changes: 20 additions & 9 deletions internal/auth/handler/forgot_password.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,41 @@
package handler

import (
"encoding/json"
"net/http"

"github.com/shigde/sfu/internal/auth/account"
"github.com/shigde/sfu/internal/rest"
"github.com/shigde/sfu/pkg/authentication"
"golang.org/x/exp/slog"
)

func ForgotPassword(accountService *account.AccountService) http.HandlerFunc {

return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
user, err := getJsonAuthPayload(w, r)
email, err := getEmailForPasswordForgotPayload(w, r)
if err != nil {
rest.HttpError(w, "", http.StatusBadRequest, err)
return
}

token, err := accountService.GetAuthToken(r.Context(), user)
if err != nil {
rest.HttpError(w, "error reading stream list", http.StatusNotFound, err)
return
}
if err := json.NewEncoder(w).Encode(token); err != nil {
rest.HttpError(w, "error reading stream list", http.StatusInternalServerError, err)
if err = accountService.CreateForgotPasswordToken(r.Context(), email); err != nil {
slog.Error("auth.ForgotPassword:", "err", err)
}

w.WriteHeader(http.StatusCreated)
}
}

func getEmailForPasswordForgotPayload(w http.ResponseWriter, r *http.Request) (string, error) {
dec, err := rest.GetJsonPayload(w, r)
if err != nil {
return "", err
}
var pass authentication.PasswordForgotEmail
if err := dec.Decode(&pass); err != nil {
return "", rest.InvalidPayload
}

return pass.Email, nil
}
8 changes: 4 additions & 4 deletions internal/auth/handler/new_password.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,25 @@ func NewPasswordByForgot(accountService *account.AccountService) http.HandlerFun
}
}

func getPassForgetPayload(w http.ResponseWriter, r *http.Request) (*authentication.PasswordForget, error) {
func getPassForgetPayload(w http.ResponseWriter, r *http.Request) (*authentication.PasswordForgot, error) {
dec, err := rest.GetJsonPayload(w, r)
if err != nil {
return nil, err
}
var pass authentication.PasswordForget
var pass authentication.PasswordForgot
if err := dec.Decode(&pass); err != nil {
return nil, rest.InvalidPayload
}

return &pass, nil
}

func getPassForgetWithTokenPayload(w http.ResponseWriter, r *http.Request) (*authentication.PasswordForgetWithToken, error) {
func getPassForgetWithTokenPayload(w http.ResponseWriter, r *http.Request) (*authentication.PasswordForgotWithToken, error) {
dec, err := rest.GetJsonPayload(w, r)
if err != nil {
return nil, err
}
var pass authentication.PasswordForgetWithToken
var pass authentication.PasswordForgotWithToken
if err := dec.Decode(&pass); err != nil {
return nil, rest.InvalidPayload
}
Expand Down
47 changes: 44 additions & 3 deletions internal/mail/sender_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ func (s *SenderService) SendActivateAccountMail(name string, email string, link

t, err := template.ParseFiles("internal/mail/template/activate_account.html")
if err != nil {
return fmt.Errorf("parsing email template: %w", err)
return fmt.Errorf("parsing account email template: %w", err)
}

var body bytes.Buffer

mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
if _, err = body.Write([]byte(fmt.Sprintf("Subject: Activate Your Account \n%s\n\n", mimeHeaders))); err != nil {
return fmt.Errorf("writing email boddy: %w", err)
return fmt.Errorf("writing account email boddy: %w", err)
}

t.Execute(&body, struct {
Expand All @@ -56,7 +56,48 @@ func (s *SenderService) SendActivateAccountMail(name string, email string, link

// Sending email.
if err = smtp.SendMail(s.cfg.SmtpHost+":"+s.cfg.SmtpPort, auth, s.cfg.From, to, body.Bytes()); err != nil {
return fmt.Errorf("sendingx email boddy: %w", err)
return fmt.Errorf("sending account email boddy: %w", err)
}
return nil
}

func (s *SenderService) SendForgotPasswordMail(name string, email string, link string) error {

if !s.cfg.Enable {
return nil
}

// Receiver email address.
to := []string{email}

// Authentication.
auth := smtp.PlainAuth("", s.cfg.From, s.cfg.Pass, s.cfg.SmtpHost)

t, err := template.ParseFiles("internal/mail/template/forgot_password.html")
if err != nil {
return fmt.Errorf("parsing pass forgot email template: %w", err)
}

var body bytes.Buffer

mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
if _, err = body.Write([]byte(fmt.Sprintf("Subject: Forgot Password \n%s\n\n", mimeHeaders))); err != nil {
return fmt.Errorf("writing pass forgot email boddy: %w", err)
}

t.Execute(&body, struct {
User string
Link string
Instance string
}{
User: name,
Link: link,
Instance: s.instanceUrl.String(),
})

// Sending email.
if err = smtp.SendMail(s.cfg.SmtpHost+":"+s.cfg.SmtpPort, auth, s.cfg.From, to, body.Bytes()); err != nil {
return fmt.Errorf("sending pass forgot email boddy: %w", err)
}
return nil
}
179 changes: 179 additions & 0 deletions internal/mail/template/forgot_password.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<!doctype html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Shig Registration Email</title>
<style media="all" type="text/css">
@media all {

.btn-primary a:hover {
background-color: #ec0867 !important;
border-color: #ec0867 !important;
}
}
@media only screen and (max-width: 640px) {
.main p,
.main td,
.main span {
font-size: 16px !important;
}

.main h3 {
font-size: 18px !important;
}

.main h1 {
font-size: 20px !important;
}

.wrapper {
padding: 8px !important;
}

.content {
padding: 0 !important;
}

.container {
padding: 0 !important;
padding-top: 8px !important;
width: 100% !important;
}

.main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}

.btn table {
max-width: 100% !important;
width: 100% !important;
}

.btn a {
font-size: 16px !important;
max-width: 100% !important;
width: 100% !important;
}
}
@media all {
.ExternalClass {
width: 100%;
}
.main h3 {
font-size: 18px !important;
}

.main h1 {
font-size: 20px !important;
}

.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}

.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}

#MessageViewBody a {
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
}
</style>
</head>
<body style="font-family: Helvetica, sans-serif; -webkit-font-smoothing: antialiased; font-size: 16px; line-height: 1.3; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background-color: #f4f5f6; margin: 0; padding: 0;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f4f5f6; width: 100%;" width="100%" bgcolor="#f4f5f6">
<tr>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top;" valign="top">&nbsp;</td>
<td class="container" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; max-width: 600px; padding: 0; padding-top: 24px; width: 600px; margin: 0 auto;" width="600" valign="top">
<div class="content" style="box-sizing: border-box; display: block; margin: 0 auto; max-width: 600px; padding: 0;">

<!-- START CENTERED WHITE CONTAINER -->
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Shig account activation email.</span>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background: #ffffff; border: 1px solid #eaebed; border-radius: 16px; width: 100%;" width="100%">

<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; box-sizing: border-box; padding: 24px;" valign="top">
<div class="" style="clear: both; padding-bottom: 15px; text-align: center; width: 100%;">
<h1 style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px; color: #3e3e3e;">Forgot Your Account Account</h1>
</div>
<h3 style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px; color: #3e3e3e;">Dear {{.User}}</h3>
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px; color: #3e3e3e;">Please click on the button below to change your password.</p>


<table role="presentation" border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; text-align: center; mso-table-lspace: 0pt; mso-table-rspace: 0pt; box-sizing: border-box; width: 100%; min-width: 100%;" width="100%">
<tbody>
<tr>
<td align="left" style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; padding-bottom: 16px; text-align: center;" valign="top">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; text-align: center; width: 100%;">
<tbody>
<tr>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top; border-radius: 4px; text-align: center;" valign="top" align="center"> <a href="{{.Link}}" target="_blank" style="border: solid 2px #8dcaff; border-radius: 4px; box-sizing: border-box; cursor: pointer; display: inline-block; font-size: 16px; font-weight: bold; margin: 0; padding: 12px 24px; text-decoration: none; text-transform: capitalize; background-color: #8dcaff; border-color: #8dcaff; color: #ffffff;">Forgot Account</a> </td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>

<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-top: 25px; margin-bottom: 16px; color: #3e3e3e;">If you cannot click the button, copy and paste this link into your browser:</p>
<div class="" style="clear: both; text-align: center; width: 100%;">
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">
<a href="{{.Link}}">{{.Link}}</a>
</p>
</div>
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px; color: #3e3e3e;">If you have any questions, please contact us: [email protected]</p>
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px; color: #3e3e3e;">Regards</p>
<p style="font-family: Helvetica, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px; color: #3e3e3e;">Your Shig Team.</p>
</td>
</tr>

<!-- END MAIN CONTENT AREA -->
</table>

<!-- START FOOTER -->
<div class="footer" style="clear: both; padding-top: 24px; text-align: center; width: 100%;">
<table role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
<tr>
<td class="content-block" style="font-family: Helvetica, sans-serif; vertical-align: top; color: #9a9ea6; font-size: 16px; text-align: center;" valign="top" align="center">
<span class="apple-link" style="color: #9a9ea6; font-size: 16px; text-align: center;">Shig</span>
</td>
</tr>
<tr>
<td class="content-block powered-by" style="font-family: Helvetica, sans-serif; vertical-align: top; color: #9a9ea6; font-size: 16px; text-align: center;" valign="top" align="center">
Powered by <a href="https://shig.de" style="color: #9a9ea6; font-size: 16px; text-align: center; text-decoration: none;">{{.Instance}}</a>
</td>
</tr>

</table>
</div>

<!-- END FOOTER -->

<!-- END CENTERED WHITE CONTAINER --></div>
</td>
<td style="font-family: Helvetica, sans-serif; font-size: 16px; vertical-align: top;" valign="top">&nbsp;</td>
</tr>
</table>
</body>
</html>
10 changes: 7 additions & 3 deletions pkg/authentication/password.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package authentication

type PasswordForget struct {
type PasswordForgotEmail struct {
Email string `json:"email"`
}

type PasswordForgot struct {
Token string `json:"token"`
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}

type PasswordForgetWithToken struct {
type PasswordForgotWithToken struct {
Token string `json:"token"`
PasswordForget
PasswordForgot
}

0 comments on commit ed046de

Please sign in to comment.