package hammerhead import ( _ "embed" "net/http" ) //go:embed web/index.html var indexHTML []byte func page(html []byte) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { h := w.Header() h.Set("Content-Type", "text/html; charset=utf-8") h.Set("Cache-Control", "no-store") h.Set("X-Frame-Options", "DENY") h.Set("X-Content-Type-Options", "nosniff") h.Set("Referrer-Policy", "no-referrer") h.Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; frame-ancestors 'none'") w.Write(html) } } // dashboardPaths are the addresses the dashboard shows pages at. They all serve the same app, // which reads the path to decide what to show. Keep in step with TAB_PATH in web/index.html. var dashboardPaths = map[string]bool{ "/": true, "/apps": true, "/resources": true, "/sites": true, "/domains": true, "/mail-egress": true, "/alerts": true, "/access": true, "/logs": true, "/account": true, "/server/edges": true, "/server/organizations": true, "/server/users": true, "/server/sso": true, "/server/settings": true, "/server/alerts": true, "/server/audit": true, } func isDashboardPath(p string) bool { return dashboardPaths[p] } func (s *Server) Handler() http.Handler { mux := http.NewServeMux() index := page(indexHTML) mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if !isDashboardPath(r.URL.Path) { http.NotFound(w, r) return } index(w, r) }) s.registerAlertRoutes(mux) mux.HandleFunc("GET /metrics", s.metrics) // public mux.HandleFunc("GET /api/public", s.publicInfo) mux.HandleFunc("POST /api/setup", s.setup) mux.HandleFunc("POST /api/login", s.login) mux.HandleFunc("POST /api/login/totp", s.loginTOTP) mux.HandleFunc("GET /api/invites/{token}", s.inviteInfo) mux.HandleFunc("POST /api/invites/accept", s.acceptInvite) mux.HandleFunc("GET /auth/oidc/{id}/start", s.oidcStart) mux.HandleFunc("GET /auth/oidc/callback", s.oidcCallback) // visitor login for protected ports res := page(resourceHTML) mux.HandleFunc("GET /auth/r/{id}", res) mux.HandleFunc("GET /auth/r/{id}/info", s.resourceInfo) mux.HandleFunc("POST /auth/r/{id}/password", s.resourceAuth("password", s.resourcePassword)) mux.HandleFunc("POST /auth/r/{id}/pin", s.resourceAuth("pin", s.resourcePin)) mux.HandleFunc("POST /auth/r/{id}/sso", s.resourceAuth("sso", s.resourceSSO)) mux.HandleFunc("POST /auth/r/{id}/share", s.resourceAuth("share", s.resourceShare)) mux.HandleFunc("POST /auth/r/{id}/email/start", s.resourceAuth("emailstart", s.resourceEmailStart)) mux.HandleFunc("POST /auth/r/{id}/email/verify", s.resourceAuth("emailverify", s.resourceEmailVerify)) // any signed-in user mux.HandleFunc("POST /api/logout", s.withUser(s.logout)) mux.HandleFunc("GET /api/me", s.withUser(s.me)) mux.HandleFunc("PUT /api/me", s.withUser(s.updateMe)) mux.HandleFunc("POST /api/me/password", s.withUser(s.changePassword)) mux.HandleFunc("POST /api/me/2fa/setup", s.withUser(s.totpSetup)) mux.HandleFunc("POST /api/me/2fa/enable", s.withUser(s.totpEnable)) mux.HandleFunc("POST /api/me/2fa/disable", s.withUser(s.totpDisable)) mux.HandleFunc("GET /api/me/resources", s.withUser(s.myResources)) mux.HandleFunc("GET /api/me/sessions", s.withUser(s.mySessions)) mux.HandleFunc("DELETE /api/me/sessions/{id}", s.withUser(s.endSession)) mux.HandleFunc("POST /api/me/sessions/revoke-others", s.withUser(s.endOtherSessions)) mux.HandleFunc("GET /api/edges", s.listEdges) // server administrators mux.HandleFunc("POST /api/edges", s.withServerAdmin(s.saveEdge)) mux.HandleFunc("PUT /api/edges/{id}", s.withServerAdmin(s.saveEdge)) mux.HandleFunc("DELETE /api/edges/{id}", s.withServerAdmin(s.deleteEdge)) mux.HandleFunc("GET /api/orgs", s.listOrgs) mux.HandleFunc("POST /api/orgs", s.withServerAdmin(s.createOrg)) mux.HandleFunc("GET /api/admin/apikeys", s.withServerAdminUser(s.listServerKeys)) mux.HandleFunc("POST /api/admin/apikeys", s.withServerAdminUser(s.createServerKey)) mux.HandleFunc("DELETE /api/admin/apikeys/{id}", s.withServerAdminUser(s.deleteServerKey)) mux.HandleFunc("GET /api/admin/users", s.withServerAdmin(s.adminUsers)) mux.HandleFunc("POST /api/admin/users", s.withServerAdmin(s.adminCreateUser)) mux.HandleFunc("PUT /api/admin/users/{id}", s.withServerAdmin(s.adminUpdateUser)) mux.HandleFunc("DELETE /api/admin/users/{id}", s.withServerAdmin(s.adminDeleteUser)) mux.HandleFunc("GET /api/admin/idps", s.withServerAdmin(s.listIdPs)) mux.HandleFunc("POST /api/admin/idps", s.withServerAdmin(s.saveIdP)) mux.HandleFunc("PUT /api/admin/idps/{id}", s.withServerAdmin(s.saveIdP)) mux.HandleFunc("DELETE /api/admin/idps/{id}", s.withServerAdmin(s.deleteIdP)) mux.HandleFunc("GET /api/admin/settings", s.withServerAdmin(s.getSettings)) mux.HandleFunc("PUT /api/admin/settings", s.withServerAdmin(s.putSettings)) mux.HandleFunc("POST /api/admin/settings/test-mail", s.withServerAdmin(s.testMail)) mux.HandleFunc("POST /api/admin/settings/sandbar-sync", s.withServerAdmin(s.syncSandbarNow)) mux.HandleFunc("GET /api/admin/update", s.withServerAdmin(s.checkUpdate)) mux.HandleFunc("POST /api/admin/update/apply", s.withServerAdmin(s.applyUpdate)) mux.HandleFunc("GET /api/admin/update/status", s.withServerAdmin(s.updateStatus)) mux.HandleFunc("GET /api/versions", s.withUser(s.latestVersions)) mux.HandleFunc("GET /api/edges/{id}/update/log", s.withServerAdmin(s.edgeUpdateLog)) mux.HandleFunc("GET /api/orgs/{org}/sites/{id}/update/log", s.withOrg(true, s.siteUpdateLog)) mux.HandleFunc("POST /api/edges/{id}/update", s.withServerAdmin(s.updateEdge)) mux.HandleFunc("POST /api/orgs/{org}/sites/{id}/update", s.withOrg(true, s.updateSite)) mux.HandleFunc("GET /api/admin/audit", s.withServerAdmin(func(w http.ResponseWriter, r *http.Request, p *principal) { writeJSON(w, 200, s.logs.queryAudit("", 300, 0)) })) // organization scope: members read, admins write mux.HandleFunc("GET /api/orgs/{org}/state", s.withOrg(false, s.orgState)) mux.HandleFunc("PUT /api/orgs/{org}", s.withOrg(true, s.updateOrg)) mux.HandleFunc("DELETE /api/orgs/{org}", s.withOrg(true, s.deleteOrg)) mux.HandleFunc("GET /api/orgs/{org}/roles", s.withOrg(false, s.listRoles)) mux.HandleFunc("POST /api/orgs/{org}/roles", s.withOrg(true, s.saveRole)) mux.HandleFunc("PUT /api/orgs/{org}/roles/{role}", s.withOrg(true, s.saveRole)) mux.HandleFunc("DELETE /api/orgs/{org}/roles/{role}", s.withOrg(true, s.deleteRole)) mux.HandleFunc("GET /api/orgs/{org}/members", s.withOrg(true, s.listMembers)) mux.HandleFunc("PUT /api/orgs/{org}/members/{user}", s.withOrg(true, s.setMemberRole)) mux.HandleFunc("DELETE /api/orgs/{org}/members/{user}", s.withOrg(true, s.removeMember)) mux.HandleFunc("GET /api/orgs/{org}/invites", s.withOrg(true, s.listInvites)) mux.HandleFunc("POST /api/orgs/{org}/invites", s.withOrg(true, s.createInvite)) mux.HandleFunc("DELETE /api/orgs/{org}/invites/{id}", s.withOrg(true, s.deleteInvite)) mux.HandleFunc("GET /api/orgs/{org}/apikeys", s.withOrg(true, s.listKeys)) mux.HandleFunc("POST /api/orgs/{org}/apikeys", s.withOrg(true, s.createKey)) mux.HandleFunc("DELETE /api/orgs/{org}/apikeys/{id}", s.withOrg(true, s.deleteKey)) mux.HandleFunc("POST /api/orgs/{org}/sites", s.withOrg(true, s.addSite)) mux.HandleFunc("PUT /api/orgs/{org}/sites/{id}", s.withOrg(true, s.putSite)) mux.HandleFunc("DELETE /api/orgs/{org}/sites/{id}", s.withOrg(true, s.deleteSite)) mux.HandleFunc("GET /api/orgs/{org}/sites/{id}/containers", s.withOrg(true, s.siteContainers)) mux.HandleFunc("GET /api/orgs/{org}/sites/{id}/discovered", s.withOrg(true, s.siteDiscovered)) mux.HandleFunc("POST /api/orgs/{org}/domains", s.withOrg(true, s.addDomain)) mux.HandleFunc("DELETE /api/orgs/{org}/domains/{id}", s.withOrg(true, s.deleteDomain)) mux.HandleFunc("POST /api/orgs/{org}/domains/{id}/check", s.withOrg(true, s.checkDomain)) mux.HandleFunc("POST /api/orgs/{org}/mappings", s.withOrg(true, s.addMapping)) mux.HandleFunc("PUT /api/orgs/{org}/mappings/{id}", s.withOrg(true, s.putMapping)) mux.HandleFunc("DELETE /api/orgs/{org}/mappings/{id}", s.withOrg(true, s.deleteMapping)) mux.HandleFunc("GET /api/orgs/{org}/mappings/{id}/shares", s.withOrg(true, s.listShares)) mux.HandleFunc("POST /api/orgs/{org}/mappings/{id}/shares", s.withOrg(true, s.createShare)) mux.HandleFunc("POST /api/orgs/{org}/mappings/{id}/revoke", s.withOrg(true, s.revokeVisitors)) mux.HandleFunc("DELETE /api/orgs/{org}/mappings/{id}/shares/{share}", s.withOrg(true, s.deleteShare)) mux.HandleFunc("POST /api/orgs/{org}/expose", s.withOrg(true, s.createExpose)) mux.HandleFunc("GET /api/orgs/{org}/expose", s.withOrg(true, s.listExpose)) mux.HandleFunc("GET /api/orgs/{org}/expose/{id}", s.withOrg(true, s.getExpose)) mux.HandleFunc("POST /api/orgs/{org}/expose/{id}/renew", s.withOrg(true, s.renewExpose)) mux.HandleFunc("DELETE /api/orgs/{org}/expose/{id}", s.withOrg(true, s.deleteExpose)) mux.HandleFunc("POST /api/orgs/{org}/mail-egress", s.withOrg(true, s.saveMailEgress)) mux.HandleFunc("PUT /api/orgs/{org}/mail-egress/{id}", s.withOrg(true, s.saveMailEgress)) mux.HandleFunc("DELETE /api/orgs/{org}/mail-egress/{id}", s.withOrg(true, s.deleteMailEgress)) mux.HandleFunc("GET /api/orgs/{org}/dkim", s.withOrg(true, s.listDKIM)) mux.HandleFunc("POST /api/orgs/{org}/dkim", s.withOrg(true, s.createDKIM)) mux.HandleFunc("DELETE /api/orgs/{org}/dkim/{id}", s.withOrg(true, s.deleteDKIM)) mux.HandleFunc("GET /api/orgs/{org}/dmarc", s.withOrg(true, s.listDMARC)) mux.HandleFunc("POST /api/orgs/{org}/dmarc", s.withOrg(true, s.uploadDMARC)) mux.HandleFunc("DELETE /api/orgs/{org}/dmarc/{domain}", s.withOrg(true, s.deleteDMARC)) mux.HandleFunc("GET /api/orgs/{org}/logs", s.withOrg(true, s.orgLogs)) mux.HandleFunc("GET /api/orgs/{org}/stats", s.withOrg(false, s.orgStats)) mux.HandleFunc("GET /api/orgs/{org}/traffic", s.withOrg(false, s.orgTraffic)) // agents mux.HandleFunc("GET /edge/config", s.edgeConfig) mux.HandleFunc("POST /edge/report", s.edgeReport) mux.HandleFunc("POST /edge/logs", s.edgeLogs) mux.HandleFunc("GET /site/config", s.siteConfig) mux.HandleFunc("POST /site/report", s.siteReport) return mux }