mirror of https://github.com/statping/statping
commit
78ab9af510
|
@ -1,4 +1,9 @@
|
|||
# Upcoming
|
||||
# 0.90.35 (05-01-2020)
|
||||
- Fixed issue with API endpoints cannot accepting Authorization header
|
||||
- Fixed issue with sass executable not being found, SASS environment var re-implemented
|
||||
- Added additional Postman API doc endpoints
|
||||
|
||||
# 0.90.34 (04-28-2020)
|
||||
- Added missing information to Mail notification ([#472](https://github.com/statping/statping/issues/472))
|
||||
- Added service.yml file to auto create services (https://github.com/statping/statping/wiki/services.yml)
|
||||
- Removed Core API_KEY, (unused code, use API_SECRET)
|
||||
|
|
|
@ -11,6 +11,7 @@ COPY --from=base /usr/local/share/ca-certificates /usr/local/share/
|
|||
WORKDIR /app
|
||||
|
||||
ENV IS_DOCKER=true
|
||||
ENV SASS=/usr/local/bin/sass
|
||||
ENV STATPING_DIR=/app
|
||||
ENV PORT=8080
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,51 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"github.com/statping/statping/types/core"
|
||||
"github.com/statping/statping/utils"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// hasSetupEnv checks to see if the GO_ENV is set to 'true'
|
||||
// or if the Statping instance has not been setup yet
|
||||
func hasSetupEnv() bool {
|
||||
if utils.Params.Get("GO_ENV") == "test" {
|
||||
return true
|
||||
}
|
||||
if core.App == nil {
|
||||
return true
|
||||
}
|
||||
if !core.App.Setup {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasAPIQuery checks the `api` query parameter against the API Secret Key
|
||||
func hasAPIQuery(r *http.Request) bool {
|
||||
query := r.URL.Query()
|
||||
key := query.Get("api")
|
||||
if key == "" {
|
||||
return false
|
||||
}
|
||||
if subtle.ConstantTimeCompare([]byte(key), []byte(core.App.ApiSecret)) == 1 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// hasAuthorizationHeader check to see if the Authorization header is the correct API Secret Key
|
||||
func hasAuthorizationHeader(r *http.Request) bool {
|
||||
var token string
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(core.App.ApiSecret)) == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/statping/statping/source"
|
||||
"github.com/statping/statping/types/errors"
|
||||
"github.com/statping/statping/types/users"
|
||||
"github.com/statping/statping/utils"
|
||||
"net/http"
|
||||
|
@ -36,7 +37,7 @@ type themeApi struct {
|
|||
Mobile string `json:"mobile"`
|
||||
}
|
||||
|
||||
func apiThemeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func apiThemeViewHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var base, variables, mobile, dir string
|
||||
assets := utils.Directory + "/assets"
|
||||
|
||||
|
@ -93,6 +94,12 @@ func apiThemeSaveHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func apiThemeCreateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
dir := utils.Params.GetString("STATPING_DIR")
|
||||
if source.UsingAssets(dir) {
|
||||
err := errors.New("assets have already been created")
|
||||
log.Errorln(err)
|
||||
sendErrorJson(err, w, r)
|
||||
return
|
||||
}
|
||||
utils.Log.Infof("creating assets in folder: %s/%s", dir, "assets")
|
||||
if err := source.CreateAllAssets(dir); err != nil {
|
||||
log.Errorln(err)
|
||||
|
|
|
@ -21,10 +21,8 @@ func findGroup(r *http.Request) (*groups.Group, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !g.Public.Bool {
|
||||
if !IsReadAuthenticated(r) {
|
||||
return nil, errors.NotAuthenticated
|
||||
}
|
||||
if !g.Public.Bool && !IsReadAuthenticated(r) {
|
||||
return nil, errors.NotAuthenticated
|
||||
}
|
||||
return g, nil
|
||||
}
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/statping/statping/types/core"
|
||||
"github.com/statping/statping/types/errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/statping/statping/source"
|
||||
|
@ -86,22 +83,14 @@ func RunHTTPServer(ip string, port int) error {
|
|||
|
||||
// IsReadAuthenticated will allow Read Only authentication for some routes
|
||||
func IsReadAuthenticated(r *http.Request) bool {
|
||||
if !core.App.Setup {
|
||||
return false
|
||||
}
|
||||
var token string
|
||||
query := r.URL.Query()
|
||||
key := query.Get("api")
|
||||
if subtle.ConstantTimeCompare([]byte(key), []byte(core.App.ApiSecret)) == 1 {
|
||||
if ok := hasSetupEnv(); ok {
|
||||
return true
|
||||
}
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(core.App.ApiSecret)) == 1 {
|
||||
return true
|
||||
}
|
||||
if ok := hasAPIQuery(r); ok {
|
||||
return true
|
||||
}
|
||||
if ok := hasAuthorizationHeader(r); ok {
|
||||
return true
|
||||
}
|
||||
return IsFullAuthenticated(r)
|
||||
}
|
||||
|
@ -109,23 +98,14 @@ func IsReadAuthenticated(r *http.Request) bool {
|
|||
// IsFullAuthenticated returns true if the HTTP request is authenticated. You can set the environment variable GO_ENV=test
|
||||
// to bypass the admin authenticate to the dashboard features.
|
||||
func IsFullAuthenticated(r *http.Request) bool {
|
||||
if utils.Params.Get("GO_ENV") == "test" {
|
||||
if ok := hasSetupEnv(); ok {
|
||||
return true
|
||||
}
|
||||
if core.App == nil {
|
||||
if ok := hasAPIQuery(r); ok {
|
||||
return true
|
||||
}
|
||||
if !core.App.Setup {
|
||||
return false
|
||||
}
|
||||
var token string
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
token = tokens[0]
|
||||
token = strings.TrimPrefix(token, "Bearer ")
|
||||
if subtle.ConstantTimeCompare([]byte(token), []byte(core.App.ApiSecret)) == 1 {
|
||||
return true
|
||||
}
|
||||
if ok := hasAuthorizationHeader(r); ok {
|
||||
return true
|
||||
}
|
||||
return IsAdmin(r)
|
||||
}
|
||||
|
@ -155,7 +135,15 @@ func getJwtToken(r *http.Request) (JwtClaim, error) {
|
|||
return claims, err
|
||||
}
|
||||
|
||||
// ScopeName will show private JSON fields in the API.
|
||||
// It will return "admin" if request has valid admin authentication.
|
||||
func ScopeName(r *http.Request) string {
|
||||
if ok := hasAPIQuery(r); ok {
|
||||
return "admin"
|
||||
}
|
||||
if ok := hasAuthorizationHeader(r); ok {
|
||||
return "admin"
|
||||
}
|
||||
claim, err := getJwtToken(r)
|
||||
if err != nil {
|
||||
return ""
|
||||
|
@ -168,12 +156,6 @@ func ScopeName(r *http.Request) string {
|
|||
|
||||
// IsAdmin returns true if the user session is an administrator
|
||||
func IsAdmin(r *http.Request) bool {
|
||||
if !core.App.Setup {
|
||||
return false
|
||||
}
|
||||
if utils.Params.GetString("GO_ENV") == "test" {
|
||||
return true
|
||||
}
|
||||
claim, err := getJwtToken(r)
|
||||
if err != nil {
|
||||
return false
|
||||
|
@ -183,10 +165,7 @@ func IsAdmin(r *http.Request) bool {
|
|||
|
||||
// IsUser returns true if the user is registered
|
||||
func IsUser(r *http.Request) bool {
|
||||
if !core.App.Setup {
|
||||
return false
|
||||
}
|
||||
if utils.Params.Get("GO_ENV") == "test" {
|
||||
if ok := hasSetupEnv(); ok {
|
||||
return true
|
||||
}
|
||||
tk, err := getJwtToken(r)
|
||||
|
|
|
@ -90,6 +90,9 @@ func sendLog(next http.Handler) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
// scoped is a middleware handler that will remove private fields based on struct tags
|
||||
// this will look for the `scope:"user,admin"` tag and remove the JSON field from response
|
||||
// if user is not authenticated based on the scope.
|
||||
func scoped(handler func(r *http.Request) interface{}) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
data := handler(r)
|
||||
|
|
|
@ -80,12 +80,12 @@ func Router() *mux.Router {
|
|||
api.Handle("/api/clear_cache", authenticated(apiClearCacheHandler, false))
|
||||
api.Handle("/api/core", authenticated(apiCoreHandler, false)).Methods("POST")
|
||||
api.Handle("/api/oauth", scoped(apiOAuthHandler)).Methods("GET")
|
||||
api.Handle("/api/oauth/{provider}", http.HandlerFunc(oauthHandler))
|
||||
api.Handle("/oauth/{provider}", http.HandlerFunc(oauthHandler))
|
||||
api.Handle("/api/logs", authenticated(logsHandler, false)).Methods("GET")
|
||||
api.Handle("/api/logs/last", authenticated(logsLineHandler, false)).Methods("GET")
|
||||
|
||||
// API SCSS and ASSETS Routes
|
||||
api.Handle("/api/theme", authenticated(apiThemeHandler, false)).Methods("GET")
|
||||
api.Handle("/api/theme", authenticated(apiThemeViewHandler, false)).Methods("GET")
|
||||
api.Handle("/api/theme", authenticated(apiThemeSaveHandler, false)).Methods("POST")
|
||||
api.Handle("/api/theme/create", authenticated(apiThemeCreateHandler, false)).Methods("GET")
|
||||
api.Handle("/api/theme", authenticated(apiThemeRemoveHandler, false)).Methods("DELETE")
|
||||
|
|
|
@ -23,8 +23,7 @@ func findService(r *http.Request) (*services.Service, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user := IsUser(r)
|
||||
if !servicer.Public.Bool && !user {
|
||||
if !servicer.Public.Bool && !IsReadAuthenticated(r) {
|
||||
return nil, errors.NotAuthenticated
|
||||
}
|
||||
return servicer, nil
|
||||
|
@ -216,10 +215,9 @@ func apiServiceDeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func apiAllServicesHandler(r *http.Request) interface{} {
|
||||
user := IsUser(r)
|
||||
var srvs []services.Service
|
||||
for _, v := range services.AllInOrder() {
|
||||
if !v.Public.Bool && !user {
|
||||
if !v.Public.Bool && !IsUser(r) {
|
||||
continue
|
||||
}
|
||||
srvs = append(srvs, v)
|
||||
|
|
|
@ -43,9 +43,14 @@ func scssRendered(name string) string {
|
|||
|
||||
// CompileSASS will attempt to compile the SASS files into CSS
|
||||
func CompileSASS(files ...string) error {
|
||||
sassBin, err := exec.LookPath("sass")
|
||||
if err != nil {
|
||||
return err
|
||||
sassBin := utils.Params.GetString("SASS")
|
||||
if sassBin == "" {
|
||||
bin, err := exec.LookPath("sass")
|
||||
if err != nil {
|
||||
log.Warnf("could not find sass executable in PATH: %s", err)
|
||||
return err
|
||||
}
|
||||
sassBin = bin
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
|
@ -61,11 +66,6 @@ func CompileSASS(files ...string) error {
|
|||
return errors.Wrapf(err, "failed to compile assets, %s %s %s", err, stdout, stderr)
|
||||
}
|
||||
|
||||
//if stdout != "" || stderr != "" {
|
||||
// log.Errorln(fmt.Sprintf("Failed to compile assets with SASS %v %v %v", err, stdout, stderr))
|
||||
// return errors.Wrap(err, "failed to capture stdout or stderr")
|
||||
//}
|
||||
|
||||
if stdout != "" || stderr != "" {
|
||||
log.Infoln(fmt.Sprintf("out: %v | error: %v", stdout, stderr))
|
||||
}
|
||||
|
@ -125,10 +125,18 @@ func CreateAllAssets(folder string) error {
|
|||
log.Infoln(fmt.Sprintf("Dump Statping assets into %v/assets", folder))
|
||||
fp := filepath.Join
|
||||
|
||||
MakePublicFolder(fp(folder, "/assets"))
|
||||
MakePublicFolder(fp(folder, "assets", "js"))
|
||||
MakePublicFolder(fp(folder, "assets", "css"))
|
||||
MakePublicFolder(fp(folder, "assets", "scss"))
|
||||
if err := MakePublicFolder(fp(folder, "/assets")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := MakePublicFolder(fp(folder, "assets", "js")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := MakePublicFolder(fp(folder, "assets", "css")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := MakePublicFolder(fp(folder, "assets", "scss")); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("Inserting scss, css, and javascript files into assets folder")
|
||||
|
||||
if err := CopyAllToPublic(TmplBox); err != nil {
|
||||
|
|
|
@ -53,6 +53,7 @@ func (c *Core) Create() error {
|
|||
MigrationId: utils.Now().Unix(),
|
||||
}
|
||||
q := db.Create(&newCore)
|
||||
utils.Log.Infof("API Key created: %s", secret)
|
||||
return q.Error()
|
||||
}
|
||||
|
||||
|
|
|
@ -26,14 +26,16 @@ func LoadServicesYaml() (*yamlFile, error) {
|
|||
log.Infof("Found %d services inside services.yml file", len(svrs.Services))
|
||||
|
||||
for _, svr := range svrs.Services {
|
||||
log.Infof("Service %s %d, hash: %s", svr.Name, svr.Id, svr.Hash())
|
||||
if findServiceByHash(svr.Hash()) == nil {
|
||||
serviceByHash := findServiceByHash(svr.Hash())
|
||||
if serviceByHash == nil {
|
||||
if err := svr.Create(); err != nil {
|
||||
return nil, errors.Wrapf(err, "could not create service %s", svr.Name)
|
||||
}
|
||||
log.Infof("Automatically created service '%s' checking %s", svr.Name, svr.Domain)
|
||||
log.Infof("Automatically creating service '%s' checking %s", svr.Name, svr.Domain)
|
||||
|
||||
go ServiceCheckQueue(svr, true)
|
||||
} else {
|
||||
log.Infof("Service %s #%d, already inserted", svr.Name, serviceByHash.Id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ func (t Timestamp) Ago() string {
|
|||
// Command will run a terminal command with 'sh -c COMMAND' and return stdout and errOut as strings
|
||||
// in, out, err := Command("sass assets/scss assets/css/base.css")
|
||||
func Command(name string, args ...string) (string, string, error) {
|
||||
Log.Info("running command: " + name + strings.Join(args, " "))
|
||||
Log.Info("Running command: " + name + " " + strings.Join(args, " "))
|
||||
testCmd := exec.Command(name, args...)
|
||||
var stdout, stderr []byte
|
||||
var errStdout, errStderr error
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.90.34
|
||||
0.90.35
|
||||
|
|
Loading…
Reference in New Issue