Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auth: disable via cli flag #17249

Merged
merged 6 commits into from
Nov 14, 2024
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
3 changes: 3 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const (
flagIgnoreDatabase = "ignore-db"
flagIgnoreDatabaseDescription = "Run command ignoring service database"

flagDisableAuth = "disable-auth"
flagDisableAuthDescription = "Disable authentication (dangerous)"

flagBatteryMode = "battery-mode"
flagBatteryModeDescription = "Set battery mode (normal, hold, charge)"
flagBatteryModeWait = "battery-mode-wait"
Expand Down
11 changes: 10 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/evcc-io/evcc/server"
"github.com/evcc-io/evcc/server/updater"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/auth"
"github.com/evcc-io/evcc/util/config"
"github.com/evcc-io/evcc/util/pipe"
"github.com/evcc-io/evcc/util/sponsor"
Expand Down Expand Up @@ -75,6 +76,8 @@ func init() {

rootCmd.Flags().Bool("profile", false, "Expose pprof profiles")
bind(rootCmd, "profile")

rootCmd.Flags().Bool(flagDisableAuth, false, flagDisableAuthDescription)
}

// initConfig reads in config file and ENV variables if set
Expand Down Expand Up @@ -270,7 +273,13 @@ func runRoot(cmd *cobra.Command, args []string) {
// allow web access for vehicles
configureAuth(conf.Network, config.Instances(config.Vehicles().Devices()), httpd.Router(), valueChan)

httpd.RegisterSystemHandler(valueChan, cache, func() {
auth := auth.New()
if ok, _ := cmd.Flags().GetBool(flagDisableAuth); ok {
log.WARN.Println("❗❗❗ Authentication is disabled. This is dangerous. Your data and credentials are not protected.")
auth.Disable()
}

httpd.RegisterSystemHandler(valueChan, cache, auth, func() {
log.INFO.Println("evcc was stopped by user. OS should restart the service. Or restart manually.")
once.Do(func() { close(stopC) }) // signal loop to end
})
Expand Down
3 changes: 1 addition & 2 deletions server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,8 @@ func (s *HTTPd) RegisterSiteHandlers(site site.API, valueChan chan<- util.Param)
}

// RegisterSystemHandler provides system level handlers
func (s *HTTPd) RegisterSystemHandler(valueChan chan<- util.Param, cache *util.Cache, shutdown func()) {
func (s *HTTPd) RegisterSystemHandler(valueChan chan<- util.Param, cache *util.Cache, auth auth.Auth, shutdown func()) {
router := s.Server.Handler.(*mux.Router)
auth := auth.New()

// api
api := router.PathPrefix("/api").Subrouter()
Expand Down
10 changes: 10 additions & 0 deletions server/http_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ func jwtFromRequest(r *http.Request) string {
// authStatusHandler login status (true/false) based on jwt token. Error if admin password is not configured
func authStatusHandler(auth auth.Auth) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if auth.Disabled() {
w.Write([]byte("true"))
return
}

if !auth.IsAdminPasswordConfigured() {
http.Error(w, "Not implemented", http.StatusNotImplemented)
return
Expand Down Expand Up @@ -130,6 +135,11 @@ func logoutHandler(w http.ResponseWriter, r *http.Request) {
func ensureAuthHandler(auth auth.Auth) mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if auth.Disabled() {
next.ServeHTTP(w, r)
return
}

// check jwt token
ok, err := auth.ValidateJwtToken(jwtFromRequest(r))
if !ok || err != nil {
Expand Down
18 changes: 17 additions & 1 deletion tests/auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ test("http iframe hint", async ({ page }) => {
});

test("update password", async ({ page }) => {
const instance = await start(BASIC, "password.sql");
await start(BASIC, "password.sql");
await page.goto("/");

const oldPassword = "secret";
Expand Down Expand Up @@ -140,3 +140,19 @@ test("update password", async ({ page }) => {

await stop();
});

test("disable auth", async ({ page }) => {
await start(BASIC, null, "--disable-auth");
await page.goto("/");

// no password modal
const modal = page.getByTestId("password-modal");
await expect(modal).not.toBeVisible();

// configuration page without login
await page.getByTestId("topnavigation-button").click();
await page.getByRole("link", { name: "Configuration" }).click();
await expect(page.getByRole("heading", { name: "Configuration" })).toBeVisible();

await stop();
});
24 changes: 16 additions & 8 deletions tests/evcc.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ function dbPath() {
return path.join(os.tmpdir(), file);
}

export async function start(config, sqlDumps) {
export async function start(config, sqlDumps, flags) {
await _clean();
if (sqlDumps) {
await _restoreDatabase(sqlDumps);
}
return await _start(config);
return await _start(config, flags);
}

export async function stop(instance) {
Expand Down Expand Up @@ -77,14 +77,15 @@ async function _restoreDatabase(sqlDumps) {
}
}

async function _start(config) {
async function _start(config, flags = []) {
const configFile = config.includes("/") ? config : `tests/${config}`;
const port = workerPort();
log(`wait until port ${port} is available`);
// wait for port to be available
await waitOn({ resources: [`tcp:${port}`], reverse: true, log: true });
log("starting evcc", { config, port });
const instance = spawn(BINARY, ["--config", configFile], {
const additionalFlags = typeof flags === "string" ? [flags] : flags;
log("starting evcc", { config, port, additionalFlags });
const instance = spawn(BINARY, ["--config", configFile, additionalFlags], {
env: { EVCC_NETWORK_PORT: port.toString(), EVCC_DATABASE_DSN: dbPath() },
stdio: ["pipe", "pipe", "pipe"],
});
Expand All @@ -108,10 +109,17 @@ async function _stop(instance) {
log("evcc is down", { port });
return;
}
// check if auth is required
const res = await axios.get(`${baseUrl()}/api/auth/status`);
log("auth status", res.status, res.statusText, res.data);
let cookie;
// login required
if (!res.data) {
const res = await axios.post(`${baseUrl()}/api/auth/login`, { password: "secret" });
log("login", res.status, res.statusText);
cookie = res.headers["set-cookie"];
}
log("shutting down evcc", { port });
const res = await axios.post(`${baseUrl()}/api/auth/login`, { password: "secret" });
log(res.status, res.statusText);
const cookie = res.headers["set-cookie"];
await axios.post(`${baseUrl()}/api/system/shutdown`, {}, { headers: { cookie } });
log(`wait until port ${port} is closed`);
await waitOn({ resources: [`tcp:${port}`], reverse: true, log: true });
Expand Down
15 changes: 13 additions & 2 deletions util/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@ type Auth interface {
GenerateJwtToken(time.Duration) (string, error)
ValidateJwtToken(string) (bool, error)
IsAdminPasswordConfigured() bool
Disable()
Disabled() bool
}

type auth struct {
settings settings.API
disabled bool
}

func New() Auth {
return &auth{settings: new(settings.Settings)}
return &auth{settings: new(settings.Settings), disabled: false}
}

func NewMock(settings settings.API) Auth {
return &auth{settings: settings}
return &auth{settings: settings, disabled: false}
}

func (a *auth) hashPassword(password string) (string, error) {
Expand Down Expand Up @@ -140,3 +143,11 @@ func (a *auth) ValidateJwtToken(tokenString string) (bool, error) {

return true, nil
}

func (a *auth) Disable() {
a.disabled = true
}

func (a *auth) Disabled() bool {
return a.disabled
}