Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
cc6d88f
spec: enroll + config parity with legacy admin
alvarofraguas Jun 9, 2026
d0f4dee
self-rotate token keeps the session alive
alvarofraguas Jun 9, 2026
4d5f67b
dashboard: flat fills on stacked 24h activity chart
alvarofraguas Jun 9, 2026
9bfcc5d
dashboard: HeroKpi halo matches the card tone
alvarofraguas Jun 9, 2026
e8ba732
apiFetch: re-prime CSRF from cookie if in-memory is null
alvarofraguas Jun 9, 2026
2382201
dashboard: separate config from query in the time-series palette
alvarofraguas Jun 9, 2026
fc1ef78
query detail: surface targets on API + SPA (parity with legacy)
alvarofraguas Jun 9, 2026
d848cf2
isAuthenticated: re-prime CSRF from cookie before answering false
alvarofraguas Jun 9, 2026
715bf26
query results table parity: Created + linked Node + Status + Refresh
alvarofraguas Jun 9, 2026
5f47118
dashboard: HeroKpi gradient background replaces halo blob
alvarofraguas Jun 9, 2026
d0f3c68
dashboard: flip HeroKpi gradient direction so tone sits top-right
alvarofraguas Jun 9, 2026
992c1e2
login: Trace Map background + theme toggle
alvarofraguas Jun 9, 2026
6ea084f
login: fix Trace Map visibility — strip baked-in fill-opacity
alvarofraguas Jun 9, 2026
0719ba2
login: use original osctrl PNG logo + match favicon
alvarofraguas Jun 9, 2026
e889dca
login: light-theme spotlight uses multiply blend (was invisible)
alvarofraguas Jun 9, 2026
c495f0a
logo: swap SVG for original PNG everywhere, theme-aware via CSS
alvarofraguas Jun 9, 2026
2644abc
sidenav: circuit-pattern background (B from preview)
alvarofraguas Jun 9, 2026
119d849
dashboard: per-user palette picker on the 24h time-series chart
alvarofraguas Jun 9, 2026
92ff4e5
dashboard: palette row replaces the in-chart legend
alvarofraguas Jun 9, 2026
3238f60
wordmark: use Bai Jamjuree font from the legacy admin
alvarofraguas Jun 9, 2026
41ad1ef
logo: preserve cabin-window color in dark mode
alvarofraguas Jun 9, 2026
b25a8c0
sidenav: tone down the light-theme rail circuit (0.18 → 0.10)
alvarofraguas Jun 9, 2026
fa9f67c
logo: fix dark-mode cabin-window color (minifier dropped invert arg)
alvarofraguas Jun 9, 2026
0162732
logo: switch invert(100%) → invert(99%) to defeat CSS minifier
alvarofraguas Jun 9, 2026
9ecede2
logo: ship dark-mode PNG variant + tone down dark rail circuit
alvarofraguas Jun 9, 2026
f90276d
wordmark: drop Bai Jamjuree, keep Space Grotesk @ 700
alvarofraguas Jun 9, 2026
a729d10
wordmark: restore Bai Jamjuree — user confirmed it's the right font
alvarofraguas Jun 9, 2026
cda253e
wordmark: self-host Bai Jamjuree to bypass ad-blockers
alvarofraguas Jun 9, 2026
a4a770e
api: per-OS flags target (spec item #1)
alvarofraguas Jun 9, 2026
3227adb
api: cert upload endpoint (spec item #2)
alvarofraguas Jun 9, 2026
ecf8c26
api: assembled configuration endpoint (spec item #3)
alvarofraguas Jun 9, 2026
8ab1dd4
api: scope assembled-config route to avoid /map/{target} clash
alvarofraguas Jun 9, 2026
6c4d347
spa: CertificateCard on Enrollment page (spec item #4)
alvarofraguas Jun 9, 2026
444d02d
spa: FlagsCard with per-OS download (spec item #5)
alvarofraguas Jun 9, 2026
086885f
queries: nest result rows in Data cell like legacy admin
alvarofraguas Jun 9, 2026
63c2aad
spa: AssembledConfigCard with Monaco read-only viewer (spec item #6)
alvarofraguas Jun 9, 2026
85ec40b
carves: link node column to node detail page
alvarofraguas Jun 9, 2026
f61db23
nodes: wire Archive + Delete buttons on NodeDetailPage
alvarofraguas Jun 9, 2026
739d71f
env-config: legacy-parity sliders + tabs + SideNav entry
alvarofraguas Jun 9, 2026
ef597be
enroll: split page into Install / Configuration / Lifecycle tabs
alvarofraguas Jun 9, 2026
075679f
nodes: restyle QuickFiltersRow to match QueriesListPage tabs
alvarofraguas Jun 9, 2026
cf79acf
nodes: add page title + drop duplicate status tab triplet
alvarofraguas Jun 9, 2026
de8566a
profile: split Security mega-card into 2x2 focused grid
alvarofraguas Jun 9, 2026
65820b9
nodes: fold chip pad into the toolbar row (one-line header)
alvarofraguas Jun 9, 2026
6c6d00b
settings: match EnvConfig + EnrollPage tabbed-section visual language
alvarofraguas Jun 9, 2026
d01ba0b
config: add-option + add-scheduled-query forms + docs links (spec ite…
alvarofraguas Jun 9, 2026
c843280
nodes: wire bulk Delete on the NodesTablePage dock
alvarofraguas Jun 9, 2026
3b6ac9f
tags: add multi-select + bulk delete dock matching Carves
alvarofraguas Jun 9, 2026
4292483
operators: add multi-select + bulk delete dock matching Tags/Carves
alvarofraguas Jun 9, 2026
85a0b46
environments: add multi-select + bulk delete dock matching the others
alvarofraguas Jun 9, 2026
b2d4ffa
docs-link: add explicit onClick + window.open fallback
alvarofraguas Jun 9, 2026
dacc425
docs-link: fix blank-tab open + surface on Enroll tabs
alvarofraguas Jun 9, 2026
0d2596a
nodes: collapse Archive/Delete into single forensically-safe Archive
alvarofraguas Jun 9, 2026
a9ae769
api: H1+H2 from security audit
alvarofraguas Jun 9, 2026
0be9610
audit: M1+M2+M3 — body cap, reserved-name guard, replace-all
alvarofraguas Jun 9, 2026
094abf0
nodes test: update status-filter selector for QuickFiltersGroup chip pad
alvarofraguas Jun 9, 2026
5426848
gitignore: hide internal planning notes; drop branch's spec file
alvarofraguas Jun 9, 2026
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,6 @@ tools/bruno/collection.bru
!CHANGELOG.md
!SECURITY.md
!frontend/**/*.md

# Local-only planning / design notes (internal toolchain output)
docs/superpowers/
148 changes: 148 additions & 0 deletions cmd/api/handlers/environments.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package handlers

import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"net/http"
"strings"
Expand Down Expand Up @@ -243,6 +246,14 @@ func (h *HandlersApi) EnvEnrollHandler(w http.ResponseWriter, r *http.Request) {
returnData = env.Certificate
case settings.DownloadFlags:
returnData = env.Flags
case settings.DownloadFlagsLinux:
returnData = substitutePlatformPaths(env.Flags, env.Name, "/etc/osquery", "/")
case settings.DownloadFlagsMac:
returnData = substitutePlatformPaths(env.Flags, env.Name, "/private/var/osquery", "/")
case settings.DownloadFlagsWin:
returnData = substitutePlatformPaths(env.Flags, env.Name, "C:\\Program Files\\osquery", "\\")
case settings.DownloadFlagsFreeBSD:
returnData = substitutePlatformPaths(env.Flags, env.Name, "/usr/local/etc", "/")
case environments.EnrollShell:
returnData, err = environments.QuickAddOneLinerShell((env.Certificate != ""), env)
if err != nil {
Expand Down Expand Up @@ -657,3 +668,140 @@ func (h *HandlersApi) EnvActionsHandler(w http.ResponseWriter, r *http.Request)
h.AuditLog.EnvAction(ctx[ctxUser], e.Action+" - "+e.Name, strings.Split(r.RemoteAddr, ":")[0], auditlog.NoEnvironment)
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: msgReturn})
}

// EnvConfigurationHandler - GET handler returning the assembled osquery
// configuration JSON for an environment. Returns the stored composed blob
// (options + schedule + packs + decorators + ATC). The composition is
// kept up to date by RefreshConfiguration, which fires from every parts
// mutation (UpdateOptions / UpdateSchedule / UpdatePacks / etc. in
// pkg/environments/osqueryconf.go), so reading the cached value is
// safe — the agents see the exact same blob.
//
// SECURITY: deliberately a pure read. The first cut of this handler
// called RefreshConfiguration on every GET, which turned the endpoint
// into a CSRF-via-GET shape and a hot-loop DB-write hazard when
// React-Query's stale refetch path hit it. The mutation path on the
// parts is the canonical place for the recompose.
func (h *HandlersApi) EnvConfigurationHandler(w http.ResponseWriter, r *http.Request) {
if h.DebugHTTPConfig.EnableHTTP {
utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody)
}
envVar := r.PathValue("env")
if envVar == "" {
apiErrorResponse(w, "error getting environment", http.StatusBadRequest, nil)
return
}
env, err := h.Envs.Get(envVar)
if err != nil {
if err.Error() == "record not found" {
apiErrorResponse(w, "environment not found", http.StatusNotFound, err)
} else {
apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, err)
}
return
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) {
h.denyEnv(w, r, ctx, env.ID, "permission check failed")
return
}
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiDataResponse{Data: env.Configuration})
}

// envCertUploadRequest is the body shape for EnvCertUploadHandler. The PEM
// is sent base64-encoded so the upload survives clients that mangle raw
// newlines (curl --data, browser fetch with a plain string body, etc.).
type envCertUploadRequest struct {
CertificateB64 string `json:"certificate_b64"`
}

// EnvCertUploadHandler - POST handler to upload the enrollment certificate
// for an environment. Body: { "certificate_b64": "<base64 PEM>" }. The PEM
// must parse as one or more CERTIFICATE blocks and the leaf must be a real
// x509 cert — we don't accept "looks like base64 of something." Legacy
// admin's equivalent path skipped this validation; the SPA target gets it
// so a typo'd paste fails fast instead of breaking enrollment downloads.
func (h *HandlersApi) EnvCertUploadHandler(w http.ResponseWriter, r *http.Request) {
if h.DebugHTTPConfig.EnableHTTP {
utils.DebugHTTPDump(h.DebugHTTP, r, h.DebugHTTPConfig.ShowBody)
}
envVar := r.PathValue("env")
if envVar == "" {
apiErrorResponse(w, "error getting environment", http.StatusBadRequest, nil)
return
}
env, err := h.Envs.Get(envVar)
if err != nil {
if err.Error() == "record not found" {
apiErrorResponse(w, "environment not found", http.StatusNotFound, err)
} else {
apiErrorResponse(w, "error getting environment", http.StatusInternalServerError, err)
}
return
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) {
h.denyEnv(w, r, ctx, env.ID, "permission check failed")
return
}
// Cap the request body at 64 KiB. A realistic PEM chain is well under
// 16 KiB; nginx alone allows up to 20 MiB and the cert upload is the
// worst-case amplifier (one big base64 string blows up ~1.33x on JSON
// decode + another 0.75x on base64 decode). 64 KiB leaves headroom
// for legitimate multi-cert chains while preventing a privileged
// operator account from being turned into an OOM lever.
r.Body = http.MaxBytesReader(w, r.Body, 64<<10)
var req envCertUploadRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
apiErrorResponse(w, "error parsing POST body", http.StatusBadRequest, err)
return
}
if req.CertificateB64 == "" {
apiErrorResponse(w, "empty certificate", http.StatusBadRequest, nil)
return
}
pemBytes, err := base64.StdEncoding.DecodeString(req.CertificateB64)
if err != nil {
apiErrorResponse(w, "error decoding certificate", http.StatusBadRequest, err)
return
}
block, _ := pem.Decode(pemBytes)
if block == nil || block.Type != "CERTIFICATE" {
apiErrorResponse(w, "invalid PEM: no CERTIFICATE block", http.StatusBadRequest, nil)
return
}
if _, err := x509.ParseCertificate(block.Bytes); err != nil {
apiErrorResponse(w, "invalid x509 certificate", http.StatusBadRequest, err)
return
}
if err := h.Envs.UpdateCertificate(env.UUID, string(pemBytes)); err != nil {
apiErrorResponse(w, "error saving certificate", http.StatusInternalServerError, err)
return
}
log.Debug().Msgf("Certificate updated for environment %s", env.Name)
h.AuditLog.EnvAction(ctx[ctxUser], "upload certificate for environment "+env.Name, strings.Split(r.RemoteAddr, ":")[0], env.ID)
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.ApiGenericResponse{Message: "certificate uploaded successfully"})
}

// substitutePlatformPaths fills the __SECRET_FILE__ / __CERT_FILE__ placeholders
// in the env.Flags template with the canonical install paths for a given OS.
// This is the same substitution legacy admin's download path performs (see
// cmd/admin/handlers/utils.go generateFlags); centralising it here keeps the
// API's per-OS flag downloads producing the exact bytes operators expect to
// drop into /etc/osquery/osctrl-{env}.flags (or the platform equivalent).
//
// sep is the path separator the OS uses ("/" for everything except Windows).
//
// strings.ReplaceAll is deliberate even though the flag template ships with
// one occurrence of each placeholder today. If an operator ever edits the
// template to reference the secret/cert path twice (e.g. an additional
// --logger_tls_endpoint that needs the same path), the single-replace
// would silently leave the second placeholder unsubstituted — a footgun
// that costs a real outage.
func substitutePlatformPaths(flags, envName, dir, sep string) string {
secretPath := dir + sep + "osctrl-" + envName + ".secret"
certPath := dir + sep + "osctrl-" + envName + ".crt"
out := strings.ReplaceAll(flags, "__SECRET_FILE__", secretPath)
out = strings.ReplaceAll(out, "__CERT_FILE__", certPath)
return out
}
15 changes: 8 additions & 7 deletions cmd/api/handlers/environments_crud.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"strings"

"github.com/jmpsec/osctrl/pkg/auditlog"
"github.com/jmpsec/osctrl/pkg/environments"
"github.com/jmpsec/osctrl/pkg/tags"
"github.com/jmpsec/osctrl/pkg/types"
Expand All @@ -27,7 +28,7 @@ func (h *HandlersApi) EnvironmentCreateHandler(w http.ResponseWriter, r *http.Re
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, users.NoEnvironment) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
h.denyEnv(w, r, ctx, auditlog.NoEnvironment, "permission check failed")
return
}
var body types.EnvCreateRequest
Expand Down Expand Up @@ -114,7 +115,7 @@ func (h *HandlersApi) EnvironmentUpdateHandler(w http.ResponseWriter, r *http.Re
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, users.NoEnvironment) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
h.denyEnv(w, r, ctx, auditlog.NoEnvironment, "permission check failed")
return
}
envVar := r.PathValue("env")
Expand Down Expand Up @@ -231,7 +232,7 @@ func (h *HandlersApi) EnvironmentDeleteHandler(w http.ResponseWriter, r *http.Re
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, users.NoEnvironment) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
h.denyEnv(w, r, ctx, auditlog.NoEnvironment, "permission check failed")
return
}
envVar := r.PathValue("env")
Expand Down Expand Up @@ -281,7 +282,7 @@ func (h *HandlersApi) EnvironmentConfigHandler(w http.ResponseWriter, r *http.Re
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
h.denyEnv(w, r, ctx, env.ID, "permission check failed")
return
}
resp := types.EnvConfigResponse{
Expand Down Expand Up @@ -321,7 +322,7 @@ func (h *HandlersApi) EnvironmentConfigPatchHandler(w http.ResponseWriter, r *ht
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
h.denyEnv(w, r, ctx, env.ID, "permission check failed")
return
}
var body types.EnvConfigPatchRequest
Expand Down Expand Up @@ -434,7 +435,7 @@ func (h *HandlersApi) EnvironmentIntervalsPatchHandler(w http.ResponseWriter, r
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
h.denyEnv(w, r, ctx, env.ID, "permission check failed")
return
}
var body types.EnvIntervalsPatchRequest
Expand Down Expand Up @@ -503,7 +504,7 @@ func (h *HandlersApi) EnvironmentExpirationPatchHandler(w http.ResponseWriter, r
}
ctx := r.Context().Value(ContextKey(contextAPI)).(ContextValue)
if !h.Users.CheckPermissions(ctx[ctxUser], users.AdminLevel, env.UUID) {
apiErrorResponse(w, "no access", http.StatusForbidden, fmt.Errorf("attempt to use API by user %s", ctx[ctxUser]))
h.denyEnv(w, r, ctx, env.ID, "permission check failed")
return
}
var body types.EnvExpirationPatchRequest
Expand Down
26 changes: 25 additions & 1 deletion cmd/api/handlers/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,34 @@ func (h *HandlersApi) QueryShowHandler(w http.ResponseWriter, r *http.Request) {
}
return
}
// Targets — the creation-time scope (platform/uuid/hostname/tag rows
// stored in the query_targets table). The legacy admin shows them
// in a small Type/Value table on the query detail page; surface
// them here so the SPA can render the same. Best-effort: a fetch
// error doesn't fail the request — we still return the query.
type queryTarget struct {
Type string `json:"type"`
Value string `json:"value"`
}
targets := []queryTarget{}
if rows, terr := h.Queries.GetTargets(name); terr == nil {
for _, t := range rows {
targets = append(targets, queryTarget{Type: t.Type, Value: t.Value})
}
} else {
log.Debug().Err(terr).Msgf("query targets fetch failed for %s", name)
}
resp := struct {
queries.DistributedQuery
Targets []queryTarget `json:"targets"`
}{
DistributedQuery: query,
Targets: targets,
}
// Serialize and serve JSON
log.Debug().Msgf("Returned query %s", name)
h.AuditLog.Visit(ctx[ctxUser], r.URL.Path, strings.Split(r.RemoteAddr, ":")[0], env.ID)
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, query)
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, resp)
}

