Skip to content
Open
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/google/uuid v1.6.0
github.com/mdp/qrterminal/v3 v3.2.1
github.com/pquerna/otp v1.5.0
github.com/redis/rueidis v1.0.72
github.com/rs/zerolog v1.34.0
github.com/traefik/paerser v0.2.2
github.com/weppos/publicsuffix-go v0.50.3
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM=
github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
Expand All @@ -240,6 +242,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/redis/rueidis v1.0.72 h1:rc1ZUha266B7PYAeZp0OafXFcGz69WKy0aHtbkZzrJg=
github.com/redis/rueidis v1.0.72/go.mod h1:lfdcZzJ1oKGKL37vh9fO3ymwt+0TdjkkUCJxbgpmcgQ=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
Expand Down Expand Up @@ -304,6 +308,8 @@ go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
Expand Down
17 changes: 15 additions & 2 deletions internal/bootstrap/app_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"
"time"

"github.com/redis/rueidis"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/controller"
"github.com/steveiliop56/tinyauth/internal/repository"
Expand Down Expand Up @@ -125,16 +126,28 @@ func (app *BootstrapApp) Setup() error {

// Database
db, err := app.SetupDatabase(app.config.Database.Path)

if err != nil {
return fmt.Errorf("failed to setup database: %w", err)
}

var redisClient rueidis.Client
if app.config.Session.Driver == "redis" {
client, err := app.SetupRedis(
app.config.Database.Redis.URL,
app.config.Database.Redis.Password,
app.config.Database.Redis.DB,
)
if err != nil {
return fmt.Errorf("failed to setup redis client: %w", err)
}
redisClient = client
}

// Queries
queries := repository.New(db)

// Services
services, err := app.initServices(queries)
services, err := app.initServices(queries, redisClient)

if err != nil {
return fmt.Errorf("failed to initialize services: %w", err)
Expand Down
13 changes: 13 additions & 0 deletions internal/bootstrap/db_bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path/filepath"

"github.com/redis/rueidis"
"github.com/steveiliop56/tinyauth/internal/assets"

"github.com/golang-migrate/migrate/v4"
Expand Down Expand Up @@ -55,3 +56,15 @@ func (app *BootstrapApp) SetupDatabase(databasePath string) (*sql.DB, error) {

return db, nil
}

func (app *BootstrapApp) SetupRedis(url, password string, db int) (rueidis.Client, error) {
client, err := rueidis.NewClient(rueidis.ClientOption{
InitAddress: []string{url},
Password: password,
SelectDB: db,
})
if err != nil {
return nil, fmt.Errorf("init redis client: %w", err)
}
return client, nil
}
17 changes: 15 additions & 2 deletions internal/bootstrap/service_bootstrap.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package bootstrap

import (
"fmt"

"github.com/redis/rueidis"
"github.com/steveiliop56/tinyauth/internal/repository"
"github.com/steveiliop56/tinyauth/internal/service"
"github.com/steveiliop56/tinyauth/internal/utils/tlog"
Expand All @@ -15,7 +18,7 @@ type Services struct {
oidcService *service.OIDCService
}

func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, error) {
func (app *BootstrapApp) initServices(queries *repository.Queries, redis rueidis.Client) (Services, error) {
services := Services{}

ldapService := service.NewLdapService(service.LdapServiceConfig{
Expand Down Expand Up @@ -58,6 +61,16 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er

services.accessControlService = accessControlsService

var sessionRepo service.SessionRepository
switch app.config.Session.Driver {
case "database":
sessionRepo = queries
case "redis":
sessionRepo = repository.NewRedisSessionRepository(redis)
default:
return Services{}, fmt.Errorf("unknown session driver %s", app.config.Session.Driver)
}

authService := service.NewAuthService(service.AuthServiceConfig{
Users: app.context.users,
OauthWhitelist: app.config.OAuth.Whitelist,
Expand All @@ -70,7 +83,7 @@ func (app *BootstrapApp) initServices(queries *repository.Queries) (Services, er
SessionCookieName: app.context.sessionCookieName,
IP: app.config.Auth.IP,
LDAPGroupsCacheTTL: app.config.Ldap.GroupCacheTTL,
}, dockerService, services.ldapService, queries)
}, dockerService, services.ldapService, sessionRepo)

err = authService.Init()

Expand Down
17 changes: 16 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ func NewDefaultConfiguration() *Config {
Database: DatabaseConfig{
Path: "./tinyauth.db",
},
Session: SessionConfig{
Driver: "database",
},
Analytics: AnalyticsConfig{
Enabled: true,
},
Expand Down Expand Up @@ -79,6 +82,7 @@ var RedirectCookieName = "tinyauth-redirect"
type Config struct {
AppURL string `description:"The base URL where the app is hosted." yaml:"appUrl"`
Database DatabaseConfig `description:"Database configuration." yaml:"database"`
Session SessionConfig `description:"Session configuration" yaml:"session"`
Analytics AnalyticsConfig `description:"Analytics configuration." yaml:"analytics"`
Resources ResourcesConfig `description:"Resources configuration." yaml:"resources"`
Server ServerConfig `description:"Server configuration." yaml:"server"`
Expand All @@ -92,8 +96,19 @@ type Config struct {
Log LogConfig `description:"Logging configuration." yaml:"log"`
}

type RedisConfig struct {
URL string `description:"The url of the redis instance to connect to" yaml:"url"`
Password string `description:"The password to connect to redis with" yaml:"password"`
DB int `description:"The DB index to use" yaml:"db"`
}

type DatabaseConfig struct {
Path string `description:"The path to the database, including file name." yaml:"path"`
Path string `description:"The path to the database, including file name." yaml:"path"`
Redis RedisConfig `description:"Configure redis connection options" yaml:"redis"`
}

type SessionConfig struct {
Driver string `description:"The session driver to use: [database, redis]" yaml:"driver"`
}

type AnalyticsConfig struct {
Expand Down
117 changes: 117 additions & 0 deletions internal/repository/redis_session_queries.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package repository

import (
"context"
"encoding/json"
"time"

"github.com/redis/rueidis"
)

type RedisSessionRepository struct {
c rueidis.Client
}

func NewRedisSessionRepository(c rueidis.Client) *RedisSessionRepository {
return &RedisSessionRepository{
c: c,
}
}

func (r *RedisSessionRepository) GetSession(ctx context.Context, cookie string) (Session, error) {
var out Session

cmd := r.c.B().Get().Key(cookie).Build()
res := r.c.Do(ctx, cmd)
if err := res.Error(); err != nil {
return out, err
}

raw, err := res.AsBytes()
if err != nil {
return out, err
}

if err := json.Unmarshal(raw, &out); err != nil {
return out, err
}

return out, nil
}

func (r *RedisSessionRepository) CreateSession(
ctx context.Context,
params CreateSessionParams,
) (Session, error) {
out := Session{
UUID: params.UUID,
Email: params.Email,
Username: params.Username,
Name: params.Name,
Provider: params.Provider,
TotpPending: params.TotpPending,
OAuthName: params.OAuthName,
OAuthGroups: params.OAuthGroups,
OAuthSub: params.OAuthSub,
CreatedAt: params.CreatedAt,
Expiry: params.Expiry,
}

raw, err := json.Marshal(out)
if err != nil {
return out, err
}

cmd := r.c.B().Set().Key(params.UUID).Value(string(raw)).
Nx().ExSeconds(params.Expiry - time.Now().Unix()).
Build()
if err := r.c.Do(ctx, cmd).Error(); err != nil {
return out, err
}

return out, nil
}

func (r *RedisSessionRepository) UpdateSession(
ctx context.Context,
params UpdateSessionParams,
) (Session, error) {
session, err := r.GetSession(ctx, params.UUID)
if err != nil {
return session, err
}

session.UUID = params.UUID
session.Email = params.Email
session.Username = params.Username
session.Name = params.Name
session.Provider = params.Provider
session.TotpPending = params.TotpPending
session.OAuthName = params.OAuthName
session.OAuthGroups = params.OAuthGroups
session.OAuthSub = params.OAuthSub
session.Expiry = params.Expiry

raw, err := json.Marshal(session)
if err != nil {
return session, err
}

cmd := r.c.B().Set().Key(params.UUID).
Value(string(raw)).
ExSeconds(params.Expiry - time.Now().Unix()).
Build()
if err := r.c.Do(ctx, cmd).Error(); err != nil {
return session, err
}

return session, nil
}

func (r *RedisSessionRepository) DeleteSession(ctx context.Context, cookie string) error {
cmd := r.c.B().Del().Key(cookie).Build()
if err := r.c.Do(ctx, cmd).Error(); err != nil {
return err
}
return nil
}
15 changes: 12 additions & 3 deletions internal/service/auth_service.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package service

import (
"context"
"database/sql"
"errors"
"fmt"
Expand All @@ -9,6 +10,7 @@ import (
"sync"
"time"

"github.com/redis/rueidis"
"github.com/steveiliop56/tinyauth/internal/config"
"github.com/steveiliop56/tinyauth/internal/repository"
"github.com/steveiliop56/tinyauth/internal/utils"
Expand Down Expand Up @@ -44,6 +46,13 @@ type AuthServiceConfig struct {
LDAPGroupsCacheTTL int
}

type SessionRepository interface {
GetSession(context.Context, string) (repository.Session, error)
CreateSession(context.Context, repository.CreateSessionParams) (repository.Session, error)
UpdateSession(context.Context, repository.UpdateSessionParams) (repository.Session, error)
DeleteSession(context.Context, string) error
}

type AuthService struct {
config AuthServiceConfig
docker *DockerService
Expand All @@ -52,10 +61,10 @@ type AuthService struct {
loginMutex sync.RWMutex
ldapGroupsMutex sync.RWMutex
ldap *LdapService
queries *repository.Queries
queries SessionRepository
}

func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, queries *repository.Queries) *AuthService {
func NewAuthService(config AuthServiceConfig, docker *DockerService, ldap *LdapService, queries SessionRepository) *AuthService {
return &AuthService{
config: config,
docker: docker,
Expand Down Expand Up @@ -354,7 +363,7 @@ func (auth *AuthService) GetSessionCookie(c *gin.Context) (repository.Session, e
session, err := auth.queries.GetSession(c, cookie)

if err != nil {
if errors.Is(err, sql.ErrNoRows) {
if errors.Is(err, sql.ErrNoRows) || rueidis.IsRedisNil(err) {
return repository.Session{}, fmt.Errorf("session not found")
}
return repository.Session{}, err
Expand Down