Skip to content

Commit 5673376

Browse files
authoredOct 6, 2024··
feat: supports auth ttl to override session with DOZZLE_AUTH_TTL env. (#3313)
1 parent 423e4e1 commit 5673376

File tree

7 files changed

+71
-6
lines changed

7 files changed

+71
-6
lines changed
 

‎docs/guide/authentication.md

+28
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,34 @@ users:
6262

6363
Dozzle uses [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) to generate tokens for authentication. This token is saved in a cookie.
6464

65+
### Extending Authentication Cookie Lifetime
66+
67+
By default, Dozzle uses session cookies which expire when the browser is closed. You can extend the lifetime of the cookie by setting `--auth-ttl` to a duration. Here is an example:
68+
69+
::: code-group
70+
71+
```sh [cli]
72+
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle --auth-provider simple --auth-ttl 48h
73+
```
74+
75+
```yaml [docker-compose.yml]
76+
services:
77+
dozzle:
78+
image: amir20/dozzle:latest
79+
volumes:
80+
- /var/run/docker.sock:/var/run/docker.sock
81+
- /path/to/dozzle/data:/data
82+
ports:
83+
- 8080:8080
84+
environment:
85+
DOZZLE_AUTH_PROVIDER: simple
86+
DOZZLE_AUTH_TTL: 48h
87+
```
88+
89+
:::
90+
91+
Note that only the duration is supported. You can only use `s`, `m`, `h` for seconds, minutes and hours respectively.
92+
6593
## Generating users.yml
6694

6795
Dozzle has a builtin `generate` command to generate `users.yml`. Here is an example:

‎internal/auth/simple.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import (
1212
type simpleAuthContext struct {
1313
UserDatabase UserDatabase
1414
tokenAuth *jwtauth.JWTAuth
15+
ttl time.Duration
1516
}
1617

1718
var ErrInvalidCredentials = errors.New("invalid credentials")
1819

19-
func NewSimpleAuth(userDatabase UserDatabase) *simpleAuthContext {
20+
func NewSimpleAuth(userDatabase UserDatabase, ttl time.Duration) *simpleAuthContext {
2021
h := sha256.New()
2122
for _, user := range userDatabase.Users {
2223
h.Write([]byte(user.Password))
@@ -27,6 +28,7 @@ func NewSimpleAuth(userDatabase UserDatabase) *simpleAuthContext {
2728
return &simpleAuthContext{
2829
UserDatabase: userDatabase,
2930
tokenAuth: tokenAuth,
31+
ttl: ttl,
3032
}
3133
}
3234

@@ -36,7 +38,14 @@ func (a *simpleAuthContext) CreateToken(username, password string) (string, erro
3638
return "", ErrInvalidCredentials
3739
}
3840

39-
_, tokenString, err := a.tokenAuth.Encode(map[string]interface{}{"username": user.Username, "email": user.Email, "name": user.Name, "timestamp": time.Now()})
41+
claims := map[string]interface{}{"username": user.Username, "email": user.Email, "name": user.Name}
42+
jwtauth.SetIssuedNow(claims)
43+
44+
if a.ttl > 0 {
45+
jwtauth.SetExpiryIn(claims, a.ttl)
46+
}
47+
48+
_, tokenString, err := a.tokenAuth.Encode(claims)
4049
if err != nil {
4150
return "", err
4251
}

‎internal/support/cli/args.go

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Args struct {
1515
Hostname string `arg:"env:DOZZLE_HOSTNAME" help:"sets the hostname for display. This is useful with multiple Dozzle instances."`
1616
Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."`
1717
AuthProvider string `arg:"--auth-provider,env:DOZZLE_AUTH_PROVIDER" default:"none" help:"sets the auth provider to use. Currently only forward-proxy is supported."`
18+
AuthTTL string `arg:"--auth-ttl,env:DOZZLE_AUTH_TTL" default:"session" help:"sets the TTL for the auth token. Accepts duration values like 12h. Valid time units are s, m, h"`
1819
AuthHeaderUser string `arg:"--auth-header-user,env:DOZZLE_AUTH_HEADER_USER" default:"Remote-User" help:"sets the HTTP Header to use for username in Forward Proxy configuration."`
1920
AuthHeaderEmail string `arg:"--auth-header-email,env:DOZZLE_AUTH_HEADER_EMAIL" default:"Remote-Email" help:"sets the HTTP Header to use for email in Forward Proxy configuration."`
2021
AuthHeaderName string `arg:"--auth-header-name,env:DOZZLE_AUTH_HEADER_NAME" default:"Remote-Name" help:"sets the HTTP Header to use for name in Forward Proxy configuration."`

‎internal/web/auth.go

+6
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ func (h *handler) createToken(w http.ResponseWriter, r *http.Request) {
1212
pass := r.PostFormValue("password")
1313

1414
if token, err := h.config.Authorization.Authorizer.CreateToken(user, pass); err == nil {
15+
expires := time.Time{}
16+
if h.config.Authorization.TTL > 0 {
17+
expires = time.Now().Add(h.config.Authorization.TTL)
18+
}
19+
1520
http.SetCookie(w, &http.Cookie{
1621
Name: "jwt",
1722
Value: token,
1823
HttpOnly: true,
1924
Path: "/",
2025
SameSite: http.SameSiteLaxMode,
26+
Expires: expires,
2127
})
2228
log.Info().Str("user", user).Msg("Token created")
2329
w.WriteHeader(http.StatusOK)

‎internal/web/auth_simple_test.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"net/http/httptest"
99
"strings"
10+
"time"
1011

1112
"testing"
1213

@@ -32,7 +33,7 @@ func Test_createRoutes_simple_redirect(t *testing.T) {
3233
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
3334
},
3435
},
35-
}),
36+
}, time.Second*100),
3637
},
3738
})
3839
req, err := http.NewRequest("GET", "/", nil)
@@ -57,7 +58,7 @@ func Test_createRoutes_simple_valid_token(t *testing.T) {
5758
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
5859
},
5960
},
60-
}),
61+
}, time.Second*100),
6162
},
6263
})
6364