// QueriesRunHandler - POST Handler to run a query
Expand Down
41 changes: 41 additions & 0 deletions cmd/api/handlers/users_profile.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package handlers

import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"

"github.com/jmpsec/osctrl/pkg/types"
"github.com/jmpsec/osctrl/pkg/users"
Expand Down Expand Up @@ -294,6 +297,44 @@ func (h *HandlersApi) RefreshUserTokenHandler(w http.ResponseWriter, r *http.Req
apiErrorResponse(w, "error persisting token", http.StatusInternalServerError, err)
return
}
// Self-rotate: the JWT in the caller's osctrl_token cookie was just
// invalidated against handlerAuthCheck's APIToken match. Re-issue
// the session cookies with the new token (and a fresh CSRF) so the
// caller stays logged in. For other-user rotate, the caller's own
// session is unaffected — no cookie work needed.
if isSelf {
csrfBytes := make([]byte, 16)
if _, err := rand.Read(csrfBytes); err != nil {
apiErrorResponse(w, "error generating csrf token", http.StatusInternalServerError, err)
return
}
csrfToken := hex.EncodeToString(csrfBytes)
if err := h.Users.UpdateMetadata(strings.Split(r.RemoteAddr, ":")[0], r.UserAgent(), username, csrfToken); err != nil {
apiErrorResponse(w, "error persisting csrf token", http.StatusInternalServerError, err)
return
}
maxAge := int(time.Until(expires).Seconds())
if maxAge > 0 {
http.SetCookie(w, &http.Cookie{
Name: "osctrl_token",
Value: token,
Path: "/",
MaxAge: maxAge,
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
http.SetCookie(w, &http.Cookie{
Name: "osctrl_csrf",
Value: csrfToken,
Path: "/",
MaxAge: maxAge,
HttpOnly: false,
Secure: true,
SameSite: http.SameSiteLaxMode,
})
}
}
h.AuditLog.NewToken(username, strings.Split(r.RemoteAddr, ":")[0])
log.Debug().Msgf("refreshed API token for %s (requested by %s)", username, requester)
utils.HTTPResponse(w, utils.JSONApplicationUTF8, http.StatusOK, types.TokenResponse{Token: token, Expires: expires})
Expand Down
6 changes: 6 additions & 0 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,12 @@ func osctrlAPIService() {
muxAPI.Handle(
"GET "+_apiPath(apiEnvironmentsPath)+"/{env}/enroll/{target}",
handlerAuthCheck(http.HandlerFunc(handlersApi.EnvEnrollHandler), flagParams.Service.Auth, flagParams.JWT.JWTSecret))
muxAPI.Handle(
"GET "+_apiPath(apiEnvironmentsPath)+"/{env}/configuration/assembled",
handlerAuthCheck(http.HandlerFunc(handlersApi.EnvConfigurationHandler), flagParams.Service.Auth, flagParams.JWT.JWTSecret))
muxAPI.Handle(
"POST "+_apiPath(apiEnvironmentsPath)+"/{env}/enroll/cert",
handlerAuthCheck(http.HandlerFunc(handlersApi.EnvCertUploadHandler), flagParams.Service.Auth, flagParams.JWT.JWTSecret))
muxAPI.Handle(
"POST "+_apiPath(apiEnvironmentsPath)+"/{env}/enroll/{action}",
handlerAuthCheck(http.HandlerFunc(handlersApi.EnvEnrollActionsHandler), flagParams.Service.Auth, flagParams.JWT.JWTSecret))
Expand Down
2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#2bc4be" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" href="/favicon.png" />
<title>osctrl</title>
<!-- Theme bootstrap runs blocking from a static asset so `script-src 'self'`
covers it without a CSP hash to maintain. -->
Expand Down
Binary file added frontend/public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/fonts/bai-jamjuree-500.woff2
Binary file not shown.
Binary file added frontend/public/fonts/bai-jamjuree-600.woff2
Binary file not shown.
Binary file added frontend/public/fonts/bai-jamjuree-700.woff2
Binary file not shown.
Loading
Loading