feat(frontend/auth): fix cookie not working due to path being /app

Cookies being set to path=/app meant that the API didn't have those
cookies.
Also fixed the Schema injection for auth'ed routes
This commit is contained in:
Maieul BOYER 2025-11-10 18:43:34 +01:00 committed by Maix0
parent e8b0b7e310
commit aba4c4498c
5 changed files with 151 additions and 131 deletions

View file

@ -9,12 +9,14 @@
"preview": "vite preview"
},
"devDependencies": {
"@types/js-cookie": "^3.0.6",
"typescript": "~5.9.3",
"vite": "^7.1.10",
"vite-tsconfig-paths": "^5.1.4"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.16",
"js-cookie": "^3.0.5",
"openapi-fetch": "^0.15.0",
"tailwindcss": "^4.1.16"
}

View file

@ -11,6 +11,9 @@ importers:
'@tailwindcss/vite':
specifier: ^4.1.16
version: 4.1.16(vite@7.1.12(jiti@2.6.1)(lightningcss@1.30.2))
js-cookie:
specifier: ^3.0.5
version: 3.0.5
openapi-fetch:
specifier: ^0.15.0
version: 0.15.0
@ -18,6 +21,9 @@ importers:
specifier: ^4.1.16
version: 4.1.16
devDependencies:
'@types/js-cookie':
specifier: ^3.0.6
version: 3.0.6
typescript:
specifier: ~5.9.3
version: 5.9.3
@ -405,6 +411,9 @@ packages:
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/js-cookie@3.0.6':
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@ -451,6 +460,10 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
js-cookie@3.0.5:
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
engines: {node: '>=14'}
lightningcss-android-arm64@1.30.2:
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
engines: {node: '>= 12.0.0'}
@ -867,6 +880,8 @@ snapshots:
'@types/estree@1.0.8': {}
'@types/js-cookie@3.0.6': {}
debug@4.4.3:
dependencies:
ms: 2.1.3
@ -920,6 +935,8 @@ snapshots:
jiti@2.6.1: {}
js-cookie@3.0.5: {}
lightningcss-android-arm64@1.30.2:
optional: true

View file

@ -3,148 +3,149 @@ import { showError, showInfo, showSuccess } from "@app/toast";
import authHtml from './login.html?raw';
import client from '@app/api'
import { updateUser } from "@app/auth";
import Cookie from 'js-cookie';
type Providers = {
name: string,
display_name: string,
icon_url?: string,
color?: { default: string, hover: string },
name: string,
display_name: string,
icon_url?: string,
color?: { default: string, hover: string },
};
function handleLogin(_url: string, _args: RouteHandlerParams): RouteHandlerReturn {
setTitle('Login')
return {
html: authHtml, postInsert: async (app) => {
const fLogin = document.querySelector<HTMLFormElement>('form#login-form');
if (fLogin === null)
return showError('Error while rendering the page: no form found');
showSuccess('got the form !')
fLogin.addEventListener('submit', async function(e: SubmitEvent) {
e.preventDefault();
let form = e.target as (HTMLFormElement | null)
if (form === null)
return showError('Failed to send form...');
let formData = Object.fromEntries((new FormData(form)).entries());
if (!('login' in formData) || typeof formData['login'] !== 'string' || (formData['login'] as string).length === 0)
return showError('Please enter a Login');
if (!('password' in formData) || typeof formData['password'] !== 'string' || (formData['password'] as string).length === 0)
return showError('Please enter a Password');
try {
const res = await client.login({ loginRequest: { name: formData.login, password: formData.password } });
switch (res.kind) {
case 'success': {
document.cookie = `token=${res.payload.token}`;
let user = await updateUser();
if (user === null)
return showError('Failed to get user: no user ?');
setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`);
break;
}
case 'otpRequired': {
showInfo('Got ask OTP, not yet implemented');
break;
}
case 'failed': {
showError(`Failed to login: ${res.msg}`);
}
}
} catch (e) {
console.error("Login error:", e);
showError('Failed to login: Unknown error');
}
});
setTitle('Login')
return {
html: authHtml, postInsert: async (app) => {
const fLogin = document.querySelector<HTMLFormElement>('form#login-form');
if (fLogin === null)
return showError('Error while rendering the page: no form found');
showSuccess('got the form !')
fLogin.addEventListener('submit', async function(e: SubmitEvent) {
e.preventDefault();
let form = e.target as (HTMLFormElement | null)
if (form === null)
return showError('Failed to send form...');
let formData = Object.fromEntries((new FormData(form)).entries());
if (!('login' in formData) || typeof formData['login'] !== 'string' || (formData['login'] as string).length === 0)
return showError('Please enter a Login');
if (!('password' in formData) || typeof formData['password'] !== 'string' || (formData['password'] as string).length === 0)
return showError('Please enter a Password');
try {
const res = await client.login({ loginRequest: { name: formData.login, password: formData.password } });
switch (res.kind) {
case 'success': {
Cookie.set('token', res.payload.token, { path: '/', sameSite: 'lax' });
let user = await updateUser();
if (user === null)
return showError('Failed to get user: no user ?');
setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`);
break;
}
case 'otpRequired': {
showInfo('Got ask OTP, not yet implemented');
break;
}
case 'failed': {
showError(`Failed to login: ${res.msg}`);
}
}
} catch (e) {
console.error("Login error:", e);
showError('Failed to login: Unknown error');
}
});
const bLoginAsGuest = document.querySelector<HTMLButtonElement>('#bGuestLogin');
bLoginAsGuest?.addEventListener('click', async () => {
try {
const res = await client.guestLogin();
switch (res.kind) {
case 'success': {
document.cookie = `token=${res.payload.token}`;
let user = await updateUser();
if (user === null)
return showError('Failed to get user: no user ?');
setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`);
break;
}
case 'failed': {
showError(`Failed to login: ${res.msg}`);
}
}
} catch (e) {
console.error("Login error:", e);
showError('Failed to login: Unknown error');
}
});
const bLoginAsGuest = document.querySelector<HTMLButtonElement>('#bGuestLogin');
bLoginAsGuest?.addEventListener('click', async () => {
try {
const res = await client.guestLogin();
switch (res.kind) {
case 'success': {
Cookie.set('token', res.payload.token, { path: '/', sameSite: 'lax' });
let user = await updateUser();
if (user === null)
return showError('Failed to get user: no user ?');
setTitle(`Welcome ${user.guest ? '[GUEST] ' : ''}${user.name}`);
break;
}
case 'failed': {
showError(`Failed to login: ${res.msg}`);
}
}
} catch (e) {
console.error("Login error:", e);
showError('Failed to login: Unknown error');
}
});
const dOtherLoginArea = document.querySelector<HTMLDivElement>('#otherLogin');
if (dOtherLoginArea) {
let styleSheetElement = document.createElement('style');
styleSheetElement.innerText = "";
// TODO: fetch all the providers from an API ?
const providers: Providers[] = [
{ name: 'discord', display_name: 'Discord', color: { default: 'bg-[#5865F2]', hover: '#FF65F2' } },
{ name: 'kanidm', display_name: 'Kanidm', color: { default: 'bg-red-500', hover: 'bg-red-700' } },
{ name: 'google', display_name: 'Google' },
]
let first = true;
for (const p of providers) {
let b = document.createElement('button');
if (first) b.classList.add('last:col-span-2');
first = false;
b.classList.add(...(
'w-full text-white font-medium py-2 rounded-xl transition'
.split(' ')
));
b.classList.add(`providerButton-${p.name}`)
const dOtherLoginArea = document.querySelector<HTMLDivElement>('#otherLogin');
if (dOtherLoginArea) {
let styleSheetElement = document.createElement('style');
styleSheetElement.innerText = "";
// TODO: fetch all the providers from an API ?
const providers: Providers[] = [
{ name: 'discord', display_name: 'Discord', color: { default: 'bg-[#5865F2]', hover: '#FF65F2' } },
{ name: 'kanidm', display_name: 'Kanidm', color: { default: 'bg-red-500', hover: 'bg-red-700' } },
{ name: 'google', display_name: 'Google' },
]
let first = true;
for (const p of providers) {
let b = document.createElement('button');
if (first) b.classList.add('last:col-span-2');
first = false;
b.classList.add(...(
'w-full text-white font-medium py-2 rounded-xl transition'
.split(' ')
));
b.classList.add(`providerButton-${p.name}`)
const col = { default: p.color?.default ?? "bg-gray-600", hover: p.color?.hover ?? "bg-gray-700" };
const col = { default: p.color?.default ?? "bg-gray-600", hover: p.color?.hover ?? "bg-gray-700" };
for (const k of Object.keys(col)) {
let c = (col as { [k: string]: string })[k].trim();
if (c.startsWith('bg-')) {
c = c.replace(/^bg-/, '');
const customProp = c.match(/^\((.+)\)$/);
const customVal = c.match(/^\[(.+)\]$/);
for (const k of Object.keys(col)) {
let c = (col as { [k: string]: string })[k].trim();
if (c.startsWith('bg-')) {
c = c.replace(/^bg-/, '');
const customProp = c.match(/^\((.+)\)$/);
const customVal = c.match(/^\[(.+)\]$/);
if (customProp)
c = `var(${customProp[1]})`
else if (customVal)
c = customVal[1];
else if (c === 'inherit')
c = 'inherit';
else if (c === 'current')
c = 'currentColor';
else if (c === 'transparent')
c = 'transparent';
else
c = `var(--color-${c})`
if (customProp)
c = `var(${customProp[1]})`
else if (customVal)
c = customVal[1];
else if (c === 'inherit')
c = 'inherit';
else if (c === 'current')
c = 'currentColor';
else if (c === 'transparent')
c = 'transparent';
else
c = `var(--color-${c})`
}
(col as { [k: string]: string })[k] = c;
}
}
(col as { [k: string]: string })[k] = c;
}
styleSheetElement.innerText += `.providerButton-${p.name} { background-color: ${col.default}; }\n`;
styleSheetElement.innerText += `.providerButton-${p.name}:hover { background-color: ${col.hover}; }\n`;
styleSheetElement.innerText += `.providerButton-${p.name} { background-color: ${col.default}; }\n`;
styleSheetElement.innerText += `.providerButton-${p.name}:hover { background-color: ${col.hover}; }\n`;
b.dataset.display_name = p.display_name;
b.dataset.name = p.name;
if (p.icon_url) b.dataset.icon = p.icon_url;
b.dataset.display_name = p.display_name;
b.dataset.name = p.name;
if (p.icon_url) b.dataset.icon = p.icon_url;
b.innerHTML = `
b.innerHTML = `
${p.icon_url ? `<img src="${p.icon_url}" alt="${p.display_name} Logo" />` : ''} <span class="">${p.display_name}</span>
`
b.addEventListener('click', () => {
location.href = `/api/auth/oauth2/${p.name}/login`;
})
b.addEventListener('click', () => {
location.href = `/api/auth/oauth2/${p.name}/login`;
})
dOtherLoginArea.insertAdjacentElement('afterbegin', b);
}
app?.appendChild(styleSheetElement);
}
}
};
dOtherLoginArea.insertAdjacentElement('afterbegin', b);
}
app?.appendChild(styleSheetElement);
}
}
};
}