Skip to content
This repository has been archived by the owner on Feb 4, 2025. It is now read-only.

Commit

Permalink
Added auth - login/logout/session (vue app)
Browse files Browse the repository at this point in the history
  • Loading branch information
GrPe committed May 20, 2024
1 parent e469759 commit 368b996
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 22 deletions.
9 changes: 9 additions & 0 deletions src/Overmoney.Portal/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/Overmoney.Portal/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"dependencies": {
"axios": "^1.6.8",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"primevue": "^3.51.0",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
Expand Down
17 changes: 14 additions & 3 deletions src/Overmoney.Portal/src/components/generic/NavigationMenu.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
<template>
<nav>
<nav v-if="session.isAuthenticated">
<router-link to="/">Dashobards</router-link> |
<router-link to="/payees">Payees</router-link> |
<router-link to="/categories">Categories</router-link> |
<router-link to="/transactions">Transactions</router-link> |
<!-- <a v-for="wallet in wallets" :key="wallet.id" href="#">{{ wallet.name }}</a> | -->
<router-link to="/settings">Settings</router-link> |
<a href="#">Log out</a> |
<a href="#" @click.prevent="logout">Log out</a> |
</nav>
</template>

<script lang="ts">
import { userSessionStore } from '@/data_access/sessionStore';
import type { Wallet } from '../../data_access/models/wallet'
export default {
props: {
wallets: Array<Wallet>
},
data() {
const session = userSessionStore();
return {
session
}
},
methods: {
logout() {
this.session.logoutUser();
this.$router.push('/login');
}
}
}
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ import type { Wallet } from '../../data_access/models/wallet'
import type { Payee } from '../../data_access/models/payee'
import type { Category } from '../../data_access/models/category'
import type { Transaction } from '../../data_access/models/transaction'
import { updateTransactionRequest } from '@/data_access/models/requests/updateTransactionRequest'
import { PropType } from 'vue';
import type { updateTransactionRequest } from '@/data_access/models/requests/updateTransactionRequest'
import type { PropType } from 'vue';
export default {
props: {
Expand Down
2 changes: 1 addition & 1 deletion src/Overmoney.Portal/src/components/views/CategoryView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default {
console.log("Category cannot be null");
}
cat!.name = newName;
await this.client.updateCategory({ name: newName, userid: category.userId, id: category.id });
await this.client.updateCategory({ name: newName, userId: category.userId, id: category.id });
}
}
};
Expand Down
34 changes: 34 additions & 0 deletions src/Overmoney.Portal/src/components/views/LoginView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<form @submit.prevent="onLogin">
<input type="text" v-model="email" />
<input type="password" v-model="password" />
<button type="submit">Login</button>
</form>
</template>

<script lang="ts">
import { userSessionStore } from '@/data_access/sessionStore';
export default {
data() {
let session = userSessionStore();
return {
email: "" as string,
password: "" as string,
session
}
},
methods: {
async onLogin() {
let response = await this.session.loginUser(this.email, this.password);
if (response === false) {
console.log("nope!");
}
this.email = "";
this.password = "";
this.$router.push('/');
}
}
}
</script>
24 changes: 24 additions & 0 deletions src/Overmoney.Portal/src/data_access/authClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import axios, { isCancel, AxiosError } from "axios";
import type { LoginResponse } from "./models/auth/loginResponse";
import type { UserProfile } from "./models/auth/profile";

export class AuthClient {
async loginUser(email: string, password: string) {
const response = await axios.post<LoginResponse>(
import.meta.env.VITE_API_URL +
`Identity/login?useCookies=false&useSessionCookies=false`,
{ email, password }
);
return response.data;
}

async getUserProfile(token: string) {
const response = await axios.get<UserProfile>(
import.meta.env.VITE_API_URL + `users/profile`, {
headers: {
"Authorization": `Bearer ${token}`
}
});
return response.data;
}
}
2 changes: 1 addition & 1 deletion src/Overmoney.Portal/src/data_access/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios, { isCancel, AxiosError } from "axios";
import type { Category } from "./models/category";
import type { Payee } from "./models/payee";
import type { createCategoryRequest } from "./models/requests/createCategoryRequest";
import type { updateCategoryRequest } from "./models/requests/createCategoryRequest";
import type { updateCategoryRequest } from "./models/requests/updateCategoryRequest";
import type { createPayeeReqeuest } from "./models/requests/createPayeeReqeuest";
import type { updatePayeeRequest } from "./models/requests/updatePayeeRequest";
import type { createTransactionRequest } from "./models/requests/createTransactionRequest";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type LoginResponse = {
tokenType: string,
accessToken: string,
expiresIn: number,
refreshToken: string
}
4 changes: 4 additions & 0 deletions src/Overmoney.Portal/src/data_access/models/auth/profile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type UserProfile = {
id: number,
email: string
}
66 changes: 66 additions & 0 deletions src/Overmoney.Portal/src/data_access/sessionStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { defineStore } from "pinia";
import type { UserContext } from "./userContext";
import { AuthClient } from "./authClient";

