Skip to content

Commit

Permalink
[feat]パスワード変更のメール送信機能のAPIの実装
Browse files Browse the repository at this point in the history
  • Loading branch information
KazumaSun committed Mar 4, 2024
1 parent 7234c51 commit b70960e
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 37 deletions.
2 changes: 1 addition & 1 deletion api/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1991,7 +1991,7 @@ const docTemplate = `{
},
},
},
"/mail_auth/reset_password":{
"/mail_auth/send_reset_password":{
"post": {
tags: ["email"],
"description": "パスワードリセットのメール送信",
Expand Down
7 changes: 4 additions & 3 deletions api/externals/controller/mail_auth_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type MailAuthController interface {
SignIn(echo.Context) error
SignOut(echo.Context) error
IsSignIn(echo.Context) error
ResetPassword(echo.Context) error
SendResetPassword(echo.Context) error
}

func NewMailAuthController(u usecase.MailAuthUseCase) MailAuthController {
Expand Down Expand Up @@ -72,11 +72,12 @@ func (auth *mailAuthController) IsSignIn(c echo.Context) error {
}

// reset password
func (auth *mailAuthController) ResetPassword(c echo.Context) error {
func (auth *mailAuthController) SendResetPassword(c echo.Context) error {
email := c.QueryParam("email")
err := auth.u.ResetPassword(c.Request().Context(), email)
token, err := auth.u.SendResetPassword(c.Request().Context(), email)
if err != nil {
return err
}
c.JSON(http.StatusOK, token)
return nil
}
16 changes: 12 additions & 4 deletions api/externals/repository/mail_auth_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type MailAuthRepository interface {
CreateMailAuth(context.Context, string, string, string) (int64, error)
FindMailAuthByEmail(context.Context, string) *sql.Row
FindMailAuthByID(context.Context, string) *sql.Row
ResetPassword(context.Context, []string) error
SendResetPassword(context.Context, []string) error
}

func NewMailAuthRepository(client db.Client) MailAuthRepository {
Expand Down Expand Up @@ -49,15 +49,23 @@ func (r *mailAuthRepository) FindMailAuthByID(c context.Context, id string) *sql
}

// reset password
func (r *mailAuthRepository) ResetPassword(c context.Context, email []string) error {
func (r *mailAuthRepository) SendResetPassword(c context.Context, email []string) error {
err := godotenv.Load("env/dev.env")
if err != nil {
fmt.Println(err)
}

mailSender := os.Getenv("NUTMEG_MAIL_SENDER")
mailPassword := os.Getenv("NUTMEG_MAIL_PASSWORD")
message := []byte("test")
resetPageUrl := os.Getenv("RESET_PASSWORD_URL")

message := []byte("From: 情報局 <" + mailSender + ">\r\n" +
"Subject: FinanSu パスワードリセットの確認メール\r\n\r\n" +
"お世話になっております。\r\n情報局 FinanSu 担当です。\r\n\r\n" +
"FinanSuに登録している本メールアドレスのパスワードをリセットするためには、下記のURLから手続きを行ってください。\r\n" +
"なお、パスワードのリセットの有効期限は本メールが送信されてから10分間とさせていただきます。\r\n" +
"今後ともよろしくお願いいたします。\r\n\r\n" +
"FinanSu: " + resetPageUrl)

smtpHost := "smtp.gmail.com"
smtpPort := "587"
Expand All @@ -71,6 +79,6 @@ func (r *mailAuthRepository) ResetPassword(c context.Context, email []string) er
fmt.Println(err)
return err
}
fmt.Println("Email Sent Successfully!")
fmt.Println("Sent password reset mail for " + email[0])
return nil
}
66 changes: 66 additions & 0 deletions api/externals/repository/sesstion_reset_password_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package repository

import (
"context"
"database/sql"
"github.com/NUTFes/FinanSu/api/drivers/db"
"fmt"
)


type sessionResetPasswordRepository struct {
client db.Client
}

type SessionResetPasswordRepository interface {
Create(context.Context, string, string, string) error
Destroy(context.Context, string) error
FindSessionByAccessToken(context.Context, string) *sql.Row
DestroyByUserID(context.Context, string) error
}

func NewSessionResetPasswordRepository(client db.Client) SessionResetPasswordRepository {
return &sessionResetPasswordRepository{client}
}

// 作成
func (r *sessionResetPasswordRepository) Create(c context.Context, authID string, userID string, accessToken string) error {
query := "insert into session_reset_password (auth_id, user_id, access_token) values (" + authID + ", " + userID + ", '" + accessToken + "')"
_, err := r.client.DB().ExecContext(c, query)
if err != nil {
return err
}
fmt.Printf("\x1b[36m%s\n", query)
return nil
}

// 削除
func (r *sessionResetPasswordRepository) Destroy(c context.Context, accessToken string) error {
// access tokenで該当のsessionを削除
query := "delete from session_reset_password where access_token = '" + accessToken + "'"
_, err := r.client.DB().ExecContext(c, query)
if err != nil {
return err
}
fmt.Printf("\x1b[36m%s\n", query)
return nil
}

// アクセストークンからセッションを取得
func (r *sessionResetPasswordRepository) FindSessionByAccessToken(c context.Context, accessToken string) *sql.Row {
query := "select * from session_reset_password where access_token = '" + accessToken + "'"
row := r.client.DB().QueryRowContext(c, query)
fmt.Printf("\x1b[36m%s\n", query)
return row
}

// user_idからsessionを削除する
func (r *sessionResetPasswordRepository) DestroyByUserID(c context.Context, userID string) error {
query := "delete from session_reset_password where user_id = " + userID
_, err := r.client.DB().ExecContext(c, query)
if err != nil {
return err
}
fmt.Printf("\x1b[36m%s\n", query)
return nil
}
3 changes: 2 additions & 1 deletion api/internals/di/di.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func InitializeServer() db.Client {
purchaseOrderRepository := repository.NewPurchaseOrderRepository(client, crud)
purchaseReportRepository := repository.NewPurchaseReportRepository(client, crud)
sessionRepository := repository.NewSessionRepository(client)
sessionResetPasswordRepository := repository.NewSessionResetPasswordRepository(client)
sourceRepository := repository.NewSourceRepository(client, crud)
sponsorRepository := repository.NewSponsorRepository(client, crud)
sponsorStyleRepository := repository.NewSponsorStyleRepository(client, crud)
Expand All @@ -52,7 +53,7 @@ func InitializeServer() db.Client {
departmentUseCase := usecase.NewDepartmentUseCase(departmentRepository)
expenseUseCase := usecase.NewExpenseUseCase(expenseRepository)
fundInformationUseCase := usecase.NewFundInformationUseCase(fundInformationRepository)
mailAuthUseCase := usecase.NewMailAuthUseCase(mailAuthRepository, sessionRepository)
mailAuthUseCase := usecase.NewMailAuthUseCase(mailAuthRepository, sessionRepository, sessionResetPasswordRepository)
purchaseItemUseCase := usecase.NewPurchaseItemUseCase(purchaseItemRepository)
purchaseOrderUseCase := usecase.NewPurchaseOrderUseCase(purchaseOrderRepository)
purchaseReportUseCase := usecase.NewPurchaseReportUseCase(purchaseReportRepository)
Expand Down
9 changes: 9 additions & 0 deletions api/internals/domain/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,12 @@ type Session struct {
CreatedAt time.Time
UpdatedAt time.Time
}

type SessionResetPassword struct {
ID ID
AuthID int
UserID int
AccessToken string
CreatedAt time.Time
UpdatedAt time.Time
}
79 changes: 52 additions & 27 deletions api/internals/usecase/mail_auth_usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ import (
type mailAuthUseCase struct {
mailAuthRep rep.MailAuthRepository
sessionRep rep.SessionRepository
sessionResetPasswordRep rep.SessionResetPasswordRepository
}

type MailAuthUseCase interface {
SignUp(context.Context, string, string, string) (domain.Token, error)
SignIn(context.Context, string, string) (domain.Token, error)
SignOut(context.Context, string) error
IsSignIn(context.Context, string) (domain.IsSignIn, error)
ResetPassword(context.Context, string) error
SendResetPassword(context.Context, string) (domain.Token, error)
}

func NewMailAuthUseCase(mailAuthRep rep.MailAuthRepository, sessionRep rep.SessionRepository) MailAuthUseCase {
return &mailAuthUseCase{mailAuthRep: mailAuthRep, sessionRep: sessionRep}
func NewMailAuthUseCase(mailAuthRep rep.MailAuthRepository, sessionRep rep.SessionRepository, sessionResetPasswordRep rep.SessionResetPasswordRepository) MailAuthUseCase {
return &mailAuthUseCase{mailAuthRep: mailAuthRep, sessionRep: sessionRep, sessionResetPasswordRep: sessionResetPasswordRep}
}

func (u *mailAuthUseCase) SignUp(c context.Context, email string, password string, userID string) (domain.Token, error) {
Expand Down Expand Up @@ -86,25 +87,6 @@ func (u *mailAuthUseCase) SignOut(c context.Context, accessToken string) error {
return nil
}

// アクセストークンを生成
func _makeRandomStr(digit uint32) (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

// 乱数を生成
b := make([]byte, digit)
if _, err := rand.Read(b); err != nil {
return "", errors.New("unexpected error...")
}

// letters からランダムに取り出して文字列を生成
var result string
for _, v := range b {
// index が letters の長さに収まるように調整
result += string(letters[int(v)%len(letters)])
}
return result, nil
}

func (u *mailAuthUseCase) IsSignIn(c context.Context, accessToken string) (domain.IsSignIn, error) {
var session = domain.Session{}
var isSignIn domain.IsSignIn
Expand All @@ -126,12 +108,55 @@ func (u *mailAuthUseCase) IsSignIn(c context.Context, accessToken string) (domai
}

// reset password
func (u *mailAuthUseCase) ResetPassword(c context.Context, email string) error {
receiverEmail := []string{email}
err := u.mailAuthRep.ResetPassword(c, receiverEmail)
func (u *mailAuthUseCase) SendResetPassword(c context.Context, email string) (domain.Token, error) {
var mailAuth = domain.MailAuth{}
var token domain.Token

// メールアドレスの存在確認
row := u.mailAuthRep.FindMailAuthByEmail(c, email)
err := row.Scan(
&mailAuth.ID,
&mailAuth.Email,
&mailAuth.Password,
&mailAuth.UserID,
&mailAuth.CreatedAt,
&mailAuth.UpdatedAt,
)
u.sessionResetPasswordRep.DestroyByUserID(c, strconv.Itoa(int(mailAuth.UserID)))
// トークン発行
accessToken, err := _makeRandomStr(10)
// リセットセッション開始
err = u.sessionResetPasswordRep.Create(c, strconv.FormatInt(int64(mailAuth.ID), 10), strconv.Itoa(int(mailAuth.UserID)), accessToken)
if err != nil {
return err
return token, err
}
token.AccessToken = accessToken

return err
// メール送信
receiverEmail := []string{mailAuth.Email}
err = u.mailAuthRep.SendResetPassword(c, receiverEmail)
if err != nil {
return token, err
}

return token, nil
}

// アクセストークンを生成
func _makeRandomStr(digit uint32) (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

// 乱数を生成
b := make([]byte, digit)
if _, err := rand.Read(b); err != nil {
return "", errors.New("unexpected error...")
}

// letters からランダムに取り出して文字列を生成
var result string
for _, v := range b {
// index が letters の長さに収まるように調整
result += string(letters[int(v)%len(letters)])
}
return result, nil
}
2 changes: 1 addition & 1 deletion api/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (r router) ProvideRouter(e *echo.Echo) {
e.POST("/mail_auth/signin", r.mailAuthController.SignIn)
e.DELETE("/mail_auth/signout", r.mailAuthController.SignOut)
e.GET("/mail_auth/is_signin", r.mailAuthController.IsSignIn)
e.POST("/mail_auth/reset_password", r.mailAuthController.ResetPassword)
e.POST("/mail_auth/send_reset_password", r.mailAuthController.SendResetPassword)

// purchaseitemsのRoute
e.GET("/purchaseitems", r.purchaseItemController.IndexPurchaseItem)
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ services:
command: "go run main.go"
env_file: ["./finansu.env"]
ports: ["1323:1323"]
environment:
RESET_PASSWORD_URL: 'https://finansu.nutfes.net/reset_password'
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ services:
container_name: "nutfes-finansu-api"
volumes:
- ./api:/app
environment:
RESET_PASSWORD_URL: 'https://localhost:3000/reset_password'
#シェルスクリプトを実行するコマンド
command: "./start.sh"
ports:
Expand Down
11 changes: 11 additions & 0 deletions mysql/db/session_reset_password.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use finansu_db;

CREATE TABLE session_reset_password (
id int(10) unsigned not null unique auto_increment,
auth_id int(10) not null,
user_id int(10) not null,
access_token varchar(255) not null,
created_at datetime not null default current_timestamp,
updated_at datetime not null default current_timestamp on update current_timestamp,
PRIMARY KEY (auth_id)
);

0 comments on commit b70960e

Please sign in to comment.