diff --git a/.gitignore b/.gitignore index 9f7f13e7..132e5530 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,9 @@ **/yarn-error.log **/.DS_Store **/dist/ -server/data/ +server/data/assets/* +server/data/tmp/* +server/data/yjs/* screenshot-server/data/ deploy/ **/.env diff --git a/client/public/jwt-signin.html b/client/public/jwt-signin.html index e33657a9..17291c74 100644 --- a/client/public/jwt-signin.html +++ b/client/public/jwt-signin.html @@ -58,7 +58,7 @@

JWT Sign In

function login(username, relm, secret = defaultSecret) { const jwt = makeToken(username, { [relm]: "ae" }); - window.location = "/?relm=" + relm + "&jwt=" + jwt; + window.location = "/" + relm + "?jwt=" + jwt; } // Example Payload: diff --git a/client/src/main/RelmRestAPI.ts b/client/src/main/RelmRestAPI.ts index aace5916..20499b85 100644 --- a/client/src/main/RelmRestAPI.ts +++ b/client/src/main/RelmRestAPI.ts @@ -10,6 +10,10 @@ export type LoginCredentials = { password: string }; +export type RegisterCredentials = LoginCredentials & { + identity: SavedIdentityData +}; + export class RelmRestAPI { url: string; relmName: string; @@ -303,7 +307,7 @@ export class RelmRestAPI { } } - async registerParticipant(credentials: LoginCredentials): Promise { + async registerParticipant(credentials: RegisterCredentials): Promise { const result: AuthenticationResponse = await this.post("/auth/connect/local/signup", { ...credentials }); diff --git a/client/src/ui/ButtonControls/ConnectionButton/SignInWindow.svelte b/client/src/ui/ButtonControls/ConnectionButton/SignInWindow.svelte index 919fa0c2..50a96ebf 100644 --- a/client/src/ui/ButtonControls/ConnectionButton/SignInWindow.svelte +++ b/client/src/ui/ButtonControls/ConnectionButton/SignInWindow.svelte @@ -6,6 +6,7 @@ import { showCenterButtons } from "~/stores/showCenterButtons"; import { getNotificationsContext } from "svelte-notifications"; import { _ } from "~/i18n"; + import ConnectionSubmitInput from "./components/ConnectionSubmitInput.svelte"; export let enabled; export let dispatch: Dispatch; @@ -14,6 +15,7 @@ let showingSignin = true; + // Handles login via social oAuths async function onSocialClick({ target }) { const socialId = target.getAttribute("data-login"); @@ -31,20 +33,21 @@ text: $_(response.reason, { default: response.details }), - position: "top-left", + position: "bottom-center", removeAfter: 3000 }); } } - async function onUsernamePasswordAction(action: "signin"|"register") { + async function onEmailPasswordAction() { const email = (document.querySelector('input[name="email"]') as HTMLInputElement).value; const password = (document.querySelector('input[name="password"]') as HTMLInputElement).value; + const identity = worldManager.participants.local.identityData; - const response = action === "register" - ? await worldManager.register({ email, password }) - : await worldManager.login({ email, password }); + const response = showingSignin + ? await worldManager.login({ email, password }) + : await worldManager.register({ email, password, identity }); if (response.status === "success") { // Username/password was successfully registered. @@ -55,7 +58,7 @@ text: $_(response.reason, { default: response.details }), - position: "top-left", + position: "bottom-center", removeAfter: 3000 }); } @@ -107,84 +110,91 @@ - + Signout - {#if showingSignin} - - - - +
+ {#if showingSignin} + + + + + + + + + + + + + + + + + +
+ +
+
+ or enter via:
+ + Facebook + Twitter + Google + Linkedin + +
+
+ + + + +
+ + Signout + +
+
+
+ {:else} + + + - - - - - - - - -
- onUsernamePasswordAction("signin")} >SIGN IN -
-
- or enter via:
+ Facebook Twitter Google Linkedin -
-
- - - - - -
- {:else} - - - - - - - - - - - Facebook - Twitter - Google - Linkedin - - - -
- onUsernamePasswordAction("register")} >CREATE ACCOUNT -
- - - Already have an account? Sign in - -
-
- {/if} + + +
+ +
+ + + Already have an account? Sign in + +
+ + {/if} +
@@ -202,9 +212,15 @@ // font-size: 1.1em; // } - .submit span { - color: #7f7f7f; - font-size: 4em; + #bottom-center-exit { + margin-top: 1em; + display: none; + } + + #top-right-exit { + position: absolute; + top: 5px; + right: 5px; } .switch-screen-text { @@ -253,15 +269,7 @@ opacity: 0.7; } - r-exit { - display: block; - position: absolute; - top: 5px; - right: 5px; - cursor: pointer; - } - - r-window { + r-window form { display: flex; justify-content: center; align-items: center; @@ -279,6 +287,7 @@ r-group { display: block; height: 33%; + min-height: 5em; } r-section { @@ -286,6 +295,10 @@ margin-top: 0.5em; } + r-exit { + cursor: pointer; + } + // r-forget-passcode-link { // color: #a77822; // cursor: pointer; @@ -316,13 +329,19 @@ } // If on Mobile, make the submit text smaller - @media only screen and (max-width: 450px) { - .submit span { - font-size: 2.5em; - } - + @media only screen and (min-width: 520px) { r-group { height: 25%; } } + + @media only screen and (max-width: 520px) { + #bottom-center-exit { + display: block; + } + + #top-right-exit { + display: none; + } + } \ No newline at end of file diff --git a/client/src/ui/ButtonControls/ConnectionButton/components/ConnectionSubmitInput.svelte b/client/src/ui/ButtonControls/ConnectionButton/components/ConnectionSubmitInput.svelte new file mode 100644 index 00000000..4560fed0 --- /dev/null +++ b/client/src/ui/ButtonControls/ConnectionButton/components/ConnectionSubmitInput.svelte @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/client/src/ui/Overlay/Overlay.svelte b/client/src/ui/Overlay/Overlay.svelte index 63672f6c..0e2d15e7 100644 --- a/client/src/ui/Overlay/Overlay.svelte +++ b/client/src/ui/Overlay/Overlay.svelte @@ -7,7 +7,7 @@ import VideoButton from "~/ui/ButtonControls/VideoButton"; import AvatarSetupButton from "~/ui/ButtonControls/AvatarSetupButton"; import ShareScreenButton from "~/ui/ButtonControls/ShareScreenButton"; - import { SignInButton } from "~/ui/ButtonControls/ConnectionButton"; + import { SignInButton, LogoutButton } from "~/ui/ButtonControls/ConnectionButton"; import InviteButton from "~/ui/ButtonControls/InviteButton"; import ChatButton from "~/ui/Chat/ChatButton.svelte"; @@ -34,7 +34,6 @@ import { permits } from "~/stores/permits"; import SignInWindow from "../ButtonControls/ConnectionButton/SignInWindow.svelte"; - import LogoutButton from "../ButtonControls/ConnectionButton/LogoutButton.svelte"; export let dispatch; export let state: State; diff --git a/client/src/world/WorldManager.ts b/client/src/world/WorldManager.ts index 7921baf2..136f24c7 100644 --- a/client/src/world/WorldManager.ts +++ b/client/src/world/WorldManager.ts @@ -83,7 +83,7 @@ import { Participant } from "~/types/identity"; import { ParticipantManager } from "~/identity/ParticipantManager"; import { ParticipantYBroker } from "~/identity/ParticipantYBroker"; import { delay } from "~/utils/delay"; -import { LoginCredentials, RelmRestAPI } from "~/main/RelmRestAPI"; +import { LoginCredentials, RegisterCredentials, RelmRestAPI } from "~/main/RelmRestAPI"; import { PhotoBooth } from "./PhotoBooth"; import { audioMode, AudioMode } from "~/stores/audioMode"; import { Outline } from "~/ecs/plugins/outline"; @@ -835,7 +835,7 @@ export class WorldManager { } async register( - credentials: LoginCredentials + credentials: RegisterCredentials ): Promise { const data = await this.api.registerParticipant(credentials); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 817a7870..8a78454a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,7 +184,7 @@ importers: svelte-loader: 3.1.2_svelte@3.46.3 svelte-loader-hot: 0.3.1_svelte@3.46.3 svelte-preprocess: 4.10.7_uc5v4ovjucquxmr54mcbsi75my - terser-webpack-plugin: 5.3.4_webpack@5.74.0 + terser-webpack-plugin: 5.3.6_webpack@5.74.0 ts-jest: 27.1.4_nrjo5i5lfjbfsgb2gjwhbfmafq ts-loader: 8.4.0_xnp4kzegbjokq62cajex2ovgkm ts-node: 10.8.1_bjctuninx3nzqxltyvshqte2ni @@ -192,8 +192,8 @@ importers: typescript: 4.7.4 util: 0.12.4 webpack: 5.74.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_r5zkyi3k4cwcnn76n4wwxu6434 - webpack-dev-server: 4.10.0_uptvlxzxtuqbyzxasnpdw7lg2a + webpack-cli: 4.10.0_6k4gs4iynuwn6fxh2o2g4kzpru + webpack-dev-server: 4.10.1_uptvlxzxtuqbyzxasnpdw7lg2a common: specifiers: @@ -272,13 +272,17 @@ importers: estrace: ^4.0.0 express: ^4.17.1 express-fileupload: ^1.1.10 + express-hbs: ^2.4.0 fastestsmallesttextencoderdecoder: ^1.0.22 + form-data: ^4.0.0 + hbs: ^4.2.0 http-errors: ^1.8.0 is: ^3.3.0 jest: ^28.0 level: ^6.0.1 lib0: 0.2.51 lodash.debounce: ^4.0.8 + mailgun.js: ^7.0.4 md5-file: ^5.0.0 minimist: ^1.2.6 moment: ^2.27.0 @@ -320,12 +324,16 @@ importers: dotenv: 10.0.0 express: 4.17.3 express-fileupload: 1.3.1 + express-hbs: 2.4.0 fastestsmallesttextencoderdecoder: 1.0.22 + form-data: 4.0.0 + hbs: 4.2.0 http-errors: 1.8.1 is: 3.3.0 level: 6.0.1 lib0: 0.2.51 lodash.debounce: 4.0.8 + mailgun.js: 7.0.4 md5-file: 5.0.0 minimist: 1.2.6 moment: 2.29.2 @@ -410,7 +418,14 @@ packages: resolution: {integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/highlight': 7.17.9 + '@babel/highlight': 7.18.6 + dev: true + + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 dev: true /@babel/compat-data/7.17.10: @@ -423,15 +438,15 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.1.2 - '@babel/code-frame': 7.16.7 - '@babel/generator': 7.17.9 - '@babel/helper-compilation-targets': 7.17.7_@babel+core@7.17.9 - '@babel/helper-module-transforms': 7.17.7 + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.18.12 + '@babel/helper-compilation-targets': 7.18.2_@babel+core@7.17.9 + '@babel/helper-module-transforms': 7.18.0 '@babel/helpers': 7.17.9 - '@babel/parser': 7.17.9 - '@babel/template': 7.16.7 - '@babel/traverse': 7.17.9 - '@babel/types': 7.17.10 + '@babel/parser': 7.18.11 + '@babel/template': 7.18.10 + '@babel/traverse': 7.18.11 + '@babel/types': 7.18.10 convert-source-map: 1.8.0 debug: 4.3.4 gensync: 1.0.0-beta.2 @@ -450,12 +465,21 @@ packages: source-map: 0.5.7 dev: true + /@babel/generator/7.18.12: + resolution: {integrity: sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.18.10 + '@jridgewell/gen-mapping': 0.3.2 + jsesc: 2.5.2 + dev: true + /@babel/generator/7.18.2: resolution: {integrity: sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.18.4 - '@jridgewell/gen-mapping': 0.3.1 + '@jridgewell/gen-mapping': 0.3.2 jsesc: 2.5.2 dev: true @@ -474,19 +498,6 @@ packages: '@babel/types': 7.18.4 dev: true - /@babel/helper-compilation-targets/7.17.7_@babel+core@7.17.9: - resolution: {integrity: sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.17.10 - '@babel/core': 7.17.9 - '@babel/helper-validator-option': 7.16.7 - browserslist: 4.21.3 - semver: 6.3.0 - dev: true - /@babel/helper-compilation-targets/7.18.2_@babel+core@7.17.9: resolution: {integrity: sha512-s1jnPotJS9uQnzFtiZVBUxe67CuBa679oWFHpxYYnTpRL/1ffhyX44R9uYiXoa/pLXcY9H2moJta0iaanlk/rQ==} engines: {node: '>=6.9.0'} @@ -547,15 +558,13 @@ packages: - supports-color dev: true - /@babel/helper-environment-visitor/7.16.7: - resolution: {integrity: sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==} + /@babel/helper-environment-visitor/7.18.2: + resolution: {integrity: sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==} engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.17.10 dev: true - /@babel/helper-environment-visitor/7.18.2: - resolution: {integrity: sha512-14GQKWkX9oJzPiQQ7/J36FTXcD4kSp8egKjO9nINlSKiHITRA9q/R74qu8S9xlc/b/yjsJItQUeeh3xnGN0voQ==} + /@babel/helper-environment-visitor/7.18.9: + resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} dev: true @@ -574,6 +583,14 @@ packages: '@babel/types': 7.18.4 dev: true + /@babel/helper-function-name/7.18.9: + resolution: {integrity: sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.18.10 + '@babel/types': 7.18.10 + dev: true + /@babel/helper-hoist-variables/7.16.7: resolution: {integrity: sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==} engines: {node: '>=6.9.0'} @@ -581,48 +598,39 @@ packages: '@babel/types': 7.18.4 dev: true - /@babel/helper-member-expression-to-functions/7.17.7: - resolution: {integrity: sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==} + /@babel/helper-hoist-variables/7.18.6: + resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.18.4 + '@babel/types': 7.18.10 dev: true - /@babel/helper-module-imports/7.16.7: - resolution: {integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==} + /@babel/helper-member-expression-to-functions/7.17.7: + resolution: {integrity: sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==} engines: {node: '>=6.9.0'} dependencies: '@babel/types': 7.18.4 dev: true - /@babel/helper-module-transforms/7.17.7: - resolution: {integrity: sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==} + /@babel/helper-module-imports/7.16.7: + resolution: {integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.18.2 - '@babel/helper-module-imports': 7.16.7 - '@babel/helper-simple-access': 7.18.2 - '@babel/helper-split-export-declaration': 7.16.7 - '@babel/helper-validator-identifier': 7.16.7 - '@babel/template': 7.16.7 - '@babel/traverse': 7.17.9 - '@babel/types': 7.18.4 - transitivePeerDependencies: - - supports-color + '@babel/types': 7.18.10 dev: true /@babel/helper-module-transforms/7.18.0: resolution: {integrity: sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-environment-visitor': 7.18.2 + '@babel/helper-environment-visitor': 7.18.9 '@babel/helper-module-imports': 7.16.7 '@babel/helper-simple-access': 7.18.2 - '@babel/helper-split-export-declaration': 7.16.7 - '@babel/helper-validator-identifier': 7.16.7 - '@babel/template': 7.16.7 - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/helper-validator-identifier': 7.18.6 + '@babel/template': 7.18.10 + '@babel/traverse': 7.18.11 + '@babel/types': 7.18.10 transitivePeerDependencies: - supports-color dev: true @@ -639,6 +647,11 @@ packages: engines: {node: '>=6.9.0'} dev: true + /@babel/helper-plugin-utils/7.18.9: + resolution: {integrity: sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-remap-async-to-generator/7.16.8: resolution: {integrity: sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==} engines: {node: '>=6.9.0'} @@ -667,7 +680,7 @@ packages: resolution: {integrity: sha512-7LIrjYzndorDY88MycupkpQLKS1AFfsVRm2k/9PtKScSy5tZq0McZTj+DiMRynboZfIqOKvo03pmhTaUgiD6fQ==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.18.4 + '@babel/types': 7.18.10 dev: true /@babel/helper-skip-transparent-expression-wrappers/7.16.0: @@ -684,11 +697,28 @@ packages: '@babel/types': 7.18.4 dev: true + /@babel/helper-split-export-declaration/7.18.6: + resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.18.10 + dev: true + + /@babel/helper-string-parser/7.18.10: + resolution: {integrity: sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-validator-identifier/7.16.7: resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==} engines: {node: '>=6.9.0'} dev: true + /@babel/helper-validator-identifier/7.18.6: + resolution: {integrity: sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==} + engines: {node: '>=6.9.0'} + dev: true + /@babel/helper-validator-option/7.16.7: resolution: {integrity: sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==} engines: {node: '>=6.9.0'} @@ -710,28 +740,28 @@ packages: resolution: {integrity: sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/template': 7.16.7 - '@babel/traverse': 7.17.9 - '@babel/types': 7.18.4 + '@babel/template': 7.18.10 + '@babel/traverse': 7.18.11 + '@babel/types': 7.18.10 transitivePeerDependencies: - supports-color dev: true - /@babel/highlight/7.17.9: - resolution: {integrity: sha512-J9PfEKCbFIv2X5bjTMiZu6Vf341N05QIY+d6FvVKynkG1S7G0j3I0QoRtWIrXhZ+/Nlb5Q0MzqL7TokEJ5BNHg==} + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.16.7 + '@babel/helper-validator-identifier': 7.18.6 chalk: 2.4.2 js-tokens: 4.0.0 dev: true - /@babel/parser/7.17.9: - resolution: {integrity: sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg==} + /@babel/parser/7.18.11: + resolution: {integrity: sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==} engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.17.10 + '@babel/types': 7.18.10 dev: true /@babel/parser/7.18.5: @@ -953,7 +983,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-bigint/7.8.3_@babel+core@7.17.9: @@ -962,7 +992,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-class-properties/7.12.13_@babel+core@7.17.9: @@ -971,7 +1001,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-class-static-block/7.14.5_@babel+core@7.17.9: @@ -1018,7 +1048,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-json-strings/7.8.3_@babel+core@7.17.9: @@ -1027,7 +1057,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-logical-assignment-operators/7.10.4_@babel+core@7.17.9: @@ -1036,7 +1066,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-nullish-coalescing-operator/7.8.3_@babel+core@7.17.9: @@ -1045,7 +1075,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-numeric-separator/7.10.4_@babel+core@7.17.9: @@ -1054,7 +1084,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-object-rest-spread/7.8.3_@babel+core@7.17.9: @@ -1063,7 +1093,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-optional-catch-binding/7.8.3_@babel+core@7.17.9: @@ -1072,7 +1102,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-optional-chaining/7.8.3_@babel+core@7.17.9: @@ -1081,7 +1111,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-private-property-in-object/7.14.5_@babel+core@7.17.9: @@ -1101,7 +1131,7 @@ packages: '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.17.9 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 dev: true /@babel/plugin-syntax-typescript/7.16.7_@babel+core@7.17.9: @@ -1114,6 +1144,16 @@ packages: '@babel/helper-plugin-utils': 7.17.12 dev: true + /@babel/plugin-syntax-typescript/7.18.6_@babel+core@7.17.9: + resolution: {integrity: sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.17.9 + '@babel/helper-plugin-utils': 7.18.9 + dev: true + /@babel/plugin-transform-arrow-functions/7.17.12_@babel+core@7.17.9: resolution: {integrity: sha512-PHln3CNi/49V+mza4xMwrg+WGYevSF1oaiXaC2EQfdp4HWlSjRsrDXWJiQBKpP7749u6vQ9mcry2uuFOv5CXvA==} engines: {node: '>=6.9.0'} @@ -1293,7 +1333,7 @@ packages: dependencies: '@babel/core': 7.17.9 '@babel/helper-module-transforms': 7.18.0 - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 '@babel/helper-simple-access': 7.18.2 babel-plugin-dynamic-import-node: 2.3.3 transitivePeerDependencies: @@ -1310,7 +1350,7 @@ packages: '@babel/helper-hoist-variables': 7.16.7 '@babel/helper-module-transforms': 7.18.0 '@babel/helper-plugin-utils': 7.17.12 - '@babel/helper-validator-identifier': 7.16.7 + '@babel/helper-validator-identifier': 7.18.6 babel-plugin-dynamic-import-node: 2.3.3 transitivePeerDependencies: - supports-color @@ -1602,23 +1642,50 @@ packages: resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.18.6 '@babel/parser': 7.18.5 '@babel/types': 7.18.4 dev: true + /@babel/template/7.18.10: + resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/parser': 7.18.11 + '@babel/types': 7.18.10 + dev: true + /@babel/traverse/7.17.9: resolution: {integrity: sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.16.7 - '@babel/generator': 7.17.9 - '@babel/helper-environment-visitor': 7.16.7 - '@babel/helper-function-name': 7.17.9 - '@babel/helper-hoist-variables': 7.16.7 - '@babel/helper-split-export-declaration': 7.16.7 - '@babel/parser': 7.17.9 - '@babel/types': 7.17.10 + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.18.12 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.18.9 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.18.11 + '@babel/types': 7.18.10 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@babel/traverse/7.18.11: + resolution: {integrity: sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.18.6 + '@babel/generator': 7.18.12 + '@babel/helper-environment-visitor': 7.18.9 + '@babel/helper-function-name': 7.18.9 + '@babel/helper-hoist-variables': 7.18.6 + '@babel/helper-split-export-declaration': 7.18.6 + '@babel/parser': 7.18.11 + '@babel/types': 7.18.10 debug: 4.3.4 globals: 11.12.0 transitivePeerDependencies: @@ -1629,7 +1696,7 @@ packages: resolution: {integrity: sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==} engines: {node: '>=6.9.0'} dependencies: - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.18.6 '@babel/generator': 7.18.2 '@babel/helper-environment-visitor': 7.18.2 '@babel/helper-function-name': 7.17.9 @@ -1647,7 +1714,7 @@ packages: resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.16.7 + '@babel/helper-validator-identifier': 7.18.6 to-fast-properties: 2.0.0 dev: true @@ -1659,11 +1726,20 @@ packages: to-fast-properties: 2.0.0 dev: true + /@babel/types/7.18.10: + resolution: {integrity: sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.18.10 + '@babel/helper-validator-identifier': 7.18.6 + to-fast-properties: 2.0.0 + dev: true + /@babel/types/7.18.4: resolution: {integrity: sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.16.7 + '@babel/helper-validator-identifier': 7.18.6 to-fast-properties: 2.0.0 dev: true @@ -2238,8 +2314,8 @@ packages: chalk: 4.1.2 dev: true - /@jridgewell/gen-mapping/0.3.1: - resolution: {integrity: sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==} + /@jridgewell/gen-mapping/0.3.2: + resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==} engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.1.1 @@ -2260,7 +2336,7 @@ packages: /@jridgewell/source-map/0.3.2: resolution: {integrity: sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==} dependencies: - '@jridgewell/gen-mapping': 0.3.1 + '@jridgewell/gen-mapping': 0.3.2 '@jridgewell/trace-mapping': 0.3.14 dev: true @@ -2415,8 +2491,8 @@ packages: resolution: {integrity: sha512-1sSSCLqsL9wcX6AQa+0GFpHFLrnOB48iLsyTCJkfHo1pP5kkXoiB3ciuuttFqoGSGFJaDVunmtOX4VJzi1R4gw==} engines: {node: '>=16'} dependencies: - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 + '@babel/traverse': 7.18.11 + '@babel/types': 7.18.10 '@putout/engine-parser': 5.0.0 '@putout/operate': 8.2.0 debug: 4.3.4 @@ -2444,10 +2520,10 @@ packages: resolution: {integrity: sha512-RT5l9PDPGC95XwUb4dLZfMsvTddPiqkHQI1TWGEmZpv7zO8ajyLwfVyD3bdBg8FJ43nNjKL31hIQeOAF6l6TPw==} engines: {node: '>=16'} dependencies: - '@babel/generator': 7.18.2 - '@babel/parser': 7.18.5 - '@babel/template': 7.16.7 - '@babel/types': 7.18.4 + '@babel/generator': 7.18.12 + '@babel/parser': 7.18.11 + '@babel/template': 7.18.10 + '@babel/types': 7.18.10 '@putout/recast': 1.6.1 estree-to-babel: 5.0.0 nano-memoize: 1.3.0 @@ -2473,8 +2549,8 @@ packages: resolution: {integrity: sha512-O6ATdqrFz/wng8XV8RzpjLwZKQC4M61Fwua/5FVeLFzK25TZLHWh3bj4adGhYCSzhUnRJDB/5HCPLl6znx2ayA==} engines: {node: '>=16'} dependencies: - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 + '@babel/traverse': 7.18.11 + '@babel/types': 7.18.10 '@putout/compare': 9.2.1 '@putout/engine-parser': 5.0.0 '@putout/operate': 8.2.0 @@ -2494,7 +2570,7 @@ packages: peerDependencies: putout: '>=25' dependencies: - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.18.6 '@putout/formatter-json': 2.0.0_putout@25.17.4 chalk: 4.1.2 putout: 25.17.4 @@ -2594,7 +2670,7 @@ packages: resolution: {integrity: sha512-fQAQr2BbjC0+3rjO5/ZxB+eLB+N7BxSXEFkOv9bPUs9xnfa8x6PowVGdibs2QTzAPb9KoVOeKGfwQZQeVuOntQ==} engines: {node: '>=16'} dependencies: - '@babel/types': 7.18.4 + '@babel/types': 7.18.10 dev: true /@putout/operator-add-args/3.0.1_putout@25.17.4: @@ -2603,7 +2679,7 @@ packages: peerDependencies: putout: '>=25' dependencies: - '@babel/types': 7.18.4 + '@babel/types': 7.18.10 '@putout/compare': 9.2.1 '@putout/engine-parser': 5.0.0 putout: 25.17.4 @@ -3625,8 +3701,8 @@ packages: resolution: {integrity: sha512-nqLFW+RT9n02gGa0qzNtzWiDnRzDDUtgq3yHRHvZIaSku+k/4QdAD52u8JakIyteWotbXOF0rbBJyup2zwZVpA==} engines: {node: '>=16'} dependencies: - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 + '@babel/traverse': 7.18.11 + '@babel/types': 7.18.10 '@putout/compare': 9.2.1 transitivePeerDependencies: - supports-color @@ -3728,8 +3804,8 @@ packages: /@types/babel__core/7.1.19: resolution: {integrity: sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==} dependencies: - '@babel/parser': 7.18.5 - '@babel/types': 7.18.4 + '@babel/parser': 7.18.11 + '@babel/types': 7.18.10 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.14.2 @@ -3738,20 +3814,20 @@ packages: /@types/babel__generator/7.6.4: resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} dependencies: - '@babel/types': 7.18.4 + '@babel/types': 7.18.10 dev: true /@types/babel__template/7.4.1: resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} dependencies: - '@babel/parser': 7.18.5 - '@babel/types': 7.18.4 + '@babel/parser': 7.18.11 + '@babel/types': 7.18.10 dev: true /@types/babel__traverse/7.14.2: resolution: {integrity: sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA==} dependencies: - '@babel/types': 7.18.4 + '@babel/types': 7.18.10 dev: true /@types/body-parser/1.19.2: @@ -3796,12 +3872,12 @@ packages: /@types/eslint-scope/3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: - '@types/eslint': 8.4.5 + '@types/eslint': 8.4.6 '@types/estree': 0.0.51 dev: true - /@types/eslint/8.4.5: - resolution: {integrity: sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==} + /@types/eslint/8.4.6: + resolution: {integrity: sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==} dependencies: '@types/estree': 0.0.51 '@types/json-schema': 7.0.11 @@ -4331,7 +4407,7 @@ packages: webpack-cli: 4.x.x dependencies: webpack: 5.74.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_r5zkyi3k4cwcnn76n4wwxu6434 + webpack-cli: 4.10.0_6k4gs4iynuwn6fxh2o2g4kzpru dev: true /@webpack-cli/info/1.5.0_webpack-cli@4.10.0: @@ -4340,10 +4416,10 @@ packages: webpack-cli: 4.x.x dependencies: envinfo: 7.8.1 - webpack-cli: 4.10.0_r5zkyi3k4cwcnn76n4wwxu6434 + webpack-cli: 4.10.0_6k4gs4iynuwn6fxh2o2g4kzpru dev: true - /@webpack-cli/serve/1.7.0_7r45by5oyo7ronrzgeqf7j6vxq: + /@webpack-cli/serve/1.7.0_eib23wgkspxiotey5yppdrjtku: resolution: {integrity: sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==} peerDependencies: webpack-cli: 4.x.x @@ -4352,8 +4428,8 @@ packages: webpack-dev-server: optional: true dependencies: - webpack-cli: 4.10.0_r5zkyi3k4cwcnn76n4wwxu6434 - webpack-dev-server: 4.10.0_uptvlxzxtuqbyzxasnpdw7lg2a + webpack-cli: 4.10.0_6k4gs4iynuwn6fxh2o2g4kzpru + webpack-dev-server: 4.10.1_uptvlxzxtuqbyzxasnpdw7lg2a dev: true /@xtuc/ieee754/1.2.0: @@ -4696,7 +4772,6 @@ packages: /asynckit/0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true /audio-activity/1.0.0: resolution: {integrity: sha1-9lO5ZoWC5/1qTJuZl6viZge8xFY=} @@ -4710,7 +4785,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.3 - caniuse-lite: 1.0.30001375 + caniuse-lite: 1.0.30001385 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -4746,6 +4821,15 @@ packages: - debug dev: false + /axios/0.27.2: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + dependencies: + follow-redirects: 1.14.9 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + dev: false + /babel-jest/27.5.1_@babel+core@7.17.9: resolution: {integrity: sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -4808,7 +4892,7 @@ packages: resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} engines: {node: '>=8'} dependencies: - '@babel/helper-plugin-utils': 7.17.12 + '@babel/helper-plugin-utils': 7.18.9 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.1.0 @@ -4831,8 +4915,8 @@ packages: resolution: {integrity: sha512-NovGCy5Hn25uMJSAU8FaHqzs13cFoOI4lhIujiepssjCKRsAo3TA734RDWSGxuFTsUJXerYOqQQodlxgmtqbzw==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@babel/template': 7.16.7 - '@babel/types': 7.18.4 + '@babel/template': 7.18.10 + '@babel/types': 7.18.10 '@types/babel__core': 7.1.19 '@types/babel__traverse': 7.14.2 dev: true @@ -4933,6 +5017,10 @@ packages: resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} dev: true + /base-64/1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + dev: false + /base64-arraybuffer-es6/0.6.0_core-js-bundle@3.23.1: resolution: {integrity: sha512-57nLqKj4ShsDwFJWJsM4sZx6u60WbCge35rWRSevUwqxDtRwwxiKAO800zD2upPv4CfdWjQp//wSLar35nDKvA==} engines: {node: '>=0.10.0'} @@ -4957,7 +5045,7 @@ packages: dev: false /batch/0.6.1: - resolution: {integrity: sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=} + resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} dev: true /bcrypt/5.0.1: @@ -4993,6 +5081,10 @@ packages: readable-stream: 3.6.0 dev: false + /bluebird/3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + dev: false + /body-parser/1.19.2: resolution: {integrity: sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==} engines: {node: '>= 0.8'} @@ -5046,6 +5138,13 @@ packages: balanced-match: 1.0.2 concat-map: 0.0.1 + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + optional: true + /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -5057,13 +5156,25 @@ packages: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} dev: true + /browserslist/4.20.4: + resolution: {integrity: sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001385 + electron-to-chromium: 1.4.161 + escalade: 3.1.1 + node-releases: 2.0.5 + picocolors: 1.0.0 + dev: true + /browserslist/4.21.3: resolution: {integrity: sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001375 - electron-to-chromium: 1.4.218 + caniuse-lite: 1.0.30001385 + electron-to-chromium: 1.4.236 node-releases: 2.0.6 update-browserslist-db: 1.0.5_browserslist@4.21.3 dev: true @@ -5194,13 +5305,13 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.21.3 - caniuse-lite: 1.0.30001375 + caniuse-lite: 1.0.30001385 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: true - /caniuse-lite/1.0.30001375: - resolution: {integrity: sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==} + /caniuse-lite/1.0.30001385: + resolution: {integrity: sha512-MpiCqJGhBkHgpyimE9GWmZTnyHyEEM35u115bD3QBrXpjvL/JgcP8cUhKJshfmg4OtEHFenifcK5sZayEw5tvQ==} dev: true /capture-website/2.3.0: @@ -5362,7 +5473,6 @@ packages: is-plain-object: 2.0.4 kind-of: 6.0.3 shallow-clone: 3.0.1 - dev: true /clone-regexp/2.2.0: resolution: {integrity: sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==} @@ -5440,11 +5550,9 @@ packages: engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - dev: true /commander/2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true /commander/7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} @@ -5524,6 +5632,14 @@ packages: typedarray: 0.0.6 dev: false + /config-chain/1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + dev: false + optional: true + /connect-history-api-fallback/2.0.0: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} engines: {node: '>=0.8'} @@ -6007,6 +6123,7 @@ packages: engines: {node: '>= 0.4'} dependencies: object-keys: 1.1.1 + dev: false /define-properties/1.1.4: resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} @@ -6031,7 +6148,6 @@ packages: /delayed-stream/1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - dev: true /delegates/1.0.0: resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} @@ -6207,11 +6323,26 @@ packages: safe-buffer: 5.2.1 dev: false + /editorconfig/0.15.3: + resolution: {integrity: sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==} + hasBin: true + dependencies: + commander: 2.20.3 + lru-cache: 4.1.5 + semver: 5.7.1 + sigmund: 1.0.1 + dev: false + optional: true + /ee-first/1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - /electron-to-chromium/1.4.218: - resolution: {integrity: sha512-INDylKH//YIf2w67D+IjkfVnGVrZ/D94DAU/FPPm6T4jEPbEDQvo9r2wTj0ncFdtJH8+V8BggZTaN8Rzk5wkgw==} + /electron-to-chromium/1.4.161: + resolution: {integrity: sha512-sTjBRhqh6wFodzZtc5Iu8/R95OkwaPNn7tj/TaDU5nu/5EFiQDtADGAXdR4tJcTEHlYfJpHqigzJqHvPgehP8A==} + dev: true + + /electron-to-chromium/1.4.236: + resolution: {integrity: sha512-41wPRcb9OIpB0RMLnIIDbfbJ5IXwuQ2qAr0jCTSpv5s7M61MazcBGS5kovW5E28SGxum/KR3fm2imswjdlTWmw==} dev: true /emittery/0.10.2: @@ -6509,8 +6640,8 @@ packages: resolution: {integrity: sha512-YLPzaZZLL2XkWC9SEX/3/yQ+pB95nxwFXhNlTWQxu+fft8/93FQ906WEcNlpoS4D5pu5hFEDX0oMx4kRI5FTrw==} engines: {node: '>=16'} dependencies: - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 + '@babel/traverse': 7.18.11 + '@babel/types': 7.18.10 transitivePeerDependencies: - supports-color dev: true @@ -6634,6 +6765,17 @@ packages: busboy: 0.3.1 dev: false + /express-hbs/2.4.0: + resolution: {integrity: sha512-CruS8DA6m8i20MTjWpE6X8zu6q0rbQJbQyIp7V7NoYD1znf0krnCHQ8tVB7I9pJSZYnOPj/VPSByNU3sVDxJfw==} + dependencies: + bluebird: 3.7.2 + handlebars: 4.7.7 + lodash: 4.17.21 + readdirp: 3.6.0 + optionalDependencies: + js-beautify: 1.14.5 + dev: false + /express/4.17.3: resolution: {integrity: sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==} engines: {node: '>= 0.10.0'} @@ -6888,6 +7030,10 @@ packages: is-callable: 1.2.4 dev: true + /foreachasync/3.0.0: + resolution: {integrity: sha512-J+ler7Ta54FwwNcx6wQRDhTIbNeyDcARMkOcguEqnEdtm0jKvN3Li3PDAb2Du3ubJYEWfYL83XMROXdsXAXycw==} + dev: false + /form-data/3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} engines: {node: '>= 6'} @@ -6904,7 +7050,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /format-io/2.0.0: resolution: {integrity: sha512-iQz8w2qr4f+doWBV6LsfScHbu1gXhccByjbmA1wjBTaKRhweH2baJL96UGR4C7Fjpr8zSkK7EXiLmbzZWTyQIA==} @@ -7107,6 +7252,18 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 + /glob/8.0.3: + resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.0 + once: 1.4.0 + dev: false + optional: true + /global-modules/2.0.0: resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} engines: {node: '>=6'} @@ -7204,8 +7361,7 @@ packages: source-map: 0.6.1 wordwrap: 1.0.0 optionalDependencies: - uglify-js: 3.16.1 - dev: true + uglify-js: 3.16.3 /hard-rejection/2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} @@ -7214,10 +7370,10 @@ packages: /has-bigints/1.0.1: resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} + dev: false /has-bigints/1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true /has-duplicates/1.0.0: resolution: {integrity: sha1-iTB2HbL3Hzm6icBbCxWKtITrlxA=} @@ -7258,6 +7414,14 @@ packages: dependencies: function-bind: 1.1.1 + /hbs/4.2.0: + resolution: {integrity: sha512-dQwHnrfWlTk5PvG9+a45GYpg0VpX47ryKF8dULVd6DtwOE6TEcYQXQ5QM6nyOx/h7v3bvEQbdn19EDAcfUAgZg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + handlebars: 4.7.7 + walk: 2.3.15 + dev: false + /he/1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -7598,7 +7762,7 @@ packages: /is-bigint/1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: - has-bigints: 1.0.1 + has-bigints: 1.0.2 /is-binary-path/2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} @@ -7907,7 +8071,7 @@ packages: dev: false /isobject/3.0.1: - resolution: {integrity: sha1-TkMekrEalzFjaqH5yNHMvP2reN8=} + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} /isobject/4.0.0: @@ -7946,7 +8110,7 @@ packages: engines: {node: '>=8'} dependencies: '@babel/core': 7.17.9 - '@babel/parser': 7.18.5 + '@babel/parser': 7.18.11 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.0 semver: 6.3.0 @@ -8479,7 +8643,7 @@ packages: resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} dependencies: - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.18.6 '@jest/types': 27.5.1 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -8494,7 +8658,7 @@ packages: resolution: {integrity: sha512-xoDOOT66fLfmTRiqkoLIU7v42mal/SqwDKvfmfiWAdJMSJiU+ozgluO7KbvoAgiwIrrGZsV7viETjc8GNrA/IQ==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.18.6 '@jest/types': 28.1.1 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -8771,10 +8935,10 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: '@babel/core': 7.17.9 - '@babel/generator': 7.18.2 - '@babel/plugin-syntax-typescript': 7.16.7_@babel+core@7.17.9 - '@babel/traverse': 7.18.5 - '@babel/types': 7.18.4 + '@babel/generator': 7.18.12 + '@babel/plugin-syntax-typescript': 7.18.6_@babel+core@7.17.9 + '@babel/traverse': 7.18.11 + '@babel/types': 7.18.10 '@jest/expect-utils': 28.1.1 '@jest/transform': 28.1.2 '@jest/types': 28.1.1 @@ -8961,6 +9125,19 @@ packages: engines: {node: '>= 0.6.0'} dev: false + /js-beautify/1.14.5: + resolution: {integrity: sha512-P2BfZBhXchh10uZ87qMKpM2tfcDXLA+jDiWU/OV864yWdTGzLUGNAdp9Y1ID5ubpNVGls3cZ1UMcO8myUB+UyA==} + engines: {node: '>=10'} + hasBin: true + requiresBuild: true + dependencies: + config-chain: 1.1.13 + editorconfig: 0.15.3 + glob: 8.0.3 + nopt: 6.0.0 + dev: false + optional: true + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} dev: true @@ -9114,7 +9291,6 @@ packages: /kind-of/6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - dev: true /kleur/3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} @@ -9403,6 +9579,17 @@ packages: sourcemap-codec: 1.4.8 dev: true + /mailgun.js/7.0.4: + resolution: {integrity: sha512-D2KTfcgqB+tNtMzDjTotgrfMOGeOSi++/jPnlPxUXmPcQB1GJ21+uQvOZQtmIFbEIzzSSULfbwoMKROz92jFyA==} + dependencies: + axios: 0.27.2 + base-64: 1.0.0 + url-join: 4.0.1 + webpack-merge: 5.8.0 + transitivePeerDependencies: + - debug + dev: false + /make-dir/3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -9839,6 +10026,14 @@ packages: dependencies: brace-expansion: 1.1.11 + /minimatch/5.1.0: + resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + optional: true + /minimist-options/4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -9962,7 +10157,6 @@ packages: /neo-async/2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - dev: true /nessy/4.0.0: resolution: {integrity: sha512-XH4zOfmpxJhxXIp0Eb4vtJDtxfSjcbjY89/Rt64BNpkiBQ1mNumJWwDGq1kXWluCDQCu5LSrwABi58lWcfsWDQ==} @@ -10049,6 +10243,10 @@ packages: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: true + /node-releases/2.0.5: + resolution: {integrity: sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==} + dev: true + /node-releases/2.0.6: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} dev: true @@ -10065,6 +10263,15 @@ packages: abbrev: 1.1.1 dev: false + /nopt/6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: false + optional: true + /normalize-package-data/2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -10178,7 +10385,7 @@ packages: engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.2 - define-properties: 1.1.3 + define-properties: 1.1.4 has-symbols: 1.0.3 object-keys: 1.1.1 @@ -10340,7 +10547,7 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} dependencies: - '@babel/code-frame': 7.16.7 + '@babel/code-frame': 7.18.6 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -10637,7 +10844,6 @@ packages: /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true /pify/2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} @@ -11204,6 +11410,11 @@ packages: sisteransi: 1.0.5 dev: true + /proto-list/1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + dev: false + optional: true + /protoo-client/4.0.6: resolution: {integrity: sha512-ZqImkKHpeJhSlgvyI6QAfZNc/aXcCgmmocMx4S1w2lAaxXtckxxeDtcVNtkOISUWm/mbC+BrmYPXoGMkfhkKOQ==} engines: {node: '>=8.0.0'} @@ -11620,7 +11831,6 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 - dev: true /real-cancellable-promise/1.1.1: resolution: {integrity: sha512-vxanUX4Aff5sRX6Rb1CSeCDWhO20L0hKQXWTLOYbtRo9WYFMjlhEBX0E75iz3+7ucrmFdPpDolwLC7L65P7hag==} @@ -11967,7 +12177,7 @@ packages: dev: true /requires-port/1.0.0: - resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=} + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} /resolve-cwd/3.0.0: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} @@ -12332,7 +12542,6 @@ packages: engines: {node: '>=8'} dependencies: kind-of: 6.0.3 - dev: true /sharp/0.25.4: resolution: {integrity: sha512-umSzJJ1oBwIOfwFFt/fJ7JgCva9FvrEU2cbbm7u/3hSDZhXvkME8WE5qpaJqLIe2Har5msF5UG4CzYlEg5o3BQ==} @@ -12381,6 +12590,11 @@ packages: get-intrinsic: 1.1.1 object-inspect: 1.12.0 + /sigmund/1.0.1: + resolution: {integrity: sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g==} + dev: false + optional: true + /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -12499,7 +12713,6 @@ packages: /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - dev: true /source-map/0.7.3: resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==} @@ -12640,7 +12853,7 @@ packages: resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} dependencies: call-bind: 1.0.2 - define-properties: 1.1.3 + define-properties: 1.1.4 dev: false /string.prototype.trimend/1.0.5: @@ -12655,7 +12868,7 @@ packages: resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} dependencies: call-bind: 1.0.2 - define-properties: 1.1.3 + define-properties: 1.1.4 dev: false /string.prototype.trimstart/1.0.5: @@ -13146,8 +13359,8 @@ packages: supports-hyperlinks: 2.2.0 dev: true - /terser-webpack-plugin/5.3.4_webpack@5.74.0: - resolution: {integrity: sha512-SmnkUhBxLDcBfTIeaq+ZqJXLVEyXxSaNcCeSezECdKjfkMrTTnPvapBILylYwyEvHFZAn2cJ8dtiXel5XnfOfQ==} + /terser-webpack-plugin/5.3.6_webpack@5.74.0: + resolution: {integrity: sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -13176,7 +13389,7 @@ packages: hasBin: true dependencies: '@jridgewell/source-map': 0.3.2 - acorn: 8.8.0 + acorn: 8.7.0 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -13198,7 +13411,7 @@ packages: lodash: 4.17.21 potpack: 1.0.2 three: 0.141.0 - uuid: 3.3.2 + uuid: 3.4.0 dev: false /three/0.141.0: @@ -13602,12 +13815,11 @@ packages: hasBin: true dev: true - /uglify-js/3.16.1: - resolution: {integrity: sha512-X5BGTIDH8U6IQ1TIRP62YC36k+ULAa1d59BxlWvPUJ1NkW5L3FwcGfEzuVvGmhJFBu0YJ5Ge25tmRISqCmLiRQ==} + /uglify-js/3.16.3: + resolution: {integrity: sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw==} engines: {node: '>=0.8.0'} hasBin: true requiresBuild: true - dev: true optional: true /uid2/0.0.4: @@ -13780,6 +13992,10 @@ packages: dependencies: punycode: 2.1.1 + /url-join/4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + dev: false + /url-parse/1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} dependencies: @@ -13825,8 +14041,8 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - /uuid/3.3.2: - resolution: {integrity: sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==} + /uuid/3.4.0: + resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. hasBin: true dev: false @@ -13942,6 +14158,12 @@ packages: xml-name-validator: 3.0.0 dev: true + /walk/2.3.15: + resolution: {integrity: sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg==} + dependencies: + foreachasync: 3.0.0 + dev: false + /walker/1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -13994,7 +14216,7 @@ packages: engines: {node: '>=10.4'} dev: true - /webpack-cli/4.10.0_r5zkyi3k4cwcnn76n4wwxu6434: + /webpack-cli/4.10.0_6k4gs4iynuwn6fxh2o2g4kzpru: resolution: {integrity: sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==} engines: {node: '>=10.13.0'} hasBin: true @@ -14017,7 +14239,7 @@ packages: '@discoveryjs/json-ext': 0.5.7 '@webpack-cli/configtest': 1.2.0_5v66e2inugklgvlh4huuavolfq '@webpack-cli/info': 1.5.0_webpack-cli@4.10.0 - '@webpack-cli/serve': 1.7.0_7r45by5oyo7ronrzgeqf7j6vxq + '@webpack-cli/serve': 1.7.0_eib23wgkspxiotey5yppdrjtku colorette: 2.0.19 commander: 7.2.0 cross-spawn: 7.0.3 @@ -14026,7 +14248,7 @@ packages: interpret: 2.2.0 rechoir: 0.7.1 webpack: 5.74.0_webpack-cli@4.10.0 - webpack-dev-server: 4.10.0_uptvlxzxtuqbyzxasnpdw7lg2a + webpack-dev-server: 4.10.1_uptvlxzxtuqbyzxasnpdw7lg2a webpack-merge: 5.8.0 dev: true @@ -14044,8 +14266,8 @@ packages: webpack: 5.74.0_webpack-cli@4.10.0 dev: true - /webpack-dev-server/4.10.0_uptvlxzxtuqbyzxasnpdw7lg2a: - resolution: {integrity: sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ==} + /webpack-dev-server/4.10.1_uptvlxzxtuqbyzxasnpdw7lg2a: + resolution: {integrity: sha512-FIzMq3jbBarz3ld9l7rbM7m6Rj1lOsgq/DyLGMX/fPEB1UBUPtf5iL/4eNfhx8YYJTRlzfv107UfWSWcBK5Odw==} engines: {node: '>= 12.13.0'} hasBin: true peerDependencies: @@ -14083,7 +14305,7 @@ packages: sockjs: 0.3.24 spdy: 4.0.2 webpack: 5.74.0_webpack-cli@4.10.0 - webpack-cli: 4.10.0_r5zkyi3k4cwcnn76n4wwxu6434 + webpack-cli: 4.10.0_6k4gs4iynuwn6fxh2o2g4kzpru webpack-dev-middleware: 5.3.3_webpack@5.74.0 ws: 8.5.0 transitivePeerDependencies: @@ -14099,7 +14321,6 @@ packages: dependencies: clone-deep: 4.0.1 wildcard: 2.0.0 - dev: true /webpack-sources/3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} @@ -14123,7 +14344,7 @@ packages: '@webassemblyjs/wasm-parser': 1.11.1 acorn: 8.8.0 acorn-import-assertions: 1.8.0_acorn@8.8.0 - browserslist: 4.21.3 + browserslist: 4.20.4 chrome-trace-event: 1.0.3 enhanced-resolve: 5.10.0 es-module-lexer: 0.9.3 @@ -14137,9 +14358,9 @@ packages: neo-async: 2.6.2 schema-utils: 3.1.1 tapable: 2.2.1 - terser-webpack-plugin: 5.3.4_webpack@5.74.0 + terser-webpack-plugin: 5.3.6_webpack@5.74.0 watchpack: 2.4.0 - webpack-cli: 4.10.0_r5zkyi3k4cwcnn76n4wwxu6434 + webpack-cli: 4.10.0_6k4gs4iynuwn6fxh2o2g4kzpru webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' @@ -14276,7 +14497,6 @@ packages: /wildcard/2.0.0: resolution: {integrity: sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==} - dev: true /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} @@ -14285,7 +14505,6 @@ packages: /wordwrap/1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - dev: true /wrap-ansi/7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} diff --git a/server/.env.example b/server/.env.example index d2dc461d..3869cbd8 100644 --- a/server/.env.example +++ b/server/.env.example @@ -5,6 +5,12 @@ YPERSISTENCE=data/yjs DATABASE_NAME=relm DATABASE_HOST=/run/postgresql +SERVER_BASE_URL=http://localhost:3000 + +MAILGUN_FROM= +MAILGUN_DOMAIN= +MAILGUN_KEY= + # TWILIO_ACCOUNT_SID= # TWILIO_API_KEY= # TWILIO_API_SECRET= diff --git a/server/data/public/email/email_confirmation-button.png b/server/data/public/email/email_confirmation-button.png new file mode 100644 index 00000000..ba840231 Binary files /dev/null and b/server/data/public/email/email_confirmation-button.png differ diff --git a/server/data/public/email/email_confirmation-envelope.png b/server/data/public/email/email_confirmation-envelope.png new file mode 100644 index 00000000..ee3681bf Binary files /dev/null and b/server/data/public/email/email_confirmation-envelope.png differ diff --git a/server/data/public/email/email_confirmation-header.png b/server/data/public/email/email_confirmation-header.png new file mode 100644 index 00000000..18f64398 Binary files /dev/null and b/server/data/public/email/email_confirmation-header.png differ diff --git a/server/data/public/email_confirmation/email_confirmation-background.png b/server/data/public/email_confirmation/email_confirmation-background.png new file mode 100644 index 00000000..39f5b68e Binary files /dev/null and b/server/data/public/email_confirmation/email_confirmation-background.png differ diff --git a/server/data/public/email_confirmation/email_confirmation-envelope_white.png b/server/data/public/email_confirmation/email_confirmation-envelope_white.png new file mode 100644 index 00000000..f1edf333 Binary files /dev/null and b/server/data/public/email_confirmation/email_confirmation-envelope_white.png differ diff --git a/server/data/public/favicon.png b/server/data/public/favicon.png new file mode 100644 index 00000000..41943d45 Binary files /dev/null and b/server/data/public/favicon.png differ diff --git a/server/data/templates/emails/INFORMATION.md b/server/data/templates/emails/INFORMATION.md new file mode 100644 index 00000000..12bf9e93 --- /dev/null +++ b/server/data/templates/emails/INFORMATION.md @@ -0,0 +1,37 @@ +# Email Templates + +Each email template can be referenced through the utility `createEmailTemplate` and the email can be sent via `sendEmail`. However, setup is required for Relm to read each template. + +All emails will try and use their HTML template first, but if the email provider does not support it, the text version of the email will be used instead. + +## File Structure +For each unique email template, the file tree should look like this +``` +/templates + /emails + /id_to_call_email_template + email.html + email.json + ... +``` + +```html + + + + + Content to send in the email if the email service supports HTML + + +``` + +```json +// email.json +{ + "subject": "Subject of the email to send", + "content": "Content to send in the email if the email service does NOT support HTML" + // Alternatively, content can be a JSON array for convenience for multilines. +} +``` + +Additionally, variables can be specified within these templates via `{{VARIABLE_NAME}}` and these get replaced when `createEmailTemplate` is called. \ No newline at end of file diff --git a/server/data/templates/emails/verify/email.html b/server/data/templates/emails/verify/email.html new file mode 100644 index 00000000..06edbe5e --- /dev/null +++ b/server/data/templates/emails/verify/email.html @@ -0,0 +1,129 @@ + + + + + + + Relm | Confirm Email Address + + + + + + + + + + + +
+
+
+ + + + + + + +
+ Envelope +
+ +
+ + {{GREETING}} +

