Vue로 PWA 개발

12. mylog 로그인 - 완성

그랜파 개발자 2024. 10. 11. 09:56

1. 로그인

이메일 패스워드를 입력하고 로그인 버튼은 누르면 signInWithEmailAndPassword로 구글에 로그인한 후 uid를 받아 이것으로 mylog의 사용자 정보를 가져와 로그인 설정을 합니다.

1.1 login

async login({ commit, dispatch }, { email, password }) {    
    try {
      const { user } = await signInWithEmailAndPassword(auth, email, password);
      //console.log('user::::', user);
      // 웹앱의 계정 정보를 가져와 로그인 설정을 한다.
      dispatch('fetchUserWithUid', {uid: user.uid});       
      router.push("/");   // home으로
    } catch (error) {
      commit('setError', error.message);
      alert("Failed to log in: " + error.message);
    }
  },

1.2 fetchUserWithUid

async fetchUserWithUid({ commit }, {uid}) {
    try {
      const users = [];
      const userRef = query(users_collection, where('uids', 'array-contains', uid));
      const querySnapshot = await getDocs(userRef);
      querySnapshot.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
         users.push({ id: doc.id, ...doc.data() });
      });
      // 로그인 설정을 한다.
      commit('setUser', users[0]);
    } catch (error) {
      console.error('Error fetching user:', error);
    }            
  },

2. 구글 계정 연결

구글 계정 연결은 사용자의 구글 계정의 uid를 mylog 계정에 저장합니다. 구글 로그인을 하게 되면 구글의 uid로 mylog의 계정을 가져와서 로그인 설정을 합니다.

2.1 addGoogleAccount

async addGoogleAccount({ commit, dispatch, getters }) {
    try {
      const provider = new GoogleAuthProvider();
      const { user } = await signInWithPopup(auth, provider);
      try {
        // 이미 연동된 회원의 경우 알림 메시지 출력한다.
        const myUser = getters.getUserByUid(user.uid);
        if (myUser) {
          // 이미 연동되어 있다.
          commit('setError','이미 연동되어 있습니다.');
        } else {
          // 구글 연동을 진행한다.
          dispatch('addUidToUser', user.uid);
        }          
      } catch (error) {
        commit('setError','Error adding google uid');
      }
    } catch (error) {
      commit('setError', error.message);
    }
  },

2.2 getUserByUid

getUserByUid: (state) => (uid) => {
    return state.users.find(user => user.uids && user.uids.includes(uid));
}

2.3 addUidToUser

async addUidToUser({ state }, newUid) {      
    //console.log('user.id:', state.user.id);
    if (state.user) {
      try {
        const userDoc = doc(db, "users", state.user.id);
        updateDoc(userDoc, {
          uids: arrayUnion(newUid)
        });
        // Update the local state if neededa
        state.user.uids = [...(state.user.uids || []), newUid];
      } catch (error) {
        console.error('Error adding UID to user:', error);
      }
    }
  },

3. 구글 로그인

googleLogin

async googleLogin({ commit, getters }) {
    try {
      // 구글 계정에 로그인
      const provider = new GoogleAuthProvider();
      const { user } = await signInWithPopup(auth, provider)
      //const { user } = await signInWithPopup(auth); //, googleProvider);
      try {
        // 구글계정의 uid로 웹앱 계정의 정보를 가져옴.
        // 웹앱 계정은 사이트에 접속할 때 전체 회원 정보를 로드하였으므로 
        // 이미 로드된 회원 리스트에서 구글 계정 uid를 가진 myUser를 가져온다.
        const myUser = getters.getUserByUid(user.uid);
        if (myUser) {
          //console.log('myUser: ', myUser);
          commit('setUser', myUser);
          router.push("/");   // home으로
        } else {
          console.log('등록된 회원이 아닙니다.');
          commit('setError', '등록된 회원이 아닙니다.');
          // 이경우 회원 가입 페이지로 이동 필요
        }
      } catch (error) {
        //console.error('Error adding user:', error);
        commit('setError', error.message);       
      }
    } catch (error) {
      commit('setError', error.message);
    }
  },

4. 자동 로그인

