From a5542cf8cd79ebec8a4ab35b430dcf6e77bb510d Mon Sep 17 00:00:00 2001 From: Dimitrie Stefanescu Date: Sat, 26 Dec 2020 20:39:35 +0200 Subject: [PATCH] feat(server/frontend): wIP: auth flows are being revamped. HIC SVNT DRACONES --- frontend/package-lock.json | 118 ++++-- frontend/public/app.html | 10 +- frontend/public/auth.html | 19 - frontend/src/App.vue | 170 ++++++++ frontend/src/AppAuth.vue | 235 ----------- frontend/src/AppFrontend.vue | 279 ------------- frontend/src/auth-helpers.js | 156 +++---- frontend/src/components/SidebarStream.vue | 23 +- frontend/src/components/auth/Blurb.vue | 25 ++ frontend/src/components/auth/Strategies.vue | 32 ++ .../src/components/dialogs/ServerDialog.vue | 25 +- frontend/src/main-auth.js | 26 -- frontend/src/main-frontend.js | 58 ++- frontend/src/plugins/vuetify.js | 54 +-- frontend/src/router/auth-router.js | 31 +- frontend/src/router/index.js | 179 +++++--- frontend/src/views/Auth.vue | 45 ++ frontend/src/views/Frontend.vue | 125 ++++++ frontend/src/views/Home.vue | 6 +- frontend/src/views/Profile.vue | 2 +- frontend/src/views/ProfileUser.vue | 2 +- frontend/src/views/Stream.vue | 2 +- frontend/src/views/auth/AuthorizeApp.vue | 229 ++++------ frontend/src/views/auth/Login.vue | 197 +++++---- frontend/src/views/auth/Registration.vue | 391 +++++++++--------- frontend/vue.config.js | 21 +- 26 files changed, 1207 insertions(+), 1253 deletions(-) delete mode 100644 frontend/public/auth.html create mode 100644 frontend/src/App.vue delete mode 100644 frontend/src/AppAuth.vue delete mode 100644 frontend/src/AppFrontend.vue create mode 100644 frontend/src/components/auth/Blurb.vue create mode 100644 frontend/src/components/auth/Strategies.vue delete mode 100644 frontend/src/main-auth.js create mode 100644 frontend/src/views/Auth.vue create mode 100644 frontend/src/views/Frontend.vue diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f209f19fa..bffc73c4b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1125,9 +1125,9 @@ "dev": true }, "import-fresh": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", - "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -6221,9 +6221,9 @@ } }, "dompurify": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.4.tgz", - "integrity": "sha512-jE21SelIgWrGKoXGfGPA524Zt1IJFBnktwfFMHDlEYRx5FZOdc+4eEH9mkA6PuhExrq3HVpJnY8hMYUzAMl0OA==" + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.6.tgz", + "integrity": "sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ==" }, "domutils": { "version": "1.7.0", @@ -6504,9 +6504,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.15.0.tgz", - "integrity": "sha512-Vr64xFDT8w30wFll643e7cGrIkPEU50yIiI36OdSIDoSGguIeaLzBo0vpGvzo9RECUqq7htURfwEtKqwytkqzA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.16.0.tgz", + "integrity": "sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -6543,7 +6543,7 @@ "semver": "^7.2.1", "strip-ansi": "^6.0.0", "strip-json-comments": "^3.1.0", - "table": "^5.2.3", + "table": "^6.0.4", "text-table": "^0.2.0", "v8-compile-cache": "^2.0.3" }, @@ -6557,6 +6557,12 @@ "color-convert": "^2.0.1" } }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "chalk": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", @@ -6648,15 +6654,21 @@ "dev": true }, "import-fresh": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", - "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -6702,6 +6714,28 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6717,6 +6751,32 @@ "has-flag": "^4.0.0" } }, + "table": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.4.tgz", + "integrity": "sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "lodash": "^4.17.20", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + } + } + }, "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", @@ -6871,24 +6931,24 @@ } }, "eslint-plugin-prettier": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.2.0.tgz", - "integrity": "sha512-kOUSJnFjAUFKwVxuzy6sA5yyMx6+o9ino4gCdShzBNx4eyFRudWRYKCFolKjoM40PEiuU6Cn7wBLfq3WsGg7qg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.3.0.tgz", + "integrity": "sha512-tMTwO8iUWlSRZIwS9k7/E4vrTsfvsrcM5p1eftyuqWH25nKsz/o6/54I7jwQ/3zobISyC7wMy9ZsFwgTxOcOpQ==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" } }, "eslint-plugin-vue": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.2.0.tgz", - "integrity": "sha512-4mt0yIv6rBDNtvis/g22a0ozJ12GfcdEzX77u0ICYjKlxOVtGrKGEvo0cbOObHaKDg9a9kJcoaNodqE4TPfS2A==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-7.3.0.tgz", + "integrity": "sha512-4rc9xrZgwT4aLz3XE6lrHu+FZtDLWennYvtzVvvS81kW9c65U4DUzQQWAFjDCgCFvN6HYWxi7ueEtxZVSB+f0g==", "dev": true, "requires": { "eslint-utils": "^2.1.0", "natural-compare": "^1.4.0", "semver": "^7.3.2", - "vue-eslint-parser": "^7.2.0" + "vue-eslint-parser": "^7.3.0" }, "dependencies": { "lru-cache": { @@ -9674,9 +9734,9 @@ } }, "marked": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.6.tgz", - "integrity": "sha512-7vVuSEZ8g/HH3hK/BH/+7u/NJj7x9VY4EHzujLDcqAQLiOUeFJYAsfSAyoWtR17lKrx7b08qyIno4lffwrzTaA==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.7.tgz", + "integrity": "sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA==" }, "md5.js": { "version": "1.3.5", @@ -14487,9 +14547,9 @@ } }, "vue-eslint-parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.2.0.tgz", - "integrity": "sha512-uVcQqe8sUNzdHGcRHMd2Z/hl6qEaWrAmglTKP92Fnq9TYU9un8xsyFgEdFJaXh/1rd7h8Aic1GaiQow5nVneow==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.3.0.tgz", + "integrity": "sha512-n5PJKZbyspD0+8LnaZgpEvNCrjQx1DyDHw8JdWwoxhhC+yRip4TAvSDpXGf9SWX6b0umeB5aR61gwUo6NVvFxw==", "dev": true, "requires": { "debug": "^4.1.1", @@ -14644,9 +14704,9 @@ } }, "vuetify": { - "version": "2.3.21", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.21.tgz", - "integrity": "sha512-c9FOjkpVPDoIim88wbfqSIuCsH3jtgQQBC1iMW+ZFxf/Bj+d73HySL2LhEnZwAQT7XTAUGfad4aLPfcNZzK5YQ==" + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.4.0.tgz", + "integrity": "sha512-FBFAtg1ZnNwDBhMzENCzgh0hBV+HMjXejrxeRQqTfKPojKQSQFswtdHatUPmlkArDulZC73GRs2F/IwdF48o5g==" }, "vuetify-image-input": { "version": "19.1.0", diff --git a/frontend/public/app.html b/frontend/public/app.html index 5196b524b..0115e1184 100644 --- a/frontend/public/app.html +++ b/frontend/public/app.html @@ -11,13 +11,13 @@ @@ -28,7 +28,7 @@
- 🔓 Logging you in... + Loading.
diff --git a/frontend/public/auth.html b/frontend/public/auth.html deleted file mode 100644 index bc5146586..000000000 --- a/frontend/public/auth.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - <%= htmlWebpackPlugin.options.title %> - - - - - -
- - - diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 000000000..24834903c --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,170 @@ + + + diff --git a/frontend/src/AppAuth.vue b/frontend/src/AppAuth.vue deleted file mode 100644 index 53b51be84..000000000 --- a/frontend/src/AppAuth.vue +++ /dev/null @@ -1,235 +0,0 @@ - - diff --git a/frontend/src/AppFrontend.vue b/frontend/src/AppFrontend.vue deleted file mode 100644 index 4b6595de4..000000000 --- a/frontend/src/AppFrontend.vue +++ /dev/null @@ -1,279 +0,0 @@ - - - diff --git a/frontend/src/auth-helpers.js b/frontend/src/auth-helpers.js index c5e3d6723..28feff256 100644 --- a/frontend/src/auth-helpers.js +++ b/frontend/src/auth-helpers.js @@ -1,81 +1,41 @@ -import crs from "crypto-random-string" +import crs from 'crypto-random-string' -const appId = "spklwebapp" -const appSecret = "spklwebapp" +const appId = 'spklwebapp' +const appSecret = 'spklwebapp' -export async function signIn() { - // Stage 0: if we have an access code, exchange it for a token - const accessCode = new URLSearchParams(window.location.search).get( - "access_code" - ) +export async function checkAccessCodeAndGetTokens() { + const accessCode = new URLSearchParams(window.location.search).get('access_code') if (accessCode) { let response = await getTokenFromAccessCode(accessCode) - if (response.hasOwnProperty("token")) { + // eslint-disable-next-line no-prototype-builtins + if (response.hasOwnProperty('token')) { localStorage.clear() - localStorage.setItem("AuthToken", response.token) - localStorage.setItem("RefreshToken", response.refreshToken) - await prefetchUserAndSetSuuid() - window.history.replaceState({}, document.title, "/") + localStorage.setItem('AuthToken', response.token) + localStorage.setItem('RefreshToken', response.refreshToken) + window.history.replaceState({}, document.title, '/') return true } + } else { + throw new Error(`No access code present in the url: ${window.location.href}`) } - - // Stage 1: check if there is an existing valid token by pinging the graphql api - let token = localStorage.getItem("AuthToken") - if (token) { - let data = await prefetchUserAndSetSuuid() - // if res.data.user is non null, means the ping was ok & token is valid - if (data.user) return true - } - - // Stage 2: check if we have a valid refresh token by using it! - let refreshToken = localStorage.getItem("RefreshToken") - - if (refreshToken) { - let refreshResponse = await fetch("/auth/token", { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify({ - refreshToken: refreshToken, - appId: appId, - appSecret: appSecret - }) - }) - - let data = await refreshResponse.json() - - if (data.hasOwnProperty("token")) { - localStorage.setItem("AuthToken", data.token) - localStorage.setItem("RefreshToken", data.refreshToken) - await prefetchUserAndSetSuuid() - return true - } - } - - // tried all avenues, means we need to init a full authorization flow. - // this will essentially refresh the browser window, so no need to return. - redirectToAuth() - return false } -async function prefetchUserAndSetSuuid() { - let token = localStorage.getItem("AuthToken") +export async function prefetchUserAndSetSuuid() { + let token = localStorage.getItem('AuthToken') if (token) { - let testResponse = await fetch("/graphql", { - method: "POST", + let testResponse = await fetch('/graphql', { + method: 'POST', headers: { - Authorization: "Bearer " + token, - "Content-Type": "application/json" + Authorization: 'Bearer ' + token, + 'Content-Type': 'application/json' }, body: JSON.stringify({ query: `{ user { id suuid } }` }) }) let data = (await testResponse.json()).data if (data.user) { - localStorage.setItem("suuid", data.user.suuid) - localStorage.setItem("uuid", data.user.id) + localStorage.setItem('suuid', data.user.suuid) + localStorage.setItem('uuid', data.user.id) } return data @@ -83,17 +43,16 @@ async function prefetchUserAndSetSuuid() { } export async function getTokenFromAccessCode(accessCode) { - console.log("found local challenge: " + localStorage.getItem("appChallenge")) - let response = await fetch("/auth/token", { - method: "POST", + let response = await fetch('/auth/token', { + method: 'POST', headers: { - "Content-Type": "application/json" + 'Content-Type': 'application/json' }, body: JSON.stringify({ accessCode: accessCode, appId: appId, appSecret: appSecret, - challenge: localStorage.getItem("appChallenge") + challenge: localStorage.getItem('appChallenge') }) }) @@ -104,9 +63,68 @@ export async function getTokenFromAccessCode(accessCode) { export function redirectToAuth() { // Reaching this stage means we're initialising a full new auth flow, // TIP: also means we need to refresh the app challenge as well. - localStorage.setItem("appChallenge", crs({ length: 10 })) + localStorage.setItem('appChallenge', crs({ length: 10 })) // Finally, redirect to the auth lock. - window.location = `/auth?app_id=spklwebapp&challenge=${localStorage.getItem( - "appChallenge" - )}` + window.location = `/auth?appId=${appId}&challenge=${localStorage.getItem('appChallenge')}` +} + +export async function signOut() { + // TODO +} + +export async function signIn() { + // Stage 0: if we have an access code, exchange it for a token + const accessCode = new URLSearchParams(window.location.search).get('access_code') + if (accessCode) { + let response = await getTokenFromAccessCode(accessCode) + // eslint-disable-next-line no-prototype-builtins + if (response.hasOwnProperty('token')) { + localStorage.clear() + localStorage.setItem('AuthToken', response.token) + localStorage.setItem('RefreshToken', response.refreshToken) + await prefetchUserAndSetSuuid() + window.history.replaceState({}, document.title, '/') + return true + } + } + + // Stage 1: check if there is an existing valid token by pinging the graphql api + let token = localStorage.getItem('AuthToken') + if (token) { + let data = await prefetchUserAndSetSuuid() + // if res.data.user is non null, means the ping was ok & token is valid + if (data.user) return true + } + + // Stage 2: check if we have a valid refresh token by using it! + let refreshToken = localStorage.getItem('RefreshToken') + + if (refreshToken) { + let refreshResponse = await fetch('/auth/token', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + refreshToken: refreshToken, + appId: appId, + appSecret: appSecret + }) + }) + + let data = await refreshResponse.json() + + // eslint-disable-next-line no-prototype-builtins + if (data.hasOwnProperty('token')) { + localStorage.setItem('AuthToken', data.token) + localStorage.setItem('RefreshToken', data.refreshToken) + await prefetchUserAndSetSuuid() + return true + } + } + + // tried all avenues, means we need to init a full authorization flow. + // this will essentially refresh the browser window, so no need to return. + redirectToAuth() + return false } diff --git a/frontend/src/components/SidebarStream.vue b/frontend/src/components/SidebarStream.vue index ef3810030..330d74c66 100644 --- a/frontend/src/components/SidebarStream.vue +++ b/frontend/src/components/SidebarStream.vue @@ -3,7 +3,7 @@ - + {{ stream.name }} @@ -44,14 +44,18 @@   link sharing off

+ + mdi-cog-outline Edit - mdi-cog-outline - + + + mdi-account-multiple Manage - mdi-account-multiple - diff --git a/frontend/src/components/auth/Blurb.vue b/frontend/src/components/auth/Blurb.vue new file mode 100644 index 000000000..536dc8f93 --- /dev/null +++ b/frontend/src/components/auth/Blurb.vue @@ -0,0 +1,25 @@ + + diff --git a/frontend/src/components/auth/Strategies.vue b/frontend/src/components/auth/Strategies.vue new file mode 100644 index 000000000..e685a3e5f --- /dev/null +++ b/frontend/src/components/auth/Strategies.vue @@ -0,0 +1,32 @@ + + diff --git a/frontend/src/components/dialogs/ServerDialog.vue b/frontend/src/components/dialogs/ServerDialog.vue index 299c005e4..af6da7ff3 100644 --- a/frontend/src/components/dialogs/ServerDialog.vue +++ b/frontend/src/components/dialogs/ServerDialog.vue @@ -4,12 +4,7 @@ Edit Server Info - + @@ -25,11 +20,7 @@ - + @@ -66,9 +57,7 @@ - - Save - + Save @@ -95,7 +84,7 @@ export default { } }, watch: { - "server.name"(val) { + 'server.name'(val) { this.nameRules = [] } }, @@ -119,9 +108,9 @@ export default { }, agree() { this.nameRules = [ - (v) => !!v || "Servers need a name too!", - (v) => (v && v.length <= 100) || "Name must be less than 25 characters", - (v) => (v && v.length >= 3) || "Name must be at least 3 characters" + (v) => !!v || 'Servers need a name too!', + (v) => (v && v.length <= 100) || 'Name must be less than 25 characters', + (v) => (v && v.length >= 3) || 'Name must be at least 3 characters' ] let self = this diff --git a/frontend/src/main-auth.js b/frontend/src/main-auth.js deleted file mode 100644 index 19a7b62c2..000000000 --- a/frontend/src/main-auth.js +++ /dev/null @@ -1,26 +0,0 @@ -import Vue from 'vue' -import App from './AppAuth.vue' -import router from './router/auth-router' -import vuetify from './plugins/vuetify'; -import { createProvider } from './vue-apollo' - -Vue.config.productionTip = false - -let urlParams = new URLSearchParams( window.location.search ) -let appId = urlParams.get( 'appId' ) || 'spklwebapp' -let token = urlParams.get( 'token' ) -let refreshToken = urlParams.get( 'refreshToken' ) - -if ( token ) - localStorage.setItem( 'AuthToken', token ) - -if ( refreshToken ) - localStorage.setItem( 'RefreshToken', token ) - - -new Vue( { - router, - vuetify, - apolloProvider: createProvider( ), - render: h => h( App ) -} ).$mount( '#app' ) \ No newline at end of file diff --git a/frontend/src/main-frontend.js b/frontend/src/main-frontend.js index c7e75362a..35d968355 100644 --- a/frontend/src/main-frontend.js +++ b/frontend/src/main-frontend.js @@ -1,11 +1,10 @@ import Vue from 'vue' -import App from './AppFrontend.vue' +import App from './App.vue' import { createProvider } from './vue-apollo' -import { signIn } from './auth-helpers' +import { signIn, checkAccessCodeAndGetTokens, prefetchUserAndSetSuuid } from './auth-helpers' import router from './router' -import store from './store' import vuetify from './plugins/vuetify'; Vue.config.productionTip = false @@ -24,26 +23,51 @@ Vue.use( VTooltip, { defaultDelay: 300 } ) import VueMatomo from 'vue-matomo' -/* Semicolon of Doom */ -; -/* Semicolon of Doom */ +Vue.use( VueMatomo, { + host: 'https://speckle.matomo.cloud', + siteId: 4, + router: router, + userId: localStorage.getItem( 'suuid' ) +} ) -( async ( ) => { - let result = await signIn( ) - if ( !result ) return +let AuthToken = localStorage.getItem('AuthToken') +let RefreshToken = localStorage.getItem('RefreshToken') - Vue.use( VueMatomo, { - host: 'https://speckle.matomo.cloud', - siteId: 4, - router: router, - userId: localStorage.getItem( 'suuid' ) - } ) +if(AuthToken) { + console.log( 'haz auth token') + prefetchUserAndSetSuuid() + .then(res => { + initVue() + }) + .catch( err => { + if(RefreshToken) { + // TODO: try to rotate token & prefetch user, etc. + } + }) +} else { + + checkAccessCodeAndGetTokens() + .then(res => { + return prefetchUserAndSetSuuid() + }) + .then(res => { + initVue() + }) + .catch( err => { + initVue() + }) + +} + +function initVue() { new Vue( { router, - store, vuetify, apolloProvider: createProvider( ), render: h => h( App ) } ).$mount( '#app' ) -} )( ) +} + + + diff --git a/frontend/src/plugins/vuetify.js b/frontend/src/plugins/vuetify.js index 08fa1ed9d..f1e4c7cce 100644 --- a/frontend/src/plugins/vuetify.js +++ b/frontend/src/plugins/vuetify.js @@ -1,38 +1,44 @@ -import "@mdi/font/css/materialdesignicons.css" -import Vue from "vue" -import Vuetify from "vuetify/lib" +import '@mdi/font/css/materialdesignicons.css' +import Vue from 'vue' +import Vuetify from 'vuetify/lib' Vue.use(Vuetify) +let darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)') +let hasDarkMode = localStorage.getItem('darkModeEnabled') +if (!hasDarkMode && darkMediaQuery) { + localStorage.setItem('darkModeEnabled', 'dark') +} + export default new Vuetify({ icons: { - iconfont: "mdi" + iconfont: 'mdi' }, theme: { - dark: localStorage.getItem("darkModeEnabled") === "dark", + dark: localStorage.getItem('darkModeEnabled') === 'dark', themes: { light: { - primary: "#047EFB", //blue - secondary: "#7BBCFF", //light blue - accent: "#FCF25E", //yellow - error: "#FF5555", //red - warning: "#FF9100", //orange - info: "#313BCF", //dark blue - success: "#4caf50", - background: "#eeeeee", - background2: "#ffffff", - text: "#FFFFFF" + primary: '#047EFB', //blue + secondary: '#7BBCFF', //light blue + accent: '#FCF25E', //yellow + error: '#FF5555', //red + warning: '#FF9100', //orange + info: '#313BCF', //dark blue + success: '#4caf50', + background: '#eeeeee', + background2: '#ffffff', + text: '#FFFFFF' }, dark: { - primary: "#047EFB", //blue - secondary: "#7BBCFF", //light blue - accent: "#FCF25E", //yellow - error: "#FF5555", //red - warning: "#FF9100", //orange - info: "#313BCF", //dark blue - success: "#4caf50", - background: "#3a3b3c", - background2: "#303132" + primary: '#047EFB', //blue + secondary: '#7BBCFF', //light blue + accent: '#FCF25E', //yellow + error: '#FF5555', //red + warning: '#FF9100', //orange + info: '#313BCF', //dark blue + success: '#4caf50', + background: '#3a3b3c', + background2: '#303132' } } } diff --git a/frontend/src/router/auth-router.js b/frontend/src/router/auth-router.js index 6745da3ee..0e8401781 100644 --- a/frontend/src/router/auth-router.js +++ b/frontend/src/router/auth-router.js @@ -1,34 +1,29 @@ -import Vue from "vue" -import VueRouter from "vue-router" -import Home from "../views/Home.vue" +import Vue from 'vue' +import VueRouter from 'vue-router' +import Home from '../views/Home.vue' Vue.use(VueRouter) const routes = [ { - path: "/auth", - name: "Login", - component: () => import("../views/auth/Login.vue") + path: '/auth', + name: 'Login', + component: () => import('../views/auth/Login.vue') }, { - path: "/auth/login", - name: "Login", - component: () => import("../views/auth/Login.vue") + path: '/auth/register', + name: 'Register', + component: () => import('../views/auth/Registration.vue') }, { - path: "/auth/register", - name: "Register", - component: () => import("../views/auth/Registration.vue") - }, - { - path: "/auth/finalize", - name: "AuthorizeApp", - component: () => import("../views/auth/AuthorizeApp.vue") + path: '/auth/finalize', + name: 'AuthorizeApp', + component: () => import('../views/auth/AuthorizeApp.vue') } ] const router = new VueRouter({ - mode: "history", + mode: 'history', base: process.env.BASE_URL, routes }) diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index f8c28a2ea..7e6a97151 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -5,85 +5,125 @@ Vue.use(VueRouter) const routes = [ { - path: '/', - name: 'home', - meta: { - title: 'Home | Speckle' - }, - component: () => import('../views/Home.vue') - }, - { - path: '/streams', - name: 'streams', - meta: { - title: 'Streams | Speckle' - }, - component: () => import('../views/Streams.vue') - }, - { - path: '/streams/:streamId', - meta: { - title: 'Stream | Speckle' - }, - component: () => import('../views/Stream.vue'), + path: '/authn', + name: 'Auth', + component: () => import('../views/Auth.vue'), children: [ { - path: '', - name: 'stream', + path: 'login', + name: 'Login', meta: { - title: 'Stream | Speckle' + title: 'Login | Speckle' }, - component: () => import('../views/StreamMain.vue') + component: () => import('../views/auth/Login.vue') }, { - path: 'branches/:branchName', - name: 'branch', + path: 'register', + name: 'Register', meta: { - title: 'Branch | Speckle' + title: 'Register | Speckle' }, - component: () => import('../views/Branch.vue') + component: () => import('../views/auth/Registration.vue') }, { - path: 'commits/:commitId', - name: 'commit', + path: 'verify/:appId/:challenge', + name: 'Authorize App', meta: { - title: 'Commit | Speckle' + title: 'Authorizing App | Speckle' }, - component: () => import('../views/Commit.vue') - }, - { - path: 'objects/:objectId', - name: 'objects', - meta: { - title: 'Object | Speckle' - }, - component: () => import('../views/Object.vue') + component: () => import('../views/auth/AuthorizeApp.vue') } ] }, { - path: '/profile', - name: 'profile', + path: '/', meta: { - title: 'Your Profile | Speckle' + title: 'Home | Speckle' }, - component: () => import('../views/Profile.vue') - }, - { - path: '/profile/:userId', - name: 'user profile', - meta: { - title: 'User Profile | Speckle' - }, - component: () => import('../views/ProfileUser.vue') - }, - { - path: '/help', - name: 'help', - meta: { - title: 'Help | Speckle' - }, - component: () => import('../views/Help.vue') + component: () => import('../views/Frontend.vue'), + children: [ + { + path: '', + name: 'home', + meta: { + title: 'Streams | Speckle' + }, + component: () => import('../views/Home.vue') + }, + { + path: 'streams', + name: 'streams', + meta: { + title: 'Streams | Speckle' + }, + component: () => import('../views/Streams.vue') + }, + { + path: 'streams/:streamId', + meta: { + title: 'Stream | Speckle' + }, + component: () => import('../views/Stream.vue'), + children: [ + { + path: '', + name: 'stream', + meta: { + title: 'Stream | Speckle' + }, + component: () => import('../views/StreamMain.vue') + }, + { + path: 'branches/:branchName', + name: 'branch', + meta: { + title: 'Branch | Speckle' + }, + component: () => import('../views/Branch.vue') + }, + { + path: 'commits/:commitId', + name: 'commit', + meta: { + title: 'Commit | Speckle' + }, + component: () => import('../views/Commit.vue') + }, + { + path: 'objects/:objectId', + name: 'objects', + meta: { + title: 'Object | Speckle' + }, + component: () => import('../views/Object.vue') + } + ] + }, + { + path: 'profile', + name: 'profile', + meta: { + title: 'Your Profile | Speckle' + }, + component: () => import('../views/Profile.vue') + }, + { + path: 'profile/:userId', + name: 'user profile', + meta: { + title: 'User Profile | Speckle' + }, + component: () => import('../views/ProfileUser.vue') + }, + { + path: 'help', + name: 'help', + meta: { + title: 'Help | Speckle' + }, + component: () => import('../views/Help.vue') + } + ] }, { path: '/about', @@ -117,6 +157,23 @@ const router = new VueRouter({ routes }) +router.beforeEach((to, from, next) => { + if (!(to.name === 'Login' || to.name === 'Register') && !localStorage.getItem('uuid')) { + localStorage.setItem('shouldRedirectTo', to.name) + return next({ name: 'Login' }) + } else if ((to.name === 'Login' || to.name === 'Register') && !!localStorage.getItem('uuid')) { + return next({ name: 'home' }) + } else return next() + + // else if (localStorage.getItem('uuid')) { + // let shouldRedirectTo = localStorage.getItem('shouldRedirectTo') + // localStorage.removeItem('shouldRedirectTo') + // if (shouldRedirectTo) { + // return next() // TODO: redirect to prev url + // } else return next() + // } +}) + //TODO: include stream name in page title eg `My Cool Stream | Speckle` router.afterEach((to, from) => { Vue.nextTick(() => { diff --git a/frontend/src/views/Auth.vue b/frontend/src/views/Auth.vue new file mode 100644 index 000000000..5c2d2d549 --- /dev/null +++ b/frontend/src/views/Auth.vue @@ -0,0 +1,45 @@ + + diff --git a/frontend/src/views/Frontend.vue b/frontend/src/views/Frontend.vue new file mode 100644 index 000000000..d1c265fc7 --- /dev/null +++ b/frontend/src/views/Frontend.vue @@ -0,0 +1,125 @@ + + diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue index 7c1dd6f5a..3b2db3f43 100644 --- a/frontend/src/views/Home.vue +++ b/frontend/src/views/Home.vue @@ -29,12 +29,12 @@ - + - + - + Recent activity: - + diff --git a/frontend/src/views/ProfileUser.vue b/frontend/src/views/ProfileUser.vue index fe62e590b..1704f483d 100644 --- a/frontend/src/views/ProfileUser.vue +++ b/frontend/src/views/ProfileUser.vue @@ -9,7 +9,7 @@ - + {{ user.name }} has {{ user.streams.totalCount }} public streams and diff --git a/frontend/src/views/Stream.vue b/frontend/src/views/Stream.vue index 85573090a..49c096759 100644 --- a/frontend/src/views/Stream.vue +++ b/frontend/src/views/Stream.vue @@ -4,7 +4,7 @@ - + diff --git a/frontend/src/views/auth/AuthorizeApp.vue b/frontend/src/views/auth/AuthorizeApp.vue index 3d4a2fa02..7c3609bd0 100644 --- a/frontend/src/views/auth/AuthorizeApp.vue +++ b/frontend/src/views/auth/AuthorizeApp.vue @@ -1,164 +1,105 @@ diff --git a/frontend/vue.config.js b/frontend/vue.config.js index a86f10314..789e952ac 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -1,26 +1,19 @@ module.exports = { pages: { app: { - entry: "src/main-frontend.js", - title: "Speckle", - template: "public/app.html", - filename: "app.html" - }, - auth: { - entry: "src/main-auth.js", - title: "Speckle Authentication", - template: "public/auth.html", - filename: "auth.html" + entry: 'src/main-frontend.js', + title: 'Speckle', + template: 'public/app.html', + filename: 'app.html' } }, devServer: { historyApiFallback: { rewrites: [ - { from: /^\/$/, to: "/app.html" }, - { from: /\/auth/, to: "/auth.html" }, - { from: /./, to: "/app.html" } + { from: /^\/$/, to: '/app.html' }, + { from: /./, to: '/app.html' } ] } }, - transpileDependencies: ["vuetify"] + transpileDependencies: ['vuetify'] }