Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions .zed/debug.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"label": "Attach to remote Delve",
"adapter": "Delve",
"mode": "remote",
"remotePath": "/tinyauth",
"request": "attach",
"tcp_connection": {
"host": "127.0.0.1",
"port": 4000,
},
},
]
Comment thread
steveiliop56 marked this conversation as resolved.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,8 @@ develop:
# Production
prod:
docker compose -f $(PROD_COMPOSE) up --force-recreate --pull=always --remove-orphans

# SQL
.PHONY: sql
sql:
sqlc generate
11 changes: 6 additions & 5 deletions cmd/tinyauth/tinyauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ func NewTinyauthCmdConfiguration() *config.Config {
Address: "0.0.0.0",
},
Auth: config.AuthConfig{
SessionExpiry: 3600,
SessionMaxLifetime: 0,
LoginTimeout: 300,
SessionExpiry: 86400, // 1 day
SessionMaxLifetime: 0, // disabled
LoginTimeout: 300, // 5 minutes
LoginMaxRetries: 3,
},
UI: config.UIConfig{
Expand All @@ -32,8 +32,9 @@ func NewTinyauthCmdConfiguration() *config.Config {
BackgroundImage: "/background.jpg",
},
Ldap: config.LdapConfig{
Insecure: false,
SearchFilter: "(uid=%s)",
Insecure: false,
SearchFilter: "(uid=%s)",
GroupCacheTTL: 900, // 15 minutes
},
Log: config.LogConfig{
Level: "info",
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/pages/login-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ export const LoginPage = () => {
const redirectUri = searchParams.get("redirect_uri");

const oauthProviders = providers.filter(
(provider) => provider.id !== "username",
(provider) => provider.id !== "local" && provider.id !== "ldap",
);
const userAuthConfigured =
providers.find((provider) => provider.id === "username") !== undefined;
providers.find(
(provider) => provider.id === "local" || provider.id === "ldap",
) !== undefined;

const oauthMutation = useMutation({
mutationFn: (provider: string) =>
Expand Down
14 changes: 11 additions & 3 deletions internal/bootstrap/app_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,18 @@ func (app *BootstrapApp) Setup() error {
return configuredProviders[i].Name < configuredProviders[j].Name
})

if services.authService.UserAuthConfigured() {
if services.authService.LocalAuthConfigured() {
configuredProviders = append(configuredProviders, controller.Provider{
Name: "Username",
ID: "username",
Name: "Local",
ID: "local",
OAuth: false,
})
}

if services.authService.LdapAuthConfigured() {
configuredProviders = append(configuredProviders, controller.Provider{
Name: "LDAP",
ID: "ldap",
OAuth: false,
})
}
Expand Down
1 change: 1 addition & 0 deletions internal/bootstrap/service_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,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,
}, dockerService, services.ldapService, queries)

err = authService.Init()
Expand Down
41 changes: 21 additions & 20 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,15 @@ type UIConfig struct {
}

type LdapConfig struct {
Address string `description:"LDAP server address." yaml:"address"`
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn"`
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword"`
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn"`
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure"`
SearchFilter string `description:"LDAP search filter." yaml:"searchFilter"`
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert"`
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey"`
Address string `description:"LDAP server address." yaml:"address"`
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn"`
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword"`
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn"`
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure"`
SearchFilter string `description:"LDAP search filter." yaml:"searchFilter"`
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert"`
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey"`
GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL"`
}

type LogConfig struct {
Expand Down Expand Up @@ -138,35 +139,30 @@ type User struct {
TotpSecret string
}

type LdapUser struct {
DN string
Groups []string
}

type UserSearch struct {
Username string
Type string // local, ldap or unknown
}

type SessionCookie struct {
UUID string
Username string
Name string
Email string
Provider string
TotpPending bool
OAuthGroups string
OAuthName string
OAuthSub string
}

type UserContext struct {
Username string
Name string
Email string
IsLoggedIn bool
IsBasicAuth bool
OAuth bool
Provider string
TotpPending bool
OAuthGroups string
TotpEnabled bool
OAuthName string
OAuthSub string
LdapGroups string
}

// API responses and queries
Expand Down Expand Up @@ -195,6 +191,7 @@ type App struct {
IP AppIP `description:"IP access configuration." yaml:"ip"`
Response AppResponse `description:"Response customization." yaml:"response"`
Path AppPath `description:"Path access configuration." yaml:"path"`
LDAP AppLDAP `description:"LDAP access configuration." yaml:"ldap"`
}

type AppConfig struct {
Expand All @@ -211,6 +208,10 @@ type AppOAuth struct {
Groups string `description:"Comma-separated list of required OAuth groups." yaml:"groups"`
}

type AppLDAP struct {
Groups string `description:"Comma-separated list of required LDAP groups." yaml:"groups"`
}

type AppIP struct {
Allow []string `description:"List of allowed IPs or CIDR ranges." yaml:"allow"`
Block []string `description:"List of blocked IPs or CIDR ranges." yaml:"block"`
Expand Down
2 changes: 0 additions & 2 deletions internal/controller/context_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ type UserContextResponse struct {
OAuth bool `json:"oauth"`
TotpPending bool `json:"totpPending"`
OAuthName string `json:"oauthName"`
OAuthSub string `json:"oauthSub"`
}

type AppContextResponse struct {
Expand Down Expand Up @@ -90,7 +89,6 @@ func (controller *ContextController) userContextHandler(c *gin.Context) {
OAuth: context.OAuth,
TotpPending: context.TotpPending,
OAuthName: context.OAuthName,
OAuthSub: context.OAuthSub,
}

if err != nil {
Expand Down
7 changes: 4 additions & 3 deletions internal/controller/context_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (
var controllerCfg = controller.ContextControllerConfig{
Providers: []controller.Provider{
{
Name: "Username",
ID: "username",
Name: "Local",
ID: "local",
OAuth: false,
},
{
Expand All @@ -40,8 +40,9 @@ var userContext = config.UserContext{
Name: "testuser",
Email: "test@example.com",
IsLoggedIn: true,
IsBasicAuth: false,
OAuth: false,
Provider: "username",
Provider: "local",
TotpPending: false,
OAuthGroups: "",
TotpEnabled: false,
Expand Down
5 changes: 3 additions & 2 deletions internal/controller/oauth_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/repository"
"github.com/steveiliop56/tinyauth/internal/service"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
Expand Down Expand Up @@ -188,10 +189,10 @@ func (controller *OAuthController) oauthCallbackHandler(c *gin.Context) {
username = user.PreferredUsername
} else {
tlog.App.Debug().Msg("No preferred username from OAuth provider, using pseudo username")
username = strings.Replace(user.Email, "@", "_", -1)
username = strings.Replace(user.Email, "@", "_", 1)
}

sessionCookie := config.SessionCookie{
sessionCookie := repository.Session{
Username: username,
Name: name,
Email: user.Email,
Expand Down
22 changes: 17 additions & 5 deletions internal/controller/proxy_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {

tlog.App.Trace().Interface("context", userContext).Msg("User context from request")

if userContext.Provider == "basic" && userContext.TotpEnabled {
if userContext.IsBasicAuth && userContext.TotpEnabled {
tlog.App.Debug().Msg("User has TOTP enabled, denying basic auth access")
userContext.IsLoggedIn = false
}
Expand Down Expand Up @@ -212,11 +212,17 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
return
}

if userContext.OAuth {
groupOK := controller.auth.IsInOAuthGroup(c, userContext, acls.OAuth.Groups)
if userContext.OAuth || userContext.Provider == "ldap" {
var groupOK bool

if userContext.OAuth {
groupOK = controller.auth.IsInOAuthGroup(c, userContext, acls.OAuth.Groups)
} else {
groupOK = controller.auth.IsInLdapGroup(c, userContext, acls.LDAP.Groups)
}

if !groupOK {
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User OAuth groups do not match resource requirements")
tlog.App.Warn().Str("user", userContext.Username).Str("resource", strings.Split(host, ".")[0]).Msg("User groups do not match resource requirements")

if req.Proxy == "nginx" || !isBrowser {
c.JSON(403, gin.H{
Expand Down Expand Up @@ -251,7 +257,13 @@ func (controller *ProxyController) proxyHandler(c *gin.Context) {
c.Header("Remote-User", utils.SanitizeHeader(userContext.Username))
c.Header("Remote-Name", utils.SanitizeHeader(userContext.Name))
c.Header("Remote-Email", utils.SanitizeHeader(userContext.Email))
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))

if userContext.Provider == "ldap" {
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.LdapGroups))
} else if userContext.Provider != "local" {
c.Header("Remote-Groups", utils.SanitizeHeader(userContext.OAuthGroups))
}

c.Header("Remote-Sub", utils.SanitizeHeader(userContext.OAuthSub))

controller.setHeaders(c, acls)
Expand Down
9 changes: 5 additions & 4 deletions internal/controller/proxy_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,11 @@ func TestProxyHandler(t *testing.T) {
// Test logged in user
c := gin.CreateTestContextOnly(recorder, router)

err := authService.CreateSessionCookie(c, &config.SessionCookie{
err := authService.CreateSessionCookie(c, &repository.Session{
Username: "testuser",
Name: "testuser",
Email: "testuser@example.com",
Provider: "username",
Provider: "local",
TotpPending: false,
OAuthGroups: "",
})
Expand All @@ -164,7 +164,7 @@ func TestProxyHandler(t *testing.T) {
Email: "testuser@example.com",
IsLoggedIn: true,
OAuth: false,
Provider: "username",
Provider: "local",
TotpPending: false,
OAuthGroups: "",
TotpEnabled: false,
Expand Down Expand Up @@ -192,8 +192,9 @@ func TestProxyHandler(t *testing.T) {
Name: "testuser",
Email: "testuser@example.com",
IsLoggedIn: true,
IsBasicAuth: true,
OAuth: false,
Provider: "basic",
Provider: "local",
TotpPending: false,
OAuthGroups: "",
TotpEnabled: true,
Expand Down
18 changes: 11 additions & 7 deletions internal/controller/user_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"strings"
"time"

"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/repository"
"github.com/steveiliop56/tinyauth/internal/service"
"github.com/steveiliop56/tinyauth/internal/utils"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
Expand Down Expand Up @@ -112,11 +112,11 @@ func (controller *UserController) loginHandler(c *gin.Context) {
if user.TotpSecret != "" {
tlog.App.Debug().Str("username", req.Username).Msg("User has TOTP enabled, requiring TOTP verification")

err := controller.auth.CreateSessionCookie(c, &config.SessionCookie{
err := controller.auth.CreateSessionCookie(c, &repository.Session{
Username: user.Username,
Name: utils.Capitalize(req.Username),
Email: fmt.Sprintf("%s@%s", strings.ToLower(req.Username), controller.config.CookieDomain),
Provider: "username",
Provider: "local",
TotpPending: true,
})

Expand All @@ -138,11 +138,15 @@ func (controller *UserController) loginHandler(c *gin.Context) {
}
}

sessionCookie := config.SessionCookie{
sessionCookie := repository.Session{
Username: req.Username,
Name: utils.Capitalize(req.Username),
Email: fmt.Sprintf("%s@%s", strings.ToLower(req.Username), controller.config.CookieDomain),
Provider: "username",
Provider: "local",
}

if userSearch.Type == "ldap" {
sessionCookie.Provider = "ldap"
}

tlog.App.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
Expand Down Expand Up @@ -248,11 +252,11 @@ func (controller *UserController) totpHandler(c *gin.Context) {

controller.auth.RecordLoginAttempt(context.Username, true)

sessionCookie := config.SessionCookie{
sessionCookie := repository.Session{
Username: user.Username,
Name: utils.Capitalize(user.Username),
Email: fmt.Sprintf("%s@%s", strings.ToLower(user.Username), controller.config.CookieDomain),
Provider: "username",
Provider: "local",
}

tlog.App.Trace().Interface("session_cookie", sessionCookie).Msg("Creating session cookie")
Expand Down
6 changes: 3 additions & 3 deletions internal/controller/user_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func TestTotpHandler(t *testing.T) {
Email: "totpuser@example.com",
IsLoggedIn: false,
OAuth: false,
Provider: "username",
Provider: "local",
TotpPending: true,
OAuthGroups: "",
TotpEnabled: true,
Expand Down Expand Up @@ -267,7 +267,7 @@ func TestTotpHandler(t *testing.T) {
Email: "totpuser@example.com",
IsLoggedIn: false,
OAuth: false,
Provider: "username",
Provider: "local",
TotpPending: true,
OAuthGroups: "",
TotpEnabled: true,
Expand All @@ -290,7 +290,7 @@ func TestTotpHandler(t *testing.T) {
Email: "totpuser@example.com",
IsLoggedIn: false,
OAuth: false,
Provider: "username",
Provider: "local",
TotpPending: false,
OAuthGroups: "",
TotpEnabled: false,
Expand Down
Loading