initializeAuth({ commit, dispatch }) {
    onAuthStateChanged(auth, (user) => {
      if (user) {
        // user.uid로 웹앱의 firestore DB에서 계정 정보를 가져온다.
        dispatch('fetchUserWithUid', {uid: user.uid});
        // fetchUserWithUid에서 setUser를 실행한다.
        // commit("setUser", user);
        //dispatch("fetchUserInfo");
      } else {
        commit("setUser", null);
        commit("setUserInfo", null);
      }
    });
  },

5. 로그 아웃

async logout({ commit }) {
    await auth.signOut();
    commit('setUser', null);
    router.push("/");   // home으로
  },

6. Source Code

6.1 src/views/LoginView.vue

<!-- src/views/LoginView.vue -->
<template>
  <v-container>
    <v-row>
      <v-col cols="12" class="text-center my-5">
        <h3>로그인</h3>
      </v-col>      
    </v-row>
    <v-row>
      <v-col class="text-center" cols="10" offset="1" sm="8" offset-sm="2">   
        <v-form @submit.prevent="loginUser">
          <v-text-field v-model="email" label="이메일" type="email" required></v-text-field>
          <v-text-field v-model="password" label="비밀번호" type="password" required></v-text-field>
          <v-btn type="submit" color="primary">Login</v-btn>
          <v-alert v-if="error" type="error" dismissible>{{ error }}</v-alert>
        </v-form>
      </v-col>
    </v-row>
    <v-row>
      <v-col class="text-center" cols="10" offset="1" sm="8" offset-sm="2">  
        <v-btn color="red" @click="googleLogin" dark>
          <v-icon left>mdi-google</v-icon>
          Sign in with Google
        </v-btn>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from 'vuex';

export default {
  name: 'Login',
  data() {
    return {
      email: '',
      password: ''
    };
  },
  computed: {
    ...mapGetters('auth',['error'])
  },
  methods: {
    ...mapActions('auth', ['login', 'googleLogin']),
    async loginUser() {
      await this.login({ email: this.email, password: this.password });
    }
  }
};
</script>

6.2 src/views/ProfileView.vue

<!-- src/views/ProfileView.vue -->
<template>
  <v-container>
    <v-card v-if="user">
      <v-card-title>User Profile</v-card-title>
      <v-card-text>
        <p><strong>이메일:</strong> {{ user.email }}</p>
        <p><strong>이름:</strong> {{ user.name }}</p>
        <p><strong>id:</strong> {{ user.id }}</p>
      </v-card-text>
      <v-card-text>
        <v-btn color="red" @click="addGoogleAccount" dark>  
          <v-icon left>mdi-google</v-icon>
          Google 계정 연동
        </v-btn>
        <v-alert v-if="error" type="error" dismissible @input="resetErrorMsg" class="my-alert">{{ error }}</v-alert>
      </v-card-text>
    </v-card>
    <v-alert v-else type="info">No user is logged in.</v-alert>
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from 'vuex';

export default {
  computed: {
    ...mapGetters('auth',['user', 'error'])
  },
  methods: {
    ...mapActions('auth',['addGoogleUid', 'resetError']),
    async addGoogleAccount() {
      await this.addGoogleUid();
    },
    resetErrorMsg() {
      this.resetError();
    }
  }
};
</script>

<style scoped>
.my-alert {
  margin: 20px 0;
}
</style>

6.3 src/firebase.js

// src/firebase.js
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore, collection, addDoc, getDocs, query, where, updateDoc, doc, arrayUnion } from "firebase/firestore";

const firebaseConfig = {
  apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
  authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.VUE_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.VUE_APP_FIREBASE_APP_ID,
};

// npm install dotenv - env가 정상 동작하지 않을 때 설치 필요함

// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);

export { auth, db, collection, addDoc, getDocs, query, where, updateDoc, doc, arrayUnion };

6.4 src/main.js

// src/main.js

import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'

Vue.config.productionTip = false

new Vue({
  router,
  store,
  vuetify,
  render: h => h(App),
  created() {
    // Set up Firebase auth state change listener
    const { dispatch } = this.$store;
    // Initialize Firebase authentication to check for the logged-in user
    dispatch('auth/initializeAuth');
    dispatch('auth/fetchUsers');
  }
}).$mount('#app')

