Skip to content

Commit

Permalink
Merge branch 'admin-jwt' into 'master'
Browse files Browse the repository at this point in the history
Admin jwt

See merge request special/change-logs-producer!3
  • Loading branch information
Wouter Dullaert committed Aug 6, 2018
2 parents cd75ab0 + 48e832d commit 79c0bee
Show file tree
Hide file tree
Showing 11 changed files with 536 additions and 229 deletions.
3 changes: 3 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const applications = require("./lib/applications")
const dataSubjects = require("./lib/data-subjects")
const policies = require("./lib/policies")
const rethink = require("./utils/rethinkdb_config")
const jwtAuth = require("./lib/middleware/jwt-auth")

app.disable("x-powered-by")

Expand All @@ -19,6 +20,8 @@ app.use(session({
secret: process.env["SESSION_SECRET"] || crypto.randomBytes(20).toString("hex")
}))
app.use("/callback", oauthCallback)
app.use("/applications", jwtAuth, rethink.createConnection, applications)
app.use("/policies", jwtAuth, rethink.createConnection, policies)
app.use(authenticate)
app.use(bodyParser.json())
app.use(rethink.createConnection)
Expand Down
39 changes: 14 additions & 25 deletions lib/applications/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const APIError = require("../../utils/api-error.js")
let router = require("express").Router()
const rethink = require("../../utils/rethinkdb_config")
const {
Expand All @@ -21,7 +22,7 @@ router.get("/:id/policies", (req, res, next) => {

if (!appId) {
req.log.info("No appId specified, can't GET.")
return next({status: 400, message: "No application ID specified"})
return next(new APIError({statusCode: 400, detail: "No application ID specified"}))
}

dataControllerPoliciesTable.getAll(
Expand All @@ -32,8 +33,7 @@ router.get("/:id/policies", (req, res, next) => {
}).then(policies => {
return res.status(200).json({policies})
})
.catch(error => { next(error) })
.finally(() => { next() })
.catch(error => next(error))
})

// TODO: ADMIN
Expand All @@ -51,20 +51,19 @@ router.get("/:id", (req, res, next) => {

if (!appId) {
req.log.info("No appId specified, can't GET.")
return next({status: 400, message: "No application ID specified"})
return next(new APIError({statusCode: 400, detail: "No application ID specified"}))
}

// Get application with $appId but replace its policies by a link call
applicationsTable.get(appId).default({}).without({"policies": true}).run(req._rdbConn)
.then(application => {
if (!application["id"]) return next({"status": 404, "message": "Application does not exist / You're not authorized"})
if (!application["id"]) return next(new APIError({statusCode: 404, detail: "Application does not exist / You're not authorized"}))
application["links"] = {
"policies": "/applications/" + appId + "/policies"
}
return res.status(200).json({"applications": [application]})
})
.catch(error => { next(error) })
.finally(() => { next() })
.catch(error => next(error))
})

// *******************************
Expand All @@ -80,7 +79,7 @@ router.put("/:id", (req, res, next) => {
let appId = req.params.id
if (!appId) {
req.log.info("No id specified, can't update.")
return next({status: 400, message: "No ID specified, can't update"})
return next(new APIError({statusCode: 400, detail: "No ID specified, can't update"}))
}
req.log.debug({id: appId}, "Received request to update application")

Expand All @@ -92,7 +91,7 @@ router.put("/:id", (req, res, next) => {
if (updateResult["skipped"] > 0 &&
updateResult["replaced"] === 0 &&
updateResult["unchanged"] === 0) {
return next({"status": 404, "message": "Application does not exist / You are not authorized"})
return next(new APIError({statusCode: 404, detail: "Application does not exist / You are not authorized"}))
}
req.log.debug({appId, updateResult}, "Application updated")
delete application["policies"]
Expand All @@ -101,10 +100,7 @@ router.put("/:id", (req, res, next) => {
}
return res.status(200).json(application)
})
.catch(error => { next(error) })
.finally(() => {
next()
})
.catch(error => next(error))
})

// *******************************
Expand All @@ -119,7 +115,7 @@ router.delete("/:id", (req, res, next) => {
let appId = req.params.id
if (!appId) {
req.log.info("No id specified, can't delete.")
return next({status: 400, message: "No ID specified, can't delete"})
return next(new APIError({statusCode: 400, detail: "No ID specified, can't delete"}))
}
req.log.debug({id: appId}, "Received request to delete application")

Expand All @@ -128,15 +124,12 @@ router.delete("/:id", (req, res, next) => {
if (updateResult["skipped"] > 0 &&
updateResult["replaced"] === 0 &&
updateResult["unchanged"] === 0) {
return next({"status": 404, "message": "Application does not exist / You are not authorized"})
return next(new APIError({statusCode: 404, detail: "Application does not exist / You are not authorized"}))
}
req.log.debug({appId, updateResult}, "Application deleted")
return res.status(204).send()
})
.catch(error => { next(error) })
.finally(() => {
next()
})
.catch(error => next(error))
})

// TODO: ADMIN
Expand All @@ -163,8 +156,7 @@ router.get("/", (req, res, next) => {
})
res.status(200).send({applications})
})
.catch(error => { next(error) })
.finally(() => { next() })
.catch(error => next(error))
})

// *******************************
Expand All @@ -190,10 +182,7 @@ router.post("/", (req, res, next) => {
}
return res.status(200).json({"application": application})
})
.catch(error => { next(error) })
.finally(() => {
next()
})
.catch(error => next(error))
})

module.exports = router
30 changes: 12 additions & 18 deletions lib/data-subjects/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const APIError = require("../../utils/api-error.js")
let router = require("express").Router()

