implements new signin/up method that allows for longer sessions, and additionally allows email with link signin

This commit is contained in:
msramalho
2025-02-24 17:36:35 +00:00
parent 9c2a63f5a9
commit ffaeaffe20
10 changed files with 92 additions and 117 deletions

View File

@@ -27,8 +27,8 @@ yarn lint
2. login to your firebase account with `firebase login` 2. login to your firebase account with `firebase login`
3. make sure you have access to the project `firebase projects:list` 3. make sure you have access to the project `firebase projects:list`
4. build `yarn build` and check with `yarn preview`, once all is good release `firebase deploy --only hosting` 4. build `yarn build` and check with `yarn preview`, once all is good release `firebase deploy --only hosting`
5. to update schedule functions `firebase deploy --only functions` currently these are disabled <!-- 5. to update schedule functions `firebase deploy --only functions` currently these are disabled -->
6. if you add any library to a function, install it inside the `/functions` folder and not in the root folder <!-- 6. if you add any library to a function, install it inside the `/functions` folder and not in the root folder -->
<!-- 7. to update secrets use `firebase functions:secrets:set SECRET_NAME` more info [here](https://firebase.google.com/docs/functions/config-env?gen=2nd#managing_secrets) --> <!-- 7. to update secrets use `firebase functions:secrets:set SECRET_NAME` more info [here](https://firebase.google.com/docs/functions/config-env?gen=2nd#managing_secrets) -->
<!-- 1. `API_SERVICE_PASSWORD` for the auto-archiver-api --> <!-- 1. `API_SERVICE_PASSWORD` for the auto-archiver-api -->
<!-- 2. `GOOGLE_API_CLIENT_EMAIL` and `GOOGLE_API_PRIVATE_KEY` for the scheduled function to validate sheets exist --> <!-- 2. `GOOGLE_API_CLIENT_EMAIL` and `GOOGLE_API_PRIVATE_KEY` for the scheduled function to validate sheets exist -->

1
functions/README.md Normal file
View File

@@ -0,0 +1 @@
these are currently disabled.

View File

@@ -12,7 +12,7 @@
"@mdi/font": "^7.2.96", "@mdi/font": "^7.2.96",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"firebase": "^9.22.0", "firebase": "^9.22.0",
"firebaseui": "^6.0.2", "firebaseui": "^6.1.0",
"gapi-script": "^1.2.0", "gapi-script": "^1.2.0",
"googleapis": "^134.0.0", "googleapis": "^134.0.0",
"moment": "^2.30.1", "moment": "^2.30.1",

View File

@@ -0,0 +1,76 @@
<template>
<section id="firebaseui-auth-container"></section>
</template>
<script>
import firebase from "firebase/compat/app";
import * as firebaseui from "firebaseui";
import "firebaseui/dist/firebaseui.css";
import "firebase/compat/auth";
import { firebaseConfig } from "@/firebase.js";
export default {
name: "FirebaseLogin",
mounted() {
console.log(`user after mount`, this.$store.state.user);
firebase.initializeApp(firebaseConfig);
let uiConfig = {
signInFlow: 'popup',
signInSuccessUrl: "/",
signInOptions: [
{
provider: firebase.auth.GoogleAuthProvider.PROVIDER_ID,
scopes: [
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email"
],
customParameters: { prompt: "select_account" },
},
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
signInMethod:
firebase.auth.EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD,
},
],
callbacks: {
signInSuccessWithAuthResult: function (authResult, redirectUrl) {
console.log("authResult", authResult);
console.log("redirectUrl", redirectUrl);
return true;
},
},
};
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.$store.commit("setLoadingUserState", true);
this.$store.commit("setUser", user);
user.getIdToken().then((token) => {
this.$store.commit("setAccessToken", token);
this.$store.dispatch("checkActiveUser");
this.$store.dispatch("checkUserPermissions");
this.$store.dispatch("checkUserUsage");
});
} else {
let ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebase.auth());
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL).then(() => {
console.log("Auth persistence set to LOCAL");
});
ui.start("#firebaseui-auth-container", uiConfig);
}
});
},
};
</script>
<style>
#firebaseui-auth-container {
margin-top: 1em;
}
</style>

View File

