Merge pull request #727 from statping/dev

Login Fix
pull/740/head^2
Hunter Long 2020-07-04 13:49:58 -07:00 committed by GitHub
commit cbc394a39f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 232 additions and 143 deletions

View File

@ -1,3 +1,6 @@
# 0.90.57 (07-04-2020)
- Fixed login issue
# 0.90.56 (06-25-2020) # 0.90.56 (06-25-2020)
- Modified metrics now include service name for each service metric - Modified metrics now include service name for each service metric
- Added switch for true/false notifier values - Added switch for true/false notifier values

View File

@ -398,12 +398,17 @@ func ExportSettings() ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
var srvs []services.Service
for _, s := range services.AllInOrder() {
s.Failures = nil
srvs = append(srvs, s)
}
data := ExportData{ data := ExportData{
Core: c, Core: c,
//Notifiers: notifications.All(), Notifiers: core.App.Notifications,
Checkins: checkins.All(), Checkins: checkins.All(),
Users: users.All(), Users: users.All(),
Services: services.AllInOrder(), Services: srvs,
Groups: groups.All(), Groups: groups.All(),
Messages: messages.All(), Messages: messages.All(),
} }

View File

@ -3,13 +3,14 @@ import axios from 'axios'
import * as Sentry from "@sentry/browser"; import * as Sentry from "@sentry/browser";
import * as Integrations from "@sentry/integrations"; import * as Integrations from "@sentry/integrations";
const qs = require('querystring'); const qs = require('querystring');
axios.defaults.withCredentials = true
const tokenKey = "statping_auth"; const tokenKey = "statping_auth";
const errorReporter = "https://bed4d75404924cb3a799e370733a1b64@sentry.statping.com/3" const errorReporter = "https://bed4d75404924cb3a799e370733a1b64@sentry.statping.com/3"
class Api { class Api {
constructor() { constructor() {
axios.defaults.withCredentials = true
} }
async oauth() { async oauth() {

View File

@ -689,7 +689,7 @@ HTML,BODY {
transition: 0.2s all; transition: 0.2s all;
} }
.switch-rd-gr input:checked + label::before { .switch-rd-gr input:checked + label::before {
background-color: #cd141b; background-color: #29b10c !important;
} }
.switch input:checked + label::before { .switch input:checked + label::before {
background-color: #08d; background-color: #08d;

View File

@ -4,18 +4,42 @@
<div class="card contain-card text-black-50 bg-white mb-3"> <div class="card contain-card text-black-50 bg-white mb-3">
<div class="card-header text-capitalize"> <div class="card-header text-capitalize">
{{notifier.title}} {{notifier.title}}
<span @click="enableToggle" class="switch switch-rd-gr float-right"> <span @click="enableToggle" class="switch switch-sm switch-rd-gr float-right">
<input v-model="notifier.enabled" type="checkbox" class="switch-sm" :id="`enable_${notifier.method}`" v-bind:checked="notifier.enabled"> <input v-model="notifier.enabled" type="checkbox" :id="`enable_${notifier.method}`" v-bind:checked="notifier.enabled">
<label class="mb-0" :for="`enable_${notifier.method}`"></label> <label class="mb-0" :for="`enable_${notifier.method}`"></label>
</span> </span>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="small text-muted" v-html="notifier.description"/> <p class="small text-muted" v-html="notifier.description"/>
<div v-if="notifier.method==='mobile'" class="col-6 offset-3"> <div v-if="notifier.method==='mobile'">
<div class="form-group row mt-3">
<label for="domain" class="col-sm-4 col-form-label">Statping Domain</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="$store.getters.core.domain" type="text" class="form-control" id="domain" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy($store.getters.core.domain)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div>
</div>
</div>
<div class="form-group row mt-3">
<label for="apisecret" class="col-sm-4 col-form-label">API Secret</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="$store.getters.core.api_secret" type="text" class="form-control" id="apisecret" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy($store.getters.core.api_secret)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 offset-0 offset-md-3">
<img :src="qrcode" class="img-thumbnail"> <img :src="qrcode" class="img-thumbnail">
<span class="text-muted small center">Scan this QR Code on the Statping Mobile App for quick setup</span> <span class="text-muted small center">Scan this QR Code on the Statping Mobile App for quick setup</span>
</div> </div>
</div>
<div v-if="notifier.method!=='mobile'" v-for="(form, index) in notifier.form" v-bind:key="index" class="form-group"> <div v-if="notifier.method!=='mobile'" v-for="(form, index) in notifier.form" v-bind:key="index" class="form-group">
<label class="text-capitalize">{{form.title}}</label> <label class="text-capitalize">{{form.title}}</label>
@ -47,10 +71,11 @@
<div v-if="notifier.data_type" class="card text-black-50 bg-white mb-3"> <div v-if="notifier.data_type" class="card text-black-50 bg-white mb-3">
<div class="card-header text-capitalize"> <div class="card-header text-capitalize">
<font-awesome-icon @click="expanded = !expanded" :icon="expanded ? 'minus' : 'plus'" class="mr-2 pointer"/>
{{notifier.title}} Outgoing Request {{notifier.title}} Outgoing Request
<span class="badge badge-dark float-right text-uppercase mt-1">{{notifier.data_type}}</span> <span class="badge badge-dark float-right text-uppercase mt-1">{{notifier.data_type}}</span>
</div> </div>
<div class="card-body"> <div class="card-body" :class="{'d-none': !expanded}">
<span class="text-muted d-block mb-3" v-if="notifier.request_info" v-html="notifier.request_info"></span> <span class="text-muted d-block mb-3" v-if="notifier.request_info" v-html="notifier.request_info"></span>
<div class="row" v-observe-visibility="visible"> <div class="row" v-observe-visibility="visible">
@ -97,16 +122,16 @@
<div class="card text-black-50 bg-white mb-3"> <div class="card text-black-50 bg-white mb-3">
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-4 col-sm-4 mb-2 mb-sm-0 mt-2 mt-sm-0"> <div class="col-12 col-sm-4 mb-2 mb-sm-0 mt-2 mt-sm-0">
<button @click.prevent="saveNotifier" type="submit" class="btn btn-block text-capitalize btn-primary save-notifier"> <button @click.prevent="saveNotifier" :disabled="loading" type="submit" class="btn btn-block text-capitalize btn-primary save-notifier">
<i class="fa fa-check-circle"></i> {{loading ? "Loading..." : saved ? "Saved" : "Save Settings"}} <font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/> {{loading ? "Loading..." : saved ? "Saved" : "Save"}}
</button> </button>
</div> </div>
<div class="col-4 col-md-4"> <div class="col-12 col-md-4 mb-2 mb-sm-0 mt-2 mt-sm-0">
<button @click.prevent="testNotifier('success')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier"> <button @click.prevent="testNotifier('success')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
<font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Success"}}</button> <font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Success"}}</button>
</div> </div>
<div class="col-4 col-md-4"> <div class="col-12 col-md-4 mb-2 mb-sm-0 mt-2 mt-sm-0">
<button @click.prevent="testNotifier('failure')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier"> <button @click.prevent="testNotifier('failure')" :disabled="loadingTest" class="btn btn-outline-dark btn-block text-capitalize test-notifier">
<font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Failure"}}</button> <font-awesome-icon v-if="loadingTest" icon="circle-notch" class="mr-2" spin/>{{loadingTest ? "Loading..." : "Test Failure"}}</button>
</div> </div>
@ -157,6 +182,7 @@ export default {
request: null, request: null,
success: false, success: false,
saved: false, saved: false,
expanded: false,
success_data: null, success_data: null,
failure_data: null, failure_data: null,
form: {}, form: {},
@ -226,6 +252,7 @@ export default {
this.form.enabled = this.notifier.enabled this.form.enabled = this.notifier.enabled
this.form.limits = parseInt(this.notifier.limits) this.form.limits = parseInt(this.notifier.limits)
this.form.method = this.notifier.method this.form.method = this.notifier.method
if (this.notifier.form) {
this.notifier.form.forEach((f) => { this.notifier.form.forEach((f) => {
let field = f.field.toLowerCase() let field = f.field.toLowerCase()
let val = this.notifier[field] let val = this.notifier[field]
@ -234,6 +261,7 @@ export default {
} }
this.form[field] = val this.form[field] = val
}); });
}
this.form.success_data = this.success_data this.form.success_data = this.success_data
this.form.failure_data = this.failure_data this.form.failure_data = this.failure_data
await Api.notifier_save(this.form) await Api.notifier_save(this.form)
@ -246,6 +274,7 @@ export default {
this.success = false this.success = false
this.loadingTest = true this.loadingTest = true
this.form.method = this.notifier.method this.form.method = this.notifier.method
if (this.notifier.form) {
this.notifier.form.forEach((f) => { this.notifier.form.forEach((f) => {
let field = f.field.toLowerCase() let field = f.field.toLowerCase()
let val = this.notifier[field] let val = this.notifier[field]
@ -254,6 +283,7 @@ export default {
} }
this.form[field] = val this.form[field] = val
}); });
}
let req = { let req = {
notifier: this.form, notifier: this.form,
method: method, method: method,

View File

@ -1,23 +1,27 @@
<template> <template>
<form @submit.prevent="saveOAuth"> <form @submit.prevent="saveOAuth">
<div class="card text-black-50 bg-white mb-3"> <div class="card text-black-50 bg-white mb-3">
<div class="card-header">Internal Login</div> <div class="card-header">
<div class="card-body"> Internal Login
<div class="form-group row"> <span @click="local_enabled = !!local_enabled" class="switch switch-sm switch-rd-gr float-right">
<label for="switch-gh-oauth" class="col-sm-6 col-form-label">Statping Authentication</label> <input v-model="local_enabled" type="checkbox" id="switch-internal-oauth" :checked="local_enabled">
<div class="col-md-6 col-xs-12 mt-1"> <label for="switch-internal-oauth" class="mb-0"> </label>
<span @click="local_enabled = !!local_enabled" class="switch float-left">
<input v-model="local_enabled" type="checkbox" class="switch" id="switch-local-oauth" :checked="local_enabled">
<label for="switch-local-oauth"></label>
<span class="small d-block">Use email/password Authentication</span>
</span> </span>
</div> </div>
</div> <div class="card-body">
Use Statping's default authentication to allow users you've created to login.
</div> </div>
</div> </div>
<div class="card text-black-50 bg-white mb-3"> <div class="card text-black-50 bg-white mb-3">
<div class="card-header">Github Settings</div> <div class="card-header text-capitalize">
<div class="card-body"> <font-awesome-icon @click="expanded.github = !expanded.github" :icon="expanded.github ? 'minus' : 'plus'" class="mr-2 pointer"/>
Github Settings
<span @click="github_enabled = !!github_enabled" class="switch switch-sm switch-rd-gr float-right">
<input v-model="github_enabled" type="checkbox" id="switch-gh-oauth" :checked="github_enabled">
<label class="mb-0" for="switch-gh-oauth"> </label>
</span>
</div>
<div class="card-body" :class="{'d-none': !expanded.github}">
<span>You will need to create a new <a href="https://github.com/settings/developers">OAuth App</a> within Github.</span> <span>You will need to create a new <a href="https://github.com/settings/developers">OAuth App</a> within Github.</span>
<div class="form-group row mt-3"> <div class="form-group row mt-3">
@ -47,12 +51,9 @@
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="switch-gh-oauth" class="col-sm-4 col-form-label">Enable Github Login</label> <label class="col-sm-4 col-form-label">Enable Github Login</label>
<div class="col-md-8 col-xs-12 mt-1"> <div class="col-md-8 col-xs-12 mt-1">
<span @click="github_enabled = !!github_enabled" class="switch float-left">
<input v-model="github_enabled" type="checkbox" class="switch" id="switch-gh-oauth" :checked="github_enabled">
<label for="switch-gh-oauth"> </label>
</span>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
@ -69,8 +70,15 @@
</div> </div>
</div> </div>
<div class="card text-black-50 bg-white mb-3"> <div class="card text-black-50 bg-white mb-3">
<div class="card-header">Google Settings</div> <div class="card-header">
<div class="card-body"> <font-awesome-icon @click="expanded.google = !expanded.google" :icon="expanded.google ? 'minus' : 'plus'" class="mr-2 pointer"/>
Google Settings
<span @click="google_enabled = !!google_enabled" class="switch switch-sm switch-rd-gr float-right">
<input v-model="google_enabled" type="checkbox" id="switch-google-oauth" :checked="google_enabled">
<label for="switch-google-oauth" class="mb-0"> </label>
</span>
</div>
<div class="card-body" :class="{'d-none': !expanded.google}">
<span>Go to <a href="https://console.cloud.google.com/apis/credentials">OAuth Consent Screen</a> on Google Console to create a new "Web Application" OAuth application. </span> <span>Go to <a href="https://console.cloud.google.com/apis/credentials">OAuth Consent Screen</a> on Google Console to create a new "Web Application" OAuth application. </span>
<div class="form-group row mt-3"> <div class="form-group row mt-3">
@ -92,15 +100,6 @@
<small>Optional comma delimited list of emails and/or domains</small> <small>Optional comma delimited list of emails and/or domains</small>
</div> </div>
</div> </div>
<div class="form-group row">
<label for="switch-google-oauth" class="col-sm-4 col-form-label">Enable Google Login</label>
<div class="col-md-8 col-xs-12 mt-1">
<span @click="google_enabled = !!google_enabled" class="switch float-left">
<input v-model="google_enabled" type="checkbox" class="switch" id="switch-google-oauth" :checked="google_enabled">
<label for="switch-google-oauth"> </label>
</span>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label for="google_callback" class="col-sm-4 col-form-label">Callback URL</label> <label for="google_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -115,8 +114,15 @@
</div> </div>
</div> </div>
<div class="card text-black-50 bg-white mb-3"> <div class="card text-black-50 bg-white mb-3">
<div class="card-header">Slack Settings</div> <div class="card-header">
<div class="card-body"> <font-awesome-icon @click="expanded.slack = !expanded.slack" :icon="expanded.slack ? 'minus' : 'plus'" class="mr-2 pointer"/>
Slack Settings
<span @click="slack_enabled = !!slack_enabled" class="switch switch-sm switch-rd-gr float-right">
<input v-model="slack_enabled" type="checkbox" id="switch-slack-oauth" :checked="slack_enabled">
<label for="switch-slack-oauth" class="mb-0"> </label>
</span>
</div>
<div class="card-body" :class="{'d-none': !expanded.slack}">
<span>Go to <a href="https://api.slack.com/apps">Slack Apps</a> and create a new Application.</span> <span>Go to <a href="https://api.slack.com/apps">Slack Apps</a> and create a new Application.</span>
<div class="form-group row mt-3"> <div class="form-group row mt-3">
@ -145,15 +151,6 @@
<small>Optional comma delimited list of email addresses</small> <small>Optional comma delimited list of email addresses</small>
</div> </div>
</div> </div>
<div class="form-group row">
<label for="switch-slack-oauth" class="col-sm-4 col-form-label">Enable Slack Login</label>
<div class="col-md-8 col-xs-12 mt-1">
<span @click="slack_enabled = !!slack_enabled" class="switch float-left">
<input v-model="slack_enabled" type="checkbox" class="switch" id="switch-slack-oauth" :checked="slack_enabled">
<label for="switch-slack-oauth"> </label>
</span>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label for="slack_callback" class="col-sm-4 col-form-label">Callback URL</label> <label for="slack_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -169,8 +166,15 @@
</div> </div>
<div class="card text-black-50 bg-white mb-3"> <div class="card text-black-50 bg-white mb-3">
<div class="card-header">Custom oAuth Settings</div> <div class="card-header">
<div class="card-body"> <font-awesome-icon @click="expanded.custom = !expanded.custom" :icon="expanded.custom ? 'minus' : 'plus'" class="mr-2 pointer"/>
Custom oAuth Settings
<span @click="custom_enabled = !!custom_enabled" class="switch switch-sm switch-rd-gr float-right">
<input v-model="custom_enabled" type="checkbox" id="switch-custom-oauth" :checked="custom_enabled">
<label for="switch-custom-oauth" class="mb-0"> </label>
</span>
</div>
<div class="card-body" :class="{'d-none': !expanded.custom}">
<div class="form-group row mt-3"> <div class="form-group row mt-3">
<label for="custom_name" class="col-sm-4 col-form-label">Custom Name</label> <label for="custom_name" class="col-sm-4 col-form-label">Custom Name</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -208,15 +212,6 @@
<small>Optional comma delimited list of oauth scopes</small> <small>Optional comma delimited list of oauth scopes</small>
</div> </div>
</div> </div>
<div class="form-group row">
<label for="switch-custom-oauth" class="col-sm-4 col-form-label">Enable Custom Login</label>
<div class="col-md-8 col-xs-12 mt-1">
<span @click="custom_enabled = !!custom_enabled" class="switch float-left">
<input v-model="custom_enabled" type="checkbox" class="switch" id="switch-custom-oauth" :checked="custom_enabled">
<label for="switch-custom-oauth"> </label>
</span>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label for="slack_callback" class="col-sm-4 col-form-label">Callback URL</label> <label for="slack_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -231,7 +226,6 @@
</div> </div>
</div> </div>
<button class="btn btn-primary btn-block" @click.prevent="saveOAuth" type="submit" :disabled="loading"> <button class="btn btn-primary btn-block" @click.prevent="saveOAuth" type="submit" :disabled="loading">
<font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/> Save OAuth Settings <font-awesome-icon v-if="loading" icon="circle-notch" class="mr-2" spin/> Save OAuth Settings
</button> </button>
@ -257,6 +251,13 @@
local_enabled: false, local_enabled: false,
custom_enabled: false, custom_enabled: false,
loading: false, loading: false,
expanded: {
github: false,
google: false,
slack: false,
custom: false,
openid: false,
},
oauth: { oauth: {
gh_client_id: "", gh_client_id: "",
gh_client_secret: "", gh_client_secret: "",

View File

@ -32,11 +32,22 @@
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label class="col-sm-4 col-form-label">{{ $t('close') }}</label> <label class="col-sm-4 col-form-label">Confirm Password</label>
<div class="col-sm-8"> <div class="col-sm-8">
<input v-model="user.confirm_password" type="password" id="password_confirm" class="form-control" placeholder="Confirm Password" required> <input v-model="user.confirm_password" type="password" id="password_confirm" class="form-control" placeholder="Confirm Password" required>
</div> </div>
</div> </div>
<div v-if="user.api_key" class="form-group row">
<label for="user_key_key" class="col-sm-4 col-form-label">API Key</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="user.api_key" type="text" class="form-control" id="user_key_key" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy(user.api_key)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
</div>
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<div class="col-sm-12"> <div class="col-sm-12">
<LoadButton <LoadButton
@ -76,7 +87,8 @@
admin: false, admin: false,
email: "", email: "",
password: "", password: "",
confirm_password: "" confirm_password: "",
api_key: "",
} }
} }
}, },

View File

@ -15,7 +15,7 @@
<font-awesome-icon icon="paperclip" class="mr-2"/> {{ $t('settings.cache') }} <font-awesome-icon icon="paperclip" class="mr-2"/> {{ $t('settings.cache') }}
</a> </a>
<a @click.prevent="changeTab" class="nav-link" v-bind:class="{active: liClass('v-pills-oauth-tab')}" id="v-pills-oauth-tab" data-toggle="pill" href="#v-pills-oauth" role="tab" aria-controls="v-pills-oauth" aria-selected="false"> <a @click.prevent="changeTab" class="nav-link" v-bind:class="{active: liClass('v-pills-oauth-tab')}" id="v-pills-oauth-tab" data-toggle="pill" href="#v-pills-oauth" role="tab" aria-controls="v-pills-oauth" aria-selected="false">
<font-awesome-icon icon="key" class="mr-2"/> {{ $t('settings.oauth') }} <span class="mt-1 float-right badge badge-light text-dark font-1">{{ $t('settings.beta') }}</span> <font-awesome-icon icon="key" class="mr-2"/> {{ $t('settings.oauth') }}
</a> </a>
<h6 class="mt-4 text-muted">Notifiers</h6> <h6 class="mt-4 text-muted">Notifiers</h6>
@ -86,13 +86,6 @@
</div> </div>
</div> </div>
<div class="card text-black-50 bg-white mt-3">
<div class="card-header">QR Code for Mobile App</div>
<div class="card-body">
<img class="rounded" width="300" height="300" :src="qrcode">
</div>
</div>
</div> </div>
<div class="tab-pane fade" v-bind:class="{active: liClass('v-pills-style-tab'), show: liClass('v-pills-style-tab')}" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab"> <div class="tab-pane fade" v-bind:class="{active: liClass('v-pills-style-tab'), show: liClass('v-pills-style-tab')}" id="v-pills-style" role="tabpanel" aria-labelledby="v-pills-style-tab">
@ -149,7 +142,6 @@
data() { data() {
return { return {
tab: "v-pills-home-tab", tab: "v-pills-home-tab",
qrcode: "",
} }
}, },
computed: { computed: {
@ -172,9 +164,6 @@
this.$store.commit('setCore', c) this.$store.commit('setCore', c)
const n = await Api.notifiers() const n = await Api.notifiers()
this.$store.commit('setNotifiers', n) this.$store.commit('setNotifiers', n)
const u = `statping://setup?domain=${c.domain}&api=${c.api_secret}`
this.qrcode = "https://chart.googleapis.com/chart?chs=500x500&cht=qr&chl=" + encodeURIComponent(u)
this.cache = await Api.cache() this.cache = await Api.cache()
}, },
changeTab(e) { changeTab(e) {

View File

@ -264,7 +264,7 @@ func TestMainApiRoutes(t *testing.T) {
type HttpFuncTest func(*testing.T) error type HttpFuncTest func(*testing.T) error
type ResponseFunc func(*testing.T, []byte) error type ResponseFunc func(*httptest.ResponseRecorder, *testing.T, []byte) error
// HTTPTest contains all the parameters for a HTTP Unit Test // HTTPTest contains all the parameters for a HTTP Unit Test
type HTTPTest struct { type HTTPTest struct {
@ -350,7 +350,7 @@ func RunHTTPTest(test HTTPTest, t *testing.T) (string, *testing.T, error) {
assert.Nil(t, err) assert.Nil(t, err)
} }
if test.ResponseFunc != nil { if test.ResponseFunc != nil {
err := test.ResponseFunc(t, body) err := test.ResponseFunc(rr, t, body)
assert.Nil(t, err) assert.Nil(t, err)
} }

View File

@ -3,6 +3,7 @@ package handlers
import ( import (
"crypto/subtle" "crypto/subtle"
"github.com/statping/statping/types/core" "github.com/statping/statping/types/core"
"github.com/statping/statping/types/users"
"github.com/statping/statping/utils" "github.com/statping/statping/utils"
"net/http" "net/http"
"strings" "strings"
@ -33,6 +34,14 @@ func hasAPIQuery(r *http.Request) bool {
if subtle.ConstantTimeCompare([]byte(key), []byte(core.App.ApiSecret)) == 1 { if subtle.ConstantTimeCompare([]byte(key), []byte(core.App.ApiSecret)) == 1 {
return true return true
} }
// find user with API key
user, err := users.FindByAPIKey(key)
if err != nil {
return false
}
if subtle.ConstantTimeCompare([]byte(key), []byte(user.ApiKey)) == 1 {
return true
}
return false return false
} }

View File

@ -11,6 +11,7 @@ import (
type JwtClaim struct { type JwtClaim struct {
Username string `json:"username"` Username string `json:"username"`
Admin bool `json:"admin"` Admin bool `json:"admin"`
Scopes string `json:"scopes"`
jwt.StandardClaims jwt.StandardClaims
} }
@ -29,6 +30,7 @@ func setJwtToken(user *users.User, w http.ResponseWriter) (JwtClaim, string) {
jwtClaim := JwtClaim{ jwtClaim := JwtClaim{
Username: user.Username, Username: user.Username,
Admin: user.Admin.Bool, Admin: user.Admin.Bool,
Scopes: user.Scopes,
StandardClaims: jwt.StandardClaims{ StandardClaims: jwt.StandardClaims{
ExpiresAt: expirationTime.Unix(), ExpiresAt: expirationTime.Unix(),
}} }}

View File

@ -10,6 +10,7 @@ import (
"github.com/statping/statping/utils" "github.com/statping/statping/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"net/http/httptest"
"testing" "testing"
"time" "time"
) )
@ -214,7 +215,7 @@ func TestApiServiceRoutes(t *testing.T) {
URL: "/api/services/1/uptime_data" + startEndQuery, URL: "/api/services/1/uptime_data" + startEndQuery,
Method: "GET", Method: "GET",
ExpectedStatus: 200, ExpectedStatus: 200,
ResponseFunc: func(t *testing.T, resp []byte) error { ResponseFunc: func(req *httptest.ResponseRecorder, t *testing.T, resp []byte) error {
var uptime *services.UptimeSeries var uptime *services.UptimeSeries
if err := json.Unmarshal(resp, &uptime); err != nil { if err := json.Unmarshal(resp, &uptime); err != nil {
return err return err

View File

@ -1,6 +1,7 @@
package notifiers package notifiers
import ( import (
"github.com/statping/statping/types/errors"
"github.com/statping/statping/types/failures" "github.com/statping/statping/types/failures"
"github.com/statping/statping/types/notifications" "github.com/statping/statping/types/notifications"
"github.com/statping/statping/types/notifier" "github.com/statping/statping/types/notifier"
@ -28,44 +29,44 @@ var Command = &commandLine{&notifications.Notification{
AuthorUrl: "https://github.com/hunterlong", AuthorUrl: "https://github.com/hunterlong",
Delay: time.Duration(1 * time.Second), Delay: time.Duration(1 * time.Second),
Icon: "fas fa-terminal", Icon: "fas fa-terminal",
Host: "/bin/bash", SuccessData: "/usr/bin/curl -L http://localhost:8080",
SuccessData: "curl -L http://localhost:8080", FailureData: "/usr/bin/curl -L http://localhost:8080",
FailureData: "curl -L http://localhost:8080",
DataType: "text", DataType: "text",
Limits: 60, Limits: 60,
Form: []notifications.NotificationForm{{ }}
Type: "text",
Title: "Executable Path",
Placeholder: "/usr/bin/curl",
DbField: "host",
SmallText: "You can use '/bin/sh', '/bin/bash', '/usr/bin/curl' or an absolute path for an application.",
}}},
}
func runCommand(app string, cmd ...string) (string, string, error) { func runCommand(cmd string) (string, string, error) {
utils.Log.Infof("Command notifier sending: %s %s", app, strings.Join(cmd, " ")) utils.Log.Infof("Command notifier sending: %s", cmd)
outStr, errStr, err := utils.Command(app, cmd...) cmdApp := strings.Split(cmd, " ")
if len(cmd) == 0 {
return "", "", errors.New("you need at least 1 command")
}
var cmdArgs []string
if len(cmd) > 1 {
cmdArgs = append(cmdArgs, cmd[1:])
}
outStr, errStr, err := utils.Command(cmdApp[0], cmdArgs...)
return outStr, errStr, err return outStr, errStr, err
} }
// OnSuccess for commandLine will trigger successful service // OnSuccess for commandLine will trigger successful service
func (c *commandLine) OnSuccess(s services.Service) (string, error) { func (c *commandLine) OnSuccess(s services.Service) (string, error) {
tmpl := ReplaceVars(c.SuccessData, s, failures.Failure{}) tmpl := ReplaceVars(c.SuccessData, s, failures.Failure{})
out, _, err := runCommand(c.Host, tmpl) out, _, err := runCommand(tmpl)
return out, err return out, err
} }
// OnFailure for commandLine will trigger failing service // OnFailure for commandLine will trigger failing service
func (c *commandLine) OnFailure(s services.Service, f failures.Failure) (string, error) { func (c *commandLine) OnFailure(s services.Service, f failures.Failure) (string, error) {
tmpl := ReplaceVars(c.FailureData, s, f) tmpl := ReplaceVars(c.FailureData, s, f)
_, ouerr, err := runCommand(c.Host, tmpl) out, _, err := runCommand(tmpl)
return ouerr, err return out, err
} }
// OnTest for commandLine triggers when this notifier has been saved // OnTest for commandLine triggers when this notifier has been saved
func (c *commandLine) OnTest() (string, error) { func (c *commandLine) OnTest() (string, error) {
tmpl := ReplaceVars(c.Var1, services.Example(true), failures.Example()) tmpl := ReplaceVars(c.Var1, services.Example(true), failures.Example())
in, out, err := runCommand(c.Host, tmpl) in, out, err := runCommand(tmpl)
utils.Log.Infoln(in) utils.Log.Infoln(in)
utils.Log.Infoln(out) utils.Log.Infoln(out)
return out, err return out, err

View File

@ -39,7 +39,7 @@ var statpingMailer = &statpingEmailer{&notifications.Notification{
Form: []notifications.NotificationForm{{ Form: []notifications.NotificationForm{{
Type: "email", Type: "email",
Title: "Send to Email Address", Title: "Send to Email Address",
Placeholder: "Insert your email address", Placeholder: "info@statping.com",
DbField: "Host", DbField: "Host",
Required: true, Required: true,
}}}, }}},

View File

@ -88,6 +88,7 @@ func CreateAdminUser(c *DbConfig) error {
Username: adminUser, Username: adminUser,
Password: adminPass, Password: adminPass,
Email: adminEmail, Email: adminEmail,
Scopes: "admin",
Admin: null.NewNullBool(true), Admin: null.NewNullBool(true),
} }

View File

@ -47,22 +47,8 @@ func Select() (*Core, error) {
} }
func (c *Core) Create() error { func (c *Core) Create() error {
secret := utils.Params.GetString("API_SECRET") q := db.Create(c)
if secret == "" { utils.Log.Infof("API Key created: %s", c.ApiSecret)
secret = utils.RandomString(32)
}
newCore := &Core{
Name: c.Name,
Description: c.Description,
ConfigFile: utils.Directory + "/config.yml",
ApiSecret: secret,
Version: App.Version,
Domain: c.Domain,
Language: c.Language,
MigrationId: utils.Now().Unix(),
}
q := db.Create(&newCore)
utils.Log.Infof("API Key created: %s", secret)
return q.Error() return q.Error()
} }

View File

@ -41,6 +41,7 @@ func Samples() error {
MigrationId: utils.Now().Unix(), MigrationId: utils.Now().Unix(),
Language: utils.Params.GetString("LANGUAGE"), Language: utils.Params.GetString("LANGUAGE"),
OAuth: oauth, OAuth: oauth,
Version: utils.Version,
} }
return core.Create() return core.Create()

View File

@ -43,6 +43,12 @@ func FindByUsername(username string) (*User, error) {
return &user, q.Error() return &user, q.Error()
} }
func FindByAPIKey(key string) (*User, error) {
var user User
q := db.Where("api_key = ?", key).Find(&user)
return &user, q.Error()
}
func All() []*User { func All() []*User {
var users []*User var users []*User
db.Find(&users) db.Find(&users)

View File

@ -5,6 +5,5 @@ import "github.com/statping/statping/utils"
func (u *User) BeforeCreate() error { func (u *User) BeforeCreate() error {
u.Password = utils.HashPassword(u.Password) u.Password = utils.HashPassword(u.Password)
u.ApiKey = utils.NewSHA256Hash() u.ApiKey = utils.NewSHA256Hash()
u.ApiSecret = utils.NewSHA256Hash()
return nil return nil
} }

View File

@ -10,6 +10,7 @@ func Samples() error {
Username: "testadmin", Username: "testadmin",
Password: "password123", Password: "password123",
Email: "info@betatude.com", Email: "info@betatude.com",
Scopes: "admin",
Admin: null.NewNullBool(true), Admin: null.NewNullBool(true),
} }
@ -21,6 +22,7 @@ func Samples() error {
Username: "testadmin2", Username: "testadmin2",
Password: "password123", Password: "password123",
Email: "info@adminhere.com", Email: "info@adminhere.com",
Scopes: "admin",
Admin: null.NewNullBool(true), Admin: null.NewNullBool(true),
} }

43
types/users/scopes.go Normal file
View File

@ -0,0 +1,43 @@
package users
import "strings"
type Scope string
const (
FullAdmin Scope = "admin"
ReadOnly Scope = "readonly"
RServices Scope = "read:services"
RWServices Scope = "write:services"
RIncidents Scope = "read:incidents"
RWIncidents Scope = "write:incidents"
EmptyUser Scope = "none"
)
func namedScope(name string) Scope {
switch name {
case "admin":
return FullAdmin
case "readonly":
return ReadOnly
case "read:services":
return RServices
case "write:services":
return RWServices
case "read:incidents":
return RIncidents
case "write:incidents":
return RWIncidents
default:
return EmptyUser
}
}
func (u *User) AllScopes() []Scope {
var scopes []Scope
for _, s := range strings.Split(u.Scopes, ",") {
scopes = append(scopes, namedScope(s))
}
return scopes
}

View File

@ -12,7 +12,7 @@ type User struct {
Password string `gorm:"column:password" json:"password,omitempty"` Password string `gorm:"column:password" json:"password,omitempty"`
Email string `gorm:"type:varchar(100);column:email" json:"email,omitempty"` Email string `gorm:"type:varchar(100);column:email" json:"email,omitempty"`
ApiKey string `gorm:"column:api_key" json:"api_key,omitempty"` ApiKey string `gorm:"column:api_key" json:"api_key,omitempty"`
ApiSecret string `gorm:"column:api_secret" json:"api_secret,omitempty"` Scopes string `gorm:"column:scopes" json:"scopes,omitempty"`
Admin null.NullBool `gorm:"column:administrator" json:"admin,omitempty"` Admin null.NullBool `gorm:"column:administrator" json:"admin,omitempty"`
CreatedAt time.Time `gorm:"column:created_at" json:"created_at"` CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"` UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`

View File

@ -31,7 +31,6 @@ func TestFind(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, "example_user", item.Username) assert.Equal(t, "example_user", item.Username)
assert.NotEmpty(t, item.ApiKey) assert.NotEmpty(t, item.ApiKey)
assert.NotEmpty(t, item.ApiSecret)
assert.NotEqual(t, "password123", item.Password) assert.NotEqual(t, "password123", item.Password)
assert.True(t, item.Admin.Bool) assert.True(t, item.Admin.Bool)
} }
@ -41,7 +40,6 @@ func TestFindByUsername(t *testing.T) {
require.Nil(t, err) require.Nil(t, err)
assert.Equal(t, "example_user", item.Username) assert.Equal(t, "example_user", item.Username)
assert.NotEmpty(t, item.ApiKey) assert.NotEmpty(t, item.ApiKey)
assert.NotEmpty(t, item.ApiSecret)
assert.NotEqual(t, "password123", item.Password) assert.NotEqual(t, "password123", item.Password)
assert.True(t, item.Admin.Bool) assert.True(t, item.Admin.Bool)
} }
@ -64,7 +62,6 @@ func TestCreate(t *testing.T) {
assert.NotEqual(t, "password12345", example.Password) assert.NotEqual(t, "password12345", example.Password)
assert.NotZero(t, example.CreatedAt) assert.NotZero(t, example.CreatedAt)
assert.NotEmpty(t, example.ApiKey) assert.NotEmpty(t, example.ApiKey)
assert.NotEmpty(t, example.ApiSecret)
} }
func TestUpdate(t *testing.T) { func TestUpdate(t *testing.T) {

View File

@ -1 +1 @@
0.90.56 0.90.57