Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/apps/components/firstrun/FirstRun/Page/TOTPFailed.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
import Icon from "$lib/Icon.svelte";
</script>

<Icon icon="ErrorIcon" />
<h1>2FA Setup Failed</h1>
<p>Something went wrong when trying to spawn the 2FA setup overlay.</p>
<br />
<p>You can always try again later in the settings app under 'Your ArcOS Identity'.</p>
7 changes: 7 additions & 0 deletions src/apps/components/firstrun/FirstRun/Page/TOTPSetup.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script lang="ts">
import Icon from "$lib/Icon.svelte";
</script>

<Icon icon="ElevationIcon" />
<h1>Set-up 2FA?</h1>
<p>Do you want to secure your ArcOS account by setting up 2FA using your authenticator app?</p>
75 changes: 72 additions & 3 deletions src/apps/components/firstrun/store.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { Daemon } from "$ts/env";
import type { ITotpConnector } from "$interfaces/modules/server/ITotpConnector";
import { Daemon, SoundBus } from "$ts/env";
import { UserPaths } from "$ts/user/store";
import { MessageBox } from "$ts/util/dialog";
import { join } from "$ts/util/fs";
import { ElevationLevel } from "$types/system/elevation";
import type { ArcShortcut } from "$types/system/shortcut";
import type { TotpSetupGuiRuntime } from "../totpsetupgui/runtime";
import DisplayName from "./FirstRun/Page/DisplayName.svelte";
import Finish from "./FirstRun/Page/Finish.svelte";
import ProfilePicture from "./FirstRun/Page/ProfilePicture.svelte";
import Style from "./FirstRun/Page/Style.svelte";
import ThirdParty from "./FirstRun/Page/ThirdParty.svelte";
import TOTPFailed from "./FirstRun/Page/TOTPFailed.svelte";
import TOTPSetup from "./FirstRun/Page/TOTPSetup.svelte";
import Welcome from "./FirstRun/Page/Welcome.svelte";
import { DarkModeGraphic, LightModeGraphic } from "./images";
import type { FirstRunPage, FirstRunTheme } from "./types";
Expand Down Expand Up @@ -127,7 +133,7 @@ export const FirstRunPages = new Map<string, FirstRunPage>([
right: [
{
caption: "Not now",
action: (process) => process.switchPage("finish"),
action: (process) => process.switchPage("totpSetup"),
},
{
caption: "Enable",
Expand All @@ -136,14 +142,77 @@ export const FirstRunPages = new Map<string, FirstRunPage>([
v.security.enableThirdParty = true;
return v;
});
process.switchPage("finish");
process.switchPage("totpSetup");
},
suggested: true,
},
],
},
},
],
[
"totpSetup",
{
name: "Set-up 2FA?",
hero: true,
component: TOTPSetup,
actions: {
left: [
{
caption: "Go back",
action: (process) => process.switchPage("thirdParty"),
},
],
right: [
{
caption: "Skip",
action: (process) => process.switchPage("finish"),
},
{
caption: "Set-up 2FA",
action: async (process) => {
const totpSetupProc = await process.spawnOverlayApp<TotpSetupGuiRuntime>("TotpSetupGui", process.pid, true);
if (!totpSetupProc) {
process.switchPage("totpFailed");
SoundBus.playSound("arcos.dialog.error");
}

totpSetupProc?.setupState.subscribe((v) => {
if (v === "successful") {
process.switchPage("finish");
}
});
},
suggested: true,
},
],
},
},
],
[
"totpFailed",
{
name: "2FA Setup Failed",
hero: true,
component: TOTPFailed,
actions: {
left: [
{
caption: "Go back",
action: (process) => process.switchPage("thirdParty"),
},
],
right: [
{
caption: "Try again",
action: (process) => process.switchPage("totpSetup"),
},

{ caption: "Finish later", action: (process) => process.switchPage("finish"), suggested: true },
],
},
},
],
[
"finish",
{
Expand Down
24 changes: 23 additions & 1 deletion src/apps/components/totpsetupgui/TotpSetupGui.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import Input from "./TotpSetupGui/Input.svelte";

const { process }: { process: ITotpSetupGuiRuntime } = $props();
const { code, url } = process;
const { code, url, setupState, firstTimeSetup } = process;
let errored = $state(false);
let locked = $state(false);

Expand Down Expand Up @@ -51,6 +51,28 @@
);
$code = "";
} else {
if (firstTimeSetup) {
await MessageBox(
{
title: "ArcOS Security",
message: "Two-factor authentication has now been enabled on your account.",
buttons: [
{
caption: "Okay",
action: () => {
setupState.set("successful");
},
},
],
sound: "arcos.dialog.info",
image: "GoodStatusIcon",
},
process.parentPid,
true
);
return;
}

MessageBox(
{
title: "ArcOS Security",
Expand Down
13 changes: 12 additions & 1 deletion src/apps/components/totpsetupgui/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,21 @@ import { AppProcess } from "$ts/apps/process";
import { Daemon } from "$ts/env";
import { Store } from "$ts/writable";
import type { AppProcessData } from "$types/apps/app";
import type { SetupState } from "./types";

export class TotpSetupGuiRuntime extends AppProcess implements ITotpSetupGuiRuntime {
public code = Store<string>("");
public url = Store<string>("");
public setupState = Store<SetupState>("unfinished");
public firstTimeSetup: boolean;

//#region LIFECYCLE

constructor(pid: number, parentPid: number, app: AppProcessData) {
constructor(pid: number, parentPid: number, app: AppProcessData, firstTimeSetup: boolean = false) {
super(pid, parentPid, app);

this.firstTimeSetup = firstTimeSetup;

this.setSource(__SOURCE__);
}

Expand All @@ -27,6 +32,12 @@ export class TotpSetupGuiRuntime extends AppProcess implements ITotpSetupGuiRunt
this.url.set(result.result!.url);
}

async onClose(): Promise<boolean> {
if (this.setupState() === "unfinished") this.setupState.set("canceled");

return true;
}

//#endregion
//#region ACCESS

Expand Down
1 change: 1 addition & 0 deletions src/apps/components/totpsetupgui/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type SetupState = "unfinished" | "canceled" | "successful";
2 changes: 1 addition & 1 deletion src/apps/core/newloginapp/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class NewLoginAppRuntime extends AppProcess implements INewLoginAppRuntim
this.Persistence.update((v) => {
const userConnector = Server.GetConn<IUserConnector>("UserConnector", "");
const persistence: LoginPersistenceUser = {
displayName: info.preferences.account.displayName ?? info.username,
displayName: info.preferences?.account?.displayName ?? info.username,
username: info.username,
userId: info._id,
profilePictureUrl: userConnector.PictureUrl(info._id),
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/runtimes/ITotpSetupGuiRuntime.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import type { SetupState } from "$apps/components/totpsetupgui/types";
import type { IAppProcess } from "$interfaces/IAppProcess";
import type { ReadableStore } from "$types/shared/writable";

// !tpa
export interface ITotpSetupGuiRuntime extends IAppProcess {
code: ReadableStore<string>;
url: ReadableStore<string>;
setupState: ReadableStore<SetupState>;
firstTimeSetup: boolean;

validate(): boolean;
activateTotp(): Promise<boolean>;
Expand Down
Loading