const rethink = require("../../utils/rethinkdb_config")
Expand All @@ -21,7 +22,7 @@ router.get("/:id/policies", (req, res, next) => {
try {
checkUserAccess(req)
} catch (error) {
return next({status: 401, message: error.message})
return next(error)
}
const reqId = req.params.id
const userId = req.session.user.id
Expand All @@ -47,8 +48,7 @@ router.get("/:id/policies", (req, res, next) => {
.then(policies => {
res.status(200).json({"policies": policies})
})
.catch(error => { next(error) })
.finally(() => { next() })
.catch(error => next(error))
})

// *******************************
Expand All @@ -63,7 +63,7 @@ router.get("/:id", (req, res, next) => {
try {
checkUserAccess(req)
} catch (error) {
return next({status: 401, message: error.message})
return next(error)
}
let reqId = req.params.id
let userId = req.session.user.id
Expand All @@ -72,17 +72,14 @@ router.get("/:id", (req, res, next) => {

dataSubjectsTable.get(userId).default({}).without({"policies": true}).run(req._rdbConn)
.then(dataSubject => {
if (!dataSubject["id"]) return next({"status": 404, "message": "User does not exist / You are not authorized"})
if (!dataSubject["id"]) return next(new APIError({statusCode: 404, detail: "User does not exist / You are not authorized"}))
dataSubject["links"] = {
"policies": "/users/" + reqId + "/policies"
}
dataSubject["id"] = reqId
res.status(200).json({"users": [dataSubject]})
})
.catch(error => { next(error) })
.finally(() => {
next()
})
.catch(error => next(error))
})

// *******************************
Expand All @@ -99,7 +96,7 @@ router.put("/:id", (req, res, next) => {
try {
checkUserAccess(req)
} catch (error) {
return next({status: 401, message: error.message})
return next(error)
}
let reqId = req.params.id
let userId = req.session.user.id
Expand All @@ -124,7 +121,7 @@ router.put("/:id", (req, res, next) => {
})
.then(appPolicies => {
return dataSubjectsTable.get(userId).run(req._rdbConn).then(dbUser => {
if (!dbUser["id"]) return next({"status": 404, "message": "User does not exist / You are not authorized"})
if (!dbUser["id"]) return next(new APIError({statusCode: 404, detail: "User does not exist / You are not authorized"}))
return {
appPolicies, dbUser
}
Expand Down Expand Up @@ -152,7 +149,7 @@ router.put("/:id", (req, res, next) => {
if (updateResult["skipped"] > 0 &&
updateResult["replaced"] === 0 &&
updateResult["unchanged"] === 0) {
return next({"status": 404, "message": "User does not exist / You are not authorized"})
return next(new APIError({statusCode: 404, detail: "User does not exist / You are not authorized"}))
}
req.log.debug({userId, updateResult}, "User updated")
dbUser["policies"] = newPolicies
Expand All @@ -164,10 +161,7 @@ router.put("/:id", (req, res, next) => {
newUser["id"] = reqId
res.status(200).send({"user": newUser})
})
.catch(error => { next(error) })
.finally(() => {
next()
})
.catch(error => next(error))
})

function checkUserAccess (req) {
Expand All @@ -179,9 +173,9 @@ function checkUserAccess (req) {

// Not current and not the same, check
// TODO: Check if admin
throw new Error("Not Authorized")
throw new APIError({statusCode: 401, detail: "Not Authorized"})
} catch (error) {
throw new Error("Current user is not authorized to do this")
throw new APIError({statusCode: 401, detail: "Current user is not authorized to do this"})
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/middleware/child-logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function addChildLogger (req, res, next) {
req.log = log.child({requestId: req.requestId})
req.log.info({req}, "API Request")
res.set("x-request-id", req.requestId)
res.on("close", () => req.log.debug({res}, "API Response"))
res.on("finish", () => req.log.debug({res}, "API Response"))
next()
}
Expand Down
1 change: 1 addition & 0 deletions lib/middleware/error-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ function errorHandler (error, req, res, next) {

let sc = error.statusCode || INTERNAL_SERVER_ERROR
if (sc >= INTERNAL_SERVER_ERROR) req.log.error({err: error})
else req.log.debug({err: error})
res.status(sc)

if (req.accepts(["html", "json"]) === "json") {
Expand Down
40 changes: 40 additions & 0 deletions lib/middleware/jwt-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use strict"
/**
* This implements a middleware which just checks the Authorization header
* for a bearer token and validates this at the OIDC /userinfo endpoint
* If authentication fails we just call next('route') to let the app default
* behaviour handle it
*
* It's a quick hack to support service accounts
*/
const request = require("superagent")
const {UNAUTHORIZED} = require("http-status-codes")
const endpoint = process.env.AUTH_USERINFO_ENDPOINT || "http://localhost:8080/auth/realms/master/protocol/openid-connect/userinfo"

function jwtAuth (req, res, next) {
// TODO: to be spec compliant a token can also be passed into the body or
// as a query parameter, but we're only supporting the Authorization header
// at the moment (it is a hack remember)
// see: https://github.com/jaredhanson/passport-http-bearer/blob/master/lib/strategy.js
if (!req.headers || !req.headers.authorization) return next("route")
const parts = req.headers.authorization.split(" ")
if (parts.length !== 2) return next("route")
const [scheme, token] = parts
if (!/^Bearer$/i.test(scheme)) return next("route")
req.log.debug({token}, "Parsed access_token")

// At this stage we have a token, let's verify it
request
.get(endpoint)
.set("Authorization", `Bearer ${token}`)
.set("Accept", "application/json")
.end((err) => {
if (err) {
req.log.warn({error: err}, "Failed to authorize access token")
return res.status(err.status || UNAUTHORIZED).send()
}
return next()
})
}

module.exports = jwtAuth
Loading

0 comments on commit 79c0bee

Please sign in to comment.