@@ -102,7 +103,7 @@ func Test_createRoutes_simple_bad_password(t *testing.T) {
102103
Password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8",
103104
},
104105
},
105-
}),
106+
}, time.Second*100),
106107
},
107108
})
108109

‎internal/web/routes.go

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package web
22

33
import (
44
"io/fs"
5+
"time"
56

67
"net/http"
78
"strings"
@@ -36,6 +37,7 @@ type Config struct {
3637
type Authorization struct {
3738
Provider AuthProvider
3839
Authorizer Authorizer
40+
TTL time.Duration
3941
}
4042

4143
type Authorizer interface {

‎main.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,24 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
229229
}
230230

231231
log.Debug().Int("users", len(db.Users)).Msg("Loaded users")
232-
authorizer = auth.NewSimpleAuth(db)
232+
ttl := time.Duration(0)
233+
if args.AuthTTL != "session" {
234+
ttl, err = time.ParseDuration(args.AuthTTL)
235+
if err != nil {
236+
log.Fatal().Err(err).Msg("Could not parse auth ttl")
237+
}
238+
}
239+
authorizer = auth.NewSimpleAuth(db, ttl)
240+
}
241+
242+
authTTL := time.Duration(0)
243+
244+
if args.AuthTTL != "session" {
245+
ttl, err := time.ParseDuration(args.AuthTTL)
246+
if err != nil {
247+
log.Fatal().Err(err).Msg("Could not parse auth ttl")
248+
}
249+
authTTL = ttl
233250
}
234251

235252
config := web.Config{
@@ -242,6 +259,7 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi
242259
Authorization: web.Authorization{
243260
Provider: provider,
244261
Authorizer: authorizer,
262+
TTL: authTTL,
245263
},
246264
EnableActions: args.EnableActions,
247265
}

0 commit comments

Comments
 (0)
Please sign in to comment.