@@ -77,8 +77,6 @@
> >
</span> </span>
<v-btn v-if="!user" @click="$store.dispatch('signin')">Sign In</v-btn>
<v-menu v-if="user?.active && smAndDown"> <v-menu v-if="user?.active && smAndDown">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-app-bar-nav-icon v-bind="props"></v-app-bar-nav-icon> <v-app-bar-nav-icon v-bind="props"></v-app-bar-nav-icon>

View File

@@ -15,27 +15,11 @@
This tool can be used to archive digital content via single URL or This tool can be used to archive digital content via single URL or
Google Sheets, you can also search for archived content. Google Sheets, you can also search for archived content.
</p> </p>
<div class="text-center"> <FirebaseLogin v-if="!user" />
<v-btn <v-container v-if="loadingUserState" class="pane" style="text-align: center">
v-if="!user && !loadingUserState"
@click="$store.dispatch('signin')"
size="large"
>Sign In</v-btn
>
</div>
<v-container
v-if="loadingUserState"
class="pane"
style="text-align: center"
>
<v-row justify="center"> <v-row justify="center">
<v-col cols="12"> <v-col cols="12">
<v-progress-circular <v-progress-circular color="teal" indeterminate :size="82" :width="7"></v-progress-circular>
color="teal"
indeterminate
:size="82"
:width="7"
></v-progress-circular>
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<h4>loading...</h4> <h4>loading...</h4>
@@ -50,8 +34,12 @@
</template> </template>
<script> <script>
import FirebaseLogin from "@/components/FirebaseLogin.vue";
export default { export default {
name: "WelcomeCard", name: "WelcomeCard",
components: {
FirebaseLogin,
},
props: {}, props: {},
computed: { computed: {
user() { user() {

View File

@@ -17,4 +17,4 @@ const firebaseAuth = getAuth(firebaseApp);
const firebaseFirestore = getFirestore(firebaseApp); const firebaseFirestore = getFirestore(firebaseApp);
export { firebaseApp, firebaseAuth, firebaseFirestore }; export { firebaseApp, firebaseAuth, firebaseFirestore, firebaseConfig };

View File

@@ -1,6 +1,4 @@
import { gapi } from "gapi-script"; import { gapi } from "gapi-script";
// import { GoogleAuthProvider, signInWithCredential } from "firebase/auth";
// import { firebaseAuth } from "@/firebase.js";
gapi.load("client:auth2", async () => { gapi.load("client:auth2", async () => {
gapi.client.init({ gapi.client.init({

View File

@@ -1,29 +1,8 @@
import { createStore } from "vuex"; import { createStore } from "vuex";
import { gapi } from "@/gapi"; import { gapi } from "@/gapi";
import { import { signOut } from "firebase/auth";
signOut,
GoogleAuthProvider,
signInWithCredential,
browserLocalPersistence,
setPersistence,
} from "firebase/auth";
import { firebaseAuth } from "@/firebase.js"; import { firebaseAuth } from "@/firebase.js";
function saveToLocalStorage(state) {
localStorage.setItem("user", JSON.stringify(state.user));
localStorage.setItem("access_token", state.access_token);
}
function loadFromLocalStorage() {
const user = JSON.parse(localStorage.getItem("user"));
const access_token = localStorage.getItem("access_token");
return { user, access_token };
}
function clearLocalStorage() {
localStorage.removeItem("user");
localStorage.removeItem("access_token");
}
async function waitForGapiAuth2() { async function waitForGapiAuth2() {
return new Promise((resolve, _reject) => { return new Promise((resolve, _reject) => {
@@ -52,11 +31,9 @@ export default createStore({
mutations: { mutations: {
setUser(state, user) { setUser(state, user) {
state.user = user; state.user = user;
saveToLocalStorage(state);
}, },
setUserActiveState(state, active) { setUserActiveState(state, active) {
state.user.active = active; state.user.active = active;
saveToLocalStorage(state);
}, },
setUserPermissions(state, permissions) { setUserPermissions(state, permissions) {
state.user.permissions = permissions; state.user.permissions = permissions;
@@ -64,7 +41,6 @@ export default createStore({
(key) => key !== "all" (key) => key !== "all"
); );
state.loadingUserState = false; state.loadingUserState = false;
saveToLocalStorage(state);
}, },
setUserUsage(state, usage) { setUserUsage(state, usage) {
state.user.usage = usage; state.user.usage = usage;
@@ -74,49 +50,15 @@ export default createStore({
}, },
setLoadingUserState(state, loadingUserState) { setLoadingUserState(state, loadingUserState) {
state.loadingUserState = loadingUserState; state.loadingUserState = loadingUserState;
saveToLocalStorage(state);
}, },
setAccessToken(state, access_token) { setAccessToken(state, access_token) {
state.access_token = access_token; state.access_token = access_token;
saveToLocalStorage(state);
}, },
setErrorMessage(state, errorMessage) { setErrorMessage(state, errorMessage) {
state.errorMessage = errorMessage; state.errorMessage = errorMessage;
}, },
}, },
actions: { actions: {
async signin({ commit, dispatch }) {
commit("setLoadingUserState", true);
async function callback(tokenResponse) {
let access_token = tokenResponse.access_token;
commit("setAccessToken", access_token);
const credential = GoogleAuthProvider.credential(null, access_token);
// Set persistence before signing in
await setPersistence(firebaseAuth, browserLocalPersistence);
// Sign in with the provided credential
const response = await signInWithCredential(firebaseAuth, credential);
commit("setUser", response.user);
dispatch("checkActiveUser");
dispatch("checkUserPermissions");
dispatch("checkUserUsage");
}
commit("setUser", null);
const client = google.accounts.oauth2.initTokenClient({
client_id:
"406209235111-r1mpkvkfaqc2jg5iqbvffl2b0rf4clbo.apps.googleusercontent.com",
scope:
"https://www.googleapis.com/auth/drive.file https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
callback,
});
await client.requestAccessToken();
},
async signout({ commit }) { async signout({ commit }) {
try { try {
const authInstance = await waitForGapiAuth2(); const authInstance = await waitForGapiAuth2();
@@ -133,7 +75,6 @@ export default createStore({
// clean user from store and local storage // clean user from store and local storage
commit("setUser", null); commit("setUser", null);
commit("setSheets", []); commit("setSheets", []);
clearLocalStorage();
} catch (error) { } catch (error) {
console.error("signOutUser (firebase/auth.js): ", error); console.error("signOutUser (firebase/auth.js): ", error);
} finally { } finally {
@@ -230,6 +171,7 @@ export default createStore({
console.error("getSheets (firebase.js): ", error); console.error("getSheets (firebase.js): ", error);
} }
}, },
async createSheet( async createSheet(
{ _state, dispatch, _commit }, { _state, dispatch, _commit },
{ name, service_account_email } { name, service_account_email }
@@ -395,33 +337,5 @@ export default createStore({
}, },
modules: {}, modules: {},
plugins: [ plugins: [
(store) => {
store.subscribe((mutation, state) => {
if (mutation.type === "setUser" || mutation.type === "setAccessToken") {
saveToLocalStorage(state);
}
});
const { user, access_token } = loadFromLocalStorage();
if (user && access_token) {
store.commit("setLoadingUserState", true);
store.commit("setUser", user);
store.commit("setAccessToken", access_token);
store.getters.isTokenExpired
.then((expired) => {
if (expired) {
store.dispatch("signout");
} else {
store.dispatch("checkActiveUser");
store.dispatch("checkUserPermissions");
store.dispatch("checkUserUsage");
}
})
.catch((error) => {
console.error("Error checking token expiration:", error);
store.dispatch("signout");
});
}
},
], ],
}); });

View File

@@ -4268,9 +4268,9 @@ firebase@^9.22.0:
"@firebase/storage-compat" "0.3.2" "@firebase/storage-compat" "0.3.2"
"@firebase/util" "1.9.3" "@firebase/util" "1.9.3"
firebaseui@^6.0.2: firebaseui@^6.1.0:
version "6.1.0" version "6.1.0"
resolved "https://registry.npmjs.org/firebaseui/-/firebaseui-6.1.0.tgz" resolved "https://registry.yarnpkg.com/firebaseui/-/firebaseui-6.1.0.tgz#a8feb91d2781342599cb8d2c79cc7553e6257612"
integrity sha512-5WiVYVxPGMANuZKxg6KLyU1tyqIsbqf/59Zm4HrdFYwPtM5lxxB0THvgaIk4ix+hCgF0qmY89sKiktcifKzGIA== integrity sha512-5WiVYVxPGMANuZKxg6KLyU1tyqIsbqf/59Zm4HrdFYwPtM5lxxB0THvgaIk4ix+hCgF0qmY89sKiktcifKzGIA==
dependencies: dependencies:
dialog-polyfill "^0.4.7" dialog-polyfill "^0.4.7"