From 7e057c8a56b51bdd7e95b3919d013ac0edd481e9 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Sun, 26 Apr 2020 19:24:57 +0300 Subject: Get user&pass and google auth working --- docker-compose.yml | 4 ++++ frontend/package-lock.json | 51 +++++++++++++++++++++++++++++++++++++++++ frontend/package.json | 1 + frontend/src/index.html | 19 +++++++++++---- frontend/src/login.html | 26 --------------------- frontend/src/main.js | 39 +++++++++++++++++++++++++------ login-service/package-lock.json | 31 +++++++++++++++++++++++++ login-service/package.json | 1 + login-service/src/index.js | 35 +++++++++++++++++++++++++--- 9 files changed, 166 insertions(+), 41 deletions(-) delete mode 100644 frontend/src/login.html diff --git a/docker-compose.yml b/docker-compose.yml index 31b19f1..d73f254 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,10 @@ services: frontend: build: ./frontend + environment: + GOOGLE_OAUTH_ID: ${GOOGLE_OAUTH_ID} + LOGIN_API_URL: http://localhost:4000 + POSTGREST_API_URL: http://localhost:3000 depends_on: - login_service ports: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cab98f4..3fd9a1f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4,6 +4,27 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@auth0/auth0-spa-js": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.7.0.tgz", + "integrity": "sha512-OAeNUI0bTBdk3cIWJMBLgf4B1DvyPXmbBBDQI7EpGR3pU0/+9d19sDCPC1b9epp6Aa7pnd4OR5u9JIQCc3eSKw==", + "requires": { + "abortcontroller-polyfill": "^1.4.0", + "browser-tabs-lock": "^1.2.8", + "core-js": "^3.6.4", + "es-cookie": "^1.3.2", + "fast-text-encoding": "^1.0.1", + "promise-polyfill": "^8.1.3", + "unfetch": "^4.1.0" + }, + "dependencies": { + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + } + } + }, "@babel/code-frame": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", @@ -1078,6 +1099,11 @@ "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", "dev": true }, + "abortcontroller-polyfill": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz", + "integrity": "sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA==" + }, "acorn": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.1.tgz", @@ -1537,6 +1563,11 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", "dev": true }, + "browser-tabs-lock": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.8.tgz", + "integrity": "sha512-Xrj33YUTltPDoGrD1KnaAn5ZuxnnlJFcIW9srVTPHbMNPd9MlcnBCWaGV0STlvGKu8Ok0ad5qxyx5sIwFTr/Ig==" + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -2722,6 +2753,11 @@ } } }, + "es-cookie": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", + "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -2998,6 +3034,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-text-encoding": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.2.tgz", + "integrity": "sha512-5rQdinSsycpzvAoHga2EDn+LRX1d5xLFsuNG0Kg61JrAT/tASXcLL0nf/33v+sAxlQcfYmWbTURa1mmAf55jGw==" + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -5827,6 +5868,11 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "promise-polyfill": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", + "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -7015,6 +7061,11 @@ } } }, + "unfetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz", + "integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg==" + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", diff --git a/frontend/package.json b/frontend/package.json index 5c07804..05e2489 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "parcel-bundler": "^1.12.4" }, "dependencies": { + "@auth0/auth0-spa-js": "^1.7.0", "axios": "^0.19.2", "jquery": "^3.5.0", "js-cookie": "^2.2.1" diff --git a/frontend/src/index.html b/frontend/src/index.html index f3e9d0e..7ffb08f 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -6,14 +6,23 @@
- - Index - Login - Logout - + +
+
+
+ + + +
+
+ \ No newline at end of file diff --git a/frontend/src/login.html b/frontend/src/login.html deleted file mode 100644 index abae4f8..0000000 --- a/frontend/src/login.html +++ /dev/null @@ -1,26 +0,0 @@ - - - - PostgREST demo - - - -
- - Index - Login - Logout - -
-
- - - -
- - - \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js index 79ed47e..3deeda3 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -3,10 +3,11 @@ import axios from 'axios'; import $ from 'jquery'; import cookie from 'js-cookie'; -const LOGIN_API_URL = 'http://localhost:4000'; -const POSTGREST_API_URL = 'http://localhost:3000'; +const LOGIN_API_URL = process.env.LOGIN_API_URL; +const POSTGREST_API_URL = process.env.POSTGREST_API_URL; +const GOOGLE_OAUTH_ID = process.env.GOOGLE_OAUTH_ID; -$('a#logout').click(function(event) { +$('button#logout').click(function(event) { event.preventDefault(); cookie.remove('jwt'); alert('Logged out.'); @@ -29,8 +30,8 @@ $('button#fetchTodosBtn').click(async function() { }); } } catch (err) { + console.error(err); $('div#todos').html(String(err)); - alert(err); } }); @@ -39,13 +40,37 @@ $('form#loginForm').submit(async function(event) { const username = $(this).find('input#username').val(); const password = $(this).find('input#password').val(); try { - const resp = await axios.post(`${LOGIN_API_URL}/login`, { username, password }); + const resp = await axios.post(`${LOGIN_API_URL}/login/userpass`, { username, password }); const payload = resp.data; - const jwt = payload.jwt; + const { jwt, displayName } = payload; cookie.set('jwt', jwt); - alert(`Logged in as ${username}.`); + alert(`Logged in as ${displayName}.`); } catch (err) { cookie.remove('jwt'); alert(err); } }); + +$('button#google-login').click(async function() { + const GoogleAuth = gapi.auth2.getAuthInstance(); + try { + const user = await GoogleAuth.signIn(); + const authResponse = user.getAuthResponse(true); + const resp = await axios.post(`${LOGIN_API_URL}/login/google`, { accessToken: authResponse.access_token }); + const payload = resp.data; + const { jwt, displayName } = payload; + cookie.set('jwt', jwt); + alert(`Logged in as ${displayName}.`); + } catch (err) { + console.error(err); + } +}); + +window.gapiInit = () => { + console.log('gapiInit'); + gapi.load('auth2', function() { + gapi.auth2.init({ + client_id: GOOGLE_OAUTH_ID, + }); + }); +}; diff --git a/login-service/package-lock.json b/login-service/package-lock.json index 0cc44b6..7af7e7d 100644 --- a/login-service/package-lock.json +++ b/login-service/package-lock.json @@ -93,6 +93,14 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -542,6 +550,29 @@ } } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", diff --git a/login-service/package.json b/login-service/package.json index 87f5bb6..0815794 100644 --- a/login-service/package.json +++ b/login-service/package.json @@ -15,6 +15,7 @@ "nodemon": "^2.0.3" }, "dependencies": { + "axios": "^0.19.2", "express": "^4.17.1", "jsonwebtoken": "^8.5.1" } diff --git a/login-service/src/index.js b/login-service/src/index.js index 0de5931..248c269 100644 --- a/login-service/src/index.js +++ b/login-service/src/index.js @@ -1,4 +1,5 @@ const express = require('express'); +const axios = require('axios'); var jwt = require('jsonwebtoken'); const app = express(); @@ -13,7 +14,17 @@ const port = 4000; const JWT_SECRET = process.env.JWT_SECRET; -const loginWithUsernameAndPassword = (username, password) => { +const authorizeUserNamePassword = (username, password) => { + // TODO + const now = Math.round(new Date().getTime() / 1000); + + return { + role: 'todo_user', + exp: now + 30 * 60, // 30 minutes + }; +}; + +const authorizeGoogle = googleUser => { // TODO const now = Math.round(new Date().getTime() / 1000); @@ -24,12 +35,30 @@ const loginWithUsernameAndPassword = (username, password) => { }; app.get('/', (req, res) => res.send('Login service API')); -app.post('/login', (req, res) => { + +// Username + Password login +app.post('/login/userpass', (req, res) => { const payload = req.body; const { username, password } = payload; const claims = loginWithUsernameAndPassword(username, password); const signedJWT = jwt.sign(claims, JWT_SECRET); - res.json({ jwt: signedJWT }); + res.json({ jwt: signedJWT, displayName: username }); +}); + +// Google access token login +app.post('/login/google', async (req, res) => { + const payload = req.body; + const { accessToken } = payload; + try { + const resp = await axios.get(`https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=${accessToken}`); + const payload = resp.data; + const { email } = payload; + const claims = authorizeGoogle(payload); + const signedJWT = jwt.sign(claims, JWT_SECRET); + res.json({ jwt: signedJWT, displayName: email }); + } catch (err) { + console.error(err); + } }); app.listen(port, () => console.log(`Listening at http://localhost:${port}`)); -- cgit v1.3