6.5 src/store/modules/auth.js

Copy// src/store/modules/auth.js

import router from '@/router';  // Vue Router import
import { auth, db, collection, getDocs, query, where, updateDoc, doc, arrayUnion } from "@/firebase";
import { createUserWithEmailAndPassword, onAuthStateChanged, signInWithEmailAndPassword, GoogleAuthProvider, signInWithPopup } from "firebase/auth";

const state = {
  user: null,
  userInfo: null,
  users: [],
  isLoading: false,
  error: null
};

const mutations = {
  setUser(state, user) {
    state.user = user;
  },
  setUserInfo(state, userInfo) {
    state.userInfo = userInfo;
  },
  setUsers(state, users) {
    state.users = users;
    state.isLoading = false;
  },
  setError(state, error) {
    state.error = error;
    state.isLoading = false;
  },
  setLoading(state, isLoading) {
    state.isLoading = isLoading;
  }
};

const actions = {
  async fetchUsers({ commit }) {
    commit('setLoading', true);
    try {
      const users = [];
      const userRef = collection(db, "users");
      const querySnapshot = await getDocs(userRef);
      querySnapshot.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
        users.push({ id: doc.id, ...doc.data() });
      });
      commit('setUsers', users);
    } catch (error) {
      commit('setError', error.message);
    }
  },
  async addUser({ dispatch, commit }, user) {
    try {
      // Save additional user data to Firestore
      const userRef = collection(db, "users");
      const newUser = await addDoc(userRef, {
        email: user.email,
        uids: user.uids,
        username: user.username,
        mylogname: user.mylogname
      });

      // 로그인 설정
      user.id = newUser.id;
      commit('setUser', user);
      dispatch('fetchUsers');

      commit("setError", null);
    } catch (error) {
      console.error('Error adding user:', error);
    }
  },

  async register({ commit, dispatch }, { email, password, username, mylogname }) {
    try {
      const userCredential = await createUserWithEmailAndPassword(auth, email, password);

      const user = userCredential.user;        
      const newUser = {
        email: email,
        uids: [user.uid],
        username: username,
        mylogname: mylogname
      };

      //console.log('newuser:', newUser);
      dispatch('addUser', newUser);

      router.push("/");   // home으로

    } catch (error) {
      commit("setError", error.message);
    }
  },

  async fetchUserInfo({ commit, state }, user) {

    // if (!state.user) return;

    // try {
    //   const q = query(collection(db, "users"), where("uid", "==", state.user.uid));
    //   const querySnapshot = await getDocs(q);
    //   querySnapshot.forEach((doc) => {
    //     commit("setUserInfo", { ...doc.data(), id: doc.id });
    //   });
    // } catch (error) {
    //   commit("setError", error.message);
    // }

    try {
      commit("setUserInfo", user);
    } catch (error) {
      commit("setError", error.message);
    }
  },
  async updateUserInfo({ commit }, updatedInfo) {
    try {
      const userDoc = doc(db, "users", updatedInfo.id);
      await updateDoc(userDoc, updatedInfo);

      commit("setUserInfo", updatedInfo);
      commit("setUser", updatedInfo);

      commit("setError", null);
    } catch (error) {
      commit("setError", error.message);
    }
  },
  async fetchUserWithUid({ commit, dispatch }, {uid}) {
    // 로그인은 google 계정으로 한다.
    // user 정보는 mylog 계정 정보를 사용한다.
    // 그러므로 구글 계정의 uid로 mylog 계정의 user 정보를 가져와야 한다.
    try {
      const users = []; 
      const userRef = query(collection(db, "users"), where('uids', 'array-contains', uid));
      const querySnapshot = await getDocs(userRef);
      querySnapshot.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
         users.push({ id: doc.id, ...doc.data() });
      });
      // 로그인 설정을 한다.
      commit('setUser', users[0]);
      dispatch("fetchUserInfo", users[0]);
    } catch (error) {
      console.error('Error fetching user:', error);
    }            
  },

  async login({ commit, dispatch }, { email, password }) {    
    try {
      const { user } = await signInWithEmailAndPassword(auth, email, password);
      //console.log('user::::', user);
      // 웹앱의 계정 정보를 가져와 로그인 설정을 한다.
      dispatch('fetchUserWithUid', {uid: user.uid});       
      router.push("/");   // home으로
    } catch (error) {
      commit('setError', error.message);
      alert("Failed to log in: " + error.message);
    }
  },
  async addUidToUser({ state }, newUid) {      
    //console.log('user.id:', state.user.id);
    if (state.user) {
      try {
        const userDoc = doc(db, "users", state.user.id);
        updateDoc(userDoc, {
          uids: arrayUnion(newUid)
        });
        // Update the local state if neededa
        state.user.uids = [...(state.user.uids || []), newUid];
      } catch (error) {
        console.error('Error adding UID to user:', error);
      }
    }
  },
  async addGoogleAccount({ commit, dispatch, getters }) {
    try {
      const provider = new GoogleAuthProvider();
      const { user } = await signInWithPopup(auth, provider);
      try {
        // 이미 연동된 회원의 경우 알림 메시지 출력한다.
        const myUser = getters.getUserByUid(user.uid);
        if (myUser) {
          // 이미 연동되어 있다.
          commit('setError','이미 연동되어 있습니다.');
        } else {
          // 구글 연동을 진행한다.
          dispatch('addUidToUser', user.uid);
        }          
      } catch (error) {
        commit('setError','Error adding google uid');
      }
    } catch (error) {
      commit('setError', error.message);
    }
  },
  async googleLogin({ commit, getters }) {
    try {
      // 구글 계정에 로그인
      const provider = new GoogleAuthProvider();
      const { user } = await signInWithPopup(auth, provider)
      //const { user } = await signInWithPopup(auth); //, googleProvider);
      try {
        // 구글계정의 uid로 웹앱 계정의 정보를 가져옴.
        // 웹앱 계정은 사이트에 접속할 때 전체 회원 정보를 로드하였으므로 
        // 이미 로드된 회원 리스트에서 구글 계정 uid를 가진 myUser를 가져온다.
        const myUser = getters.getUserByUid(user.uid);
        if (myUser) {
          //console.log('myUser: ', myUser);
          commit('setUser', myUser);
          router.push("/");   // home으로
        } else {
          console.log('등록된 회원이 아닙니다.');
          commit('setError', '등록된 회원이 아닙니다.');
          // 이경우 회원 가입 페이지로 이동 필요
        }
      } catch (error) {
        //console.error('Error adding user:', error);
        commit('setError', error.message);       
      }
    } catch (error) {
      commit('setError', error.message);
    }
  },
  async logout({ commit }) {
    await auth.signOut();
    commit('setUser', null);
    router.push("/");   // home으로
  },

  setError({ commit }, err_message) {
    commit('setError', err_message);
  },
  resetError({ commit }) {
    commit('setError', null);
  },
  initializeAuth({ commit, dispatch }) {
    onAuthStateChanged(auth, (user) => {
      if (user) {
        // user.uid로 웹앱의 firestore DB에서 계정 정보를 가져온다.
        dispatch('fetchUserWithUid', {uid: user.uid});
        // fetchUserWithUid에서 setUser를 실행한다.
        // commit("setUser", user);
        //dispatch("fetchUserInfo");
      } else {
        commit("setUser", null);
        commit("setUserInfo", null);
      }
    });
  },
};

const getters = {
  user: state => state.user, 
  userInfo: state => state.userInfo,  
  users: state => state.users,
  error: state => state.error,
  isAuthenticated: state => !!state.user,
  isLoading: state => state.isLoading,
  getUserByUid: (state) => (uid) => {
    return state.users.find(user => user.uids && user.uids.includes(uid));
  }
};

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
};



'Vue로 PWA 개발' 카테고리의 다른 글

14. myLog 페이지 - 접근 제한  (0) 2024.10.12
13. mylog 페이지 - 인증 후 보기  (5) 2024.10.12
11. mylog 로그인 - google 로그인  (2) 2024.10.11
10. mylog 로그인 - firebase auth  (0) 2024.10.11
9. mylog 로그인  (0) 2024.10.11