Compare commits
6 Commits
2642f706bf
...
6e3f283a64
Author | SHA1 | Date | |
---|---|---|---|
6e3f283a64 | |||
b98b61aa76 | |||
333499f76e | |||
2a5841d649 | |||
7db64eff6a | |||
7a7a823715 |
@ -31,6 +31,8 @@ type config struct {
|
|||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
}
|
}
|
||||||
|
secretkey string
|
||||||
|
frontend string
|
||||||
}
|
}
|
||||||
|
|
||||||
type application struct {
|
type application struct {
|
||||||
@ -79,6 +81,8 @@ func main() {
|
|||||||
flag.IntVar(&cfg.smtp.port, "smtpport", 1025, "smtp host")
|
flag.IntVar(&cfg.smtp.port, "smtpport", 1025, "smtp host")
|
||||||
flag.StringVar(&cfg.smtp.username, "smtpuser", "user", "smtp user")
|
flag.StringVar(&cfg.smtp.username, "smtpuser", "user", "smtp user")
|
||||||
flag.StringVar(&cfg.smtp.password, "smtppwd", "password", "smtp password")
|
flag.StringVar(&cfg.smtp.password, "smtppwd", "password", "smtp password")
|
||||||
|
flag.StringVar(&cfg.secretkey, "secretkey", "b47df3d8380241c1177f13bdd69c6a60", "secret key")
|
||||||
|
flag.StringVar(&cfg.frontend, "frontend", "http://localhost:4000", "frontend address")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -5,7 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"myapp/internal/cards"
|
"myapp/internal/cards"
|
||||||
|
"myapp/internal/cards/encryption"
|
||||||
"myapp/internal/models"
|
"myapp/internal/models"
|
||||||
|
"myapp/internal/urlsigner"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -13,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/stripe/stripe-go/v79"
|
"github.com/stripe/stripe-go/v79"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type stripePayload struct {
|
type stripePayload struct {
|
||||||
@ -417,16 +420,36 @@ func (app *application) SendPasswordResetEmail(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// verify that email exists
|
||||||
|
_, err = app.DB.GetUserByEmail(payload.Email)
|
||||||
|
if err != nil {
|
||||||
|
var resp struct {
|
||||||
|
Error bool `json:"error"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
resp.Error = true
|
||||||
|
resp.Message = "No matching email found on our system"
|
||||||
|
app.writeJSON(w, http.StatusAccepted, resp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
link := fmt.Sprintf("%s/reset-password?email=%s", app.config.frontend, payload.Email)
|
||||||
|
sign := urlsigner.Signer{
|
||||||
|
Secret: []byte(app.config.secretkey),
|
||||||
|
}
|
||||||
|
|
||||||
|
signedLink := sign.GenerateTokenFromString(link)
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
Link string
|
Link string
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Link = "http://www.vinchent.xyz"
|
data.Link = signedLink
|
||||||
|
|
||||||
// send mail
|
// send mail
|
||||||
err = app.SendMail(
|
err = app.SendMail(
|
||||||
"info@widgets.com",
|
"info@widgets.com",
|
||||||
"info@widgets.com",
|
payload.Email,
|
||||||
"Password Reset Request",
|
"Password Reset Request",
|
||||||
"password-reset",
|
"password-reset",
|
||||||
data,
|
data,
|
||||||
@ -444,3 +467,56 @@ func (app *application) SendPasswordResetEmail(w http.ResponseWriter, r *http.Re
|
|||||||
|
|
||||||
app.writeJSON(w, http.StatusCreated, resp)
|
app.writeJSON(w, http.StatusCreated, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) ResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var payload struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.readJSON(w, r, &payload)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
app.badRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptor := encryption.Encryption{
|
||||||
|
Key: []byte(app.config.secretkey),
|
||||||
|
}
|
||||||
|
|
||||||
|
realEmail, err := encryptor.Decrypt(payload.Email)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
app.badRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := app.DB.GetUserByEmail(realEmail)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
app.badRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newHash, err := bcrypt.GenerateFromPassword([]byte(payload.Password), 12)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
app.badRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.DB.UpdatePasswordForUser(user, string(newHash))
|
||||||
|
if err != nil {
|
||||||
|
app.badRequest(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp struct {
|
||||||
|
Error bool `json:"error"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
resp.Error = false
|
||||||
|
resp.Message = "Password reset."
|
||||||
|
app.writeJSON(w, http.StatusCreated, resp)
|
||||||
|
}
|
||||||
|
@ -27,13 +27,14 @@ func (app *application) routes() http.Handler {
|
|||||||
mux.Route("/api/admin", func(mux chi.Router) {
|
mux.Route("/api/admin", func(mux chi.Router) {
|
||||||
mux.Use(app.Auth)
|
mux.Use(app.Auth)
|
||||||
|
|
||||||
mux.Get("/test", func(w http.ResponseWriter, r *http.Request) {
|
// mux.Get("/test", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte("got in"))
|
// w.Write([]byte("got in"))
|
||||||
})
|
// })
|
||||||
|
|
||||||
mux.Post("/virtual-terminal-succeeded", app.VirtualTerminalPaymentSucceeded)
|
mux.Post("/virtual-terminal-succeeded", app.VirtualTerminalPaymentSucceeded)
|
||||||
})
|
})
|
||||||
mux.Post("/api/forgot-password", app.SendPasswordResetEmail)
|
mux.Post("/api/forgot-password", app.SendPasswordResetEmail)
|
||||||
|
mux.Post("/api/reset-password", app.ResetPassword)
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<p>
|
<p>
|
||||||
<a href="{{.Link}}">{{.Link}}</a>
|
<a href="{{.Link}}">{{.Link}}</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p>This link expires in 10 minutes.</p>
|
||||||
<p>
|
<p>
|
||||||
--
|
--
|
||||||
<br>
|
<br>
|
||||||
|
@ -7,6 +7,8 @@ Click on the link below to get started:
|
|||||||
|
|
||||||
{{.Link}}
|
{{.Link}}
|
||||||
|
|
||||||
|
This link expires in 10 minutes.
|
||||||
|
|
||||||
--
|
--
|
||||||
Widget Co.
|
Widget Co.
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"myapp/internal/cards"
|
"myapp/internal/cards"
|
||||||
|
"myapp/internal/cards/encryption"
|
||||||
"myapp/internal/models"
|
"myapp/internal/models"
|
||||||
|
"myapp/internal/urlsigner"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -324,3 +327,43 @@ func (app *application) ForgotPassword(w http.ResponseWriter, r *http.Request) {
|
|||||||
app.errorLog.Println(err)
|
app.errorLog.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *application) ShowResetPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
email := r.URL.Query().Get("email")
|
||||||
|
theURL := r.RequestURI
|
||||||
|
testURL := fmt.Sprintf("%s%s", app.config.frontend, theURL)
|
||||||
|
|
||||||
|
signer := urlsigner.Signer{
|
||||||
|
Secret: []byte(app.config.secretkey),
|
||||||
|
}
|
||||||
|
valid := signer.VerifyToken(testURL)
|
||||||
|
if !valid {
|
||||||
|
app.errorLog.Println("Invalid url - tampering detected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure not expired
|
||||||
|
expired := signer.Expired(testURL, 10)
|
||||||
|
if expired {
|
||||||
|
app.errorLog.Println("Link expired")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptor := encryption.Encryption{
|
||||||
|
Key: []byte(app.config.secretkey),
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedEmail, err := encryptor.Encrypt(email)
|
||||||
|
if err != nil {
|
||||||
|
app.errorLog.Println("Encryption failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[string]interface{})
|
||||||
|
data["email"] = encryptedEmail
|
||||||
|
if err := app.renderTemplate(w, r, "reset-password", &templateData{
|
||||||
|
Data: data,
|
||||||
|
}); err != nil {
|
||||||
|
app.errorLog.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -34,6 +34,8 @@ type config struct {
|
|||||||
secret string
|
secret string
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
secretkey string
|
||||||
|
frontend string
|
||||||
}
|
}
|
||||||
|
|
||||||
type application struct {
|
type application struct {
|
||||||
@ -83,6 +85,8 @@ func main() {
|
|||||||
"DSN",
|
"DSN",
|
||||||
)
|
)
|
||||||
flag.StringVar(&cfg.api, "api", "http://localhost:4001", "URL to api")
|
flag.StringVar(&cfg.api, "api", "http://localhost:4001", "URL to api")
|
||||||
|
flag.StringVar(&cfg.secretkey, "secretkey", "b47df3d8380241c1177f13bdd69c6a60", "secret key")
|
||||||
|
flag.StringVar(&cfg.frontend, "frontend", "http://localhost:4000", "frontend address")
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ func (app *application) routes() http.Handler {
|
|||||||
mux.Post("/login", app.PostLoginPage)
|
mux.Post("/login", app.PostLoginPage)
|
||||||
mux.Get("/logout", app.Logout)
|
mux.Get("/logout", app.Logout)
|
||||||
mux.Get("/forgot-password", app.ForgotPassword)
|
mux.Get("/forgot-password", app.ForgotPassword)
|
||||||
|
mux.Get("/reset-password", app.ShowResetPassword)
|
||||||
|
|
||||||
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))
|
||||||
|
@ -34,8 +34,15 @@ Forgot Password
|
|||||||
{{define "js"}}
|
{{define "js"}}
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import {forgot} from "/static/js/login.js"
|
import {forgot} from "/static/js/login.js"
|
||||||
document.getElementById("reset-btn").addEventListener("click", () => {
|
document.getElementById("reset-btn").addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
forgot({{.API}});
|
forgot({{.API}});
|
||||||
})
|
})
|
||||||
|
document.getElementById("email").addEventListener("keypress", (event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
forgot({{.API}});
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
51
cmd/web/templates/reset-password.page.gohtml
Normal file
51
cmd/web/templates/reset-password.page.gohtml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{{template "base" .}}
|
||||||
|
{{define "title"}}
|
||||||
|
Reset 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="reset-messages"></div>
|
||||||
|
<form action=""
|
||||||
|
method="post"
|
||||||
|
name="reset-form"
|
||||||
|
id="reset-form"
|
||||||
|
class="d-blick needs-validation"
|
||||||
|
autocomplete="off"
|
||||||
|
novalidate="">
|
||||||
|
<h2 class="mt-2 mb-3 text-center">Reset Password</h2>
|
||||||
|
<hr>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="password" class="form-label">Password</label>
|
||||||
|
<input type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
autocomplete="password-new"
|
||||||
|
required=""
|
||||||
|
class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="verify-password" class="form-label">Verify Password</label>
|
||||||
|
<input type="password"
|
||||||
|
id="verify-password"
|
||||||
|
name="verify-password"
|
||||||
|
autocomplete="verify-password-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 {reset} from "/static/js/login.js"
|
||||||
|
document.getElementById("reset-btn").addEventListener("click", () => {
|
||||||
|
reset({{.API}}, {{index .Data "email"}});
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{{end}}
|
||||||
|
|
2
go.mod
2
go.mod
@ -5,6 +5,7 @@ go 1.22.5
|
|||||||
require (
|
require (
|
||||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885
|
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/bwmarrin/go-alone v0.0.0-20190806015146-742bb55d1631
|
||||||
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
|
||||||
@ -17,4 +18,5 @@ require (
|
|||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
github.com/go-test/deep v1.1.1 // indirect
|
github.com/go-test/deep v1.1.1 // indirect
|
||||||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
|
||||||
|
golang.org/x/sys v0.23.0 // indirect
|
||||||
)
|
)
|
||||||
|
4
go.sum
4
go.sum
@ -4,6 +4,8 @@ github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885 h1:C7QA
|
|||||||
github.com/alexedwards/scs/mysqlstore v0.0.0-20240316134038-7e11d57e8885/go.mod h1:p8jK3D80sw1PFrCSdlcJF1O75bp55HqbgDyyCLM0FrE=
|
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/bwmarrin/go-alone v0.0.0-20190806015146-742bb55d1631 h1:Xb5rra6jJt5Z1JsZhIMby+IP5T8aU+Uc2RC9RzSxs9g=
|
||||||
|
github.com/bwmarrin/go-alone v0.0.0-20190806015146-742bb55d1631/go.mod h1:P86Dksd9km5HGX5UMIocXvX87sEp2xUARle3by+9JZ4=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||||
@ -33,6 +35,8 @@ golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
|||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||||
|
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
|
54
internal/cards/encryption/encryption.go
Normal file
54
internal/cards/encryption/encryption.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package encryption
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Encryption struct {
|
||||||
|
Key []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encryption) Encrypt(text string) (string, error) {
|
||||||
|
plaintext := []byte(text)
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(e.Key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipherText := make([]byte, aes.BlockSize+len(plaintext))
|
||||||
|
iv := cipherText[:aes.BlockSize]
|
||||||
|
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
stream := cipher.NewCFBEncrypter(block, iv)
|
||||||
|
stream.XORKeyStream(cipherText[aes.BlockSize:], plaintext)
|
||||||
|
|
||||||
|
return base64.URLEncoding.EncodeToString(cipherText), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Encryption) Decrypt(cryptoText string) (string, error) {
|
||||||
|
cipherText, _ := base64.URLEncoding.DecodeString(cryptoText)
|
||||||
|
|
||||||
|
block, err := aes.NewCipher(e.Key)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cipherText) < aes.BlockSize {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
iv := cipherText[:aes.BlockSize]
|
||||||
|
cipherText = cipherText[aes.BlockSize:]
|
||||||
|
|
||||||
|
stream := cipher.NewCFBDecrypter(block, iv)
|
||||||
|
stream.XORKeyStream(cipherText, cipherText)
|
||||||
|
|
||||||
|
return string(cipherText), nil
|
||||||
|
}
|
@ -280,3 +280,16 @@ func (m *DBModel) Authenticate(email, password string) (int, error) {
|
|||||||
|
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *DBModel) UpdatePasswordForUser(u User, hash string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stmt := `UPDATE users SET password = ? where id = ?`
|
||||||
|
|
||||||
|
_, err := m.DB.ExecContext(ctx, stmt, hash, u.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
45
internal/urlsigner/signer.go
Normal file
45
internal/urlsigner/signer.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package urlsigner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
goalone "github.com/bwmarrin/go-alone"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Signer struct {
|
||||||
|
Secret []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Signer) GenerateTokenFromString(data string) string {
|
||||||
|
var urlToSign string
|
||||||
|
|
||||||
|
crypt := goalone.New(s.Secret, goalone.Timestamp)
|
||||||
|
if strings.Contains(data, "?") {
|
||||||
|
urlToSign = fmt.Sprintf("%s&hash=", data)
|
||||||
|
} else {
|
||||||
|
urlToSign = fmt.Sprintf("%s?hash=", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenBytes := crypt.Sign([]byte(urlToSign))
|
||||||
|
token := string(tokenBytes)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Signer) VerifyToken(token string) bool {
|
||||||
|
crypt := goalone.New(s.Secret, goalone.Timestamp)
|
||||||
|
_, err := crypt.Unsign([]byte(token))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Signer) Expired(token string, minutesUntilExpire int) bool {
|
||||||
|
crypt := goalone.New(s.Secret, goalone.Timestamp)
|
||||||
|
ts := crypt.Parse([]byte(token))
|
||||||
|
|
||||||
|
return time.Since(ts.Timestamp) > time.Duration(minutesUntilExpire)*time.Minute
|
||||||
|
}
|
@ -80,3 +80,48 @@ export function forgot(api) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function reset(api, email) {
|
||||||
|
let form = document.getElementById("reset-form");
|
||||||
|
|
||||||
|
if (form.checkValidity() === false) {
|
||||||
|
// this.event.preventDefault();
|
||||||
|
// this.event.stopPropagation();
|
||||||
|
form.classList.add("was-validated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
form.classList.add("was-validated");
|
||||||
|
|
||||||
|
if (document.getElementById("password").value !== document.getElementById("verify-password").value) {
|
||||||
|
showError("reset-messages", "Passwords do not match.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
password: document.getElementById("password").value,
|
||||||
|
email: email,
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'post',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(api + "/api/reset-password", requestOptions)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
console.log(response)
|
||||||
|
if (response.error === false) {
|
||||||
|
showSuccess("reset-messages", "Password reset")
|
||||||
|
setTimeout(function () {
|
||||||
|
location.href = "/login"
|
||||||
|
}, 2000)
|
||||||
|
} else {
|
||||||
|
showError("reset-messages", response.message)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user