export const userSessionStore = defineStore("user", {
state: () => {
return {
userContext: null as UserContext | null,
};
},
getters: {
apiToken(): string | undefined {
return this.userContext?.token;
},
getUserId(): number | undefined {
return this.userContext?.userId;
},
isAuthenticated(): boolean {
//no token
if (this.userContext === null || this.userContext === undefined) {
return false;
}

//no token
if (this.userContext?.token === null) {
return false;
}

//token expired
if (this.userContext?.expiresOn <= new Date()) {
return false;
}

return true;
},
},
actions: {
async loginUser(email: string, password: string): Promise<boolean> {
const client = new AuthClient();
try {
const authResponse = await client.loginUser(email, password);

const profileResponse = await client.getUserProfile(
authResponse.accessToken
);

this.userContext = {
token: authResponse.accessToken,
userId: profileResponse.id,
expiresOn: new Date(
new Date().getTime() + authResponse.expiresIn * 1000
),
};

return true;
} catch (error) {
console.log(error);
return false;
}
},
logoutUser() {
this.$reset();
},
},
persist: true,
});
2 changes: 2 additions & 0 deletions src/Overmoney.Portal/src/data_access/userContext.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export type UserContext = {
userId: number;
token: string;
expiresOn: Date
}
2 changes: 2 additions & 0 deletions src/Overmoney.Portal/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
import App from './App.vue'
import router from './router';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

const app = createApp(App);
app.use(router);
Expand Down
69 changes: 54 additions & 15 deletions src/Overmoney.Portal/src/router.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,61 @@
import { createMemoryHistory, createRouter } from 'vue-router'
import { createRouter, createWebHistory } from "vue-router";
import axios from "axios";
import { userSessionStore } from "./data_access/sessionStore";

import MainView from './components/views/testComp.vue'
import PayeeView from './components/views/PayeeView.vue'
import CategoryView from './components/views/CategoryView.vue'
import TransactionView from './components/views/TransactionView.vue'
import SettingsView from './components/views/SettingsView.vue'
import MainView from "./components/views/testComp.vue";
import PayeeView from "./components/views/PayeeView.vue";
import CategoryView from "./components/views/CategoryView.vue";
import TransactionView from "./components/views/TransactionView.vue";
import SettingsView from "./components/views/SettingsView.vue";
import LoginView from "./components/views/LoginView.vue";

const routes = [
{ path: '/', component: MainView },
{ path: '/payees', component: PayeeView },
{ path: '/categories', component: CategoryView },
{ path: '/transactions', component: TransactionView },
{ path: '/settings', component: SettingsView }
]
{ path: "/login", component: LoginView, meta: { requiresAuth: false } },
{ path: "/", component: MainView, meta: { requiresAuth: true } },
{ path: "/payees", component: PayeeView, meta: { requiresAuth: true } },
{
path: "/categories",
component: CategoryView,
meta: { requiresAuth: true },
},
{
path: "/transactions",
component: TransactionView,
meta: { requiresAuth: true },
},
{ path: "/settings", component: SettingsView, meta: { requiresAuth: true } },
];

const router = createRouter({
history: createMemoryHistory(),
history: createWebHistory(),
routes,
})
});

export default router;
const setAuthorization = (token: string) => {
axios.defaults.headers.common.Authorization = `Bearer ${token}`;
};

router.beforeEach((to, from, next) => {
const requiresAuth = to.matched.some((record) => record.meta.requiresAuth);
const session = userSessionStore();
if (session.isAuthenticated) {
setAuthorization(session.apiToken!);
}
if (requiresAuth && !session.isAuthenticated) {
next("/login");
} else if (to.path === "/login" && session.isAuthenticated) {
next("/");
} else {
next();
}
});

axios.interceptors.response.use(null, (error) => {
if (error.response.status == 403 || error.response.status == 401) {
router.push("/login");
}

return Promise.reject(error);
});

export default router;

0 comments on commit 368b996

Please sign in to comment.