mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-27 18:53:47 +02:00
refactor: app.js (#1291)
* refactor: frontend * Fix avatar gallery sort * Update .NET dependencies * Update npm dependencies electron v37.1.0 * bulkRefreshFriends * fix dark theme * Remove crowdin * Fix config.json dialog not updating * VRCX log file fixes & add Cef log * Remove SharedVariable, fix startup * Revert init theme change * Logging date not working? Fix WinformThemer designer error * Add Cef request hander, no more escaping main page * clean * fix * fix * clean * uh * Apply thememode at startup, fixes random user colours * Split database into files * Instance info remove empty lines * Open external VRC links with VRCX * Electron fixes * fix userdialog style * ohhhh * fix store * fix store * fix: load all group members after kicking a user * fix: world dialog favorite button style * fix: Clear VRCX Cache Timer input value * clean * Fix VR overlay * Fix VR overlay 2 * Fix Discord discord rich presence for RPC worlds * Clean up age verified user tags * Fix playerList being occupied after program reload * no `this` * Fix login stuck loading * writable: false * Hide dialogs on logout * add flush sync option * rm LOGIN event * rm LOGOUT event * remove duplicate event listeners * remove duplicate event listeners * clean * remove duplicate event listeners * clean * fix theme style * fix t * clearable * clean * fix ipcEvent * Small changes * Popcorn Palace support * Remove checkActiveFriends * Clean up * Fix dragEnterCef * Block API requests when not logged in * Clear state on login & logout * Fix worldDialog instances not updating * use <script setup> * Fix avatar change event, CheckGameRunning at startup * Fix image dragging * fix * Remove PWI * fix updateLoop * add webpack-dev-server to dev environment * rm unnecessary chunks * use <script setup> * webpack-dev-server changes * use <script setup> * use <script setup> * Fix UGC text size * Split login event * t * use <script setup> * fix * Update .gitignore and enable checkJs in jsconfig * fix i18n t * use <script setup> * use <script setup> * clean * global types * fix * use checkJs for debugging * Add watchState for login watchers * fix .vue template * type fixes * rm Vue.filter * Cef v138.0.170, VC++ 2022 * Settings fixes * Remove 'USER:CURRENT' * clean up 2FA callbacks * remove userApply * rm i18n import * notification handling to use notification store methods * refactor favorite handling to use favorite store methods and clean up event emissions * refactor moderation handling to use dedicated functions for player moderation events * refactor friend handling to use dedicated functions for friend events * Fix program startup, move lang init * Fix friend state * Fix status change error * Fix user notes diff * fix * rm group event * rm auth event * rm avatar event * clean * clean * getUser * getFriends * getFavoriteWorlds, getFavoriteAvatars * AvatarGalleryUpload btn style & package.json update * Fix friend requests * Apply user * Apply world * Fix note diff * Fix VR overlay * Fixes * Update build scripts * Apply avatar * Apply instance * Apply group * update hidden VRC+ badge * Fix sameInstance "private" * fix 502/504 API errors * fix 502/504 API errors * clean * Fix friend in same instance on orange showing twice in friends list * Add back in broken friend state repair methods * add types --------- Co-authored-by: Natsumi <cmcooper123@hotmail.com>
This commit is contained in:
882
src/stores/auth.js
Normal file
882
src/stores/auth.js
Normal file
@@ -0,0 +1,882 @@
|
||||
import Noty from 'noty';
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, watch } from 'vue';
|
||||
import { authRequest } from '../api';
|
||||
import { $app } from '../app';
|
||||
import { useI18n } from 'vue-i18n-bridge';
|
||||
import configRepository from '../service/config';
|
||||
import { database } from '../service/database';
|
||||
import { AppGlobal } from '../service/appConfig';
|
||||
import { request } from '../service/request';
|
||||
import security from '../service/security';
|
||||
import webApiService from '../service/webapi';
|
||||
import { closeWebSocket, initWebsocket } from '../service/websocket';
|
||||
import { watchState } from '../service/watchState';
|
||||
import { escapeTag } from '../shared/utils';
|
||||
import { useNotificationStore } from './notification';
|
||||
import { useAdvancedSettingsStore } from './settings/advanced';
|
||||
import { useUpdateLoopStore } from './updateLoop';
|
||||
import { useUserStore } from './user';
|
||||
|
||||
export const useAuthStore = defineStore('Auth', () => {
|
||||
const advancedSettingsStore = useAdvancedSettingsStore();
|
||||
const notificationStore = useNotificationStore();
|
||||
const userStore = useUserStore();
|
||||
const updateLoopStore = useUpdateLoopStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const state = reactive({
|
||||
attemptingAutoLogin: false,
|
||||
autoLoginAttempts: new Set(),
|
||||
loginForm: {
|
||||
loading: false,
|
||||
username: '',
|
||||
password: '',
|
||||
endpoint: '',
|
||||
websocket: '',
|
||||
saveCredentials: false,
|
||||
savedCredentials: {},
|
||||
lastUserLoggedIn: '',
|
||||
rules: {
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
enablePrimaryPasswordDialog: {
|
||||
visible: false,
|
||||
password: '',
|
||||
rePassword: '',
|
||||
beforeClose(done) {
|
||||
$app._data.enablePrimaryPassword = false;
|
||||
done();
|
||||
}
|
||||
},
|
||||
saveCredentials: null,
|
||||
// it's a flag
|
||||
twoFactorAuthDialogVisible: false,
|
||||
enableCustomEndpoint: false,
|
||||
cachedConfig: {}
|
||||
});
|
||||
|
||||
async function init() {
|
||||
const [savedCredentials, lastUserLoggedIn, enableCustomEndpoint] =
|
||||
await Promise.all([
|
||||
configRepository.getString('savedCredentials'),
|
||||
configRepository.getString('lastUserLoggedIn'),
|
||||
configRepository.getBool('VRCX_enableCustomEndpoint', false)
|
||||
]);
|
||||
state.loginForm = {
|
||||
...state.loginForm,
|
||||
savedCredentials: savedCredentials
|
||||
? JSON.parse(savedCredentials)
|
||||
: {},
|
||||
lastUserLoggedIn
|
||||
};
|
||||
state.enableCustomEndpoint = enableCustomEndpoint;
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
const loginForm = computed({
|
||||
get: () => state.loginForm,
|
||||
set: (value) => {
|
||||
state.loginForm = value;
|
||||
}
|
||||
});
|
||||
|
||||
const enablePrimaryPasswordDialog = computed({
|
||||
get: () => state.enablePrimaryPasswordDialog,
|
||||
set: (value) => {
|
||||
state.enablePrimaryPasswordDialog = value;
|
||||
}
|
||||
});
|
||||
|
||||
const saveCredentials = computed({
|
||||
get: () => state.saveCredentials,
|
||||
set: (value) => {
|
||||
state.saveCredentials = value;
|
||||
}
|
||||
});
|
||||
|
||||
const twoFactorAuthDialogVisible = computed({
|
||||
get: () => state.twoFactorAuthDialogVisible,
|
||||
set: (value) => {
|
||||
state.twoFactorAuthDialogVisible = value;
|
||||
}
|
||||
});
|
||||
|
||||
const cachedConfig = computed({
|
||||
get: () => state.cachedConfig,
|
||||
set: (value) => {
|
||||
state.cachedConfig = value;
|
||||
}
|
||||
});
|
||||
|
||||
const enableCustomEndpoint = computed({
|
||||
get: () => state.enableCustomEndpoint,
|
||||
set: (value) => {
|
||||
state.enableCustomEndpoint = value;
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
[() => watchState.isLoggedIn, () => userStore.currentUser],
|
||||
([isLoggedIn, currentUser]) => {
|
||||
state.twoFactorAuthDialogVisible = 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 handleLogoutEvent() {
|
||||
if (watchState.isLoggedIn) {
|
||||
new Noty({
|
||||
type: 'success',
|
||||
text: `See you again, <strong>${escapeTag(
|
||||
userStore.currentUser.displayName
|
||||
)}</strong>!`
|
||||
}).show();
|
||||
}
|
||||
watchState.isLoggedIn = false;
|
||||
watchState.isFriendsLoaded = false;
|
||||
notificationStore.notificationInitStatus = false;
|
||||
await updateStoredUser(userStore.currentUser);
|
||||
webApiService.clearCookies();
|
||||
state.loginForm.lastUserLoggedIn = '';
|
||||
await configRepository.remove('lastUserLoggedIn');
|
||||
// workerTimers.setTimeout(() => location.reload(), 500);
|
||||
state.attemptingAutoLogin = 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 =
|
||||
state.loginForm.savedCredentials[
|
||||
state.loginForm.lastUserLoggedIn
|
||||
];
|
||||
if (user?.loginParmas?.endpoint) {
|
||||
AppGlobal.endpointDomain = user.loginParmas.endpoint;
|
||||
AppGlobal.websocketDomain = user.loginParmas.websocket;
|
||||
}
|
||||
// login at startup
|
||||
state.loginForm.loading = true;
|
||||
authRequest
|
||||
.getConfig()
|
||||
.catch((err) => {
|
||||
state.loginForm.loading = false;
|
||||
throw err;
|
||||
})
|
||||
.then(() => {
|
||||
userStore
|
||||
.getCurrentUser()
|
||||
.finally(() => {
|
||||
state.loginForm.loading = false;
|
||||
})
|
||||
.catch((err) => {
|
||||
updateLoopStore.nextCurrentUserRefresh = 60; // 1min
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function clearCookiesTryLogin() {
|
||||
await webApiService.clearCookies();
|
||||
if (state.loginForm.lastUserLoggedIn) {
|
||||
const user =
|
||||
state.loginForm.savedCredentials[
|
||||
state.loginForm.lastUserLoggedIn
|
||||
];
|
||||
if (typeof user !== 'undefined') {
|
||||
delete user.cookies;
|
||||
await relogin(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function resendEmail2fa() {
|
||||
if (state.loginForm.lastUserLoggedIn) {
|
||||
const user =
|
||||
state.loginForm.savedCredentials[
|
||||
state.loginForm.lastUserLoggedIn
|
||||
];
|
||||
if (typeof user !== 'undefined') {
|
||||
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;
|
||||
|
||||
state.enablePrimaryPasswordDialog.password = '';
|
||||
state.enablePrimaryPasswordDialog.rePassword = '';
|
||||
if (advancedSettingsStore.enablePrimaryPassword) {
|
||||
state.enablePrimaryPasswordDialog.visible = true;
|
||||
} else {
|
||||
$app.$prompt(
|
||||
t('prompt.primary_password.description'),
|
||||
t('prompt.primary_password.header'),
|
||||
{
|
||||
inputType: 'password',
|
||||
inputPattern: /[\s\S]{1,32}/
|
||||
}
|
||||
)
|
||||
.then(({ value }) => {
|
||||
for (const userId in state.loginForm.savedCredentials) {
|
||||
security
|
||||
.decrypt(
|
||||
state.loginForm.savedCredentials[userId]
|
||||
.loginParmas.password,
|
||||
value
|
||||
)
|
||||
.then(async (pt) => {
|
||||
state.saveCredentials = {
|
||||
username:
|
||||
state.loginForm.savedCredentials[userId]
|
||||
.loginParmas.username,
|
||||
password: pt
|
||||
};
|
||||
await updateStoredUser(
|
||||
state.loginForm.savedCredentials[userId]
|
||||
.user
|
||||
);
|
||||
await configRepository.setBool(
|
||||
'enablePrimaryPassword',
|
||||
false
|
||||
);
|
||||
})
|
||||
.catch(async () => {
|
||||
advancedSettingsStore.enablePrimaryPassword = true;
|
||||
advancedSettingsStore.setEnablePrimaryPasswordConfigRepository(
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(async () => {
|
||||
advancedSettingsStore.enablePrimaryPassword = true;
|
||||
advancedSettingsStore.setEnablePrimaryPasswordConfigRepository(
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
async function setPrimaryPassword() {
|
||||
await configRepository.setBool(
|
||||
'enablePrimaryPassword',
|
||||
advancedSettingsStore.enablePrimaryPassword
|
||||
);
|
||||
state.enablePrimaryPasswordDialog.visible = false;
|
||||
if (advancedSettingsStore.enablePrimaryPassword) {
|
||||
const key = state.enablePrimaryPasswordDialog.password;
|
||||
for (const userId in state.loginForm.savedCredentials) {
|
||||
security
|
||||
.encrypt(
|
||||
state.loginForm.savedCredentials[userId].loginParmas
|
||||
.password,
|
||||
key
|
||||
)
|
||||
.then((ct) => {
|
||||
state.saveCredentials = {
|
||||
username:
|
||||
state.loginForm.savedCredentials[userId]
|
||||
.loginParmas.username,
|
||||
password: ct
|
||||
};
|
||||
updateStoredUser(
|
||||
state.loginForm.savedCredentials[userId].user
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateStoredUser(user) {
|
||||
let savedCredentials = {};
|
||||
if ((await configRepository.getString('savedCredentials')) !== null) {
|
||||
savedCredentials = JSON.parse(
|
||||
await configRepository.getString('savedCredentials')
|
||||
);
|
||||
}
|
||||
if (state.saveCredentials) {
|
||||
const credentialsToSave = {
|
||||
user,
|
||||
loginParmas: state.saveCredentials
|
||||
};
|
||||
savedCredentials[user.id] = credentialsToSave;
|
||||
state.saveCredentials = null;
|
||||
} else if (typeof savedCredentials[user.id] !== 'undefined') {
|
||||
savedCredentials[user.id].user = user;
|
||||
savedCredentials[user.id].cookies =
|
||||
await webApiService.getCookies();
|
||||
}
|
||||
state.loginForm.savedCredentials = savedCredentials;
|
||||
const jsonCredentialsArray = JSON.stringify(savedCredentials);
|
||||
await configRepository.setString(
|
||||
'savedCredentials',
|
||||
jsonCredentialsArray
|
||||
);
|
||||
state.loginForm.lastUserLoggedIn = user.id;
|
||||
await configRepository.setString('lastUserLoggedIn', user.id);
|
||||
}
|
||||
|
||||
async function migrateStoredUsers() {
|
||||
let savedCredentials = {};
|
||||
if ((await configRepository.getString('savedCredentials')) !== null) {
|
||||
savedCredentials = JSON.parse(
|
||||
await configRepository.getString('savedCredentials')
|
||||
);
|
||||
}
|
||||
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);
|
||||
}
|
||||
$app.$prompt(
|
||||
t('prompt.primary_password.description'),
|
||||
t('prompt.primary_password.header'),
|
||||
{
|
||||
inputType: 'password',
|
||||
inputPattern: /[\s\S]{1,32}/
|
||||
}
|
||||
)
|
||||
.then(({ value }) => {
|
||||
security
|
||||
.decrypt(args.password, value)
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleCustomEndpoint() {
|
||||
await configRepository.setBool(
|
||||
'VRCX_enableCustomEndpoint',
|
||||
state.enableCustomEndpoint
|
||||
);
|
||||
state.loginForm.endpoint = '';
|
||||
state.loginForm.websocket = '';
|
||||
}
|
||||
|
||||
function logout() {
|
||||
$app.$confirm('Continue? Logout', 'Confirm', {
|
||||
confirmButtonText: 'Confirm',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'info',
|
||||
callback: (action) => {
|
||||
if (action === 'confirm') {
|
||||
handleLogoutEvent();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function relogin(user) {
|
||||
const { loginParmas } = user;
|
||||
if (user.cookies) {
|
||||
await webApiService.setCookies(user.cookies);
|
||||
}
|
||||
state.loginForm.lastUserLoggedIn = user.user.id; // for resend email 2fa
|
||||
if (loginParmas.endpoint) {
|
||||
AppGlobal.endpointDomain = loginParmas.endpoint;
|
||||
AppGlobal.websocketDomain = loginParmas.websocket;
|
||||
} else {
|
||||
AppGlobal.endpointDomain = AppGlobal.endpointDomainVrchat;
|
||||
AppGlobal.websocketDomain = AppGlobal.websocketDomainVrchat;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
state.loginForm.loading = true;
|
||||
if (advancedSettingsStore.enablePrimaryPassword) {
|
||||
checkPrimaryPassword(loginParmas)
|
||||
.then((pwd) => {
|
||||
return authRequest
|
||||
.getConfig()
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
})
|
||||
.then(() => {
|
||||
authLogin({
|
||||
username: loginParmas.username,
|
||||
password: pwd,
|
||||
cipher: loginParmas.password,
|
||||
endpoint: loginParmas.endpoint,
|
||||
websocket: loginParmas.websocket
|
||||
})
|
||||
.catch((err2) => {
|
||||
reject(err2);
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((_) => {
|
||||
$app.$message({
|
||||
message: 'Incorrect primary password',
|
||||
type: 'error'
|
||||
});
|
||||
reject(_);
|
||||
});
|
||||
} else {
|
||||
authRequest
|
||||
.getConfig()
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
})
|
||||
.then(() => {
|
||||
authLogin({
|
||||
username: loginParmas.username,
|
||||
password: loginParmas.password,
|
||||
endpoint: loginParmas.endpoint,
|
||||
websocket: loginParmas.websocket
|
||||
})
|
||||
.catch((err2) => {
|
||||
handleLogoutEvent();
|
||||
reject(err2);
|
||||
})
|
||||
.then(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}).finally(() => (state.loginForm.loading = false));
|
||||
}
|
||||
|
||||
async function deleteSavedLogin(userId) {
|
||||
const savedCredentials = JSON.parse(
|
||||
await configRepository.getString('savedCredentials')
|
||||
);
|
||||
delete savedCredentials[userId];
|
||||
// Disable primary password when no account is available.
|
||||
if (Object.keys(savedCredentials).length === 0) {
|
||||
advancedSettingsStore.enablePrimaryPassword = false;
|
||||
advancedSettingsStore.setEnablePrimaryPasswordConfigRepository(
|
||||
false
|
||||
);
|
||||
}
|
||||
state.loginForm.savedCredentials = savedCredentials;
|
||||
const jsonCredentials = JSON.stringify(savedCredentials);
|
||||
await configRepository.setString('savedCredentials', jsonCredentials);
|
||||
new Noty({
|
||||
type: 'success',
|
||||
text: 'Account removed.'
|
||||
}).show();
|
||||
}
|
||||
|
||||
async function login() {
|
||||
// TODO: remove/refactor saveCredentials & primaryPassword (security)
|
||||
await webApiService.clearCookies();
|
||||
if (!state.loginForm.loading) {
|
||||
state.loginForm.loading = true;
|
||||
if (state.loginForm.endpoint) {
|
||||
AppGlobal.endpointDomain = state.loginForm.endpoint;
|
||||
AppGlobal.websocketDomain = state.loginForm.websocket;
|
||||
} else {
|
||||
AppGlobal.endpointDomain = AppGlobal.endpointDomainVrchat;
|
||||
AppGlobal.websocketDomain = AppGlobal.websocketDomainVrchat;
|
||||
}
|
||||
authRequest
|
||||
.getConfig()
|
||||
.catch((err) => {
|
||||
state.loginForm.loading = false;
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
if (
|
||||
state.loginForm.saveCredentials &&
|
||||
advancedSettingsStore.enablePrimaryPassword
|
||||
) {
|
||||
$app.$prompt(
|
||||
t('prompt.primary_password.description'),
|
||||
t('prompt.primary_password.header'),
|
||||
{
|
||||
inputType: 'password',
|
||||
inputPattern: /[\s\S]{1,32}/
|
||||
}
|
||||
)
|
||||
.then(({ value }) => {
|
||||
const saveCredential =
|
||||
state.loginForm.savedCredentials[
|
||||
Object.keys(
|
||||
state.loginForm.savedCredentials
|
||||
)[0]
|
||||
];
|
||||
security
|
||||
.decrypt(
|
||||
saveCredential.loginParmas.password,
|
||||
value
|
||||
)
|
||||
.then(() => {
|
||||
security
|
||||
.encrypt(
|
||||
state.loginForm.password,
|
||||
value
|
||||
)
|
||||
.then((pwd) => {
|
||||
authLogin({
|
||||
username:
|
||||
state.loginForm
|
||||
.username,
|
||||
password:
|
||||
state.loginForm
|
||||
.password,
|
||||
endpoint:
|
||||
state.loginForm
|
||||
.endpoint,
|
||||
websocket:
|
||||
state.loginForm
|
||||
.websocket,
|
||||
saveCredentials:
|
||||
state.loginForm
|
||||
.saveCredentials,
|
||||
cipher: pwd
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
state.loginForm.loading = false;
|
||||
});
|
||||
return args;
|
||||
}
|
||||
authLogin({
|
||||
username: state.loginForm.username,
|
||||
password: state.loginForm.password,
|
||||
endpoint: state.loginForm.endpoint,
|
||||
websocket: state.loginForm.websocket,
|
||||
saveCredentials: state.loginForm.saveCredentials
|
||||
}).finally(() => {
|
||||
state.loginForm.loading = false;
|
||||
});
|
||||
return args;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function promptTOTP() {
|
||||
if (state.twoFactorAuthDialogVisible) {
|
||||
return;
|
||||
}
|
||||
AppApi.FlashWindow();
|
||||
state.twoFactorAuthDialogVisible = true;
|
||||
$app.$prompt(t('prompt.totp.description'), t('prompt.totp.header'), {
|
||||
distinguishCancelAndClose: true,
|
||||
cancelButtonText: t('prompt.totp.use_otp'),
|
||||
confirmButtonText: t('prompt.totp.verify'),
|
||||
inputPlaceholder: t('prompt.totp.input_placeholder'),
|
||||
inputPattern: /^[0-9]{6}$/,
|
||||
inputErrorMessage: t('prompt.totp.input_error'),
|
||||
callback: (action, instance) => {
|
||||
if (action === 'confirm') {
|
||||
authRequest
|
||||
.verifyTOTP({
|
||||
code: instance.inputValue.trim()
|
||||
})
|
||||
.catch((err) => {
|
||||
clearCookiesTryLogin();
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
userStore.getCurrentUser();
|
||||
return args;
|
||||
});
|
||||
} else if (action === 'cancel') {
|
||||
promptOTP();
|
||||
}
|
||||
},
|
||||
beforeClose: (action, instance, done) => {
|
||||
state.twoFactorAuthDialogVisible = false;
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function promptOTP() {
|
||||
if (state.twoFactorAuthDialogVisible) {
|
||||
return;
|
||||
}
|
||||
state.twoFactorAuthDialogVisible = true;
|
||||
$app.$prompt(t('prompt.otp.description'), t('prompt.otp.header'), {
|
||||
distinguishCancelAndClose: true,
|
||||
cancelButtonText: t('prompt.otp.use_totp'),
|
||||
confirmButtonText: t('prompt.otp.verify'),
|
||||
inputPlaceholder: t('prompt.otp.input_placeholder'),
|
||||
inputPattern: /^[a-z0-9]{4}-[a-z0-9]{4}$/,
|
||||
inputErrorMessage: t('prompt.otp.input_error'),
|
||||
callback: (action, instance) => {
|
||||
if (action === 'confirm') {
|
||||
authRequest
|
||||
.verifyOTP({
|
||||
code: instance.inputValue.trim()
|
||||
})
|
||||
.catch((err) => {
|
||||
clearCookiesTryLogin();
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
userStore.getCurrentUser();
|
||||
return args;
|
||||
});
|
||||
} else if (action === 'cancel') {
|
||||
promptTOTP();
|
||||
}
|
||||
},
|
||||
beforeClose: (action, instance, done) => {
|
||||
state.twoFactorAuthDialogVisible = false;
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function promptEmailOTP() {
|
||||
if (state.twoFactorAuthDialogVisible) {
|
||||
return;
|
||||
}
|
||||
AppApi.FlashWindow();
|
||||
state.twoFactorAuthDialogVisible = true;
|
||||
$app.$prompt(
|
||||
t('prompt.email_otp.description'),
|
||||
t('prompt.email_otp.header'),
|
||||
{
|
||||
distinguishCancelAndClose: true,
|
||||
cancelButtonText: t('prompt.email_otp.resend'),
|
||||
confirmButtonText: t('prompt.email_otp.verify'),
|
||||
inputPlaceholder: t('prompt.email_otp.input_placeholder'),
|
||||
inputPattern: /^[0-9]{6}$/,
|
||||
inputErrorMessage: t('prompt.email_otp.input_error'),
|
||||
callback: (action, instance) => {
|
||||
if (action === 'confirm') {
|
||||
authRequest
|
||||
.verifyEmailOTP({
|
||||
code: instance.inputValue.trim()
|
||||
})
|
||||
.catch((err) => {
|
||||
promptEmailOTP();
|
||||
throw err;
|
||||
})
|
||||
.then((args) => {
|
||||
userStore.getCurrentUser();
|
||||
return args;
|
||||
});
|
||||
} else if (action === 'cancel') {
|
||||
resendEmail2fa();
|
||||
}
|
||||
},
|
||||
beforeClose: (action, instance, done) => {
|
||||
state.twoFactorAuthDialogVisible = false;
|
||||
done();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ username: string, password: string, saveCredentials: any, cipher: string }} params credential to login
|
||||
* @returns {Promise<{origin: boolean, json: any}>}
|
||||
*/
|
||||
function authLogin(params) {
|
||||
let { username, password, saveCredentials, cipher } = params;
|
||||
username = encodeURIComponent(username);
|
||||
password = encodeURIComponent(password);
|
||||
const auth = btoa(`${username}:${password}`);
|
||||
if (saveCredentials) {
|
||||
delete params.saveCredentials;
|
||||
if (cipher) {
|
||||
params.password = cipher;
|
||||
delete params.cipher;
|
||||
}
|
||||
state.saveCredentials = params;
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
function handleAutoLogin() {
|
||||
if (state.attemptingAutoLogin) {
|
||||
return;
|
||||
}
|
||||
state.attemptingAutoLogin = true;
|
||||
const user =
|
||||
state.loginForm.savedCredentials[state.loginForm.lastUserLoggedIn];
|
||||
if (typeof user === 'undefined') {
|
||||
state.attemptingAutoLogin = false;
|
||||
return;
|
||||
}
|
||||
if (advancedSettingsStore.enablePrimaryPassword) {
|
||||
console.error(
|
||||
'Primary password is enabled, this disables auto login.'
|
||||
);
|
||||
state.attemptingAutoLogin = false;
|
||||
logout();
|
||||
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.'
|
||||
);
|
||||
state.attemptingAutoLogin = false;
|
||||
logout();
|
||||
return;
|
||||
}
|
||||
state.autoLoginAttempts.add(new Date().getTime());
|
||||
relogin(user)
|
||||
.then(() => {
|
||||
if (AppGlobal.errorNoty) {
|
||||
AppGlobal.errorNoty.close();
|
||||
}
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
type: 'success',
|
||||
text: 'Automatically logged in.'
|
||||
}).show();
|
||||
console.log('Automatically logged in.');
|
||||
})
|
||||
.catch((err) => {
|
||||
if (AppGlobal.errorNoty) {
|
||||
AppGlobal.errorNoty.close();
|
||||
}
|
||||
AppGlobal.errorNoty = new Noty({
|
||||
type: 'error',
|
||||
text: 'Failed to login automatically.'
|
||||
}).show();
|
||||
console.error('Failed to login automatically.', err);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!navigator.onLine) {
|
||||
AppGlobal.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,
|
||||
saveCredentials,
|
||||
twoFactorAuthDialogVisible,
|
||||
cachedConfig,
|
||||
enableCustomEndpoint,
|
||||
|
||||
clearCookiesTryLogin,
|
||||
resendEmail2fa,
|
||||
enablePrimaryPasswordChange,
|
||||
setPrimaryPassword,
|
||||
updateStoredUser,
|
||||
migrateStoredUsers,
|
||||
checkPrimaryPassword,
|
||||
autoLoginAfterMounted,
|
||||
toggleCustomEndpoint,
|
||||
logout,
|
||||
relogin,
|
||||
deleteSavedLogin,
|
||||
login,
|
||||
handleAutoLogin,
|
||||
handleLogoutEvent,
|
||||
handleCurrentUserUpdate,
|
||||
loginComplete
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user