Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/tinyauth/generate_totp.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func generateTotpCmd() *cli.Command {
docker = true
}

if user.TotpSecret != "" {
if user.TOTPSecret != "" {
return fmt.Errorf("user already has a TOTP secret")
}

Expand Down Expand Up @@ -102,14 +102,14 @@ func generateTotpCmd() *cli.Command {

qrterminal.GenerateWithConfig(key.URL(), config)

user.TotpSecret = secret
user.TOTPSecret = secret

// If using docker escape re-escape it
if docker {
user.Password = strings.ReplaceAll(user.Password, "$", "$$")
}

tlog.App.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TotpSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")
tlog.App.Info().Str("user", fmt.Sprintf("%s:%s:%s", user.Username, user.Password, user.TOTPSecret)).Msg("Add the totp secret to your authenticator app then use the verify command to ensure everything is working correctly.")

return nil
},
Expand Down
8 changes: 4 additions & 4 deletions cmd/tinyauth/tinyauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (

"charm.land/huh/v2"
"github.com/tinyauthapp/tinyauth/internal/bootstrap"
"github.com/tinyauthapp/tinyauth/internal/config"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/utils/loaders"
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"

Expand All @@ -14,7 +14,7 @@ import (
)

func main() {
tConfig := config.NewDefaultConfiguration()
tConfig := model.NewDefaultConfiguration()

loaders := []cli.ResourceLoader{
&loaders.FileLoader{},
Expand Down Expand Up @@ -108,11 +108,11 @@ func main() {
}
}

func runCmd(cfg config.Config) error {
func runCmd(cfg model.Config) error {
logger := tlog.NewLogger(cfg.Log)
logger.Init()

tlog.App.Info().Str("version", config.Version).Msg("Starting tinyauth")
tlog.App.Info().Str("version", model.Version).Msg("Starting tinyauth")

app := bootstrap.NewBootstrapApp(cfg)

Expand Down
4 changes: 2 additions & 2 deletions cmd/tinyauth/verify_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ func verifyUserCmd() *cli.Command {
return fmt.Errorf("password is incorrect: %w", err)
}

if user.TotpSecret == "" {
if user.TOTPSecret == "" {
if tCfg.Totp != "" {
tlog.App.Warn().Msg("User does not have TOTP secret")
}
tlog.App.Info().Msg("User verified")
return nil
}

ok := totp.Validate(tCfg.Totp, user.TotpSecret)
ok := totp.Validate(tCfg.Totp, user.TOTPSecret)

if !ok {
return fmt.Errorf("TOTP code incorrect")
Expand Down
9 changes: 4 additions & 5 deletions cmd/tinyauth/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ package main
import (
"fmt"

"github.com/tinyauthapp/tinyauth/internal/config"

"github.com/tinyauthapp/paerser/cli"
"github.com/tinyauthapp/tinyauth/internal/model"
)

func versionCmd() *cli.Command {
Expand All @@ -15,9 +14,9 @@ func versionCmd() *cli.Command {
Configuration: nil,
Resources: nil,
Run: func(_ []string) error {
fmt.Printf("Version: %s\n", config.Version)
fmt.Printf("Commit Hash: %s\n", config.CommitHash)
fmt.Printf("Build Timestamp: %s\n", config.BuildTimestamp)
fmt.Printf("Version: %s\n", model.Version)
fmt.Printf("Commit Hash: %s\n", model.CommitHash)
fmt.Printf("Build Timestamp: %s\n", model.BuildTimestamp)
return nil
},
}
Expand Down
4 changes: 2 additions & 2 deletions gen/gen_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"reflect"
"strings"

"github.com/tinyauthapp/tinyauth/internal/config"
"github.com/tinyauthapp/tinyauth/internal/model"
)

type EnvEntry struct {
Expand All @@ -20,7 +20,7 @@ type EnvEntry struct {
}

func generateExampleEnv() {
cfg := config.NewDefaultConfiguration()
cfg := model.NewDefaultConfiguration()
entries := make([]EnvEntry, 0)

root := reflect.TypeOf(cfg).Elem()
Expand Down
4 changes: 2 additions & 2 deletions gen/gen_md.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"reflect"
"strings"

"github.com/tinyauthapp/tinyauth/internal/config"
"github.com/tinyauthapp/tinyauth/internal/model"
)

type MarkdownEntry struct {
Expand All @@ -21,7 +21,7 @@ type MarkdownEntry struct {
}

func generateMarkdown() {
cfg := config.NewDefaultConfiguration()
cfg := model.NewDefaultConfiguration()
entries := make([]MarkdownEntry, 0)

root := reflect.TypeOf(cfg).Elem()
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ require (
github.com/weppos/publicsuffix-go v0.50.3
golang.org/x/crypto v0.50.0
golang.org/x/oauth2 v0.36.0
gotest.tools/v3 v3.5.2
k8s.io/apimachinery v0.32.2
k8s.io/client-go v0.32.2
modernc.org/sqlite v1.49.1
Expand Down Expand Up @@ -133,6 +132,7 @@ require (
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.2 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
modernc.org/libc v1.72.0 // indirect
Expand Down
32 changes: 16 additions & 16 deletions internal/bootstrap/app_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import (
"strings"
"time"

"github.com/tinyauthapp/tinyauth/internal/config"
"github.com/tinyauthapp/tinyauth/internal/controller"
"github.com/tinyauthapp/tinyauth/internal/model"
"github.com/tinyauthapp/tinyauth/internal/repository"
"github.com/tinyauthapp/tinyauth/internal/utils"
"github.com/tinyauthapp/tinyauth/internal/utils/tlog"
)

type BootstrapApp struct {
config config.Config
config model.Config
context struct {
appUrl string
uuid string
Expand All @@ -29,15 +29,15 @@ type BootstrapApp struct {
csrfCookieName string
redirectCookieName string
oauthSessionCookieName string
users []config.User
oauthProviders map[string]config.OAuthServiceConfig
localUsers []model.LocalUser
oauthProviders map[string]model.OAuthServiceConfig
configuredProviders []controller.Provider
oidcClients []config.OIDCClientConfig
oidcClients []model.OIDCClientConfig
}
services Services
}

func NewBootstrapApp(config config.Config) *BootstrapApp {
func NewBootstrapApp(config model.Config) *BootstrapApp {
return &BootstrapApp{
config: config,
}
Expand Down Expand Up @@ -69,7 +69,7 @@ func (app *BootstrapApp) Setup() error {
return err
}

app.context.users = users
app.context.localUsers = *users

// Setup OAuth providers
app.context.oauthProviders = app.config.OAuth.Providers
Expand All @@ -88,7 +88,7 @@ func (app *BootstrapApp) Setup() error {

for id, provider := range app.context.oauthProviders {
if provider.Name == "" {
if name, ok := config.OverrideProviders[id]; ok {
if name, ok := model.OverrideProviders[id]; ok {
provider.Name = name
} else {
provider.Name = utils.Capitalize(id)
Expand All @@ -115,14 +115,14 @@ func (app *BootstrapApp) Setup() error {
// Cookie names
app.context.uuid = utils.GenerateUUID(appUrl.Hostname())
cookieId := strings.Split(app.context.uuid, "-")[0]
app.context.sessionCookieName = fmt.Sprintf("%s-%s", config.SessionCookieName, cookieId)
app.context.csrfCookieName = fmt.Sprintf("%s-%s", config.CSRFCookieName, cookieId)
app.context.redirectCookieName = fmt.Sprintf("%s-%s", config.RedirectCookieName, cookieId)
app.context.oauthSessionCookieName = fmt.Sprintf("%s-%s", config.OAuthSessionCookieName, cookieId)
app.context.sessionCookieName = fmt.Sprintf("%s-%s", model.SessionCookieName, cookieId)
app.context.csrfCookieName = fmt.Sprintf("%s-%s", model.CSRFCookieName, cookieId)
app.context.redirectCookieName = fmt.Sprintf("%s-%s", model.RedirectCookieName, cookieId)
app.context.oauthSessionCookieName = fmt.Sprintf("%s-%s", model.OAuthSessionCookieName, cookieId)

// Dumps
tlog.App.Trace().Interface("config", app.config).Msg("Config dump")
tlog.App.Trace().Interface("users", app.context.users).Msg("Users dump")
tlog.App.Trace().Interface("users", app.context.localUsers).Msg("Users dump")
tlog.App.Trace().Interface("oauthProviders", app.context.oauthProviders).Msg("OAuth providers dump")
Comment on lines 123 to 126
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't trace raw local user records.

app.context.localUsers contains credential material, so this dump will leak password hashes and TOTP secrets into logs. Please log counts or usernames only here.

Suggested change
  tlog.App.Trace().Interface("config", app.config).Msg("Config dump")
- tlog.App.Trace().Interface("users", app.context.localUsers).Msg("Users dump")
+ tlog.App.Trace().Int("userCount", len(app.context.localUsers)).Msg("Loaded local users")
  tlog.App.Trace().Interface("oauthProviders", app.context.oauthProviders).Msg("OAuth providers dump")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Dumps
tlog.App.Trace().Interface("config", app.config).Msg("Config dump")
tlog.App.Trace().Interface("users", app.context.users).Msg("Users dump")
tlog.App.Trace().Interface("users", app.context.localUsers).Msg("Users dump")
tlog.App.Trace().Interface("oauthProviders", app.context.oauthProviders).Msg("OAuth providers dump")
// Dumps
tlog.App.Trace().Interface("config", app.config).Msg("Config dump")
tlog.App.Trace().Int("userCount", len(app.context.localUsers)).Msg("Loaded local users")
tlog.App.Trace().Interface("oauthProviders", app.context.oauthProviders).Msg("OAuth providers dump")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/bootstrap/app_bootstrap.go` around lines 123 - 126, The trace call
currently logging raw user records via tlog.App.Trace().Interface("users",
app.context.localUsers) leaks credential material; change the dump to avoid
sensitive fields by logging only safe metadata (e.g., user count and a list of
usernames or IDs) instead of the full app.context.localUsers structure — replace
the Interface call with one that computes and logs something like
len(app.context.localUsers) and a slice/map of usernames (or IDs) extracted from
each user record, ensuring no password hashes, TOTP secrets, or other
credentials are included.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dump is on purpose to help debug Tinyauth.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@steveiliop56, understood — if it's an intentional debug aid, that's your call. Just worth keeping in mind that TRACE-level logging is sometimes accidentally enabled in production environments (e.g. via a misconfigured env var), which would expose password hashes and TOTP secrets in log output. If you'd like a middle-ground in the future, logging usernames only (no credentials) would still give plenty of debugging signal while reducing that risk. No changes needed from your side!

tlog.App.Trace().Str("cookieDomain", app.context.cookieDomain).Msg("Cookie domain")
tlog.App.Trace().Str("sessionCookieName", app.context.sessionCookieName).Msg("Session cookie name")
Expand Down Expand Up @@ -171,7 +171,7 @@ func (app *BootstrapApp) Setup() error {
})
}

if services.authService.LdapAuthConfigured() {
if services.authService.LDAPAuthConfigured() {
configuredProviders = append(configuredProviders, controller.Provider{
Name: "LDAP",
ID: "ldap",
Expand Down Expand Up @@ -244,7 +244,7 @@ func (app *BootstrapApp) heartbeatRoutine() {
var body heartbeat

body.UUID = app.context.uuid
body.Version = config.Version
body.Version = model.Version

bodyJson, err := json.Marshal(body)

Expand All @@ -257,7 +257,7 @@ func (app *BootstrapApp) heartbeatRoutine() {
Timeout: 30 * time.Second, // The server should never take more than 30 seconds to respond
}

heartbeatURL := config.ApiServer + "/v1/instances/heartbeat"
heartbeatURL := model.APIServer + "/v1/instances/heartbeat"

for range ticker.C {
tlog.App.Debug().Msg("Sending heartbeat")
Expand Down
10 changes: 6 additions & 4 deletions internal/bootstrap/router_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import (
"fmt"
"slices"

"github.com/tinyauthapp/tinyauth/internal/config"
"github.com/tinyauthapp/tinyauth/internal/controller"
"github.com/tinyauthapp/tinyauth/internal/middleware"
"github.com/tinyauthapp/tinyauth/internal/model"

"github.com/gin-gonic/gin"
)

var DEV_MODES = []string{"main", "test", "development"}

func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
if !slices.Contains(DEV_MODES, config.Version) {
if !slices.Contains(DEV_MODES, model.Version) {
gin.SetMode(gin.ReleaseMode)
}

Expand All @@ -30,7 +30,8 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
}

contextMiddleware := middleware.NewContextMiddleware(middleware.ContextMiddlewareConfig{
CookieDomain: app.context.cookieDomain,
CookieDomain: app.context.cookieDomain,
SessionCookieName: app.context.sessionCookieName,
}, app.services.authService, app.services.oauthBrokerService)

err := contextMiddleware.Init()
Expand Down Expand Up @@ -98,7 +99,8 @@ func (app *BootstrapApp) setupRouter() (*gin.Engine, error) {
proxyController.SetupRoutes()

userController := controller.NewUserController(controller.UserControllerConfig{
CookieDomain: app.context.cookieDomain,
CookieDomain: app.context.cookieDomain,
SessionCookieName: app.context.sessionCookieName,
}, apiRouter, app.services.authService)
Comment on lines 101 to 104
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

SessionCookieName is incorrectly used as a redirect URL in UserController.logoutHandler.

The context snippet from internal/controller/user_controller.go:229-235 shows:

if err != nil {
    c.Redirect(http.StatusFound, controller.config.SessionCookieName)
    return
}

controller.config.SessionCookieName holds a cookie identifier string (e.g., "tinyauth_session"), not a URL. Passing it as gin's redirect location sets the Location header to that literal string, producing a broken redirect on every unauthenticated logout request.

It should redirect to a real path (e.g., "/" or "/login"):

-    c.Redirect(http.StatusFound, controller.config.SessionCookieName)
+    c.Redirect(http.StatusFound, "/")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/bootstrap/router_bootstrap.go` around lines 101 - 104, The
logoutHandler in UserController is redirecting to
controller.config.SessionCookieName (a cookie name) instead of a URL; update
UserController.logoutHandler to use a real path such as "/" or "/login" for
c.Redirect, and keep SessionCookieName only for cookie operations. Locate the
UserController.logoutHandler (referenced by UserController and
controller.config.SessionCookieName) and replace the redirect target with the
chosen route (e.g., "/" or "/login") while leaving cookie clearing logic
unchanged; ensure any tests or callers expecting the previous behavior are
updated accordingly.


userController.SetupRoutes()
Expand Down
20 changes: 10 additions & 10 deletions internal/bootstrap/service_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
services := Services{}

ldapService := service.NewLdapService(service.LdapServiceConfig{
Address: app.config.Ldap.Address,
BindDN: app.config.Ldap.BindDN,
BindPassword: app.config.Ldap.BindPassword,
BaseDN: app.config.Ldap.BaseDN,
Insecure: app.config.Ldap.Insecure,
SearchFilter: app.config.Ldap.SearchFilter,
AuthCert: app.config.Ldap.AuthCert,
AuthKey: app.config.Ldap.AuthKey,
Address: app.config.LDAP.Address,
BindDN: app.config.LDAP.BindDN,
BindPassword: app.config.LDAP.BindPassword,
BaseDN: app.config.LDAP.BaseDN,
Insecure: app.config.LDAP.Insecure,
SearchFilter: app.config.LDAP.SearchFilter,
AuthCert: app.config.LDAP.AuthCert,
AuthKey: app.config.LDAP.AuthKey,
})

err := ldapService.Init()
Expand Down Expand Up @@ -89,7 +89,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
services.oauthBrokerService = oauthBrokerService

authService := service.NewAuthService(service.AuthServiceConfig{
Users: app.context.users,
LocalUsers: app.context.localUsers,
OauthWhitelist: app.config.OAuth.Whitelist,
SessionExpiry: app.config.Auth.SessionExpiry,
SessionMaxLifetime: app.config.Auth.SessionMaxLifetime,
Expand All @@ -99,7 +99,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
LoginMaxRetries: app.config.Auth.LoginMaxRetries,
SessionCookieName: app.context.sessionCookieName,
IP: app.config.Auth.IP,
LDAPGroupsCacheTTL: app.config.Ldap.GroupCacheTTL,
LDAPGroupsCacheTTL: app.config.LDAP.GroupCacheTTL,
}, services.ldapService, queries, services.oauthBrokerService)

err = authService.Init()
Expand Down
Loading
Loading