├── .gitignore ├── README.md ├── babel.config.js ├── firestoreRules.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── App.vue ├── assets ├── logo.png ├── logo.svg └── main.scss ├── components └── Landing.vue ├── firebase └── index.js ├── main.js ├── plugins └── vuetify.js ├── routes └── index.js ├── store └── store.js └── views ├── About.vue ├── Home.vue ├── Profile.vue ├── SignIn.vue ├── SignOut.vue └── SignUp.vue /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-auth-bolierplate 2 | 3 | 4 | ## Firebase / Firestore Setup 5 | go to 6 | ``` 7 | http://firebase.coogle.com 8 | ``` 9 | 1. Under the Authentication tab - select email/password and click the activate switch. 10 | - create a user and set a field in that user 11 | ``` 12 | Name: isAdmin 13 | Type: boolean 14 | Value: true 15 | ``` 16 | This will be your admin user. You can add more admin users if you like. 17 | 18 | 2. Under the database tab - Create a Cloud Firestore database 19 | 3. Under Storage tab - enable storage. 20 | 4. At the top of the main menu find "Project Settings" & click on the gear to the right. At the bottom of the next screen in "Your Apps" select "Web App" and replace the code in src/firebase/index.js with the setting shown. 21 | 22 | You should then be good to go. 23 | 24 | ## Project setup 25 | ``` 26 | npm install 27 | ``` 28 | 29 | ### Compiles and hot-reloads for development 30 | ``` 31 | npm run serve 32 | ``` 33 | 34 | ### Compiles and minifies for production 35 | ``` 36 | npm run build 37 | ``` 38 | 39 | ### Run your tests 40 | ``` 41 | npm run test 42 | ``` 43 | 44 | ### Lints and fixes files 45 | ``` 46 | npm run lint 47 | ``` 48 | 49 | ### Customize configuration 50 | See [Configuration Reference](https://cli.vuejs.org/config/). 51 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /firestoreRules.md: -------------------------------------------------------------------------------- 1 | Is admin 2 | QWBhjkJqE6VHpZTNSdYdm1AmJXF3 3 | 4 | IS normal 5 | mULPAmagVnN00HhtW8G0M6qMrYY2 6 | 7 | 8 | 9 | 10 | match /user/{document} { 11 | allow read: if getRole('subscriber') == true; 12 | allow update: if getRole('editor') == true; 13 | allow create, delete: if getRole('admin') == true; 14 | } 15 | // Allow the user to read data if the document has the 'visibility' 16 | // field set to 'public' 17 | match /cities/{city} { 18 | allow read: if resource.data.visibility == 'public'; 19 | } 20 | 21 | 22 | 23 | 24 | 25 | match /user/{document} { 26 | allow read: if isAdmin('admin') == true; 27 | allow update: if getRole('editor') == true; 28 | allow create, delete: if getRole('admin') == true; 29 | } 30 | 31 | // Check if current user is admin 32 | function isAdmin(isAdmin) { 33 | return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin[isAdmin] 34 | // check with --> if isAdmin('admin') == true; 35 | } 36 | 37 | // Check if current user is level 38 | function getLevel(userLevel) { 39 | return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.userLevel[userLevel] 40 | // check with --> 41 | // if getLevel('free') == true; 42 | // if getLevel('personal') == true; 43 | // if getLevel('business') == true; 44 | // if getLevel('godLike') == true; 45 | } 46 | 47 | // check if user owns profile 48 | 49 | 50 | // Check is user is creator of document 51 | function isOwner(createdBy) { 52 | return get(/databases/$(database)/documents/dashboard/{document}).data.createdBy[$(request.auth.uid)] 53 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-auth-bolierplate", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@firebase/firestore": "^0.9.3", 12 | "axios": "^0.18.0", 13 | "firebase": "^5.7.3", 14 | "node-sass": "^4.11.0", 15 | "sass-loader": "^7.1.0", 16 | "vue": "^2.5.21", 17 | "vue-router": "^3.0.1", 18 | "vuetify": "^1.4.1", 19 | "vuex": "^3.0.1", 20 | "vuex-router-sync": "^5.0.0" 21 | }, 22 | "devDependencies": { 23 | "@vue/cli-plugin-babel": "^3.3.0", 24 | "@vue/cli-plugin-eslint": "^3.3.0", 25 | "@vue/cli-service": "^3.3.0", 26 | "babel-eslint": "^10.0.1", 27 | "eslint": "^5.8.0", 28 | "eslint-plugin-vue": "^5.0.0", 29 | "stylus": "^0.54.5", 30 | "stylus-loader": "^3.0.1", 31 | "vue-cli-plugin-vuetify": "^0.4.6", 32 | "vue-template-compiler": "^2.5.21", 33 | "vuetify-loader": "^1.0.5" 34 | }, 35 | "eslintConfig": { 36 | "root": true, 37 | "env": { 38 | "node": true 39 | }, 40 | "extends": [ 41 | "plugin:vue/essential", 42 | "eslint:recommended" 43 | ], 44 | "rules": {}, 45 | "parserOptions": { 46 | "parser": "babel-eslint" 47 | } 48 | }, 49 | "postcss": { 50 | "plugins": { 51 | "autoprefixer": {} 52 | } 53 | }, 54 | "browserslist": [ 55 | "> 1%", 56 | "last 2 versions", 57 | "not ie <= 8" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiaranPearse/Vie-firebase-firestore-auth-storage-database/3983b0c22cc824f19320a427716de94091d9d0e0/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-auth-bolierplate 9 | 10 | 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 58 | 59 | 64 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CiaranPearse/Vie-firebase-firestore-auth-storage-database/3983b0c22cc824f19320a427716de94091d9d0e0/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /src/assets/main.scss: -------------------------------------------------------------------------------- 1 | #app { 2 | .fingerprint-spinner, .fingerprint-spinner * { 3 | box-sizing: border-box; 4 | } 5 | .fingerprint-spinner { 6 | height: 64px; 7 | width: 64px; 8 | padding: 2px; 9 | overflow: hidden; 10 | position: relative; 11 | margin: 0 auto; 12 | .spinner-ring { 13 | position: absolute; 14 | border-radius: 50%; 15 | border: 2px solid transparent; 16 | border-top-color: #ff1d5e; 17 | animation: fingerprint-spinner-animation 1500ms cubic-bezier(0.680, -0.750, 0.265, 1.750) infinite forwards; 18 | margin: auto; 19 | bottom: 0; 20 | left: 0; 21 | right: 0; 22 | top: 0; 23 | &:nth-child(1) { 24 | height: calc(60px / 9 + 0 * 60px / 9); 25 | width: calc(60px / 9 + 0 * 60px / 9); 26 | animation-delay: calc(50ms * 1); 27 | } 28 | &:nth-child(2) { 29 | height: calc(60px / 9 + 1 * 60px / 9); 30 | width: calc(60px / 9 + 1 * 60px / 9); 31 | animation-delay: calc(50ms * 2); 32 | } 33 | &:nth-child(3) { 34 | height: calc(60px / 9 + 2 * 60px / 9); 35 | width: calc(60px / 9 + 2 * 60px / 9); 36 | animation-delay: calc(50ms * 3); 37 | } 38 | &:nth-child(4) { 39 | height: calc(60px / 9 + 3 * 60px / 9); 40 | width: calc(60px / 9 + 3 * 60px / 9); 41 | animation-delay: calc(50ms * 4); 42 | } 43 | &:nth-child(5) { 44 | height: calc(60px / 9 + 4 * 60px / 9); 45 | width: calc(60px / 9 + 4 * 60px / 9); 46 | animation-delay: calc(50ms * 5); 47 | } 48 | &:nth-child(6) { 49 | height: calc(60px / 9 + 5 * 60px / 9); 50 | width: calc(60px / 9 + 5 * 60px / 9); 51 | animation-delay: calc(50ms * 6); 52 | } 53 | &:nth-child(7) { 54 | height: calc(60px / 9 + 6 * 60px / 9); 55 | width: calc(60px / 9 + 6 * 60px / 9); 56 | animation-delay: calc(50ms * 7); 57 | } 58 | &:nth-child(8) { 59 | height: calc(60px / 9 + 7 * 60px / 9); 60 | width: calc(60px / 9 + 7 * 60px / 9); 61 | animation-delay: calc(50ms * 8); 62 | } 63 | &:nth-child(9) { 64 | height: calc(60px / 9 + 8 * 60px / 9); 65 | width: calc(60px / 9 + 8 * 60px / 9); 66 | animation-delay: calc(50ms * 9); 67 | } 68 | } 69 | } 70 | @keyframes fingerprint-spinner-animation { 71 | 100% { 72 | transform: rotate( 360deg ); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/components/Landing.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /src/firebase/index.js: -------------------------------------------------------------------------------- 1 | import store from '@/store/store' 2 | import Firebase from 'firebase' 3 | import 'firebase/firestore' 4 | 5 | const config = { 6 | apiKey: 'YOUR_FIREBASE_API_KAY', 7 | authDomain: 'something.firebaseapp.com', 8 | databaseURL: 'https://something.firebaseio.com', 9 | projectId: 'something', 10 | storageBucket: 'something.appspot.com', 11 | messagingSenderId: 'SOME_NUMBER' 12 | } 13 | 14 | export default { 15 | install: (Vue, options) => { 16 | const firebase = Firebase.initializeApp(config) 17 | const auth = firebase.auth() 18 | Vue.prototype.$auth = { 19 | login: async (username, pass) => { 20 | return await auth.signInWithEmailAndPassword(username, pass) 21 | }, 22 | logout: async () => { 23 | await auth.signOut() 24 | } 25 | } 26 | auth.onAuthStateChanged(user => { 27 | store.commit('updateUser',{ user }) 28 | store.dispatch('fetchUserData') 29 | }) 30 | } 31 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import './plugins/vuetify' 3 | import App from './App.vue' 4 | import { sync } from 'vuex-router-sync' 5 | import Vuetify from 'vuetify' 6 | import router from './routes' 7 | import store from './store/store' 8 | import FirebaseAuthPlugin from './firebase/' 9 | 10 | Vue.config.productionTip = false 11 | Vue.use(FirebaseAuthPlugin) 12 | Vue.use(Vuetify) 13 | 14 | sync(store, router) 15 | 16 | new Vue({ 17 | router, 18 | store, 19 | render: h => h(App) 20 | }).$mount('#app') 21 | 22 | // const app = new Vue({ 23 | // router, 24 | // store, 25 | // ...App 26 | // }) 27 | 28 | export { router, store } 29 | -------------------------------------------------------------------------------- /src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuetify from 'vuetify/lib' 3 | import 'vuetify/src/stylus/app.styl' 4 | 5 | Vue.use(Vuetify, { 6 | iconfont: 'md', 7 | }) 8 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import store from '@/store/store' 4 | import Home from '@/views/Home' 5 | import SignIn from '@/views/SignIn' 6 | import SignOut from '@/views/SignOut' 7 | import SignUp from '@/views/SignUp' 8 | import Profile from '@/views/Profile' 9 | 10 | Vue.use(Router) 11 | 12 | const router = new Router({ 13 | mode: 'history', 14 | routes: [ 15 | { 16 | path: '/', 17 | name: 'home', 18 | component: Home 19 | }, 20 | { 21 | path: '/signin', 22 | name: 'signin', 23 | component: SignIn 24 | }, 25 | { 26 | path: '/signout', 27 | name: 'signout', 28 | component: SignOut 29 | }, 30 | { 31 | path: '/signup', 32 | name: 'signup', 33 | component: SignUp 34 | }, 35 | { 36 | path: '/profile', 37 | name: 'profile', 38 | component: Profile, 39 | meta: { 40 | authRequired: true 41 | } 42 | } 43 | ] 44 | }) 45 | 46 | router.beforeEach((to, from, next) => { 47 | if (to.matched.some(record => record.meta.authRequired)) { 48 | if (!store.state.user) { 49 | next({ 50 | path: '/signin', 51 | query: { redirect: to.fullPath } 52 | }) 53 | } else { 54 | next() 55 | } 56 | } else { 57 | next() 58 | } 59 | }) 60 | 61 | export default router 62 | -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import firebase from 'firebase' 4 | import 'firebase/firestore'; 5 | import router from '@/routes' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state: { 11 | user: null, 12 | userProfile: null 13 | }, 14 | mutations: { 15 | updateUser (state, { user }) { 16 | Vue.set(state, 'user', user) 17 | }, 18 | setUser (state, payload) { 19 | state.user = payload 20 | }, 21 | setProfile (state, payload) { 22 | state.userProfile = payload 23 | }, 24 | setLoading (state, payload) { 25 | state.loading = payload 26 | }, 27 | setError (state, payload) { 28 | state.error = payload 29 | }, 30 | clearError (state) { 31 | state.error = null 32 | } 33 | }, 34 | getters: { 35 | user (state) { 36 | return state.user 37 | }, 38 | userProfile (state) { 39 | return state.userProfile 40 | } 41 | }, 42 | computed: { 43 | }, 44 | actions: { 45 | signUserUp ({commit}, payload) { 46 | firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password) 47 | .then( 48 | user => { 49 | const newUser = { 50 | id: user.user.uid 51 | } 52 | commit('setUser', newUser) 53 | } 54 | ) 55 | .then( 56 | // eslint-disable-next-line 57 | user => { 58 | const dateNow = new Date() 59 | firebase.firestore().collection("users").doc(this.state.user.id).set({ 60 | userLevel: 'free', 61 | joinDate: dateNow.toISOString(), 62 | isAdmin: false 63 | }) 64 | } 65 | ) 66 | .then( 67 | router.push('/profile') 68 | ) 69 | .catch(function(error) { 70 | commit('setError', error) 71 | }) 72 | }, 73 | signUserOut ({commit}) { 74 | commit('setProfile', null) 75 | }, 76 | fetchUserData ({commit, getters}) { 77 | let currentUser = this.state.user.uid 78 | commit('setLoading', true) 79 | var docRef = firebase.firestore().collection('/users').doc(currentUser) 80 | docRef.get().then(function(doc) { 81 | if (doc.exists) { 82 | commit('setProfile', doc.data()) 83 | } else { 84 | this.state.usersProfile = null 85 | } 86 | }) 87 | .catch( 88 | error => { 89 | commit('setLoading', false) 90 | commit('setError', error) 91 | } 92 | ) 93 | }, 94 | updateUserData ({commit}, payload) { 95 | commit('setLoading', true) 96 | const updateUserObj = {} 97 | let filename 98 | let ext 99 | console.log('PAYLOAD', payload) 100 | if (payload.firstName) { 101 | updateUserObj.firstName = payload.firstName 102 | } 103 | if (payload.lastName) { 104 | updateUserObj.lastName = payload.lastName 105 | } 106 | if (payload.avatar) { 107 | updateUserObj.avatar = payload.avatar 108 | } 109 | if (payload.currency) { 110 | updateUserObj.currency = payload.currency 111 | } 112 | if (payload.location) { 113 | updateUserObj.location = payload.location 114 | } 115 | if (payload.timeZone) { 116 | updateUserObj.timeZone = payload.timeZone 117 | } 118 | if (payload.longitude) { 119 | updateUserObj.longitude = payload.longitude 120 | } 121 | if (payload.latitude) { 122 | updateUserObj.latitude = payload.latitude 123 | } 124 | if (payload.language) { 125 | updateUserObj.language = payload.language 126 | } 127 | if (payload.updated) { 128 | updateUserObj.updated = payload.updated 129 | } 130 | if (payload.image) { 131 | filename = payload.image.name 132 | ext = filename.slice(filename.lastIndexOf('.')) 133 | updateUserObj.imageExt = ext 134 | } 135 | 136 | let imageUrl 137 | let key = firebase.auth().currentUser.uid 138 | let userRef = firebase.firestore().collection('users').doc(firebase.auth().currentUser.uid) 139 | let setWithMerge = userRef.set({ 140 | ...updateUserObj 141 | }, { merge: true }) 142 | .then(() => { 143 | return firebase.storage().ref('users/' + key + ext).put(payload.image) 144 | }) 145 | .then(() => { 146 | commit('setLoading', false) 147 | console.log('This is the push of the USER payload', payload) 148 | // commit('updateUser', payload) 149 | }) 150 | .catch(error => { 151 | console.log(error) 152 | commit('setLoading', false) 153 | }) 154 | console.log('at the end of the loop') 155 | } 156 | } 157 | }) 158 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /src/views/Profile.vue: -------------------------------------------------------------------------------- 1 | 80 | 185 | -------------------------------------------------------------------------------- /src/views/SignIn.vue: -------------------------------------------------------------------------------- 1 | 52 | 84 | 85 | -------------------------------------------------------------------------------- /src/views/SignOut.vue: -------------------------------------------------------------------------------- 1 | 4 | 13 | -------------------------------------------------------------------------------- /src/views/SignUp.vue: -------------------------------------------------------------------------------- 1 | 62 | 84 | 85 | --------------------------------------------------------------------------------