Merge branch 'dev/front-methapon' into dev/front

This commit is contained in:
Methapon2001 2023-11-29 16:59:06 +07:00
commit 2dd8b2b92d
No known key found for this signature in database
GPG key ID: 849924FEF46BD132
8 changed files with 72 additions and 172 deletions

View file

@ -1,7 +1,5 @@
<script setup lang="ts"></script>
<template> <template>
<div id="azay-admin-app"> <div>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition> <transition>
<component :is="Component" /> <component :is="Component" />
@ -9,5 +7,3 @@
</router-view> </router-view>
</div> </div>
</template> </template>
<style scoped></style>

View file

@ -1,10 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { getUsername, logout } from '@/services/KeyCloakService'
import { ref } from 'vue' import { ref } from 'vue'
import KeyCloakService from '@/services/KeyCloakService'
const dropdownOpen = ref<boolean>(false) const dropdownOpen = ref<boolean>(false)
const accountName = ref<string>() const accountName = ref<string>(getUsername())
accountName.value = KeyCloakService.GetUserName()
</script> </script>
<template> <template>
@ -32,16 +30,7 @@ accountName.value = KeyCloakService.GetUserName()
</div> </div>
<q-btn-dropdown stretch flat v-model="dropdownOpen"> <q-btn-dropdown stretch flat v-model="dropdownOpen">
<q-list> <q-list>
<q-item <q-item clickable v-close-popup tabindex="0" @click="() => logout()">
clickable
v-close-popup
tabindex="0"
@click="
() => {
KeyCloakService.CallLogOut()
}
"
>
<q-item-section avatar> <q-item-section avatar>
<q-avatar icon="logout" color="primary" text-color="white" caption> <q-avatar icon="logout" color="primary" text-color="white" caption>
</q-avatar> </q-avatar>
@ -50,7 +39,6 @@ accountName.value = KeyCloakService.GetUserName()
<q-item-label>Logout</q-item-label> <q-item-label>Logout</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-separator inset spaced></q-separator>
</q-list> </q-list>
</q-btn-dropdown> </q-btn-dropdown>
</template> </template>

View file

