Skip to content

Commit 02294ce

Browse files
Valentin SirakovRAMTO
authored andcommitted
Not ready: auto login draft implementation
Signed-off-by: Valentin Sirakov <valentin.sirakov@limechain.tech>
1 parent de50320 commit 02294ce

File tree

9 files changed

+231
-11
lines changed

9 files changed

+231
-11
lines changed

front-end/src/main/modules/ipcHandlers/localUser/organizationCredentials.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
updateOrganizationCredentials,
77
deleteOrganizationCredentials,
88
tryAutoSignIn,
9+
tryAutoSignInOrganization,
910
} from '@main/services/localUser';
1011
import { createIPCChannel, renameFunc } from '@main/utils/electronInfra';
1112

@@ -19,5 +20,6 @@ export default () => {
1920
renameFunc(updateOrganizationCredentials, 'updateOrganizationCredentials'),
2021
renameFunc(deleteOrganizationCredentials, 'deleteOrganizationCredentials'),
2122
renameFunc(tryAutoSignIn, 'tryAutoSignIn'),
23+
renameFunc(tryAutoSignInOrganization, 'tryAutoSignInOrganization'),
2224
]);
2325
};

front-end/src/main/services/localUser/organizationCredentials.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,65 @@ export const tryAutoSignIn = async (user_id: string, decryptPassword: string | n
274274
return failedLogins;
275275
};
276276

277+
/* Tries to auto sign in to a single organization */
278+
export const tryAutoSignInOrganization = async (
279+
user_id: string,
280+
organization_id: string,
281+
decryptPassword: string | null,
282+
): Promise<{ success: boolean; error?: string }> => {
283+
const prisma = getPrismaClient();
284+
285+
try {
286+
// Find credentials for this organization
287+
const credentials = await prisma.organizationCredentials.findFirst({
288+
where: { user_id, organization_id },
289+
include: {
290+
organization: true,
291+
},
292+
});
293+
294+
if (!credentials) {
295+
return { success: false, error: 'No credentials found for this organization' };
296+
}
297+
298+
// Check if JWT is already valid (no need to login)
299+
const isInvalid = await organizationCredentialsInvalid(credentials);
300+
if (!isInvalid) {
301+
return { success: true }; // Already logged in
302+
}
303+
304+
// Decrypt the stored password
305+
let password: string;
306+
try {
307+
password = await decryptData(credentials.password, decryptPassword);
308+
} catch {
309+
return { success: false, error: 'Incorrect decryption password' };
310+
}
311+
312+
// Login to the organization server
313+
try {
314+
const { accessToken } = await login(
315+
credentials.organization.serverUrl,
316+
credentials.email,
317+
password,
318+
);
319+
320+
// Save the new JWT token to database
321+
await prisma.organizationCredentials.update({
322+
where: { id: credentials.id },
323+
data: { jwtToken: accessToken },
324+
});
325+
326+
return { success: true };
327+
} catch (error) {
328+
return { success: false, error: error instanceof Error ? error.message : 'Login failed' };
329+
}
330+
} catch (error) {
331+
console.error('Auto-sign in failed:', error);
332+
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
333+
}
334+
};
335+
277336
/* Encrypt data */
278337
async function encryptData(data: string, encryptPassword?: string | null) {
279338
const useKeychain = await getUseKeychainClaim();

front-end/src/preload/localUser/organizationCredentials.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,16 @@ export default {
6262
),
6363
tryAutoSignIn: (user_id: string, decryptPassword: string | null): Promise<Organization[]> =>
6464
ipcRenderer.invoke('organizationCredentials:tryAutoSignIn', user_id, decryptPassword),
65+
tryAutoSignInOrganization: (
66+
user_id: string,
67+
organization_id: string,
68+
decryptPassword: string | null,
69+
): Promise<{ success: boolean; error?: string }> =>
70+
ipcRenderer.invoke(
71+
'organizationCredentials:tryAutoSignInOrganization',
72+
user_id,
73+
organization_id,
74+
decryptPassword,
75+
),
6576
},
6677
};

