auth: split dev-mode auth constructor and wire up dev-login/logout UI
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 5s
Squeamish about New()'s empty-domain-string sentinel for "dev mode, skip OIDC discovery" - split into New (always makes a real OIDC discovery call, all params required) and NewDev (no ctx/domain/credentials at all, since none are used). main.go now branches on cfg.DevAuthEnabled to pick the right constructor instead of main.go/config.go coordinating on when it's safe to pass empty strings. Also finishes out the dev-auth flow this enables: config.Load reads a DEV_AUTH_ENABLED-aware env file and only requires Auth0 vars when dev auth is off; a PORT config var replaces the hardcoded :8082; and the nav UI (layout/index templates, ui router) points login/logout links at /api/auth/dev-login and a new /api/auth/dev-logout route when dev auth is enabled, so the whole login/logout loop works locally without a real Auth0 app. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+67
-15
@@ -3,6 +3,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -25,14 +26,33 @@ type Config struct {
|
|||||||
// login session for any user_id without going through Auth0. Must
|
// login session for any user_id without going through Auth0. Must
|
||||||
// never be true outside local development.
|
// never be true outside local development.
|
||||||
DevAuthEnabled bool
|
DevAuthEnabled bool
|
||||||
|
|
||||||
|
Port int
|
||||||
|
}
|
||||||
|
|
||||||
|
var envFlagPtr = flag.String("env", "", "")
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load reads a .env file, if present, into the process environment, then
|
// Load reads a .env file, if present, into the process environment, then
|
||||||
// reads the required configuration values from the environment.
|
// reads the required configuration values from the environment.
|
||||||
func Load() (Config, error) {
|
func Load() (Config, error) {
|
||||||
|
dev, err := checkEnvFlag()
|
||||||
|
if err != nil {
|
||||||
|
return Config{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dev {
|
||||||
|
if err := godotenv.Load(".env.dev"); err != nil && !os.IsNotExist(err) {
|
||||||
|
return Config{}, fmt.Errorf("failed to load .env.dev file: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if err := godotenv.Load(); err != nil && !os.IsNotExist(err) {
|
if err := godotenv.Load(); err != nil && !os.IsNotExist(err) {
|
||||||
return Config{}, fmt.Errorf("failed to load .env file: %w", err)
|
return Config{}, fmt.Errorf("failed to load .env file: %w", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cfg Config
|
cfg Config
|
||||||
@@ -49,21 +69,8 @@ func Load() (Config, error) {
|
|||||||
|
|
||||||
cfg.DatabaseURL = required("DATABASE_URL")
|
cfg.DatabaseURL = required("DATABASE_URL")
|
||||||
|
|
||||||
cfg.Auth0Domain = required("AUTH0_DOMAIN")
|
// DEV_AUTH_ENABLED must be known before the Auth0 vars are read below,
|
||||||
cfg.Auth0ClientID = required("AUTH0_CLIENT_ID")
|
// since it decides whether those are required at all.
|
||||||
cfg.Auth0ClientSecret = required("AUTH0_CLIENT_SECRET")
|
|
||||||
cfg.Auth0CallbackURL = required("AUTH0_CALLBACK_URL")
|
|
||||||
|
|
||||||
cfg.EtsyAPIKeystring = required("ETSY_API_KEYSTRING")
|
|
||||||
cfg.EtsyAPISharedSecret = required("ETSY_API_SHARED_SECRET")
|
|
||||||
|
|
||||||
if len(missing) > 0 {
|
|
||||||
return Config{}, fmt.Errorf(
|
|
||||||
"missing required environment variables: %v (see .env.example)",
|
|
||||||
missing,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw := os.Getenv("DEV_AUTH_ENABLED"); raw != "" {
|
if raw := os.Getenv("DEV_AUTH_ENABLED"); raw != "" {
|
||||||
devAuthEnabled, err := strconv.ParseBool(raw)
|
devAuthEnabled, err := strconv.ParseBool(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -72,5 +79,50 @@ func Load() (Config, error) {
|
|||||||
cfg.DevAuthEnabled = devAuthEnabled
|
cfg.DevAuthEnabled = devAuthEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With DEV_AUTH_ENABLED, real Auth0 login/logout never runs (dev-login
|
||||||
|
// mints sessions locally instead), so these are unused and optional.
|
||||||
|
if cfg.DevAuthEnabled {
|
||||||
|
cfg.Auth0Domain = os.Getenv("AUTH0_DOMAIN")
|
||||||
|
cfg.Auth0ClientID = os.Getenv("AUTH0_CLIENT_ID")
|
||||||
|
cfg.Auth0ClientSecret = os.Getenv("AUTH0_CLIENT_SECRET")
|
||||||
|
cfg.Auth0CallbackURL = os.Getenv("AUTH0_CALLBACK_URL")
|
||||||
|
} else {
|
||||||
|
cfg.Auth0Domain = required("AUTH0_DOMAIN")
|
||||||
|
cfg.Auth0ClientID = required("AUTH0_CLIENT_ID")
|
||||||
|
cfg.Auth0ClientSecret = required("AUTH0_CLIENT_SECRET")
|
||||||
|
cfg.Auth0CallbackURL = required("AUTH0_CALLBACK_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.EtsyAPIKeystring = required("ETSY_API_KEYSTRING")
|
||||||
|
cfg.EtsyAPISharedSecret = required("ETSY_API_SHARED_SECRET")
|
||||||
|
|
||||||
|
portStr := required("PORT")
|
||||||
|
|
||||||
|
if len(missing) > 0 {
|
||||||
|
return Config{}, fmt.Errorf(
|
||||||
|
"missing required environment variables: %v (see .env.example)",
|
||||||
|
missing,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if port, err := strconv.Atoi(portStr); err != nil {
|
||||||
|
return Config{}, fmt.Errorf("invalid port number: %w", err)
|
||||||
|
} else {
|
||||||
|
cfg.Port = port
|
||||||
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkEnvFlag() (dev bool, err error) {
|
||||||
|
switch env := *envFlagPtr; env {
|
||||||
|
case "dev":
|
||||||
|
return true, nil
|
||||||
|
case "prd":
|
||||||
|
return false, nil
|
||||||
|
case "":
|
||||||
|
return false, fmt.Errorf("no env flag specified: must be one of dev, prd")
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("invalid env flag specified: must be one of dev, prd: %q", env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,7 +45,10 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// New instantiates the *Authenticator.
|
// New instantiates an *Authenticator backed by a real Auth0 tenant: it makes
|
||||||
|
// an OIDC discovery call against domain, so login/callback/logout are fully
|
||||||
|
// functional. Use NewDev instead when DEV_AUTH_ENABLED is set and no real
|
||||||
|
// Auth0 app is configured.
|
||||||
func New(
|
func New(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
db *pgxpool.Pool,
|
db *pgxpool.Pool,
|
||||||
@@ -75,6 +78,18 @@ func New(
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDev instantiates an *Authenticator with no real Auth0 tenant behind it:
|
||||||
|
// no OIDC discovery call is made, and Provider/Config are left zero-valued.
|
||||||
|
// Only DevLogin is safe to call on the result - Exchange, VerifyIDToken, and
|
||||||
|
// GetLogoutURL all assume a real Auth0 setup and will misbehave. Only use
|
||||||
|
// this from a route gated on an explicit dev-mode flag.
|
||||||
|
func NewDev(db *pgxpool.Pool, logger *logging.Logger) *Authenticator {
|
||||||
|
return &Authenticator{
|
||||||
|
log: logger,
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Authenticator) RunBackgroundCleanup(ctx context.Context) error {
|
func (a *Authenticator) RunBackgroundCleanup(ctx context.Context) error {
|
||||||
for {
|
for {
|
||||||
if _, err := a.db.Exec(ctx, "DELETE FROM oauth_tokens WHERE expiry < NOW()"); err != nil {
|
if _, err := a.db.Exec(ctx, "DELETE FROM oauth_tokens WHERE expiry < NOW()"); err != nil {
|
||||||
|
|||||||
@@ -78,7 +78,11 @@ func runApp(ctx context.Context, logger *logging.Logger) error {
|
|||||||
return fmt.Errorf("failed to initialize database connection pool: %w", err)
|
return fmt.Errorf("failed to initialize database connection pool: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
auth, err := authentication.New(
|
var auth *authentication.Authenticator
|
||||||
|
if cfg.DevAuthEnabled {
|
||||||
|
auth = authentication.NewDev(connPool, logger.WithGroup("authenticator"))
|
||||||
|
} else {
|
||||||
|
auth, err = authentication.New(
|
||||||
ctx,
|
ctx,
|
||||||
connPool,
|
connPool,
|
||||||
logger.WithGroup("authenticator"),
|
logger.WithGroup("authenticator"),
|
||||||
@@ -90,6 +94,7 @@ func runApp(ctx context.Context, logger *logging.Logger) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to construct authenticator: %w", err)
|
return fmt.Errorf("failed to construct authenticator: %w", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
accts := accounts.NewStore(logger.WithGroup("accounts"), connPool)
|
accts := accounts.NewStore(logger.WithGroup("accounts"), connPool)
|
||||||
|
|
||||||
@@ -240,7 +245,7 @@ func runServer(
|
|||||||
)
|
)
|
||||||
|
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: ":8082", // local
|
Addr: fmt.Sprintf(":%d", cfg.Port), // local
|
||||||
Handler: r,
|
Handler: r,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +258,7 @@ func runServer(
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
defer close(alreadyShutdownCh)
|
defer close(alreadyShutdownCh)
|
||||||
|
|
||||||
logger.Info("server running on 8082...")
|
logger.Infof("server running on %d...", cfg.Port)
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
if err := srv.ListenAndServe(); err != nil {
|
||||||
if !errors.Is(err, http.ErrServerClosed) {
|
if !errors.Is(err, http.ErrServerClosed) {
|
||||||
runningErrCh <- fmt.Errorf("server experienced error: %w", err)
|
runningErrCh <- fmt.Errorf("server experienced error: %w", err)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ func Routes(
|
|||||||
if devAuthEnabled {
|
if devAuthEnabled {
|
||||||
logger.Warn("DEV_AUTH_ENABLED is set: /api/auth/dev-login is live and lets any caller authenticate as any user_id with no credentials. Never enable this outside local development.")
|
logger.Warn("DEV_AUTH_ENABLED is set: /api/auth/dev-login is live and lets any caller authenticate as any user_id with no credentials. Never enable this outside local development.")
|
||||||
r.GET("/dev-login", response.Handler(ls.devLoginPage))
|
r.GET("/dev-login", response.Handler(ls.devLoginPage))
|
||||||
|
r.GET("/dev-logout", response.Handler(ls.devLogoutPage))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,3 +136,21 @@ func (s *loginSubrouter) logoutPage(c *gin.Context) (response.Response, error) {
|
|||||||
return response.TemporaryRedirect(s.auth.GetLogoutURL(host).String()).
|
return response.TemporaryRedirect(s.auth.GetLogoutURL(host).String()).
|
||||||
Cookie(cookies.Expired("access_token")), nil
|
Cookie(cookies.Expired("access_token")), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *loginSubrouter) devLogoutPage(c *gin.Context) (response.Response, error) {
|
||||||
|
r := c.Request
|
||||||
|
|
||||||
|
host := r.Header.Get("X-Forwarded-Host")
|
||||||
|
if host == "" {
|
||||||
|
host = r.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
if ck, err := r.Cookie("access_token"); err == nil && ck != nil {
|
||||||
|
if err := s.auth.DeleteOAuthTokens(r.Context(), ck.Value); err != nil {
|
||||||
|
s.log.Error("failed to delete auth token", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.TemporaryRedirect("/ui").
|
||||||
|
Cookie(cookies.Expired("access_token")), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ func NewRouter(
|
|||||||
reps,
|
reps,
|
||||||
etsy,
|
etsy,
|
||||||
authM.Authenticate(),
|
authM.Authenticate(),
|
||||||
|
devAuthEnabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
// non-html content: scripts, styles, images, etc
|
// non-html content: scripts, styles, images, etc
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type (
|
|||||||
accts *accounts.Store
|
accts *accounts.Store
|
||||||
reports *reports.Store
|
reports *reports.Store
|
||||||
etsy *etsy_platform.Platform
|
etsy *etsy_platform.Platform
|
||||||
|
devAuthEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrTemplateNotFound is returned if the reason the template failed to compile
|
// ErrTemplateNotFound is returned if the reason the template failed to compile
|
||||||
@@ -49,6 +50,7 @@ func Routes(
|
|||||||
reps *reports.Store,
|
reps *reports.Store,
|
||||||
etsy *etsy_platform.Platform,
|
etsy *etsy_platform.Platform,
|
||||||
authenticate gin.HandlerFunc,
|
authenticate gin.HandlerFunc,
|
||||||
|
devAuthEnabled bool,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
s := &webpageRouter{
|
s := &webpageRouter{
|
||||||
@@ -181,6 +183,7 @@ func Routes(
|
|||||||
accts: accts,
|
accts: accts,
|
||||||
reports: reps,
|
reports: reps,
|
||||||
etsy: etsy,
|
etsy: etsy,
|
||||||
|
devAuthEnabled: devAuthEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
r.GET("", response.Handler(s.redirectToAccountsIfLoggedInWithAnAccount), response.Handler(s.serveTemplate))
|
r.GET("", response.Handler(s.redirectToAccountsIfLoggedInWithAnAccount), response.Handler(s.serveTemplate))
|
||||||
@@ -219,6 +222,11 @@ func (s *webpageRouter) serveTemplate(c *gin.Context) (response.Response, error)
|
|||||||
args := []any{
|
args := []any{
|
||||||
"Request",
|
"Request",
|
||||||
r,
|
r,
|
||||||
|
|
||||||
|
// dev mode
|
||||||
|
"DevAuthEnabled",
|
||||||
|
s.devAuthEnabled,
|
||||||
|
|
||||||
// add services and data here
|
// add services and data here
|
||||||
"RawEvents",
|
"RawEvents",
|
||||||
s.rawEvents.WithContext(ctx),
|
s.rawEvents.WithContext(ctx),
|
||||||
|
|||||||
+106
-10
@@ -18,6 +18,7 @@
|
|||||||
--text-xl--line-height: calc(1.75 / 1.25);
|
--text-xl--line-height: calc(1.75 / 1.25);
|
||||||
--text-3xl: 1.875rem;
|
--text-3xl: 1.875rem;
|
||||||
--text-6xl: 3.75rem;
|
--text-6xl: 3.75rem;
|
||||||
|
--text-6xl--line-height: 1;
|
||||||
--font-weight-semibold: 600;
|
--font-weight-semibold: 600;
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
--radius-lg: var(--radius);
|
--radius-lg: var(--radius);
|
||||||
@@ -179,9 +180,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
@layer utilities {
|
@layer utilities {
|
||||||
|
.invisible {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
.visible {
|
.visible {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
.absolute {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.fixed {
|
||||||
|
position: fixed;
|
||||||
|
}
|
||||||
.relative {
|
.relative {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -319,6 +329,9 @@
|
|||||||
.hidden {
|
.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.inline {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
.inline-block {
|
.inline-block {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@@ -585,6 +598,10 @@
|
|||||||
.font-display {
|
.font-display {
|
||||||
font-family: var(--display-family);
|
font-family: var(--display-family);
|
||||||
}
|
}
|
||||||
|
.text-6xl {
|
||||||
|
font-size: var(--text-6xl);
|
||||||
|
line-height: var(--tw-leading, var(--text-6xl--line-height));
|
||||||
|
}
|
||||||
.text-lg {
|
.text-lg {
|
||||||
font-size: var(--text-lg);
|
font-size: var(--text-lg);
|
||||||
line-height: var(--tw-leading, var(--text-lg--line-height));
|
line-height: var(--tw-leading, var(--text-lg--line-height));
|
||||||
@@ -617,12 +634,22 @@
|
|||||||
.capitalize {
|
.capitalize {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
.lowercase {
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
.italic {
|
.italic {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.underline {
|
.underline {
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
|
.accent-secondary {
|
||||||
|
accent-color: var(--secondary);
|
||||||
|
}
|
||||||
|
.shadow {
|
||||||
|
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||||
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
|
}
|
||||||
.outline-1 {
|
.outline-1 {
|
||||||
outline-style: var(--tw-outline-style);
|
outline-style: var(--tw-outline-style);
|
||||||
outline-width: 1px;
|
outline-width: 1px;
|
||||||
@@ -643,11 +670,6 @@
|
|||||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||||
}
|
}
|
||||||
.not-group-focus-within\:hidden {
|
|
||||||
&:not(*:is(:where(.group):focus-within *)) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.not-group-hover\:hidden {
|
.not-group-hover\:hidden {
|
||||||
&:not(*:is(:where(.group):hover *)) {
|
&:not(*:is(:where(.group):hover *)) {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -656,11 +678,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.not-group-focus\:hidden {
|
|
||||||
&:not(*:is(:where(.group):focus *)) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.not-open\:mb-\[1em\] {
|
.not-open\:mb-\[1em\] {
|
||||||
&:not(*:is([open], :popover-open, :open)) {
|
&:not(*:is([open], :popover-open, :open)) {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
@@ -1249,6 +1266,71 @@
|
|||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
|
@property --tw-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-shadow-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-shadow-alpha {
|
||||||
|
syntax: "<percentage>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 100%;
|
||||||
|
}
|
||||||
|
@property --tw-inset-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-inset-shadow-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-inset-shadow-alpha {
|
||||||
|
syntax: "<percentage>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 100%;
|
||||||
|
}
|
||||||
|
@property --tw-ring-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-ring-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-inset-ring-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-inset-ring-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
|
@property --tw-ring-inset {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
}
|
||||||
|
@property --tw-ring-offset-width {
|
||||||
|
syntax: "<length>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0px;
|
||||||
|
}
|
||||||
|
@property --tw-ring-offset-color {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: #fff;
|
||||||
|
}
|
||||||
|
@property --tw-ring-offset-shadow {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0 0 #0000;
|
||||||
|
}
|
||||||
@property --tw-outline-style {
|
@property --tw-outline-style {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
@@ -1346,6 +1428,20 @@
|
|||||||
--tw-skew-y: initial;
|
--tw-skew-y: initial;
|
||||||
--tw-border-style: solid;
|
--tw-border-style: solid;
|
||||||
--tw-font-weight: initial;
|
--tw-font-weight: initial;
|
||||||
|
--tw-shadow: 0 0 #0000;
|
||||||
|
--tw-shadow-color: initial;
|
||||||
|
--tw-shadow-alpha: 100%;
|
||||||
|
--tw-inset-shadow: 0 0 #0000;
|
||||||
|
--tw-inset-shadow-color: initial;
|
||||||
|
--tw-inset-shadow-alpha: 100%;
|
||||||
|
--tw-ring-color: initial;
|
||||||
|
--tw-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-inset-ring-color: initial;
|
||||||
|
--tw-inset-ring-shadow: 0 0 #0000;
|
||||||
|
--tw-ring-inset: initial;
|
||||||
|
--tw-ring-offset-width: 0px;
|
||||||
|
--tw-ring-offset-color: #fff;
|
||||||
|
--tw-ring-offset-shadow: 0 0 #0000;
|
||||||
--tw-outline-style: solid;
|
--tw-outline-style: solid;
|
||||||
--tw-blur: initial;
|
--tw-blur: initial;
|
||||||
--tw-brightness: initial;
|
--tw-brightness: initial;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
{{- $acctID = .Identity.Account.AccountID }}
|
{{- $acctID = .Identity.Account.AccountID }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- $mockMode := .MockMode }}
|
{{- $mockMode := .MockMode }}
|
||||||
|
{{- $devAuthEnabled := .DevAuthEnabled }}
|
||||||
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@@ -117,8 +118,13 @@
|
|||||||
>
|
>
|
||||||
{{- if not $loggedIn }}
|
{{- if not $loggedIn }}
|
||||||
|
|
||||||
|
{{- $loginPath := "/api/auth/login" }}
|
||||||
|
{{- if $devAuthEnabled }}
|
||||||
|
{{- $loginPath = "/api/auth/dev-login" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
{{ template "navbar-link" (props
|
{{ template "navbar-link" (props
|
||||||
"Href" "/api/auth/login"
|
"Href" $loginPath
|
||||||
"NoHXBoost" true
|
"NoHXBoost" true
|
||||||
"Selected" (eq $path "/auth/login")
|
"Selected" (eq $path "/auth/login")
|
||||||
"Content" "Log In"
|
"Content" "Log In"
|
||||||
@@ -231,7 +237,8 @@
|
|||||||
<a href="mailto:contact-us@inventory-plus-plus.com" class="p-[1em] font-display text-center">Contact Us</a>
|
<a href="mailto:contact-us@inventory-plus-plus.com" class="p-[1em] font-display text-center">Contact Us</a>
|
||||||
<a href="mailto:support@inventory-plus-plus.com" class="p-[1em] font-display text-center">Support</a>
|
<a href="mailto:support@inventory-plus-plus.com" class="p-[1em] font-display text-center">Support</a>
|
||||||
</address>
|
</address>
|
||||||
{{- if .Identity.Claims.Picture }}
|
|
||||||
|
{{- if (or .Identity.Claims.Picture .Identity.Claims.Name) }}
|
||||||
<button
|
<button
|
||||||
popovertarget="identity-popover"
|
popovertarget="identity-popover"
|
||||||
class="
|
class="
|
||||||
@@ -244,6 +251,7 @@
|
|||||||
items-center
|
items-center
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
{{ if .Identity.Claims.Picture }}
|
||||||
<img
|
<img
|
||||||
src="{{ .Identity.Claims.Picture }}"
|
src="{{ .Identity.Claims.Picture }}"
|
||||||
class="rounded-[50%]"
|
class="rounded-[50%]"
|
||||||
@@ -252,6 +260,12 @@
|
|||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
{{ else if .Identity.Claims.Name }}
|
||||||
|
{{ .Identity.Claims.Name }}
|
||||||
|
{{ else }}
|
||||||
|
User
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
popover="auto"
|
popover="auto"
|
||||||
@@ -308,7 +322,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
href="/api/auth/logout"
|
href="/api/auth/{{if $devAuthEnabled}}dev-logout{{else}}logout{{end}}"
|
||||||
hx-boost="false"
|
hx-boost="false"
|
||||||
class="
|
class="
|
||||||
w-fit
|
w-fit
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{{- $loggedIn := and (and .Identity .Identity.AccessToken) true -}}
|
{{- $loggedIn := and (and .Identity .Identity.AccessToken) true -}}
|
||||||
|
{{- $devAuthEnabled := .DevAuthEnabled }}
|
||||||
|
|
||||||
|
|
||||||
<section class="flex justify-center max-w-full mt-[3em] mb-[3em]">
|
<section class="flex justify-center max-w-full mt-[3em] mb-[3em]">
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
Create an Account
|
Create an Account
|
||||||
</a>
|
</a>
|
||||||
{{- else }}
|
{{- else }}
|
||||||
<a href="/api/auth/login" hx-boost="false" class="block p-[1em] font-bold">
|
<a href="/api/auth/{{if $devAuthEnabled}}dev-login{{else}}login{{end}}" hx-boost="false" class="block p-[1em] font-bold">
|
||||||
New Login
|
New Login
|
||||||
</a>
|
</a>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user