Skip to content

Commit daf7854

Browse files
authored
Merge pull request #570 from Geode-solutions/feat/auto_login_on_desktop
Feat/auto login on desktop
2 parents 59fc2af + ee8b37f commit daf7854

8 files changed

Lines changed: 266 additions & 24 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<script setup>
2+
import GlassCard from "@ogw_front/components/GlassCard";
3+
import { useAuth } from "@vease/composables/auth";
4+
import { useFeedbackStore } from "@ogw_front/stores/feedback";
5+
6+
const feedbackStore = useFeedbackStore();
7+
const { deleteAccount } = useAuth();
8+
const showDeleteAccount = defineModel({ type: Boolean, default: false });
9+
const loading = ref(false);
10+
const password = ref("");
11+
const passwordVisible = ref(false);
12+
13+
const emit = defineEmits(["submit"]);
14+
15+
async function handleDeleteAccount() {
16+
loading.value = true;
17+
try {
18+
await deleteAccount(password.value);
19+
feedbackStore.add_success("Account successfully deleted");
20+
await navigateTo("/login");
21+
} catch (error) {
22+
const code = 500;
23+
feedbackStore.add_error(code, "/delete-account", "Delete account", error);
24+
} finally {
25+
loading.value = false;
26+
password.value = "";
27+
}
28+
}
29+
</script>
30+
31+
<template>
32+
<v-dialog v-model="showDeleteAccount" max-width="450">
33+
<GlassCard variant="panel" padding="pa-8" rounded="xl" border="border-primary">
34+
<v-card-title class="text-h4 font-weight-bold mb-4 text-white px-0">
35+
Delete Account
36+
</v-card-title>
37+
<v-card-text class="text-body-1 text-white opacity-80 mb-8 px-0">
38+
Are you sure you want to delete your account? This action is irreversible. Please enter your
39+
password to confirm.
40+
</v-card-text>
41+
42+
<v-text-field
43+
v-model="password"
44+
label="Password"
45+
:type="passwordVisible ? 'text' : 'password'"
46+
variant="outlined"
47+
prepend-inner-icon="mdi-lock-outline"
48+
:append-inner-icon="passwordVisible ? 'mdi-eye-off-outline' : 'mdi-eye-outline'"
49+
@click:append-inner="passwordVisible = !passwordVisible"
50+
class="mb-4"
51+
color="white"
52+
autocomplete="current-password"
53+
hide-details="auto"
54+
/>
55+
56+
<v-card-actions class="px-0 ga-4">
57+
<v-btn
58+
variant="text"
59+
color="white"
60+
class="flex-grow-1 text-none"
61+
@click="showDeleteAccount = false"
62+
>
63+
Cancel
64+
</v-btn>
65+
<v-btn
66+
type="submit"
67+
color="primary"
68+
:loading="loading"
69+
:disabled="!password"
70+
variant="flat"
71+
class="flex-grow-1 text-none font-weight-black"
72+
@click="handleDeleteAccount"
73+
>
74+
Delete Account
75+
</v-btn>
76+
</v-card-actions>
77+
</GlassCard>
78+
</v-dialog>
79+
</template>

app/components/Layout/SideBar.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { useAuth } from "@vease/composables/auth";
33
import { useUIStore } from "@vease/stores/ui";
44
5-
const { user, logout } = useAuth();
5+
const { user } = useAuth();
66
const UIStore = useUIStore();
77
88
const drawer = ref(true);

app/composables/auth.js

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
1-
import { createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut } from "firebase/auth";
2-
import { useAPIStore } from "@vease/stores/api";
1+
// Third-party imports
2+
import {
3+
EmailAuthProvider,
4+
createUserWithEmailAndPassword,
5+
deleteUser,
6+
reauthenticateWithCredential,
7+
signInWithEmailAndPassword,
8+
signOut,
9+
} from "firebase/auth";
10+
import { appMode } from "@ogw_front/utils/local/app_mode";
311
import { useFirebaseAuth } from "vuefire";
12+
import { useInfraStore } from "@ogw_front/stores/infra";
13+
14+
// Local imports
15+
import { useAPIStore } from "@vease/stores/api";
416