front-end/src/renderer/components/Organization/ConnectionToggle.vue

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useToast } from 'vue-toast-notification';
1111
import { errorToastOptions } from '@renderer/utils/toastOptions';
1212
1313
import AppSwitch from '@renderer/components/ui/AppSwitch.vue';
14+
import { useRouter } from 'vue-router';
1415
1516
/* Props */
1617
const props = defineProps<{
@@ -28,6 +29,7 @@ const emit = defineEmits<{
2829
/* Stores */
2930
const orgConnection = useOrganizationConnection();
3031
const toast = useToast();
32+
const router = useRouter();
3133
3234
/* State */
3335
const isProcessing = ref(false);
@@ -39,7 +41,16 @@ const isConnected = computed(() => {
3941
});
4042
4143
const isDisabled = computed(() => {
42-
return props.disabled || isProcessing.value;
44+
if (props.disabled || isProcessing.value) {
45+
return true;
46+
}
47+
48+
const disconnectReason = orgConnection.getDisconnectReason(props.organization.serverUrl);
49+
if (disconnectReason === 'upgradeRequired') {
50+
return true;
51+
}
52+
53+
return false;
4354
});
4455
4556
/* Handlers */
@@ -68,11 +79,14 @@ const handleToggle = async (checked: boolean) => {
6879
const handleReconnect = async () => {
6980
const result = await reconnectOrganization(props.organization.serverUrl);
7081
82+
if (result.redirectToLogin) {
83+
await router.push({ name: 'organizationLogin' });
84+
return;
85+
}
86+
7187
if (result.success) {
7288
emit('connect');
7389
} else if (result.requiresUpdate) {
74-
// Version check failed - modal will be shown by reconnect service
75-
// Don't emit error, just let the user know via the modal
7690
console.log('Reconnection requires update - modal should be shown');
7791
} else {
7892
throw new Error('Failed to reconnect');

front-end/src/renderer/pages/OrganizationLogin/OrganizationLogin.vue

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,30 @@ const handleLogin = async () => {
9090
);
9191
await user.refetchOrganizationTokens();
9292
await performVersionCheck(user.selectedOrganization.serverUrl);
93-
93+
await user.refetchOrganizations();
94+
9495
toast.success('Successfully signed in', successToastOptions);
9596
9697
loading.value = false;
9798
98-
await withLoader(
99-
user.selectOrganization.bind(null, user.selectedOrganization),
100-
'Failed to change user mode',
101-
10000,
102-
false,
103-
);
99+
// Small delay to ensure database write is committed
100+
await new Promise(resolve => setTimeout(resolve, 50));
101+
102+
const updatedOrg = user.organizations.find(org => org.id === user.selectedOrganization?.id);
103+
if (!updatedOrg) {
104+
throw new Error('Organization not found after refresh');
105+
}
106+
107+
if ('loginRequired' in updatedOrg && !updatedOrg.loginRequired) {
108+
await withLoader(
109+
user.selectOrganization.bind(null, updatedOrg),
110+
'Failed to change user mode',
111+
10000,
112+
false,
113+
);
114+
} else {
115+
await user.selectOrganization(updatedOrg);
116+
}
104117
105118
if (isOrganizationActive(user.selectedOrganization)) {
106119
await setLast(user.selectedOrganization.id);

front-end/src/renderer/services/organization/disconnect.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import useOrganizationConnection from '@renderer/stores/storeOrganizationConnect
77
import { toggleAuthTokenInSessionStorage } from '@renderer/utils';
88
import { useToast } from 'vue-toast-notification';
99
import { errorToastOptions, infoToastOptions } from '@renderer/utils/toastOptions';
10+
import { updateOrganizationCredentials } from '../organizationCredentials';
1011

1112
export async function disconnectOrganization(
1213
serverUrl: string,
@@ -24,6 +25,15 @@ export async function disconnectOrganization(
2425
toggleAuthTokenInSessionStorage(serverUrl, '', true);
2526

2627
const org = userStore.organizations.find(o => o.serverUrl === serverUrl);
28+
if (org && userStore.personal && userStore.personal.isLoggedIn) {
29+
try {
30+
await updateOrganizationCredentials(org.id, userStore.personal.id, undefined, undefined, null);
31+
await userStore.refetchOrganizationTokens();
32+
} catch (error) {
33+
console.error('Failed to remove JWT from SQLite:', error);
34+
}
35+
}
36+
2737
if (org) {
2838
org.connectionStatus = 'disconnected';
2939
org.disconnectReason = reason;

front-end/src/renderer/services/organization/reconnect.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import useVersionCheck from '@renderer/composables/useVersionCheck';
1919

2020
import { useToast } from 'vue-toast-notification';
2121
import { errorToastOptions } from '@renderer/utils/toastOptions';
22+
import usePersonalPassword from '@renderer/composables/usePersonalPassword';
23+
import { tryAutoSignInOrganization } from '@renderer/services/organizationCredentials';
2224

2325
/**
2426
* Reconnect an organization with version checking and compatibility validation.
@@ -35,6 +37,7 @@ export async function reconnectOrganization(serverUrl: string): Promise<{
3537
success: boolean;
3638
requiresUpdate?: boolean;
3739
hasCompatibilityConflict?: boolean;
40+
redirectToLogin?: boolean;
3841
}> {
3942
const userStore = useUserStore();
4043
const ws = useWebsocketConnection();
@@ -49,6 +52,66 @@ export async function reconnectOrganization(serverUrl: string): Promise<{
4952
return { success: false };
5053
}
5154

55+
if (
56+
!org.isLoading &&
57+
org.isServerActive &&
58+
'loginRequired' in org &&
59+
org.loginRequired &&
60+
userStore.personal &&
61+
userStore.personal.isLoggedIn
62+
) {
63+
const personalId = userStore.personal.id;
64+
const organizationId = org.id;
65+
66+
const { getPasswordV2 } = usePersonalPassword();
67+
68+
const autoLoginResult = await new Promise<{ success: boolean; noCredentials?: boolean }>((resolve) => {
69+
getPasswordV2(
70+
async (password: string | null) => {
71+
try {
72+
const result = await tryAutoSignInOrganization(
73+
personalId,
74+
organizationId,
75+
password,
76+
);
77+
78+
if (!result.success) {
79+
if (result.error?.includes('No credentials found')) {
80+
resolve({ success: false, noCredentials: true });
81+
return;
82+
}
83+
84+
console.error('Auto-login failed:', result.error);
85+
resolve({ success: false });
86+
return;
87+
}
88+
89+
await userStore.refetchOrganizationTokens();
90+
resolve({ success: true });
91+
} catch (error) {
92+
console.error('Auto-login error:', error);
93+
resolve({ success: false });
94+
}
95+
},
96+
{
97+
heading: 'Enter your application password',
98+
subHeading: 'To decrypt organization credentials and reconnect',
99+
},
100+
);
101+
});
102+
103+
if (autoLoginResult.noCredentials) {
104+
console.log(`No credentials found for ${org.nickname || serverUrl}`);
105+
userStore.selectedOrganization = org;
106+
return { success: false, redirectToLogin: true };
107+
}
108+
109+
if (!autoLoginResult.success) {
110+
return { success: false };
111+
}
112+
}
113+
114+
52115
try {
53116
console.log(
54117
`[${new Date().toISOString()}] RECONNECT Starting version check for: ${org.nickname || serverUrl}`,

front-end/src/renderer/services/organizationCredentials.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,17 @@ export const tryAutoSignIn = async (user_id: string, decryptPassword: string | n
8282
decryptPassword,
8383
);
8484
}, 'Failed failed to auto sign in to organizations');
85+
86+
/* Try auto sign in to a single organization */
87+
export const tryAutoSignInOrganization = async (
88+
user_id: string,
89+
organization_id: string,
90+
decryptPassword: string | null,
91+
) =>
92+
commonIPCHandler(async () => {
93+
return await window.electronAPI.local.organizationCredentials.tryAutoSignInOrganization(
94+
user_id,
95+
organization_id,
96+
decryptPassword,
97+
);
98+
}, 'Failed to auto sign in to organization');

front-end/src/renderer/stores/storeUser.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,40 @@ const useUserStore = defineStore('user', () => {
229229
return;
230230
}
231231
}
232+
233+
const connectedOrg = await ush.getConnectedOrganization(organization, personal.value);
234+
235+
if (
236+
connectedOrg &&
237+
!connectedOrg.isLoading &&
238+
connectedOrg.isServerActive &&
239+
'loginRequired' in connectedOrg &&
240+
connectedOrg.loginRequired &&
241+
personal.value &&
242+
personal.value.isLoggedIn
243+
) {
244+
const orgServerUrl = connectedOrg.serverUrl;
245+
246+
const { reconnectOrganization } = await import('@renderer/services/organization/reconnect');
247+
const result = await reconnectOrganization(orgServerUrl);
248+
249+
if (result.redirectToLogin) {
250+
selectedOrganization.value = connectedOrg;
251+
await afterOrganizationSelection();
252+
return;
253+
}
254+
255+
if (result.success) {
256+
await refetchOrganizations();
257+
selectedOrganization.value = await ush.getConnectedOrganization(organization, personal.value);
258+
await afterOrganizationSelection();
259+
return;
260+
} else {
261+
selectedOrganization.value = connectedOrg;
262+
await afterOrganizationSelection();
263+
return;
264+
}
265+
}
232266
}
233267

234268
// Check if currently selected organization becomes disconnected
@@ -260,8 +294,8 @@ const useUserStore = defineStore('user', () => {
260294
};
261295

262296
const refetchOrganizations = async () => {
263-
await ush.updateConnectedOrganizations(organizations, personal.value);
264297
await refetchOrganizationTokens();
298+
await ush.updateConnectedOrganizations(organizations, personal.value);
265299
};
266300

267301
const deleteOrganization = async (organizationId: string) => {

0 commit comments

Comments
 (0)