Hi… I am well aware that this diff view is very suboptimal. It will be fixed when the refactored server comes along!
Rename updateUser to manageUser Used for both creation and update.
package main
import (
	"context"
	"embed"
	"flag"
	"html/template"
	"log"
	"net"
	"net/http"
	"time"
	"github.com/go-chi/chi/v5"
)
var (
	//go:embed template
	templateFS embed.FS
	//go:embed static
	staticFS embed.FS
)
func main() {
	var configFilename, listenAddr string
	flag.StringVar(&configFilename, "config", "/etc/sinwon/config", "Configuration filename")
	flag.StringVar(&listenAddr, "listen", ":8080", "HTTP listen address")
	flag.Parse()
	cfg, err := loadConfig(configFilename)
	if err != nil {
		log.Fatalf("Failed to load config file: %v", err)
	}
	if listenAddr == "" {
		listenAddr = cfg.Listen
	}
	if listenAddr == "" {
		log.Fatalf("Missing listen configuration")
	}
	if cfg.Database == "" {
		log.Fatalf("Missing database configuration")
	}
	db, err := openDB(cfg.Database)
	if err != nil {
		log.Fatalf("Failed to open DB: %v", err)
	}
	tpl := template.Must(template.ParseFS(templateFS, "template/*.html"))
	mux := chi.NewRouter()
	mux.Handle("/static/*", http.FileServer(http.FS(staticFS)))
	mux.Get("/", index)
	mux.HandleFunc("/login", login)
	mux.Post("/logout", logout)
	mux.Post("/client/new", createClient)
	mux.HandleFunc("/user/new", updateUser)
	mux.HandleFunc("/user/{id}", updateUser)
	mux.HandleFunc("/user/new", manageUser)
	mux.HandleFunc("/user/{id}", manageUser)
	mux.Get("/.well-known/oauth-authorization-server", getOAuthServerMetadata)
	mux.HandleFunc("/authorize", authorize)
	mux.Post("/token", exchangeToken)
	go maintainDBLoop(db)
	server := http.Server{
		Addr:    listenAddr,
		Handler: loginTokenMiddleware(mux),
		BaseContext: func(net.Listener) context.Context {
			return newBaseContext(db, tpl)
		},
	}
	log.Printf("OAuth server listening on %v", server.Addr)
	if err := server.ListenAndServe(); err != nil {
		log.Fatalf("Failed to listen and serve: %v", err)
	}
}
func httpError(w http.ResponseWriter, err error) {
	log.Print(err)
	http.Error(w, "Internal server error", http.StatusInternalServerError)
}
func maintainDBLoop(db *DB) {
	ticker := time.NewTicker(15 * time.Minute)
	defer ticker.Stop()
	for range ticker.C {
		ctx := context.Background()
		ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
		if err := db.Maintain(ctx); err != nil {
			log.Printf("Failed to perform database maintenance: %v", err)
		}
		cancel()
	}
}
{{ template "head.html" }}
<main>
<h1>sinwon</h1>
<form method="post" action="">
	Username: <input type="text" name="username" value="{{ .User.Username }}" required><br>
	Password: <input type="password" name="password"><br>
	{{ if not (eq .Me.ID .User.ID) }}
		<label>
			<input type="checkbox" name="admin" {{ if .User.Admin }}checked{{ end }}>
			Administrator
		</label><br>
	{{ end }}
	<a href="/"><button type="button">Cancel</button></a>
	<button type="submit">
		{{ if .User.ID }}
			Update user
		{{ else }}
			Create user
		{{ end }}
	</button>
</form>
</main>
{{ template "foot.html" }}
package main
import (
	"fmt"
	"log"
	"net/http"
	"net/url"
	"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
	}
	clients, err := db.ListClients(ctx, loginToken.User)
	if err != nil {
		httpError(w, err)
		return
	}
	data := struct {
		Clients []Client
		Me      *User
	}{
		Clients: clients,
		Me:      me,
	}
	if err := tpl.ExecuteTemplate(w, "index.html", &data); err != nil {
		panic(err)
	}
}
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 := req.PostFormValue("username")
	password := req.PostFormValue("password")
	if username == "" {
		if err := tpl.ExecuteTemplate(w, "login.html", nil); err != nil {
			panic(err)
		}
		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
		if err := tpl.ExecuteTemplate(w, "login.html", nil); err != nil {
			panic(err)
		}
		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()
	if err != nil {
		httpError(w, fmt.Errorf("failed to generate access token: %v", err))
		return
	}
	if err := db.CreateAccessToken(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 updateUser(w http.ResponseWriter, req *http.Request) {
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 := req.PostFormValue("username")
	password := req.PostFormValue("password")
	admin := req.PostFormValue("admin") == "on"
	if username == "" {
		data := struct {
			User *User
			Me   *User
		}{
			User: user,
			Me:   me,
		}
		if err := tpl.ExecuteTemplate(w, "update-user.html", &data); err != nil {
		if err := tpl.ExecuteTemplate(w, "manage-user.html", &data); err != nil {
			panic(err)
		}
		return
	}
	user.Username = username
	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)
}