mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-13 20:03:51 +02:00
903 lines
32 KiB
JavaScript
903 lines
32 KiB
JavaScript
import { reactive, ref, watch } from 'vue';
|
|
import { defineStore } from 'pinia';
|
|
import { toast } from 'vue-sonner';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
import Noty from 'noty';
|
|
|
|
import { closeWebSocket, initWebsocket } from '../service/websocket';
|
|
import { AppDebug } from '../service/appConfig';
|
|
import { authRequest } from '../api';
|
|
import { database } from '../service/database';
|
|
import { escapeTag } from '../shared/utils';
|
|
import { request } from '../service/request';
|
|
import { useAdvancedSettingsStore } from './settings/advanced';
|
|
import { useModalStore } from './modal';
|
|
import { useNotificationStore } from './notification';
|
|
import { useUpdateLoopStore } from './updateLoop';
|
|
import { useUserStore } from './user';
|
|
import { watchState } from '../service/watchState';
|
|
|
|
import configRepository from '../service/config';
|
|
import security from '../service/security';
|
|
import webApiService from '../service/webapi';
|
|
|
|
export const useAuthStore = defineStore('Auth', () => {
|
|
const advancedSettingsStore = useAdvancedSettingsStore();
|
|
const notificationStore = useNotificationStore();
|
|
const userStore = useUserStore();
|
|
const updateLoopStore = useUpdateLoopStore();
|
|
const modalStore = useModalStore();
|
|
|
|
const { t } = useI18n();
|
|
const state = reactive({
|
|
autoLoginAttempts: new Set(),
|
|
enableCustomEndpoint: false,
|
|
cachedConfig: {}
|
|
});
|
|
|
|
const loginForm = ref({
|
|
loading: false,
|
|
username: '',
|
|
password: '',
|
|
endpoint: '',
|
|
websocket: '',
|
|
saveCredentials: false,
|
|
lastUserLoggedIn: ''
|
|
});
|
|
|
|
const enablePrimaryPasswordDialog = ref({
|
|
visible: false,
|
|
password: '',
|
|
rePassword: '',
|
|
beforeClose(done) {
|
|
// $app._data.enablePrimaryPassword = false;
|
|
done();
|
|
}
|
|
});
|
|
|
|
const credentialsToSave = ref(null);
|
|
|
|
const twoFactorAuthDialogVisible = ref(false);
|
|
|
|
const cachedConfig = ref({});
|
|
|
|
const enableCustomEndpoint = ref(false);
|
|
|
|
const attemptingAutoLogin = ref(false);
|
|
|
|
watch(
|
|
[() => watchState.isLoggedIn, () => userStore.currentUser],
|
|
([isLoggedIn, currentUser]) => {
|
|
twoFactorAuthDialogVisible.value = false;
|
|
if (isLoggedIn) {
|
|
updateStoredUser(currentUser);
|
|
new Noty({
|
|
type: 'success',
|
|
text: `Hello there, <strong>${escapeTag(
|
|
currentUser.displayName
|
|
)}</strong>!`
|
|
}).show();
|
|
}
|
|
},
|
|
{ flush: 'sync' }
|
|
);
|
|
|
|
watch(
|
|
() => watchState.isFriendsLoaded,
|
|
(isFriendsLoaded) => {
|
|
if (isFriendsLoaded) {
|
|
initWebsocket();
|
|
AppApi.IPCAnnounceStart();
|
|
}
|
|
},
|
|
{ flush: 'sync' }
|
|
);
|
|
|
|
async function init() {
|
|
const [lastUserLoggedIn, enableCustomEndpoint] = await Promise.all([
|
|
configRepository.getString('lastUserLoggedIn', ''),
|
|
configRepository.getBool('VRCX_enableCustomEndpoint', false)
|
|
]);
|
|
loginForm.value.lastUserLoggedIn = lastUserLoggedIn;
|
|
state.enableCustomEndpoint = enableCustomEndpoint;
|
|
}
|
|
|
|
init();
|
|
|
|
async function getAllSavedCredentials() {
|
|
let savedCredentials = {};
|
|
try {
|
|
savedCredentials = JSON.parse(
|
|
await configRepository.getString('savedCredentials', '{}')
|
|
);
|
|
let edited = false;
|
|
for (const userId in savedCredentials) {
|
|
// fix goofy typo
|
|
if (savedCredentials[userId].loginParmas) {
|
|
savedCredentials[userId].loginParams =
|
|
savedCredentials[userId].loginParmas;
|
|
delete savedCredentials[userId].loginParmas;
|
|
edited = true;
|
|
}
|
|
// fix missing fields
|
|
if (!savedCredentials[userId].loginParams.endpoint) {
|
|
savedCredentials[userId].loginParams.endpoint = '';
|
|
edited = true;
|
|
}
|
|
if (!savedCredentials[userId].loginParams.websocket) {
|
|
savedCredentials[userId].loginParams.websocket = '';
|
|
edited = true;
|
|
}
|
|
}
|
|
if (edited) {
|
|
await configRepository.setString(
|
|
'savedCredentials',
|
|
JSON.stringify(savedCredentials)
|
|
);
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to get saved credentials:', e);
|
|
}
|
|
return savedCredentials;
|
|
}
|
|
|
|
async function getSavedCredentials(userId) {
|
|
const savedCredentials = await getAllSavedCredentials();
|
|
return savedCredentials[userId];
|
|
}
|
|
|
|
async function handleLogoutEvent() {
|
|
if (watchState.isLoggedIn) {
|
|
new Noty({
|
|
type: 'success',
|
|
text: `See you again, <strong>${escapeTag(
|
|
userStore.currentUser.displayName
|
|
)}</strong>!`
|
|
}).show();
|
|
}
|
|
userStore.userDialog.visible = false;
|
|
watchState.isLoggedIn = false;
|
|
watchState.isFriendsLoaded = false;
|
|
watchState.isFavoritesLoaded = false;
|
|
notificationStore.notificationInitStatus = false;
|
|
await updateStoredUser(userStore.currentUser);
|
|
webApiService.clearCookies();
|
|
loginForm.value.lastUserLoggedIn = '';
|
|
await configRepository.remove('lastUserLoggedIn');
|
|
// workerTimers.setTimeout(() => location.reload(), 500);
|
|
attemptingAutoLogin.value = false;
|
|
state.autoLoginAttempts.clear();
|
|
closeWebSocket();
|
|
}
|
|
|
|
/**
|
|
* Automatically logs in the last user after the app is mounted.
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function autoLoginAfterMounted() {
|
|
if (
|
|
!advancedSettingsStore.enablePrimaryPassword &&
|
|
(await configRepository.getString('lastUserLoggedIn')) !== null
|
|
) {
|
|
const user = await getSavedCredentials(
|
|
loginForm.value.lastUserLoggedIn
|
|
);
|
|
if (user?.loginParams?.endpoint) {
|
|
AppDebug.endpointDomain = user.loginParams.endpoint;
|
|
AppDebug.websocketDomain = user.loginParams.websocket;
|
|
}
|
|
// login at startup
|
|
loginForm.value.loading = true;
|
|
authRequest
|
|
.getConfig()
|
|
.catch((err) => {
|
|
loginForm.value.loading = false;
|
|
throw err;
|
|
})
|
|
.then(() => {
|
|
userStore
|
|
.getCurrentUser()
|
|
.finally(() => {
|
|
loginForm.value.loading = false;
|
|
})
|
|
.catch((err) => {
|
|
updateLoopStore.nextCurrentUserRefresh = 60; // 1min
|
|
console.error(err);
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
async function clearCookiesTryLogin() {
|
|
await webApiService.clearCookies();
|
|
if (loginForm.value.lastUserLoggedIn) {
|
|
const user = await getSavedCredentials(
|
|
loginForm.value.lastUserLoggedIn
|
|
);
|
|
if (user) {
|
|
delete user.cookies;
|
|
await relogin(user);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function resendEmail2fa() {
|
|
if (loginForm.value.lastUserLoggedIn) {
|
|
const user = await getSavedCredentials(
|
|
loginForm.value.lastUserLoggedIn
|
|
);
|
|
if (user) {
|
|
await webApiService.clearCookies();
|
|
delete user.cookies;
|
|
relogin(user).then(() => {
|
|
new Noty({
|
|
type: 'success',
|
|
text: 'Email 2FA resent.'
|
|
}).show();
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
new Noty({
|
|
type: 'error',
|
|
text: 'Cannot send 2FA email without saved credentials. Please login again.'
|
|
}).show();
|
|
}
|
|
|
|
function enablePrimaryPasswordChange() {
|
|
advancedSettingsStore.enablePrimaryPassword =
|
|
!advancedSettingsStore.enablePrimaryPassword;
|
|
|
|
enablePrimaryPasswordDialog.value.password = '';
|
|
enablePrimaryPasswordDialog.value.rePassword = '';
|
|
if (advancedSettingsStore.enablePrimaryPassword) {
|
|
enablePrimaryPasswordDialog.value.visible = true;
|
|
} else {
|
|
modalStore
|
|
.prompt({
|
|
title: t('prompt.primary_password.header'),
|
|
description: t('prompt.primary_password.description'),
|
|
pattern: /[\s\S]{1,32}/
|
|
})
|
|
.then(async ({ ok, value }) => {
|
|
if (!ok) {
|
|
advancedSettingsStore.enablePrimaryPassword = true;
|
|
advancedSettingsStore.setEnablePrimaryPasswordConfigRepository(
|
|
true
|
|
);
|
|
return;
|
|
}
|
|
|
|
const savedCredentials = JSON.parse(
|
|
await configRepository.getString(
|
|
'savedCredentials',
|
|
'{}'
|
|
)
|
|
);
|
|
for (const userId in savedCredentials) {
|
|
security
|
|
.decrypt(
|
|
savedCredentials[userId].loginParams.password,
|
|
value
|
|
)
|
|
.then(async (pt) => {
|
|
credentialsToSave.value = {
|
|
username:
|
|
savedCredentials[userId].loginParams
|
|
.username,
|
|
password: pt
|
|
};
|
|
await updateStoredUser(
|
|
savedCredentials[userId].user
|
|
);
|
|
await configRepository.setBool(
|
|
'enablePrimaryPassword',
|
|
false
|
|
);
|
|
})
|
|
.catch(async () => {
|
|
advancedSettingsStore.enablePrimaryPassword = true;
|
|
advancedSettingsStore.setEnablePrimaryPasswordConfigRepository(
|
|
true
|
|
);
|
|
});
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
advancedSettingsStore.enablePrimaryPassword = true;
|
|
advancedSettingsStore.setEnablePrimaryPasswordConfigRepository(
|
|
true
|
|
);
|
|
});
|
|
}
|
|
}
|
|
async function setPrimaryPassword() {
|
|
await configRepository.setBool(
|
|
'enablePrimaryPassword',
|
|
advancedSettingsStore.enablePrimaryPassword
|
|
);
|
|
enablePrimaryPasswordDialog.value.visible = false;
|
|
if (advancedSettingsStore.enablePrimaryPassword) {
|
|
const key = enablePrimaryPasswordDialog.value.password;
|
|
const savedCredentials = await getAllSavedCredentials();
|
|
for (const userId in savedCredentials) {
|
|
security
|
|
.encrypt(savedCredentials[userId].loginParams.password, key)
|
|
.then((ct) => {
|
|
credentialsToSave.value = {
|
|
username:
|
|
savedCredentials[userId].loginParams.username,
|
|
password: ct
|
|
};
|
|
updateStoredUser(savedCredentials[userId].user);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
async function updateStoredUser(user) {
|
|
const savedCredentials = await getAllSavedCredentials();
|
|
if (credentialsToSave.value) {
|
|
savedCredentials[user.id] = {
|
|
user,
|
|
loginParams: {
|
|
username: '',
|
|
password: '',
|
|
endpoint: '',
|
|
websocket: '',
|
|
...credentialsToSave.value
|
|
}
|
|
};
|
|
credentialsToSave.value = null;
|
|
} else if (typeof savedCredentials[user.id] !== 'undefined') {
|
|
savedCredentials[user.id].user = user;
|
|
savedCredentials[user.id].cookies =
|
|
await webApiService.getCookies();
|
|
}
|
|
const jsonCredentialsArray = JSON.stringify(savedCredentials);
|
|
await configRepository.setString(
|
|
'savedCredentials',
|
|
jsonCredentialsArray
|
|
);
|
|
loginForm.value.lastUserLoggedIn = user.id;
|
|
await configRepository.setString('lastUserLoggedIn', user.id);
|
|
}
|
|
|
|
async function migrateStoredUsers() {
|
|
const savedCredentials = await getAllSavedCredentials();
|
|
for (const name in savedCredentials) {
|
|
const userId = savedCredentials[name]?.user?.id;
|
|
if (userId && userId !== name) {
|
|
savedCredentials[userId] = savedCredentials[name];
|
|
delete savedCredentials[name];
|
|
}
|
|
}
|
|
await configRepository.setString(
|
|
'savedCredentials',
|
|
JSON.stringify(savedCredentials)
|
|
);
|
|
}
|
|
|
|
function checkPrimaryPassword(args) {
|
|
return new Promise((resolve, reject) => {
|
|
if (!advancedSettingsStore.enablePrimaryPassword) {
|
|
resolve(args.password);
|
|
return;
|
|
}
|
|
modalStore
|
|
.prompt({
|
|
title: t('prompt.primary_password.header'),
|
|
description: t('prompt.primary_password.description'),
|
|
pattern: /[\s\S]{1,32}/
|
|
})
|
|
.then(({ ok, value }) => {
|
|
if (!ok) {
|
|
reject(new Error('primary password prompt cancelled'));
|
|
return;
|
|
}
|
|
security
|
|
.decrypt(args.password, value)
|
|
.then(resolve)
|
|
.catch(reject);
|
|
})
|
|
.catch(reject);
|
|
});
|
|
}
|
|
|
|
async function toggleCustomEndpoint() {
|
|
await configRepository.setBool(
|
|
'VRCX_enableCustomEndpoint',
|
|
state.enableCustomEndpoint
|
|
);
|
|
loginForm.value.endpoint = '';
|
|
loginForm.value.websocket = '';
|
|
}
|
|
|
|
function logout() {
|
|
modalStore
|
|
.confirm({
|
|
description: 'Continue? Logout',
|
|
title: 'Confirm'
|
|
})
|
|
.then(({ ok }) => {
|
|
if (!ok) return;
|
|
const existingStyle = document.getElementById(
|
|
'login-container-style'
|
|
);
|
|
if (existingStyle) {
|
|
existingStyle.parentNode.removeChild(existingStyle);
|
|
}
|
|
handleLogoutEvent();
|
|
})
|
|
.catch(() => {});
|
|
}
|
|
|
|
async function relogin(user) {
|
|
const { loginParams } = user;
|
|
if (user.cookies) {
|
|
await webApiService.setCookies(user.cookies);
|
|
}
|
|
loginForm.value.lastUserLoggedIn = user.user.id; // for resend email 2fa
|
|
if (loginParams.endpoint) {
|
|
AppDebug.endpointDomain = loginParams.endpoint;
|
|
AppDebug.websocketDomain = loginParams.websocket;
|
|
} else {
|
|
AppDebug.endpointDomain = AppDebug.endpointDomainVrchat;
|
|
AppDebug.websocketDomain = AppDebug.websocketDomainVrchat;
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
loginForm.value.loading = true;
|
|
if (advancedSettingsStore.enablePrimaryPassword) {
|
|
checkPrimaryPassword(loginParams)
|
|
.then((pwd) => {
|
|
return authRequest
|
|
.getConfig()
|
|
.catch((err) => {
|
|
reject(err);
|
|
})
|
|
.then(() => {
|
|
authLogin({
|
|
username: loginParams.username,
|
|
password: pwd,
|
|
cipher: loginParams.password,
|
|
endpoint: loginParams.endpoint,
|
|
websocket: loginParams.websocket
|
|
})
|
|
.catch((err2) => {
|
|
reject(err2);
|
|
})
|
|
.then(() => {
|
|
resolve();
|
|
});
|
|
});
|
|
})
|
|
.catch((_) => {
|
|
toast.error('Incorrect primary password');
|
|
reject(_);
|
|
});
|
|
} else {
|
|
authRequest
|
|
.getConfig()
|
|
.catch((err) => {
|
|
reject(err);
|
|
})
|
|
.then(() => {
|
|
authLogin({
|
|
username: loginParams.username,
|
|
password: loginParams.password,
|
|
endpoint: loginParams.endpoint,
|
|
websocket: loginParams.websocket
|
|
})
|
|
.catch((err2) => {
|
|
handleLogoutEvent();
|
|
reject(err2);
|
|
})
|
|
.then(() => {
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
}).finally(() => (loginForm.value.loading = false));
|
|
}
|
|
|
|
async function deleteSavedLogin(userId) {
|
|
const savedCredentials = await getAllSavedCredentials();
|
|
delete savedCredentials[userId];
|
|
// Disable primary password when no account is available.
|
|
if (Object.keys(savedCredentials).length === 0) {
|
|
advancedSettingsStore.enablePrimaryPassword = false;
|
|
advancedSettingsStore.setEnablePrimaryPasswordConfigRepository(
|
|
false
|
|
);
|
|
}
|
|
await configRepository.setString(
|
|
'savedCredentials',
|
|
JSON.stringify(savedCredentials)
|
|
);
|
|
new Noty({
|
|
type: 'success',
|
|
text: 'Account removed.'
|
|
}).show();
|
|
}
|
|
|
|
async function login() {
|
|
// TODO: remove/refactor saveCredentials & primaryPassword (security)
|
|
await webApiService.clearCookies();
|
|
if (!loginForm.value.loading) {
|
|
loginForm.value.loading = true;
|
|
if (loginForm.value.endpoint) {
|
|
AppDebug.endpointDomain = loginForm.value.endpoint;
|
|
AppDebug.websocketDomain = loginForm.value.websocket;
|
|
} else {
|
|
AppDebug.endpointDomain = AppDebug.endpointDomainVrchat;
|
|
AppDebug.websocketDomain = AppDebug.websocketDomainVrchat;
|
|
}
|
|
authRequest
|
|
.getConfig()
|
|
.catch((err) => {
|
|
loginForm.value.loading = false;
|
|
throw err;
|
|
})
|
|
.then((args) => {
|
|
if (
|
|
loginForm.value.saveCredentials &&
|
|
advancedSettingsStore.enablePrimaryPassword
|
|
) {
|
|
modalStore
|
|
.prompt({
|
|
title: t('prompt.primary_password.header'),
|
|
description: t(
|
|
'prompt.primary_password.description'
|
|
),
|
|
pattern: /[\s\S]{1,32}/
|
|
})
|
|
.then(async ({ ok, value }) => {
|
|
if (!ok) return;
|
|
const savedCredentials = JSON.parse(
|
|
await configRepository.getString(
|
|
'savedCredentials'
|
|
)
|
|
);
|
|
const saveCredential =
|
|
savedCredentials[
|
|
Object.keys(savedCredentials)[0]
|
|
];
|
|
security
|
|
.decrypt(
|
|
saveCredential.loginParams.password,
|
|
value
|
|
)
|
|
.then(() => {
|
|
security
|
|
.encrypt(
|
|
loginForm.value.password,
|
|
value
|
|
)
|
|
.then((pwd) => {
|
|
authLogin({
|
|
username:
|
|
loginForm.value
|
|
.username,
|
|
password:
|
|
loginForm.value
|
|
.password,
|
|
endpoint:
|
|
loginForm.value
|
|
.endpoint,
|
|
websocket:
|
|
loginForm.value
|
|
.websocket,
|
|
saveCredentials:
|
|
loginForm.value
|
|
.saveCredentials,
|
|
cipher: pwd
|
|
});
|
|
});
|
|
});
|
|
})
|
|
.finally(() => {
|
|
loginForm.value.loading = false;
|
|
})
|
|
.catch(() => {});
|
|
return args;
|
|
}
|
|
authLogin({
|
|
username: loginForm.value.username,
|
|
password: loginForm.value.password,
|
|
endpoint: loginForm.value.endpoint,
|
|
websocket: loginForm.value.websocket,
|
|
saveCredentials: loginForm.value.saveCredentials
|
|
}).finally(() => {
|
|
loginForm.value.loading = false;
|
|
});
|
|
return args;
|
|
});
|
|
}
|
|
}
|
|
|
|
function promptTOTP() {
|
|
if (twoFactorAuthDialogVisible.value) {
|
|
return;
|
|
}
|
|
AppApi.FlashWindow();
|
|
twoFactorAuthDialogVisible.value = true;
|
|
modalStore
|
|
.prompt({
|
|
title: t('prompt.totp.header'),
|
|
description: t('prompt.totp.description'),
|
|
cancelText: t('prompt.totp.use_otp'),
|
|
confirmText: t('prompt.totp.verify'),
|
|
pattern: /^[0-9]{6}$/,
|
|
errorMessage: t('prompt.totp.input_error')
|
|
})
|
|
.then(({ ok, reason, value }) => {
|
|
twoFactorAuthDialogVisible.value = false;
|
|
|
|
if (reason === 'cancel') {
|
|
promptOTP();
|
|
return;
|
|
}
|
|
if (!ok) return;
|
|
|
|
authRequest
|
|
.verifyTOTP({
|
|
code: value.trim()
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
clearCookiesTryLogin();
|
|
})
|
|
.then(() => {
|
|
userStore.getCurrentUser();
|
|
});
|
|
})
|
|
.catch(() => {
|
|
twoFactorAuthDialogVisible.value = false;
|
|
});
|
|
}
|
|
|
|
function promptOTP() {
|
|
if (twoFactorAuthDialogVisible.value) {
|
|
return;
|
|
}
|
|
twoFactorAuthDialogVisible.value = true;
|
|
modalStore
|
|
.prompt({
|
|
title: t('prompt.otp.header'),
|
|
description: t('prompt.otp.description'),
|
|
cancelText: t('prompt.otp.use_totp'),
|
|
confirmText: t('prompt.otp.verify'),
|
|
pattern: /^[a-z0-9]{4}-[a-z0-9]{4}$/,
|
|
errorMessage: t('prompt.otp.input_error')
|
|
})
|
|
.then(({ ok, reason, value }) => {
|
|
twoFactorAuthDialogVisible.value = false;
|
|
|
|
if (reason === 'cancel') {
|
|
promptTOTP();
|
|
return;
|
|
}
|
|
if (!ok) return;
|
|
|
|
authRequest
|
|
.verifyOTP({
|
|
code: value.trim()
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
clearCookiesTryLogin();
|
|
})
|
|
.then(() => {
|
|
userStore.getCurrentUser();
|
|
});
|
|
})
|
|
.catch(() => {
|
|
twoFactorAuthDialogVisible.value = false;
|
|
});
|
|
}
|
|
|
|
function promptEmailOTP() {
|
|
if (twoFactorAuthDialogVisible.value) {
|
|
return;
|
|
}
|
|
AppApi.FlashWindow();
|
|
twoFactorAuthDialogVisible.value = true;
|
|
modalStore
|
|
.prompt({
|
|
title: t('prompt.email_otp.header'),
|
|
description: t('prompt.email_otp.description'),
|
|
cancelText: t('prompt.email_otp.resend'),
|
|
confirmText: t('prompt.email_otp.verify'),
|
|
pattern: /^[0-9]{6}$/,
|
|
errorMessage: t('prompt.email_otp.input_error')
|
|
})
|
|
.then(({ ok, reason, value }) => {
|
|
twoFactorAuthDialogVisible.value = false;
|
|
|
|
if (reason === 'cancel') {
|
|
resendEmail2fa();
|
|
return;
|
|
}
|
|
if (!ok) return;
|
|
|
|
authRequest
|
|
.verifyEmailOTP({
|
|
code: value.trim()
|
|
})
|
|
.catch((err) => {
|
|
console.error(err);
|
|
promptEmailOTP();
|
|
})
|
|
.then(() => {
|
|
userStore.getCurrentUser();
|
|
});
|
|
})
|
|
.catch(() => {
|
|
twoFactorAuthDialogVisible.value = false;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {{ username: string, password: string, endpoint: string, websocket: string, saveCredentials?: any, cipher?: string }} params credential to login
|
|
* @returns {Promise<{origin: boolean, json: any}>}
|
|
*/
|
|
function authLogin(params) {
|
|
let {
|
|
username,
|
|
password,
|
|
endpoint,
|
|
websocket,
|
|
saveCredentials,
|
|
cipher
|
|
} = params;
|
|
const auth = btoa(
|
|
`${encodeURIComponent(username)}:${encodeURIComponent(password)}`
|
|
);
|
|
if (saveCredentials) {
|
|
params.saveCredentials = false;
|
|
if (cipher) {
|
|
password = cipher;
|
|
}
|
|
credentialsToSave.value = {
|
|
username,
|
|
password,
|
|
endpoint,
|
|
websocket
|
|
};
|
|
}
|
|
return request('auth/user', {
|
|
method: 'GET',
|
|
headers: {
|
|
Authorization: `Basic ${auth}`
|
|
}
|
|
}).then((json) => {
|
|
const args = {
|
|
json,
|
|
origin: true
|
|
};
|
|
handleCurrentUserUpdate(json);
|
|
return args;
|
|
});
|
|
}
|
|
|
|
function handleCurrentUserUpdate(json) {
|
|
if (
|
|
json.requiresTwoFactorAuth &&
|
|
json.requiresTwoFactorAuth.includes('emailOtp')
|
|
) {
|
|
promptEmailOTP();
|
|
} else if (json.requiresTwoFactorAuth) {
|
|
promptTOTP();
|
|
} else {
|
|
updateLoopStore.nextCurrentUserRefresh = 420; // 7mins
|
|
userStore.applyCurrentUser(json);
|
|
initWebsocket();
|
|
}
|
|
}
|
|
|
|
async function handleAutoLogin() {
|
|
if (attemptingAutoLogin.value) {
|
|
return;
|
|
}
|
|
attemptingAutoLogin.value = true;
|
|
const user = await getSavedCredentials(
|
|
loginForm.value.lastUserLoggedIn
|
|
);
|
|
if (!user) {
|
|
attemptingAutoLogin.value = false;
|
|
return;
|
|
}
|
|
if (advancedSettingsStore.enablePrimaryPassword) {
|
|
console.error(
|
|
'Primary password is enabled, this disables auto login.'
|
|
);
|
|
attemptingAutoLogin.value = false;
|
|
handleLogoutEvent();
|
|
return;
|
|
}
|
|
const attemptsInLastHour = Array.from(state.autoLoginAttempts).filter(
|
|
(timestamp) => timestamp > new Date().getTime() - 3600000
|
|
).length;
|
|
if (attemptsInLastHour >= 3) {
|
|
console.error(
|
|
'More than 3 auto login attempts within the past hour, logging out instead of attempting auto login.'
|
|
);
|
|
attemptingAutoLogin.value = false;
|
|
handleLogoutEvent();
|
|
AppApi.FlashWindow();
|
|
return;
|
|
}
|
|
state.autoLoginAttempts.add(new Date().getTime());
|
|
console.log('Attempting automatic login...');
|
|
relogin(user)
|
|
.then(() => {
|
|
if (AppDebug.errorNoty) {
|
|
AppDebug.errorNoty.close();
|
|
}
|
|
AppDebug.errorNoty = new Noty({
|
|
type: 'success',
|
|
text: 'Automatically logged in.'
|
|
}).show();
|
|
console.log('Automatically logged in.');
|
|
})
|
|
.catch((err) => {
|
|
if (AppDebug.errorNoty) {
|
|
AppDebug.errorNoty.close();
|
|
}
|
|
AppDebug.errorNoty = new Noty({
|
|
type: 'error',
|
|
text: 'Failed to login automatically.'
|
|
}).show();
|
|
console.error('Failed to login automatically.', err);
|
|
})
|
|
.finally(() => {
|
|
if (!navigator.onLine) {
|
|
AppDebug.errorNoty = new Noty({
|
|
type: 'error',
|
|
text: `You're offline.`
|
|
}).show();
|
|
console.error(`You're offline.`);
|
|
}
|
|
});
|
|
}
|
|
|
|
async function loginComplete() {
|
|
await database.initUserTables(userStore.currentUser.id);
|
|
watchState.isLoggedIn = true;
|
|
AppApi.CheckGameRunning(); // restore state from hot-reload
|
|
}
|
|
|
|
return {
|
|
state,
|
|
|
|
loginForm,
|
|
enablePrimaryPasswordDialog,
|
|
credentialsToSave,
|
|
twoFactorAuthDialogVisible,
|
|
cachedConfig,
|
|
enableCustomEndpoint,
|
|
attemptingAutoLogin,
|
|
|
|
clearCookiesTryLogin,
|
|
resendEmail2fa,
|
|
enablePrimaryPasswordChange,
|
|
setPrimaryPassword,
|
|
updateStoredUser,
|
|
migrateStoredUsers,
|
|
checkPrimaryPassword,
|
|
autoLoginAfterMounted,
|
|
toggleCustomEndpoint,
|
|
logout,
|
|
relogin,
|
|
deleteSavedLogin,
|
|
login,
|
|
handleAutoLogin,
|
|
handleLogoutEvent,
|
|
handleCurrentUserUpdate,
|
|
loginComplete,
|
|
getAllSavedCredentials
|
|
};
|
|
});
|