answer/ui/src/utils/guard.ts

289 lines
7.0 KiB
TypeScript

import { getLoggedUserInfo, getAppSettings } from '@/services';
import {
loggedUserInfoStore,
siteInfoStore,
interfaceStore,
brandingStore,
loginSettingStore,
customizeStore,
themeSettingStore,
seoSettingStore,
loginToContinueStore,
} from '@/stores';
import { RouteAlias } from '@/router/alias';
import { LOGGED_USER_STORAGE_KEY } from '@/common/constants';
import Storage from './storage';
import { setupAppLanguage, setupAppTimeZone } from './localize';
import { floppyNavigation } from './floppyNavigation';
type TLoginState = {
isLogged: boolean;
isNotActivated: boolean;
isActivated: boolean;
isForbidden: boolean;
isNormal: boolean;
isAdmin: boolean;
};
export type TGuardResult = {
ok: boolean;
redirect?: string;
};
export type TGuardFunc = () => TGuardResult;
export const deriveLoginState = (): TLoginState => {
const ls: TLoginState = {
isLogged: false,
isNotActivated: false,
isActivated: false,
isForbidden: false,
isNormal: false,
isAdmin: false,
};
const { user } = loggedUserInfoStore.getState();
if (user.access_token) {
ls.isLogged = true;
}
if (ls.isLogged && user.mail_status === 1) {
ls.isActivated = true;
}
if (ls.isLogged && user.mail_status === 2) {
ls.isNotActivated = true;
}
if (ls.isLogged && user.status === 'forbidden') {
ls.isForbidden = true;
}
if (ls.isActivated && !ls.isForbidden) {
ls.isNormal = true;
}
if (ls.isNormal && user.is_admin === true) {
ls.isAdmin = true;
}
return ls;
};
export const isIgnoredPath = (ignoredPath: string | string[]) => {
if (!Array.isArray(ignoredPath)) {
ignoredPath = [ignoredPath];
}
const { pathname } = window.location;
const matchingPath = ignoredPath.find((_) => {
return pathname.indexOf(_) !== -1;
});
return !!matchingPath;
};
let pullLock = false;
let dedupeTimestamp = 0;
export const pullLoggedUser = async (forceRePull = false) => {
// only pull once if not force re-pull
if (pullLock && !forceRePull) {
return;
}
// dedupe pull requests in this time span in 10 seconds
if (Date.now() - dedupeTimestamp < 1000 * 10) {
return;
}
dedupeTimestamp = Date.now();
const loggedUserInfo = await getLoggedUserInfo().catch((ex) => {
dedupeTimestamp = 0;
if (!deriveLoginState().isLogged) {
// load fallback userInfo from local storage
const storageLoggedUserInfo = Storage.get(LOGGED_USER_STORAGE_KEY);
if (storageLoggedUserInfo) {
loggedUserInfoStore.getState().update(storageLoggedUserInfo);
}
}
console.error(ex);
});
if (loggedUserInfo) {
pullLock = true;
loggedUserInfoStore.getState().update(loggedUserInfo);
}
};
export const logged = () => {
const gr: TGuardResult = { ok: true };
const us = deriveLoginState();
if (!us.isLogged) {
gr.ok = false;
gr.redirect = RouteAlias.login;
}
return gr;
};
export const notLogged = () => {
const gr: TGuardResult = { ok: true };
const us = deriveLoginState();
if (us.isLogged) {
gr.ok = false;
gr.redirect = RouteAlias.home;
}
return gr;
};
export const notActivated = () => {
const gr: TGuardResult = { ok: true };
const us = deriveLoginState();
if (us.isActivated) {
gr.ok = false;
gr.redirect = RouteAlias.home;
}
return gr;
};
export const activated = () => {
const gr = logged();
const us = deriveLoginState();
if (us.isNotActivated) {
gr.ok = false;
gr.redirect = RouteAlias.activation;
}
return gr;
};
export const forbidden = () => {
const gr: TGuardResult = { ok: true };
const us = deriveLoginState();
if (gr.ok && !us.isForbidden) {
gr.ok = false;
gr.redirect = RouteAlias.home;
}
return gr;
};
export const notForbidden = () => {
const gr: TGuardResult = { ok: true };
const us = deriveLoginState();
if (us.isForbidden) {
gr.ok = false;
gr.redirect = RouteAlias.suspended;
}
return gr;
};
export const admin = () => {
const gr = logged();
const us = deriveLoginState();
if (gr.ok && !us.isAdmin) {
gr.ok = false;
gr.redirect = RouteAlias.home;
}
return gr;
};
export const allowNewRegistration = () => {
const gr: TGuardResult = { ok: true };
const loginSetting = loginSettingStore.getState().login;
if (!loginSetting.allow_new_registrations) {
gr.ok = false;
gr.redirect = RouteAlias.home;
}
return gr;
};
export const shouldLoginRequired = () => {
const gr: TGuardResult = { ok: true };
const loginSetting = loginSettingStore.getState().login;
if (!loginSetting.login_required) {
return gr;
}
const us = deriveLoginState();
if (us.isLogged) {
return gr;
}
if (
isIgnoredPath([
RouteAlias.login,
RouteAlias.register,
'/users/account-recovery',
'users/change-email',
'users/password-reset',
'users/account-activation',
'users/account-activation/success',
'/users/account-activation/failed',
'/users/confirm-new-email',
])
) {
return gr;
}
gr.ok = false;
gr.redirect = RouteAlias.login;
return gr;
};
/**
* try user was logged and all state ok
* @param canNavigate // if true, will navigate to login page if not logged
*/
export const tryNormalLogged = (canNavigate: boolean = false) => {
const us = deriveLoginState();
if (us.isNormal) {
return true;
}
// must assert logged state first and return
if (!us.isLogged) {
if (canNavigate) {
loginToContinueStore.getState().update({ show: true });
}
return false;
}
if (us.isNotActivated) {
floppyNavigation.navigate(RouteAlias.activation, () => {
window.location.href = RouteAlias.activation;
});
} else if (us.isForbidden) {
floppyNavigation.navigate(RouteAlias.suspended, () => {
window.location.replace(RouteAlias.suspended);
});
}
return false;
};
export const tryLoggedAndActivated = () => {
const gr: TGuardResult = { ok: true };
const us = deriveLoginState();
if (!us.isLogged || !us.isActivated) {
gr.ok = false;
}
return gr;
};
/**
* Initialize app configuration
*/
let appInitialized = false;
export const initAppSettingsStore = async () => {
const appSettings = await getAppSettings();
if (appSettings) {
siteInfoStore.getState().update(appSettings.general);
siteInfoStore.getState().updateVersion(appSettings.version);
interfaceStore.getState().update(appSettings.interface);
brandingStore.getState().update(appSettings.branding);
loginSettingStore.getState().update(appSettings.login);
customizeStore.getState().update(appSettings.custom_css_html);
themeSettingStore.getState().update(appSettings.theme);
seoSettingStore.getState().update(appSettings.site_seo);
}
};
export const setupApp = async () => {
if (appInitialized) {
return;
}
/**
* WARN:
* 1. must pre init logged user info for router guard
* 2. must pre init app settings for app render
*/
// TODO: optimize `initAppSettingsStore` by server render
await Promise.allSettled([pullLoggedUser(), initAppSettingsStore()]);
setupAppLanguage();
setupAppTimeZone();
appInitialized = true;
};