+ So glad you're here :) +

+ You're one click away from confirming your account with us. This account will allow you to sign in across devices to access the places and items you've collected during your visits. +

+ + + + + + +
+
+ + Confirm Account + +
+
+


+ Looking forward to seeing you at future events! +

+ Relm Team + +
+
+ + \ No newline at end of file diff --git a/server/data/templates/emails/verify/email.json b/server/data/templates/emails/verify/email.json new file mode 100644 index 00000000..3e9ed70a --- /dev/null +++ b/server/data/templates/emails/verify/email.json @@ -0,0 +1,14 @@ +{ + "subject": "Welcome to Relm!", + "content": [ + "Hi Ashley!", + "So glad you're here :)", + "", + "You're one click away from confirming your account with us. This account will allow you to sign in across devices to access the places and items you've collected during your visits.", + "Verify your account here: {{SERVER_URL}}/email/verify/{{CODE}}", + "", + "Looking forward to seeing you at future events!", + "", + "Relm Team" + ] +} \ No newline at end of file diff --git a/server/data/templates/views/verifyAccount.hbs b/server/data/templates/views/verifyAccount.hbs new file mode 100644 index 00000000..5764b2b1 --- /dev/null +++ b/server/data/templates/views/verifyAccount.hbs @@ -0,0 +1,58 @@ + + + + + + + Relm | Account Verified + + + + + +
+
+ {{#if confirmed}} + Your email +
+ has been confirmed +
+
+ Email Confirmed +
+ {{else}} + Invalid or +
+ expired code + {{/if}} +
+
+ + \ No newline at end of file diff --git a/server/docs/postman_collection.json b/server/docs/postman_collection.json index 27c3ec97..dfd14dc0 100644 --- a/server/docs/postman_collection.json +++ b/server/docs/postman_collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "cae134f4-d572-45c8-94a9-a109a23e61b0", + "_postman_id": "70b2d48e-8867-43db-a04b-3782871997d3", "name": "Relm Server", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, @@ -15,7 +15,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"relms\": []\n}", + "raw": "{\n \"relms\": [\"default\", \"test\"]\n}", "options": { "raw": { "language": "json" @@ -584,7 +584,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"relmName\": \"\"\n}", + "raw": "{\n \"relmName\": \"myrelm\"\n}", "options": { "raw": { "language": "json" @@ -749,7 +749,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"relmName\": \"\"\n}", + "raw": "{\n \"relmName\": \"myrelm\"\n}", "options": { "raw": { "language": "json" @@ -776,7 +776,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"relmName\": \"\",\n \"publicPermits\": [],\n \"clonePermitRequired\": \"read\",\n \"clonePermitAssigned\": \"edit\"\n}", + "raw": "{\n \"relmName\": \"myrelm\",\n \"publicPermits\": [],\n \"clonePermitRequired\": \"read\",\n \"clonePermitAssigned\": \"edit\"\n}", "options": { "raw": { "language": "json" diff --git a/server/migrations/017-email-verified.sql b/server/migrations/017-email-verified.sql new file mode 100644 index 00000000..8d523b0a --- /dev/null +++ b/server/migrations/017-email-verified.sql @@ -0,0 +1,6 @@ +CREATE TABLE pending_email_verifications ( + user_id UUID NOT NULL, + code TEXT NOT NULL, + FOREIGN KEY(user_id) REFERENCES users(user_id), + UNIQUE(code) +); \ No newline at end of file diff --git a/server/migrations/018-jwt-support.sql b/server/migrations/018-jwt-support.sql new file mode 100644 index 00000000..aa28c306 --- /dev/null +++ b/server/migrations/018-jwt-support.sql @@ -0,0 +1,5 @@ +ALTER TABLE users +ADD COLUMN is_jwt BOOLEAN DEFAULT false; + +ALTER TABLE users +RENAME COLUMN email TO login_id; \ No newline at end of file diff --git a/server/package.json b/server/package.json index 431d3637..656a1db0 100644 --- a/server/package.json +++ b/server/package.json @@ -34,12 +34,16 @@ "dotenv": "^10.0.0", "express": "^4.17.1", "express-fileupload": "^1.1.10", + "express-hbs": "^2.4.0", "fastestsmallesttextencoderdecoder": "^1.0.22", + "form-data": "^4.0.0", + "hbs": "^4.2.0", "http-errors": "^1.8.0", "is": "^3.3.0", "level": "^6.0.1", "lib0": "0.2.51", "lodash.debounce": "^4.0.8", + "mailgun.js": "^7.0.4", "md5-file": "^5.0.0", "minimist": "^1.2.6", "moment": "^2.27.0", diff --git a/server/src/config.ts b/server/src/config.ts index 231ca30e..ec0c6fb0 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -46,6 +46,12 @@ const FACEBOOK_CLIENT_SECRET = process.env.FACEBOOK_CLIENT_SECRET; const TWITTER_CLIENT_ID = process.env.TWITTER_CLIENT_ID; const TWITTER_CLIENT_SECRET = process.env.TWITTER_CLIENT_SECRET; +const SERVER_BASE_URL = (process.env.SERVER_BASE_URL || `http://localhost:${PORT}`).replace(/\/$/, ""); + +const MAILGUN_FROM = process.env.MAILGUN_FROM; +const MAILGUN_DOMAIN = process.env.MAILGUN_DOMAIN; +const MAILGUN_KEY = process.env.MAILGUN_KEY; + const SPACES_KEY = process.env.SPACES_KEY; const SPACES_SECRET = process.env.SPACES_SECRET; const SPACES_BUCKET = process.env.SPACES_BUCKET; @@ -149,5 +155,9 @@ export { FACEBOOK_CLIENT_ID, FACEBOOK_CLIENT_SECRET, TWITTER_CLIENT_ID, - TWITTER_CLIENT_SECRET + TWITTER_CLIENT_SECRET, + SERVER_BASE_URL, + MAILGUN_FROM, + MAILGUN_DOMAIN, + MAILGUN_KEY }; diff --git a/server/src/db/permission.test.ts b/server/src/db/permission.test.ts index 18647697..c28da144 100644 --- a/server/src/db/permission.test.ts +++ b/server/src/db/permission.test.ts @@ -182,7 +182,7 @@ describe("Permission model tests", () => { permits: ["admin"], }); - const userId = await User.createUser({ + const { userId } = await User.createUser({ email: "example-permissions@example.com", password: "example", }); diff --git a/server/src/db/socialConnection.test.ts b/server/src/db/socialConnection.test.ts index 64b6557c..52f7ed9e 100644 --- a/server/src/db/socialConnection.test.ts +++ b/server/src/db/socialConnection.test.ts @@ -11,10 +11,10 @@ describe("Social connection model tests", () => { // All tests require this user created. let userId; beforeAll(async () => { - userId = await User.createUser({ + userId = (await User.createUser({ email: "example-social@example.com", password: "example" - }); + })).userId; }); it("Registers a social connection", async () => { diff --git a/server/src/db/user.test.ts b/server/src/db/user.test.ts index c43d9807..c2054d95 100644 --- a/server/src/db/user.test.ts +++ b/server/src/db/user.test.ts @@ -16,7 +16,7 @@ describe("User model tests", () => { it("creates a user", async () => { await User.createUser(defaultUserData); - const userId = await User.getUserIdByEmail({ + const userId = await User.getUserIdByLoginId({ email: defaultUserData.email }); @@ -44,8 +44,8 @@ describe("User model tests", () => { }); it("signs in successfully with the correct credentials", async () => { - const successfullyLoggedIn = await User.verifyCredentials(defaultUserData); - const shouldBeFailedLogin = await User.verifyCredentials({ + const successfullyLoggedIn = await User.verifyEmailPassword(defaultUserData); + const shouldBeFailedLogin = await User.verifyEmailPassword({ email: "example@example.com", password: "" }); diff --git a/server/src/db/user.ts b/server/src/db/user.ts index ed8ece23..9e8a6413 100644 --- a/server/src/db/user.ts +++ b/server/src/db/user.ts @@ -1,35 +1,69 @@ import { SavedIdentityData } from "relm-common"; import { compareEncryptedPassword, encrypt } from "../utils/encryption.js"; import { db, sql } from "./db.js"; +import { nanoid } from "nanoid"; import { INSERT } from "./pgSqlHelpers.js"; type UserCreationData = { email : string, - password? : string + password? : string, + emailVerificationRequired?: boolean +} | { + jwtId : string }; -export async function createUser({ email, password }: UserCreationData) { - const userData: any = { email }; - if (password) { - userData.password_hash = await encrypt(password); +type UserCreationResult = { + userId: string, + emailCode?: string +}; + +export async function createUser(data: UserCreationData): Promise { + const isJWT = "jwtId" in data; + + const userData: any = { + login_id: isJWT ? data.jwtId : data.email, + is_jwt: isJWT + }; + if (!isJWT && data.password) { + userData["password_hash"] = await encrypt(data.password); } - const data = await db.one(sql` + const insertData = await db.one(sql` ${INSERT("users", userData)} RETURNING user_id `); - return data.user_id; + + const { user_id : userId } = insertData; + + if (!isJWT && data.emailVerificationRequired) { + const emailCode = nanoid(); + + await db.none(sql`INSERT INTO pending_email_verifications (user_id, code) VALUES (${userId}, ${emailCode}) ON CONFLICT DO NOTHING`); + + return { + userId, + emailCode + }; + } + + return { + userId + }; } -export async function deleteUserByEmail({ email }) { - await db.none(sql` - DELETE FROM users WHERE LOWER(email)=LOWER(${email}) - `); +export async function deleteUserByLoginId(data : { email: string } | { jwtId: string }) { + const userId = await this.getUserIdByLoginId(data); + + await db.none(sql`DELETE FROM pending_email_verifications WHERE user_id=${userId}`); + await db.none(sql`DELETE FROM users WHERE user_id=${userId}`); } -export async function getUserIdByEmail({ email } : { email : string }) { +export async function getUserIdByLoginId(data : { email: string } | { jwtId: string }) { + const isJWT = "jwtId" in data; + const loginId = isJWT ? data.jwtId : data.email; + const row = await db.oneOrNone(sql` - SELECT user_id FROM users WHERE LOWER(email)=LOWER(${email}) + SELECT user_id FROM users WHERE LOWER(login_id)=LOWER(${loginId}) AND is_jwt=${isJWT} `); if (!row) { @@ -38,9 +72,9 @@ export async function getUserIdByEmail({ email } : { email : string }) { return row.user_id; } -export async function verifyCredentials({ email, password }) { +export async function verifyEmailPassword({ email, password }) { const data = await db.oneOrNone(sql` - SELECT password_hash FROM users WHERE LOWER(email)=LOWER(${email}) + SELECT password_hash FROM users WHERE LOWER(login_id)=LOWER(${email}) AND is_jwt=false `); if (data === null || data.password_hash === null) { // user doesn't exist or no password is assigned with user. @@ -69,4 +103,10 @@ export async function getIdentityData({ userId }): Promise { } return data.identity_data; +} + +// Returns whether or not the user's email was marked as complete ONLY IF a pending verification is available. +export async function markAsCompletedEmailVerification({ code }) { + const { length: rows } = await db.query(sql`DELETE FROM pending_email_verifications WHERE code=${code} RETURNING *`); + return rows > 0; } \ No newline at end of file diff --git a/server/src/middleware.ts b/server/src/middleware.ts index a97880bc..07db6293 100644 --- a/server/src/middleware.ts +++ b/server/src/middleware.ts @@ -8,7 +8,7 @@ import { unabbreviatedPermits, } from "./utils/index.js"; import { AuthenticationHeaders, canonicalIdentifier } from "relm-common"; -import { Participant, Permission, Relm, useToken } from "./db/index.js"; +import { Participant, Permission, Relm, User, useToken } from "./db/index.js"; import { JWTSECRET } from "./config.js"; export function relmName(key = "relmName") { @@ -109,7 +109,7 @@ export function acceptToken() { export function acceptJwt() { return async (req, _res, next) => { if (JWTSECRET && req.headers["x-relm-jwt"]) { - const result: { relms: Record } = decodedValidJwt( + const result: { username: string, relms: Record } = decodedValidJwt( req.headers["x-relm-jwt"], JWTSECRET ); @@ -126,6 +126,25 @@ export function acceptJwt() { }); } + if (result.username) { + let userId = await User.getUserIdByLoginId({ jwtId: result.username }); + if (userId !== null) { + req.authenticatedUserId = userId; + } else { + // If the jwt user has no relm user, create one + + userId = (await User.createUser({ + jwtId: result.username + })).userId; + } + + req.authenticatedUserId = userId; + await Participant.assignToUserId({ + userId, + participantId: req.authenticatedParticipantId + }); + } + // Success next(); } else { diff --git a/server/src/passportAuth.ts b/server/src/passportAuth.ts index ffca63e8..a2a9729f 100644 --- a/server/src/passportAuth.ts +++ b/server/src/passportAuth.ts @@ -36,13 +36,13 @@ passport.use(new PassportLocalStrategy({ passwordField: "password", }, wrapPassportSync(async function(email, password, done) { // Check if user credentials are valid. - const validCredentials = await User.verifyCredentials({ email, password }); + const validCredentials = await User.verifyEmailPassword({ email, password }); if (!validCredentials) { return done(null, false); } // Get user id and pass it along. - const userId = await User.getUserIdByEmail({ email }); + const userId = await User.getUserIdByLoginId({ email }); if (userId === null) { return done({ isError: true, @@ -90,11 +90,11 @@ async function handleOAuthPassport(socialId, req, email, profileId, done) { // The participant is not linked to any user. // The social we are trying to use is not linked to any account either. // Try searching for an user id using the email, otherwise create an account. - const existingEmailUserId = await User.getUserIdByEmail({ email }); + const existingEmailUserId = await User.getUserIdByLoginId({ email }); if (existingEmailUserId !== null) { connectedSocialUserId = existingEmailUserId; } else { - connectedSocialUserId = await User.createUser({ email }); + connectedSocialUserId = (await User.createUser({ email, emailVerificationRequired: false })).userId; } } diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index d6044842..c769b0da 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -18,8 +18,11 @@ import { PassportResponse, respondWithErrorPostMessage, respondWithSuccessPostMessage, - respondWithFailurePostMessage + respondWithFailurePostMessage, + createEmailTemplate, + sendEmail } from "../utils/index.js"; +import { SERVER_BASE_URL } from "../config.js"; export const auth = express.Router(); @@ -61,6 +64,7 @@ auth.get( "/identity", cors(), middleware.authenticated(), + middleware.acceptJwt(), wrapAsync(async (req, res) => { const userId = await Participant.getUserId({ participantId: req.authenticatedParticipantId @@ -86,6 +90,7 @@ auth.post( "/identity", cors(), middleware.authenticated(), + middleware.acceptJwt(), middleware.authenticatedWithUser(), wrapAsync(async (req, res) => { const userId = req.authenticatedUserId; @@ -140,7 +145,7 @@ auth.post( cors(), middleware.authenticated(), wrapAsync(async (req, res) => { - const { email, password } = req.body; + const { email, password, identity } = req.body; // Check that the email is valid if (!isValidEmailFormat(email)) { @@ -149,9 +154,12 @@ auth.post( if (!isValidPasswordFormat(password)) { return respondWithFailure(res, "invalid_password", "Your password needs to be at least 8 characters long!"); } + if (!isValidIdentity(identity)) { + return respondWithError(res, "Missing identity in payload."); + } // Check if someone is using that email - const userExistsWithEmailProvided = (await User.getUserIdByEmail({ email })) !== null; + const userExistsWithEmailProvided = (await User.getUserIdByLoginId({ email })) !== null; if (userExistsWithEmailProvided) { return respondWithFailure(res, "invalid_credentials", "This email is already used by another user!"); } @@ -163,16 +171,34 @@ auth.post( return respondWithFailure(res, "participant_already_linked", "This participant is already linked to another email!"); } - const userId = await User.createUser({ + const { userId, emailCode } = await User.createUser({ email, - password + password, + emailVerificationRequired: true }); - await Participant.assignToUserId({ participantId, userId }); + const name = (identity as SavedIdentityData).name; + const verifyEmailDetails = createEmailTemplate("verify", { + code: emailCode, + greeting: name ? `Hi ${name}!` : `Hi there!`, + server_url: SERVER_BASE_URL + }); + try { + await sendEmail(email, verifyEmailDetails); + } catch (error) { + // delete account as email confirmation email could not be sent. + await User.deleteUserByLoginId({ email }); + return respondWithError(res, "Email service failed to process request.", error); + } + + await Participant.assignToUserId({ participantId, userId }); + await User.setIdentityData({ userId, identity }); return respondWithSuccess(res, {}); }) ); +sendEmail("dapersonmgn@gmail.com", createEmailTemplate("verify", { code: "asd", greeting: "Hi William", server_url: "asd" })).then(console.log); + auth.post( "/connect/local/signin", cors(), diff --git a/server/src/routes/email.ts b/server/src/routes/email.ts new file mode 100644 index 00000000..34888d04 --- /dev/null +++ b/server/src/routes/email.ts @@ -0,0 +1,20 @@ +import express from "express"; +import cors from "cors"; +import { User } from "../db/index.js"; +import { wrapAsync } from "../utils/index.js"; + +export const email = express.Router(); + +email.get( + "/verify/:code", + cors(), + wrapAsync(async (req, res) => { + const code = req.params.code; + + const confirmed = await User.markAsCompletedEmailVerification({ code }); + + res.render("verifyAccount", { + confirmed + }); + }) +); \ No newline at end of file diff --git a/server/src/routes/index.ts b/server/src/routes/index.ts index cee1f14d..3efef7e7 100644 --- a/server/src/routes/index.ts +++ b/server/src/routes/index.ts @@ -1,6 +1,7 @@ export { admin } from "./admin.js"; export { asset } from "./asset.js"; export { auth } from "./auth.js"; +export { email } from "./email.js"; export { invite } from "./invite.js"; export { relm } from "./relm.js"; export { relms } from "./relms.js"; \ No newline at end of file diff --git a/server/src/server_http.ts b/server/src/server_http.ts index 69a9383b..7eb18f4c 100644 --- a/server/src/server_http.ts +++ b/server/src/server_http.ts @@ -1,9 +1,10 @@ import express from "express"; import cors from "cors"; +import path from "path"; import * as middleware from "./middleware.js"; import * as routes from "./routes/index.js"; import passportMiddleware from "./passportAuth.js"; -import { respondWithError, uuidv4 } from "./utils/index.js"; +import { getRootPath, respondWithError, uuidv4 } from "./utils/index.js"; export const app = express(); @@ -14,6 +15,14 @@ app.use(express.json()); // See https://expressjs.com/en/resources/middleware/cors.html#enabling-cors-pre-flight app.options("*", cors()); +// Set the view engine to the module hbs +app.set("view engine", "hbs"); +app.set("views", path.join(getRootPath(), "data", "templates", "views")); + +// Static assets +app.use("/public", express.static(path.join(getRootPath(), "data", "public"))); + +// Passport for OAuthauthentication app.use(passportMiddleware); // Courtesy page just to say we're a Relm web server @@ -24,6 +33,7 @@ app.get("/", function (_req, res) { app.use("/admin", routes.admin); app.use("/asset", routes.asset); app.use("/auth", routes.auth); +app.use("/email", routes.email); app.use("/invite", middleware.relmName(), routes.invite); app.use("/relm", middleware.relmName(), routes.relm); app.use("/relms", routes.relms); diff --git a/server/src/utils/getRootPath.ts b/server/src/utils/getRootPath.ts new file mode 100644 index 00000000..aba46c27 --- /dev/null +++ b/server/src/utils/getRootPath.ts @@ -0,0 +1,8 @@ +import url from "url"; +import path from "path"; + +export function getRootPath() { + const __filename = url.fileURLToPath(import.meta.url); + const __dirname = path.dirname(__filename); + return path.join(__dirname, "..", ".."); +}; \ No newline at end of file diff --git a/server/src/utils/index.ts b/server/src/utils/index.ts index 9208c6b6..f8bc46d5 100644 --- a/server/src/utils/index.ts +++ b/server/src/utils/index.ts @@ -5,6 +5,7 @@ export { export { decodedValidJwt } from "./decodedValidJwt.js"; export { encrypt, compareEncryptedPassword } from "./encryption.js"; export { getDefinedKeys } from "./getDefinedKeys.js"; +export { getRootPath } from "./getRootPath.js"; export { hasPermission } from "./hasPermission.js"; export { isAllowed } from "./isAllowed.js"; export { isArray } from "./isArray.js"; @@ -12,6 +13,7 @@ export { isValidEmailFormat } from "./isValidEmailFormat.js"; export { isValidIdentity } from "./isValidIdentity.js"; export { isValidPasswordFormat } from "./isValidPasswordFormat.js"; export { joinError } from "./joinError.js"; +export { getMailClient, sendEmail, createEmailTemplate } from "./mail.js"; export { nullOr } from "./nullOr.js"; export { randomToken } from "./randomToken.js"; export { required, req } from "./required.js"; diff --git a/server/src/utils/mail.ts b/server/src/utils/mail.ts new file mode 100644 index 00000000..c8af2f57 --- /dev/null +++ b/server/src/utils/mail.ts @@ -0,0 +1,99 @@ +import Mailgun from "mailgun.js"; +import type Client from "mailgun.js/client.js"; +import * as path from "path"; +import * as fs from "fs"; +import { MAILGUN_DOMAIN, MAILGUN_FROM, MAILGUN_KEY } from "../config.js"; +import { getRootPath } from "./index.js"; + +const EMAIL_TEMPLATE_FOLDER_PATH = path.join(getRootPath(), "data", "templates", "emails"); + +// Gather all email templates +const templates: { [template: string]: EmailDetails } = {}; +const emailTemplateFolders = fs.readdirSync(EMAIL_TEMPLATE_FOLDER_PATH) + .filter(file => file !== "INFORMATION.md"); +for (const folderName of emailTemplateFolders) { + const htmlFilePath = path.join(EMAIL_TEMPLATE_FOLDER_PATH, folderName, "email.html"); + const jsonFilePath = path.join(EMAIL_TEMPLATE_FOLDER_PATH, folderName, "email.json"); + + let html = null; + if (fs.existsSync(htmlFilePath)) { + html = fs.readFileSync(htmlFilePath, "utf-8"); + } + + if (!fs.existsSync(jsonFilePath)) { + throw Error(`Invalid email configuration for "${folderName}" (Missing email.json!)`); + } + const { subject, content } = JSON.parse(fs.readFileSync(jsonFilePath, "utf-8")); + if (!subject || !content) { + throw Error(`Invalid email configuration for "${folderName}" (Missing property "subject" or "content")`); + } + + templates[folderName] = { + subject, + content: typeof content === "string" ? content : content.join("\n"), + html + }; +} + +export type EmailDetails = { + subject: string; + content: string; + html?: string; +} + +export function createEmailTemplate(templateName: string, params = {}): EmailDetails { + const emailDetails = templates[templateName]; + if (!emailDetails) { + throw Error(`Email template ${templateName} doesn't exist!`); + } + + return { + subject: applyParams(emailDetails.subject, params), + content: applyParams(emailDetails.content, params), + html: applyParams(emailDetails.html, params) + }; +} + +/** + * Given a string e.g. "{{variable}} text," this function can be used to + * replace instances of {{variable}} with something else given params being { variable: "replace it with this!" } + * @param content the content to replace text in + * @param params the parameters to replace and their values + * @returns content with params applied + */ +function applyParams(content: string, params = {}): string { + let str = content; + + for (const key in params) { + str = str.replace(new RegExp(`{{${key}}}`, "gi"), params[key]); + } + + return str; +} + +let cachedClient: Client; +export async function getMailClient() { + if (!cachedClient) { + const { default: formData } = await import("form-data"); + const mailgunApi = new Mailgun(formData); + + cachedClient = mailgunApi.client({ + username: "api", + key: MAILGUN_KEY + }); + } + + return cachedClient; +} + +export async function sendEmail(recipients: string|string[], details: EmailDetails) { + const client = await getMailClient(); + + return client.messages.create(MAILGUN_DOMAIN, { + from: MAILGUN_FROM, + to: recipients, + subject: details.subject, + text: details.content, + html: details.html + }); +} \ No newline at end of file