@ -5,18 +5,18 @@ import th from 'quasar/lang/th'
import App from './App.vue' import App from './App.vue'
import HttpService from '@/services/HttpService'
import quasarUserOptions from './quasar-user-options' import quasarUserOptions from './quasar-user-options'
import router from './router'
import 'quasar/src/css/index.sass' import 'quasar/src/css/index.sass'
import { login } from './services/KeyCloakService'
await login()
const app = createApp(App) const app = createApp(App)
const pinia = createPinia() const pinia = createPinia()
app.use(router) app.use((await import('./router')).default)
app.use(pinia) app.use(pinia)
app.use(Quasar, { app.use(Quasar, {
...quasarUserOptions, ...quasarUserOptions,
plugins: { plugins: {
@ -28,9 +28,7 @@ app.use(Quasar, {
app.component( app.component(
'full-loader', 'full-loader',
defineAsyncComponent(() => import('@/components/FullLoader.vue')) defineAsyncComponent(() => import('@/components/FullLoader.vue')),
) )
app.mount('#app') app.mount('#app')
HttpService.configureAxiosKeycloak()

View file

@ -1,73 +1,52 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import UserModule from '@/modules/01_user/router' import UserModule from '@/modules/01_user/router'
import AdminModule from '@/modules/02_admin/router' import AdminModule from '@/modules/02_admin/router'
import KeyCloakService from '@/services/KeyCloakService' import { getRole, getToken, login } from '@/services/KeyCloakService'
const history = createWebHistory(import.meta.env.BASE_URL)
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history,
routes: [ routes: [
{ {
path: '/', path: '/',
name: 'UserModule', name: 'UserModule',
component: () => import('@/views/MainLayout.vue'), component: () => import('@/views/MainLayout.vue'),
beforeEnter: (_to, _from, next) => { beforeEnter: async (_to, _from, next) => {
const token = KeyCloakService.GetAccesToken() const token = await getToken()
if (token) {
next()
} else {
KeyCloakService.CallLogin(() => {
const tokenAfterLogin = KeyCloakService.GetAccesToken()
const roles = KeyCloakService.GetUserRoles()
if ( if (token) return next()
tokenAfterLogin &&
(roles.includes('user') || roles.includes('admin')) await login(async () => {
) { if (await getToken()) return next()
next() return next('/')
} else { })
console.error('ไม่สามารถดึง Token หลังจากล็อกอินได้')
next('/')
}
})
}
}, },
children: [...UserModule], children: [...UserModule],
meta: { meta: { statusAccount: false },
statusAccount: false,
},
}, },
{ {
path: '/admin', path: '/admin',
name: 'AdminModule', name: 'AdminModule',
component: () => import('@/views/MainLayout.vue'), component: () => import('@/views/MainLayout.vue'),
beforeEnter: (_to, _from, next) => { beforeEnter: async (_to, _from, next) => {
const token = KeyCloakService.GetAccesToken() const token = await getToken()
if (token) {
next()
} else {
KeyCloakService.CallLogin(() => {
const tokenAfterLogin = KeyCloakService.GetAccesToken()
const roles = KeyCloakService.GetUserRoles()
console.log(roles)
if (tokenAfterLogin && roles.includes('admin')) { if (token) return next()
next()
} else { await login(async () => {
console.error('ไม่สามารถดึง Token หลังจากล็อกอินได้') const token = await getToken()
next('/') const roles = getRole()
}
}) if (token && roles.includes('admin')) {
} return next()
}, }
meta: {
statusAccount: true, return next('/')
})
}, },
meta: { statusAccount: true },
children: [...AdminModule], children: [...AdminModule],
}, },
/**
* 404 Not Found
* ref: https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route
*/
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
name: 'NotFound', name: 'NotFound',

View file

@ -1,36 +1,11 @@
import type { AxiosInstance, InternalAxiosRequestConfig } from 'axios'
import axios from 'axios' import axios from 'axios'
import KeyCloakService from '@/services/KeyCloakService' import { getToken } from './KeyCloakService'
const HttpMethods = { const instance = axios.create()
GET: 'GET',
POST: 'POST',
DELETE: 'DELETE',
}
const _axios = axios.create() instance.interceptors.request.use(async (config) => {
const cb = (config: InternalAxiosRequestConfig) => { config.headers.Authorization = `Bearer ${await getToken()}`
config.headers.Authorization = `Bearer ${KeyCloakService.GetAccesToken()}`
return config return config
} })
const configureAxiosKeycloak = (): void => { export default instance
_axios.interceptors.request.use(
(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig => {
if (KeyCloakService.IsLoggedIn()) {
KeyCloakService.UpdateToken(cb(config))
}
return config
}
)
}
const getAxiosClient = (): AxiosInstance => _axios
const HttpService = {
HttpMethods,
configureAxiosKeycloak,
getAxiosClient,
}
export default HttpService

View file

@ -1,77 +1,42 @@
import Keycloak from 'keycloak-js' import Keycloak from 'keycloak-js'
const keycloakInstance = new Keycloak() const keycloak = new Keycloak()
interface CallbackOneParam<T1 = void, T2 = void> { export async function login(cb?: (...args: any[]) => void) {
(param1: T1): T2 const auth = await keycloak
} .init({
/** onLoad: 'login-required',
* Initializes Keycloak instance and calls the provided callback function if successfully authenticated. responseMode: 'query',
* checkLoginIframe: false,
* @param onAuthenticatedCallback
*/
const Login = (onAuthenticatedCallback: CallbackOneParam): void => {
keycloakInstance
.init({ onLoad: 'login-required' })
.then(function (authenticated) {
authenticated ? onAuthenticatedCallback() : alert('non authenticated')
})
.catch((e) => {
console.dir(e)
console.log(`keycloak init exception: ${e}`)
}) })
.catch((e) => console.dir(e))
if (auth && cb) cb()
} }
const UserName = (): string | undefined => export async function logout() {
keycloakInstance?.tokenParsed?.preferred_username await keycloak.logout()
}
const Token = (): string | undefined => keycloakInstance?.token export async function getToken() {
const IdToken = (): string | undefined => keycloakInstance?.idToken await keycloak.updateToken(60).catch(() => login())
return keycloak.token
}
const LogOut = () => keycloakInstance.logout() export function getUsername(): string {
return keycloak.tokenParsed?.preferred_username
}
/* export function getRole(): string[] {
const UserRoles = (): string[] | undefined => { const decoded = keycloak.tokenParsed
if (keycloakInstance.resourceAccess === undefined) return undefined;
if (keycloakInstance.resourceAccess["express-client"] === undefined) return undefined;
return keycloakInstance.resourceAccess["express-client"].roles; if (decoded && decoded.resource_access && decoded.azp) {
}; return decoded.resource_access[decoded.azp].roles
*/
const UserRoles = () => {
const decoded = DecodeToken()
if (decoded && decoded.resource_access) {
return decoded.resource_access[decoded.azp ?? ''].roles
} }
return [] return []
} }
const updateToken = (successCallback: any) => export function isLoggedIn() {
keycloakInstance.updateToken(5).then(successCallback).catch(doLogin) return !!keycloak.token
const doLogin = keycloakInstance.login
const isLoggedIn = () => !!keycloakInstance.token
const DecodeToken = () => {
return keycloakInstance.tokenParsed
} }
const DecodeIdToken = () => {
return keycloakInstance.idTokenParsed
}
const KeycloakService = {
CallLogin: Login,
GetUserName: UserName,
GetAccesToken: Token,
GetIdToken: IdToken,
CallLogOut: LogOut,
GetUserRoles: UserRoles,
UpdateToken: updateToken,
IsLoggedIn: isLoggedIn,
GetDecodeToken: DecodeToken,
GetDecodeIdToken: DecodeIdToken,
}
export default KeycloakService

View file

@ -1,9 +1,8 @@
import { ref } from 'vue' import { ref } from 'vue'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { useLoader } from '@/stores/loader' import { useLoader } from '@/stores/loader'
import HttpService from '@/services/HttpService' import axiosClient from '@/services/HttpService'
const axiosClient = HttpService.getAxiosClient()
const apiEndpoint: string = import.meta.env.VITE_API_ENDPOINT const apiEndpoint: string = import.meta.env.VITE_API_ENDPOINT
export interface EhrFolder { export interface EhrFolder {

View file

@ -29,9 +29,9 @@ const { loader } = storeToRefs(loaderStore)
</q-header> </q-header>
<q-page-container> <q-page-container>
<QPage> <q-page>
<router-view :key="$route.fullPath" /> <router-view :key="$route.fullPath" />
</QPage> </q-page>
</q-page-container> </q-page-container>
<full-loader :visibility="loader" /> <full-loader :visibility="loader" />
</q-layout> </q-layout>