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