From 3afe40083d6bde9d4ba7db2f094b3df9c559cb22 Mon Sep 17 00:00:00 2001 From: Christoph Kluge Date: Fri, 5 Jul 2024 11:48:06 +0200 Subject: [PATCH] rename api userconfig to frontend, return json on api auth error --- cmd/cc-backend/main.go | 56 ++++++++----------- internal/api/rest.go | 5 +- internal/auth/auth.go | 50 ++++++++--------- internal/repository/job.go | 24 +++++++- .../src/config/user/PlotColorScheme.svelte | 2 +- .../src/config/user/PlotRenderOptions.svelte | 6 +- .../src/config/user/UserOptions.svelte | 2 +- web/frontend/src/utils.js | 4 +- 8 files changed, 80 insertions(+), 69 deletions(-) diff --git a/cmd/cc-backend/main.go b/cmd/cc-backend/main.go index fdddb99..b0faa13 100644 --- a/cmd/cc-backend/main.go +++ b/cmd/cc-backend/main.go @@ -374,7 +374,7 @@ func main() { securedapi := r.PathPrefix("/api").Subrouter() userapi := r.PathPrefix("/userapi").Subrouter() configapi := r.PathPrefix("/config").Subrouter() - userconfigapi := r.PathPrefix("/userconfig").Subrouter() + frontendapi := r.PathPrefix("/frontend").Subrouter() if !config.Keys.DisableAuthentication { r.Handle("/login", authentication.Login( @@ -447,15 +447,13 @@ func main() { // On success; next, - // On failure: + // On failure: JSON Response func(rw http.ResponseWriter, r *http.Request, err error) { + rw.Header().Add("Content-Type", "application/json") rw.WriteHeader(http.StatusUnauthorized) - web.RenderTemplate(rw, "login.tmpl", &web.Page{ - Title: "Authentication failed - ClusterCockpit", - MsgType: "alert-danger", - Message: err.Error(), - Build: buildInfo, - Infos: info, + json.NewEncoder(rw).Encode(map[string]string{ + "status": http.StatusText(http.StatusUnauthorized), + "error": err.Error(), }) }) }) @@ -465,15 +463,13 @@ func main() { // On success; next, - // On failure: + // On failure: JSON Response func(rw http.ResponseWriter, r *http.Request, err error) { + rw.Header().Add("Content-Type", "application/json") rw.WriteHeader(http.StatusUnauthorized) - web.RenderTemplate(rw, "login.tmpl", &web.Page{ - Title: "Authentication failed - ClusterCockpit", - MsgType: "alert-danger", - Message: err.Error(), - Build: buildInfo, - Infos: info, + json.NewEncoder(rw).Encode(map[string]string{ + "status": http.StatusText(http.StatusUnauthorized), + "error": err.Error(), }) }) }) @@ -483,33 +479,29 @@ func main() { // On success; next, - // On failure: + // On failure: JSON Response func(rw http.ResponseWriter, r *http.Request, err error) { + rw.Header().Add("Content-Type", "application/json") rw.WriteHeader(http.StatusUnauthorized) - web.RenderTemplate(rw, "login.tmpl", &web.Page{ - Title: "Authentication failed - ClusterCockpit", - MsgType: "alert-danger", - Message: err.Error(), - Build: buildInfo, - Infos: info, + json.NewEncoder(rw).Encode(map[string]string{ + "status": http.StatusText(http.StatusUnauthorized), + "error": err.Error(), }) }) }) - userconfigapi.Use(func(next http.Handler) http.Handler { - return authentication.AuthUserConfigApi( + frontendapi.Use(func(next http.Handler) http.Handler { + return authentication.AuthFrontendApi( // On success; next, - // On failure: + // On failure: JSON Response func(rw http.ResponseWriter, r *http.Request, err error) { + rw.Header().Add("Content-Type", "application/json") rw.WriteHeader(http.StatusUnauthorized) - web.RenderTemplate(rw, "login.tmpl", &web.Page{ - Title: "Authentication failed - ClusterCockpit", - MsgType: "alert-danger", - Message: err.Error(), - Build: buildInfo, - Infos: info, + json.NewEncoder(rw).Encode(map[string]string{ + "status": http.StatusText(http.StatusUnauthorized), + "error": err.Error(), }) }) }) @@ -532,7 +524,7 @@ func main() { api.MountApiRoutes(securedapi) api.MountUserApiRoutes(userapi) api.MountConfigApiRoutes(configapi) - api.MountUserConfigApiRoutes(userconfigapi) + api.MountFrontendApiRoutes(frontendapi) if config.Keys.EmbedStaticFiles { if i, err := os.Stat("./var/img"); err == nil { diff --git a/internal/api/rest.go b/internal/api/rest.go index 17a3183..b447a21 100644 --- a/internal/api/rest.go +++ b/internal/api/rest.go @@ -106,12 +106,13 @@ func (api *RestApi) MountConfigApiRoutes(r *mux.Router) { } } -func (api *RestApi) MountUserConfigApiRoutes(r *mux.Router) { +func (api *RestApi) MountFrontendApiRoutes(r *mux.Router) { r.StrictSlash(true) if api.Authentication != nil { - r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) // Role:Admin Check in + r.HandleFunc("/jwt/", api.getJWT).Methods(http.MethodGet) r.HandleFunc("/configuration/", api.updateConfiguration).Methods(http.MethodPost) + r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet) // Fetched in Job.svelte: Needs All-User-Access-Session-Auth } } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index 7e6f30e..50f4121 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -219,27 +219,25 @@ func (auth *Authentication) Auth( return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { user, err := auth.JwtAuth.AuthViaJWT(rw, r) if err != nil { - log.Infof("authentication failed: %s", err.Error()) + log.Infof("auth -> authentication failed: %s", err.Error()) http.Error(rw, err.Error(), http.StatusUnauthorized) return } - if user == nil { user, err = auth.AuthViaSession(rw, r) if err != nil { - log.Infof("authentication failed: %s", err.Error()) + log.Infof("auth -> authentication failed: %s", err.Error()) http.Error(rw, err.Error(), http.StatusUnauthorized) return } } - if user != nil { ctx := context.WithValue(r.Context(), repository.ContextUserKey, user) onsuccess.ServeHTTP(rw, r.WithContext(ctx)) return } - log.Debug("authentication failed") + log.Info("auth -> authentication failed") onfailure(rw, r, errors.New("unauthorized (please login first)")) }) } @@ -251,8 +249,8 @@ func (auth *Authentication) AuthApi( return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { user, err := auth.JwtAuth.AuthViaJWT(rw, r) if err != nil { - log.Infof("authentication failed: %s", err.Error()) - http.Error(rw, err.Error(), http.StatusUnauthorized) + log.Infof("auth api -> authentication failed: %s", err.Error()) + onfailure(rw, r, err) return } if user != nil { @@ -270,12 +268,12 @@ func (auth *Authentication) AuthApi( return } default: - log.Debug("authentication failed") - onfailure(rw, r, errors.New("unauthorized (missing role)")) + log.Info("auth api -> authentication failed: missing role") + onfailure(rw, r, errors.New("unauthorized")) } } - log.Debug("authentication failed") - onfailure(rw, r, errors.New("unauthorized (no auth)")) + log.Info("auth api -> authentication failed: no auth") + onfailure(rw, r, errors.New("unauthorized")) }) } @@ -286,8 +284,8 @@ func (auth *Authentication) AuthUserApi( return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { user, err := auth.JwtAuth.AuthViaJWT(rw, r) if err != nil { - log.Infof("authentication failed: %s", err.Error()) - http.Error(rw, err.Error(), http.StatusUnauthorized) + log.Infof("auth user api -> authentication failed: %s", err.Error()) + onfailure(rw, r, err) return } if user != nil { @@ -305,12 +303,12 @@ func (auth *Authentication) AuthUserApi( return } default: - log.Debug("authentication failed") - onfailure(rw, r, errors.New("unauthorized (missing role)")) + log.Info("auth user api -> authentication failed: missing role") + onfailure(rw, r, errors.New("unauthorized")) } } - log.Debug("authentication failed") - onfailure(rw, r, errors.New("unauthorized (no auth)")) + log.Info("auth user api -> authentication failed: no auth") + onfailure(rw, r, errors.New("unauthorized")) }) } @@ -321,8 +319,8 @@ func (auth *Authentication) AuthConfigApi( return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { user, err := auth.AuthViaSession(rw, r) if err != nil { - log.Infof("authentication failed: %s", err.Error()) - http.Error(rw, err.Error(), http.StatusUnauthorized) + log.Infof("auth config api -> authentication failed: %s", err.Error()) + onfailure(rw, r, err) return } if user != nil && user.HasRole(schema.RoleAdmin) { @@ -330,20 +328,20 @@ func (auth *Authentication) AuthConfigApi( onsuccess.ServeHTTP(rw, r.WithContext(ctx)) return } - log.Debug("authentication failed") - onfailure(rw, r, errors.New("unauthorized (no auth)")) + log.Info("auth config api -> authentication failed: no auth") + onfailure(rw, r, errors.New("unauthorized")) }) } -func (auth *Authentication) AuthUserConfigApi( +func (auth *Authentication) AuthFrontendApi( onsuccess http.Handler, onfailure func(rw http.ResponseWriter, r *http.Request, authErr error), ) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { user, err := auth.AuthViaSession(rw, r) if err != nil { - log.Infof("authentication failed: %s", err.Error()) - http.Error(rw, err.Error(), http.StatusUnauthorized) + log.Infof("auth frontend api -> authentication failed: %s", err.Error()) + onfailure(rw, r, err) return } if user != nil { @@ -351,8 +349,8 @@ func (auth *Authentication) AuthUserConfigApi( onsuccess.ServeHTTP(rw, r.WithContext(ctx)) return } - log.Debug("authentication failed") - onfailure(rw, r, errors.New("unauthorized (no auth)")) + log.Info("auth frontend api -> authentication failed: no auth") + onfailure(rw, r, errors.New("unauthorized")) }) } diff --git a/internal/repository/job.go b/internal/repository/job.go index ddd93d3..20496b2 100644 --- a/internal/repository/job.go +++ b/internal/repository/job.go @@ -305,16 +305,36 @@ func (r *JobRepository) FindByIdDirect(jobId int64) (*schema.Job, error) { return scanJob(q.RunWith(r.stmtCache).QueryRow()) } +// FindByJobId executes a SQL query to find a specific batch job. +// The job is queried using the slurm id and the clustername. +// It returns a pointer to a schema.Job data structure and an error variable. +// To check if no job was found test err == sql.ErrNoRows +func (r *JobRepository) FindByJobId(ctx context.Context, jobId int64, startTime int64, cluster string) (*schema.Job, error) { + q := sq.Select(jobColumns...). + From("job"). + Where("job.job_id = ?", jobId). + Where("job.cluster = ?", cluster). + Where("job.start_time = ?", startTime) + + q, qerr := SecurityCheck(ctx, q) + if qerr != nil { + return nil, qerr + } + + return scanJob(q.RunWith(r.stmtCache).QueryRow()) +} + // IsJobOwner executes a SQL query to find a specific batch job. // The job is queried using the slurm id,a username and the cluster. // It returns a bool. // If job was found, user is owner: test err != sql.ErrNoRows -func (r *JobRepository) IsJobOwner(jobId int64, user string, cluster string) bool { +func (r *JobRepository) IsJobOwner(jobId int64, startTime int64, user string, cluster string) bool { q := sq.Select("id"). From("job"). Where("job.job_id = ?", jobId). Where("job.user = ?", user). - Where("job.cluster = ?", cluster) + Where("job.cluster = ?", cluster). + Where("job.start_time = ?", startTime) _, err := scanJob(q.RunWith(r.stmtCache).QueryRow()) return err != sql.ErrNoRows diff --git a/web/frontend/src/config/user/PlotColorScheme.svelte b/web/frontend/src/config/user/PlotColorScheme.svelte index ad73791..a36f47c 100644 --- a/web/frontend/src/config/user/PlotColorScheme.svelte +++ b/web/frontend/src/config/user/PlotColorScheme.svelte @@ -262,7 +262,7 @@
diff --git a/web/frontend/src/config/user/PlotRenderOptions.svelte b/web/frontend/src/config/user/PlotRenderOptions.svelte index eb96eda..a237ed3 100644 --- a/web/frontend/src/config/user/PlotRenderOptions.svelte +++ b/web/frontend/src/config/user/PlotRenderOptions.svelte @@ -30,7 +30,7 @@ updateSetting("#line-width-form", "lw")} @@ -76,7 +76,7 @@ updateSetting("#plots-per-row-form", "ppr")} @@ -122,7 +122,7 @@ updateSetting("#backgrounds-form", "bg")} diff --git a/web/frontend/src/config/user/UserOptions.svelte b/web/frontend/src/config/user/UserOptions.svelte index fe63118..d589414 100644 --- a/web/frontend/src/config/user/UserOptions.svelte +++ b/web/frontend/src/config/user/UserOptions.svelte @@ -51,7 +51,7 @@ updateSetting("#paging-form", "pag")} diff --git a/web/frontend/src/utils.js b/web/frontend/src/utils.js index 48bca6b..bb43094 100644 --- a/web/frontend/src/utils.js +++ b/web/frontend/src/utils.js @@ -239,7 +239,7 @@ export async function fetchMetrics(job, metrics, scopes) { try { let res = await fetch( - `/api/jobs/metrics/${job.id}${query.length > 0 ? "?" : ""}${query.join( + `/frontend/jobs/metrics/${job.id}${query.length > 0 ? "?" : ""}${query.join( "&" )}` ); @@ -434,7 +434,7 @@ export function transformPerNodeDataForRoofline(nodes) { } export async function fetchJwt(username) { - const raw = await fetch(`/userconfig/jwt/?username=${username}`); + const raw = await fetch(`/frontend/jwt/?username=${username}`); if (!raw.ok) { const message = `An error has occured: ${response.status}`;