Implement OIDC single sign-on #27
@ -13,12 +13,18 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (app *application) getUserRegister(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	if !app.config.localAuthEnabled {
 | 
			
		||||
		http.Redirect(w, r, "/users/login/oidc", http.StatusFound)
 | 
			
		||||
	}
 | 
			
		||||
	form := forms.UserRegistrationForm{}
 | 
			
		||||
	data := app.newCommonData(r)
 | 
			
		||||
	views.UserRegistration("User Registration", data, form).Render(r.Context(), w)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *application) getUserLogin(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	if !app.config.localAuthEnabled {
 | 
			
		||||
		http.Redirect(w, r, "/users/login/oidc", http.StatusFound)
 | 
			
		||||
	}
 | 
			
		||||
	views.UserLogin("Login", app.newCommonData(r), forms.UserLoginForm{}).Render(r.Context(), w)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -94,6 +100,9 @@ func (app *application) postUserLogin(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *application) userLoginOIDC(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	if !app.config.oauthEnabled {
 | 
			
		||||
		http.Redirect(w, r, "/users/login", http.StatusFound)
 | 
			
		||||
	}
 | 
			
		||||
	state, err := randString(16)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		app.serverError(w, r, err)
 | 
			
		||||
@ -108,7 +117,7 @@ func (app *application) userLoginOIDC(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	setCallbackCookie(w, r, "state", state)
 | 
			
		||||
	setCallbackCookie(w, r, "nonce", nonce)
 | 
			
		||||
 | 
			
		||||
	http.Redirect(w, r, app.oauth.config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound)
 | 
			
		||||
	http.Redirect(w, r, app.config.oauth.config.AuthCodeURL(state, oidc.Nonce(nonce)), http.StatusFound)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (app *application) userLoginOIDCCallback(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
@ -122,7 +131,7 @@ func (app *application) userLoginOIDCCallback(w http.ResponseWriter, r *http.Req
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	oauth2Token, err := app.oauth.config.Exchange(r.Context(), r.URL.Query().Get("code"))
 | 
			
		||||
	oauth2Token, err := app.config.oauth.config.Exchange(r.Context(), r.URL.Query().Get("code"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		app.logger.Error("Failed to exchange token")
 | 
			
		||||
		app.serverError(w, r, err)
 | 
			
		||||
@ -133,7 +142,7 @@ func (app *application) userLoginOIDCCallback(w http.ResponseWriter, r *http.Req
 | 
			
		||||
		app.serverError(w, r, errors.New("No id_token field in oauth2 token"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	idToken, err := app.oauth.verifier.Verify(r.Context(), rawIDToken)
 | 
			
		||||
	idToken, err := app.config.oauth.verifier.Verify(r.Context(), rawIDToken)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		app.logger.Error("Failed to verify ID token")
 | 
			
		||||
		app.serverError(w, r, err)
 | 
			
		||||
 | 
			
		||||
@ -107,13 +107,15 @@ func (app *application) getCurrentUser(r *http.Request) *models.User {
 | 
			
		||||
 | 
			
		||||
func (app *application) newCommonData(r *http.Request) views.CommonData {
 | 
			
		||||
	return views.CommonData{
 | 
			
		||||
		CurrentYear:     time.Now().Year(),
 | 
			
		||||
		Flash:           app.sessionManager.PopString(r.Context(), "flash"),
 | 
			
		||||
		IsAuthenticated: app.isAuthenticated(r),
 | 
			
		||||
		CSRFToken:       nosurf.Token(r),
 | 
			
		||||
		CurrentUser:     app.getCurrentUser(r),
 | 
			
		||||
		IsHtmx:          r.Header.Get("Hx-Request") == "true",
 | 
			
		||||
		RootUrl:         app.rootUrl,
 | 
			
		||||
		CurrentYear:      time.Now().Year(),
 | 
			
		||||
		Flash:            app.sessionManager.PopString(r.Context(), "flash"),
 | 
			
		||||
		IsAuthenticated:  app.isAuthenticated(r),
 | 
			
		||||
		CSRFToken:        nosurf.Token(r),
 | 
			
		||||
		CurrentUser:      app.getCurrentUser(r),
 | 
			
		||||
		IsHtmx:           r.Header.Get("Hx-Request") == "true",
 | 
			
		||||
		RootUrl:          app.config.rootUrl,
 | 
			
		||||
		LocalAuthEnabled: app.config.localAuthEnabled,
 | 
			
		||||
		OIDCEnabled:      app.config.oauthEnabled,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,9 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log/slog"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
	"unicode"
 | 
			
		||||
@ -32,6 +34,13 @@ type applicationOauthConfig struct {
 | 
			
		||||
	verifier   *oidc.IDTokenVerifier
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type applicationConfig struct {
 | 
			
		||||
	oauthEnabled     bool
 | 
			
		||||
	localAuthEnabled bool
 | 
			
		||||
	oauth            applicationOauthConfig
 | 
			
		||||
	rootUrl          string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type application struct {
 | 
			
		||||
	sequence          uint16
 | 
			
		||||
	logger            *slog.Logger
 | 
			
		||||
@ -40,21 +49,24 @@ type application struct {
 | 
			
		||||
	guestbookComments models.GuestbookCommentModelInterface
 | 
			
		||||
	sessionManager    *scs.SessionManager
 | 
			
		||||
	formDecoder       *schema.Decoder
 | 
			
		||||
	oauth             applicationOauthConfig
 | 
			
		||||
	config            applicationConfig
 | 
			
		||||
	debug             bool
 | 
			
		||||
	timezones         []string
 | 
			
		||||
	rootUrl           string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	addr := flag.String("addr", ":3000", "HTTP network address")
 | 
			
		||||
	dsn := flag.String("dsn", "guestbook.db", "data source name")
 | 
			
		||||
	debug := flag.Bool("debug", false, "enable debug mode")
 | 
			
		||||
	root := flag.String("root", "https://localhost:3000", "root URL of application")
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))
 | 
			
		||||
	godotenv.Load(".env.dev")
 | 
			
		||||
	cfg, err := setupConfig(*addr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error(err.Error())
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db, err := openDB(*dsn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@ -78,18 +90,11 @@ func main() {
 | 
			
		||||
		users:             &models.UserModel{DB: db, Settings: make(map[string]models.Setting)},
 | 
			
		||||
		guestbookComments: &models.GuestbookCommentModel{DB: db},
 | 
			
		||||
		formDecoder:       formDecoder,
 | 
			
		||||
		config:            cfg,
 | 
			
		||||
		debug:             *debug,
 | 
			
		||||
		timezones:         getAvailableTimezones(),
 | 
			
		||||
		rootUrl:           *root,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o, err := setupOauth(app.rootUrl)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error(err.Error())
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
	app.oauth = o
 | 
			
		||||
 | 
			
		||||
	err = app.users.InitializeSettingsMap()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Error(err.Error())
 | 
			
		||||
@ -137,36 +142,73 @@ func openDB(dsn string) (*sql.DB, error) {
 | 
			
		||||
	return db, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupOauth(rootUrl string) (applicationOauthConfig, error) {
 | 
			
		||||
	var c applicationOauthConfig
 | 
			
		||||
func setupConfig(addr string) (applicationConfig, error) {
 | 
			
		||||
	var c applicationConfig
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		oauth2Provider = os.Getenv("OAUTH2_PROVIDER")
 | 
			
		||||
		clientID       = os.Getenv("OAUTH2_CLIENT_ID")
 | 
			
		||||
		clientSecret   = os.Getenv("OAUTH2_CLIENT_SECRET")
 | 
			
		||||
		rootUrl           = os.Getenv("ROOT_URL")
 | 
			
		||||
		oidcEnabled       = os.Getenv("ENABLE_OIDC")
 | 
			
		||||
		localLoginEnabled = os.Getenv("ENABLE_LOCAL_LOGIN")
 | 
			
		||||
		oauth2Provider    = os.Getenv("OAUTH2_PROVIDER")
 | 
			
		||||
		clientID          = os.Getenv("OAUTH2_CLIENT_ID")
 | 
			
		||||
		clientSecret      = os.Getenv("OAUTH2_CLIENT_SECRET")
 | 
			
		||||
	)
 | 
			
		||||
	if oauth2Provider == "" || clientID == "" || clientSecret == "" {
 | 
			
		||||
		return applicationOauthConfig{}, errors.New("OAUTH2_PROVIDER, OAUTH2_CLIENT_ID, and OAUTH2_CLIENT_SECRET must be specified as environment variables.")
 | 
			
		||||
	if rootUrl != "" {
 | 
			
		||||
		c.rootUrl = rootUrl
 | 
			
		||||
	} else {
 | 
			
		||||
		u, err := url.Parse(fmt.Sprintf("https://localhost%s", addr))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return c, err
 | 
			
		||||
		}
 | 
			
		||||
		c.rootUrl = u.String()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.ctx = context.Background()
 | 
			
		||||
	provider, err := oidc.NewProvider(c.ctx, oauth2Provider)
 | 
			
		||||
	oauthEnabled, err := strconv.ParseBool(oidcEnabled)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return applicationOauthConfig{}, err
 | 
			
		||||
		c.oauthEnabled = false
 | 
			
		||||
	}
 | 
			
		||||
	c.provider = provider
 | 
			
		||||
	c.oidcConfig = &oidc.Config{
 | 
			
		||||
	c.oauthEnabled = oauthEnabled
 | 
			
		||||
 | 
			
		||||
	localAuthEnabled, err := strconv.ParseBool(localLoginEnabled)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		c.localAuthEnabled = true
 | 
			
		||||
	}
 | 
			
		||||
	c.localAuthEnabled = localAuthEnabled
 | 
			
		||||
 | 
			
		||||
	if !c.oauthEnabled && !c.localAuthEnabled {
 | 
			
		||||
		return c, errors.New("Either ENABLE_OIDC or ENABLE_LOCAL_LOGIN must be set to true")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// if OIDC is disabled, no more configuration needs to be read
 | 
			
		||||
	if !oauthEnabled {
 | 
			
		||||
		return c, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var o applicationOauthConfig
 | 
			
		||||
	if oauth2Provider == "" || clientID == "" || clientSecret == "" {
 | 
			
		||||
		return c, errors.New("OAUTH2_PROVIDER, OAUTH2_CLIENT_ID, and OAUTH2_CLIENT_SECRET must be specified as environment variables.")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.ctx = context.Background()
 | 
			
		||||
	provider, err := oidc.NewProvider(o.ctx, oauth2Provider)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return c, err
 | 
			
		||||
	}
 | 
			
		||||
	o.provider = provider
 | 
			
		||||
	o.oidcConfig = &oidc.Config{
 | 
			
		||||
		ClientID: clientID,
 | 
			
		||||
	}
 | 
			
		||||
	c.verifier = provider.Verifier(c.oidcConfig)
 | 
			
		||||
	c.config = oauth2.Config{
 | 
			
		||||
	o.verifier = provider.Verifier(o.oidcConfig)
 | 
			
		||||
	o.config = oauth2.Config{
 | 
			
		||||
		ClientID:     clientID,
 | 
			
		||||
		ClientSecret: clientSecret,
 | 
			
		||||
		Endpoint:     provider.Endpoint(),
 | 
			
		||||
		RedirectURL:  fmt.Sprintf("%s/users/login/oidc/callback", rootUrl),
 | 
			
		||||
		RedirectURL:  fmt.Sprintf("%s/users/login/oidc/callback", c.rootUrl),
 | 
			
		||||
		Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
 | 
			
		||||
	}
 | 
			
		||||
	return c, nil
 | 
			
		||||
 | 
			
		||||
	c.oauth = o
 | 
			
		||||
	return c, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAvailableTimezones() []string {
 | 
			
		||||
 | 
			
		||||
@ -6,13 +6,15 @@ import "fmt"
 | 
			
		||||
import "strings"
 | 
			
		||||
 | 
			
		||||
type CommonData struct {
 | 
			
		||||
	CurrentYear     int
 | 
			
		||||
	Flash           string
 | 
			
		||||
	IsAuthenticated bool
 | 
			
		||||
	CSRFToken       string
 | 
			
		||||
	CurrentUser     *models.User
 | 
			
		||||
	IsHtmx          bool
 | 
			
		||||
	RootUrl         string
 | 
			
		||||
	CurrentYear      int
 | 
			
		||||
	Flash            string
 | 
			
		||||
	IsAuthenticated  bool
 | 
			
		||||
	CSRFToken        string
 | 
			
		||||
	CurrentUser      *models.User
 | 
			
		||||
	IsHtmx           bool
 | 
			
		||||
	RootUrl          string
 | 
			
		||||
	LocalAuthEnabled bool
 | 
			
		||||
	OIDCEnabled      bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func shortIdToSlug(shortId uint64) string {
 | 
			
		||||
@ -52,7 +54,9 @@ templ topNav(data CommonData) {
 | 
			
		||||
				<a href="/users/settings">Settings</a> | 
 | 
			
		||||
				<a href="#" hx-post="/users/logout" hx-headers={ hxHeaders }>Logout</a>
 | 
			
		||||
			} else {
 | 
			
		||||
				<a href="/users/register">Create an Account</a> | 
 | 
			
		||||
				if data.LocalAuthEnabled {
 | 
			
		||||
					<a href="/users/register">Create an Account</a> | 
 | 
			
		||||
				}
 | 
			
		||||
				<a href="/users/login">Login</a>
 | 
			
		||||
			}
 | 
			
		||||
		</div>
 | 
			
		||||
 | 
			
		||||
@ -14,13 +14,15 @@ import "fmt"
 | 
			
		||||
import "strings"
 | 
			
		||||
 | 
			
		||||
type CommonData struct {
 | 
			
		||||
	CurrentYear     int
 | 
			
		||||
	Flash           string
 | 
			
		||||
	IsAuthenticated bool
 | 
			
		||||
	CSRFToken       string
 | 
			
		||||
	CurrentUser     *models.User
 | 
			
		||||
	IsHtmx          bool
 | 
			
		||||
	RootUrl         string
 | 
			
		||||
	CurrentYear      int
 | 
			
		||||
	Flash            string
 | 
			
		||||
	IsAuthenticated  bool
 | 
			
		||||
	CSRFToken        string
 | 
			
		||||
	CurrentUser      *models.User
 | 
			
		||||
	IsHtmx           bool
 | 
			
		||||
	RootUrl          string
 | 
			
		||||
	LocalAuthEnabled bool
 | 
			
		||||
	OIDCEnabled      bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func shortIdToSlug(shortId uint64) string {
 | 
			
		||||
@ -102,7 +104,7 @@ func topNav(data CommonData) templ.Component {
 | 
			
		||||
			var templ_7745c5c3_Var3 string
 | 
			
		||||
			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 45, Col: 40}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 47, Col: 40}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
@ -121,7 +123,7 @@ func topNav(data CommonData) templ.Component {
 | 
			
		||||
			var templ_7745c5c3_Var4 string
 | 
			
		||||
			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 53, Col: 62}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 55, Col: 62}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
@ -132,12 +134,18 @@ func topNav(data CommonData) templ.Component {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"/users/register\">Create an Account</a> |  <a href=\"/users/login\">Login</a>")
 | 
			
		||||
			if data.LocalAuthEnabled {
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"/users/register\">Create an Account</a> | ")
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " <a href=\"/users/login\">Login</a>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</div></nav>")
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div></nav>")
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
@ -166,7 +174,7 @@ func commonFooter() templ.Component {
 | 
			
		||||
			templ_7745c5c3_Var5 = templ.NopComponent
 | 
			
		||||
		}
 | 
			
		||||
		ctx = templ.ClearChildren(ctx)
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<footer><p>A <a href=\"https://32bit.cafe\">32bit.cafe</a> Project</p></footer>")
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><p>A <a href=\"https://32bit.cafe\">32bit.cafe</a> Project</p></footer>")
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
@ -195,20 +203,20 @@ func base(title string, data CommonData) templ.Component {
 | 
			
		||||
			templ_7745c5c3_Var6 = templ.NopComponent
 | 
			
		||||
		}
 | 
			
		||||
		ctx = templ.ClearChildren(ctx)
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<!doctype html><html lang=\"en\"><head><title>")
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<!doctype html><html lang=\"en\"><head><title>")
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
		var templ_7745c5c3_Var7 string
 | 
			
		||||
		templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title)
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 72, Col: 17}
 | 
			
		||||
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 76, Col: 17}
 | 
			
		||||
		}
 | 
			
		||||
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/classless.min.css\" rel=\"stylesheet\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>")
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/classless.min.css\" rel=\"stylesheet\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>")
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
@ -220,25 +228,25 @@ func base(title string, data CommonData) templ.Component {
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<main>")
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<main>")
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
		if data.Flash != "" {
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<div class=\"flash\">")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"flash\">")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			var templ_7745c5c3_Var8 string
 | 
			
		||||
			templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 84, Col: 36}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 88, Col: 36}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</div>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
@ -247,7 +255,7 @@ func base(title string, data CommonData) templ.Component {
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</main>")
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</main>")
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
@ -255,7 +263,7 @@ func base(title string, data CommonData) templ.Component {
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</body></html>")
 | 
			
		||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</body></html>")
 | 
			
		||||
		if templ_7745c5c3_Err != nil {
 | 
			
		||||
			return templ_7745c5c3_Err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -31,7 +31,9 @@ templ UserLogin(title string, data CommonData, form forms.UserLoginForm) {
 | 
			
		||||
			</div>
 | 
			
		||||
			<div>
 | 
			
		||||
				<input type="submit" value="Login"/>
 | 
			
		||||
				<a href="/users/login/oidc">Login with SSO</a>
 | 
			
		||||
				if data.OIDCEnabled {
 | 
			
		||||
					<a href="/users/login/oidc">Login with SSO</a>
 | 
			
		||||
				}
 | 
			
		||||
			</div>
 | 
			
		||||
		</form>
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -143,7 +143,17 @@ func UserLogin(title string, data CommonData, form forms.UserLoginForm) templ.Co
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<input type=\"password\" name=\"password\"></div><div><input type=\"submit\" value=\"Login\"> <a href=\"/users/login/oidc\">Login with SSO</a></div></form>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<input type=\"password\" name=\"password\"></div><div><input type=\"submit\" value=\"Login\"> ")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			if data.OIDCEnabled {
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<a href=\"/users/login/oidc\">Login with SSO</a>")
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></form>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
@ -190,130 +200,130 @@ func UserRegistration(title string, data CommonData, form forms.UserRegistration
 | 
			
		||||
				}()
 | 
			
		||||
			}
 | 
			
		||||
			ctx = templ.InitializeContext(ctx)
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<h1>User Registration</h1><form action=\"/users/register\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<h1>User Registration</h1><form action=\"/users/register\" method=\"post\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			var templ_7745c5c3_Var10 string
 | 
			
		||||
			templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 44, Col: 64}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 46, Col: 64}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "\"><div>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "\"><div>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			error, exists := form.FieldErrors["name"]
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<label for=\"username\">Username: </label> ")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "<label for=\"username\">Username: </label> ")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			if exists {
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<label class=\"error\">")
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<label class=\"error\">")
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
				var templ_7745c5c3_Var11 string
 | 
			
		||||
				templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(error)
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 49, Col: 33}
 | 
			
		||||
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 51, Col: 33}
 | 
			
		||||
				}
 | 
			
		||||
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</label> ")
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</label> ")
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<input type=\"text\" name=\"username\" id=\"username\" value=\"")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<input type=\"text\" name=\"username\" id=\"username\" value=\"")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			var templ_7745c5c3_Var12 string
 | 
			
		||||
			templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(form.Name)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 51, Col: 70}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 53, Col: 70}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" required></div><div>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "\" required></div><div>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			error, exists = form.FieldErrors["email"]
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<label for=\"email\">Email: </label> ")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<label for=\"email\">Email: </label> ")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			if exists {
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<label class=\"error\">")
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<label class=\"error\">")
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
				var templ_7745c5c3_Var13 string
 | 
			
		||||
				templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(error)
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 57, Col: 33}
 | 
			
		||||
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 59, Col: 33}
 | 
			
		||||
				}
 | 
			
		||||
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</label> ")
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</label> ")
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<input type=\"text\" name=\"email\" id=\"email\" value=\"")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<input type=\"text\" name=\"email\" id=\"email\" value=\"")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			var templ_7745c5c3_Var14 string
 | 
			
		||||
			templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(form.Email)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 59, Col: 65}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 61, Col: 65}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" required></div><div>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "\" required></div><div>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			error, exists = form.FieldErrors["password"]
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<label for=\"password\">Password: </label> ")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<label for=\"password\">Password: </label> ")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			if exists {
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<label class=\"error\">")
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<label class=\"error\">")
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
				var templ_7745c5c3_Var15 string
 | 
			
		||||
				templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(error)
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 65, Col: 33}
 | 
			
		||||
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 67, Col: 33}
 | 
			
		||||
				}
 | 
			
		||||
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</label> ")
 | 
			
		||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</label> ")
 | 
			
		||||
				if templ_7745c5c3_Err != nil {
 | 
			
		||||
					return templ_7745c5c3_Err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<input type=\"password\" name=\"password\" id=\"password\"></div><div><input type=\"submit\" value=\"Register\"></div></form>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<input type=\"password\" name=\"password\" id=\"password\"></div><div><input type=\"submit\" value=\"Register\"></div></form>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
@ -360,33 +370,33 @@ func UserProfile(title string, data CommonData, user models.User) templ.Componen
 | 
			
		||||
				}()
 | 
			
		||||
			}
 | 
			
		||||
			ctx = templ.InitializeContext(ctx)
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<h1>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<h1>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			var templ_7745c5c3_Var18 string
 | 
			
		||||
			templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 78, Col: 21}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 80, Col: 21}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</h1><p>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</h1><p>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			var templ_7745c5c3_Var19 string
 | 
			
		||||
			templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 79, Col: 17}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 81, Col: 17}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</p>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</p>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
