[chore] disable download oculus version with Meta login

+ Add possibility to store token with password
This commit is contained in:
MathieuG-P
2023-12-26 22:41:05 +01:00
parent 0539b0da8d
commit fa50d90941
18 changed files with 418 additions and 122 deletions
@@ -129,6 +129,7 @@ export class BsOculusDownloaderService {
return tokenObs$.pipe(
switchMap(token => {
isOculusTokenValid(token, log.info); // Log token validity
if(!downloadInfo.isVerification){
return this.createDownloadVersion(downloadInfo.bsVersion).then(({version, dest}) => ({token, version, dest}))
}
@@ -5,55 +5,203 @@ import BeatWaiting from "../../../../../../assets/images/apngs/beat-impatient.pn
import { useTranslation } from "renderer/hooks/use-translation.hook";
import { useState } from "react";
import { isOculusTokenValid } from "shared/helpers/oculus.helpers";
import crypto from "crypto-js";
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
import Tippy from "@tippyjs/react";
import { tryit } from "shared/helpers/error.helpers";
import { logRenderError } from "renderer";
import { useService } from "renderer/hooks/use-service.hook";
import { ConfigurationService } from "renderer/services/configuration.service";
const OCULUS_TOKEN_STORAGE_KEY = "meta-token";
export const EnterMetaTokenModal: ModalComponent<string> = ({resolver}) => {
const t = useTranslation();
const [token, setToken] = useState("");
const [showToken, setShowToken] = useState(false);
const config = useService(ConfigurationService);
const submit = () => {
const [passwordView, setPasswordView] = useState(() => !!config.get(OCULUS_TOKEN_STORAGE_KEY));
const submit = (token: string) => {
resolver({exitCode: ModalExitCode.COMPLETED, data: token});
}
const cancel = () => {
resolver({exitCode: ModalExitCode.CANCELED});
}
const isTokenValid = (() => {
return isOculusTokenValid(token, logRenderError);
})();
return (
<form className="flex flex-col w-80 gap-4">
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t("modals.enter-meta-token.title")}</h1>
<BsmImage className="h-20 w-20 mx-auto" image={BeatWaiting}/>
{passwordView ? (
<EnterPasswordView onValid={submit} onCancel={cancel} dontHaveToken={() => setPasswordView(() => false)}/>
) : (
<EnterOculusTokenView onValid={submit} onCancel={cancel} alreadyHaveToken={() => setPasswordView(() => true)}/>
)}
</form>
);
}
<p>{t("modals.enter-meta-token.body.need-token")}</p>
const isPasswordValid = (password: string) => {
return password.length >= 8;
}
<a href="https://github.com/Zagrios/bs-manager/wiki/How-to-obtain-your-Oculus-Token" target="_blank" className="underline">{t("modals.enter-meta-token.body.how-obtain-token")}</a>
<div>
<div className="flex flex-row items-center justify-between">
<label className="font-bold cursor-pointer tracking-wide inline-block" htmlFor="meta_token">{t("modals.enter-meta-token.body.input-label")}</label>
{!isTokenValid && token.length > 0 && (
<span className="text-orange-700 dark:text-orange-400 text-xs whitespace-normal min-w-0">{t("modals.enter-meta-token.body.token-is-invalid")}</span>
)}
const EnterPasswordView = ({onValid, onCancel, dontHaveToken}: {onValid: (token: string) => void, onCancel: () => void, dontHaveToken: () => void}) => {
const t = useTranslation();
const config = useService(ConfigurationService);
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const getTokenFromStorage = () => {
const cryptedToken = config.get<string>(OCULUS_TOKEN_STORAGE_KEY);
if(!cryptedToken) return null;
const { result: token, error } = tryit(() => crypto.AES.decrypt(cryptedToken, password).toString(crypto.enc.Utf8));
if(error){
logRenderError(error, "Often indicate that the password is not the same as the one used to save the token.");
}
return token;
}
const isTokenValid = () => {
const token = getTokenFromStorage();
return isOculusTokenValid(token);
}
const valid = () => {
const token = getTokenFromStorage();
onValid(token);
}
return (
<>
<p>{t("modals.enter-meta-token.body.info-enter-password")}</p>
<div className="flex flex-col gap-2">
<div>
<div className="flex flex-row items-center justify-between">
<label className="font-bold cursor-pointer tracking-wide inline-block" htmlFor="password">{t("modals.enter-meta-token.body.password")}</label>
{!isPasswordValid(password) && password.length > 0 && (
<span className="text-orange-700 dark:text-orange-400 text-xs whitespace-normal min-w-0">{t("modals.enter-meta-token.body.password-too-short")}</span>
)}
</div>
<div className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md flex box-border h-9">
<input className="grow px-1 py-[2px] outline-none bg-transparent" onChange={e => setPassword(e.target.value)} value={password} type={showPassword ? "text" : "password"} name="password" id="password" placeholder={t("modals.enter-meta-token.body.password")} />
<BsmButton className="shrink-0 m-1 rounded-md p-0.5 !bg-light-main-color-3 dark:!bg-main-color-3" icon={showPassword ? "eye-cross" : "eye"} withBar={false} onClick={() => setShowPassword(prev => !prev)} />
</div>
</div>
<div className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md flex box-border h-9">
<input className="grow px-1 py-[2px] outline-none bg-transparent" onChange={e => setToken(e.target.value)} value={token} type={showToken ? "text" : "password"} name="meta_token" id="meta_token" placeholder="OCAStr43sdx2..." />
<BsmButton className="shrink-0 m-1 rounded-md p-0.5 !bg-light-main-color-3 dark:!bg-main-color-3" icon={showToken ? "eye-cross" : "eye"} withBar={false} onClick={() => setShowToken(prev => !prev)} />
<div>
<span className="underline italic text-sm float-right cursor-pointer" onClick={dontHaveToken}>{t("modals.enter-meta-token.body.enter-oculus-token")}</span>
</div>
</div>
<div className="flex flex-row gap-2">
<BsmButton className="rounded-md flex justify-center items-center transition-all h-9 w-full" typeColor="cancel" text="misc.cancel" withBar={false} onClick={cancel}/>
<BsmButton className="rounded-md flex justify-center items-center transition-all h-9 w-full" typeColor="primary" text="modals.enter-meta-token.valid-btn" withBar={false} onClick={submit} disabled={!isTokenValid}/>
<BsmButton className="rounded-md flex justify-center items-center transition-all h-9 w-full" typeColor="cancel" text="misc.cancel" withBar={false} onClick={onCancel}/>
<Tippy className="!bg-neutral-900" disabled={!password || isTokenValid()} arrow={false} content={t("modals.enter-meta-token.body.info-disabled-btn-password")} hideOnClick={false}>
<div className="w-full">
<BsmButton className="rounded-md flex justify-center items-center transition-all h-9 w-full" typeColor="primary" text="modals.enter-meta-token.valid-btn" withBar={false} onClick={valid} disabled={!isTokenValid()}/>
</div>
</Tippy>
</div>
</form>
</>
);
}
const EnterOculusTokenView = ({onValid, onCancel, alreadyHaveToken}: {onValid: (token: string) => void, onCancel: () => void, alreadyHaveToken: () => void}) => {
const t = useTranslation();
const config = useService(ConfigurationService);
const [token, setToken] = useState("");
const [showToken, setShowToken] = useState(false);
const [stay, setStay] = useState(false);
const [password, setPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const storeToken = (token: string, password: string) => {
const cryptedToken = crypto.AES.encrypt(token, password).toString();
config.set(OCULUS_TOKEN_STORAGE_KEY, cryptedToken);
}
const valid = () => {
if(stay){
storeToken(token, password);
} else {
config.delete(OCULUS_TOKEN_STORAGE_KEY);
}
onValid(token);
}
const isCryptedTokenPresent = () => {
return !!config.get(OCULUS_TOKEN_STORAGE_KEY);
}
return (
<>
<p>{t("modals.enter-meta-token.body.info-enter-token")}</p>
<a href="https://github.com/Zagrios/bs-manager/wiki/How-to-obtain-your-Oculus-Token" target="_blank" className="underline">{t("modals.enter-meta-token.body.how-obtain-token")}</a>
<div className="flex flex-col gap-2">
<div>
<div className="flex flex-row items-center justify-between">
<label className="font-bold cursor-pointer tracking-wide inline-block" htmlFor="meta_token">{t("modals.enter-meta-token.body.oculus-token")}</label>
{!isOculusTokenValid(token) && token.length > 0 && (
<span className="text-orange-700 dark:text-orange-400 text-xs whitespace-normal min-w-0">{t("modals.enter-meta-token.body.token-is-invalid")}</span>
)}
</div>
<div className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md flex box-border h-9">
<input className="grow px-1 py-[2px] outline-none bg-transparent" onChange={e => setToken(e.target.value)} value={token} type={showToken ? "text" : "password"} name="meta_token" id="meta_token" placeholder="FRLAStr43sdx2..." />
<BsmButton className="shrink-0 m-1 rounded-md p-0.5 !bg-light-main-color-3 dark:!bg-main-color-3" icon={showToken ? "eye-cross" : "eye"} withBar={false} onClick={() => setShowToken(prev => !prev)} />
</div>
</div>
<Tippy className="!bg-neutral-900" arrow={false} content={t("modals.enter-meta-token.body.save-token-info")}>
<div className="flex flex-row items-center gap-1 w-fit">
<BsmCheckbox className="h-6 w-6 relative z-[1]" onChange={setStay} checked={stay}/>
<span className="font-bold cursor-help">{t("modals.enter-meta-token.body.save-my-token")}</span>
</div>
</Tippy>
<div className="grid grid-rows-[0fr] transition-[grid-template-rows]" style={{gridTemplateRows: stay && "1fr"}}>
<div className="overflow-hidden">
<div className="flex flex-row items-center justify-between">
<label className="font-bold cursor-pointer tracking-wide inline-block" htmlFor="password">{t("modals.enter-meta-token.body.password")}</label>
{!isPasswordValid(password) && password.length > 0 && (
<span className="text-orange-700 dark:text-orange-400 text-xs whitespace-normal min-w-0">{t("modals.enter-meta-token.body.password-too-short")}</span>
)}
</div>
<div className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md flex box-border h-9">
<input className="grow px-1 py-[2px] outline-none bg-transparent" onChange={e => setPassword(e.target.value)} value={password} type={showPassword ? "text" : "password"} name="password" id="password" placeholder={t("modals.enter-meta-token.body.password")} />
<BsmButton className="shrink-0 m-1 rounded-md p-0.5 !bg-light-main-color-3 dark:!bg-main-color-3" icon={showPassword ? "eye-cross" : "eye"} withBar={false} onClick={() => setShowPassword(prev => !prev)} />
</div>
</div>
</div>
{isCryptedTokenPresent() && (
<div>
<span className="underline italic text-sm float-right cursor-pointer" onClick={alreadyHaveToken}>{t("modals.enter-meta-token.body.have-token-saved")}</span>
</div>
)}
</div>
<div className="flex flex-row gap-2">
<BsmButton className="rounded-md flex justify-center items-center transition-all h-9 w-full" typeColor="cancel" text="misc.cancel" withBar={false} onClick={onCancel}/>
<BsmButton className="rounded-md flex justify-center items-center transition-all h-9 w-full" typeColor="primary" text="modals.enter-meta-token.valid-btn" withBar={false} onClick={valid} disabled={!isOculusTokenValid(token) || (stay && !isPasswordValid(password))}/>
</div>
</>
);
}
@@ -80,6 +80,7 @@ export function SettingsPage() {
const themeSelected = useObservable(themeService.theme$, "os");
const languageSelected = useObservable(i18nService.currentLanguage$, i18nService.getFallbackLanguage());
const downloadStore = useObservable(bsDownloader.defaultStore$);
const [installationFolder, setInstallationFolder] = useState(null);
const [showSupporters, setShowSupporters] = useState(false);
const [mapDeepLinksEnabled, setMapDeepLinksEnabled] = useState(false);
@@ -236,7 +237,7 @@ export function SettingsPage() {
<SettingRadioArray items={[
{ id: 1, text: "Steam", value: BsStore.STEAM, icon: <SteamIcon className="h-6 w-6 float-left"/> },
{ id: 2, text: "Oculus Store (PC)", value: BsStore.OCULUS, icon: <OculusIcon className="h-6 w-6 float-left bg-white text-black rounded-full p-0.5"/>},
{ id: 0, text: t("pages.settings.steam-and-oculus.download-platform.always-ask"), value: undefined, },
{ id: 0, text: t("pages.settings.steam-and-oculus.download-platform.always-ask"), value: null, },
]} selectedItemValue={downloadStore} onItemSelected={handleChangeBsStore}/>
</SettingContainer>
@@ -28,7 +28,7 @@ export class BsDownloaderService extends AbstractBsDownloaderService {
private readonly oculusDownloader: OculusDownloaderService;
private readonly versionManager: BSVersionManagerService;
private readonly SELECTED_STORE_TO_DOWNLOAD_KET = "selectedStoreToDownload";
private readonly SELECTED_STORE_TO_DOWNLOAD_KEY = "selectedStoreToDownload";
private constructor(){
super();
@@ -55,11 +55,11 @@ export class BsDownloaderService extends AbstractBsDownloaderService {
this._isVerifying$.next(false);
}
public get defaultStore(): BsStore | undefined { return this.config.get<BsStore>(this.SELECTED_STORE_TO_DOWNLOAD_KET); }
public get defaultStore$(): Observable<BsStore | undefined> { return this.config.watch(this.SELECTED_STORE_TO_DOWNLOAD_KET); }
public get defaultStore(): BsStore | undefined { return this.config.get<BsStore>(this.SELECTED_STORE_TO_DOWNLOAD_KEY); }
public get defaultStore$(): Observable<BsStore | undefined> { return this.config.watch(this.SELECTED_STORE_TO_DOWNLOAD_KEY); }
public setDefaultStore(store: BsStore|undefined): void {
this.config.set(this.SELECTED_STORE_TO_DOWNLOAD_KET, store);
this.config.set(this.SELECTED_STORE_TO_DOWNLOAD_KEY, store);
}
public async chooseStoreToDownloadFrom(): Promise<BsStore | undefined> {
@@ -87,37 +87,39 @@ export class OculusDownloaderService extends AbstractBsDownloaderService impleme
private async doDownloadBsVersion(bsVersion: BSVersion, isVerification: boolean): Promise<BSVersion> {
let autoDownloadFailed = false;
const autoDownloadFailed = false;
return (async () => {
const autoDownload = await lastValueFrom(this.tryAutoDownload({ bsVersion, isVerification })).then(() => true).catch((err: CustomError) => {
const doNotRestartCodes: string[] = [
OculusDownloaderErrorCodes.ALREADY_DOWNLOADING,
OculusDownloaderErrorCodes.SOME_FILES_FAILED_TO_DOWNLOAD,
OculusDownloaderErrorCodes.VERIFY_INTEGRITY_FAILED,
OculusDownloaderErrorCodes.DOWNLOAD_CANCELLED
];
autoDownloadFailed = true;
// DISABLE FOR NOW, WILL MAYBE BE RE-ENABLED WHEN META AUTH IS FIXED
if(doNotRestartCodes.includes(err?.code)){
return true;
}
// const autoDownload = await lastValueFrom(this.tryAutoDownload({ bsVersion, isVerification })).then(() => true).catch((err: CustomError) => {
// const doNotRestartCodes: string[] = [
// OculusDownloaderErrorCodes.ALREADY_DOWNLOADING,
// OculusDownloaderErrorCodes.SOME_FILES_FAILED_TO_DOWNLOAD,
// OculusDownloaderErrorCodes.VERIFY_INTEGRITY_FAILED,
// OculusDownloaderErrorCodes.DOWNLOAD_CANCELLED
// ];
// autoDownloadFailed = true;
return false;
});
// if(doNotRestartCodes.includes(err?.code)){
// return true;
// }
// return false;
// });
if(autoDownload){ return autoDownload; }
// if(autoDownload){ return autoDownload; }
const {exitCode, data} = await this.modals.openModal(LoginToMetaModal)
// const {exitCode, data} = await this.modals.openModal(LoginToMetaModal)
if(exitCode !== ModalExitCode.COMPLETED){
return false
}
// if(exitCode !== ModalExitCode.COMPLETED){
// return false
// }
if(data.method === MetaAuthMethod.META){
return lastValueFrom(this.startDownloadBsVersion({ bsVersion, isVerification, stay: data.stay })).then(() => true);
}
// if(data.method === MetaAuthMethod.META){
// return lastValueFrom(this.startDownloadBsVersion({ bsVersion, isVerification, stay: data.stay })).then(() => true);
// }
const tokenRes = await this.modals.openModal(EnterMetaTokenModal);
@@ -129,8 +131,6 @@ export class OculusDownloaderService extends AbstractBsDownloaderService impleme
})().then(res => {
console.log("aaa", res);
if(autoDownloadFailed || !res){
return bsVersion;
}
@@ -30,10 +30,14 @@ export class ConfigurationService {
public get<Type>(key: string | DefaultConfigKey): Type {
const rawValue = (window.sessionStorage.getItem(key) ?? window.localStorage.getItem(key));
const tryParse = tryit<Type>(() => JSON.parse(rawValue));
if (!tryParse.result) {
return defaultConfiguration[key as DefaultConfigKey];
const res = (tryParse.error ? rawValue : tryParse.result) as Type;
if(!res && Object.keys(defaultConfiguration).includes(key)){
return defaultConfiguration[key as DefaultConfigKey] as Type;
}
return (tryParse.result ?? rawValue) as Type;
return res;
}
public set(key: string, value: unknown, persistant = true) {
+9 -5
View File
@@ -1,6 +1,6 @@
export function isOculusTokenValid(token: string, logger?: (...args: unknown[]) => void): boolean{
// Code taken from "https://github.com/ComputerElite/QuestAppVersionSwitcher" (TokenTools.cs)
// Part of code taken from "https://github.com/ComputerElite/QuestAppVersionSwitcher" (TokenTools.cs)
logger?.("Checking if Oculus user token is valid");
@@ -12,12 +12,16 @@ export function isOculusTokenValid(token: string, logger?: (...args: unknown[])
logger?.("Token contains %. Token most likely comes from an uri and won't work");
return false;
}
if(!token.startsWith("OC")){
logger?.("Token does not start with OC.");
if(!token.startsWith("FRL")){
logger?.("Tokens must start with 'FRL'.");
return false;
}
if(token.includes("|")){
logger?.("Token contains | which usually indicates an application token which is not valid for user tokens");
logger?.("Token contains '|' which usually indicates an application token which is not valid for user tokens");
return false;
}
if(token.includes(":")){
logger?.("Token contains ':' wich can indicate that the user have copied the 'access_token' field with the token");
return false;
}
if(token.match(/OC\d{15}/)){
@@ -25,7 +29,7 @@ export function isOculusTokenValid(token: string, logger?: (...args: unknown[])
return false;
}
logger?.("Oculus user token is valid");
logger?.("Oculus user token is valid.", `token lenght : ${token.length}`);
return true;