From 717aba380cf406e094532d166a09ad493d118ca5 Mon Sep 17 00:00:00 2001 From: Mark <mark@openappstack.net> Date: Thu, 17 Oct 2019 15:45:44 +0200 Subject: [PATCH] Add serverMiddleware that exposes backend API --- frontend/README.md | 14 ++++++++ frontend/api/index.js | 28 +++++++++++++++ frontend/api/middleware/admin.js | 9 +++++ frontend/api/middleware/auth.js | 59 ++++++++++++++++++++++++++++++++ frontend/api/routes/graphql.js | 17 +++++++++ frontend/nuxt.config.js | 7 ++++ frontend/package.json | 8 +++-- 7 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 frontend/api/index.js create mode 100644 frontend/api/middleware/admin.js create mode 100644 frontend/api/middleware/auth.js create mode 100644 frontend/api/routes/graphql.js diff --git a/frontend/README.md b/frontend/README.md index 1024349..2b91ffa 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -27,3 +27,17 @@ correctly is essential for the login process to work For details on how to configure authentication for external providers like GitHub or Facebook please refer to [the nuxt-oauth documentation](https://auth.nuxtjs.org/schemes/oauth2.html) + + +### Usage + +Navigate your browser to the `server` URL, that you specified in `nuxt.config.js`. The default port +is 3000. + +As an administrator you can access the user backend API by accessing the api endpoint +that is exposed by the userpanel at `/api/admin/graphql`. If you run your backend server in +development mode, you can access the `GraphiQL` web interface of the backend by navigating +your browser to the URL. +Note that you need to manually provide a cookie containing the Baerer token in case you don't +access the api with a browser that automatically includes your cookie in the request. +Passing the token as a GET or POST variable is not supported. diff --git a/frontend/api/index.js b/frontend/api/index.js new file mode 100644 index 0000000..237765b --- /dev/null +++ b/frontend/api/index.js @@ -0,0 +1,28 @@ +const express = require('express') +const cookieParser = require('cookie-parser') + +// Create Express Server that is started by nuxt to process authenticated +// requests on the serverside as a serverMiddleware. Can function as a proxy +// that authenticates requests. +const app = express() + +// Include authentication middleware +const auth = require('./middleware/auth') +const isAdmin = require('./middleware/admin') + +// Include routes that creare response transmitted to client +const graphql = require('./routes/graphql') + +// Register authentication middleware +app.use(cookieParser()) +app.use(auth) +app.use('/admin/', isAdmin) + +// Register routes +app.use('/admin/graphql', graphql) + +// Export the server middleware +module.exports = { + path: '/api', + handler: app +} diff --git a/frontend/api/middleware/admin.js b/frontend/api/middleware/admin.js new file mode 100644 index 0000000..35e0fc0 --- /dev/null +++ b/frontend/api/middleware/admin.js @@ -0,0 +1,9 @@ +const isAdmin = function (req, res, next) { + const user = req.user; + if (user.roles.includes("admin") === false){ + res.status(403).send("Not authorized") + } + next() +} + +module.exports = isAdmin diff --git a/frontend/api/middleware/auth.js b/frontend/api/middleware/auth.js new file mode 100644 index 0000000..40c1229 --- /dev/null +++ b/frontend/api/middleware/auth.js @@ -0,0 +1,59 @@ +const nuxtConfig = require('../../nuxt.config') +const { Issuer, TokenSet } = require('openid-client') + +// Configure openID-Connect client to verify tokens +// use the configuration that is supplied in ~/nuxt.config.js +const authStrategy = nuxtConfig.auth.strategies["oas"] +const openIdIssuer = new Issuer({ + authorization_endpoint: authStrategy.authorization_endpoint, + token_endpoint: authStrategy.access_token_endpoint, + userinfo_endpoint: authStrategy.userinfo_endpoint +}); +var openIdClient; +(async () => { + const openIdIssuer2 = await Issuer.discover(authStrategy.base_url); + openIdClient = new openIdIssuer2.Client({ + client_id: authStrategy.client_id, + client_secret: authStrategy.client_secret, + redirect_uris: [authStrategy.redirect_uri], + response_types: [authStrategy.response_type], + grant_types: ["implicit"] + }) +})() + + +const authenticateWithToken = function (req, res, next) { + // Get the oAuth2.0 access token from the user request + // The User cookies are forwarded to this middleware + // by nuxt add parsed by the cookieParser module which + // makes the cookies accessible via req.cookies + const authToken = req.cookies['auth._token.oas'] + const tokenSet = new TokenSet( + { + access_token: authToken.slice("Bearer ".length), + token_type: "Bearer" + }) + // We don't trust the token beacuse we received it via + // the request object which is create by the client. + // In order to veryfi the token, we query the userinfo + // endpoint using the provided token. If the OpenID + // provider acceppts the token, we know it's valid and + // we will get the userinfo as at the same time + openIdClient.userinfo(tokenSet).then(function(userinfo){ + // Safe our validated userinfo in the request object + // for use in other express functions + req.user = { + name: userinfo.name, + id: userinfo.sid, + roles: userinfo[nuxtConfig.auth.scopeKey] + } + if ( userinfo.hasOwnProperty('preferred_username')){ + req.user.name = userinfo.preferred_username; + } + next() + }).catch(function(error){ + res.status(403).send("Authentication failed: " + error) + }); +} + +module.exports = authenticateWithToken diff --git a/frontend/api/routes/graphql.js b/frontend/api/routes/graphql.js new file mode 100644 index 0000000..0071223 --- /dev/null +++ b/frontend/api/routes/graphql.js @@ -0,0 +1,17 @@ +const httpProxy = require('http-proxy') +const proxy = httpProxy.createProxyServer() +const nuxtConfig = require('../../nuxt.config') +const API_URL = nuxtConfig.env.BACKEND_API_URL || 'http://localhost:5000/graphql' +const { Router } = require('express') + +const router = Router() + +// Proxy all requests going to this route to the API URL. +router.use(function(req, res, next) { + proxy.web(req, res, { + target: API_URL, + ignorePath: true // Don't add a trailing '/' + }) +}) + +module.exports = router diff --git a/frontend/nuxt.config.js b/frontend/nuxt.config.js index 21fa739..f676592 100644 --- a/frontend/nuxt.config.js +++ b/frontend/nuxt.config.js @@ -2,6 +2,12 @@ module.exports = { server: { host: '0.0.0.0' }, + env: { + BACKEND_API_URL: 'http://localhost:5002/graphql' + }, + serverMiddleware: [ + '~/api/index.js' + ], meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' } @@ -14,6 +20,7 @@ module.exports = { strategies: { oas: { _scheme: 'oauth2', + base_url: 'http://oas.alumnicloud.net:4444', authorization_endpoint: 'http://oas.alumnicloud.net:4444/oauth2/auth', userinfo_endpoint: 'http://oas.alumnicloud.net:4444/userinfo', scope: ['openid', 'profile', 'email', 'openappstack_roles'], diff --git a/frontend/package.json b/frontend/package.json index 729a4b0..9e4b669 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "user-panel", - "version": "1.1.0", + "version": "1.2.0", "description": "", "main": "nuxt.config.js", "scripts": { @@ -13,9 +13,11 @@ "author": "", "license": "ISC", "dependencies": { - "@nuxtjs/axios": "^5.6.0", "@nuxtjs/auth": "^4.8.4", + "@nuxtjs/axios": "^5.6.0", "bootstrap-vue": "^2.0.3", - "nuxt": "latest" + "cookie-parser": "^1.4.4", + "nuxt": "latest", + "openid-client": "^3.7.3" } } -- GitLab