From 6297fcd56f15586dfbe5b3aad7d92d16b372ab09 Mon Sep 17 00:00:00 2001 From: Paul Bienkowski Date: Tue, 23 Feb 2021 20:20:16 +0100 Subject: [PATCH] api: Add some nicer html templates for auth dialogs --- api/Dockerfile | 1 + api/package-lock.json | 213 ++++++++++++++++++++++++++++++++++++++- api/package.json | 1 + api/src/index.js | 4 + api/src/models/Client.js | 1 + api/src/routes/auth.js | 32 ++---- api/views/authorize.pug | 17 ++++ api/views/layout.pug | 104 +++++++++++++++++++ api/views/login.pug | 16 +++ api/views/message.pug | 8 ++ docker-compose.yaml | 1 + 11 files changed, 372 insertions(+), 26 deletions(-) create mode 100644 api/views/authorize.pug create mode 100644 api/views/layout.pug create mode 100644 api/views/login.pug create mode 100644 api/views/message.pug diff --git a/api/Dockerfile b/api/Dockerfile index 1fa6439..b074128 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -4,6 +4,7 @@ WORKDIR /opt/obs/api ADD package.json package-lock.json /opt/obs/api/ RUN npm ci +ADD views /opt/obs/api/views/ ADD src /opt/obs/api/src/ EXPOSE 3000 diff --git a/api/package-lock.json b/api/package-lock.json index 05209ff..7b4466d 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -1373,6 +1373,11 @@ } } }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -1381,6 +1386,11 @@ "safer-buffer": "~2.1.0" } }, + "assert-never": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.2.1.tgz", + "integrity": "sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==" + }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -1533,6 +1543,14 @@ "babel-preset-current-node-syntax": "^1.0.0" } }, + "babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "requires": { + "@babel/types": "^7.9.6" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1867,6 +1885,14 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, "chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -2029,6 +2055,15 @@ "busboy": "*" } }, + "constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "requires": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -2330,6 +2365,11 @@ "esutils": "^2.0.2" } }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, "domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -3744,8 +3784,7 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "has-value": { "version": "1.0.0", @@ -4046,6 +4085,15 @@ "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", "optional": true }, + "is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "requires": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -4128,11 +4176,15 @@ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=" }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, "is-regex": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -5441,6 +5493,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5586,6 +5643,15 @@ "verror": "1.10.0" } }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, "jsts": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/jsts/-/jsts-1.1.2.tgz", @@ -6680,6 +6746,14 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, "prompts": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", @@ -6709,6 +6783,118 @@ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, + "pug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.0.tgz", + "integrity": "sha512-inmsJyFBSHZaiGLaguoFgJGViX0If6AcfcElimvwj9perqjDpUpw79UIEDZbWFmoGVidh08aoE+e8tVkjVJPCw==", + "requires": { + "pug-code-gen": "^3.0.0", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.0", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.0", + "pug-strip-comments": "^2.0.0" + } + }, + "pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "requires": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "pug-code-gen": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.1.tgz", + "integrity": "sha512-xJIGvmXTQlkJllq6hqxxjRWcay2F9CU69TuAuiVZgHK0afOhG5txrQOcZyaPHBvSWCU/QQOqEp5XCH94rRZpBQ==", + "requires": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.0.0", + "pug-runtime": "^3.0.0", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==" + }, + "pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "requires": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "pug-lexer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.0.tgz", + "integrity": "sha512-52xMk8nNpuyQ/M2wjZBN5gXQLIylaGkAoTk5Y1pBhVqaopaoj8Z0iVzpbFZAqitL4RHNVDZRnJDsqEYe99Ti0A==", + "requires": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "requires": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "requires": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "requires": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "pug-runtime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.0.tgz", + "integrity": "sha512-GoEPcmQNnaTsePEdVA05bDpY+Op5VLHKayg08AQiqJBWU/yIaywEYv7TetC5dEQS3fzBBoyb2InDcZEg3mPTIA==" + }, + "pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "requires": { + "pug-error": "^2.0.0" + } + }, + "pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -7989,6 +8175,11 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, + "token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=" + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -8878,6 +9069,11 @@ "extsprintf": "^1.2.0" } }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=" + }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -8957,6 +9153,17 @@ "string-width": "^4.0.0" } }, + "with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "requires": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + } + }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", diff --git a/api/package.json b/api/package.json index 4ccd1ee..f15ca79 100644 --- a/api/package.json +++ b/api/package.json @@ -48,6 +48,7 @@ "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "pkce": "^1.0.0-beta2", + "pug": "^3.0.0", "request": "2.88.2", "sanitize-filename": "^1.6.3", "slug": "^3.5.2", diff --git a/api/src/index.js b/api/src/index.js index 8a8b5cc..01831f9 100644 --- a/api/src/index.js +++ b/api/src/index.js @@ -15,6 +15,10 @@ const app = express(); app.use(cors()); +// Express configuration +app.set('views', './views') +app.set('view engine', 'pug') + // Normal express config defaults app.use(require('morgan')('dev')); app.use(bodyParser.json({ limit: '50mb' })); diff --git a/api/src/models/Client.js b/api/src/models/Client.js index e8eab70..184af39 100644 --- a/api/src/models/Client.js +++ b/api/src/models/Client.js @@ -2,6 +2,7 @@ const mongoose = require('mongoose'); const schema = new mongoose.Schema( { + title: { type: String, required: true }, clientId: { type: String, required: true }, // this is external, so we do not use the ObjectID validRedirectUris: [{ type: String }], diff --git a/api/src/routes/auth.js b/api/src/routes/auth.js index f806b2d..ca498f8 100644 --- a/api/src/routes/auth.js +++ b/api/src/routes/auth.js @@ -1,4 +1,3 @@ -const crypto = require('crypto'); const router = require('express').Router(); const passport = require('passport'); const { URL } = require('url'); @@ -59,7 +58,7 @@ router.post( req.session.next = null; return; } - return res.type('html').end('You are logged in.'); + return res.render('message', { type: 'success', title: 'You are logged in.' }); }), ); @@ -67,14 +66,10 @@ router.get( '/login', wrapRoute(async (req, res) => { if (req.user) { - return res.type('html').end('Already logged in, nothing to do.'); + return res.render('message', { type: 'success', title: 'You are already logged in.' }); } - res - .type('html') - .end( - '
', - ); + res.render('login'); }), ); @@ -211,20 +206,7 @@ router.get( codeChallenge, }; - res.type('html').end(` -

