Skip to content

Commit

Permalink
add route guard and login fnc
Browse files Browse the repository at this point in the history
  • Loading branch information
tmeftah committed Aug 12, 2024
1 parent 6ec178b commit 5e55f34
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 13 deletions.
2 changes: 1 addition & 1 deletion frontend/src/layouts/LoginLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<q-page-container class="pt-0">
<q-page>
<div class="row" style="height: 100vh">
<div class="col-12 flex content-center justify-center">
<div class="col flex content-center justify-center">
<router-view />
</div>
</div>
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/layouts/MainLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
Aora.
</q-toolbar-title>
<!-- Orion Inova -->
<q-btn flat round icon="logout" class="q-mr-xs" @click="logout" />
</q-toolbar>
</q-header>

Expand All @@ -32,6 +33,9 @@

<script setup>
import { ref } from "vue";
import { useAuthStore } from "../stores/auth";
import { useRouter } from "vue-router";
import EssentialLink from "components/EssentialLink.vue";
defineOptions({
Expand Down Expand Up @@ -61,8 +65,15 @@ const linksList = [
const leftDrawerOpen = ref(false);
const miniState = ref(true);
const authStore = useAuthStore();
const router = useRouter();
function toggleLeftDrawer() {
leftDrawerOpen.value = !leftDrawerOpen.value;
}
function logout() {
authStore.logout();
router.push("/login");
}
</script>
56 changes: 44 additions & 12 deletions frontend/src/pages/LoginPage.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
<template>
<div class="row">
<q-card class="col-0 col-sm-5 bg-primary xs-hide">
<div class="row q-px-xl q-pb-xl full-height flex flex-center">
<div class="text-h4 text-uppercase text-white" style="min-width: 220px">
<div class="row flex justify-center">
<q-card
class="col-sm-5 bg-primary xs-hide no-shadow"
square
bordered
id="leftcard"
>
<div class="row q-px-xl q-pb-xl full-height content-center">
<div class="text-h4 text-uppercase text-white">
<span class="text-weight-bolder">Aora.</span>

<p class="text-caption text-weight-bold">Ahead Of Rest, Always.</p>
Expand All @@ -13,7 +18,7 @@
</div>
</q-card>

<q-card class="col-12 col-sm-5">
<q-card class="col-12 col-sm-5 no-shadow" bordered id="rightcard">
<div class="row q-pa-sm-sm q-pa-md">
<div class="col-12">
<q-card-section>
Expand All @@ -39,8 +44,8 @@
name="Email"
:rules="[
(val) => !!val || 'Email required!',
(val, rules) =>
rules.email(val) || 'Please enter a valid email address',
// (val, rules) =>
// rules.email(val) || 'Please enter a valid email address',
]"
/>

Expand All @@ -53,7 +58,7 @@
:rules="[
(val) => !!val || 'Please enter a password',
(val) =>
!(val.length <= 8) || 'Please type more than 8 characters',
!(val.length <= 3) || 'Please type more than 8 characters',
]"
>
<template v-slot:append>
Expand Down Expand Up @@ -105,20 +110,47 @@

<script setup>
import { ref, reactive } from "vue";
import { useRouter } from "vue-router";
import { useAuthStore } from "../stores/auth";
const isPwd = ref(true);
const authStore = useAuthStore();
const router = useRouter();
const user = reactive({
email: null,
password: null,
});
const form = ref(null);
const submit = async () => {
async function submit() {
if (form.value.validate()) {
console.log("valid..");
await authStore
.login(user.email, user.password)
.then(() => {
authStore.getCurrentUser().then(() => {
if (authStore.user) {
router.push("/");
}
});
})
.catch((error) => {
console.log("Errorx");
});
} else {
console.log("err validate form...");
console.log("not valid form");
}
};
}
</script>

<style scoped>
#leftcard {
border-top-left-radius: 4px !important;
border-bottom-left-radius: 4px !important;
z-index: 2;
right: -3px;
}
</style>
18 changes: 18 additions & 0 deletions frontend/src/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
createWebHashHistory,
} from "vue-router";
import routes from "./routes";
import { useAuthStore } from "../stores/auth";

/*
* If not building with SSR mode, you can
Expand Down Expand Up @@ -33,5 +34,22 @@ export default route(function (/* { store, ssrContext } */) {
history: createHistory(process.env.VUE_ROUTER_BASE),
});

