├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── babel.config.js ├── jsconfig.json ├── login.png ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── google-logo.png └── icons │ ├── favicon-128x128.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ └── favicon-96x96.png ├── quasar.conf.js └── src ├── App.vue ├── assets └── quasar-logo-vertical.svg ├── boot ├── .gitkeep └── firebase.js ├── components ├── AuthComponent.vue ├── EssentialLink.vue └── ForgotPassword.vue ├── css ├── app.scss └── quasar.variables.scss ├── helpers └── firebaseConfig.js ├── index.template.html ├── layouts ├── AuthLayout.vue └── MainLayout.vue ├── pages ├── Auth.vue ├── Error404.vue └── Index.vue ├── router ├── index.js └── routes.js ├── services └── auth.js └── store ├── index.js ├── module-example ├── actions.js ├── getters.js ├── index.js ├── mutations.js └── state.js └── store-flag.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src-bex/www 3 | /src-capacitor 4 | /src-cordova 5 | /.quasar 6 | /node_modules 7 | .eslintrc.js 8 | babel.config.js 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 3 | // This option interrupts the configuration hierarchy at this file 4 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 5 | root: true, 6 | 7 | parserOptions: { 8 | parser: '@babel/eslint-parser', 9 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 10 | sourceType: 'module' // Allows for the use of imports 11 | }, 12 | 13 | env: { 14 | browser: true 15 | }, 16 | 17 | // Rules order is important, please avoid shuffling them 18 | extends: [ 19 | // Base ESLint recommended rules 20 | // 'eslint:recommended', 21 | 22 | 23 | // Uncomment any of the lines below to choose desired strictness, 24 | // but leave only one uncommented! 25 | // See https://eslint.vuejs.org/rules/#available-rules 26 | 'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention) 27 | // 'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 28 | // 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 29 | 30 | // https://github.com/prettier/eslint-config-prettier#installation 31 | // usage with Prettier, provided by 'eslint-config-prettier'. 32 | 'prettier' 33 | ], 34 | 35 | plugins: [ 36 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file 37 | // required to lint *.vue files 38 | 'vue', 39 | 40 | // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 41 | // Prettier has not been included as plugin to avoid performance impact 42 | // add it as an extension for your IDE 43 | ], 44 | 45 | globals: { 46 | ga: 'readonly', // Google Analytics 47 | cordova: 'readonly', 48 | __statics: 'readonly', 49 | __QUASAR_SSR__: 'readonly', 50 | __QUASAR_SSR_SERVER__: 'readonly', 51 | __QUASAR_SSR_CLIENT__: 'readonly', 52 | __QUASAR_SSR_PWA__: 'readonly', 53 | process: 'readonly', 54 | Capacitor: 'readonly', 55 | chrome: 'readonly' 56 | }, 57 | 58 | // add your custom rules here 59 | rules: { 60 | 'prefer-promise-reject-errors': 'off', 61 | 62 | 63 | // allow debugger during development only 64 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | 5 | # Quasar core related directories 6 | .quasar 7 | /dist 8 | .idea 9 | 10 | .cf_api_token 11 | 12 | # Cordova related directories and files 13 | /src-cordova/node_modules 14 | /src-cordova/platforms 15 | /src-cordova/plugins 16 | /src-cordova/www 17 | 18 | # Capacitor related directories and files 19 | /src-capacitor/www 20 | /src-capacitor/node_modules 21 | 22 | # BEX related directories and files 23 | /src-bex/www 24 | /src-bex/js/core 25 | 26 | # Log files 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # Editor directories and files 32 | .idea 33 | *.suo 34 | *.ntvs* 35 | *.njsproj 36 | *.sln 37 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "octref.vetur" 6 | ], 7 | "unwantedRecommendations": [ 8 | "hookyqr.beautify", 9 | "dbaeumer.jshint", 10 | "ms-vscode.vscode-typescript-tslint-plugin" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vetur.validation.template": false, 3 | "vetur.format.enable": false, 4 | "eslint.validate": ["javascript", "javascriptreact", "typescript", "vue"], 5 | 6 | "vetur.experimental.templateInterpolationService": true 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quasar2 / Vue3 / Firebase Authentication (web9) 2 | 3 | This project is a minimal working quasar project that has all the needed pages and functionality to handle signup, login, forgot-password and verify email, with Google or email/password. 4 | Everything else is mostly left untouched as Quasar vanilla project. 5 | 6 | ![login image](./login.png) 7 | 8 | ## Getting started 9 | This project was tested with node v14.17.4 10 | To get started: 11 | 1. Fork the repo 12 | 2. Install all dependencies 13 | ```bash 14 | npm install 15 | ``` 16 | 3. Create Firebase projct - see next section 17 | 4. Start the app 18 | ```bash 19 | npm run serve 20 | ``` 21 | ## Creating Firebase project 22 | 1. Go to [Firebase Console](https://console.firebase.google.com/) 23 | 2. Click _Add Project_ Give it a name and click _Continue_. 24 | 3. Disable Google Analytics click _Continue_. 25 | 4. After the project was created click _Continue_. It will take you to the project overview. Now we need to create an app. 26 | 5. Click the _web_ icon, give your app a name and click _Register App_. 27 | 6. You’ll be taken to _Step 2 - Add Firebase SDK_. 28 | 7. Copy `firebaseConfig` json that looks like this: 29 | ```javascript 30 | const firebaseConfig = { 31 | apiKey: "xxxxxxxxxxx", 32 | authDomain: "xxxxxxxxxx", 33 | projectId: "xxxxxxxxxx", 34 | storageBucket: "xxxxxxxx", 35 | messagingSenderId: "xxxxxxxxx", 36 | appId: "xxxxxxx" 37 | }; 38 | ``` 39 | 8. Go to `src/boot/firebase.js` and replace the existing config with your own 40 | 9. Back in Firebase Console, click _Continue to Console_. 41 | 10. On the left panel click _Authentication_, then click _Sign-in method_. 42 | 11. Enable _Email/Password_. Do not enable _Email link_. 43 | 12. Before enabling Google, let's verify our domain so Google can send emails on our behalf. Click the _Templates_ tab. 44 | 13. On one of the email templates, click the pencil icon next to the _From_ address. 45 | 14. Click _Customise domain_ link under the domain name. 46 | 15. Follow the instructions on configuring your DNS and complete the verification. 47 | 16. Once done, we can go back to adding Google as login method. Click _Sign-in method_ tab again. 48 | 17. Enable _Google_. You'll have to provide your app's support-email. Also make sure that your _Project public facing name_ appears as you want your customers to see it. It will appear on Google's authentication dialog. 49 | 50 | ## Server authentication 51 | 52 | To authenticate user on your server, get idToken and send it as `Authorization` header. I usually prefix it with the string `IDTOKEN.` so on the server I know what type of token was sent and how I should verify it. Of course you don't have to do that, and if you only expect this type of token you can skip the prefix. Here's client side example: 53 | ```javascript 54 | import { getIdToken } from '../services/auth' 55 | // ... 56 | const idToken = await getIdToken(); 57 | const res = await fetch (url, { 58 | method, 59 | headers: { 60 | 'Authorization': `IDTOKEN.${idToken}` 61 | }, 62 | body 63 | }) 64 | ``` 65 | 66 | ### Validating on the Server 67 | Depending on your environment, here's an example how to verify the token manually. You can also use Firestore SDK as explained [here](https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_the_firebase_admin_sdk) 68 | The returned parsed idToken will have `uid` property which you can use as unique identifier in your server code. 69 | 70 | ```javascript 71 | import jwt from 'jsonwebtoken'; 72 | 73 | const auth = request.headers.authorization; 74 | if (auth && auth.startsWith('IDTOKEN.')) { 75 | const idToken = await validateIdToken(auth.substr('IDTOKEN.'.length)); 76 | if (idToken.uid) { 77 | // verification succeeded. Use uid as unique identifier for that user 78 | } 79 | } 80 | 81 | export async function validateIdToken (idToken) { 82 | async function getKey(header){ 83 | const res = await fetch('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com') 84 | const keys = await res.json(); 85 | return keys[header.kid]; 86 | } 87 | const tokenHeader = jwt.decode(idToken, {complete: true}).header; 88 | // check that tokenHeader.iss === 'https://securetoken.google.com/' 89 | // check that tokenHeader.aud === '' 90 | let key = await getKey(tokenHeader); 91 | key = key.trim(); 92 | try { 93 | return jwt.verify(idToken, key); 94 | } catch (err) { 95 | console.log("ERROR!", err); 96 | return {}; 97 | } 98 | } 99 | ``` 100 | 101 | ## Contribution 102 | 103 | Contributions are welcome. Just create a PR and explain what you've done :) 104 | 105 | ## Todo (in no particular order) 106 | 1. Email verification 107 | 2. Change password flow - make "choose new password" part of this webapp. Currently it's under myapp.firebaseio subdomain. 108 | 3. Passwordless login (email link) 109 | 4. ~~Send token to server for authorization~~ 110 | 5. Anonymous signin - allow users to try before signing up, and then continue with their work after signup - https://firebase.google.com/docs/auth/web/anonymous-auth 111 | 112 | 113 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = api => { 4 | return { 5 | presets: [ 6 | [ 7 | '@quasar/babel-preset-app', 8 | api.caller(caller => caller && caller.target === 'node') 9 | ? { targets: { node: 'current' } } 10 | : {} 11 | ] 12 | ] 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "src/*": [ 6 | "src/*" 7 | ], 8 | "app/*": [ 9 | "*" 10 | ], 11 | "components/*": [ 12 | "src/components/*" 13 | ], 14 | "layouts/*": [ 15 | "src/layouts/*" 16 | ], 17 | "pages/*": [ 18 | "src/pages/*" 19 | ], 20 | "assets/*": [ 21 | "src/assets/*" 22 | ], 23 | "boot/*": [ 24 | "src/boot/*" 25 | ], 26 | "vue$": [ 27 | "node_modules/vue/dist/vue.runtime.esm-bundler.js" 28 | ] 29 | } 30 | }, 31 | "exclude": [ 32 | "dist", 33 | ".quasar", 34 | "node_modules" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamule-io/quasar2-vue3-firebase-auth/b7fcb2d5a686d125d07f71615a198a4adfe5b5f5/login.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "datamule-app", 3 | "version": "0.0.1", 4 | "description": "Datamule web application", 5 | "productName": "Datamule", 6 | "author": "Dan Bar-Shalom ", 7 | "private": true, 8 | "scripts": { 9 | "lint": "eslint --ext .js,.vue ./", 10 | "test": "echo \"No test specified\" && exit 0", 11 | "serve": "quasar dev -m spa", 12 | "build": "quasar build -m spa" 13 | }, 14 | "dependencies": { 15 | "@quasar/extras": "^1.0.0", 16 | "core-js": "^3.6.5", 17 | "firebase": "^9.0.2", 18 | "quasar": "^2.0.0", 19 | "vuex": "^4.0.1" 20 | }, 21 | "devDependencies": { 22 | "@babel/eslint-parser": "^7.13.14", 23 | "@quasar/app": "^3.0.0", 24 | "eslint": "^7.14.0", 25 | "eslint-config-prettier": "^8.1.0", 26 | "eslint-plugin-vue": "^7.0.0", 27 | "eslint-webpack-plugin": "^2.4.0" 28 | }, 29 | "browserslist": [ 30 | "last 10 Chrome versions", 31 | "last 10 Firefox versions", 32 | "last 4 Edge versions", 33 | "last 7 Safari versions", 34 | "last 8 Android versions", 35 | "last 8 ChromeAndroid versions", 36 | "last 8 FirefoxAndroid versions", 37 | "last 10 iOS versions", 38 | "last 5 Opera versions" 39 | ], 40 | "engines": { 41 | "node": ">= 12.22.1", 42 | "npm": ">= 6.13.4", 43 | "yarn": ">= 1.21.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamule-io/quasar2-vue3-firebase-auth/b7fcb2d5a686d125d07f71615a198a4adfe5b5f5/public/favicon.ico -------------------------------------------------------------------------------- /public/google-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamule-io/quasar2-vue3-firebase-auth/b7fcb2d5a686d125d07f71615a198a4adfe5b5f5/public/google-logo.png -------------------------------------------------------------------------------- /public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamule-io/quasar2-vue3-firebase-auth/b7fcb2d5a686d125d07f71615a198a4adfe5b5f5/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamule-io/quasar2-vue3-firebase-auth/b7fcb2d5a686d125d07f71615a198a4adfe5b5f5/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamule-io/quasar2-vue3-firebase-auth/b7fcb2d5a686d125d07f71615a198a4adfe5b5f5/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamule-io/quasar2-vue3-firebase-auth/b7fcb2d5a686d125d07f71615a198a4adfe5b5f5/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /quasar.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file runs in a Node context (it's NOT transpiled by Babel), so use only 3 | * the ES6 features that are supported by your Node version. https://node.green/ 4 | */ 5 | 6 | // Configuration for your app 7 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js 8 | 9 | /* eslint-env node */ 10 | const ESLintPlugin = require('eslint-webpack-plugin') 11 | const { configure } = require('quasar/wrappers'); 12 | 13 | module.exports = configure(function (ctx) { 14 | return { 15 | // https://v2.quasar.dev/quasar-cli/supporting-ts 16 | supportTS: false, 17 | 18 | // https://v2.quasar.dev/quasar-cli/prefetch-feature 19 | // preFetch: true, 20 | 21 | // app boot file (/src/boot) 22 | // --> boot files are part of "main.js" 23 | // https://v2.quasar.dev/quasar-cli/boot-files 24 | boot: [ 25 | 'firebase' 26 | ], 27 | 28 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css 29 | css: [ 30 | 'app.scss' 31 | ], 32 | 33 | // https://github.com/quasarframework/quasar/tree/dev/extras 34 | extras: [ 35 | // 'ionicons-v4', 36 | // 'mdi-v5', 37 | 'fontawesome-v5', 38 | // 'eva-icons', 39 | // 'themify', 40 | // 'line-awesome', 41 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both! 42 | 43 | 'roboto-font', // optional, you are not bound to it 44 | 'material-icons', // optional, you are not bound to it 45 | ], 46 | 47 | // Full list of options: https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-build 48 | build: { 49 | vueRouterMode: 'history', // available values: 'hash', 'history' 50 | 51 | // transpile: false, 52 | 53 | // Add dependencies for transpiling with Babel (Array of string/regex) 54 | // (from node_modules, which are by default not transpiled). 55 | // Applies only if "transpile" is set to true. 56 | // transpileDependencies: [], 57 | 58 | // rtl: true, // https://v2.quasar.dev/options/rtl-support 59 | // preloadChunks: true, 60 | // showProgress: false, 61 | // gzip: true, 62 | // analyze: true, 63 | 64 | // Options below are automatically set depending on the env, set them if you want to override 65 | // extractCSS: false, 66 | 67 | // https://v2.quasar.dev/quasar-cli/handling-webpack 68 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 69 | chainWebpack (chain) { 70 | chain.plugin('eslint-webpack-plugin') 71 | .use(ESLintPlugin, [{ extensions: [ 'js', 'vue' ] }]) 72 | }, 73 | }, 74 | 75 | // Full list of options: https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer 76 | devServer: { 77 | https: false, 78 | port: 8080, 79 | open: true // opens browser window automatically 80 | }, 81 | 82 | // https://v2.quasar.dev/quasar-cli/quasar-conf-js#Property%3A-framework 83 | framework: { 84 | config: {}, 85 | 86 | // iconSet: 'material-icons', // Quasar icon set 87 | // lang: 'en-US', // Quasar language pack 88 | 89 | // For special cases outside of where the auto-import strategy can have an impact 90 | // (like functional components as one of the examples), 91 | // you can manually specify Quasar components/directives to be available everywhere: 92 | // 93 | // components: [], 94 | // directives: [], 95 | 96 | // Quasar plugins 97 | plugins: [ 'Notify' ] 98 | }, 99 | 100 | // animations: 'all', // --- includes all animations 101 | // https://v2.quasar.dev/options/animations 102 | animations: [], 103 | 104 | // https://v2.quasar.dev/quasar-cli/developing-ssr/configuring-ssr 105 | ssr: { 106 | pwa: false, 107 | 108 | // manualStoreHydration: true, 109 | // manualPostHydrationTrigger: true, 110 | 111 | prodPort: 3000, // The default port that the production server should use 112 | // (gets superseded if process.env.PORT is specified at runtime) 113 | 114 | maxAge: 1000 * 60 * 60 * 24 * 30, 115 | // Tell browser when a file from the server should expire from cache (in ms) 116 | 117 | chainWebpackWebserver (chain) { 118 | chain.plugin('eslint-webpack-plugin') 119 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 120 | }, 121 | 122 | middlewares: [ 123 | ctx.prod ? 'compression' : '', 124 | 'render' // keep this as last one 125 | ] 126 | }, 127 | 128 | // https://v2.quasar.dev/quasar-cli/developing-pwa/configuring-pwa 129 | pwa: { 130 | workboxPluginMode: 'GenerateSW', // 'GenerateSW' or 'InjectManifest' 131 | workboxOptions: {}, // only for GenerateSW 132 | 133 | // for the custom service worker ONLY (/src-pwa/custom-service-worker.[js|ts]) 134 | // if using workbox in InjectManifest mode 135 | chainWebpackCustomSW (chain) { 136 | chain.plugin('eslint-webpack-plugin') 137 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 138 | }, 139 | 140 | manifest: { 141 | name: `Datamule`, 142 | short_name: `Datamule`, 143 | description: `Datamule web application`, 144 | display: 'standalone', 145 | orientation: 'portrait', 146 | background_color: '#ffffff', 147 | theme_color: '#027be3', 148 | icons: [ 149 | { 150 | src: 'icons/icon-128x128.png', 151 | sizes: '128x128', 152 | type: 'image/png' 153 | }, 154 | { 155 | src: 'icons/icon-192x192.png', 156 | sizes: '192x192', 157 | type: 'image/png' 158 | }, 159 | { 160 | src: 'icons/icon-256x256.png', 161 | sizes: '256x256', 162 | type: 'image/png' 163 | }, 164 | { 165 | src: 'icons/icon-384x384.png', 166 | sizes: '384x384', 167 | type: 'image/png' 168 | }, 169 | { 170 | src: 'icons/icon-512x512.png', 171 | sizes: '512x512', 172 | type: 'image/png' 173 | } 174 | ] 175 | } 176 | }, 177 | 178 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-cordova-apps/configuring-cordova 179 | cordova: { 180 | // noIosLegacyBuildFlag: true, // uncomment only if you know what you are doing 181 | }, 182 | 183 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-capacitor-apps/configuring-capacitor 184 | capacitor: { 185 | hideSplashscreen: true 186 | }, 187 | 188 | // Full list of options: https://v2.quasar.dev/quasar-cli/developing-electron-apps/configuring-electron 189 | electron: { 190 | bundler: 'packager', // 'packager' or 'builder' 191 | 192 | packager: { 193 | // https://github.com/electron-userland/electron-packager/blob/master/docs/api.md#options 194 | 195 | // OS X / Mac App Store 196 | // appBundleId: '', 197 | // appCategoryType: '', 198 | // osxSign: '', 199 | // protocol: 'myapp://path', 200 | 201 | // Windows only 202 | // win32metadata: { ... } 203 | }, 204 | 205 | builder: { 206 | // https://www.electron.build/configuration/configuration 207 | 208 | appId: 'datamule-app' 209 | }, 210 | 211 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 212 | chainWebpackMain (chain) { 213 | chain.plugin('eslint-webpack-plugin') 214 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 215 | }, 216 | 217 | // "chain" is a webpack-chain object https://github.com/neutrinojs/webpack-chain 218 | chainWebpackPreload (chain) { 219 | chain.plugin('eslint-webpack-plugin') 220 | .use(ESLintPlugin, [{ extensions: [ 'js' ] }]) 221 | }, 222 | } 223 | } 224 | }); 225 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 11 | -------------------------------------------------------------------------------- /src/assets/quasar-logo-vertical.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /src/boot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datamule-io/quasar2-vue3-firebase-auth/b7fcb2d5a686d125d07f71615a198a4adfe5b5f5/src/boot/.gitkeep -------------------------------------------------------------------------------- /src/boot/firebase.js: -------------------------------------------------------------------------------- 1 | import { boot } from 'quasar/wrappers' 2 | // import firebase from 'firebase' 3 | import { initializeApp, getCurrentUser } from 'firebase/app'; 4 | import { getAuth } from "firebase/auth"; 5 | 6 | const firebaseConfig = { 7 | apiKey: "AIzaSyCs4mrNWBdZntj8N1UKPqI5hWcfjbQ5bAQ", 8 | authDomain: "datamule-io.firebaseapp.com", 9 | projectId: "datamule-io", 10 | storageBucket: "datamule-io.appspot.com", 11 | messagingSenderId: "518806260193", 12 | appId: "1:518806260193:web:ad8e32a1300705bac9eaa0", 13 | measurementId: "G-3EC2BNZL7E" 14 | }; 15 | 16 | // "async" is optional; 17 | // more info on params: https://v2.quasar.dev/quasar-cli/boot-files 18 | export default boot(async ({ router, app }) => { 19 | initializeApp(firebaseConfig); 20 | const auth = getAuth(); 21 | router.beforeEach((to, from, next) => { 22 | return new Promise((resolve, reject) => { 23 | const unsubscribe = auth.onAuthStateChanged(function (user) { 24 | unsubscribe(); 25 | if (!user && to.path != "/auth/login") { 26 | next("/auth/login"); 27 | } else if (user) { 28 | // if (!user.emailVerified && to.path != "/auth/verifyEmail" && to.path != "/auth/completeAccount") { 29 | // next("/auth/verifyEmail"); 30 | // } else 31 | if (to.path == '/auth/login' || to.path == "/auth/verifyEmail" || to.path == "/auth/completeAccount") { 32 | next("/"); 33 | } else { 34 | next() 35 | } 36 | } else { 37 | next(); 38 | } 39 | resolve(user); 40 | }, reject) 41 | }); 42 | }) 43 | }) 44 | 45 | -------------------------------------------------------------------------------- /src/components/AuthComponent.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 143 | -------------------------------------------------------------------------------- /src/components/EssentialLink.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 52 | -------------------------------------------------------------------------------- /src/components/ForgotPassword.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 65 | -------------------------------------------------------------------------------- /src/css/app.scss: -------------------------------------------------------------------------------- 1 | // app global css in SCSS form 2 | -------------------------------------------------------------------------------- /src/css/quasar.variables.scss: -------------------------------------------------------------------------------- 1 | // Quasar SCSS (& Sass) Variables 2 | // -------------------------------------------------- 3 | // To customize the look and feel of this app, you can override 4 | // the Sass/SCSS variables found in Quasar's source Sass/SCSS files. 5 | 6 | // Check documentation for full list of Quasar variables 7 | 8 | // Your own variables (that are declared here) and Quasar's own 9 | // ones will be available out of the box in your .vue/.scss/.sass files 10 | 11 | // It's highly recommended to change the default colors 12 | // to match your app's branding. 13 | // Tip: Use the "Theme Builder" on Quasar's documentation website. 14 | 15 | $primary : #1976D2; 16 | $secondary : #26A69A; 17 | $accent : #9C27B0; 18 | 19 | $dark : #1D1D1D; 20 | 21 | $positive : #21BA45; 22 | $negative : #C10015; 23 | $info : #31CCEC; 24 | $warning : #F2C037; 25 | -------------------------------------------------------------------------------- /src/helpers/firebaseConfig.js: -------------------------------------------------------------------------------- 1 | export const config = { 2 | apiKey: "AIzaSyCs4mrNWBdZntj8N1UKPqI5hWcfjbQ5bAQ", 3 | authDomain: "datamule-io.firebaseapp.com", 4 | projectId: "datamule-io", 5 | storageBucket: "datamule-io.appspot.com", 6 | messagingSenderId: "518806260193", 7 | appId: "1:518806260193:web:ad8e32a1300705bac9eaa0", 8 | measurementId: "G-3EC2BNZL7E" 9 | }; 10 | -------------------------------------------------------------------------------- /src/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= productName %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /src/layouts/AuthLayout.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 24 | -------------------------------------------------------------------------------- /src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 161 | -------------------------------------------------------------------------------- /src/pages/Auth.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 50 | -------------------------------------------------------------------------------- /src/pages/Error404.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 32 | -------------------------------------------------------------------------------- /src/pages/Index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import { route } from 'quasar/wrappers' 2 | import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router' 3 | import routes from './routes' 4 | 5 | /* 6 | * If not building with SSR mode, you can 7 | * directly export the Router instantiation; 8 | * 9 | * The function below can be async too; either use 10 | * async/await or return a Promise which resolves 11 | * with the Router instance. 12 | */ 13 | 14 | // const router = route(function (/* { store, ssrContext } */) { 15 | const createHistory = process.env.SERVER 16 | ? createMemoryHistory 17 | : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) 18 | 19 | const Router = createRouter({ 20 | scrollBehavior: () => ({ left: 0, top: 0 }), 21 | routes, 22 | 23 | // Leave this as is and make changes in quasar.conf.js instead! 24 | // quasar.conf.js -> build -> vueRouterMode 25 | // quasar.conf.js -> build -> publicPath 26 | history: createHistory(process.env.MODE === 'ssr' ? void 0 : process.env.VUE_ROUTER_BASE) 27 | }) 28 | 29 | // return Router 30 | // }) 31 | 32 | export default Router; 33 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | 2 | const routes = [ 3 | { 4 | path: '/', 5 | component: () => import('layouts/MainLayout.vue'), 6 | children: [ 7 | { path: '', component: () => import('pages/Index.vue'), meta: {requiresAuth: true} }, 8 | ] 9 | }, 10 | { 11 | path: '/auth', 12 | component: () => import('layouts/AuthLayout.vue'), 13 | children: [ 14 | { path: '/auth/login', component: () => import('pages/Auth.vue') } 15 | ] 16 | }, 17 | // { 18 | // path: '/login', 19 | // component: () => import('pages/auth/login') 20 | // }, 21 | // { 22 | // path: '/success', 23 | // component: () => import('pages/auth/success'), 24 | // meta: { 25 | // requiresAuth: true 26 | // } 27 | // }, 28 | // { 29 | // path: '/verifyEmail', 30 | // component: () => import('pages/auth/verifyEmail'), 31 | // meta: { 32 | // requiresAuth: true 33 | // } 34 | // }, 35 | // { 36 | // path: '/completeAccount', 37 | // component: () => import('pages/auth/completeAccount'), 38 | // meta: { 39 | // requiresAuth: true 40 | // } 41 | // }, 42 | // Always leave this as last one, 43 | // but you can also remove it 44 | { 45 | path: '/:catchAll(.*)*', 46 | component: () => import('pages/Error404.vue') 47 | } 48 | ] 49 | 50 | export default routes 51 | -------------------------------------------------------------------------------- /src/services/auth.js: -------------------------------------------------------------------------------- 1 | import { getAuth, onAuthStateChanged } from 'firebase/auth' 2 | 3 | /** 4 | * Returns a promise that resolves with an ID token, if available, that can be verified on a server. 5 | * @return {!Promise} The promise that resolves with an ID token if 6 | * available. Otherwise, the promise resolves with null. 7 | */ 8 | export const getIdToken = () => { 9 | return new Promise((resolve, reject) => { 10 | const auth = getAuth(); 11 | const unsubscribe = onAuthStateChanged( auth, (user) => { 12 | unsubscribe() // unsubscribe the observer that we've just created 13 | if (user) { 14 | user.getIdToken().then((idToken) => { 15 | resolve(idToken) 16 | }, () => { 17 | resolve(null) 18 | }) 19 | } else { 20 | resolve(null) 21 | } 22 | }) 23 | }).catch((error) => { 24 | console.log(error) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { store } from 'quasar/wrappers' 2 | import { createStore } from 'vuex' 3 | 4 | // import example from './module-example' 5 | 6 | /* 7 | * If not building with SSR mode, you can 8 | * directly export the Store instantiation; 9 | * 10 | * The function below can be async too; either use 11 | * async/await or return a Promise which resolves 12 | * with the Store instance. 13 | */ 14 | 15 | export default store(function (/* { ssrContext } */) { 16 | const Store = createStore({ 17 | modules: { 18 | // example 19 | }, 20 | 21 | // enable strict mode (adds overhead!) 22 | // for dev mode and --debug builds only 23 | strict: process.env.DEBUGGING 24 | }) 25 | 26 | return Store 27 | }) 28 | -------------------------------------------------------------------------------- /src/store/module-example/actions.js: -------------------------------------------------------------------------------- 1 | export function someAction (/* context */) { 2 | } 3 | -------------------------------------------------------------------------------- /src/store/module-example/getters.js: -------------------------------------------------------------------------------- 1 | export function someGetter (/* state */) { 2 | } 3 | -------------------------------------------------------------------------------- /src/store/module-example/index.js: -------------------------------------------------------------------------------- 1 | import state from './state' 2 | import * as getters from './getters' 3 | import * as mutations from './mutations' 4 | import * as actions from './actions' 5 | 6 | export default { 7 | namespaced: true, 8 | getters, 9 | mutations, 10 | actions, 11 | state 12 | } 13 | -------------------------------------------------------------------------------- /src/store/module-example/mutations.js: -------------------------------------------------------------------------------- 1 | export function someMutation (/* state */) { 2 | } 3 | -------------------------------------------------------------------------------- /src/store/module-example/state.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return { 3 | // 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/store/store-flag.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 3 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 4 | import "quasar/dist/types/feature-flag"; 5 | 6 | declare module "quasar/dist/types/feature-flag" { 7 | interface QuasarFeatureFlags { 8 | store: true; 9 | } 10 | } 11 | --------------------------------------------------------------------------------