- You are about to confirm a login to client ${clientId} - with redirectUri ${redirectUri} and scope ${scope}. - You have 2 minutes time for your decision. -

- -
- -
-
- -
- `); + res.render('authorize', { clientTitle: client.title, scope, redirectUri }); } catch (err) { console.error(err); res.status(400).json({ error: 'invalid_request', error_description: 'unknown error' }); @@ -247,7 +229,11 @@ router.post( const { clientId, redirectUri, scope, expiresAt, codeChallenge } = req.session.authorizationTransaction; if (expiresAt < new Date().getTime()) { - return res.status(400).type('html').end(`Your authorization has expired. Please go back and retry the process.`); + return res.status(400).render('message', { + type: 'error', + title: 'Expired', + description: 'Your authorization has expired. Please go back and retry the process.', + }); } const client = await Client.findOne({ clientId }); diff --git a/api/views/authorize.pug b/api/views/authorize.pug new file mode 100644 index 0000000..0ef8f73 --- /dev/null +++ b/api/views/authorize.pug @@ -0,0 +1,17 @@ +extends layout.pug + +block title + | Authorize #{clientTitle} + +block content + p. + You are about to confirm a login to client #[b= clientTitle]. You have 2 + minutes time for your decision. + + .authorization + form(method="post", action="/authorize/decline") + button(type="submit", class="red") Decline + + form(method="post", action="/authorize/approve") + button(type="submit", class="green") Approve + diff --git a/api/views/layout.pug b/api/views/layout.pug new file mode 100644 index 0000000..8a4a77b --- /dev/null +++ b/api/views/layout.pug @@ -0,0 +1,104 @@ +html + head + title + block title + | Authorization Server + + style. + html, body { + font-family: "Roboto"; + font-size: 11pt; + background: #FAFAFE; + } + + main { + max-width: 30rem; + margin: 2rem auto; + background: white; + border: 1px solid #DDD; + padding: 2rem; + border-radius: 6px; + } + + h1 { + font-weight: 500; + text-align: center; + } + + input, button { + font: inherit; + } + + input { + background: white; + height: 2rem; + display: block; + width: 100%; + margin: 0.5rem 0; + padding: 0 0.5rem; + border: 1px solid #DDD; + } + + input:focus { + border: 1px solid #000; + outline: none; + } + + button { + padding: 0 1rem; + height: 2.5rem; + background: #dedede; + font-size: inherit; + border: 0; + border-radius: 6px; + color: black; + font-weight: 500; + cursor: pointer; + } + + fieldset { + border: none; + padding: 0; + margin: 1rem 0; + } + + .message.error { + color: red; + } + + .authorization { + display: flex; + } + + .authorization form { + flex: 1 1 0; + } + .authorization form:first-child { + margin-right: 2rem; + } + + .authorization form button { + width: 100%; + } + + button.red { + background: #b33f3f; + color: white; + } + button.red:hover { + background: #ff7878; + } + + button.green { + background: #41b141; + color: white; + } + button.green:hover { + background: #83d283; + } + + body + main + header: h1 + block title + block content diff --git a/api/views/login.pug b/api/views/login.pug new file mode 100644 index 0000000..f697af5 --- /dev/null +++ b/api/views/login.pug @@ -0,0 +1,16 @@ +extends layout.pug + +block title + | Login + +block content + form(method="post") + fieldset + label(for="email") E-Mail Address + input(id="email", name="email") + + fieldset + label(for="password") Password + input(name="password", type="password") + + button(type="submit") Login diff --git a/api/views/message.pug b/api/views/message.pug new file mode 100644 index 0000000..582b72f --- /dev/null +++ b/api/views/message.pug @@ -0,0 +1,8 @@ +extends layout.pug + +block title + = title + +block content + if description + div(class="message " + type)= description diff --git a/docker-compose.yaml b/docker-compose.yaml index f6ab96e..34e11b5 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -16,6 +16,7 @@ services: context: ./api volumes: - ./api/src:/opt/obs/api/src + - ./api/views:/opt/obs/api/views - ./local/api-data:/data - ./api/.migrations.js:/opt/obs/api/.migrations.js - ./api/migrations:/opt/obs/api/migrations/