mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
refactor: use centralized config in auth service
This commit is contained in:
11
cmd/root.go
11
cmd/root.go
@@ -113,6 +113,15 @@ var rootCmd = &cobra.Command{
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create auth config
|
||||||
|
authConfig := types.AuthConfig{
|
||||||
|
Users: users,
|
||||||
|
OauthWhitelist: oauthWhitelist,
|
||||||
|
SessionExpiry: config.SessionExpiry,
|
||||||
|
LoginTimeout: config.LoginTimeout,
|
||||||
|
LoginMaxRetries: config.LoginMaxRetries,
|
||||||
|
}
|
||||||
|
|
||||||
// Create docker service
|
// Create docker service
|
||||||
docker := docker.NewDocker()
|
docker := docker.NewDocker()
|
||||||
|
|
||||||
@@ -121,7 +130,7 @@ var rootCmd = &cobra.Command{
|
|||||||
HandleError(err, "Failed to initialize docker")
|
HandleError(err, "Failed to initialize docker")
|
||||||
|
|
||||||
// Create auth service
|
// Create auth service
|
||||||
auth := auth.NewAuth(docker, users, oauthWhitelist, config.SessionExpiry, config.LoginTimeout, config.LoginMaxRetries)
|
auth := auth.NewAuth(authConfig, docker)
|
||||||
|
|
||||||
// Create OAuth providers service
|
// Create OAuth providers service
|
||||||
providers := providers.NewProviders(oauthConfig)
|
providers := providers.NewProviders(oauthConfig)
|
||||||
|
|||||||
@@ -38,6 +38,15 @@ var handlersConfig = types.HandlersConfig{
|
|||||||
GenericName: "Generic",
|
GenericName: "Generic",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple auth config for tests
|
||||||
|
var authConfig = types.AuthConfig{
|
||||||
|
Users: types.Users{},
|
||||||
|
OauthWhitelist: []string{},
|
||||||
|
SessionExpiry: 3600,
|
||||||
|
LoginTimeout: 0,
|
||||||
|
LoginMaxRetries: 0,
|
||||||
|
}
|
||||||
|
|
||||||
// Cookie
|
// Cookie
|
||||||
var cookie string
|
var cookie string
|
||||||
|
|
||||||
@@ -61,12 +70,13 @@ func getAPI(t *testing.T) *api.API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create auth service
|
// Create auth service
|
||||||
auth := auth.NewAuth(docker, types.Users{
|
authConfig.Users = types.Users{
|
||||||
{
|
{
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
},
|
},
|
||||||
}, nil, apiConfig.SessionExpiry, 300, 5)
|
}
|
||||||
|
auth := auth.NewAuth(authConfig, docker)
|
||||||
|
|
||||||
// Create providers service
|
// Create providers service
|
||||||
providers := providers.NewProviders(types.OAuthConfig{})
|
providers := providers.NewProviders(types.OAuthConfig{})
|
||||||
|
|||||||
@@ -15,39 +15,24 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewAuth(docker *docker.Docker, userList types.Users, oauthWhitelist []string, sessionExpiry int, loginTimeout int, loginMaxRetries int) *Auth {
|
func NewAuth(config types.AuthConfig, docker *docker.Docker) *Auth {
|
||||||
return &Auth{
|
return &Auth{
|
||||||
Docker: docker,
|
Config: config,
|
||||||
Users: userList,
|
Docker: docker,
|
||||||
OAuthWhitelist: oauthWhitelist,
|
LoginAttempts: make(map[string]*types.LoginAttempt),
|
||||||
SessionExpiry: sessionExpiry,
|
|
||||||
LoginTimeout: loginTimeout,
|
|
||||||
LoginMaxRetries: loginMaxRetries,
|
|
||||||
LoginAttempts: make(map[string]*LoginAttempt),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginAttempt tracks information about login attempts for rate limiting
|
|
||||||
type LoginAttempt struct {
|
|
||||||
FailedAttempts int
|
|
||||||
LastAttempt time.Time
|
|
||||||
LockedUntil time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
Users types.Users
|
Config types.AuthConfig
|
||||||
Docker *docker.Docker
|
Docker *docker.Docker
|
||||||
OAuthWhitelist []string
|
LoginAttempts map[string]*types.LoginAttempt
|
||||||
SessionExpiry int
|
LoginMutex sync.RWMutex
|
||||||
LoginTimeout int
|
|
||||||
LoginMaxRetries int
|
|
||||||
LoginAttempts map[string]*LoginAttempt // Map of username/IP to login attempts
|
|
||||||
LoginMutex sync.RWMutex // Mutex to protect the LoginAttempts map
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) GetUser(username string) *types.User {
|
func (auth *Auth) GetUser(username string) *types.User {
|
||||||
// Loop through users and return the user if the username matches
|
// Loop through users and return the user if the username matches
|
||||||
for _, user := range auth.Users {
|
for _, user := range auth.Config.Users {
|
||||||
if user.Username == username {
|
if user.Username == username {
|
||||||
return &user
|
return &user
|
||||||
}
|
}
|
||||||
@@ -64,25 +49,25 @@ func (auth *Auth) CheckPassword(user types.User, password string) bool {
|
|||||||
func (auth *Auth) IsAccountLocked(identifier string) (bool, int) {
|
func (auth *Auth) IsAccountLocked(identifier string) (bool, int) {
|
||||||
auth.LoginMutex.RLock()
|
auth.LoginMutex.RLock()
|
||||||
defer auth.LoginMutex.RUnlock()
|
defer auth.LoginMutex.RUnlock()
|
||||||
|
|
||||||
// Return false if rate limiting is not configured
|
// Return false if rate limiting is not configured
|
||||||
if auth.LoginMaxRetries <= 0 || auth.LoginTimeout <= 0 {
|
if auth.Config.LoginMaxRetries <= 0 || auth.Config.LoginTimeout <= 0 {
|
||||||
return false, 0
|
return false, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the identifier exists in the map
|
// Check if the identifier exists in the map
|
||||||
attempt, exists := auth.LoginAttempts[identifier]
|
attempt, exists := auth.LoginAttempts[identifier]
|
||||||
if !exists {
|
if !exists {
|
||||||
return false, 0
|
return false, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// If account is locked, check if lock time has expired
|
// If account is locked, check if lock time has expired
|
||||||
if attempt.LockedUntil.After(time.Now()) {
|
if attempt.LockedUntil.After(time.Now()) {
|
||||||
// Calculate remaining lockout time in seconds
|
// Calculate remaining lockout time in seconds
|
||||||
remaining := int(time.Until(attempt.LockedUntil).Seconds())
|
remaining := int(time.Until(attempt.LockedUntil).Seconds())
|
||||||
return true, remaining
|
return true, remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock has expired
|
// Lock has expired
|
||||||
return false, 0
|
return false, 0
|
||||||
}
|
}
|
||||||
@@ -90,48 +75,48 @@ func (auth *Auth) IsAccountLocked(identifier string) (bool, int) {
|
|||||||
// RecordLoginAttempt records a login attempt for rate limiting
|
// RecordLoginAttempt records a login attempt for rate limiting
|
||||||
func (auth *Auth) RecordLoginAttempt(identifier string, success bool) {
|
func (auth *Auth) RecordLoginAttempt(identifier string, success bool) {
|
||||||
// Skip if rate limiting is not configured
|
// Skip if rate limiting is not configured
|
||||||
if auth.LoginMaxRetries <= 0 || auth.LoginTimeout <= 0 {
|
if auth.Config.LoginMaxRetries <= 0 || auth.Config.LoginTimeout <= 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
auth.LoginMutex.Lock()
|
auth.LoginMutex.Lock()
|
||||||
defer auth.LoginMutex.Unlock()
|
defer auth.LoginMutex.Unlock()
|
||||||
|
|
||||||
// Get current attempt record or create a new one
|
// Get current attempt record or create a new one
|
||||||
attempt, exists := auth.LoginAttempts[identifier]
|
attempt, exists := auth.LoginAttempts[identifier]
|
||||||
if !exists {
|
if !exists {
|
||||||
attempt = &LoginAttempt{}
|
attempt = &types.LoginAttempt{}
|
||||||
auth.LoginAttempts[identifier] = attempt
|
auth.LoginAttempts[identifier] = attempt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update last attempt time
|
// Update last attempt time
|
||||||
attempt.LastAttempt = time.Now()
|
attempt.LastAttempt = time.Now()
|
||||||
|
|
||||||
// If successful login, reset failed attempts
|
// If successful login, reset failed attempts
|
||||||
if success {
|
if success {
|
||||||
attempt.FailedAttempts = 0
|
attempt.FailedAttempts = 0
|
||||||
attempt.LockedUntil = time.Time{} // Reset lock time
|
attempt.LockedUntil = time.Time{} // Reset lock time
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment failed attempts
|
// Increment failed attempts
|
||||||
attempt.FailedAttempts++
|
attempt.FailedAttempts++
|
||||||
|
|
||||||
// If max retries reached, lock the account
|
// If max retries reached, lock the account
|
||||||
if attempt.FailedAttempts >= auth.LoginMaxRetries {
|
if attempt.FailedAttempts >= auth.Config.LoginMaxRetries {
|
||||||
attempt.LockedUntil = time.Now().Add(time.Duration(auth.LoginTimeout) * time.Second)
|
attempt.LockedUntil = time.Now().Add(time.Duration(auth.Config.LoginTimeout) * time.Second)
|
||||||
log.Warn().Str("identifier", identifier).Int("timeout", auth.LoginTimeout).Msg("Account locked due to too many failed login attempts")
|
log.Warn().Str("identifier", identifier).Int("timeout", auth.Config.LoginTimeout).Msg("Account locked due to too many failed login attempts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) EmailWhitelisted(emailSrc string) bool {
|
func (auth *Auth) EmailWhitelisted(emailSrc string) bool {
|
||||||
// If the whitelist is empty, allow all emails
|
// If the whitelist is empty, allow all emails
|
||||||
if len(auth.OAuthWhitelist) == 0 {
|
if len(auth.Config.OauthWhitelist) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop through the whitelist and return true if the email matches
|
// Loop through the whitelist and return true if the email matches
|
||||||
for _, email := range auth.OAuthWhitelist {
|
for _, email := range auth.Config.OauthWhitelist {
|
||||||
if email == emailSrc {
|
if email == emailSrc {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -155,7 +140,7 @@ func (auth *Auth) CreateSessionCookie(c *gin.Context, data *types.SessionCookie)
|
|||||||
if data.TotpPending {
|
if data.TotpPending {
|
||||||
sessionExpiry = 3600
|
sessionExpiry = 3600
|
||||||
} else {
|
} else {
|
||||||
sessionExpiry = auth.SessionExpiry
|
sessionExpiry = auth.Config.SessionExpiry
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set data
|
// Set data
|
||||||
@@ -228,7 +213,7 @@ func (auth *Auth) GetSessionCookie(c *gin.Context) types.SessionCookie {
|
|||||||
|
|
||||||
func (auth *Auth) UserAuthConfigured() bool {
|
func (auth *Auth) UserAuthConfigured() bool {
|
||||||
// If there are users, return true
|
// If there are users, return true
|
||||||
return len(auth.Users) > 0
|
return len(auth.Config.Users) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext) (bool, error) {
|
func (auth *Auth) ResourceAllowed(c *gin.Context, context types.UserContext) (bool, error) {
|
||||||
|
|||||||
@@ -8,26 +8,38 @@ import (
|
|||||||
"tinyauth/internal/types"
|
"tinyauth/internal/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var config = types.AuthConfig{
|
||||||
|
Users: types.Users{},
|
||||||
|
OauthWhitelist: []string{},
|
||||||
|
SessionExpiry: 3600,
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoginRateLimiting(t *testing.T) {
|
func TestLoginRateLimiting(t *testing.T) {
|
||||||
// Initialize a new auth service with 3 max retries and 5 seconds timeout
|
// Initialize a new auth service with 3 max retries and 5 seconds timeout
|
||||||
authService := auth.NewAuth(&docker.Docker{}, types.Users{}, []string{}, 3600, 5, 3)
|
config.LoginMaxRetries = 3
|
||||||
|
config.LoginTimeout = 5
|
||||||
|
authService := auth.NewAuth(config, &docker.Docker{})
|
||||||
|
|
||||||
// Test identifier
|
// Test identifier
|
||||||
identifier := "test_user"
|
identifier := "test_user"
|
||||||
|
|
||||||
// Test successful login - should not lock account
|
// Test successful login - should not lock account
|
||||||
t.Log("Testing successful login")
|
t.Log("Testing successful login")
|
||||||
|
|
||||||
authService.RecordLoginAttempt(identifier, true)
|
authService.RecordLoginAttempt(identifier, true)
|
||||||
locked, _ := authService.IsAccountLocked(identifier)
|
locked, _ := authService.IsAccountLocked(identifier)
|
||||||
|
|
||||||
if locked {
|
if locked {
|
||||||
t.Fatalf("Account should not be locked after successful login")
|
t.Fatalf("Account should not be locked after successful login")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test 2 failed attempts - should not lock account yet
|
// Test 2 failed attempts - should not lock account yet
|
||||||
t.Log("Testing 2 failed login attempts")
|
t.Log("Testing 2 failed login attempts")
|
||||||
|
|
||||||
authService.RecordLoginAttempt(identifier, false)
|
authService.RecordLoginAttempt(identifier, false)
|
||||||
authService.RecordLoginAttempt(identifier, false)
|
authService.RecordLoginAttempt(identifier, false)
|
||||||
locked, _ = authService.IsAccountLocked(identifier)
|
locked, _ = authService.IsAccountLocked(identifier)
|
||||||
|
|
||||||
if locked {
|
if locked {
|
||||||
t.Fatalf("Account should not be locked after only 2 failed attempts")
|
t.Fatalf("Account should not be locked after only 2 failed attempts")
|
||||||
}
|
}
|
||||||
@@ -36,6 +48,7 @@ func TestLoginRateLimiting(t *testing.T) {
|
|||||||
t.Log("Testing 3 failed login attempts")
|
t.Log("Testing 3 failed login attempts")
|
||||||
authService.RecordLoginAttempt(identifier, false)
|
authService.RecordLoginAttempt(identifier, false)
|
||||||
locked, remainingTime := authService.IsAccountLocked(identifier)
|
locked, remainingTime := authService.IsAccountLocked(identifier)
|
||||||
|
|
||||||
if !locked {
|
if !locked {
|
||||||
t.Fatalf("Account should be locked after reaching max retries")
|
t.Fatalf("Account should be locked after reaching max retries")
|
||||||
}
|
}
|
||||||
@@ -45,32 +58,42 @@ func TestLoginRateLimiting(t *testing.T) {
|
|||||||
|
|
||||||
// Test reset after waiting for timeout - use 1 second timeout for fast testing
|
// Test reset after waiting for timeout - use 1 second timeout for fast testing
|
||||||
t.Log("Testing unlocking after timeout")
|
t.Log("Testing unlocking after timeout")
|
||||||
// Create a new service for this test with very short timeout (1 second)
|
|
||||||
authService2 := auth.NewAuth(&docker.Docker{}, types.Users{}, []string{}, 3600, 1, 3)
|
// Reinitialize auth service with a shorter timeout for testing
|
||||||
|
config.LoginTimeout = 1
|
||||||
|
config.LoginMaxRetries = 3
|
||||||
|
authService = auth.NewAuth(config, &docker.Docker{})
|
||||||
|
|
||||||
// Add enough failed attempts to lock the account
|
// Add enough failed attempts to lock the account
|
||||||
for i := 0; i < 3; i++ {
|
for i := 0; i < 3; i++ {
|
||||||
authService2.RecordLoginAttempt(identifier, false)
|
authService.RecordLoginAttempt(identifier, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify it's locked
|
// Verify it's locked
|
||||||
locked, _ = authService2.IsAccountLocked(identifier)
|
locked, _ = authService.IsAccountLocked(identifier)
|
||||||
if !locked {
|
if !locked {
|
||||||
t.Fatalf("Account should be locked initially")
|
t.Fatalf("Account should be locked initially")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait a bit and verify it gets unlocked after timeout
|
// Wait a bit and verify it gets unlocked after timeout
|
||||||
time.Sleep(1500 * time.Millisecond) // Wait longer than the timeout
|
time.Sleep(1500 * time.Millisecond) // Wait longer than the timeout
|
||||||
locked, _ = authService2.IsAccountLocked(identifier)
|
locked, _ = authService.IsAccountLocked(identifier)
|
||||||
|
|
||||||
if locked {
|
if locked {
|
||||||
t.Fatalf("Account should be unlocked after timeout period")
|
t.Fatalf("Account should be unlocked after timeout period")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test disabled rate limiting
|
// Test disabled rate limiting
|
||||||
t.Log("Testing disabled rate limiting")
|
t.Log("Testing disabled rate limiting")
|
||||||
authDisabled := auth.NewAuth(&docker.Docker{}, types.Users{}, []string{}, 3600, 0, 0)
|
config.LoginMaxRetries = 0
|
||||||
|
config.LoginTimeout = 0
|
||||||
|
authService = auth.NewAuth(config, &docker.Docker{})
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
authDisabled.RecordLoginAttempt(identifier, false)
|
authService.RecordLoginAttempt(identifier, false)
|
||||||
}
|
}
|
||||||
locked, _ = authDisabled.IsAccountLocked(identifier)
|
|
||||||
|
locked, _ = authService.IsAccountLocked(identifier)
|
||||||
if locked {
|
if locked {
|
||||||
t.Fatalf("Account should not be locked when rate limiting is disabled")
|
t.Fatalf("Account should not be locked when rate limiting is disabled")
|
||||||
}
|
}
|
||||||
@@ -78,24 +101,26 @@ func TestLoginRateLimiting(t *testing.T) {
|
|||||||
|
|
||||||
func TestConcurrentLoginAttempts(t *testing.T) {
|
func TestConcurrentLoginAttempts(t *testing.T) {
|
||||||
// Initialize a new auth service with 2 max retries and 5 seconds timeout
|
// Initialize a new auth service with 2 max retries and 5 seconds timeout
|
||||||
authService := auth.NewAuth(&docker.Docker{}, types.Users{}, []string{}, 3600, 5, 2)
|
config.LoginMaxRetries = 2
|
||||||
|
config.LoginTimeout = 5
|
||||||
|
authService := auth.NewAuth(config, &docker.Docker{})
|
||||||
|
|
||||||
// Test multiple identifiers
|
// Test multiple identifiers
|
||||||
identifiers := []string{"user1", "user2", "user3"}
|
identifiers := []string{"user1", "user2", "user3"}
|
||||||
|
|
||||||
// Test that locking one identifier doesn't affect others
|
// Test that locking one identifier doesn't affect others
|
||||||
t.Log("Testing multiple identifiers")
|
t.Log("Testing multiple identifiers")
|
||||||
|
|
||||||
// Add enough failed attempts to lock first user (2 attempts with maxRetries=2)
|
// Add enough failed attempts to lock first user (2 attempts with maxRetries=2)
|
||||||
authService.RecordLoginAttempt(identifiers[0], false)
|
authService.RecordLoginAttempt(identifiers[0], false)
|
||||||
authService.RecordLoginAttempt(identifiers[0], false)
|
authService.RecordLoginAttempt(identifiers[0], false)
|
||||||
|
|
||||||
// Check if first user is locked
|
// Check if first user is locked
|
||||||
locked, _ := authService.IsAccountLocked(identifiers[0])
|
locked, _ := authService.IsAccountLocked(identifiers[0])
|
||||||
if !locked {
|
if !locked {
|
||||||
t.Fatalf("User1 should be locked after reaching max retries")
|
t.Fatalf("User1 should be locked after reaching max retries")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that other users are not affected
|
// Check that other users are not affected
|
||||||
for i := 1; i < len(identifiers); i++ {
|
for i := 1; i < len(identifiers); i++ {
|
||||||
locked, _ := authService.IsAccountLocked(identifiers[i])
|
locked, _ := authService.IsAccountLocked(identifiers[i])
|
||||||
@@ -106,11 +131,13 @@ func TestConcurrentLoginAttempts(t *testing.T) {
|
|||||||
|
|
||||||
// Test successful login after failed attempts (but before lock)
|
// Test successful login after failed attempts (but before lock)
|
||||||
t.Log("Testing successful login after failed attempts but before lock")
|
t.Log("Testing successful login after failed attempts but before lock")
|
||||||
|
|
||||||
// One failed attempt for user2
|
// One failed attempt for user2
|
||||||
authService.RecordLoginAttempt(identifiers[1], false)
|
authService.RecordLoginAttempt(identifiers[1], false)
|
||||||
|
|
||||||
// Successful login should reset the counter
|
// Successful login should reset the counter
|
||||||
authService.RecordLoginAttempt(identifiers[1], true)
|
authService.RecordLoginAttempt(identifiers[1], true)
|
||||||
|
|
||||||
// Now try a failed login again - should not be locked as counter was reset
|
// Now try a failed login again - should not be locked as counter was reset
|
||||||
authService.RecordLoginAttempt(identifiers[1], false)
|
authService.RecordLoginAttempt(identifiers[1], false)
|
||||||
locked, _ = authService.IsAccountLocked(identifiers[1])
|
locked, _ = authService.IsAccountLocked(identifiers[1])
|
||||||
|
|||||||
59
internal/types/api.go
Normal file
59
internal/types/api.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// LoginQuery is the query parameters for the login endpoint
|
||||||
|
type LoginQuery struct {
|
||||||
|
RedirectURI string `url:"redirect_uri"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginRequest is the request body for the login endpoint
|
||||||
|
type LoginRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuthRequest is the request for the OAuth endpoint
|
||||||
|
type OAuthRequest struct {
|
||||||
|
Provider string `uri:"provider" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnauthorizedQuery is the query parameters for the unauthorized endpoint
|
||||||
|
type UnauthorizedQuery struct {
|
||||||
|
Username string `url:"username"`
|
||||||
|
Resource string `url:"resource"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TailscaleQuery is the query parameters for the tailscale endpoint
|
||||||
|
type TailscaleQuery struct {
|
||||||
|
Code int `url:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy is the uri parameters for the proxy endpoint
|
||||||
|
type Proxy struct {
|
||||||
|
Proxy string `uri:"proxy" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// User Context response is the response for the user context endpoint
|
||||||
|
type UserContextResponse struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
IsLoggedIn bool `json:"isLoggedIn"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Oauth bool `json:"oauth"`
|
||||||
|
TotpPending bool `json:"totpPending"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// App Context is the response for the app context endpoint
|
||||||
|
type AppContext struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
ConfiguredProviders []string `json:"configuredProviders"`
|
||||||
|
DisableContinue bool `json:"disableContinue"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
GenericName string `json:"genericName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Totp request is the request for the totp endpoint
|
||||||
|
type TotpRequest struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
84
internal/types/config.go
Normal file
84
internal/types/config.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// Config is the configuration for the tinyauth server
|
||||||
|
type Config struct {
|
||||||
|
Port int `mapstructure:"port" validate:"required"`
|
||||||
|
Address string `validate:"required,ip4_addr" mapstructure:"address"`
|
||||||
|
Secret string `validate:"required,len=32" mapstructure:"secret"`
|
||||||
|
SecretFile string `mapstructure:"secret-file"`
|
||||||
|
AppURL string `validate:"required,url" mapstructure:"app-url"`
|
||||||
|
Users string `mapstructure:"users"`
|
||||||
|
UsersFile string `mapstructure:"users-file"`
|
||||||
|
CookieSecure bool `mapstructure:"cookie-secure"`
|
||||||
|
GithubClientId string `mapstructure:"github-client-id"`
|
||||||
|
GithubClientSecret string `mapstructure:"github-client-secret"`
|
||||||
|
GithubClientSecretFile string `mapstructure:"github-client-secret-file"`
|
||||||
|
GoogleClientId string `mapstructure:"google-client-id"`
|
||||||
|
GoogleClientSecret string `mapstructure:"google-client-secret"`
|
||||||
|
GoogleClientSecretFile string `mapstructure:"google-client-secret-file"`
|
||||||
|
TailscaleClientId string `mapstructure:"tailscale-client-id"`
|
||||||
|
TailscaleClientSecret string `mapstructure:"tailscale-client-secret"`
|
||||||
|
TailscaleClientSecretFile string `mapstructure:"tailscale-client-secret-file"`
|
||||||
|
GenericClientId string `mapstructure:"generic-client-id"`
|
||||||
|
GenericClientSecret string `mapstructure:"generic-client-secret"`
|
||||||
|
GenericClientSecretFile string `mapstructure:"generic-client-secret-file"`
|
||||||
|
GenericScopes string `mapstructure:"generic-scopes"`
|
||||||
|
GenericAuthURL string `mapstructure:"generic-auth-url"`
|
||||||
|
GenericTokenURL string `mapstructure:"generic-token-url"`
|
||||||
|
GenericUserURL string `mapstructure:"generic-user-url"`
|
||||||
|
GenericName string `mapstructure:"generic-name"`
|
||||||
|
DisableContinue bool `mapstructure:"disable-continue"`
|
||||||
|
OAuthWhitelist string `mapstructure:"oauth-whitelist"`
|
||||||
|
SessionExpiry int `mapstructure:"session-expiry"`
|
||||||
|
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
|
||||||
|
Title string `mapstructure:"app-title"`
|
||||||
|
EnvFile string `mapstructure:"env-file"`
|
||||||
|
LoginTimeout int `mapstructure:"login-timeout"`
|
||||||
|
LoginMaxRetries int `mapstructure:"login-max-retries"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server configuration
|
||||||
|
type HandlersConfig struct {
|
||||||
|
AppURL string
|
||||||
|
Domain string
|
||||||
|
CookieSecure bool
|
||||||
|
DisableContinue bool
|
||||||
|
GenericName string
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
// OAuthConfig is the configuration for the providers
|
||||||
|
type OAuthConfig struct {
|
||||||
|
GithubClientId string
|
||||||
|
GithubClientSecret string
|
||||||
|
GoogleClientId string
|
||||||
|
GoogleClientSecret string
|
||||||
|
TailscaleClientId string
|
||||||
|
TailscaleClientSecret string
|
||||||
|
GenericClientId string
|
||||||
|
GenericClientSecret string
|
||||||
|
GenericScopes []string
|
||||||
|
GenericAuthURL string
|
||||||
|
GenericTokenURL string
|
||||||
|
GenericUserURL string
|
||||||
|
AppURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIConfig is the configuration for the API
|
||||||
|
type APIConfig struct {
|
||||||
|
Port int
|
||||||
|
Address string
|
||||||
|
Secret string
|
||||||
|
CookieSecure bool
|
||||||
|
SessionExpiry int
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthConfig is the configuration for the auth service
|
||||||
|
type AuthConfig struct {
|
||||||
|
Users Users
|
||||||
|
OauthWhitelist []string
|
||||||
|
SessionExpiry int
|
||||||
|
LoginTimeout int
|
||||||
|
LoginMaxRetries int
|
||||||
|
}
|
||||||
@@ -1,17 +1,9 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import "tinyauth/internal/oauth"
|
import (
|
||||||
|
"time"
|
||||||
// LoginQuery is the query parameters for the login endpoint
|
"tinyauth/internal/oauth"
|
||||||
type LoginQuery struct {
|
)
|
||||||
RedirectURI string `url:"redirect_uri"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginRequest is the request body for the login endpoint
|
|
||||||
type LoginRequest struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// User is the struct for a user
|
// User is the struct for a user
|
||||||
type User struct {
|
type User struct {
|
||||||
@@ -23,84 +15,6 @@ type User struct {
|
|||||||
// Users is a list of users
|
// Users is a list of users
|
||||||
type Users []User
|
type Users []User
|
||||||
|
|
||||||
// Config is the configuration for the tinyauth server
|
|
||||||
type Config struct {
|
|
||||||
Port int `mapstructure:"port" validate:"required"`
|
|
||||||
Address string `validate:"required,ip4_addr" mapstructure:"address"`
|
|
||||||
Secret string `validate:"required,len=32" mapstructure:"secret"`
|
|
||||||
SecretFile string `mapstructure:"secret-file"`
|
|
||||||
AppURL string `validate:"required,url" mapstructure:"app-url"`
|
|
||||||
Users string `mapstructure:"users"`
|
|
||||||
UsersFile string `mapstructure:"users-file"`
|
|
||||||
CookieSecure bool `mapstructure:"cookie-secure"`
|
|
||||||
GithubClientId string `mapstructure:"github-client-id"`
|
|
||||||
GithubClientSecret string `mapstructure:"github-client-secret"`
|
|
||||||
GithubClientSecretFile string `mapstructure:"github-client-secret-file"`
|
|
||||||
GoogleClientId string `mapstructure:"google-client-id"`
|
|
||||||
GoogleClientSecret string `mapstructure:"google-client-secret"`
|
|
||||||
GoogleClientSecretFile string `mapstructure:"google-client-secret-file"`
|
|
||||||
TailscaleClientId string `mapstructure:"tailscale-client-id"`
|
|
||||||
TailscaleClientSecret string `mapstructure:"tailscale-client-secret"`
|
|
||||||
TailscaleClientSecretFile string `mapstructure:"tailscale-client-secret-file"`
|
|
||||||
GenericClientId string `mapstructure:"generic-client-id"`
|
|
||||||
GenericClientSecret string `mapstructure:"generic-client-secret"`
|
|
||||||
GenericClientSecretFile string `mapstructure:"generic-client-secret-file"`
|
|
||||||
GenericScopes string `mapstructure:"generic-scopes"`
|
|
||||||
GenericAuthURL string `mapstructure:"generic-auth-url"`
|
|
||||||
GenericTokenURL string `mapstructure:"generic-token-url"`
|
|
||||||
GenericUserURL string `mapstructure:"generic-user-url"`
|
|
||||||
GenericName string `mapstructure:"generic-name"`
|
|
||||||
DisableContinue bool `mapstructure:"disable-continue"`
|
|
||||||
OAuthWhitelist string `mapstructure:"oauth-whitelist"`
|
|
||||||
SessionExpiry int `mapstructure:"session-expiry"`
|
|
||||||
LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"`
|
|
||||||
Title string `mapstructure:"app-title"`
|
|
||||||
EnvFile string `mapstructure:"env-file"`
|
|
||||||
LoginTimeout int `mapstructure:"login-timeout"`
|
|
||||||
LoginMaxRetries int `mapstructure:"login-max-retries"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserContext is the context for the user
|
|
||||||
type UserContext struct {
|
|
||||||
Username string
|
|
||||||
IsLoggedIn bool
|
|
||||||
OAuth bool
|
|
||||||
Provider string
|
|
||||||
TotpPending bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// APIConfig is the configuration for the API
|
|
||||||
type APIConfig struct {
|
|
||||||
Port int
|
|
||||||
Address string
|
|
||||||
Secret string
|
|
||||||
CookieSecure bool
|
|
||||||
SessionExpiry int
|
|
||||||
Domain string
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuthConfig is the configuration for the providers
|
|
||||||
type OAuthConfig struct {
|
|
||||||
GithubClientId string
|
|
||||||
GithubClientSecret string
|
|
||||||
GoogleClientId string
|
|
||||||
GoogleClientSecret string
|
|
||||||
TailscaleClientId string
|
|
||||||
TailscaleClientSecret string
|
|
||||||
GenericClientId string
|
|
||||||
GenericClientSecret string
|
|
||||||
GenericScopes []string
|
|
||||||
GenericAuthURL string
|
|
||||||
GenericTokenURL string
|
|
||||||
GenericUserURL string
|
|
||||||
AppURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuthRequest is the request for the OAuth endpoint
|
|
||||||
type OAuthRequest struct {
|
|
||||||
Provider string `uri:"provider" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// OAuthProviders is the struct for the OAuth providers
|
// OAuthProviders is the struct for the OAuth providers
|
||||||
type OAuthProviders struct {
|
type OAuthProviders struct {
|
||||||
Github *oauth.OAuth
|
Github *oauth.OAuth
|
||||||
@@ -108,12 +22,6 @@ type OAuthProviders struct {
|
|||||||
Microsoft *oauth.OAuth
|
Microsoft *oauth.OAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnauthorizedQuery is the query parameters for the unauthorized endpoint
|
|
||||||
type UnauthorizedQuery struct {
|
|
||||||
Username string `url:"username"`
|
|
||||||
Resource string `url:"resource"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SessionCookie is the cookie for the session (exculding the expiry)
|
// SessionCookie is the cookie for the session (exculding the expiry)
|
||||||
type SessionCookie struct {
|
type SessionCookie struct {
|
||||||
Username string
|
Username string
|
||||||
@@ -129,48 +37,18 @@ type TinyauthLabels struct {
|
|||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// TailscaleQuery is the query parameters for the tailscale endpoint
|
// UserContext is the context for the user
|
||||||
type TailscaleQuery struct {
|
type UserContext struct {
|
||||||
Code int `url:"code"`
|
Username string
|
||||||
|
IsLoggedIn bool
|
||||||
|
OAuth bool
|
||||||
|
Provider string
|
||||||
|
TotpPending bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy is the uri parameters for the proxy endpoint
|
// LoginAttempt tracks information about login attempts for rate limiting
|
||||||
type Proxy struct {
|
type LoginAttempt struct {
|
||||||
Proxy string `uri:"proxy" binding:"required"`
|
FailedAttempts int
|
||||||
}
|
LastAttempt time.Time
|
||||||
|
LockedUntil time.Time
|
||||||
// User Context response is the response for the user context endpoint
|
|
||||||
type UserContextResponse struct {
|
|
||||||
Status int `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
IsLoggedIn bool `json:"isLoggedIn"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Provider string `json:"provider"`
|
|
||||||
Oauth bool `json:"oauth"`
|
|
||||||
TotpPending bool `json:"totpPending"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// App Context is the response for the app context endpoint
|
|
||||||
type AppContext struct {
|
|
||||||
Status int `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
ConfiguredProviders []string `json:"configuredProviders"`
|
|
||||||
DisableContinue bool `json:"disableContinue"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
GenericName string `json:"genericName"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Totp request is the request for the totp endpoint
|
|
||||||
type TotpRequest struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server configuration
|
|
||||||
type HandlersConfig struct {
|
|
||||||
AppURL string
|
|
||||||
Domain string
|
|
||||||
CookieSecure bool
|
|
||||||
DisableContinue bool
|
|
||||||
GenericName string
|
|
||||||
Title string
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user