feat: add user authentication flow and configuration management

This commit is contained in:
HorizonCode 2025-07-02 11:59:22 +02:00
parent ccb755603e
commit c97cfabfa4
7 changed files with 145 additions and 26 deletions

View File

@ -4,12 +4,14 @@ import { invoke } from '@tauri-apps/api/core';
import { Crypto } from './crypto'; import { Crypto } from './crypto';
export class Config { export class Config {
private fileName: string;
private config: Record<string, unknown> = {}; private config: Record<string, unknown> = {};
private crypto: Crypto | undefined; private crypto: Crypto | undefined;
private configFilePath: string | undefined; private configFilePath: string | undefined;
private encrypt: boolean; private encrypt: boolean;
constructor(encrypt?: boolean) { constructor(fileName: string, encrypt?: boolean) {
this.fileName = fileName;
this.encrypt = encrypt ?? false; this.encrypt = encrypt ?? false;
} }
@ -20,7 +22,7 @@ export class Config {
const homeDir = await path.homeDir(); const homeDir = await path.homeDir();
const folderPath = await path.join(homeDir, '.ezpplauncher'); const folderPath = await path.join(homeDir, '.ezpplauncher');
this.configFilePath = await path.join(folderPath, 'user_settings'); this.configFilePath = await path.join(folderPath, this.fileName);
const createFolder = !(await exists(folderPath)); const createFolder = !(await exists(folderPath));
if (createFolder) await mkdir(folderPath); if (createFolder) await mkdir(folderPath);

View File

@ -1,7 +1,6 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { Config } from './config'; import { Config } from './config';
import type { EZPPUser } from './types';
export const userAuth = writable<Config>(new Config(true)); export const userAuth = writable<Config>(new Config("user_auth", true));
export const currentUser = writable<EZPPUser | undefined>(undefined);
export const username = writable<string>("");
export const password = writable<string>("")

View File

@ -1,7 +1,7 @@
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import { Config } from './config'; import { Config } from './config';
export const userSettings = writable<Config>(new Config(false)); export const userSettings = writable<Config>(new Config('user_settings', false));
export const customCursor = writable<boolean>(true); export const customCursor = writable<boolean>(true);
export const cursorSmoothening = writable<boolean>(true); export const cursorSmoothening = writable<boolean>(true);

View File

@ -4,14 +4,12 @@
import Badge from '@/components/ui/badge/badge.svelte'; import Badge from '@/components/ui/badge/badge.svelte';
import Button from '@/components/ui/button/button.svelte'; import Button from '@/components/ui/button/button.svelte';
import * as Select from '@/components/ui/select'; import * as Select from '@/components/ui/select';
import { beatmap_sets, online_friends, server_connection_fails, server_ping } from '@/global'; import { beatmap_sets, current_view, server_connection_fails, server_ping } from '@/global';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { import {
LoaderCircle, LoaderCircle,
Logs, Logs,
Music2, Music2,
Play, Play,
Users,
Wifi, Wifi,
Gamepad2, Gamepad2,
WifiOff, WifiOff,
@ -22,7 +20,7 @@
import * as AlertDialog from '@/components/ui/alert-dialog'; import * as AlertDialog from '@/components/ui/alert-dialog';
import Progress from '@/components/ui/progress/progress.svelte'; import Progress from '@/components/ui/progress/progress.svelte';
import { numberHumanReadable } from '@/utils'; import { numberHumanReadable } from '@/utils';
import { fade, fly, scale } from 'svelte/transition'; import { scale } from 'svelte/transition';
import { Checkbox } from '@/components/ui/checkbox'; import { Checkbox } from '@/components/ui/checkbox';
import Label from '@/components/ui/label/label.svelte'; import Label from '@/components/ui/label/label.svelte';
import { import {
@ -36,6 +34,8 @@
import { open } from '@tauri-apps/plugin-dialog'; import { open } from '@tauri-apps/plugin-dialog';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import Login from './Login.svelte';
import { currentUser } from '@/userAuthentication';
let selectedTab = $state('home'); let selectedTab = $state('home');
let launching = $state(false); let launching = $state(false);
@ -85,14 +85,17 @@
<div class="w-full h-full border-r border-theme-800/90 flex flex-col gap-6 py-3"> <div class="w-full h-full border-r border-theme-800/90 flex flex-col gap-6 py-3">
<div class="flex flex-col items-center gap-2 border-b pb-6"> <div class="flex flex-col items-center gap-2 border-b pb-6">
<Avatar.Root class="w-20 h-20 border-2 border-theme-800"> <Avatar.Root class="w-20 h-20 border-2 border-theme-800">
<Avatar.Image src="https://a.ez-pp.farm/1001" /> <Avatar.Image src="https://a.ez-pp.farm/{$currentUser?.id ?? 0}" />
<Avatar.Fallback class="bg-theme-900"> <Avatar.Fallback class="bg-theme-900">
<LoaderCircle class="animate-spin" size={32} /> <LoaderCircle class="animate-spin" size={32} />
</Avatar.Fallback> </Avatar.Fallback>
</Avatar.Root> </Avatar.Root>
<span class="font-semibold text-2xl text-theme-50">Quetzalcoatl</span> <span class="font-semibold text-2xl text-theme-50">{$currentUser?.name ?? 'Guest'}</span>
<div class="flex flex-row gap-2"> <div class="flex flex-row gap-2">
<Badge variant="destructive">Owner</Badge> <!-- <Badge variant="destructive">Owner</Badge> -->
{#if !$currentUser}
<Button variant="outline" size="sm" onclick={() => current_view.set(Login)}>Login</Button>
{/if}
</div> </div>
</div> </div>
<div class="flex flex-col gap-6 h-full px-3"> <div class="flex flex-col gap-6 h-full px-3">
@ -402,7 +405,7 @@
}} }}
> >
<div class="flex flex-row items-center gap-3 font-semibold text-xl px-3 pt-3"> <div class="flex flex-row items-center gap-3 font-semibold text-xl px-3 pt-3">
<Settings2 /> EZPPLauncher Settings <Settings2 /> osu! Settings
</div> </div>
<div <div
class="grid grid-cols-[0.7fr_auto] gap-y-5 items-center border-t border-theme-800 py-3 px-6" class="grid grid-cols-[0.7fr_auto] gap-y-5 items-center border-t border-theme-800 py-3 px-6"

View File

@ -7,6 +7,9 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import SetupWizard from './SetupWizard.svelte'; import SetupWizard from './SetupWizard.svelte';
import Launch from './Launch.svelte'; import Launch from './Launch.svelte';
import { currentUser, userAuth } from '@/userAuthentication';
import { ezppfarm } from '@/api/ezpp';
import { toast } from 'svelte-sonner';
let ezppLogo: HTMLImageElement; let ezppLogo: HTMLImageElement;
let spinnerCircle: SVGCircleElement; let spinnerCircle: SVGCircleElement;
@ -48,6 +51,29 @@
const prepare = async () => { const prepare = async () => {
await calculateCursorSmoothness(); await calculateCursorSmoothness();
const username = $userAuth.value('username').get('');
const password = $userAuth.value('password').get('');
try {
const loginResult = await ezppfarm.login(username, password);
if (loginResult && loginResult.user) {
toast.success('Login successful!', {
description: `Welcome back, ${loginResult.user.name}!`,
});
currentUser.set(loginResult.user);
} else {
toast.error('Login failed!', {
description: 'Please check your username and password.',
});
}
} catch {
toast.error('Server error occurred during login.', {
description: 'There was an issue connecting to the server. Please try again later.',
});
}
animate(ezppLogo, { animate(ezppLogo, {
opacity: [1, 0], opacity: [1, 0],
scale: [1, 1.05], scale: [1, 1.05],

View File

@ -1,28 +1,115 @@
<script lang="ts"> <script lang="ts">
import Logo from '$assets/logo.png';
import { ezppfarm } from '@/api/ezpp'; import { ezppfarm } from '@/api/ezpp';
import Button from '@/components/ui/button/button.svelte';
import Input from '@/components/ui/input/input.svelte';
import Label from '@/components/ui/label/label.svelte';
import { current_view } from '@/global';
import { currentUser, userAuth } from '@/userAuthentication';
import { animate } from 'animejs';
import { LoaderCircle } from 'lucide-svelte';
import { toast } from 'svelte-sonner';
import Launch from './Launch.svelte';
let username = $state(''); let username = $state('');
let password = $state(''); let password = $state('');
let errorMessage = $state('');
let isLoading = $state(false); let isLoading = $state(false);
let ezppLogo: HTMLImageElement | undefined = $state(undefined);
const logo_mouseenter = () => {
if (ezppLogo) {
animate(ezppLogo, {
duration: 700,
scale: 1.2,
ease: (t: number) => Math.pow(2, -5 * t) * Math.sin((t - 0.075) * 20.94) + 1 - 0.0005 * t,
});
}
};
const logo_mouseleave = () => {
if (ezppLogo) {
animate(ezppLogo, {
duration: 700,
scale: 1,
ease: (t: number) => (t - 1) ** 7 + 1,
});
}
};
const performLogin = async () => { const performLogin = async () => {
isLoading = true; isLoading = true;
errorMessage = '';
try { try {
const loginResult = await ezppfarm.login(username, password); const loginResult = await ezppfarm.login(username, password);
if (loginResult) { if (loginResult && loginResult.user) {
// Handle successful login, e.g., redirect to the main app or store the token toast.success('Login successful!', {
console.log('Login successful:', loginResult); description: `Welcome back, ${loginResult.user.name}!`,
});
$userAuth.value('username').set(username);
$userAuth.value('password').set(password);
await $userAuth.save();
currentUser.set(loginResult.user);
current_view.set(Launch);
} else { } else {
errorMessage = 'Invalid username or password.'; toast.error('Login failed!', {
description: 'Please check your username and password.',
});
isLoading = false;
} }
} catch { } catch {
errorMessage = 'Login failed. Please check your credentials.'; toast.error('Server error occurred during login.', {
} finally { description: 'There was an issue connecting to the server. Please try again later.',
});
isLoading = false; isLoading = false;
} }
}; };
</script> </script>
<div class="mt-[50px] h-[calc(100vh-50px)] w-full">
<div class="w-full h-full flex flex-col items-center justify-center">
<img
src={Logo}
alt="EZPPLauncher Logo"
class="w-52 h-52 mb-2"
bind:this={ezppLogo}
onmouseenter={logo_mouseenter}
onmouseleave={logo_mouseleave}
/>
<form onsubmit={performLogin} class="w-full max-w-sm">
<div class="mb-4">
<Label for="username" class="block text-sm font-medium">Username</Label>
<Input
class="mt-4 w-full bg-theme-900 border-theme-800"
type="text"
id="username"
bind:value={username}
disabled={isLoading}
autocomplete="off"
autocorrect="off"
/>
</div>
<div class="mb-4">
<Label for="password" class="block text-sm font-medium">Password</Label>
<Input
class="mt-4 w-full bg-theme-900 border-theme-800"
type="password"
id="password"
bind:value={password}
disabled={isLoading}
autocomplete="off"
autocorrect="off"
/>
</div>
<Button class="w-full" type="submit" disabled={isLoading}>
{#if isLoading}
<LoaderCircle class="animate-spin" />
{:else}
Login
{/if}
</Button>
</form>
</div>
</div>

View File

@ -13,6 +13,7 @@
} from '@/userSettings'; } from '@/userSettings';
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
import { Toaster } from '@/components/ui/sonner'; import { Toaster } from '@/components/ui/sonner';
import { userAuth } from '@/userAuthentication';
let { children } = $props(); let { children } = $props();
function disableReload() { function disableReload() {
@ -68,6 +69,7 @@
disableReload(); disableReload();
setupValues(); setupValues();
const firstStartup = await $userSettings.init(); const firstStartup = await $userSettings.init();
$userAuth.init();
const config_custom_cursor = $userSettings.value('custom_cursor'); const config_custom_cursor = $userSettings.value('custom_cursor');
const config_cursor_smoothening = $userSettings.value('cursor_smoothening'); const config_cursor_smoothening = $userSettings.value('cursor_smoothening');