Auth: disable via cli flag (#17249)
This commit is contained in:
parent
e71f7602aa
commit
576cd71a44
7 changed files with 70 additions and 14 deletions
|
|
@ -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"
|
||||
|
|
|
|||
11
cmd/root.go
11
cmd/root.go
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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"],
|
||||
});
|
||||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue