login
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
# this file is ignored when the app is built since we're using SSG
|
||||
|
||||
NUXT_PUBLIC_API_BASE="http://127.0.0.1:8080/api"
|
||||
NUXT_COOKIES_SECURE="false"
|
||||
NUXT_COOKIES_SAME_SITE="strict"
|
||||
|
||||
20
frontend/composables/useAuthSession.ts
Normal file
20
frontend/composables/useAuthSession.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { AuthSession } from '~/types/auth';
|
||||
|
||||
const SAME_SITE_SETTINGS = ['strict', 'lax', 'none'] as const;
|
||||
|
||||
export function useAuthSession() {
|
||||
const config = useRuntimeConfig().public;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const sameSite = SAME_SITE_SETTINGS.includes(config.cookiesSameSite as any)
|
||||
? (config.cookiesSameSite as (typeof SAME_SITE_SETTINGS)[number])
|
||||
: 'strict';
|
||||
|
||||
return useCookie('auth_session', {
|
||||
default: () => null as AuthSession | null,
|
||||
secure: config.cookiesSecure.toLowerCase() === 'true',
|
||||
sameSite,
|
||||
path: '/',
|
||||
httpOnly: false,
|
||||
});
|
||||
}
|
||||
42
frontend/lib/api/auth/login.ts
Normal file
42
frontend/lib/api/auth/login.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { toast } from 'vue-sonner';
|
||||
import type { ApiResponse } from '~/types/api';
|
||||
|
||||
export async function loginUser(
|
||||
email: string,
|
||||
password: string
|
||||
): Promise<{ success: boolean }> {
|
||||
const { data, error } = await useFetch<ApiResponse<{ token: string }>>(
|
||||
getApiUrl('auth/login'),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
password: password,
|
||||
}),
|
||||
responseType: 'json',
|
||||
}
|
||||
);
|
||||
|
||||
if (data.value == null) {
|
||||
toast.error('Login', {
|
||||
description: error.value?.data ?? 'Failed to login',
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
useAuthSession().value = { type: 'WarrenAuth', id: data.value.data.token };
|
||||
|
||||
toast.success('Login', {
|
||||
description: `Successfully logged in`,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
15
frontend/lib/api/index.ts
Normal file
15
frontend/lib/api/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export function getApiHeaders(
|
||||
includeAuth: boolean = true
|
||||
): Record<string, string> {
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
if (includeAuth) {
|
||||
const authSession = useAuthSession().value;
|
||||
|
||||
if (authSession != null) {
|
||||
headers['authorization'] = `${authSession.type} ${authSession.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
@@ -2,12 +2,14 @@ import { toast } from 'vue-sonner';
|
||||
import type { DirectoryEntry } from '~/types';
|
||||
import type { ApiResponse } from '~/types/api';
|
||||
import type { Warren } from '~/types/warrens';
|
||||
import { getApiHeaders } from '.';
|
||||
|
||||
export async function getWarrens(): Promise<Record<string, Warren>> {
|
||||
const { data, error } = await useFetch<ApiResponse<{ warrens: Warren[] }>>(
|
||||
getApiUrl('warrens'),
|
||||
{
|
||||
method: 'GET',
|
||||
headers: getApiHeaders(),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ export default defineNuxtConfig({
|
||||
runtimeConfig: {
|
||||
public: {
|
||||
apiBase: '/api',
|
||||
cookiesSecure: 'false',
|
||||
cookiesSameSite: 'strict',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
CardContent,
|
||||
CardFooter,
|
||||
} from '@/components/ui/card';
|
||||
import { loginUser } from '~/lib/api/auth/login';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'auth',
|
||||
@@ -14,6 +15,35 @@ definePageMeta({
|
||||
|
||||
// TODO: Get this from the backend
|
||||
const OPEN_ID = false;
|
||||
const loggingIn = ref(false);
|
||||
const email = ref('');
|
||||
const password = ref('');
|
||||
|
||||
const inputValid = computed(
|
||||
() => email.value.trim().length > 0 && password.value.trim().length > 0
|
||||
);
|
||||
|
||||
async function submit() {
|
||||
if (!inputValid.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
loggingIn.value = true;
|
||||
|
||||
const { success } = await loginUser(email.value, password.value);
|
||||
|
||||
if (success) {
|
||||
await navigateTo({ path: '/' });
|
||||
}
|
||||
|
||||
loggingIn.value = false;
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
submit();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -29,24 +59,30 @@ const OPEN_ID = false;
|
||||
<Label for="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
v-model="email"
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
autocomplete="off"
|
||||
required
|
||||
@keydown="onKeyDown"
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="password">Password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
v-model="password"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
required
|
||||
@keydown="onKeyDown"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter class="flex-col gap-2">
|
||||
<Button class="w-full">Sign in</Button>
|
||||
<Button class="w-full" :disabled="!inputValid" @click="submit"
|
||||
>Sign in</Button
|
||||
>
|
||||
<Button class="w-full" variant="outline" :disabled="!OPEN_ID"
|
||||
>OpenID Connect</Button
|
||||
>
|
||||
|
||||
5
frontend/types/auth.ts
Normal file
5
frontend/types/auth.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type AuthSessionType = 'WarrenAuth';
|
||||
export interface AuthSession {
|
||||
type: AuthSessionType;
|
||||
id: string;
|
||||
}
|
||||
Reference in New Issue
Block a user