Router.beforeEach(async (to) => {
// redirect to login page if not logged in and trying to access a restricted page
const publicPages = ["/login"];
const authRequired = !publicPages.includes(to.path);
const authStore = useAuthStore();

if (authRequired && !authStore.token) {
return {
path: "/login",
};
} else if (to.path.includes("/login") && authStore.token) {
return {
path: "/",
};
}
});

return Router;
});
172 changes: 172 additions & 0 deletions frontend/src/stores/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { defineStore } from "pinia";
import { Notify } from "quasar";

const baseUrl = `${process.env.API}`;

export const useAuthStore = defineStore("auth", {
state: () => ({
user: JSON.parse(localStorage.getItem("user")) || null,
token: localStorage.getItem("token") || null,
refreshTokenTimeout: localStorage.getItem("refreshTokenTimeout") || null,
}),

getters: {
current_user(state) {
return state.user;
},
},

actions: {
async login(email, password) {
const formData = new URLSearchParams();
formData.append("grant_type", "password");
formData.append("username", email);
formData.append("password", password);
try {
const response = await fetch(`${baseUrl}/token`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
body: formData.toString(),
});

// Check for HTTP errors
if (!response.ok) {
// Extract error message if available
const errorData = await response.json().catch(() => ({})); // Parsing might fail, default to empty object
const errorMessage =
errorData.detail || `Error: ${response.statusText}`;

throw new Error(errorMessage);
}

// Parse the success response
const data = await response.json();
this.token = data.access_token;
localStorage.setItem("token", this.token);

Notify.create({
color: "positive",
position: "bottom",
message: "you are loged in",
icon: "done",
});

//
} catch (error) {
// Handle network errors and HTTP errors
if (error.name === "TypeError") {
// This typically indicates a network error
console.error("Network error: Could not reach the server");
Notify.create({
color: "negative",
position: "bottom",
message: error.message,
icon: "report_problem",
});
} else {
// HTTP error, or some other error
console.error(`API error: ${error.message}`);
Notify.create({
color: "negative",
position: "bottom",
message: error.message,
icon: "report_problem",
});
}

// You can rethrow the error or handle it in some way, e.g., user notification
throw error; // Optionally rethrow if you want to propagate the error
}
},

async getCurrentUser() {
try {
const response = await fetch(`${baseUrl}/users/me/`, {
method: "GET",
headers: {
Accept: "application/json",
Authorization: "Bearer " + this.token,
},
});

if (!response.ok) {
// Extract error message if available
const errorData = await response.json().catch(() => ({})); // Parsing might fail, default to empty object
const errorMessage =
errorData.message || `Error: ${response.statusText}`;
throw new Error(errorMessage);
}

const data = await response.json();
this.user = data;
localStorage.setItem("user", JSON.stringify(this.user));

//
} catch (error) {
// Handle network errors and HTTP errors
if (error.name === "TypeError") {
// This typically indicates a network error
console.error("Network error: Could not reach the server");
Notify.create({
color: "negative",
position: "bottom",
message: error.message,
icon: "report_problem",
});
} else {
// HTTP error, or some other error
console.error(`API error: ${error.message}`);
Notify.create({
color: "negative",
position: "bottom",
message: error.message,
icon: "report_problem",
});
}

// You can rethrow the error or handle it in some way, e.g., user notification
}
},

logout() {
// api.post(`${baseUrl}/revoke-token`, {}, { credentials: "include" });

this.stopRefreshTokenTimer();
this.token = null;
this.user = null;
localStorage.removeItem("token");
localStorage.removeItem("user");
},

startRefreshTokenTimer() {
// parse json object from base64 encoded jwt token
const jwtBase64 = this.token.split(".")[1];
const jwtToken = JSON.parse(atob(jwtBase64));

// set a timeout to refresh the token a minute before it expires
const expires = new Date(jwtToken.exp * 1000);
const timeout = expires.getTime() - Date.now() - 60 * 1000;
this.refreshTokenTimeout = setTimeout(this.refreshToken, timeout);
},

async refreshToken() {
this.token = await api.post(
`${baseUrl}/refresh-token`,
{
headers: {
Authorization: "Bearer " + this.token,
},
},
{ credentials: "include" }
);
this.startRefreshTokenTimer();
},

stopRefreshTokenTimer() {
clearTimeout(this.refreshTokenTimeout);
},
},
});

0 comments on commit 5e55f34

Please sign in to comment.