Skip to content

Commit aa7ee05

Browse files
committed
Added authentication barrier for the backend API. Added global error handling for the frontend. Added interval stops in case of backend request errors
1 parent c02cba5 commit aa7ee05

File tree

5 files changed

+143
-18
lines changed

5 files changed

+143
-18
lines changed

frontend/client/app.js

+54
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import router from './router'
77
import store from './store'
88
import * as filters from './filters'
99
import { TOGGLE_SIDEBAR } from 'vuex-store/mutation-types'
10+
import Notification from 'vue-bulma-notification-fixed'
1011
import auth from './auth'
1112
import lodash from 'lodash'
1213
import VueLodash from 'vue-lodash'
@@ -40,6 +41,59 @@ Vue.directive('focus', {
4041
}
4142
})
4243

44+
const NotificationComponent = Vue.extend(Notification)
45+
const openNotification = (propsData = {
46+
title: '',
47+
message: '',
48+
type: '',
49+
direction: '',
50+
duration: 4500,
51+
container: '.notifications'
52+
}) => {
53+
return new NotificationComponent({
54+
el: document.createElement('div'),
55+
propsData
56+
})
57+
}
58+
Vue.prototype.$notify = openNotification
59+
60+
function handleError (error) {
61+
// if the server gave a response message, print that
62+
if (error.response.data.error) {
63+
// duration should be proportional to the error message length
64+
openNotification({
65+
title: 'Error: ' + error.response.status,
66+
message: error.response.data.error,
67+
type: 'danger',
68+
duration: error.response.data.error.length > 60 ? 20000 : 4500
69+
})
70+
console.log(error.response.data.error)
71+
} else {
72+
if (error.response.status === 404) {
73+
openNotification({
74+
title: 'Error: 404',
75+
message: 'Not found',
76+
type: 'danger'
77+
})
78+
} else if (error.response.status === 403) {
79+
// Access denied
80+
openNotification({
81+
title: 'Error: 403',
82+
message: 'Not authorized. Please login first.',
83+
type: 'danger'
84+
})
85+
} else {
86+
openNotification({
87+
title: 'Error: ' + error.response.status.toString(),
88+
message: '',
89+
type: 'danger'
90+
})
91+
}
92+
console.log(error.response.data)
93+
}
94+
}
95+
Vue.prototype.$onError = handleError
96+
4397
router.beforeEach((route, redirect, next) => {
4498
if (state.app.device.isMobile && state.app.sidebar.opened) {
4599
store.commit(TOGGLE_SIDEBAR, false)

frontend/client/views/overview/index.vue

+9-6
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
export default {
3232
data () {
3333
return {
34-
pipelines: []
34+
pipelines: [],
35+
intervalID: null
3536
}
3637
},
3738
@@ -40,7 +41,7 @@ export default {
4041
this.fetchData()
4142
4243
// periodically update dashboard
43-
setInterval(function () {
44+
this.intervalID = setInterval(function () {
4445
this.fetchData()
4546
}.bind(this), 3000)
4647
},
@@ -58,8 +59,9 @@ export default {
5859
this.pipelines = response.data
5960
}
6061
})
61-
.catch(error => {
62-
console.log(error.response.data)
62+
.catch((error) => {
63+
clearInterval(this.intervalID)
64+
this.$onError(error)
6365
})
6466
},
6567
@@ -72,8 +74,9 @@ export default {
7274
this.$router.push({path: '/pipelines/detail', query: { pipelineid: pipelineid, runid: response.data.id }})
7375
}
7476
})
75-
.catch(error => {
76-
console.log(error.response.data)
77+
.catch((error) => {
78+
clearInterval(this.intervalID)
79+
this.$onError(error)
7780
})
7881
},
7982

frontend/client/views/pipelines/create.vue

+8-6
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ export default {
231231
field: 'created'
232232
}
233233
],
234-
historyRows: []
234+
historyRows: [],
235+
intervalID: null
235236
}
236237
},
237238
@@ -247,7 +248,7 @@ export default {
247248
this.fetchData()
248249
249250
// periodically update history dashboard
250-
setInterval(function () {
251+
this.intervalID = setInterval(function () {
251252
this.fetchData()
252253
}.bind(this), 3000)
253254
},
@@ -265,8 +266,9 @@ export default {
265266
this.historyRows = response.data
266267
}
267268
})
268-
.catch(error => {
269-
console.log(error.response.data)
269+
.catch((error) => {
270+
clearInterval(this.intervalID)
271+
this.$onError(error)
270272
})
271273
},
272274
@@ -350,8 +352,8 @@ export default {
350352
// Run fetchData to see the pipeline in our history table
351353
this.fetchData()
352354
})
353-
.catch(error => {
354-
console.log(error.response.data)
355+
.catch((error) => {
356+
this.$onError(error)
355357
})
356358
},
357359

frontend/client/views/pipelines/detail.vue

+15-6
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,10 @@
4646
<script>
4747
import Vue from 'vue'
4848
import Vis from 'vis'
49-
import VueTippy from 'vue-tippy'
5049
import VueGoodTable from 'vue-good-table'
5150
import moment from 'moment'
5251
5352
Vue.use(VueGoodTable)
54-
Vue.use(VueTippy)
5553
5654
export default {
5755
@@ -106,15 +104,18 @@ export default {
106104
},
107105
arrows: {to: true}
108106
}
109-
}
107+
},
108+
intervalID: null
110109
}
111110
},
112111
113112
mounted () {
114-
this.fetchData()
113+
// View should be re-rendered
114+
this.lastRedraw = false
115115
116116
// periodically update view
117-
setInterval(function () {
117+
this.fetchData()
118+
this.intervalID = setInterval(function () {
118119
this.fetchData()
119120
}.bind(this), 3000)
120121
},
@@ -158,6 +159,10 @@ export default {
158159
}
159160
this.runsRows = pipelineRuns.data
160161
}.bind(this)))
162+
.catch((error) => {
163+
clearInterval(this.intervalID)
164+
this.$onError(error)
165+
})
161166
} else {
162167
// Do concurrent request
163168
this.$http.all([this.getPipeline(pipelineID), this.getPipelineRuns(pipelineID)])
@@ -168,6 +173,10 @@ export default {
168173
}
169174
this.runsRows = pipelineRuns.data
170175
}.bind(this)))
176+
.catch((error) => {
177+
clearInterval(this.intervalID)
178+
this.$onError(error)
179+
})
171180
}
172181
},
173182
@@ -271,7 +280,7 @@ export default {
271280
}
272281
273282
// If pipelineView already exist, just update it
274-
if (window.pipelineView) {
283+
if (window.pipelineView && this.nodes && this.edges) {
275284
// Redraw
276285
this.nodes.clear()
277286
this.edges.clear()

handlers/handler.go

+57
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package handlers
22

33
import (
44
"crypto/rand"
5+
"errors"
6+
"fmt"
7+
"strings"
58

9+
jwt "github.com/dgrijalva/jwt-go"
610
scheduler "github.com/gaia-pipeline/gaia/scheduler"
711
"github.com/gaia-pipeline/gaia/store"
812
"github.com/kataras/iris"
@@ -12,6 +16,10 @@ const (
1216
apiVersion = "v1"
1317
)
1418

19+
var (
20+
errNotAuthorized = errors.New("no or invalid jwt token provided. You are not authorized")
21+
)
22+
1523
// storeService is an instance of store.
1624
// Use this to talk to the store.
1725
var storeService *store.Store
@@ -53,5 +61,54 @@ func InitHandlers(i *iris.Application, store *store.Store, scheduler *scheduler.
5361
i.Get(p+"pipelines/start/{id:string}", PipelineStart)
5462
i.Get(p+"pipelines/runs/{pipelineid:string}", PipelineGetAllRuns)
5563

64+
// Authentication Barrier
65+
i.UseGlobal(authBarrier)
66+
5667
return nil
5768
}
69+
70+
// authBarrier is the middleware which prevents user exploits.
71+
// It makes sure that the request contains a valid jwt token.
72+
// TODO: Role based access
73+
func authBarrier(ctx iris.Context) {
74+
// Login resource is open
75+
if strings.Contains(ctx.Path(), "users/login") {
76+
ctx.Next()
77+
return
78+
}
79+
80+
// Get JWT token
81+
jwtRaw := ctx.GetHeader("Authorization")
82+
split := strings.Split(jwtRaw, " ")
83+
if len(split) != 2 {
84+
ctx.StatusCode(iris.StatusForbidden)
85+
ctx.WriteString(errNotAuthorized.Error())
86+
return
87+
}
88+
jwtString := split[1]
89+
90+
// Parse token
91+
token, err := jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
92+
// Validate signing method
93+
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
94+
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
95+
}
96+
97+
// return secret
98+
return jwtKey, nil
99+
})
100+
if err != nil {
101+
ctx.StatusCode(iris.StatusForbidden)
102+
ctx.WriteString(err.Error())
103+
return
104+
}
105+
106+
// Validate token
107+
if _, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
108+
// All ok, continue
109+
ctx.Next()
110+
} else {
111+
ctx.StatusCode(iris.StatusForbidden)
112+
ctx.WriteString(errNotAuthorized.Error())
113+
}
114+
}

0 commit comments

Comments
 (0)