(incomplete) change password feature
This commit is contained in:
parent
5531ef6bab
commit
0052c470f9
|
@ -3,6 +3,7 @@ package admin
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -13,13 +14,22 @@ import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TemplateData struct {
|
type loginRegisterResponse struct {
|
||||||
Account *model.Account
|
Account *model.Account
|
||||||
Message string
|
Message string
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
||||||
func AccountHandler(app *model.AppState) http.Handler {
|
func AccountHandler(app *model.AppState) http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.Handle("/password", changePasswordHandler(app))
|
||||||
|
mux.Handle("/", accountIndexHandler(app))
|
||||||
|
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
func accountIndexHandler(app *model.AppState) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
account := r.Context().Value("account").(*model.Account)
|
account := r.Context().Value("account").(*model.Account)
|
||||||
|
|
||||||
|
@ -29,14 +39,18 @@ func AccountHandler(app *model.AppState) http.Handler {
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountResponse struct {
|
type accountResponse struct {
|
||||||
Account *model.Account
|
Account *model.Account
|
||||||
TOTPs []model.TOTP
|
TOTPs []model.TOTP
|
||||||
|
Message string
|
||||||
|
Error string
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pages["account"].Execute(w, AccountResponse{
|
err = pages["account"].Execute(w, accountResponse{
|
||||||
Account: account,
|
Account: account,
|
||||||
TOTPs: totps,
|
TOTPs: totps,
|
||||||
|
Message: r.URL.Query().Get("message"),
|
||||||
|
Error: r.URL.Query().Get("error"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Failed to render admin account page: %v\n", err)
|
fmt.Printf("WARN: Failed to render admin account page: %v\n", err)
|
||||||
|
@ -59,7 +73,7 @@ func LoginHandler(app *model.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pages["login"].Execute(w, TemplateData{})
|
err = pages["login"].Execute(w, loginRegisterResponse{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Error rendering admin login page: %s\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Error rendering admin login page: %s\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -213,12 +227,12 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateAccountResponse struct {
|
type CreateaccountResponse struct {
|
||||||
Account *model.Account
|
Account *model.Account
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
render := func(data CreateAccountResponse) {
|
render := func(data CreateaccountResponse) {
|
||||||
err := pages["create-account"].Execute(w, data)
|
err := pages["create-account"].Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("WARN: Error rendering create account page: %s\n", err)
|
fmt.Printf("WARN: Error rendering create account page: %s\n", err)
|
||||||
|
@ -227,7 +241,7 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
render(CreateAccountResponse{})
|
render(CreateaccountResponse{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +252,7 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
|
|
||||||
err = r.ParseForm()
|
err = r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render(CreateAccountResponse{
|
render(CreateaccountResponse{
|
||||||
Message: "Malformed data.",
|
Message: "Malformed data.",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@ -261,7 +275,7 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
invite, err := controller.GetInvite(app.DB, credentials.Invite)
|
invite, err := controller.GetInvite(app.DB, credentials.Invite)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve invite: %v\n", err)
|
||||||
render(CreateAccountResponse{
|
render(CreateaccountResponse{
|
||||||
Message: "Something went wrong. Please try again.",
|
Message: "Something went wrong. Please try again.",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@ -271,7 +285,7 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
err := controller.DeleteInvite(app.DB, invite.Code)
|
err := controller.DeleteInvite(app.DB, invite.Code)
|
||||||
if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) }
|
if err != nil { fmt.Fprintf(os.Stderr, "WARN: Failed to delete expired invite: %v\n", err) }
|
||||||
}
|
}
|
||||||
render(CreateAccountResponse{
|
render(CreateaccountResponse{
|
||||||
Message: "Invalid invite code.",
|
Message: "Invalid invite code.",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@ -280,7 +294,7 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(credentials.Password), bcrypt.DefaultCost)
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(credentials.Password), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to generate password hash: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to generate password hash: %v\n", err)
|
||||||
render(CreateAccountResponse{
|
render(CreateaccountResponse{
|
||||||
Message: "Something went wrong. Please try again.",
|
Message: "Something went wrong. Please try again.",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@ -295,13 +309,13 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
err = controller.CreateAccount(app.DB, &account)
|
err = controller.CreateAccount(app.DB, &account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
|
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
|
||||||
render(CreateAccountResponse{
|
render(CreateaccountResponse{
|
||||||
Message: "An account with that username already exists.",
|
Message: "An account with that username already exists.",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to create account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to create account: %v\n", err)
|
||||||
render(CreateAccountResponse{
|
render(CreateaccountResponse{
|
||||||
Message: "Something went wrong. Please try again.",
|
Message: "Something went wrong. Please try again.",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
@ -330,7 +344,7 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
cookie.Path = "/"
|
cookie.Path = "/"
|
||||||
http.SetCookie(w, &cookie)
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
err = pages["login"].Execute(w, TemplateData{
|
err = pages["login"].Execute(w, loginRegisterResponse{
|
||||||
Account: &account,
|
Account: &account,
|
||||||
Token: token.Token,
|
Token: token.Token,
|
||||||
})
|
})
|
||||||
|
@ -341,3 +355,30 @@ func createAccountHandler(app *model.AppState) http.Handler {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func changePasswordHandler(app *model.AppState) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
account := r.Context().Value("account").(*model.Account)
|
||||||
|
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
currentPassword := r.Form.Get("current-password")
|
||||||
|
if err := bcrypt.CompareHashAndPassword([]byte(account.Password), []byte(currentPassword)); err != nil {
|
||||||
|
http.Redirect(w, r, "/admin/account?error=" + url.PathEscape("Incorrect password."), http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
newPassword := r.Form.Get("new-password")
|
||||||
|
|
||||||
|
http.Redirect(
|
||||||
|
w, r, "/admin/account?message=" +
|
||||||
|
url.PathEscape(fmt.Sprintf("Updating password to <code>%s</code>", newPassword)),
|
||||||
|
http.StatusFound,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ func Handler(app *model.AppState) http.Handler {
|
||||||
mux.Handle("/login", LoginHandler(app))
|
mux.Handle("/login", LoginHandler(app))
|
||||||
mux.Handle("/register", createAccountHandler(app))
|
mux.Handle("/register", createAccountHandler(app))
|
||||||
mux.Handle("/logout", RequireAccount(app, LogoutHandler(app)))
|
mux.Handle("/logout", RequireAccount(app, LogoutHandler(app)))
|
||||||
mux.Handle("/account", RequireAccount(app, AccountHandler(app)))
|
mux.Handle("/account/", RequireAccount(app, http.StripPrefix("/account", AccountHandler(app))))
|
||||||
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
mux.Handle("/static/", http.StripPrefix("/static", staticHandler()))
|
||||||
mux.Handle("/release/", RequireAccount(app, http.StripPrefix("/release", serveRelease(app))))
|
mux.Handle("/release/", RequireAccount(app, http.StripPrefix("/release", serveRelease(app))))
|
||||||
mux.Handle("/artist/", RequireAccount(app, http.StripPrefix("/artist", serveArtist(app))))
|
mux.Handle("/artist/", RequireAccount(app, http.StripPrefix("/artist", serveArtist(app))))
|
||||||
|
|
|
@ -6,22 +6,28 @@
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<main>
|
<main>
|
||||||
|
{{if .Message}}
|
||||||
|
<p id="message">{{.Message}}</p>
|
||||||
|
{{end}}
|
||||||
|
{{if .Error}}
|
||||||
|
<p id="error">{{.Error}}</p>
|
||||||
|
{{end}}
|
||||||
<h1>Account Settings ({{.Account.Username}})</h1>
|
<h1>Account Settings ({{.Account.Username}})</h1>
|
||||||
|
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>Change Password</h2>
|
<h2>Change Password</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<form action="/api/v1/change-password" method="POST" id="change-password">
|
<form action="/admin/account/password" method="POST" id="change-password">
|
||||||
<div>
|
<div>
|
||||||
<label for="current-password">Current Password</label>
|
<label for="current-password">Current Password</label>
|
||||||
<input type="password" name="current-password" value="" autocomplete="current-password">
|
<input type="password" id="current-password" name="current-password" value="" autocomplete="current-password">
|
||||||
|
|
||||||
<label for="new-password">Password</label>
|
<label for="new-password">Password</label>
|
||||||
<input type="password" name="new-password" value="" autocomplete="new-password">
|
<input type="password" id="new-password" name="new-password" value="" autocomplete="new-password">
|
||||||
|
|
||||||
<label for="confirm-password">Confirm Password</label>
|
<label for="confirm-password">Confirm Password</label>
|
||||||
<input type="password" name="confirm-password" value="" autocomplete="new-password">
|
<input type="password" id="confirm-password" value="" autocomplete="new-password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="save">Change Password</button>
|
<button type="submit" class="save">Change Password</button>
|
||||||
|
|
|
@ -26,7 +26,10 @@
|
||||||
<div class="flex-fill"></div>
|
<div class="flex-fill"></div>
|
||||||
{{if .Account}}
|
{{if .Account}}
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
<a href="/admin/logout" id="logout">logged in as {{.Account.Username}}. log out</a>
|
<a href="/admin/account">account ({{.Account.Username}})</a>
|
||||||
|
</div>
|
||||||
|
<div class="nav-item">
|
||||||
|
<a href="/admin/logout" id="logout">log out</a>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="nav-item">
|
<div class="nav-item">
|
||||||
|
|
|
@ -19,6 +19,7 @@ func GetConfig() model.Config {
|
||||||
|
|
||||||
config := model.Config{
|
config := model.Config{
|
||||||
BaseUrl: "https://arimelody.me",
|
BaseUrl: "https://arimelody.me",
|
||||||
|
Host: "0.0.0.0",
|
||||||
Port: 8080,
|
Port: 8080,
|
||||||
DB: model.DBConfig{
|
DB: model.DBConfig{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
|
@ -55,6 +56,7 @@ func handleConfigOverrides(config *model.Config) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env }
|
if env, has := os.LookupEnv("ARIMELODY_BASE_URL"); has { config.BaseUrl = env }
|
||||||
|
if env, has := os.LookupEnv("ARIMELODY_HOST"); has { config.Host = env }
|
||||||
if env, has := os.LookupEnv("ARIMELODY_PORT"); has {
|
if env, has := os.LookupEnv("ARIMELODY_PORT"); has {
|
||||||
config.Port, err = strconv.ParseInt(env, 10, 0)
|
config.Port, err = strconv.ParseInt(env, 10, 0)
|
||||||
if err != nil { return errors.New("ARIMELODY_PORT: " + err.Error()) }
|
if err != nil { return errors.New("ARIMELODY_PORT: " + err.Error()) }
|
||||||
|
|
4
main.go
4
main.go
|
@ -338,9 +338,9 @@ func main() {
|
||||||
|
|
||||||
// start the web server!
|
// start the web server!
|
||||||
mux := createServeMux(&app)
|
mux := createServeMux(&app)
|
||||||
fmt.Printf("Now serving at %s:%d\n", app.Config.BaseUrl, app.Config.Port)
|
fmt.Printf("Now serving at http://%s:%d\n", app.Config.Host, app.Config.Port)
|
||||||
log.Fatal(
|
log.Fatal(
|
||||||
http.ListenAndServe(fmt.Sprintf(":%d", app.Config.Port),
|
http.ListenAndServe(fmt.Sprintf("%s:%d", app.Config.Host, app.Config.Port),
|
||||||
HTTPLog(DefaultHeaders(mux)),
|
HTTPLog(DefaultHeaders(mux)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ type (
|
||||||
|
|
||||||
Config struct {
|
Config struct {
|
||||||
BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."`
|
BaseUrl string `toml:"base_url" comment:"Used for OAuth redirects."`
|
||||||
|
Host string `toml:"host"`
|
||||||
Port int64 `toml:"port"`
|
Port int64 `toml:"port"`
|
||||||
DataDirectory string `toml:"data_dir"`
|
DataDirectory string `toml:"data_dir"`
|
||||||
DB DBConfig `toml:"db"`
|
DB DBConfig `toml:"db"`
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
CREATE SCHEMA IF NOT EXISTS arimelody;
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Tables
|
-- Tables
|
||||||
--
|
--
|
||||||
|
|
Loading…
Reference in a new issue