checkin form fix, user/token route for auth

pull/773/head
hunterlong 2020-08-03 17:31:42 -07:00
parent aeaba54f82
commit 79b6e620bf
19 changed files with 302 additions and 97 deletions

View File

@ -7,6 +7,7 @@
- Modified SCSS/SASS files to be generated from 1, main.scss to main.css - Modified SCSS/SASS files to be generated from 1, main.scss to main.css
- Modified index page to use /assets directory for assets, (main.css, style.css) - Modified index page to use /assets directory for assets, (main.css, style.css)
- Modified index page to use CDN asset paths - Modified index page to use CDN asset paths
- Fixed New Checkin form
# 0.90.61 (07-22-2020) # 0.90.61 (07-22-2020)
- Modified sass layouts, organized and split up sections - Modified sass layouts, organized and split up sections

131
dev/postman.json vendored
View File

@ -3275,6 +3275,8 @@
"pm.test(\"Check Login JWT Token\", function () {", "pm.test(\"Check Login JWT Token\", function () {",
" var jsonData = pm.response.json();", " var jsonData = pm.response.json();",
" pm.expect(jsonData).to.have.property('token');", " pm.expect(jsonData).to.have.property('token');",
" pm.expect(jsonData).to.have.property('admin');",
" pm.globals.set(\"token\", jsonData.token);",
"});" "});"
], ],
"type": "text/javascript" "type": "text/javascript"
@ -3371,24 +3373,141 @@
"_postman_previewlanguage": "json", "_postman_previewlanguage": "json",
"header": [ "header": [
{ {
"key": "Content-Length", "key": "Content-Type",
"value": "174" "value": "application/json"
}, },
{
"key": "Set-Cookie",
"value": "statping_auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsInNjb3BlcyI6ImFkbWluIiwiZXhwIjoxNTk2NzQzMDUzfQ.dQQGgUDhFEjCL2Gi-Seg0hBp_sqVsDn3cXB0GpSorJI; Path=/; Expires=Thu, 06 Aug 2020 19:44:13 GMT; Max-Age=259200"
},
{
"key": "Date",
"value": "Mon, 03 Aug 2020 19:44:13 GMT"
},
{
"key": "Content-Length",
"value": "197"
},
{
"key": "Connection",
"value": "close"
}
],
"cookie": [],
"body": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsInNjb3BlcyI6ImFkbWluIiwiZXhwIjoxNTk2NzQzMDUzfQ.dQQGgUDhFEjCL2Gi-Seg0hBp_sqVsDn3cXB0GpSorJI\",\n \"admin\": true\n}"
}
]
},
{
"name": "Check User Token",
"event": [
{
"listen": "test",
"script": {
"id": "560e439b-d588-4a2f-a8a6-a0607531d74c",
"exec": [
"pm.test(\"Response is ok\", function () {",
" pm.response.to.have.status(200);",
"});",
"",
"pm.test(\"View Token Response\", function () {",
" var jsonData = pm.response.json();",
" pm.expect(jsonData.username).to.eql(\"admin\");",
" pm.expect(jsonData.admin).to.eql(true);",
" pm.expect(jsonData.scopes).to.eql(\"admin\");",
"});"
],
"type": "text/javascript"
}
}
],
"request": {
"auth": {
"type": "bearer",
"bearer": [
{
"key": "token",
"value": "{{api_key}}",
"type": "string"
}
]
},
"method": "POST",
"header": [],
"body": {
"mode": "urlencoded",
"urlencoded": [
{
"key": "token",
"value": "{{token}}",
"type": "text"
}
]
},
"url": {
"raw": "{{endpoint}}/api/users/token",
"host": [
"{{endpoint}}"
],
"path": [
"api",
"users",
"token"
]
},
"description": "Send your JWT token from login to this endpoint to return the JSON values."
},
"response": [
{
"name": "Check User Token",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "urlencoded",
"urlencoded": [
{
"key": "token",
"value": "{{token}}",
"type": "text"
}
]
},
"url": {
"raw": "{{endpoint}}/api/users/token",
"host": [
"{{endpoint}}"
],
"path": [
"api",
"users",
"token"
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{ {
"key": "Content-Type", "key": "Content-Type",
"value": "application/json" "value": "application/json"
}, },
{ {
"key": "Date", "key": "Date",
"value": "Sat, 02 May 2020 00:56:17 GMT" "value": "Mon, 03 Aug 2020 19:47:23 GMT"
}, },
{ {
"key": "Set-Cookie", "key": "Content-Length",
"value": "statping_auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsImV4cCI6MTU4ODY0MDE3N30.tf399_LfAphSGlKMtgphg6qpPrn-_w92XfCrK5FwbZY; Expires=Tue, 05 May 2020 00:56:17 GMT" "value": "68"
},
{
"key": "Connection",
"value": "close"
} }
], ],
"cookie": [], "cookie": [],
"body": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiYWRtaW4iOnRydWUsImV4cCI6MTU4ODY0MDE3N30.tf399_LfAphSGlKMtgphg6qpPrn-_w92XfCrK5FwbZY\",\n \"admin\": true\n}" "body": "{\n \"username\": \"admin\",\n \"admin\": true,\n \"scopes\": \"admin\",\n \"exp\": 1596743053\n}"
} }
] ]
}, },

