tidying some things up
session message handling is pretty annoying; should look into a better method of doing this
This commit is contained in:
parent
45f33b8b46
commit
e457e979ff
|
@ -17,7 +17,6 @@ func accountHandler(app *model.AppState) http.Handler {
|
||||||
|
|
||||||
mux.Handle("/password", changePasswordHandler(app))
|
mux.Handle("/password", changePasswordHandler(app))
|
||||||
mux.Handle("/delete", deleteAccountHandler(app))
|
mux.Handle("/delete", deleteAccountHandler(app))
|
||||||
mux.Handle("/", accountIndexHandler(app))
|
|
||||||
|
|
||||||
return mux
|
return mux
|
||||||
}
|
}
|
||||||
|
@ -37,6 +36,13 @@ func accountIndexHandler(app *model.AppState) http.Handler {
|
||||||
TOTPs []model.TOTP
|
TOTPs []model.TOTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionMessage := session.Message
|
||||||
|
sessionError := session.Error
|
||||||
|
controller.SetSessionMessage(app.DB, session, "")
|
||||||
|
controller.SetSessionError(app.DB, session, "")
|
||||||
|
session.Message = sessionMessage
|
||||||
|
session.Error = sessionError
|
||||||
|
|
||||||
err = pages["account"].Execute(w, accountResponse{
|
err = pages["account"].Execute(w, accountResponse{
|
||||||
Session: session,
|
Session: session,
|
||||||
TOTPs: totps,
|
TOTPs: totps,
|
||||||
|
@ -57,18 +63,39 @@ func changePasswordHandler(app *model.AppState) http.Handler {
|
||||||
|
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
|
|
||||||
|
controller.SetSessionMessage(app.DB, session, "")
|
||||||
|
controller.SetSessionError(app.DB, session, "")
|
||||||
|
|
||||||
r.ParseForm()
|
r.ParseForm()
|
||||||
|
|
||||||
currentPassword := r.Form.Get("current-password")
|
currentPassword := r.Form.Get("current-password")
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(currentPassword)); err != nil {
|
if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(currentPassword)); err != nil {
|
||||||
controller.SetSessionMessage(app.DB, session, "Incorrect password.")
|
controller.SetSessionError(app.DB, session, "Incorrect password.")
|
||||||
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPassword := r.Form.Get("new-password")
|
newPassword := r.Form.Get("new-password")
|
||||||
|
|
||||||
controller.SetSessionMessage(app.DB, session, fmt.Sprintf("Updating password to <code>%s</code>", newPassword))
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "WARN: Failed to generate password hash: %v\n", err)
|
||||||
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||||
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Account.Password = string(hashedPassword)
|
||||||
|
err = controller.UpdateAccount(app.DB, session.Account)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "WARN: Failed to update account password: %v\n", err)
|
||||||
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||||
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.SetSessionError(app.DB, session, "")
|
||||||
|
controller.SetSessionMessage(app.DB, session, "Password updated successfully.")
|
||||||
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -97,10 +124,10 @@ func deleteAccountHandler(app *model.AppState) http.Handler {
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(r.Form.Get("password"))); err != nil {
|
if err := bcrypt.CompareHashAndPassword([]byte(session.Account.Password), []byte(r.Form.Get("password"))); err != nil {
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"[%s] WARN: Account \"%s\" attempted account deletion with incorrect password.\n",
|
"[%s] WARN: Account \"%s\" attempted account deletion with incorrect password.\n",
|
||||||
time.Now().Format("2006-02-01 15:04:05"),
|
time.Now().Format(time.UnixDate),
|
||||||
session.Account.Username,
|
session.Account.Username,
|
||||||
)
|
)
|
||||||
controller.SetSessionMessage(app.DB, session, "Incorrect password.")
|
controller.SetSessionError(app.DB, session, "Incorrect password.")
|
||||||
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -108,35 +135,36 @@ func deleteAccountHandler(app *model.AppState) http.Handler {
|
||||||
totpMethod, err := controller.CheckTOTPForAccount(app.DB, session.Account.ID, r.Form.Get("totp"))
|
totpMethod, err := controller.CheckTOTPForAccount(app.DB, session.Account.ID, r.Form.Get("totp"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to fetch account: %v\n", err)
|
||||||
controller.SetSessionMessage(app.DB, session, "Something went wrong. Please try again.")
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||||
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if totpMethod == nil {
|
if totpMethod == nil {
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"[%s] WARN: Account \"%s\" attempted account deletion with incorrect TOTP.\n",
|
"[%s] WARN: Account \"%s\" attempted account deletion with incorrect TOTP.\n",
|
||||||
time.Now().Format("2006-02-01 15:04:05"),
|
time.Now().Format(time.UnixDate),
|
||||||
session.Account.Username,
|
session.Account.Username,
|
||||||
)
|
)
|
||||||
controller.SetSessionMessage(app.DB, session, "Incorrect TOTP.")
|
controller.SetSessionError(app.DB, session, "Incorrect TOTP.")
|
||||||
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.DeleteAccount(app.DB, session.Account.ID)
|
err = controller.DeleteAccount(app.DB, session.Account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err)
|
||||||
controller.SetSessionMessage(app.DB, session, "Something went wrong. Please try again.")
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||||
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
http.Redirect(w, r, "/admin/account", http.StatusFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"[%s] INFO: Account \"%s\" deleted by user request.\n",
|
"[%s] INFO: Account \"%s\" deleted by user request.\n",
|
||||||
time.Now().Format("2006-02-01 15:04:05"),
|
time.Now().Format(time.UnixDate),
|
||||||
session.Account.Username,
|
session.Account.Username,
|
||||||
)
|
)
|
||||||
|
|
||||||
session.Account = nil
|
controller.SetSessionAccount(app.DB, session, nil)
|
||||||
|
controller.SetSessionError(app.DB, session, "")
|
||||||
controller.SetSessionMessage(app.DB, session, "Account deleted successfully.")
|
controller.SetSessionMessage(app.DB, session, "Account deleted successfully.")
|
||||||
http.Redirect(w, r, "/admin/login", http.StatusFound)
|
http.Redirect(w, r, "/admin/login", http.StatusFound)
|
||||||
})
|
})
|
||||||
|
|
|
@ -93,8 +93,8 @@ func AdminIndexHandler(app *model.AppState) http.Handler {
|
||||||
func registerAccountHandler(app *model.AppState) http.Handler {
|
func registerAccountHandler(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) {
|
||||||
session := r.Context().Value("session").(*model.Session)
|
session := r.Context().Value("session").(*model.Session)
|
||||||
session.Error = sql.NullString{}
|
controller.SetSessionError(app.DB, session, "")
|
||||||
session.Message = sql.NullString{}
|
controller.SetSessionMessage(app.DB, session, "")
|
||||||
|
|
||||||
if session.Account != nil {
|
if session.Account != nil {
|
||||||
// user is already logged in
|
// user is already logged in
|
||||||
|
@ -102,8 +102,12 @@ func registerAccountHandler(app *model.AppState) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type registerData struct {
|
||||||
|
Session *model.Session
|
||||||
|
}
|
||||||
|
|
||||||
render := func() {
|
render := func() {
|
||||||
err := pages["register"].Execute(w, session)
|
err := pages["register"].Execute(w, registerData{ Session: session })
|
||||||
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)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
@ -122,7 +126,7 @@ func registerAccountHandler(app *model.AppState) http.Handler {
|
||||||
|
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
session.Error = sql.NullString{ String: "Malformed data.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Malformed data.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -144,7 +148,7 @@ func registerAccountHandler(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)
|
||||||
session.Error = sql.NullString{ String: "Something went wrong. Please try again.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -153,7 +157,7 @@ func registerAccountHandler(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) }
|
||||||
}
|
}
|
||||||
session.Error = sql.NullString{ String: "Invalid invite code.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Invalid invite code.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -161,7 +165,7 @@ func registerAccountHandler(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)
|
||||||
session.Error = sql.NullString{ String: "Something went wrong. Please try again.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -175,16 +179,23 @@ func registerAccountHandler(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") {
|
||||||
session.Error = sql.NullString{ String: "An account with that username already exists.", Valid: true }
|
controller.SetSessionError(app.DB, session, "An account with that username already exists.")
|
||||||
render()
|
render()
|
||||||
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)
|
||||||
session.Error = sql.NullString{ String: "Something went wrong. Please try again.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf(
|
||||||
|
"[%s]: Account registered: %s (%s)\n",
|
||||||
|
time.Now().Format(time.UnixDate),
|
||||||
|
account.Username,
|
||||||
|
account.ID,
|
||||||
|
)
|
||||||
|
|
||||||
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) }
|
||||||
|
|
||||||
|
@ -229,7 +240,7 @@ func loginHandler(app *model.AppState) http.Handler {
|
||||||
|
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
session.Error = sql.NullString{ String: "Malformed data.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Malformed data.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -253,12 +264,12 @@ func loginHandler(app *model.AppState) http.Handler {
|
||||||
account, err := controller.GetAccountByUsername(app.DB, credentials.Username)
|
account, err := controller.GetAccountByUsername(app.DB, credentials.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account for login: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch account for login: %v\n", err)
|
||||||
session.Error = sql.NullString{ String: "Invalid username or password.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Invalid username or password.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if account == nil {
|
if account == nil {
|
||||||
session.Error = sql.NullString{ String: "Invalid username or password.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Invalid username or password.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -267,10 +278,10 @@ func loginHandler(app *model.AppState) http.Handler {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"[%s] INFO: Account \"%s\" attempted login with incorrect password.\n",
|
"[%s] INFO: Account \"%s\" attempted login with incorrect password.\n",
|
||||||
time.Now().Format("2006-02-01 15:04:05"),
|
time.Now().Format(time.UnixDate),
|
||||||
account.Username,
|
account.Username,
|
||||||
)
|
)
|
||||||
session.Error = sql.NullString{ String: "Invalid username or password.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Invalid username or password.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -278,12 +289,12 @@ func loginHandler(app *model.AppState) http.Handler {
|
||||||
totpMethod, err := controller.CheckTOTPForAccount(app.DB, account.ID, credentials.TOTP)
|
totpMethod, err := controller.CheckTOTPForAccount(app.DB, account.ID, credentials.TOTP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch TOTPs: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch TOTPs: %v\n", err)
|
||||||
session.Error = sql.NullString{ String: "Something went wrong. Please try again.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if totpMethod == nil {
|
if totpMethod == nil {
|
||||||
session.Error = sql.NullString{ String: "Invalid TOTP.", Valid: true }
|
controller.SetSessionError(app.DB, session, "Invalid TOTP.")
|
||||||
render()
|
render()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -291,7 +302,7 @@ func loginHandler(app *model.AppState) http.Handler {
|
||||||
// TODO: log login activity to user
|
// TODO: log login activity to user
|
||||||
fmt.Printf(
|
fmt.Printf(
|
||||||
"[%s] INFO: Account \"%s\" logged in with method \"%s\"\n",
|
"[%s] INFO: Account \"%s\" logged in with method \"%s\"\n",
|
||||||
time.Now().Format("2006-02-01 15:04:05"),
|
time.Now().Format(time.UnixDate),
|
||||||
account.Username,
|
account.Username,
|
||||||
totpMethod.Name,
|
totpMethod.Name,
|
||||||
)
|
)
|
||||||
|
@ -379,11 +390,7 @@ func enforceSession(app *model.AppState, next http.Handler) http.Handler {
|
||||||
// fetch existing session
|
// fetch existing session
|
||||||
session, err = controller.GetSession(app.DB, sessionCookie.Value)
|
session, err = controller.GetSession(app.DB, sessionCookie.Value)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil && !strings.Contains(err.Error(), "no rows") {
|
||||||
if strings.Contains(err.Error(), "no rows") {
|
|
||||||
http.Error(w, "Invalid session. Please try clearing your cookies and refresh.", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve session: %v\n", err)
|
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve session: %v\n", err)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|
|
@ -52,7 +52,10 @@
|
||||||
<p>You have no MFA devices.</p>
|
<p>You have no MFA devices.</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<button type="submit" class="new" id="add-mfa-device">Add MFA Device</button>
|
<div>
|
||||||
|
<button type="submit" class="save" id="enable-email" disabled>Enable Email TOTP</button>
|
||||||
|
<a class="button new" id="add-totp-device" href="/admin/account/totp-setup">Add TOTP Device</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
|
|
|
@ -108,7 +108,7 @@ func CreateAccount(db *sqlx.DB, account *model.Account) error {
|
||||||
func UpdateAccount(db *sqlx.DB, account *model.Account) error {
|
func UpdateAccount(db *sqlx.DB, account *model.Account) error {
|
||||||
_, err := db.Exec(
|
_, err := db.Exec(
|
||||||
"UPDATE account " +
|
"UPDATE account " +
|
||||||
"SET username=$2, password=$3, email=$4, avatar_url=$5) " +
|
"SET username=$2,password=$3,email=$4,avatar_url=$5 " +
|
||||||
"WHERE id=$1",
|
"WHERE id=$1",
|
||||||
account.ID,
|
account.ID,
|
||||||
account.Username,
|
account.Username,
|
||||||
|
@ -120,7 +120,7 @@ func UpdateAccount(db *sqlx.DB, account *model.Account) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteAccount(db *sqlx.DB, username string) error {
|
func DeleteAccount(db *sqlx.DB, accountID string) error {
|
||||||
_, err := db.Exec("DELETE FROM account WHERE username=$1", username)
|
_, err := db.Exec("DELETE FROM account WHERE id=$1", accountID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,6 +63,7 @@ func SetSessionAccount(db *sqlx.DB, session *model.Session, account *model.Accou
|
||||||
func SetSessionMessage(db *sqlx.DB, session *model.Session, message string) error {
|
func SetSessionMessage(db *sqlx.DB, session *model.Session, message string) error {
|
||||||
var err error
|
var err error
|
||||||
if message == "" {
|
if message == "" {
|
||||||
|
if !session.Message.Valid { return nil }
|
||||||
session.Message = sql.NullString{ }
|
session.Message = sql.NullString{ }
|
||||||
_, err = db.Exec("UPDATE session SET message=NULL WHERE token=$1", session.Token)
|
_, err = db.Exec("UPDATE session SET message=NULL WHERE token=$1", session.Token)
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,10 +76,11 @@ func SetSessionMessage(db *sqlx.DB, session *model.Session, message string) erro
|
||||||
func SetSessionError(db *sqlx.DB, session *model.Session, message string) error {
|
func SetSessionError(db *sqlx.DB, session *model.Session, message string) error {
|
||||||
var err error
|
var err error
|
||||||
if message == "" {
|
if message == "" {
|
||||||
session.Message = sql.NullString{ }
|
if !session.Error.Valid { return nil }
|
||||||
|
session.Error = sql.NullString{ }
|
||||||
_, err = db.Exec("UPDATE session SET error=NULL WHERE token=$1", session.Token)
|
_, err = db.Exec("UPDATE session SET error=NULL WHERE token=$1", session.Token)
|
||||||
} else {
|
} else {
|
||||||
session.Message = sql.NullString{ String: message, Valid: true }
|
session.Error = sql.NullString{ String: message, Valid: true }
|
||||||
_, err = db.Exec("UPDATE session SET error=$2 WHERE token=$1", session.Token, message)
|
_, err = db.Exec("UPDATE session SET error=$2 WHERE token=$1", session.Token, message)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
|
83
main.go
83
main.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -22,6 +23,7 @@ import (
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// used for database migrations
|
// used for database migrations
|
||||||
|
@ -91,12 +93,12 @@ func main() {
|
||||||
|
|
||||||
account, err := controller.GetAccountByUsername(app.DB, username)
|
account, err := controller.GetAccountByUsername(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account == nil {
|
if account == nil {
|
||||||
fmt.Fprintf(os.Stderr, "Account \"%s\" does not exist.\n", username)
|
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,10 +111,10 @@ func main() {
|
||||||
err = controller.CreateTOTP(app.DB, &totp)
|
err = controller.CreateTOTP(app.DB, &totp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
|
if strings.HasPrefix(err.Error(), "pq: duplicate key") {
|
||||||
fmt.Fprintf(os.Stderr, "Account \"%s\" already has a TOTP method named \"%s\"!\n", account.Username, totp.Name)
|
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" already has a TOTP method named \"%s\"!\n", account.Username, totp.Name)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to create TOTP method: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,18 +132,18 @@ func main() {
|
||||||
|
|
||||||
account, err := controller.GetAccountByUsername(app.DB, username)
|
account, err := controller.GetAccountByUsername(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account == nil {
|
if account == nil {
|
||||||
fmt.Fprintf(os.Stderr, "Account \"%s\" does not exist.\n", username)
|
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.DeleteTOTP(app.DB, account.ID, totpName)
|
err = controller.DeleteTOTP(app.DB, account.ID, totpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP method: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to create TOTP method: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,18 +159,18 @@ func main() {
|
||||||
|
|
||||||
account, err := controller.GetAccountByUsername(app.DB, username)
|
account, err := controller.GetAccountByUsername(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account == nil {
|
if account == nil {
|
||||||
fmt.Fprintf(os.Stderr, "Account \"%s\" does not exist.\n", username)
|
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
totps, err := controller.GetTOTPsForAccount(app.DB, account.ID)
|
totps, err := controller.GetTOTPsForAccount(app.DB, account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create TOTP methods: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to create TOTP methods: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,23 +192,23 @@ func main() {
|
||||||
|
|
||||||
account, err := controller.GetAccountByUsername(app.DB, username)
|
account, err := controller.GetAccountByUsername(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account == nil {
|
if account == nil {
|
||||||
fmt.Fprintf(os.Stderr, "Account \"%s\" does not exist.\n", username)
|
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
totp, err := controller.GetTOTP(app.DB, account.ID, totpName)
|
totp, err := controller.GetTOTP(app.DB, account.ID, totpName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch TOTP method \"%s\": %v\n", totpName, err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch TOTP method \"%s\": %v\n", totpName, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if totp == nil {
|
if totp == nil {
|
||||||
fmt.Fprintf(os.Stderr, "TOTP method \"%s\" does not exist for account \"%s\"\n", totpName, username)
|
fmt.Fprintf(os.Stderr, "FATAL: TOTP method \"%s\" does not exist for account \"%s\"\n", totpName, username)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,18 +220,22 @@ func main() {
|
||||||
fmt.Printf("Creating invite...\n")
|
fmt.Printf("Creating invite...\n")
|
||||||
invite, err := controller.CreateInvite(app.DB, 16, time.Hour * 24)
|
invite, err := controller.CreateInvite(app.DB, 16, time.Hour * 24)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to create invite code: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to create invite code: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Here you go! This code expires in 24 hours: %s\n", invite.Code)
|
fmt.Printf(
|
||||||
|
"Here you go! This code expires in %d hours: %s\n",
|
||||||
|
int(math.Ceil(invite.ExpiresAt.Sub(invite.CreatedAt).Hours())),
|
||||||
|
invite.Code,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
case "purgeInvites":
|
case "purgeInvites":
|
||||||
fmt.Printf("Deleting all invites...\n")
|
fmt.Printf("Deleting all invites...\n")
|
||||||
err := controller.DeleteAllInvites(app.DB)
|
err := controller.DeleteAllInvites(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to delete invites: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to delete invites: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -239,7 +245,7 @@ func main() {
|
||||||
case "listAccounts":
|
case "listAccounts":
|
||||||
accounts, err := controller.GetAllAccounts(app.DB)
|
accounts, err := controller.GetAllAccounts(app.DB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch accounts: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch accounts: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +265,39 @@ func main() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
case "changePassword":
|
||||||
|
if len(os.Args) < 4 {
|
||||||
|
fmt.Fprintf(os.Stderr, "FATAL: `username` and `password` must be specified for changePassword\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
username := os.Args[2]
|
||||||
|
password := os.Args[3]
|
||||||
|
account, err := controller.GetAccountByUsername(app.DB, username)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if account == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to update password: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
account.Password = string(hashedPassword)
|
||||||
|
err = controller.UpdateAccount(app.DB, account)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to delete account: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Account \"%s\" deleted successfully.\n", account.Username)
|
||||||
|
return
|
||||||
|
|
||||||
case "deleteAccount":
|
case "deleteAccount":
|
||||||
if len(os.Args) < 3 {
|
if len(os.Args) < 3 {
|
||||||
fmt.Fprintf(os.Stderr, "FATAL: `username` must be specified for deleteAccount\n")
|
fmt.Fprintf(os.Stderr, "FATAL: `username` must be specified for deleteAccount\n")
|
||||||
|
@ -269,12 +308,12 @@ func main() {
|
||||||
|
|
||||||
account, err := controller.GetAccountByUsername(app.DB, username)
|
account, err := controller.GetAccountByUsername(app.DB, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to fetch account \"%s\": %v\n", username, err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to fetch account \"%s\": %v\n", username, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if account == nil {
|
if account == nil {
|
||||||
fmt.Fprintf(os.Stderr, "Account \"%s\" does not exist.\n", username)
|
fmt.Fprintf(os.Stderr, "FATAL: Account \"%s\" does not exist.\n", username)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,9 +324,9 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = controller.DeleteAccount(app.DB, username)
|
err = controller.DeleteAccount(app.DB, account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Failed to delete account: %v\n", err)
|
fmt.Fprintf(os.Stderr, "FATAL: Failed to delete account: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,19 @@
|
||||||
|
|
||||||
-- Accounts
|
-- Accounts
|
||||||
CREATE TABLE arimelody.account (
|
CREATE TABLE arimelody.account (
|
||||||
id uuid DEFAULT gen_random_uuid(),
|
id UUID DEFAULT gen_random_uuid(),
|
||||||
username text NOT NULL UNIQUE,
|
username TEXT NOT NULL UNIQUE,
|
||||||
password text NOT NULL,
|
password TEXT NOT NULL,
|
||||||
email text,
|
email TEXT,
|
||||||
avatar_url text,
|
avatar_url TEXT,
|
||||||
created_at TIMESTAMP DEFAULT current_timestamp
|
created_at TIMESTAMP DEFAULT current_timestamp
|
||||||
);
|
);
|
||||||
ALTER TABLE arimelody.account ADD CONSTRAINT account_pk PRIMARY KEY (id);
|
ALTER TABLE arimelody.account ADD CONSTRAINT account_pk PRIMARY KEY (id);
|
||||||
|
|
||||||
-- Privilege
|
-- Privilege
|
||||||
CREATE TABLE arimelody.privilege (
|
CREATE TABLE arimelody.privilege (
|
||||||
account uuid NOT NULL,
|
account UUID NOT NULL,
|
||||||
privilege text NOT NULL
|
privilege TEXT NOT NULL
|
||||||
);
|
);
|
||||||
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_pk PRIMARY KEY (account, privilege);
|
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_pk PRIMARY KEY (account, privilege);
|
||||||
|
|
||||||
|
@ -33,12 +33,12 @@ CREATE TABLE arimelody.session (
|
||||||
token TEXT,
|
token TEXT,
|
||||||
user_agent TEXT NOT NULL,
|
user_agent TEXT NOT NULL,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||||
expires_at TIMESTAMP DEFAULT NULL
|
expires_at TIMESTAMP DEFAULT NULL,
|
||||||
account UUID,
|
account UUID,
|
||||||
message TEXT,
|
message TEXT,
|
||||||
error TEXT,
|
error TEXT
|
||||||
);
|
);
|
||||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_pk PRIMARY KEY (session);
|
ALTER TABLE arimelody.session ADD CONSTRAINT session_pk PRIMARY KEY (token);
|
||||||
|
|
||||||
-- TOTPs
|
-- TOTPs
|
||||||
CREATE TABLE arimelody.totp (
|
CREATE TABLE arimelody.totp (
|
||||||
|
|
|
@ -2,21 +2,21 @@
|
||||||
-- New items
|
-- New items
|
||||||
--
|
--
|
||||||
|
|
||||||
-- Acounts
|
-- Accounts
|
||||||
CREATE TABLE arimelody.account (
|
CREATE TABLE arimelody.account (
|
||||||
id uuid DEFAULT gen_random_uuid(),
|
id UUID DEFAULT gen_random_uuid(),
|
||||||
username text NOT NULL UNIQUE,
|
username TEXT NOT NULL UNIQUE,
|
||||||
password text NOT NULL,
|
password TEXT NOT NULL,
|
||||||
email text,
|
email TEXT,
|
||||||
avatar_url text,
|
avatar_url TEXT,
|
||||||
created_at TIMESTAMP DEFAULT current_timestamp
|
created_at TIMESTAMP DEFAULT current_timestamp
|
||||||
);
|
);
|
||||||
ALTER TABLE arimelody.account ADD CONSTRAINT account_pk PRIMARY KEY (id);
|
ALTER TABLE arimelody.account ADD CONSTRAINT account_pk PRIMARY KEY (id);
|
||||||
|
|
||||||
-- Privilege
|
-- Privilege
|
||||||
CREATE TABLE arimelody.privilege (
|
CREATE TABLE arimelody.privilege (
|
||||||
account uuid NOT NULL,
|
account UUID NOT NULL,
|
||||||
privilege text NOT NULL
|
privilege TEXT NOT NULL
|
||||||
);
|
);
|
||||||
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_pk PRIMARY KEY (account, privilege);
|
ALTER TABLE arimelody.privilege ADD CONSTRAINT privilege_pk PRIMARY KEY (account, privilege);
|
||||||
|
|
||||||
|
@ -33,12 +33,12 @@ CREATE TABLE arimelody.session (
|
||||||
token TEXT,
|
token TEXT,
|
||||||
user_agent TEXT NOT NULL,
|
user_agent TEXT NOT NULL,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
created_at TIMESTAMP NOT NULL DEFAULT current_timestamp,
|
||||||
expires_at TIMESTAMP DEFAULT NULL
|
expires_at TIMESTAMP DEFAULT NULL,
|
||||||
account UUID,
|
account UUID,
|
||||||
message TEXT,
|
message TEXT,
|
||||||
error TEXT,
|
error TEXT
|
||||||
);
|
);
|
||||||
ALTER TABLE arimelody.session ADD CONSTRAINT session_pk PRIMARY KEY (session);
|
ALTER TABLE arimelody.session ADD CONSTRAINT session_pk PRIMARY KEY (token);
|
||||||
|
|
||||||
-- TOTPs
|
-- TOTPs
|
||||||
CREATE TABLE arimelody.totp (
|
CREATE TABLE arimelody.totp (
|
||||||
|
|
Loading…
Reference in a new issue