Compare commits
5 Commits
30d56ce778
...
e0cc893493
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0cc893493 | ||
|
|
43a039ae91 | ||
|
|
49986ffd79 | ||
|
|
0897361260 | ||
|
|
211cf1db3f |
@@ -14,7 +14,7 @@ export async function handle({ event, resolve }) {
|
|||||||
// if there is no jwt load page as normal
|
// if there is no jwt load page as normal
|
||||||
return await resolve(event);
|
return await resolve(event);
|
||||||
}
|
}
|
||||||
const response = await fetch(`${BASE_API_URI}/users/backend/current-user`, {
|
const response = await fetch(`${BASE_API_URI}/backend/users/current`, {
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: {
|
headers: {
|
||||||
Cookie: `jwt=${jwt}`,
|
Cookie: `jwt=${jwt}`,
|
||||||
@@ -29,8 +29,22 @@ export async function handle({ event, resolve }) {
|
|||||||
|
|
||||||
const userData = await response.json();
|
const userData = await response.json();
|
||||||
|
|
||||||
|
// Check if the server sent a new token
|
||||||
|
const newToken = response.headers.get("Set-Cookie");
|
||||||
|
if (newToken) {
|
||||||
|
const match = newToken.match(/jwt=([^;]+)/);
|
||||||
|
if (match) {
|
||||||
|
event.cookies.set("jwt", match[1], {
|
||||||
|
path: "/",
|
||||||
|
httpOnly: true,
|
||||||
|
secure: process.env.NODE_ENV === "production", // Secure in production
|
||||||
|
sameSite: "lax",
|
||||||
|
maxAge: 5 * 24 * 60 * 60, // 5 days in seconds
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event.locals.user = userData;
|
event.locals.user = userData;
|
||||||
// event.locals.user = await response.json();
|
|
||||||
if (event.locals.user.date_of_birth) {
|
if (event.locals.user.date_of_birth) {
|
||||||
event.locals.user.date_of_birth =
|
event.locals.user.date_of_birth =
|
||||||
event.locals.user.date_of_birth.split("T")[0];
|
event.locals.user.date_of_birth.split("T")[0];
|
||||||
|
|||||||
@@ -22,10 +22,15 @@ export const actions = {
|
|||||||
*/
|
*/
|
||||||
updateUser: async ({ request, fetch, cookies, locals }) => {
|
updateUser: async ({ request, fetch, cookies, locals }) => {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const firstName = String(formData.get("first_name"));
|
/** @type {Record<string, string>} */
|
||||||
const lastName = String(formData.get("last_name"));
|
const updateData = {};
|
||||||
const phone = String(formData.get("phone"));
|
|
||||||
const birthDate = String(formData.get("birth_date"));
|
// Convert FormData to a plain object
|
||||||
|
formData.forEach((value, key) => {
|
||||||
|
if (typeof value === "string" && value !== "") {
|
||||||
|
updateData[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const apiURL = `${BASE_API_URI}/backend/users/update/`;
|
const apiURL = `${BASE_API_URI}/backend/users/update/`;
|
||||||
|
|
||||||
@@ -36,12 +41,7 @@ export const actions = {
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Cookie: `jwt=${cookies.get("jwt")}`,
|
Cookie: `jwt=${cookies.get("jwt")}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(updateData),
|
||||||
first_name: firstName,
|
|
||||||
last_name: lastName,
|
|
||||||
phone: phone,
|
|
||||||
birth_date: birthDate,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@@ -57,7 +57,14 @@ export const actions = {
|
|||||||
if (locals.user.date_of_birth) {
|
if (locals.user.date_of_birth) {
|
||||||
locals.user.date_of_birth = response["date_of_birth"].split("T")[0];
|
locals.user.date_of_birth = response["date_of_birth"].split("T")[0];
|
||||||
}
|
}
|
||||||
|
if (locals.user.membership?.start_date) {
|
||||||
|
locals.user.membership.start_date =
|
||||||
|
locals.user.membership.start_date.split("T")[0];
|
||||||
|
}
|
||||||
|
if (locals.user.membership?.end_date) {
|
||||||
|
locals.user.membership.end_date =
|
||||||
|
locals.user.membership.end_date.split("T")[0];
|
||||||
|
}
|
||||||
throw redirect(303, `/auth/about/${response.id}`);
|
throw redirect(303, `/auth/about/${response.id}`);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
import { page } from "$app/stores";
|
import { page } from "$app/stores";
|
||||||
import { receive, send } from "$lib/utils/helpers";
|
import { receive, send } from "$lib/utils/helpers";
|
||||||
import { t } from "svelte-i18n";
|
import { t } from "svelte-i18n";
|
||||||
|
import { fly } from "svelte/transition";
|
||||||
|
|
||||||
$: ({ user } = $page.data);
|
$: ({ user } = $page.data);
|
||||||
|
|
||||||
@@ -19,33 +20,19 @@
|
|||||||
/** @type{Avatar[]} */
|
/** @type{Avatar[]} */
|
||||||
let avatars = [];
|
let avatars = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} FormData
|
|
||||||
* @property {string} first_name
|
|
||||||
* @property {string} last_name
|
|
||||||
* @property {string} email
|
|
||||||
* @property {string} [password]
|
|
||||||
* @property {string} [password2]
|
|
||||||
* @property {string} [phone]
|
|
||||||
* @property {string} address
|
|
||||||
* @property {string} zip_code
|
|
||||||
* @property {string} city
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @typedef {Object.<string, string>} ValidationErrors
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @type {ValidationErrors}
|
|
||||||
*/
|
|
||||||
let validationErrors = {};
|
|
||||||
|
|
||||||
const TABS = ["profile", "membership", "bankaccount"];
|
const TABS = ["profile", "membership", "bankaccount"];
|
||||||
let activeTab = TABS[0];
|
let activeTab = TABS[0];
|
||||||
|
|
||||||
|
/** @type{string[]} */
|
||||||
|
let errorMessages = [];
|
||||||
|
|
||||||
let showModal = false,
|
let showModal = false,
|
||||||
isUploading = false,
|
isUploading = false,
|
||||||
isUpdating = false,
|
isUpdating = false,
|
||||||
showAvatars = false;
|
showAvatars = false,
|
||||||
|
password = "",
|
||||||
|
password2 = "";
|
||||||
|
|
||||||
const open = () => (showModal = true);
|
const open = () => (showModal = true);
|
||||||
const close = () => (showModal = false);
|
const close = () => (showModal = false);
|
||||||
const toggleAvatars = () => (showAvatars = !showAvatars);
|
const toggleAvatars = () => (showAvatars = !showAvatars);
|
||||||
@@ -84,62 +71,30 @@
|
|||||||
activeTab = tab;
|
activeTab = tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates thek form data
|
|
||||||
* @param {Object} data - The form data to validate
|
|
||||||
* @returns {ValidationErrors} An object containing validation errors
|
|
||||||
*/
|
|
||||||
function validateForm(data) {
|
|
||||||
/** @type {ValidationErrors} */
|
|
||||||
let errors = {};
|
|
||||||
|
|
||||||
if ("first_name" in data && !String(data.first_name).trim()) {
|
|
||||||
errors.first_name = $t("required");
|
|
||||||
}
|
|
||||||
if ("last_name" in data && !String(data.last_name).trim()) {
|
|
||||||
errors.last_name = $t("required");
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
"email" in data &&
|
|
||||||
(!data.email || !/^\S+@\S+\.\S+$/.test(String(data.email)))
|
|
||||||
) {
|
|
||||||
errors.email = $t("required");
|
|
||||||
}
|
|
||||||
if ("password" in data) {
|
|
||||||
if (String(data.password).length < 8) {
|
|
||||||
errors.password = $t("required_password");
|
|
||||||
}
|
|
||||||
if ("password2" in data && data.password !== data.password2) {
|
|
||||||
errors.password2 = $t("required_password_match");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {import('./$types').SubmitFunction} */
|
/** @type {import('./$types').SubmitFunction} */
|
||||||
const handleUpdate = async ({ form, formData, action, cancel }) => {
|
const handleUpdate = async ({ form, formData, action, cancel }) => {
|
||||||
/** @type {Object.<string, FormDataEntryValue>} */
|
errorMessages = [];
|
||||||
const fd = Object.fromEntries(formData);
|
const errorElements = form.querySelectorAll(".error-message");
|
||||||
validationErrors = validateForm(fd);
|
|
||||||
if (Object.keys(validationErrors).length > 0) {
|
errorElements.forEach((element) => {
|
||||||
|
if (element.textContent) {
|
||||||
|
errorMessages.push(element.textContent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (errorMessages.length > 0) {
|
||||||
cancel();
|
cancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorMessages = [];
|
||||||
isUpdating = true;
|
isUpdating = true;
|
||||||
return async ({ result }) => {
|
return async ({ result }) => {
|
||||||
isUpdating = false;
|
isUpdating = false;
|
||||||
if (result.type === "success" || result.type === "redirect") {
|
if (result.type === "success" || result.type === "redirect") {
|
||||||
validationErrors = {};
|
|
||||||
close();
|
close();
|
||||||
} else if (result.type == "failure" && result.data?.errors) {
|
} else if (result.type == "failure" && result.data?.errors) {
|
||||||
/** @type {ValidationErrors} */
|
errorMessages = result.data.errors.map((error) => error.error);
|
||||||
validationErrors = {};
|
|
||||||
// Assuming result.data.errors is an array of {error: string, id: string}
|
|
||||||
result.data.errors.forEach(({ error, id }) => {
|
|
||||||
validationErrors[id] = error;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await applyAction(result);
|
await applyAction(result);
|
||||||
};
|
};
|
||||||
@@ -345,12 +300,16 @@
|
|||||||
type="password"
|
type="password"
|
||||||
label={$t("password")}
|
label={$t("password")}
|
||||||
placeholder={$t("placeholder_password")}
|
placeholder={$t("placeholder_password")}
|
||||||
|
bind:value={password}
|
||||||
|
otherPasswordValue={password2}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="password2"
|
name="password2"
|
||||||
type="password"
|
type="password"
|
||||||
label={$t("password_repeat")}
|
label={$t("password_repeat")}
|
||||||
placeholder={$t("placeholder_password")}
|
placeholder={$t("placeholder_password")}
|
||||||
|
bind:value={password2}
|
||||||
|
otherPasswordValue={password}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="first_name"
|
name="first_name"
|
||||||
@@ -452,12 +411,14 @@
|
|||||||
label={$t("iban")}
|
label={$t("iban")}
|
||||||
value={user.bank_account?.iban || ""}
|
value={user.bank_account?.iban || ""}
|
||||||
placeholder={$t("placeholder_iban")}
|
placeholder={$t("placeholder_iban")}
|
||||||
|
toUpperCase={true}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="bic"
|
name="bic"
|
||||||
label={$t("bic")}
|
label={$t("bic")}
|
||||||
value={user.bank_account?.bic || ""}
|
value={user.bank_account?.bic || ""}
|
||||||
placeholder={$t("placeholder_bic")}
|
placeholder={$t("placeholder_bic")}
|
||||||
|
toUpperCase={true}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="mandate_reference"
|
name="mandate_reference"
|
||||||
@@ -467,6 +428,15 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if errorMessages.length > 0}
|
||||||
|
<div class="error-container">
|
||||||
|
{#each errorMessages as message, i (i)}
|
||||||
|
<p class="error-message" transition:fly={{ y: -20, duration: 300 }}>
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
{#if isUpdating}
|
{#if isUpdating}
|
||||||
<SmallLoader width={30} message={"Aktualisiere..."} />
|
<SmallLoader width={30} message={"Aktualisiere..."} />
|
||||||
@@ -482,6 +452,19 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.error-container {
|
||||||
|
background-color: #fee2e2;
|
||||||
|
border: 1px solid #f87171;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
color: #dc2626;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
.tab-content {
|
.tab-content {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 0 0 3px 3px;
|
border-radius: 0 0 3px 3px;
|
||||||
|
|||||||
@@ -61,27 +61,21 @@ export const actions = {
|
|||||||
const responseBody = await res.json();
|
const responseBody = await res.json();
|
||||||
console.log("Login response body:", responseBody);
|
console.log("Login response body:", responseBody);
|
||||||
|
|
||||||
// Check for the cookie in the response headers
|
// Extract the JWT from the response headers
|
||||||
const setCookieHeader = res.headers.get("set-cookie");
|
const setCookieHeader = res.headers.get("set-cookie");
|
||||||
console.log("Set-Cookie header:", setCookieHeader);
|
|
||||||
|
|
||||||
if (setCookieHeader) {
|
if (setCookieHeader) {
|
||||||
// Parse the Set-Cookie header to get the JWT
|
const jwtMatch = setCookieHeader.match(/jwt=([^;]+)/);
|
||||||
const jwtCookie = setCookieHeader.split(";")[0];
|
if (jwtMatch) {
|
||||||
const [cookieName, cookieValue] = jwtCookie.split("=");
|
const jwtValue = jwtMatch[1];
|
||||||
if (cookieName.trim() === "jwt") {
|
// Set the cookie for the client
|
||||||
console.log("JWT cookie found in response");
|
cookies.set("jwt", jwtValue, {
|
||||||
cookies.set("jwt", cookieValue.trim(), {
|
|
||||||
path: "/",
|
path: "/",
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: "strict",
|
secure: process.env.NODE_ENV === "production", // Secure in production
|
||||||
secure: process.env.NODE_ENV === "production",
|
sameSite: "lax",
|
||||||
|
maxAge: 5 * 24 * 60 * 60, // 5 days in seconds
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
console.log("JWT cookie not found in response");
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
console.log("No Set-Cookie header in response");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Redirecting to:", next || "/");
|
console.log("Redirecting to:", next || "/");
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { fail, redirect } from "@sveltejs/kit";
|
|||||||
export async function load({ locals }) {
|
export async function load({ locals }) {
|
||||||
// redirect user if not logged in
|
// redirect user if not logged in
|
||||||
if (!locals.user) {
|
if (!locals.user) {
|
||||||
throw redirect(302, `/auth/login?next=/auth/logout`);
|
throw redirect(302, `/auth/login?next=/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,9 +34,24 @@ export const actions = {
|
|||||||
return fail(400, { errors: errors });
|
return fail(400, { errors: errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The server should clear the cookie, so we don't need to handle it here
|
||||||
// eat the cookie
|
// eat the cookie
|
||||||
cookies.delete("jwt", { path: "/" });
|
cookies.delete("jwt", { path: "/" });
|
||||||
|
|
||||||
|
// The server should clear the cookie, so we don't need to handle it here
|
||||||
|
// Just check if the cookie is cleared in the response
|
||||||
|
const setCookieHeader = res.headers.get("set-cookie");
|
||||||
|
if (!setCookieHeader || !setCookieHeader.includes("jwt=;")) {
|
||||||
|
console.error("JWT cookie not cleared in response");
|
||||||
|
return fail(500, {
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
error: "Server error: Failed to clear authentication token",
|
||||||
|
id: Date.now(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
// redirect the user
|
// redirect the user
|
||||||
throw redirect(302, "/auth/login");
|
throw redirect(302, "/auth/login");
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user