mirror of
https://github.com/amir20/dozzle.git
synced 2026-01-04 20:14:59 +01:00
feat!: removes legacy authentication model (#2633)
This commit is contained in:
@@ -21,9 +21,6 @@
|
||||
>
|
||||
<mdi:cog />
|
||||
</router-link>
|
||||
<a :href="`${base}/logout`" :title="$t('button.logout')" v-if="secured" class="btn btn-circle btn-sm">
|
||||
<mdi:logout />
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
class="input input-sm mt-4 inline-flex cursor-pointer items-center gap-2 font-light hover:border-primary"
|
||||
@@ -40,5 +37,5 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { base, secured, hostname } = config;
|
||||
const { hostname } = config;
|
||||
</script>
|
||||
|
||||
@@ -32,9 +32,6 @@
|
||||
<router-link :to="{ name: 'settings' }" class="btn btn-outline btn-sm">
|
||||
<mdi:cog /> {{ $t("button.settings") }}
|
||||
</router-link>
|
||||
<a class="btn btn-outline btn-sm" :href="`${base}/logout`" :title="$t('button.logout')" v-if="secured">
|
||||
<mdi:logout /> {{ $t("button.logout") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ul class="menu">
|
||||
@@ -56,7 +53,6 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { base, secured } = config;
|
||||
import { sessionHost } from "@/composable/storage";
|
||||
const store = useContainerStore();
|
||||
const route = useRoute();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="!authorizationNeeded">
|
||||
<div>
|
||||
<mobile-menu v-if="isMobile" @search="showFuzzySearch"></mobile-menu>
|
||||
<splitpanes @resized="onResized($event)">
|
||||
<pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav">
|
||||
@@ -71,7 +71,6 @@
|
||||
// @ts-ignore - splitpanes types are not available
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import { collapseNav } from "@/stores/settings";
|
||||
const { authorizationNeeded } = config;
|
||||
|
||||
const containerStore = useContainerStore();
|
||||
const { activeContainers } = storeToRefs(containerStore);
|
||||
|
||||
@@ -32,7 +32,6 @@
|
||||
import { Container } from "@/models/Container";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { showToast } = useToast();
|
||||
const { version } = config;
|
||||
const containerStore = useContainerStore();
|
||||
const { containers, ready } = storeToRefs(containerStore) as unknown as {
|
||||
@@ -66,14 +65,6 @@ watchEffect(() => {
|
||||
setTitle(t("title.dashboard", { count: runningContainers.length }));
|
||||
}
|
||||
});
|
||||
|
||||
if (config.secured) {
|
||||
showToast({
|
||||
title: "Deprecation Warning",
|
||||
message: `The secured option is deprecated and will be removed in a future versions. See <a href="https://github.com/amir20/dozzle/issues/2630" target="_blank">this issue</a> for more information.`,
|
||||
type: "warning",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style lang="postcss" scoped>
|
||||
:deep(tr td) {
|
||||
|
||||
@@ -5,8 +5,6 @@ const text = document.querySelector("script#config__json")?.textContent || "{}";
|
||||
export interface Config {
|
||||
version: string;
|
||||
base: string;
|
||||
authorizationNeeded: boolean;
|
||||
secured: boolean;
|
||||
maxLogs: number;
|
||||
hostname: string;
|
||||
hosts: { name: string; id: string }[];
|
||||
|
||||
@@ -12,19 +12,6 @@ services:
|
||||
- 8080:8080
|
||||
build:
|
||||
context: .
|
||||
auth:
|
||||
container_name: auth
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- DOZZLE_FILTER=name=auth
|
||||
- DOZZLE_USERNAME=foo
|
||||
- DOZZLE_PASSWORD=bar
|
||||
- DOZZLE_NO_ANALYTICS=1
|
||||
ports:
|
||||
- 9090:8080
|
||||
build:
|
||||
context: .
|
||||
simple-auth:
|
||||
container_name: simple-auth
|
||||
volumes:
|
||||
@@ -82,5 +69,4 @@ services:
|
||||
depends_on:
|
||||
- dozzle
|
||||
- custom_base
|
||||
- auth
|
||||
- remote
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("authentication", async ({ page }) => {
|
||||
await page.goto("http://auth:8080/");
|
||||
await page.locator('input[name="username"]').fill("foo");
|
||||
await page.locator('input[name="password"]').fill("bar");
|
||||
await page.locator('button[type="submit"]').click();
|
||||
await expect(page.getByTestId("containers")).toHaveText("Containers");
|
||||
});
|
||||
2
go.mod
2
go.mod
@@ -10,7 +10,6 @@ require (
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
@@ -42,7 +41,6 @@ require (
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -45,12 +45,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
|
||||
@@ -1,106 +1,12 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var secured = false
|
||||
var store *sessions.CookieStore
|
||||
|
||||
const authorityKey = "AUTH_TIMESTAMP"
|
||||
const sessionName = "session"
|
||||
|
||||
func initializeAuth(h *handler) {
|
||||
secured = false
|
||||
if h.config.Username != "" && h.config.Password != "" {
|
||||
store = sessions.NewCookieStore(generateSessionStorageKey(h.config.Username, h.config.Password))
|
||||
store.Options.HttpOnly = true
|
||||
store.Options.SameSite = http.SameSiteLaxMode
|
||||
store.Options.MaxAge = 0
|
||||
secured = true
|
||||
}
|
||||
}
|
||||
|
||||
func authorizationRequired(next http.Handler) http.Handler {
|
||||
if secured {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if isAuthorized(r) {
|
||||
next.ServeHTTP(w, r)
|
||||
} else {
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return next
|
||||
}
|
||||
}
|
||||
|
||||
func isAuthorized(r *http.Request) bool {
|
||||
if !secured {
|
||||
return true
|
||||
}
|
||||
|
||||
session, _ := store.Get(r, sessionName)
|
||||
if session.IsNew {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, ok := session.Values[authorityKey]; ok {
|
||||
// TODO check for timestamp
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *handler) isAuthorizationNeeded(r *http.Request) bool {
|
||||
return secured && !isAuthorized(r)
|
||||
}
|
||||
|
||||
func (h *handler) validateCredentials(w http.ResponseWriter, r *http.Request) {
|
||||
if !secured {
|
||||
log.Panic("Validating credentials without username and password should not happen")
|
||||
}
|
||||
|
||||
if r.Method != "POST" {
|
||||
log.Fatal("Expecting credential validation method to be POST")
|
||||
http.Error(w, http.StatusText(http.StatusNotAcceptable), http.StatusNotAcceptable)
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ParseMultipartForm(4 * 1024); err != nil {
|
||||
log.Fatalf("Error while parsing form data: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user := r.PostFormValue("username")
|
||||
pass := r.PostFormValue("password")
|
||||
session, _ := store.Get(r, sessionName)
|
||||
|
||||
if user == h.config.Username && pass == h.config.Password {
|
||||
session.Values[authorityKey] = time.Now().Unix()
|
||||
|
||||
if err := session.Save(r, w); err != nil {
|
||||
log.Fatalf("Error while parsing saving session: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(http.StatusText(http.StatusOK)))
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func (h *handler) createToken(w http.ResponseWriter, r *http.Request) {
|
||||
user := r.PostFormValue("username")
|
||||
pass := r.PostFormValue("password")
|
||||
@@ -134,25 +40,3 @@ func (h *handler) deleteToken(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(http.StatusText(http.StatusOK)))
|
||||
}
|
||||
|
||||
func (h *handler) clearSession(w http.ResponseWriter, r *http.Request) {
|
||||
if !secured {
|
||||
log.Panic("Validating credentials with secured=false should not happen")
|
||||
}
|
||||
|
||||
session, _ := store.Get(r, sessionName)
|
||||
delete(session.Values, authorityKey)
|
||||
|
||||
if err := session.Save(r, w); err != nil {
|
||||
log.Fatalf("Error while parsing saving session: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, h.config.Base, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func generateSessionStorageKey(username string, password string) []byte {
|
||||
key := sha256.Sum256([]byte(fmt.Sprintf("%s:%s", username, password)))
|
||||
return key[:]
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
|
||||
if err == nil && req.URL.Path != "" && req.URL.Path != "/" {
|
||||
fileServer.ServeHTTP(w, req)
|
||||
} else {
|
||||
if !isAuthorized(req) && req.URL.Path != "login" {
|
||||
http.Redirect(w, req, path.Clean(h.config.Base+"/login"), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
h.executeTemplate(w, req)
|
||||
}
|
||||
}
|
||||
@@ -43,14 +39,12 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) {
|
||||
})
|
||||
|
||||
config := map[string]interface{}{
|
||||
"base": base,
|
||||
"version": h.config.Version,
|
||||
"authorizationNeeded": h.isAuthorizationNeeded(req),
|
||||
"secured": secured,
|
||||
"hostname": h.config.Hostname,
|
||||
"hosts": hosts,
|
||||
"authProvider": h.config.Authorization.Provider,
|
||||
"enableActions": h.config.EnableActions,
|
||||
"base": base,
|
||||
"version": h.config.Version,
|
||||
"hostname": h.config.Hostname,
|
||||
"hosts": hosts,
|
||||
"authProvider": h.config.Authorization.Provider,
|
||||
"enableActions": h.config.EnableActions,
|
||||
}
|
||||
|
||||
user := auth.UserFromContext(req.Context())
|
||||
|
||||
@@ -29,8 +29,6 @@ type Config struct {
|
||||
Base string
|
||||
Addr string
|
||||
Version string
|
||||
Username string
|
||||
Password string
|
||||
Hostname string
|
||||
NoAnalytics bool
|
||||
Dev bool
|
||||
@@ -79,8 +77,6 @@ func CreateServer(clients map[string]DockerClient, content fs.FS, config Config)
|
||||
var fileServer http.Handler
|
||||
|
||||
func createRouter(h *handler) *chi.Mux {
|
||||
initializeAuth(h)
|
||||
|
||||
base := h.config.Base
|
||||
r := chi.NewRouter()
|
||||
|
||||
@@ -101,7 +97,6 @@ func createRouter(h *handler) *chi.Mux {
|
||||
if h.config.Authorization.Provider != NONE {
|
||||
r.Use(auth.RequireAuthentication)
|
||||
}
|
||||
r.Use(authorizationRequired) // TODO remove this
|
||||
r.Get("/api/logs/stream/{host}/{id}", h.streamLogs)
|
||||
r.Get("/api/logs/download/{host}/{id}", h.downloadLogs)
|
||||
r.Get("/api/logs/{host}/{id}", h.fetchLogsBetweenDates)
|
||||
@@ -112,7 +107,6 @@ func createRouter(h *handler) *chi.Mux {
|
||||
r.Get("/api/releases", h.releases)
|
||||
r.Get("/api/profile/avatar", h.avatar)
|
||||
r.Patch("/api/profile", h.updateProfile)
|
||||
r.Get("/logout", h.clearSession) // TODO remove this
|
||||
r.Get("/version", h.version)
|
||||
})
|
||||
|
||||
@@ -127,7 +121,6 @@ func createRouter(h *handler) *chi.Mux {
|
||||
r.Delete("/api/token", h.deleteToken)
|
||||
}
|
||||
|
||||
r.Post("/api/validateCredentials", h.validateCredentials) // TODO remove this
|
||||
r.Get("/healthcheck", h.healthcheck)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"io"
|
||||
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/magiconair/properties/assert"
|
||||
|
||||
"github.com/amir20/dozzle/internal/docker"
|
||||
"github.com/beme/abide"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
@@ -49,19 +40,6 @@ func Test_createRoutes_redirect(t *testing.T) {
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_redirect_with_auth(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("index page"), 0644), "WriteFile should have no error.")
|
||||
|
||||
handler := createHandler(nil, afero.NewIOFS(fs), Config{Base: "/foobar", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
|
||||
req, err := http.NewRequest("GET", "/foobar/", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_foobar(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
require.NoError(t, afero.WriteFile(fs, "index.html", []byte("foo page"), 0644), "WriteFile should have no error.")
|
||||
@@ -100,116 +78,3 @@ func Test_createRoutes_version(t *testing.T) {
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_username_password(t *testing.T) {
|
||||
|
||||
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_username_password_invalid(t *testing.T) {
|
||||
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream/localhost/123?stdout=1&stderr=1", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_username_password_login_happy(t *testing.T) {
|
||||
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
fw, err := writer.CreateFormField("username")
|
||||
require.NoError(t, err, "Creating field should not be error.")
|
||||
_, err = io.Copy(fw, strings.NewReader("amir"))
|
||||
require.NoError(t, err, "Copying field should not result in error.")
|
||||
|
||||
fw, err = writer.CreateFormField("password")
|
||||
require.NoError(t, err, "Creating field should not be error.")
|
||||
_, err = io.Copy(fw, strings.NewReader("password"))
|
||||
require.NoError(t, err, "Copying field should not result in error.")
|
||||
|
||||
writer.Close()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/validateCredentials", bytes.NewReader(body.Bytes()))
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, rr.Code, 200)
|
||||
cookie := rr.Header().Get("Set-Cookie")
|
||||
assert.Matches(t, cookie, "session=.+")
|
||||
}
|
||||
|
||||
func Test_createRoutes_username_password_login_failed(t *testing.T) {
|
||||
handler := createHandler(nil, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
|
||||
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
fw, err := writer.CreateFormField("username")
|
||||
require.NoError(t, err, "Creating field should not be error.")
|
||||
_, err = io.Copy(fw, strings.NewReader("amir"))
|
||||
require.NoError(t, err, "Copying field should not result in error.")
|
||||
|
||||
fw, err = writer.CreateFormField("password")
|
||||
require.NoError(t, err, "Creating field should not be error.")
|
||||
_, err = io.Copy(fw, strings.NewReader("bad"))
|
||||
require.NoError(t, err, "Copying field should not result in error.")
|
||||
|
||||
writer.Close()
|
||||
|
||||
req, err := http.NewRequest("POST", "/api/validateCredentials", bytes.NewReader(body.Bytes()))
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, rr.Code, 401)
|
||||
}
|
||||
|
||||
func Test_createRoutes_username_password_valid_session(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
mockedClient.On("FindContainer", "123").Return(docker.Container{ID: "123"}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, "123", "", docker.STDALL).Return(io.NopCloser(strings.NewReader("test data")), io.EOF)
|
||||
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
|
||||
|
||||
// Get cookie first
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream/localhost/123?stdout=1&stderr=1", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
session, _ := store.Get(req, sessionName)
|
||||
session.Values[authorityKey] = time.Now().Unix()
|
||||
recorder := httptest.NewRecorder()
|
||||
session.Save(req, recorder)
|
||||
cookies := recorder.Result().Cookies()
|
||||
|
||||
// Test with cookie
|
||||
req, err = http.NewRequest("GET", "/api/logs/stream/localhost/123?stdout=1&stderr=1", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
req.AddCookie(cookies[0])
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
abide.AssertHTTPResponse(t, t.Name(), rr.Result())
|
||||
}
|
||||
|
||||
func Test_createRoutes_username_password_invalid_session(t *testing.T) {
|
||||
mockedClient := new(MockedClient)
|
||||
mockedClient.On("FindContainer", "123").Return(docker.Container{ID: "123"}, nil)
|
||||
mockedClient.On("ContainerLogs", mock.Anything, "since", docker.STDALL).Return(io.NopCloser(strings.NewReader("test data")), io.EOF)
|
||||
handler := createHandler(mockedClient, nil, Config{Base: "/", Username: "amir", Password: "password", Authorization: Authorization{Provider: NONE}})
|
||||
req, err := http.NewRequest("GET", "/api/logs/stream/localhost/123?stdout=1&stderr=1", nil)
|
||||
require.NoError(t, err, "NewRequest should not return an error.")
|
||||
req.AddCookie(&http.Cookie{Name: "session", Value: "baddata"})
|
||||
rr := httptest.NewRecorder()
|
||||
handler.ServeHTTP(rr, req)
|
||||
assert.Equal(t, rr.Code, 401)
|
||||
}
|
||||
|
||||
17
main.go
17
main.go
@@ -44,8 +44,6 @@ type args struct {
|
||||
Level string `arg:"env:DOZZLE_LEVEL" default:"info" help:"set Dozzle log level. Use debug for more logging."`
|
||||
Username string `arg:"env:DOZZLE_USERNAME" help:"sets the username for auth."`
|
||||
Password string `arg:"env:DOZZLE_PASSWORD" help:"sets password for auth"`
|
||||
UsernameFile *DockerSecret `arg:"env:DOZZLE_USERNAME_FILE" help:"sets the secret path read username for auth."`
|
||||
PasswordFile *DockerSecret `arg:"env:DOZZLE_PASSWORD_FILE" help:"sets the secret path read password for auth"`
|
||||
NoAnalytics bool `arg:"--no-analytics,env:DOZZLE_NO_ANALYTICS" help:"disables anonymous analytics"`
|
||||
WaitForDockerSeconds int `arg:"--wait-for-docker-seconds,env:DOZZLE_WAIT_FOR_DOCKER_SECONDS" help:"wait for docker to be available for at most this many seconds before starting the server."`
|
||||
FilterStrings []string `arg:"env:DOZZLE_FILTER,--filter,separate" help:"filters docker containers using Docker syntax."`
|
||||
@@ -193,8 +191,6 @@ func createServer(args args, clients map[string]web.DockerClient) *http.Server {
|
||||
Addr: args.Addr,
|
||||
Base: args.Base,
|
||||
Version: version,
|
||||
Username: args.Username,
|
||||
Password: args.Password,
|
||||
Hostname: args.Hostname,
|
||||
NoAnalytics: args.NoAnalytics,
|
||||
Dev: dev,
|
||||
@@ -274,19 +270,8 @@ func parseArgs() args {
|
||||
args.Filter[key] = append(args.Filter[key], val)
|
||||
}
|
||||
|
||||
if args.Username == "" && args.UsernameFile != nil {
|
||||
args.Username = args.UsernameFile.Value
|
||||
}
|
||||
|
||||
if args.Password == "" && args.PasswordFile != nil {
|
||||
args.Password = args.PasswordFile.Value
|
||||
}
|
||||
|
||||
if args.Username != "" || args.Password != "" {
|
||||
log.Warn("Using --username and --password is being deprecated and removed in v6.x. Use --auth-provider instead. See https://dozzle.dev/guide/authentication#file-based-user-management for more information.")
|
||||
if args.Username == "" || args.Password == "" {
|
||||
log.Fatalf("Username AND password are required for authentication")
|
||||
}
|
||||
log.Fatal("Using --username and --password is removed v6.x. See https://github.com/amir20/dozzle/issues/2630 for details.")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user