View File

@ -235,6 +235,11 @@ class Api {
return axios.post('api/theme', data).then(response => (response.data)) return axios.post('api/theme', data).then(response => (response.data))
} }
async check_token(token) {
const f = {token: token}
return axios.post('api/users/token', qs.stringify(f)).then(response => (response.data))
}
async login(username, password) { async login(username, password) {
const f = {username: username, password: password} const f = {username: username, password: password}
return axios.post('api/login', qs.stringify(f)).then(response => (response.data)) return axios.post('api/login', qs.stringify(f)).then(response => (response.data))

View File

@ -15,7 +15,7 @@ A:HOVER {
} }
.text-muted { .text-muted {
color: darken($text-color, 30%) !important; color: lighten($text-color, 30%) !important;
} }
.day-success { .day-success {

View File

@ -15,7 +15,7 @@ $navbar-background: #ffffff;
$input-background: #fdfdfd; $input-background: #fdfdfd;
$input-color: #4e4e4e; $input-color: #4e4e4e;
$input-border: 1px solid #c9c9c9; $input-border: 1px solid #c9c9c9;
$day-success-background: #18ce08; $day-success-background: #20ac13;
$day-error-background: #d50a0a; $day-error-background: #d50a0a;
/* Status Container */ /* Status Container */

View File

@ -9,7 +9,6 @@
<button @click="deleteCheckin(checkin)" class="btn btn-sm small btn-danger float-right text-uppercase">Delete</button> <button @click="deleteCheckin(checkin)" class="btn btn-sm small btn-danger float-right text-uppercase">Delete</button>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" :value="`${core.domain}/checkin/${checkin.api_key}`" readonly> <input type="text" class="form-control" :value="`${core.domain}/checkin/${checkin.api_key}`" readonly>
<div class="input-group-append copy-btn"> <div class="input-group-append copy-btn">
@ -137,6 +136,9 @@ export default {
}, },
last_record(checkin) { last_record(checkin) {
const r = this.records(checkin) const r = this.records(checkin)
if (r.length === 0) {
return {success: false}
}
return r[0] return r[0]
}, },
fixInts() { fixInts() {

View File

@ -24,10 +24,6 @@
<label for="checkin_interval" class="col-form-label">Interval (minutes)</label> <label for="checkin_interval" class="col-form-label">Interval (minutes)</label>
<input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="1" min="1"> <input v-model="checkin.interval" type="number" name="interval" class="form-control" id="checkin_interval" placeholder="1" min="1">
</div> </div>
<div class="col-12 col-md-5">
<label for="grace_period" class="col-form-label">Grace Period</label>
<input v-model="checkin.grace" type="number" name="grace" class="form-control" id="grace_period" placeholder="10">
</div>
<div class="col-12 col-md-5"> <div class="col-12 col-md-5">
<label class="col-form-label"></label> <label class="col-form-label"></label>
<button @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-success d-block mt-2">Save Checkin</button> <button @click.prevent="saveCheckin" type="submit" id="submit" class="btn btn-success d-block mt-2">Save Checkin</button>
@ -54,7 +50,6 @@
checkin: { checkin: {
name: "", name: "",
interval: 60, interval: 60,
grace: 60,
service_id: this.service.id service_id: this.service.id
} }
} }

View File

@ -5,11 +5,9 @@
No updates found, create a new Incident Update below. No updates found, create a new Incident Update below.
</div> </div>
<transition-group name="fade" tag="div"> <div v-for="update in updates.reverse()" :key="update.id">
<div v-for="update in updates.reverse()" :key="update.id"> <IncidentUpdate :update="update" :onUpdate="loadUpdates" :admin="true"/>
<IncidentUpdate :update="update" :onUpdate="loadUpdates" :admin="true"/> </div>
</div>
</transition-group>
<form class="row" @submit.prevent="createIncidentUpdate"> <form class="row" @submit.prevent="createIncidentUpdate">
<div class="col-12 col-md-3 mb-3 mb-md-0"> <div class="col-12 col-md-3 mb-3 mb-md-0">
@ -51,7 +49,7 @@
}, },
data () { data () {
return { return {
updates: [], updates: null,
incident_update: { incident_update: {
incident: this.incident.id, incident: this.incident.id,
message: "", message: "",

View File

@ -4,13 +4,13 @@
<div class="form-group row"> <div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">{{$t('username')}}</label> <label for="username" class="col-sm-2 col-form-label">{{$t('username')}}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input @keyup="checkForm" type="text" v-model="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none"> <input @keyup="checkForm" type="text" v-model="username" autocomplete="username" name="username" class="form-control" id="username" placeholder="Username" autocorrect="off" autocapitalize="none">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="password" class="col-sm-2 col-form-label">{{$t('password')}}</label> <label for="password" class="col-sm-2 col-form-label">{{$t('password')}}</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input @keyup="checkForm" type="password" v-model="password" name="password" class="form-control" id="password" placeholder="Password"> <input @keyup="checkForm" type="password" v-model="password" autocomplete="current-password" name="password" class="form-control" id="password" placeholder="Password">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
@ -46,6 +46,7 @@
<script> <script>
import Api from "../API"; import Api from "../API";
import store from "@/store";
export default { export default {
name: 'FormLogin', name: 'FormLogin',
@ -59,17 +60,20 @@
}, },
data() { data() {
return { return {
username: "", username: "",
password: "", password: "",
auth: {}, auth: {},
loading: false, loading: false,
error: false, error: false,
disabled: true, disabled: true,
google_scope: "https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email", google_scope: "https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email",
slack_scope: "identity.email,identity.basic" slack_scope: "identity.email,identity.basic"
} }
}, },
methods: { mounted() {
this.$cookies.remove("statping_auth")
},
methods: {
checkForm() { checkForm() {
if (!this.username || !this.password) { if (!this.username || !this.password) {
this.disabled = true this.disabled = true
@ -84,9 +88,10 @@
if (auth.error) { if (auth.error) {
this.error = true this.error = true
} else if (auth.token) { } else if (auth.token) {
// this.$cookies.set("statping_auth", auth.token) this.$cookies.set("statping_auth", auth.token)
await this.$store.dispatch('loadAdmin') await this.$store.dispatch('loadAdmin')
this.$store.commit('setAdmin', auth.admin) this.$store.commit('setAdmin', auth.admin)
this.$store.commit('setLoggedIn', true)
this.$router.push('/dashboard') this.$router.push('/dashboard')
} }
this.loading = false this.loading = false

View File

@ -14,10 +14,10 @@
<div v-if="notifier.method==='mobile'"> <div v-if="notifier.method==='mobile'">
<div class="form-group row mt-3"> <div class="form-group row mt-3">
<label for="domain" class="col-sm-4 col-form-label">Statping Domain</label> <label for="statping_domain" class="col-sm-4 col-form-label">Statping Domain</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="input-group"> <div class="input-group">
<input v-bind:value="$store.getters.core.domain" type="text" class="form-control" id="domain" readonly> <input v-bind:value="$store.getters.core.domain" type="text" class="form-control" id="statping_domain" readonly>
<div class="input-group-append copy-btn"> <div class="input-group-append copy-btn">
<button @click.prevent="copy($store.getters.core.domain)" class="btn btn-outline-secondary" type="button">Copy</button> <button @click.prevent="copy($store.getters.core.domain)" class="btn btn-outline-secondary" type="button">Copy</button>
</div> </div>

View File

@ -106,10 +106,6 @@ export default Vue.mixin({
isAdmin() { isAdmin() {
return this.$store.state.admin return this.$store.state.admin
}, },
loggedIn() {
const core = this.$store.getters.core
return core.logged_in === true
},
iconName(name) { iconName(name) {
switch (name) { switch (name) {
case "fas fa-terminal": case "fas fa-terminal":

View File

@ -32,6 +32,9 @@
</template> </template>
<script> <script>
import Api from "@/API";
import store from "@/store";
const Group = () => import('@/components/Index/Group') const Group = () => import('@/components/Index/Group')
const Header = () => import('@/components/Index/Header') const Header = () => import('@/components/Index/Header')
const MessageBlock = () => import('@/components/Index/MessageBlock') const MessageBlock = () => import('@/components/Index/MessageBlock')
@ -44,10 +47,10 @@ export default {
components: { components: {
IncidentsBlock, IncidentsBlock,
GroupServiceFailures, GroupServiceFailures,
ServiceBlock, ServiceBlock,
MessageBlock, MessageBlock,
Group, Group,
Header Header
}, },
data() { data() {
return { return {
@ -67,14 +70,24 @@ export default {
services_no_group() { services_no_group() {
return this.$store.getters.servicesNoGroup return this.$store.getters.servicesNoGroup
} }
},
async created() {
this.logged_in = this.loggedIn()
},
async mounted() {
}, },
methods: { methods: {
async checkLogin() {
const token = this.$cookies.get('statping_auth')
if (!token) {
this.$store.commit('setLoggedIn', false)
return
}
try {
const jwt = await Api.check_token(token)
this.$store.commit('setAdmin', jwt.admin)
if (jwt.username) {
this.$store.commit('setLoggedIn', true)
}
} catch (e) {
console.error(e)
}
},
inRange(message) { inRange(message) {
return this.isBetween(this.now(), message.start_on, message.start_on === message.end_on ? this.maxDate().toISOString() : message.end_on) return this.isBetween(this.now(), message.start_on, message.start_on === message.end_on ? this.maxDate().toISOString() : message.end_on)
} }

View File

@ -22,7 +22,10 @@
} }
}, },
methods: { mounted() {
},
methods: {
} }
} }

View File

@ -17,6 +17,7 @@ const NotFound = () => import('@/pages/NotFound')
import VueRouter from "vue-router"; import VueRouter from "vue-router";
import Api from "./API"; import Api from "./API";
import store from "./store"
const Loading = { const Loading = {
template: '<div class="jumbotron">LOADING</div>' template: '<div class="jumbotron">LOADING</div>'
@ -26,7 +27,10 @@ const routes = [
{ {
path: '/setup', path: '/setup',
name: 'Setup', name: 'Setup',
component: Setup component: Setup,
meta: {
title: 'Statping Setup',
}
}, },
{ {
path: '/', path: '/',
@ -37,14 +41,37 @@ const routes = [
path: '/dashboard', path: '/dashboard',
component: Dashboard, component: Dashboard,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Dashboard',
}, },
beforeEnter: async (to, from, next) => { beforeEnter: async (to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) { if (to.matched.some(record => record.meta.requiresAuth)) {
let tk = await Api.token() if (to.path !== '/login') {
if (to.path !== '/login' && !tk) { if(store.getters.loggedIn) {
next('/login') next()
return return
}
const token = $cookies.get('statping_auth')
if (!token) {
next('/login')
return
}
try {
const jwt = await Api.check_token(token)
store.commit('setAdmin', jwt.admin)
if (jwt.admin) {
store.commit('setLoggedIn', true)
store.commit('setUser', true)
} else {
store.commit('setLoggedIn', false)
next('/login')
return
}
} catch (e) {
console.error(e)
next('/login')
return
}
} }
next() next()
} else { } else {
@ -55,81 +82,96 @@ const routes = [
path: '', path: '',
component: DashboardIndex, component: DashboardIndex,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Dashboard',
} }
},{ },{
path: 'users', path: 'users',
component: DashboardUsers, component: DashboardUsers,
loading: Loading, loading: Loading,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Users',
} }
},{ },{
path: 'services', path: 'services',
component: DashboardServices, component: DashboardServices,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Services',
} }
},{ },{
path: 'create_service', path: 'create_service',
component: EditService, component: EditService,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Create Service',
} }
},{ },{
path: 'edit_service/:id', path: 'edit_service/:id',
component: EditService, component: EditService,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Edit Service',
} }
},{ },{
path: 'service/:id/incidents', path: 'service/:id/incidents',
component: Incidents, component: Incidents,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Incidents',
} }
},{ },{
path: 'service/:id/checkins', path: 'service/:id/checkins',
component: Checkins, component: Checkins,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Checkins',
} }
},{ },{
path: 'service/:id/failures', path: 'service/:id/failures',
component: Failures, component: Failures,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Service Failures',
} }
},{ },{
path: 'messages', path: 'messages',
component: DashboardMessages, component: DashboardMessages,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Messages',
} }
},{ },{
path: 'settings', path: 'settings',
component: Settings, component: Settings,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Settings',
} }
},{ },{
path: 'logs', path: 'logs',
component: Logs, component: Logs,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Logs',
} }
},{ },{
path: 'help', path: 'help',
component: Logs, component: Logs,
meta: { meta: {
requiresAuth: true requiresAuth: true,
title: 'Statping - Help',
} }
}] }]
}, },
{ {
path: '/login', path: '/login',
name: 'Login', name: 'Login',
component: Login component: Login,
meta: {
title: 'Statping - Login',
}
}, },
{ path: '/logout', redirect: '/' }, { path: '/logout', redirect: '/' },
{ {
@ -157,23 +199,23 @@ const router = new VueRouter({
routes routes
}) })
let CheckAuth = (to, from, next) => { router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) { const nearestWithTitle = to.matched.slice().reverse().find(r => r.meta && r.meta.title);
let item = this.$cookies.get("statping_auth") const nearestWithMeta = to.matched.slice().reverse().find(r => r.meta && r.meta.metaTags);
window.console.log(item) const previousNearestWithMeta = from.matched.slice().reverse().find(r => r.meta && r.meta.metaTags);
if (to.path !== '/login' && !item) { if(nearestWithTitle) document.title = nearestWithTitle.meta.title;
next('/login') Array.from(document.querySelectorAll('[data-vue-router-controlled]')).map(el => el.parentNode.removeChild(el));
return if(!nearestWithMeta) return next();
} nearestWithMeta.meta.metaTags.map(tagDef => {
const auth = JSON.parse(item) const tag = document.createElement('meta');
if (!auth.token) { Object.keys(tagDef).forEach(key => {
next('/login') tag.setAttribute(key, tagDef[key]);
return });
} tag.setAttribute('data-vue-router-controlled', '');
next() return tag;
} else { })
next() .forEach(tag => document.head.appendChild(tag));
} next();
} });
export default router export default router

