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';
export class Config {
private fileName: string;
private config: Record<string, unknown> = {};
private crypto: Crypto | undefined;
private configFilePath: string | undefined;
private encrypt: boolean;
constructor(encrypt?: boolean) {
constructor(fileName: string, encrypt?: boolean) {
this.fileName = fileName;
this.encrypt = encrypt ?? false;
}
@ -20,7 +22,7 @@ export class Config {
const homeDir = await path.homeDir();
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));
if (createFolder) await mkdir(folderPath);

View File

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

View File

@ -1,7 +1,7 @@
import { writable } from 'svelte/store';
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 cursorSmoothening = writable<boolean>(true);

View File

@ -4,14 +4,12 @@
import Badge from '@/components/ui/badge/badge.svelte';
import Button from '@/components/ui/button/button.svelte';
import * as Select from '@/components/ui/select';
import { beatmap_sets, online_friends, server_connection_fails, server_ping } from '@/global';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { beatmap_sets, current_view, server_connection_fails, server_ping } from '@/global';
import {
LoaderCircle,
Logs,
Music2,
Play,
Users,
Wifi,
Gamepad2,
WifiOff,
@ -22,7 +20,7 @@
import * as AlertDialog from '@/components/ui/alert-dialog';
import Progress from '@/components/ui/progress/progress.svelte';
import { numberHumanReadable } from '@/utils';
import { fade, fly, scale } from 'svelte/transition';
import { scale } from 'svelte/transition';
import { Checkbox } from '@/components/ui/checkbox';
import Label from '@/components/ui/label/label.svelte';
import {
@ -36,6 +34,8 @@
import { open } from '@tauri-apps/plugin-dialog';
import { invoke } from '@tauri-apps/api/core';
import { toast } from 'svelte-sonner';
import Login from './Login.svelte';
import { currentUser } from '@/userAuthentication';
let selectedTab = $state('home');
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="flex flex-col items-center gap-2 border-b pb-6">
<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">
<LoaderCircle class="animate-spin" size={32} />
</Avatar.Fallback>
</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">
<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 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">
<Settings2 /> EZPPLauncher Settings
<Settings2 /> osu! Settings
</div>
<div
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 SetupWizard from './SetupWizard.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 spinnerCircle: SVGCircleElement;
@ -48,6 +51,29 @@
const prepare = async () => {
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, {
opacity: [1, 0],
scale: [1, 1.05],

View File

@ -1,28 +1,115 @@
<script lang="ts">
import Logo from '$assets/logo.png';
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 password = $state('');
let errorMessage = $state('');
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 () => {
isLoading = true;
errorMessage = '';
try {
const loginResult = await ezppfarm.login(username, password);
if (loginResult) {
// Handle successful login, e.g., redirect to the main app or store the token
console.log('Login successful:', loginResult);
} else {
errorMessage = 'Invalid username or password.';
}
if (loginResult && loginResult.user) {
toast.success('Login successful!', {
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 {
toast.error('Login failed!', {
description: 'Please check your username and password.',
});
isLoading = false;
}
} catch {
errorMessage = 'Login failed. Please check your credentials.';
} finally {
toast.error('Server error occurred during login.', {
description: 'There was an issue connecting to the server. Please try again later.',
});
isLoading = false;
}
};
</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';
import { Buffer } from 'buffer';
import { Toaster } from '@/components/ui/sonner';
import { userAuth } from '@/userAuthentication';
let { children } = $props();
function disableReload() {
@ -68,6 +69,7 @@
disableReload();
setupValues();
const firstStartup = await $userSettings.init();
$userAuth.init();
const config_custom_cursor = $userSettings.value('custom_cursor');
const config_cursor_smoothening = $userSettings.value('cursor_smoothening');