Compare commits
4 Commits
2f7300db0f
...
21c5f42aff
Author | SHA1 | Date | |
---|---|---|---|
21c5f42aff | |||
6edfc529ea | |||
d256bfb438 | |||
a6d54242bb |
@ -25,6 +25,12 @@ type config struct {
|
|||||||
secret string
|
secret string
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
smtp struct {
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type application struct {
|
type application struct {
|
||||||
@ -69,6 +75,10 @@ func main() {
|
|||||||
"root:example@tcp(localhost:3306)/widgets?parseTime=true&tls=false",
|
"root:example@tcp(localhost:3306)/widgets?parseTime=true&tls=false",
|
||||||
"Application environment {development|production}",
|
"Application environment {development|production}",
|
||||||
)
|
)
|
||||||
|
flag.StringVar(&cfg.smtp.host, "smtphost", "0.0.0.0", "smtp host")
|
||||||
|
flag.IntVar(&cfg.smtp.port, "smtpport", 1025, "smtp host")
|
||||||
|
flag.StringVar(&cfg.smtp.username, "smtpuser", "user", "smtp user")
|
||||||
|
flag.StringVar(&cfg.smtp.password, "smtppwd", "password", "smtp password")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -405,3 +405,23 @@ func (app *application) VirtualTerminalPaymentSucceeded(w http.ResponseWriter, r
|
|||||||
|
|
||||||
app.writeJSON(w, http.StatusOK, txn)
|
app.writeJSON(w, http.StatusOK, txn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) SendPasswordResetEmail(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var payload struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.readJSON(w, r, &payload)
|
||||||
|
if err != nil {
|
||||||
|
app.badRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Link string
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Link = "http://www.vinchent.xyz"
|
||||||
|
|
||||||
|
// send mail
|
||||||
|
}
|
||||||
|
78
cmd/api/mailer.go
Normal file
78
cmd/api/mailer.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
mail "github.com/xhit/go-simple-mail/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed templates
|
||||||
|
var emailTemplateFS embed.FS
|
||||||
|
|
||||||
|
func (app *application) SendMail(from, to, subject, tmpl string, data interface{}) error {
|
||||||
|
templateToRender := fmt.Sprintf("templates/%s.html.tmpl", tmpl)
|
||||||
|
|
||||||
|
t, err := template.New("email-html").ParseFS(emailTemplateFS, templateToRender)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var tpl bytes.Buffer
|
||||||
|
if err = t.ExecuteTemplate(&tpl, "body", data); err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedMessage := tpl.String()
|
||||||
|
|
||||||
|
templateToRender = fmt.Sprintf("templates/%s.plain.tmpl", tmpl)
|
||||||
|
t, err = template.New("email-plain").ParseFS(emailTemplateFS, templateToRender)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = t.ExecuteTemplate(&tpl, "body", data); err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
plainMessage := tpl.String()
|
||||||
|
|
||||||
|
app.infoLog.Println(formattedMessage, plainMessage)
|
||||||
|
|
||||||
|
// send the mail
|
||||||
|
server := mail.NewSMTPClient()
|
||||||
|
server.Host = app.config.smtp.host
|
||||||
|
server.Port = app.config.smtp.port
|
||||||
|
// NOTE: not needed for MailHog
|
||||||
|
// server.Username = app.config.smtp.username
|
||||||
|
// server.Password = app.config.smtp.password
|
||||||
|
// server.Encryption = mail.EncryptionTLS
|
||||||
|
server.KeepAlive = false
|
||||||
|
server.ConnectTimeout = 10 * time.Second
|
||||||
|
server.SendTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
smtpClient, err := server.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
email := mail.NewMSG()
|
||||||
|
email.SetFrom(from).AddTo(to).SetSubject(subject)
|
||||||
|
email.SetBody(mail.TextHTML, formattedMessage)
|
||||||
|
email.AddAlternative(mail.TextPlain, plainMessage)
|
||||||
|
|
||||||
|
err = email.Send(smtpClient)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
app.infoLog.Println("send mail")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -33,6 +33,7 @@ func (app *application) routes() http.Handler {
|
|||||||
|
|
||||||
mux.Post("/virtual-terminal-succeeded", app.VirtualTerminalPaymentSucceeded)
|
mux.Post("/virtual-terminal-succeeded", app.VirtualTerminalPaymentSucceeded)
|
||||||
})
|
})
|
||||||
|
mux.Post("/api/forgot-password", app.SendPasswordResetEmail)
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
0
cmd/api/templates/password-reset.html.gohtml
Normal file
0
cmd/api/templates/password-reset.html.gohtml
Normal file
0
cmd/api/templates/password-reset.plain.gohtml
Normal file
0
cmd/api/templates/password-reset.plain.gohtml
Normal file
@ -289,3 +289,38 @@ func (app *application) LoginPage(w http.ResponseWriter, r *http.Request) {
|
|||||||
app.errorLog.Println(err)
|
app.errorLog.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) PostLoginPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
app.Session.RenewToken(r.Context())
|
||||||
|
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
email := r.Form.Get("email")
|
||||||
|
password := r.Form.Get("password")
|
||||||
|
|
||||||
|
id, err := app.DB.Authenticate(email, password)
|
||||||
|
if err != nil {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Session.Put(r.Context(), "userID", id)
|
||||||
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) Logout(w http.ResponseWriter, r *http.Request) {
|
||||||
|
app.Session.Destroy(r.Context())
|
||||||
|
app.Session.RenewToken(r.Context())
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *application) ForgotPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if err := app.renderTemplate(w, r, "forgot-password", &templateData{}); err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alexedwards/scs/mysqlstore"
|
||||||
"github.com/alexedwards/scs/v2"
|
"github.com/alexedwards/scs/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -91,16 +92,17 @@ func main() {
|
|||||||
infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
|
infoLog := log.New(os.Stdout, "INFO\t", log.Ldate|log.Ltime)
|
||||||
errorLog := log.New(os.Stdout, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
|
errorLog := log.New(os.Stdout, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
// set up session
|
|
||||||
session = scs.New()
|
|
||||||
session.Lifetime = 24 * time.Hour
|
|
||||||
|
|
||||||
conn, err := driver.OpenDB(cfg.db.dsn)
|
conn, err := driver.OpenDB(cfg.db.dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorLog.Fatal(err)
|
errorLog.Fatal(err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
|
// set up session
|
||||||
|
session = scs.New()
|
||||||
|
session.Lifetime = 24 * time.Hour
|
||||||
|
session.Store = mysqlstore.New(conn)
|
||||||
|
|
||||||
tc := make(map[string]*template.Template)
|
tc := make(map[string]*template.Template)
|
||||||
|
|
||||||
app := &application{
|
app := &application{
|
||||||
|
@ -7,3 +7,12 @@ import (
|
|||||||
func SessionLoad(next http.Handler) http.Handler {
|
func SessionLoad(next http.Handler) http.Handler {
|
||||||
return session.LoadAndSave(next)
|
return session.LoadAndSave(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) Auth(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !app.Session.Exists(r.Context(), "userID") {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -11,9 +11,14 @@ func (app *application) routes() http.Handler {
|
|||||||
mux.Use(SessionLoad)
|
mux.Use(SessionLoad)
|
||||||
|
|
||||||
mux.Get("/", app.Home)
|
mux.Get("/", app.Home)
|
||||||
mux.Get("/virtual-terminal", app.VirtualTerminal)
|
|
||||||
mux.Post("/virtual-terminal-payment-succeeded", app.VirtualTerminalPaymentSucceeded)
|
mux.Route("/admin", func(mux chi.Router) {
|
||||||
mux.Get("/virtual-terminal-receipt", app.VirtualTerminalReceipt)
|
mux.Use(app.Auth)
|
||||||
|
mux.Get("/virtual-terminal", app.VirtualTerminal)
|
||||||
|
})
|
||||||
|
|
||||||
|
// mux.Post("/virtual-terminal-payment-succeeded", app.VirtualTerminalPaymentSucceeded)
|
||||||
|
// mux.Get("/virtual-terminal-receipt", app.VirtualTerminalReceipt)
|
||||||
|
|
||||||
mux.Get("/widget/{id}", app.ChargeOnce)
|
mux.Get("/widget/{id}", app.ChargeOnce)
|
||||||
mux.Get("/receipt", app.Receipt)
|
mux.Get("/receipt", app.Receipt)
|
||||||
@ -24,6 +29,9 @@ func (app *application) routes() http.Handler {
|
|||||||
|
|
||||||
// auth routes
|
// auth routes
|
||||||
mux.Get("/login", app.LoginPage)
|
mux.Get("/login", app.LoginPage)
|
||||||
|
mux.Post("/login", app.PostLoginPage)
|
||||||
|
mux.Get("/logout", app.Logout)
|
||||||
|
mux.Get("/forgot-password", app.ForgotPassword)
|
||||||
|
|
||||||
fileServer := http.FileServer(http.Dir("./static"))
|
fileServer := http.FileServer(http.Dir("./static"))
|
||||||
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li id="vt-link" class="nav-item d-none">
|
<li id="vt-link" class="nav-item d-none">
|
||||||
<a class="nav-link" href="/virtual-terminal">Virtual Terminal</a>
|
<a class="nav-link" href="/admin/virtual-terminal">Virtual Terminal</a>
|
||||||
</li>
|
</li>
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
<li id="login-link" class="nav-item d-none">
|
<li id="login-link" class="nav-item d-none">
|
||||||
|
41
cmd/web/templates/forgot-password.page.gohtml
Normal file
41
cmd/web/templates/forgot-password.page.gohtml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{{template "base" .}}
|
||||||
|
{{define "title"}}
|
||||||
|
Forgot Password
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 offset-md-3">
|
||||||
|
<div class="alert alert-danger text-center d-none" id="forgot-messages"></div>
|
||||||
|
<form action=""
|
||||||
|
method="post"
|
||||||
|
name="forgot-form"
|
||||||
|
id="forgot-form"
|
||||||
|
class="d-blick needs-validation"
|
||||||
|
autocomplete="off"
|
||||||
|
novalidate="">
|
||||||
|
<h2 class="mt-2 mb-3 text-center">Forgot Password</h2>
|
||||||
|
<hr>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="-email" class="form-label">Email</label>
|
||||||
|
<input type="text"
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
autocomplete="email-new"
|
||||||
|
required=""
|
||||||
|
class="form-control">
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<a href="javascript:void(0)" id="reset-btn" class="btn btn-primary">Reset Password</a>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{define "js"}}
|
||||||
|
<script type="module">
|
||||||
|
import {forgot} from "/static/js/login.js"
|
||||||
|
document.getElementById("reset-btn").addEventListener("click", () => {
|
||||||
|
forgot({{.API}});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{{end}}
|
@ -6,7 +6,7 @@ Login
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 offset-md-3">
|
<div class="col-md-6 offset-md-3">
|
||||||
<div class="alert alert-danger text-center d-none" id="login-messages"></div>
|
<div class="alert alert-danger text-center d-none" id="login-messages"></div>
|
||||||
<form action=""
|
<form action="/login"
|
||||||
method="post"
|
method="post"
|
||||||
name="login-form"
|
name="login-form"
|
||||||
id="login-form"
|
id="login-form"
|
||||||
@ -35,6 +35,9 @@ Login
|
|||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<a href="javascript:void(0)" id="login-btn" class="btn btn-primary">Login</a>
|
<a href="javascript:void(0)" id="login-btn" class="btn btn-primary">Login</a>
|
||||||
|
<p class="mt-2">
|
||||||
|
<small><a href="/forgot-password">Forgot password</a></small>
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,7 +80,7 @@ Virtual Terminal
|
|||||||
<strong>Bank Return Code</strong>: <span id="bank-return-code"></span>
|
<strong>Bank Return Code</strong>: <span id="bank-return-code"></span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="/virtual-terminal" class="btn btn-primary">
|
<a href="/admin/virtual-terminal" class="btn btn-primary">
|
||||||
Charge another card
|
Charge another card
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
8
go.mod
8
go.mod
@ -3,12 +3,18 @@ module myapp
|
|||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885
|
||||||
github.com/alexedwards/scs/v2 v2.8.0
|
github.com/alexedwards/scs/v2 v2.8.0
|
||||||
github.com/go-chi/chi/v5 v5.1.0
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
github.com/go-chi/cors v1.2.1
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-sql-driver/mysql v1.8.1
|
github.com/go-sql-driver/mysql v1.8.1
|
||||||
github.com/stripe/stripe-go/v79 v79.6.0
|
github.com/stripe/stripe-go/v79 v79.6.0
|
||||||
|
github.com/xhit/go-simple-mail/v2 v2.16.0
|
||||||
golang.org/x/crypto v0.26.0
|
golang.org/x/crypto v0.26.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require filippo.io/edwards25519 v1.1.0 // indirect
|
require (
|
||||||
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/go-test/deep v1.1.1 // indirect
|
||||||
|
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
||||||
|
)
|
||||||
|
9
go.sum
9
go.sum
@ -1,5 +1,7 @@
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
|
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885 h1:C7QAamNjR5yz6di4KJWAKcnxueKBgq4L/JGXhlnu35w=
|
||||||
|
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885/go.mod h1:p8jK3D80sw1PFrCSdlcJF1O75bp55HqbgDyyCLM0FrE=
|
||||||
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
|
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
|
||||||
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
@ -8,8 +10,11 @@ github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
|||||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||||
|
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@ -17,6 +22,10 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
|||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stripe/stripe-go/v79 v79.6.0 h1:qSBV2f2rpLEEZTdTlVLzdmQJZNmfoo2E3hUEkFT8GBc=
|
github.com/stripe/stripe-go/v79 v79.6.0 h1:qSBV2f2rpLEEZTdTlVLzdmQJZNmfoo2E3hUEkFT8GBc=
|
||||||
github.com/stripe/stripe-go/v79 v79.6.0/go.mod h1:cuH6X0zC8peY6f1AubHwgJ/fJSn2dh5pfiCr6CjyKVU=
|
github.com/stripe/stripe-go/v79 v79.6.0/go.mod h1:cuH6X0zC8peY6f1AubHwgJ/fJSn2dh5pfiCr6CjyKVU=
|
||||||
|
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
|
||||||
|
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||||
|
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
|
||||||
|
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
|
||||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
@ -3,8 +3,11 @@ package models
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DBModel is the type for database connection values
|
// DBModel is the type for database connection values
|
||||||
@ -254,3 +257,26 @@ func (m *DBModel) GetUserByEmail(email string) (User, error) {
|
|||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *DBModel) Authenticate(email, password string) (int, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var id int
|
||||||
|
var hashedPassword string
|
||||||
|
|
||||||
|
row := m.DB.QueryRowContext(ctx, "SELECT id, password from users WHERE email = ?", email)
|
||||||
|
err := row.Scan(&id, &hashedPassword)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||||
|
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||||
|
return 0, errors.New("incorrect password")
|
||||||
|
} else if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE sessions;
|
@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE sessions (
|
||||||
|
token CHAR(43) PRIMARY KEY,
|
||||||
|
data BLOB NOT NULL,
|
||||||
|
expiry TIMESTAMP(6) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX sessions_expiry_idx ON sessions (expiry);
|
@ -14,6 +14,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
function logout() {
|
function logout() {
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
localStorage.removeItem("token_expiry");
|
localStorage.removeItem("token_expiry");
|
||||||
location.href = "/login";
|
location.href = "/logout";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ export function val(api) {
|
|||||||
localStorage.setItem("token", response.authentication_token.token);
|
localStorage.setItem("token", response.authentication_token.token);
|
||||||
localStorage.setItem("token_expiry", response.authentication_token.expiry);
|
localStorage.setItem("token_expiry", response.authentication_token.expiry);
|
||||||
showSuccess("login-messages", "Login successful.")
|
showSuccess("login-messages", "Login successful.")
|
||||||
location.href = "/";
|
// location.href = "/";
|
||||||
|
document.getElementById("login-form").submit()
|
||||||
} else {
|
} else {
|
||||||
showError("login-messages", response.message)
|
showError("login-messages", response.message)
|
||||||
}
|
}
|
||||||
@ -44,3 +45,38 @@ export function val(api) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function forgot(api) {
|
||||||
|
let form = document.getElementById("forgot-form");
|
||||||
|
|
||||||
|
if (form.checkValidity() === false) {
|
||||||
|
// this.event.preventDefault();
|
||||||
|
// this.event.stopPropagation();
|
||||||
|
form.classList.add("was-validated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
form.classList.add("was-validated");
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
email: document.getElementById("email").value,
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(api + "/api/forgot-password", requestOptions)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
console.log(response)
|
||||||
|
if (response.error === false) {
|
||||||
|
showSuccess("forgot-messages", "Password reset email sent")
|
||||||
|
} else {
|
||||||
|
showError("forgot-messages", response.message)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user