@ -434,89 +444,89 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
 | 
			
		||||
				}()
 | 
			
		||||
			}
 | 
			
		||||
			ctx = templ.InitializeContext(ctx)
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<div><h1>User Settings</h1><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div><h1>User Settings</h1><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			var templ_7745c5c3_Var22 string
 | 
			
		||||
			templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken)
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 89, Col: 65}
 | 
			
		||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 91, Col: 65}
 | 
			
		||||
			}
 | 
			
		||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "\"> <label>Local Timezone</label> <select name=\"timezones\" id=\"timezone-select\">")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"> <label>Local Timezone</label> <select name=\"timezones\" id=\"timezone-select\">")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
			for _, tz := range timezones {
 | 
			
		||||
				if tz == user.Settings.LocalTimezone.String() {
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<option value=\"")
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<option value=\"")
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
					var templ_7745c5c3_Var23 string
 | 
			
		||||
					templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 94, Col: 25}
 | 
			
		||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 96, Col: 25}
 | 
			
		||||
					}
 | 
			
		||||
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\" selected=\"true\">")
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "\" selected=\"true\">")
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
					var templ_7745c5c3_Var24 string
 | 
			
		||||
					templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 94, Col: 48}
 | 
			
		||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 96, Col: 48}
 | 
			
		||||
					}
 | 
			
		||||
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</option>")
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</option>")
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<option value=\"")
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<option value=\"")
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
					var templ_7745c5c3_Var25 string
 | 
			
		||||
					templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 96, Col: 25}
 | 
			
		||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 98, Col: 25}
 | 
			
		||||
					}
 | 
			
		||||
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "\">")
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "\">")
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
					var templ_7745c5c3_Var26 string
 | 
			
		||||
					templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 96, Col: 32}
 | 
			
		||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 98, Col: 32}
 | 
			
		||||
					}
 | 
			
		||||
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</option>")
 | 
			
		||||
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</option>")
 | 
			
		||||
					if templ_7745c5c3_Err != nil {
 | 
			
		||||
						return templ_7745c5c3_Err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</select> <input type=\"submit\" value=\"Submit\"></form></div>")
 | 
			
		||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</select> <input type=\"submit\" value=\"Submit\"></form></div>")
 | 
			
		||||
			if templ_7745c5c3_Err != nil {
 | 
			
		||||
				return templ_7745c5c3_Err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user