Skip to content
Extraits de code Groupes Projets
Valider ebd2eb67 rédigé par Brieuc Dubois's avatar Brieuc Dubois
Parcourir les fichiers

Toats, sessions and JWT

parent 043ea279
Aucune branche associée trouvée
Aucune étiquette associée trouvée
Aucune requête de fusion associée trouvée
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0", "@typescript-eslint/parser": "^7.0.0",
"@zerodevx/svelte-toast": "^0.9.5",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1", "eslint-plugin-svelte": "^2.35.1",
...@@ -1143,6 +1144,15 @@ ...@@ -1143,6 +1144,15 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true "dev": true
}, },
"node_modules/@zerodevx/svelte-toast": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/@zerodevx/svelte-toast/-/svelte-toast-0.9.5.tgz",
"integrity": "sha512-JLeB/oRdJfT+dz9A5bgd3Z7TuQnBQbeUtXrGIrNWMGqWbabpepBF2KxtWVhL2qtxpRqhae2f6NAOzH7xs4jUSw==",
"dev": true,
"peerDependencies": {
"svelte": "^3.57.0 || ^4.0.0"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.11.3", "version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@typescript-eslint/eslint-plugin": "^7.0.0", "@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0", "@typescript-eslint/parser": "^7.0.0",
"@zerodevx/svelte-toast": "^0.9.5",
"eslint": "^8.56.0", "eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.35.1", "eslint-plugin-svelte": "^2.35.1",
......
...@@ -4,41 +4,47 @@ import { axiosPublicInstance } from './apiInstance'; ...@@ -4,41 +4,47 @@ import { axiosPublicInstance } from './apiInstance';
import { jwtDecode } from 'jwt-decode'; import { jwtDecode } from 'jwt-decode';
import { type JWTContent } from '$lib/utils/login'; import { type JWTContent } from '$lib/utils/login';
export async function loginAPI(username: string, password: string) { export async function loginAPI(username: string, password: string): Promise<string> {
const response = await axiosPublicInstance.post( return axiosPublicInstance
`/auth/login`, .post(
{ `/auth/login`,
username, {
password username,
}, password
{ },
headers: { {
'Content-Type': 'application/x-www-form-urlencoded' headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
} }
} )
); .then((response) => {
if (response.status === 401) {
return response.data.detail ?? 'Unauthorized';
} else if (response.status === 422) {
return 'Invalid request';
} else if (response.status === 200) {
session.accessToken.set(response.data.access_token);
session.refreshToken.set(response.data.refresh_token);
if (response.status === 401) { setAuthTokens({
return response.data.detail ?? 'Unauthorized'; accessToken: response.data.access_token,
} else if (response.status === 422) { refreshToken: response.data.refresh_token
return 'Invalid request'; });
} else if (response.status === 200) {
session.accessToken.set(response.data.access_token);
session.refreshToken.set(response.data.refresh_token);
setAuthTokens({ const decoded = jwtDecode<JWTContent>(response.data.access_token);
accessToken: response.data.access_token,
refreshToken: response.data.refresh_token
});
const decoded = jwtDecode<JWTContent>(response.data.access_token);
session.username.set(decoded.username); session.username.set(decoded.username);
session.type.set(decoded.type.toFixed(0)); session.type.set(decoded.type.toFixed(0));
session.id.set(decoded.sub); session.id.set(decoded.sub);
session.exp.set(decoded.exp.toFixed(0));
return 'OK'; return 'OK';
} }
return 'Unknown error occurred'; return 'Unknown error occurred: ' + response.status;
})
.catch((error) => {
return error.toString();
});
} }
import { toastAlert } from '$lib/utils/toasts';
import { axiosInstance } from './apiInstance'; import { axiosInstance } from './apiInstance';
export async function getSessionsAPI() { export async function getSessionsAPI() {
...@@ -6,6 +7,14 @@ export async function getSessionsAPI() { ...@@ -6,6 +7,14 @@ export async function getSessionsAPI() {
return response.data; return response.data;
} }
export async function createSessionAPI() { export async function createSessionAPI(): Promise<bool> {
const response = await axiosInstance.post(`/sessions`); const response = await axiosInstance.post(`/sessions`);
if (response.status !== 204) {
toastAlert('Failed to create session');
}
}
export async function deleteSessionAPI(id: string) {
const response = await axiosInstance.delete(`/sessions/${id}`);
} }
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<h1>LanguageLab</h1> <h1>LanguageLab</h1>
<span> <span>
{#if session.isLoggedIn} {#if session.isLoggedIn}
<span>Connecté en tant <strong>{get(session.username)}</strong></span> <span>Connecté en tant que <strong>{get(session.username)}</strong></span>
<a href="/logout"><Logout /></a> <a href="/logout"><Logout /></a>
{:else} {:else}
<a href="/login"><Login /></a> <a href="/login"><Login /></a>
......
...@@ -13,6 +13,7 @@ const refreshToken = localWritable('refreshToken', ''); ...@@ -13,6 +13,7 @@ const refreshToken = localWritable('refreshToken', '');
const username = localWritable('username', ''); const username = localWritable('username', '');
const type = localWritable('type', ''); const type = localWritable('type', '');
const id = localWritable('id', ''); const id = localWritable('id', '');
const exp = localWritable('exp', '');
export default { export default {
accessToken, accessToken,
...@@ -20,5 +21,14 @@ export default { ...@@ -20,5 +21,14 @@ export default {
username, username,
type, type,
id, id,
isLoggedIn: get(accessToken) !== '' exp,
isLoggedIn: () => {
if (get(accessToken) === '') return false;
const expiration = parseInt(get(exp));
if (isNaN(expiration) || expiration < Date.now() / 1000) return false;
return true;
}
}; };
...@@ -9,7 +9,8 @@ export const sessions = { ...@@ -9,7 +9,8 @@ export const sessions = {
subscribe, subscribe,
set, set,
update, update,
reload: () => update((sessions) => sessions) reload: () => update((sessions) => sessions),
add: (session: Session) => update((sessions) => [...sessions, session])
}; };
export default class Session { export default class Session {
...@@ -49,6 +50,18 @@ export default class Session { ...@@ -49,6 +50,18 @@ export default class Session {
return users.substring(0, maxLength) + '...'; return users.substring(0, maxLength) + '...';
} }
async delete(): Promise<boolean> {
const response = await axiosInstance.delete(`/sessions/${this.id}`);
if (response.status !== 204) {
toastAlert('Failed to delete session');
return false;
}
sessions.update((sessions) => sessions.filter((s) => s.id !== this.id));
return true;
}
static parse(json: any): Session { static parse(json: any): Session {
if (json === null || json === undefined) { if (json === null || json === undefined) {
toastAlert('Failed to parse session: json is null'); toastAlert('Failed to parse session: json is null');
......
import session from '$lib/stores/session'; import session from '$lib/stores/session';
export function requireLogin() { export function requireLogin() {
if (!session.isLoggedIn) { if (!session.isLoggedIn()) {
window.location.href = '/login?redirect=' + window.location.pathname; window.location.href = '/login?redirect=' + window.location.pathname;
} }
} }
......
...@@ -27,3 +27,17 @@ export function toastWarning(title: string, subtitle: string = '', persistant: b ...@@ -27,3 +27,17 @@ export function toastWarning(title: string, subtitle: string = '', persistant: b
pausable: true pausable: true
}); });
} }
export function toastSuccess(title: string, subtitle: string = '', persistant: boolean = false) {
toast.push(`<strong>${title}</strong><br>${subtitle}`, {
theme: {
'--toastBackground': '#52c41a',
'--toastBarBackground': '#389e0d',
'--toastColor': '#fff'
},
initial: persistant ? 0 : 1,
next: 0,
duration: 3000,
pausable: true
});
}
<script>
import { SvelteToast } from '@zerodevx/svelte-toast';
</script>
<slot />
<SvelteToast />
<script> <script lang="ts">
import { createSessionAPI, getSessionsAPI } from '$lib/api/sessions'; import { getSessionsAPI } from '$lib/api/sessions';
import Header from '$lib/components/header.svelte'; import Header from '$lib/components/header.svelte';
import Session, { sessions } from '$lib/types/session'; import Session, { sessions } from '$lib/types/session';
import { requireLogin } from '$lib/utils/login'; import { requireLogin } from '$lib/utils/login';
import { toastAlert, toastSuccess } from '$lib/utils/toasts';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
onMount(async () => { onMount(async () => {
...@@ -10,13 +11,21 @@ ...@@ -10,13 +11,21 @@
Session.parseAll(await getSessionsAPI()); Session.parseAll(await getSessionsAPI());
}); });
async function createSession() {
await Session.create();
}
async function deleteSession(session: Session) {
await session.delete();
}
</script> </script>
<Header /> <Header />
<h1>Sessions</h1> <h1>Sessions</h1>
<button on:click|preventDefault={() => Session.create()}>Create session</button> <button on:click|preventDefault={createSession}>Create session</button>
{#if $sessions.length === 0} {#if $sessions.length === 0}
<p>No sessions found</p> <p>No sessions found</p>
...@@ -27,6 +36,7 @@ ...@@ -27,6 +36,7 @@
<th>#</th> <th>#</th>
<th>Date</th> <th>Date</th>
<th>participants</th> <th>participants</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
...@@ -35,6 +45,11 @@ ...@@ -35,6 +45,11 @@
<td>{session.id}</td> <td>{session.id}</td>
<td></td> <td></td>
<td>{session.usersList()}</td> <td>{session.usersList()}</td>
<td
><button on:click|preventDefault|stopPropagation={() => deleteSession(session)}
>X</button
></td
>
</tr> </tr>
{/each} {/each}
</tbody> </tbody>
......
...@@ -25,10 +25,10 @@ ...@@ -25,10 +25,10 @@
{#if message} {#if message}
<div class="errorCard">{message}</div> <div class="errorCard">{message}</div>
{/if} {/if}
<label for="username">Username:</label> <label for="username">Nom d'utilisateur:</label>
<input type="text" id="username" name="username" bind:value={username} required /> <input type="text" id="username" name="username" bind:value={username} required />
<label for="password">Password:</label> <label for="password">Mot de passe:</label>
<input type="password" id="password" name="password" bind:value={password} required /> <input type="password" id="password" name="password" bind:value={password} required />
<button type="submit" on:click|preventDefault={login}>Login</button> <button type="submit" on:click|preventDefault={login}>Login</button>
......
0% Chargement en cours ou .
You are about to add 0 people to the discussion. Proceed with caution.
Terminez d'abord l'édition de ce message.
Veuillez vous inscrire ou vous pour commenter