17+
//oxlint-disable max-lines-per-function
518
function useAuth() {
619
const auth = useFirebaseAuth();
720
const user = useCurrentUser();
821
const APIStore = useAPIStore();
22+
const infraStore = useInfraStore();
923

1024
async function register(email, password) {
1125
const { user: newUser } = await createUserWithEmailAndPassword(auth, email, password);
@@ -23,21 +37,78 @@ function useAuth() {
2337
return newUser;
2438
}
2539

40+
async function autoLogin() {
41+
console.log("Attempting to retrieve credentials for auto-login");
42+
if (infraStore.app_mode !== appMode.DESKTOP) {
43+
return;
44+
}
45+
try {
46+
const { success, credentials, error } = await globalThis.electronAPI.get_credentials();
47+
if (!success) {
48+
console.error("Failed to get credentials:", error);
49+
return;
50+
}
51+
console.log("Credentials retrieval successful", { credentials });
52+
if (credentials) {
53+
const { email, password } = credentials;
54+
try {
55+
return login(email, password);
56+
} catch (loginError) {
57+
console.error("Auto-login failed:", loginError);
58+
return globalThis.electronAPI.delete_credentials();
59+
}
60+
}
61+
} catch (error) {
62+
console.error("Failed to get credentials:", error);
63+
}
64+
}
65+
2666
async function login(email, password) {
2767
const { user: loggedInUser } = await signInWithEmailAndPassword(auth, email, password);
2868
await loggedInUser.reload();
2969
if (!loggedInUser.emailVerified) {
3070
await signOut(auth);
3171
throw new Error("Please verify your email address before logging in.");
3272
}
73+
if (infraStore.app_mode === appMode.DESKTOP) {
74+
console.log("Saving credentials for desktop mode");
75+
globalThis.electronAPI.save_credentials({
76+
email,
77+
password,
78+
});
79+
}
3380
return loggedInUser;
3481
}
3582

36-
function logout() {
37-
signOut(auth);
83+
async function deleteAccount(password) {
84+
if (!user.value) {
85+
throw new Error("No user logged in");
86+
}
87+
88+
// Re-authenticate before deleting (required by Identity Platform)
89+
const credential = EmailAuthProvider.credential(user.value.email, password);
90+
await reauthenticateWithCredential(user.value, credential);
91+
92+
await deleteUser(user.value);
93+
await logout();
3894
}
3995

40-
return { user, register, login, logout, resetPassword };
96+
async function logout() {
97+
if (infraStore.app_mode === appMode.DESKTOP) {
98+
try {
99+
const { success } = await globalThis.electronAPI.delete_credentials();
100+
if (!success) {
101+
console.error("Failed to delete credentials:", error);
102+
return;
103+
}
104+
console.log("Credentials deletion successful");
105+
} catch (error) {
106+
console.error("Failed to delete credentials:", error);
107+
}
108+
}
109+
await signOut(auth);
110+
}
111+
return { user, autoLogin, deleteAccount, register, login, logout, resetPassword };
41112
}
42113

43114
function resetPassword(email) {

app/layouts/default.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import FeedBackSnackers from "@ogw_front/components/FeedBack/Snackers";
1010
import GlassCard from "@ogw_front/components/GlassCard";
1111
import InfraConnected from "@ogw_front/components/InfraConnected";
1212
import Launcher from "@ogw_front/components/Launcher";
13+
import { useAuth } from "@vease/composables/auth";
1314
1415
const UIStore = useUIStore();
1516
const infraStore = useInfraStore();
1617
18+
const { autoLogin } = useAuth();
19+
autoLogin();
20+
1721
function handleFilesDropped(files) {
1822
if (!UIStore.showStepper && !UIStore.showExtensions) {
1923
UIStore.setDroppedFiles([...files]);

app/pages/account.vue

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
<script setup>
22
import AuthBackground from "@vease/components/Auth/AuthBackground";
3+
import AuthDeleteAccountDialog from "@vease/components/Auth/AuthDeleteAccountDialog";
34
import { useAuth } from "@vease/composables/auth";
45
5-
const { user, logout } = useAuth();
6+
const { logout, user } = useAuth();
67
78
function handleLogout() {
89
logout();
910
navigateTo("/login");
1011
}
12+
13+
const showDeleteAccount = ref(false);
1114
</script>
1215

1316
<template>
@@ -17,7 +20,7 @@ function handleLogout() {
1720
<v-row no-gutters class="fill-height align-center justify-center">
1821
<v-col cols="12" sm="8" md="6" lg="4" xl="3" class="pa-6">
1922
<div class="d-flex flex-column align-center w-100 px-4">
20-
<v-avatar color="rgba(255, 255, 255, 0.1)" size="120" class="mb-8 account-avatar">
23+
<v-avatar color="rgba(0, 0, 0, 0.1)" size="120" class="mb-8 account-avatar">
2124
<v-icon icon="mdi-account" size="80" color="white" />
2225
</v-avatar>
2326

@@ -38,19 +41,32 @@ function handleLogout() {
3841
>
3942
Logout
4043
</v-btn>
44+
<v-row>
45+
<v-btn
46+
text="Back to Viewer"
47+
variant="text"
48+
color="white"
49+
class="text-none opacity-70"
50+
@click="navigateTo('/')"
51+
prepend-icon="mdi-arrow-left"
52+
/>
53+
</v-row>
4154

42-
<v-btn
43-
variant="text"
44-
color="white"
45-
class="text-none opacity-70"
46-
@click="navigateTo('/')"
47-
prepend-icon="mdi-arrow-left"
48-
>
49-
Back to Viewer
50-
</v-btn>
55+
<v-row class="pa-15">
56+
<v-btn
57+
text="Delete my account"
58+
variant="text"
59+
color="white"
60+
class="text-none opacity-70"
61+
@click="showDeleteAccount = true"
62+
prepend-icon="mdi-delete"
63+
/>
64+
</v-row>
5165
</div>
5266
</v-col>
5367
</v-row>
68+
69+
<AuthDeleteAccountDialog v-model="showDeleteAccount" />
5470
</v-container>
5571
</template>
5672

electron/main.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ import { autoUpdater } from "electron-updater";
66
import { cleanupBackend } from "@geode/opengeodeweb-front/app/utils/local/cleanup.js";
77

88
// Local imports
9-
// oxlint-disable-next-line import/no-relative-parent-imports
10-
import { createNewWindow, parseArgs } from "../utils/desktop.js";
9+
import {
10+
createNewWindow,
11+
deleteCredentials,
12+
getCredentials,
13+
parseArgs,
14+
saveCredentials,
15+
// oxlint-disable-next-line import/no-relative-parent-imports
16+
} from "../utils/desktop.js";
1117

1218
const appArgs = parseArgs();
1319
console.log(`App launched with args: ${appArgs}`);
@@ -27,6 +33,22 @@ ipcMain.handle("project_folder_path", (_event, args) => {
2733
console.log(`[Electron] Updated projectFolderPath: ${projectFolderPath}`);
2834
});
2935

36+
ipcMain.handle("save_credentials", (_event, args) => {
37+
const { email, password } = args;
38+
return saveCredentials(email, password);
39+
});
40+
41+
ipcMain.handle("get_credentials", () => {
42+
console.log("Getting credentials");
43+
const credentials = getCredentials();
44+
return credentials;
45+
});
46+
47+
ipcMain.handle("delete_credentials", () => {
48+
console.log("Deleting credentials");
49+
return deleteCredentials();
50+
});
51+
3052
// oxlint-disable promise/always-return, promise/prefer-await-to-then, promise/catch-or-return, unicorn/prefer-top-level-await
3153
app.whenReady().then(() => createNewWindow());
3254

electron/preload.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
const { contextBridge, ipcRenderer } = require("electron");
33

44
contextBridge.exposeInMainWorld("electronAPI", {
5-
project_folder_path: async (args) => {
6-
await ipcRenderer.invoke("project_folder_path", args);
5+
project_folder_path: (args) => {
6+
ipcRenderer.invoke("project_folder_path", args);
77
},
88
new_window: (args) => {
9-
console.log("PRELOAD new_window", args);
109
ipcRenderer.invoke("new_window", args);
1110
},
11+
save_credentials: (args) => ipcRenderer.invoke("save_credentials", args),
12+
get_credentials: () => ipcRenderer.invoke("get_credentials"),
13+
delete_credentials: () => ipcRenderer.invoke("delete_credentials"),
1214
});

0 commit comments

Comments
 (0)