This repository has been archived by the owner on Feb 4, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added auth - login/logout/session (vue app)
- Loading branch information
Showing
14 changed files
with
220 additions
and
22 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 14 additions & 3 deletions
17
src/Overmoney.Portal/src/components/generic/NavigationMenu.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/Overmoney.Portal/src/data_access/models/auth/loginResponse.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export type UserProfile = { | ||
id: number, | ||
email: string | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
export type UserContext = { | ||
userId: number; | ||
token: string; | ||
expiresOn: Date | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |