add janky password reset form
This commit is contained in:
parent
2e9ea4ddf5
commit
be04dd6156
14
config.go
14
config.go
@ -15,8 +15,16 @@ type LdapConfig struct {
|
||||
LdapPass string
|
||||
}
|
||||
|
||||
type MailConfig struct {
|
||||
Username string
|
||||
Password string
|
||||
SmtpServer string
|
||||
SmtpPort int
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Ldap *LdapConfig
|
||||
Mail *MailConfig
|
||||
Secret string
|
||||
TplPath string
|
||||
Tls bool
|
||||
@ -44,6 +52,7 @@ func LoadConfig() (*Config, error) {
|
||||
viper.SetConfigType("yaml")
|
||||
c := &Config{}
|
||||
l := &LdapConfig{}
|
||||
m := &MailConfig{}
|
||||
viper.SetDefault("port", "8080")
|
||||
viper.SetDefault("secret", "")
|
||||
viper.SetDefault("Tls", false)
|
||||
@ -60,11 +69,16 @@ func LoadConfig() (*Config, error) {
|
||||
c.Key = viper.GetString("tls_key")
|
||||
c.Cert = viper.GetString("tls_cert")
|
||||
c.TplPath = viper.GetString("templates_path")
|
||||
m.SmtpServer = viper.GetString("SmtpServer")
|
||||
m.Username = viper.GetString("SmtpUsername")
|
||||
m.Password = viper.GetString("SmtpPassword")
|
||||
m.SmtpPort = viper.GetInt("SmtpPort")
|
||||
|
||||
//Validate configs
|
||||
if validateConfigEntry(l.Url, "ldapUrl") || validateConfigEntry(l.AdminUser, "adminUser") || validateConfigEntry(l.UserOu, "userOu") || validateConfigEntry(l.LdapDc, "ldapDc") || validateConfigEntry(l.UserAttr, "userAttr") {
|
||||
log.Fatalf("FATAL: Error in config file, bailing")
|
||||
}
|
||||
c.Ldap = l
|
||||
c.Mail = m
|
||||
return c, nil
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -8,5 +8,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/gorilla/securecookie v1.1.1
|
||||
github.com/spf13/viper v1.7.1
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@ -301,12 +301,16 @@ google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiq
|
||||
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
|
78
ldap.go
78
ldap.go
@ -84,3 +84,81 @@ func loginLDAPAccount(uname string, pwd string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resetLDAPAccountPassword(user string, newPass string) error {
|
||||
url := Conf.Ldap.Url
|
||||
userdn := fmt.Sprintf("%v=%v,%v,%v", Conf.Ldap.UserAttr, user, Conf.Ldap.UserOu, Conf.Ldap.LdapDc)
|
||||
binddn := fmt.Sprintf("%v,%v", Conf.Ldap.AdminUser, Conf.Ldap.LdapDc)
|
||||
basedn := fmt.Sprintf("%v,%v", Conf.Ldap.UserOu, Conf.Ldap.LdapDc)
|
||||
l, err := ldap.DialURL(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer l.Close()
|
||||
err = l.Bind(binddn, Conf.Ldap.LdapPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := l.Search(ldap.NewSearchRequest(
|
||||
basedn,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
fmt.Sprintf("(&(objectClass=organizationalPerson)(%s=%s))", Conf.Ldap.UserAttr, user),
|
||||
[]string{"dn"},
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(result.Entries) != 1 {
|
||||
err_text := fmt.Sprintf("Error finding login user: Wanted 1 result, got %v\n", len(result.Entries))
|
||||
return errors.New(err_text)
|
||||
}
|
||||
passwordModifyRequest := ldap.NewPasswordModifyRequest(userdn, "", newPass)
|
||||
_, err = l.PasswordModify(passwordModifyRequest)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Password could not be changed: %s", err.Error())
|
||||
return errors.New("Error setting password")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findLDAPAccountByEmail(email string) (string, error) {
|
||||
url := Conf.Ldap.Url
|
||||
binddn := fmt.Sprintf("%v,%v", Conf.Ldap.AdminUser, Conf.Ldap.LdapDc)
|
||||
basedn := fmt.Sprintf("%v,%v", Conf.Ldap.UserOu, Conf.Ldap.LdapDc)
|
||||
l, err := ldap.DialURL(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer l.Close()
|
||||
err = l.Bind(binddn, Conf.Ldap.LdapPass)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result, err := l.Search(ldap.NewSearchRequest(
|
||||
basedn,
|
||||
ldap.ScopeWholeSubtree,
|
||||
ldap.NeverDerefAliases,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
fmt.Sprintf("(&(objectClass=organizationalPerson)(mail=%s))", email),
|
||||
[]string{"dn", Conf.Ldap.UserAttr},
|
||||
nil,
|
||||
))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(result.Entries) != 1 {
|
||||
err_text := fmt.Sprintf("Error finding user: Wanted 1 result, got %v\n", len(result.Entries))
|
||||
return "", errors.New(err_text)
|
||||
}
|
||||
entry := result.Entries[0]
|
||||
|
||||
return entry.GetAttributeValue(Conf.Ldap.UserAttr), nil
|
||||
}
|
||||
|
8
main.go
8
main.go
@ -26,8 +26,14 @@ func main() {
|
||||
router.HandleFunc("/login", login).Methods("POST")
|
||||
router.HandleFunc("/logout", logoutPage).Methods("GET")
|
||||
router.HandleFunc("/token", tokenPage).Methods("GET")
|
||||
router.HandleFunc("/passwordreset", resetPageFront).Methods("GET")
|
||||
router.HandleFunc("/passwordreset", resetLookup).Methods("POST")
|
||||
router.HandleFunc("/passwordresetform", resetPageBack).Methods("GET")
|
||||
router.HandleFunc("/passwordresetform", reset).Methods("POST")
|
||||
router.HandleFunc("/resetsuccess", resetSuccessPage).Methods("GET")
|
||||
router.HandleFunc("/reseterror", resetErrorPage).Methods("GET")
|
||||
log.Printf("Registering templates from %v/\n", Conf.TplPath)
|
||||
tpl = template.Must(template.ParseGlob(Conf.TplPath + "/*.html"))
|
||||
tpl = template.Must(template.ParseGlob(Conf.TplPath + "/*"))
|
||||
log.Printf("Guildgate starting on %v\n", Conf.Port)
|
||||
var err error
|
||||
if Conf.Tls {
|
||||
|
98
reset.go
Normal file
98
reset.go
Normal file
@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
func resetLookup(res http.ResponseWriter, req *http.Request) {
|
||||
log.Println("POST /passwordreset")
|
||||
email := req.FormValue("email")
|
||||
uname, err := findLDAPAccountByEmail(email)
|
||||
if err != nil {
|
||||
log.Printf("Error while looking up account to email password reset to: %v\n. Account may not exist", err)
|
||||
http.Redirect(res, req, "/passwordresetform", 303)
|
||||
}
|
||||
if uname == "" {
|
||||
log.Printf("Error while looking up account to email password reset to: %v\n", err)
|
||||
http.Error(res, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("Found user %v, generating password token\n", uname)
|
||||
token, err := generateToken(uname)
|
||||
fmt.Println(token)
|
||||
if err != nil {
|
||||
log.Printf("Error generating password token %v\n", err)
|
||||
http.Error(res, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
log.Printf("Sending password reset email to %v\n", email)
|
||||
/*go func() {
|
||||
err = sendMail(email, uname, token)
|
||||
if err != nil {
|
||||
log.Printf("Error sending password reset email %v\n", err)
|
||||
http.Error(res, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
}
|
||||
}()*/
|
||||
log.Println("Redirecting to next part of password reset")
|
||||
http.Redirect(res, req, "/passwordresetform", 303)
|
||||
}
|
||||
func reset(res http.ResponseWriter, req *http.Request) {
|
||||
token := req.FormValue("token")
|
||||
newPass := req.FormValue("new_password")
|
||||
|
||||
user, err := validateToken(token)
|
||||
if err != nil {
|
||||
log.Printf("Error validing password reset token: %v\n", err)
|
||||
http.Redirect(res, req, "/reseterror", 302)
|
||||
return
|
||||
}
|
||||
if user == "" {
|
||||
log.Printf("Error resetting password without a username\n")
|
||||
http.Error(res, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("Attempting to reset password for %v", user)
|
||||
err = resetLDAPAccountPassword(user, newPass)
|
||||
if err == nil {
|
||||
log.Printf("reset password for %v\n", user)
|
||||
http.Redirect(res, req, "/resetsuccess", 302)
|
||||
return
|
||||
} else {
|
||||
log.Printf("failed to reset password for %v:%v\n", user, err)
|
||||
http.Redirect(res, req, "/reseterror", 302)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func sendMail(recp string, uname string, token string) error {
|
||||
data := struct {
|
||||
Recipient string
|
||||
Name string
|
||||
Token string
|
||||
}{
|
||||
Recipient: recp,
|
||||
Name: uname,
|
||||
Token: token,
|
||||
}
|
||||
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", Conf.Mail.Username)
|
||||
m.SetHeader("To", recp)
|
||||
m.SetHeader("Subject", "Identity Server Password Reset")
|
||||
m.SetBody("text/html", "Hello <b>Bob</b> and <i>Cora</i>!")
|
||||
|
||||
msg := new(bytes.Buffer)
|
||||
|
||||
tpl.ExecuteTemplate(msg, "reset_pass", data)
|
||||
m.SetBody("text/plain", string(msg.Bytes()))
|
||||
d := gomail.NewDialer(Conf.Mail.SmtpServer, Conf.Mail.SmtpPort, Conf.Mail.Username, Conf.Mail.Password)
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -47,7 +47,7 @@ func signup(res http.ResponseWriter, req *http.Request) {
|
||||
|
||||
if Conf.Secret != "" && Conf.Secret != secret {
|
||||
//Checking it as a token
|
||||
err := validateToken(secret)
|
||||
_, err := validateToken(secret)
|
||||
if err != nil {
|
||||
log.Printf("Bad secret entered: %v\n", err)
|
||||
res.Write([]byte("Get a load of this guy, not knowing the secret code"))
|
||||
|
@ -4,6 +4,7 @@
|
||||
<p><a href="/token">Get Token</a></p>
|
||||
{{else}}
|
||||
<p><a href="/register">Register</a></p>
|
||||
<p><a href="/passwordreset">Reset Password</a></p>
|
||||
{{end}}
|
||||
{{template "footer" .}}
|
||||
{{ end }}
|
||||
|
8
templates/reset_error.html
Normal file
8
templates/reset_error.html
Normal file
@ -0,0 +1,8 @@
|
||||
{{ define "reset_error" }}
|
||||
{{ template "header" .}}
|
||||
<p>Unable to reset your password due to the following error:</p>
|
||||
<p>{{.Error}}</p>
|
||||
<p>Please try again or let the admin know.</p>
|
||||
<p><a href="/">Return to homepage</a></p>
|
||||
{{template "footer" .}}
|
||||
{{ end }}
|
9
templates/reset_pass.eml
Normal file
9
templates/reset_pass.eml
Normal file
@ -0,0 +1,9 @@
|
||||
{{ define "reset_pass" }}
|
||||
Hi {{.Name}},
|
||||
|
||||
Enclosed in this message is your Password Reset token.
|
||||
If you did not request this token, please disregard this email.
|
||||
Token:
|
||||
{{.Token}}
|
||||
|
||||
{{end}}
|
44
templates/reset_password_back.html
Normal file
44
templates/reset_password_back.html
Normal file
@ -0,0 +1,44 @@
|
||||
{{ define "reset_password_page_back" }}
|
||||
{{ template "header" .}}
|
||||
<h1> Password Recovery </h1>
|
||||
<p> You should receive a Password Recovery token momentarily in your email inbox</p>
|
||||
<p> Please enter it, and your new password, into the form below to reset your password</p>
|
||||
<form action="/passwordresetform" method="POST" novalidate>
|
||||
<div>
|
||||
<label>Password Token:</label>
|
||||
<input type="text" name="token">
|
||||
</div>
|
||||
<div>
|
||||
<label>New Password:</label>
|
||||
<input type="password" name="new_password"pattern="(?=.*\d)(?=.*[a-z]).{8,}" title="Must contain at least one number and at least 8 or more characters" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>Confirm New Password:</label>
|
||||
<input type="password" name="confirm_password" id="confirm_password" onchange="check()"/>
|
||||
<span id='message'></span>
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Reset">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<div id="requirements">
|
||||
<h3>Password must contain the following:</h3>
|
||||
<p id="letter" class="invalid">A <b>lowercase</b> letter</p>
|
||||
<p id="capital" class="invalid">A <b>capital (uppercase)</b> letter</p>
|
||||
<p id="number" class="invalid">A <b>number</b></p>
|
||||
<p id="length" class="invalid">Minimum <b>8 characters</b></p>
|
||||
</div>
|
||||
<script>
|
||||
function check() {
|
||||
if(document.getElementById('password').value ===
|
||||
document.getElementById('confirm_password').value) {
|
||||
document.getElementById('message').innerHTML = "Passwords match";
|
||||
} else {
|
||||
document.getElementById('message').innerHTML = "Passwords don't match";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{{ template "footer" .}}
|
||||
{{end}}
|
16
templates/reset_password_front.html
Normal file
16
templates/reset_password_front.html
Normal file
@ -0,0 +1,16 @@
|
||||
{{ define "reset_password_page_front" }}
|
||||
{{ template "header" .}}
|
||||
<h1> Lookup By Email </h1>
|
||||
<p> Please enter your email address to reset your password </p>
|
||||
<form action="/passwordreset" method="POST" novalidate>
|
||||
<div>
|
||||
<label>Email Address:</label>
|
||||
<input type="text" name="email">
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Reset">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{{ template "footer" .}}
|
||||
{{end}}
|
6
templates/reset_success.html
Normal file
6
templates/reset_success.html
Normal file
@ -0,0 +1,6 @@
|
||||
{{ define "reset_success" }}
|
||||
{{ template "header" .}}
|
||||
<p>Your password has been succesfully reset!</p>
|
||||
<p><a href="/">Return to homepage</a></p>
|
||||
{{template "footer" .}}
|
||||
{{ end }}
|
10
token.go
10
token.go
@ -30,7 +30,7 @@ func generateToken(sponsor string) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func validateToken(tok string) error {
|
||||
func validateToken(tok string) (string, error) {
|
||||
token, err := jwt.ParseWithClaims(
|
||||
tok,
|
||||
&tokenClaim{},
|
||||
@ -39,15 +39,15 @@ func validateToken(tok string) error {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
claims, ok := token.Claims.(*tokenClaim)
|
||||
if !ok {
|
||||
return errors.New("Invalid token sponsor passed")
|
||||
return "", errors.New("Invalid token sponsor passed")
|
||||
}
|
||||
if claims.ExpiresAt < time.Now().UTC().Unix() {
|
||||
return errors.New("Token has expired")
|
||||
return "", errors.New("Token has expired")
|
||||
}
|
||||
log.Printf("Valid token received; sponsored by %v\n", claims.Sponsor)
|
||||
return nil
|
||||
return claims.Sponsor, nil
|
||||
}
|
||||
|
69
web.go
69
web.go
@ -5,6 +5,73 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func resetPageFront(res http.ResponseWriter, req *http.Request) {
|
||||
log.Println("GET /passwordreset")
|
||||
u := getUserName(req)
|
||||
if u != "" {
|
||||
http.Redirect(res, req, "/", 302) //TODO create password change form, direct to that
|
||||
} else {
|
||||
data := struct {
|
||||
Title string
|
||||
Username string
|
||||
LoggedIn bool
|
||||
}{
|
||||
"Reset Password",
|
||||
"Unregistered",
|
||||
false,
|
||||
}
|
||||
tpl.ExecuteTemplate(res, "reset_password_page_front", data)
|
||||
}
|
||||
}
|
||||
|
||||
func resetPageBack(res http.ResponseWriter, req *http.Request) {
|
||||
log.Println("GET /passwordresetform")
|
||||
u := getUserName(req)
|
||||
if u != "" {
|
||||
http.Redirect(res, req, "/", 302) //TODO create password change form, direct to that
|
||||
} else {
|
||||
data := struct {
|
||||
Title string
|
||||
Username string
|
||||
LoggedIn bool
|
||||
}{
|
||||
"Reset Password",
|
||||
"Unregistered",
|
||||
false,
|
||||
}
|
||||
tpl.ExecuteTemplate(res, "reset_password_page_back", data)
|
||||
}
|
||||
}
|
||||
func resetSuccessPage(res http.ResponseWriter, req *http.Request) {
|
||||
log.Println("GET /resetsuccess")
|
||||
data := struct {
|
||||
Title string
|
||||
Username string
|
||||
LoggedIn bool
|
||||
}{
|
||||
"Reset Password Success",
|
||||
"Unregistered",
|
||||
false,
|
||||
}
|
||||
tpl.ExecuteTemplate(res, "reset_success", data)
|
||||
return
|
||||
}
|
||||
func resetErrorPage(res http.ResponseWriter, req *http.Request) {
|
||||
log.Println("GET /reseterror")
|
||||
data := struct {
|
||||
Title string
|
||||
Username string
|
||||
LoggedIn bool
|
||||
Error string
|
||||
}{
|
||||
"Reset Password Failure",
|
||||
"Unregistered",
|
||||
false,
|
||||
"Undefined",
|
||||
}
|
||||
tpl.ExecuteTemplate(res, "reset_error", data)
|
||||
return
|
||||
}
|
||||
func signupPage(res http.ResponseWriter, req *http.Request) {
|
||||
log.Println("GET /register")
|
||||
u := getUserName(req)
|
||||
@ -46,12 +113,14 @@ func loginPage(res http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
func logoutPage(res http.ResponseWriter, req *http.Request) {
|
||||
log.Println("GET /logout")
|
||||
logout(res, req)
|
||||
tpl.ExecuteTemplate(res, "logout", nil)
|
||||
return
|
||||
}
|
||||
|
||||
func tokenPage(res http.ResponseWriter, req *http.Request) {
|
||||
log.Println("GET /token")
|
||||
u := getUserName(req)
|
||||
if u == "" {
|
||||
http.Redirect(res, req, "/", 302)
|
||||
|
Loading…
Reference in New Issue
Block a user