native auth
This commit is contained in:
@@ -4,6 +4,14 @@
|
||||
import Power from "../../../assets/icons/power.svelte";
|
||||
|
||||
let signingOut = false;
|
||||
let isChangingPassword = false;
|
||||
let passwordError = "";
|
||||
let passwordSuccess = "";
|
||||
let passwordForm = {
|
||||
currentPassword: "",
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
};
|
||||
|
||||
async function handleSignOut() {
|
||||
if (signingOut) return;
|
||||
@@ -23,10 +31,43 @@
|
||||
console.error("Failed to open client config manager", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangePassword = async () => {
|
||||
if (isChangingPassword) return;
|
||||
passwordError = "";
|
||||
passwordSuccess = "";
|
||||
|
||||
if (!passwordForm.currentPassword || !passwordForm.newPassword) {
|
||||
passwordError = "Current and new password are required";
|
||||
return;
|
||||
}
|
||||
|
||||
if (passwordForm.newPassword !== passwordForm.confirmPassword) {
|
||||
passwordError = "New password confirmation does not match";
|
||||
return;
|
||||
}
|
||||
|
||||
isChangingPassword = true;
|
||||
try {
|
||||
await invoke("change_password", {
|
||||
currentPassword: passwordForm.currentPassword,
|
||||
newPassword: passwordForm.newPassword,
|
||||
});
|
||||
passwordSuccess = "Password updated";
|
||||
passwordForm.currentPassword = "";
|
||||
passwordForm.newPassword = "";
|
||||
passwordForm.confirmPassword = "";
|
||||
} catch (error) {
|
||||
console.error("Failed to change password", error);
|
||||
passwordError = error instanceof Error ? error.message : "Unable to update password";
|
||||
} finally {
|
||||
isChangingPassword = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="size-full flex flex-col justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-col gap-4">
|
||||
<p>{$appData?.user?.name}'s preferences</p>
|
||||
<div class="flex flex-row gap-2">
|
||||
<button class="btn" class:btn-disabled={signingOut} onclick={handleSignOut}>
|
||||
@@ -36,6 +77,53 @@
|
||||
Advanced options
|
||||
</button>
|
||||
</div>
|
||||
<div class="divider my-0"></div>
|
||||
<div class="flex flex-col gap-3 max-w-sm">
|
||||
<p class="text-sm opacity-70">Change password</p>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs opacity-60">Current password</span>
|
||||
<input
|
||||
class="input input-bordered input-sm"
|
||||
type="password"
|
||||
autocomplete="current-password"
|
||||
bind:value={passwordForm.currentPassword}
|
||||
/>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs opacity-60">New password</span>
|
||||
<input
|
||||
class="input input-bordered input-sm"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
bind:value={passwordForm.newPassword}
|
||||
/>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs opacity-60">Confirm new password</span>
|
||||
<input
|
||||
class="input input-bordered input-sm"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
bind:value={passwordForm.confirmPassword}
|
||||
/>
|
||||
</label>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
class:btn-disabled={isChangingPassword}
|
||||
disabled={isChangingPassword}
|
||||
onclick={handleChangePassword}
|
||||
>
|
||||
{isChangingPassword ? "Updating..." : "Update password"}
|
||||
</button>
|
||||
{#if passwordSuccess}
|
||||
<span class="text-xs text-success">{passwordSuccess}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if passwordError}
|
||||
<p class="text-xs text-error">{passwordError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex flex-row justify-between">
|
||||
<div></div>
|
||||
|
||||
@@ -2,19 +2,12 @@
|
||||
import { onMount } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
type AuthConfig = {
|
||||
audience: string;
|
||||
auth_url: string;
|
||||
};
|
||||
|
||||
type AppConfig = {
|
||||
api_base_url?: string | null;
|
||||
auth: AuthConfig;
|
||||
};
|
||||
|
||||
let form: AppConfig = {
|
||||
api_base_url: "",
|
||||
auth: { audience: "", auth_url: "" },
|
||||
};
|
||||
|
||||
let saving = false;
|
||||
@@ -27,10 +20,6 @@
|
||||
const config = (await invoke("get_client_config")) as AppConfig;
|
||||
form = {
|
||||
api_base_url: config.api_base_url ?? "",
|
||||
auth: {
|
||||
audience: config.auth.audience,
|
||||
auth_url: config.auth.auth_url,
|
||||
},
|
||||
};
|
||||
} catch (err) {
|
||||
errorMessage = `Failed to load config: ${err}`;
|
||||
@@ -38,23 +27,6 @@
|
||||
};
|
||||
|
||||
const validate = () => {
|
||||
if (!form.auth.auth_url.trim()) {
|
||||
return "Auth URL is required";
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(form.auth.auth_url.trim());
|
||||
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
||||
return "Auth URL must start with http or https";
|
||||
}
|
||||
} catch (e) {
|
||||
return "Auth URL must be a valid URL";
|
||||
}
|
||||
|
||||
if (!form.auth.audience.trim()) {
|
||||
return "JWT audience is required";
|
||||
}
|
||||
|
||||
if (form.api_base_url?.trim()) {
|
||||
try {
|
||||
const parsed = new URL(
|
||||
@@ -86,10 +58,6 @@
|
||||
await invoke("save_client_config", {
|
||||
config: {
|
||||
api_base_url: form.api_base_url?.trim() || null,
|
||||
auth: {
|
||||
audience: form.auth.audience.trim(),
|
||||
auth_url: form.auth.auth_url.trim(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -117,7 +85,7 @@
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-xl font-semibold">Client Configuration</p>
|
||||
<p class="opacity-70 text-sm">Set custom API and auth endpoints.</p>
|
||||
<p class="opacity-70 text-sm">Set a custom API endpoint.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
@@ -129,14 +97,6 @@
|
||||
placeholder="https://api.fdolls.adamcv.com"
|
||||
/>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-sm">Auth URL</span>
|
||||
<input class="input input-bordered" bind:value={form.auth.auth_url} />
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-sm">JWT Audience</span>
|
||||
<input class="input input-bordered" bind:value={form.auth.audience} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
|
||||
@@ -5,17 +5,59 @@
|
||||
import ExternalLink from "../../assets/icons/external-link.svelte";
|
||||
|
||||
let isContinuing = false;
|
||||
let useRegister = false;
|
||||
let errorMessage = "";
|
||||
let form = {
|
||||
email: "",
|
||||
password: "",
|
||||
name: "",
|
||||
username: "",
|
||||
};
|
||||
|
||||
const normalizeError = (value: unknown) => {
|
||||
if (value instanceof Error) {
|
||||
return value.message;
|
||||
}
|
||||
return typeof value === "string" ? value : "Something went wrong";
|
||||
};
|
||||
|
||||
const handleContinue = async () => {
|
||||
if (isContinuing) return;
|
||||
if (!form.email.trim() || !form.password) {
|
||||
errorMessage = "Email and password are required";
|
||||
return;
|
||||
}
|
||||
isContinuing = true;
|
||||
errorMessage = "";
|
||||
try {
|
||||
await invoke("start_auth_flow");
|
||||
if (useRegister) {
|
||||
await invoke("register", {
|
||||
email: form.email.trim(),
|
||||
password: form.password,
|
||||
name: form.name.trim() || null,
|
||||
username: form.username.trim() || null,
|
||||
});
|
||||
useRegister = false;
|
||||
resetRegisterFields();
|
||||
form.password = "";
|
||||
return;
|
||||
}
|
||||
|
||||
await invoke("login", {
|
||||
email: form.email.trim(),
|
||||
password: form.password,
|
||||
});
|
||||
await getCurrentWebviewWindow().close();
|
||||
} catch (error) {
|
||||
console.error("Failed to start auth flow", error);
|
||||
isContinuing = false;
|
||||
console.error("Failed to authenticate", error);
|
||||
errorMessage = normalizeError(error);
|
||||
}
|
||||
isContinuing = false;
|
||||
};
|
||||
|
||||
const resetRegisterFields = () => {
|
||||
form.name = "";
|
||||
form.username = "";
|
||||
};
|
||||
|
||||
const openClientConfigManager = async () => {
|
||||
@@ -42,7 +84,47 @@
|
||||
a cute passive socialization layer!
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 *:w-max">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs opacity-60">Email</span>
|
||||
<input
|
||||
class="input input-bordered input-sm"
|
||||
type="email"
|
||||
autocomplete="email"
|
||||
bind:value={form.email}
|
||||
placeholder="you@example.com"
|
||||
/>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs opacity-60">Password</span>
|
||||
<input
|
||||
class="input input-bordered input-sm"
|
||||
type="password"
|
||||
autocomplete={useRegister ? "new-password" : "current-password"}
|
||||
bind:value={form.password}
|
||||
placeholder="••••••••"
|
||||
/>
|
||||
</label>
|
||||
{#if useRegister}
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs opacity-60">Name (optional)</span>
|
||||
<input
|
||||
class="input input-bordered input-sm"
|
||||
autocomplete="name"
|
||||
bind:value={form.name}
|
||||
/>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs opacity-60">Username (optional)</span>
|
||||
<input
|
||||
class="input input-bordered input-sm"
|
||||
autocomplete="username"
|
||||
bind:value={form.username}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary btn-xl"
|
||||
onclick={handleContinue}
|
||||
@@ -54,20 +136,36 @@
|
||||
<div class="scale-70">
|
||||
<ExternalLink />
|
||||
</div>
|
||||
Sign in
|
||||
{useRegister ? "Create account" : "Sign in"}
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-link p-0 btn-sm text-base-content"
|
||||
class="btn btn-ghost btn-sm px-0 justify-start"
|
||||
onclick={() => {
|
||||
useRegister = !useRegister;
|
||||
errorMessage = "";
|
||||
if (!useRegister) {
|
||||
resetRegisterFields();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{useRegister ? "Already have an account? Sign in" : "New here? Create an account"}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-link p-0 btn-sm text-base-content w-max"
|
||||
onclick={openClientConfigManager}
|
||||
>
|
||||
Advanced options
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-xs opacity-50 max-w-60">
|
||||
An account is needed to identify you for connecting with friends.
|
||||
</p>
|
||||
{#if errorMessage}
|
||||
<p class="text-xs text-error max-w-72">{errorMessage}</p>
|
||||
{:else}
|
||||
<p class="text-xs opacity-50 max-w-60">
|
||||
An account is needed to identify you for connecting with friends.
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user