From aad843fec90cda863e5c08734d55a092778fdd25 Mon Sep 17 00:00:00 2001 From: Kittapath Date: Wed, 15 Nov 2023 09:51:52 +0700 Subject: [PATCH 1/3] =?UTF-8?q?=E0=B9=80=E0=B8=9E=E0=B8=B4=E0=B9=88?= =?UTF-8?q?=E0=B8=A1=20login=20keycloak?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/main.ts | 7 +++++-- src/plugins/axios.ts | 25 +++++++++++++++++++++++ src/plugins/filters.ts | 22 ++++++++++++++++++++ src/plugins/http.ts | 45 +++++++++++++++++++++++++++++++++++++++++ src/plugins/keycloak.ts | 23 +++++++++++++++++++++ src/router/index.ts | 41 +++++++++++++++++++++++++++++++++++-- 7 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/plugins/axios.ts create mode 100644 src/plugins/filters.ts create mode 100644 src/plugins/http.ts create mode 100644 src/plugins/keycloak.ts diff --git a/package.json b/package.json index b872917..ecec221 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@googlemaps/js-api-loader": "^1.16.2", "@quasar/extras": "^1.15.8", "@vuepic/vue-datepicker": "^5.2.1", + "keycloak-js": "^22.0.5", "moment": "^2.29.4", "pinia": "^2.1.4", "quasar": "^2.11.1", diff --git a/src/main.ts b/src/main.ts index 16db08d..c99174c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -9,8 +9,9 @@ import '@vuepic/vue-datepicker/dist/main.css' import quasarUserOptions from './quasar-user-options' import 'quasar/src/css/index.sass' -import th from "quasar/lang/th"; +import th from 'quasar/lang/th' +import http from '@/plugins/http' const app = createApp(App) const pinia = createPinia() @@ -30,7 +31,9 @@ app.use(Quasar, { app.component( 'datepicker', - defineAsyncComponent(() => import('@vuepic/vue-datepicker')) + defineAsyncComponent(() => import('@vuepic/vue-datepicker')), ) +app.config.globalProperties.$http = http + app.mount('#app') diff --git a/src/plugins/axios.ts b/src/plugins/axios.ts new file mode 100644 index 0000000..79bba4e --- /dev/null +++ b/src/plugins/axios.ts @@ -0,0 +1,25 @@ +import axios from "axios" +import config from "process" +// import { dotnetPath } from "../path/axiosPath"; +// import { getToken } from "@baloise/vue-keycloak"; +import keycloak from "../plugins/keycloak" + +const axiosInstance = axios.create({ + withCredentials: false, +}) + +// axiosInstance.defaults.baseURL = dotnetPath; +axiosInstance.interceptors.request.use( + async (config) => { + const token = await keycloak.token + config.headers = { + Authorization: `Bearer ${token}`, + } + return config + }, + (error) => { + Promise.reject(error) + } +) + +export default axiosInstance diff --git a/src/plugins/filters.ts b/src/plugins/filters.ts new file mode 100644 index 0000000..d5f1d75 --- /dev/null +++ b/src/plugins/filters.ts @@ -0,0 +1,22 @@ +/** + * GLOABL Filters + * - ไฟล์นี้จะไว้เก็บฟังก์ชันง่าย ๆ พวก Helper Functions ทั้งหลาย + */ + + +const filters = { + + /** + * ฟังก์ชัน compactNumber ใช้แปลงตัวเลขยาว ๆ ให้กลายเป็นเลขสั้น ๆ แบบที่พวก Social Media ชอบใช้กัน เช่น 1,000 แปลงเป็น 1K หรือ 1,000,000 แปลงเป็น 1M + * วิธีใช้ : {{ $filters.compactNumber(value) }} + * + * @param val รับค่าพารามิเตอร์เป็นตัวแปรชนิดตัวเลข + * @returns คืนค่าเป็นตัวเลขที่แปลงค่าแล้ว + */ + compactNumber (val: number) { + const formatter = Intl.NumberFormat('en', { notation: 'compact'}) + return formatter.format(val) + } +} + +export default filters; \ No newline at end of file diff --git a/src/plugins/http.ts b/src/plugins/http.ts new file mode 100644 index 0000000..494c9aa --- /dev/null +++ b/src/plugins/http.ts @@ -0,0 +1,45 @@ +import Axios, { type AxiosRequestConfig, type AxiosResponse } from "axios"; +import keycloak from "./keycloak"; + +const http = Axios.create({ + timeout: 1000000000, // เพิ่มค่า timeout + headers: { + "X-Requested-With": "XMLHttpRequest", + }, +}); + +http.interceptors.request.use( + async function (config: AxiosRequestConfig) { + await keycloak.updateToken(1); + config.headers = config.headers ?? {}; + const token = keycloak.token; + // const token = localStorage.getItem("access_token") + // const token = + // "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxU2VKV2dVRFVlNXZwNS13Q1ZHaG9lT2l4bDJTTkdKemthLU5ZN211NXZJIn0.eyJleHAiOjE2NzI0MTI1NDksImlhdCI6MTY3MjM3NjU0OSwiYXV0aF90aW1lIjoxNjcyMzc2NTQ5LCJqdGkiOiI1MTVhY2IwNC1jODQ3LTQzM2YtYjUxOC03ODUzMzJhY2ZjNWYiLCJpc3MiOiJodHRwczovL2tleWNsb2FrLmZyYXBwZXQuc3lub2xvZ3kubWUvYXV0aC9yZWFsbXMvYm1hLWVociIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJlZmM5YjRlMC1mZGU2LTQ1NDQtYmU1OS1lMTA0MjEwMjUzZjAiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJibWEtZWhyIiwibm9uY2UiOiI3NjMyMGI3ZS0xZTMxLTQ5ODYtYWIzOC1iOTUyYjFlODY3OGYiLCJzZXNzaW9uX3N0YXRlIjoiMDZlNTBkZjktNzAyNi00ZGIwLTkxMjgtMWY3Y2FiYTRkNDEyIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwczovL2xvY2FsaG9zdDo3MDA2Il0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLWJtYS1laHIiLCJvZmZsaW5lX2FjY2VzcyIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIiwic2lkIjoiMDZlNTBkZjktNzAyNi00ZGIwLTkxMjgtMWY3Y2FiYTRkNDEyIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsInJvbGUiOlsiZGVmYXVsdC1yb2xlcy1ibWEtZWhyIiwib2ZmbGluZV9hY2Nlc3MiLCJhZG1pbiIsInVtYV9hdXRob3JpemF0aW9uIl0sIm5hbWUiOiJTeXN0ZW0gQWRtaW5pc3RyYXRvciIsInByZWZlcnJlZF91c2VybmFtZSI6ImFkbWluIiwiZ2l2ZW5fbmFtZSI6IlN5c3RlbSIsImZhbWlseV9uYW1lIjoiQWRtaW5pc3RyYXRvciIsImVtYWlsIjoiYWRtaW5AbG9jYWxob3N0In0.xmfJ3pzI-jLYsaiFXyjTW7gfAEpvUmMVsp9BsB1CfRCVOKiGBbuZhnQY8W-1SWVAx1NjJ55L-zMHPK6hk1dRPLbEse3DlIBZw04W9j8m-Wz3eqdHf_UCjmrXb8qAwkeq0Iaxq9mVfJJeQWeKhFBi-Ff8ek4hCXTYDICXS8ny_BaC5WkyrefHQ2xBqQjwRyoxsg4IoVMjXYNb8L9A-4BNlRfs928SqgFYCRlF5h6zw_rC0XoLrGTmqeacBdpey-r3j2g_lTqWy8mQg2T9s65IDqW3kFPOsr0SVO88sjlFbN9Et0L57RmiqORk_RwzbWg-_Yb6dOuolXsnjBOhOoTzkA"; + if (token) config.headers.Authorization = `Bearer ${token}`; + return config; + }, + function (error: any) { + return Promise.reject(error); + } +); + +http.interceptors.response.use( + function (response: AxiosResponse) { + return response; + }, + function (error: any) { + if (typeof error !== undefined) { + // eslint-disable-next-line no-prototype-builtins + if (error.hasOwnProperty("response")) { + if (error.response.status === 401 || error.response.status === 403) { + // Store.commit("SET_ERROR_MESSAGE", error.response.data.message); + // Store.commit("REMOVE_ACCESS_TOKEN") + } + } + } + return Promise.reject(error); + } +); + +export default http; diff --git a/src/plugins/keycloak.ts b/src/plugins/keycloak.ts new file mode 100644 index 0000000..ba64df9 --- /dev/null +++ b/src/plugins/keycloak.ts @@ -0,0 +1,23 @@ +/** + * front connect to keycloak + */ +import Keycloak from "keycloak-js"; +// import config from "../app.config"; +// import http from "../shared/http"; +// import router from "../router"; + +const initOptions = { + // realm: import.meta.env.VITE_REALM_KEYCLOAK, + // clientId: import.meta.env.VITE_CLIENTID_KEYCLOAK, + // url: import.meta.env.VITE_URL_KEYCLOAK, + realm: "bma-ehr", + clientId: "bma-ehr-vue3", + url: "https://id.frappet.synology.me/", +}; //option keycloak ที่จะ connect + +const keycloak = new Keycloak(initOptions); + +keycloak.onAuthSuccess = () => {}; //เพิ่มlogin สำเร็จจะมาทำฟังก์ชันนี้ + +await keycloak.init({ onLoad: "check-sso", checkLoginIframe: false }); //ทำการ connect keycloak +export default keycloak; diff --git a/src/router/index.ts b/src/router/index.ts index 9a565ec..57785e5 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,6 +1,8 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '@/views/HomeView.vue' +import keycloak from '@/plugins/keycloak' + const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ @@ -8,6 +10,9 @@ const router = createRouter({ path: '/', name: 'home', component: HomeView, + meta: { + Auth: true, + }, }, { path: '/about', @@ -16,11 +21,17 @@ const router = createRouter({ // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import('@/views/AboutView.vue'), + meta: { + Auth: true, + }, }, { path: '/history', name: 'history', component: () => import('@/views/HistoryView.vue'), + meta: { + Auth: true, + }, }, // { // path: '/camera', @@ -30,13 +41,39 @@ const router = createRouter({ /** * 404 Not Found * ref: https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route - */ + */ { path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/ErrorNotFoundPage.vue'), - }, + meta: { + Auth: true, + }, + }, ], }) +router.beforeEach((to, from, next) => { + if (to.meta.Auth) { + if (!keycloak.authenticated) { + keycloak.login({ + redirectUri: `${window.location.protocol}//${window.location.host}${to.path}`, + locale: 'th', + }) + } else { + // keycloak.updateToken(60); + // const role = keycloak.tokenParsed?.role + // if (role.includes(to.meta.Role)) { + next() + // } else { + // next({ path: '' }) + // // next(); + // } + } + } else { + next() + } + // next(); +}) + export default router From 39532a4a0fc23207f266f46fdd56d23497dad0de Mon Sep 17 00:00:00 2001 From: Kittapath Date: Wed, 15 Nov 2023 09:56:36 +0700 Subject: [PATCH 2/3] =?UTF-8?q?=E0=B9=80=E0=B8=9E=E0=B8=B4=E0=B9=88?= =?UTF-8?q?=E0=B8=A1keycloak=E0=B9=83=E0=B8=99env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.production | 6 +++++- src/plugins/keycloak.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.env.production b/.env.production index 88a4288..e9ad3c6 100644 --- a/.env.production +++ b/.env.production @@ -1,3 +1,7 @@ VITE_REALM_KEYCLOAK=VITE_REALM_KEYCLOAK VITE_CLIENTID_KEYCLOAK=VITE_CLIENTID_KEYCLOAK -VITE_URL_KEYCLOAK=VITE_URL_KEYCLOAK \ No newline at end of file +VITE_URL_KEYCLOAK=VITE_URL_KEYCLOAK + +# VITE_REALM_KEYCLOAK: "bma-ehr" +# VITE_CLIENTID_KEYCLOAK: "bma-ehr-exam-vue3" +# VITE_URL_KEYCLOAK: "https://id.frappet.synology.me/" \ No newline at end of file diff --git a/src/plugins/keycloak.ts b/src/plugins/keycloak.ts index ba64df9..7b7c689 100644 --- a/src/plugins/keycloak.ts +++ b/src/plugins/keycloak.ts @@ -7,12 +7,12 @@ import Keycloak from "keycloak-js"; // import router from "../router"; const initOptions = { - // realm: import.meta.env.VITE_REALM_KEYCLOAK, - // clientId: import.meta.env.VITE_CLIENTID_KEYCLOAK, - // url: import.meta.env.VITE_URL_KEYCLOAK, - realm: "bma-ehr", - clientId: "bma-ehr-vue3", - url: "https://id.frappet.synology.me/", + realm: import.meta.env.VITE_REALM_KEYCLOAK, + clientId: import.meta.env.VITE_CLIENTID_KEYCLOAK, + url: import.meta.env.VITE_URL_KEYCLOAK, +// realm: "bma-ehr", +// clientId: "bma-ehr-vue3", +// url: "https://id.frappet.synology.me/", }; //option keycloak ที่จะ connect const keycloak = new Keycloak(initOptions); From cdb9ec5b97d3b12fc3599569d6b6e7f841461cde Mon Sep 17 00:00:00 2001 From: waruneeta Date: Wed, 15 Nov 2023 10:17:25 +0700 Subject: [PATCH 3/3] add config & api --- src/api/api.checkin.ts | 7 +++++++ src/api/api.history.ts | 6 ++++++ src/api/index.ts | 29 +++++++++++++++++++++++++++++ src/app.config.ts | 20 ++++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 src/api/api.checkin.ts create mode 100644 src/api/api.history.ts create mode 100644 src/api/index.ts create mode 100644 src/app.config.ts diff --git a/src/api/api.checkin.ts b/src/api/api.checkin.ts new file mode 100644 index 0000000..011bfb1 --- /dev/null +++ b/src/api/api.checkin.ts @@ -0,0 +1,7 @@ +import env from "./index"; +const leave = `${env.API_URI}/leave`; + +export default { + checkin: () => `${leave}/check-in`, + checkTime: () => `${leave}/check-time`, +}; diff --git a/src/api/api.history.ts b/src/api/api.history.ts new file mode 100644 index 0000000..c6f78c9 --- /dev/null +++ b/src/api/api.history.ts @@ -0,0 +1,6 @@ +import env from "./index"; +const history = `${env.API_URI}/leave/check-in/history`; + +export default { + history +}; diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..66bac05 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,29 @@ +/**config api */ +import { ref } from "vue"; + +const env = ref(process.env.NODE_ENV || "development"); +// if (process.env.VUE_APP_TEST) { +// env = "test"; +// } + +const config = ref({ + development: { + // API_URI: "https://localhost:7260/api", + API_URI: "https://bma-ehr.frappet.synology.me/api/v1", + }, + test: { + API_URI: "http://localhost:5010/api/v1", + }, + production: { + // API_URI: "https://localhost:5010", + API_URI: `${window.location.protocol}//${window.location.host}/api/v1`, + }, +}); + +const API_URI = ref(config.value[env.value].API_URI); + +export default { + env: env.value, + config: config.value, + API_URI: API_URI.value, +}; diff --git a/src/app.config.ts b/src/app.config.ts new file mode 100644 index 0000000..d0fbb30 --- /dev/null +++ b/src/app.config.ts @@ -0,0 +1,20 @@ +/**ใช้รวมไฟล์ย่อยๆ ของ api แต่ละไฟล์ */ + +/** API ระบบลงเวลา */ +import leave from "@/api/api.checkin"; +import history from "@/api/api.history"; + +// environment variables +export const s3ClusterUrl = import.meta.env.VITE_S3CLUSTER_PUBLIC_URL; + +const API = { + /**leave */ + ...leave, + /**history */ + ...history, +}; + +export default { + API: API, + s3ClusterUrl, +};