Warning: Due to various recent migrations, viewing non-HEAD refs may be broken.
/user.go (raw)
package main
import (
"fmt"
"log"
"net/http"
"net/url"
"strings"
"time"
"github.com/go-chi/chi/v5"
)
func index(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
db := dbFromContext(ctx)
tpl := templateFromContext(ctx)
loginToken := loginTokenFromContext(ctx)
if loginToken == nil {
http.Redirect(w, req, "/login", http.StatusFound)
return
}
me, err := db.FetchUser(ctx, loginToken.User)
if err != nil {
httpError(w, err)
return
}
authorizedClients, err := db.ListAuthorizedClients(ctx, loginToken.User)
if err != nil {
httpError(w, err)
return
}
clients, err := db.ListClients(ctx, loginToken.User)
if err != nil {
httpError(w, err)
return
}
var users []User
if me.Admin {
users, err = db.ListUsers(ctx)
if err != nil {
httpError(w, err)
return
}
}
data := struct {
TemplateBaseData
Me *User
AuthorizedClients []AuthorizedClient
Clients []Client
Users []User
}{
Me: me,
AuthorizedClients: authorizedClients,
Clients: clients,
Users: users,
}
tpl.MustExecuteTemplate(req.Context(), w, "index.html", &data)
}
func login(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
db := dbFromContext(ctx)
tpl := templateFromContext(ctx)
q := req.URL.Query()
rawRedirectURI := q.Get("redirect_uri")
if rawRedirectURI == "" {
rawRedirectURI = "/"
}
redirectURI, err := url.Parse(rawRedirectURI)
if err != nil || redirectURI.Scheme != "" || redirectURI.Opaque != "" || redirectURI.User != nil || redirectURI.Host != "" {
http.Error(w, "Invalid redirect URI", http.StatusBadRequest)
return
}
if loginTokenFromContext(ctx) != nil {
http.Redirect(w, req, redirectURI.String(), http.StatusFound)
return
}
username := strings.TrimSpace(req.PostFormValue("username"))
password := req.PostFormValue("password")
if username == "" {
tpl.MustExecuteTemplate(req.Context(), w, "login.html", nil)
return
}
user, err := db.FetchUserByUsername(ctx, username)
if err != nil && err != errNoDBRows {
httpError(w, fmt.Errorf("failed to fetch user: %v", err))
return
}
if err == nil {
err = user.VerifyPassword(password)
}
if err != nil {
log.Printf("login failed for user %q: %v", username, err)
// TODO: show error message
tpl.MustExecuteTemplate(req.Context(), w, "login.html", nil)
return
}
if user.PasswordNeedsRehash() {
if err := user.SetPassword(password); err != nil {
httpError(w, fmt.Errorf("failed to rehash password: %v", err))
return
}
if err := db.StoreUser(ctx, user); err != nil {
httpError(w, fmt.Errorf("failed to store user: %v", err))
return
}
}
token := AccessToken{
User: user.ID,
Scope: internalTokenScope,
}
secret, err := token.Generate(4 * time.Hour)
if err != nil {
httpError(w, fmt.Errorf("failed to generate access token: %v", err))
return
}
token.AuthTime = token.IssuedAt
if err := db.StoreAccessToken(ctx, &token); err != nil {
httpError(w, fmt.Errorf("failed to create access token: %v", err))
return
}
setLoginTokenCookie(w, req, &token, secret)
http.Redirect(w, req, redirectURI.String(), http.StatusFound)
}
func logout(w http.ResponseWriter, req *http.Request) {
unsetLoginTokenCookie(w, req)
http.Redirect(w, req, "/login", http.StatusFound)
}
func manageUser(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
db := dbFromContext(ctx)
tpl := templateFromContext(ctx)
user := new(User)
if idStr := chi.URLParam(req, "id"); idStr != "" {
id, err := ParseID[*User](idStr)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
user, err = db.FetchUser(ctx, id)
if err != nil {
httpError(w, err)
return
}
}
loginToken := loginTokenFromContext(ctx)
if loginToken == nil {
http.Redirect(w, req, "/login", http.StatusFound)
return
}
me, err := db.FetchUser(ctx, loginToken.User)
if err != nil {
httpError(w, err)
return
} else if loginToken.User != user.ID && !me.Admin {
http.Error(w, "Access denied", http.StatusForbidden)
return
}
username := strings.TrimSpace(req.PostFormValue("username"))
name := strings.TrimSpace(req.PostFormValue("name"))
email := strings.TrimSpace(req.PostFormValue("email"))
password := req.PostFormValue("password")
admin := req.PostFormValue("admin") == "on"
if username == "" {
data := struct {
TemplateBaseData
User *User
Me *User
}{
User: user,
Me: me,
}
tpl.MustExecuteTemplate(req.Context(), w, "manage-user.html", &data)
return
}
user.Username = username
user.Name = name
user.Email = email
if me.Admin && user.ID != me.ID {
user.Admin = admin
}
if password != "" {
if err := user.SetPassword(password); err != nil {
httpError(w, err)
return
}
}
if err := db.StoreUser(ctx, user); err != nil {
httpError(w, err)
return
}
http.Redirect(w, req, "/", http.StatusFound)
}