View File

@ -30,7 +30,8 @@ export default new Vuex.Store({
notifiers: [], notifiers: [],
checkins: [], checkins: [],
admin: false, admin: false,
user: false user: false,
loggedIn: false
}, },
getters: { getters: {
hasAllData: state => state.hasAllData, hasAllData: state => state.hasAllData,
@ -46,6 +47,7 @@ export default new Vuex.Store({
users: state => state.users, users: state => state.users,
notifiers: state => state.notifiers, notifiers: state => state.notifiers,
checkins: state => state.checkins, checkins: state => state.checkins,
loggedIn: state => state.loggedIn,
isAdmin: state => state.admin, isAdmin: state => state.admin,
isUser: state => state.user, isUser: state => state.user,
@ -131,6 +133,9 @@ export default new Vuex.Store({
setAdmin (state, admin) { setAdmin (state, admin) {
state.admin = admin state.admin = admin
}, },
setLoggedIn (state, loggedIn) {
state.loggedIn = loggedIn
},
setUser (state, user) { setUser (state, user) {
state.user = user state.user = user
}, },

View File

@ -51,17 +51,9 @@ func setJwtToken(user *users.User, w http.ResponseWriter) (JwtClaim, string) {
return jwtClaim, tokenString return jwtClaim, tokenString
} }
func getJwtToken(r *http.Request) (JwtClaim, error) { func parseToken(token string) (JwtClaim, error) {
c, err := r.Cookie(cookieName)
if err != nil {
if err == http.ErrNoCookie {
return JwtClaim{}, err
}
return JwtClaim{}, err
}
var claims JwtClaim var claims JwtClaim
tkn, err := jwt.ParseWithClaims(c.Value, &claims, func(token *jwt.Token) (interface{}, error) { tkn, err := jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil return jwtKey, nil
}) })
@ -74,5 +66,16 @@ func getJwtToken(r *http.Request) (JwtClaim, error) {
if !tkn.Valid { if !tkn.Valid {
return claims, errors.New("token is not valid") return claims, errors.New("token is not valid")
} }
return claims, err return claims, nil
}
func getJwtToken(r *http.Request) (JwtClaim, error) {
c, err := r.Cookie(cookieName)
if err != nil {
if err == http.ErrNoCookie {
return JwtClaim{}, err
}
return JwtClaim{}, err
}
return parseToken(c.Value)
} }

View File

@ -154,6 +154,7 @@ func Router() *mux.Router {
// API USER Routes // API USER Routes
api.Handle("/api/users", authenticated(apiAllUsersHandler, false)).Methods("GET") api.Handle("/api/users", authenticated(apiAllUsersHandler, false)).Methods("GET")
api.Handle("/api/users", authenticated(apiCreateUsersHandler, false)).Methods("POST") api.Handle("/api/users", authenticated(apiCreateUsersHandler, false)).Methods("POST")
api.Handle("/api/users/token", http.HandlerFunc(apiCheckUserTokenHandler)).Methods("POST")
api.Handle("/api/users/{id}", authenticated(apiUserHandler, false)).Methods("GET") api.Handle("/api/users/{id}", authenticated(apiUserHandler, false)).Methods("GET")
api.Handle("/api/users/{id}", authenticated(apiUserUpdateHandler, false)).Methods("POST") api.Handle("/api/users/{id}", authenticated(apiUserUpdateHandler, false)).Methods("POST")
api.Handle("/api/users/{id}", authenticated(apiUserDeleteHandler, false)).Methods("DELETE") api.Handle("/api/users/{id}", authenticated(apiUserDeleteHandler, false)).Methods("DELETE")

View File

@ -80,6 +80,23 @@ func apiAllUsersHandler(w http.ResponseWriter, r *http.Request) {
returnJson(allUsers, w, r) returnJson(allUsers, w, r)
} }
func apiCheckUserTokenHandler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
token := r.PostForm.Get("token")
if token == "" {
sendErrorJson(errors.New("missing token parameter"), w, r)
return
}
claim, err := parseToken(token)
if err != nil {
sendErrorJson(err, w, r)
return
}
returnJson(claim, w, r)
}
func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) { func apiCreateUsersHandler(w http.ResponseWriter, r *http.Request) {
var user *users.User var user *users.User
err := DecodeJSON(r, &user) err := DecodeJSON(r, &user)

View File

@ -19,11 +19,11 @@ var (
) )
func TestMobileNotifier(t *testing.T) { func TestMobileNotifier(t *testing.T) {
t.SkipNow()
err := utils.InitLogs() err := utils.InitLogs()
require.Nil(t, err) require.Nil(t, err)
t.Parallel() t.Parallel()
t.SkipNow()
mobileToken = utils.Params.GetString("MOBILE_TOKEN") mobileToken = utils.Params.GetString("MOBILE_TOKEN")
if mobileToken == "" { if mobileToken == "" {