From ed80866111329cac836d73154f83fa5855b103e7 Mon Sep 17 00:00:00 2001 From: ManuTrt Date: Sun, 8 Dec 2024 22:55:05 +0200 Subject: [PATCH 1/7] integrated authorization flow --- .env.development | 6 + .env.production | 6 + src/components/partials/Nav.vue | 586 ++++++++++------- src/router/index.ts | 37 +- src/services/auth.ts | 78 +-- src/services/http-interceptor.ts | 34 +- src/views/Authorized.vue | 112 ++++ src/views/HomeView.vue | 1042 ++++++++++++++++++------------ src/views/Login.vue | 150 ----- src/views/SignUp.vue | 153 ----- 10 files changed, 1154 insertions(+), 1050 deletions(-) create mode 100644 .env.development create mode 100644 .env.production create mode 100644 src/views/Authorized.vue delete mode 100644 src/views/Login.vue delete mode 100644 src/views/SignUp.vue diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..ae0354c --- /dev/null +++ b/.env.development @@ -0,0 +1,6 @@ +VUE_APP_READERBENCH_API_BASE_URL=http://localhost:8000 +VUE_APP_CLIENT_ID=gGwEwuF2PDJjnVwhUda89pHFPdobdyduOznz2enY +VUE_APP_REDIRECT_URI=http://localhost:8080/authorized +VUE_APP_READERBENCH_API_LOGIN_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/login/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_SIGNUP_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/signup/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_TOKEN_PATH=/oauth2/token/ \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..54a8450 --- /dev/null +++ b/.env.production @@ -0,0 +1,6 @@ +VUE_APP_READERBENCH_API_BASE_URL=https://readerbench.com/api/v2 +VUE_APP_CLIENT_ID=q2w45itLPXzlApDZyLgsoDGLO3HAArySZeQaQt40 +VUE_APP_REDIRECT_URI=https://readerbench.com/authorized +VUE_APP_READERBENCH_API_LOGIN_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/login/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_SIGNUP_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/signup/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_TOKEN_PATH=/oauth2/token/ \ No newline at end of file diff --git a/src/components/partials/Nav.vue b/src/components/partials/Nav.vue index 511ec6e..cb50a34 100644 --- a/src/components/partials/Nav.vue +++ b/src/components/partials/Nav.vue @@ -1,274 +1,392 @@ diff --git a/src/router/index.ts b/src/router/index.ts index 7d4137d..e7ccf28 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -12,7 +12,10 @@ const routes: Array = [ { path: '/models', name: 'models', - component: () => import('../views/ModelsView.vue') + component: () => import('../views/ModelsView.vue'), + meta: { + requiresAuth: true, + }, }, { path: '/services', @@ -25,14 +28,9 @@ const routes: Array = [ component: () => import('../views/People.vue') }, { - path: '/login', - name: 'login', - component: () => import('@/views/Login.vue') - }, - { - path: '/sign-up', - name: 'sign-up', - component: () => import('@/views/SignUp.vue') + path: '/authorized', + name: 'authorized', + component: () => import('@/views/Authorized.vue') }, { path: '/projects', @@ -69,21 +67,33 @@ const routes: Array = [ path: '/datasets', name: 'datasets', component: () => import('@/views/DatasetsView.vue'), + meta: { + requiresAuth: true, + } }, { path: '/datasets/:dataset_id', name: 'dataset', - component: () => import('@/views/DatasetView.vue') + component: () => import('@/views/DatasetView.vue'), + meta: { + requiresAuth: true, + }, }, { path: '/processing-queue', name: 'processingqueue', component: () => import('@/views/ProcessingQueueView.vue'), + meta: { + requiresAuth: true, + }, }, { path: '/profile', name: 'userprofileView', component: () => import('@/views/UserProfileView.vue'), + meta: { + requiresAuth: true, + }, }, { path: '/services/stt', @@ -105,6 +115,9 @@ const routes: Array = [ path: '/models/:id/prediction', name: 'modelpredictionview', component: () => import('@/views/ModelPredictionView.vue'), + meta: { + requiresAuth: true, + }, } ] @@ -115,13 +128,13 @@ const router = createRouter({ router.beforeEach((to, from, next) => { const route: RouteRecordNormalized = first(to.matched); - if (!isNil(route)) { if (route.meta.requiresAuth && !auth.isAuthenticated()) { const location: RouteLocationRaw = { - name: 'login', + name: 'home', query: { redirect: encodeURIComponent(to.fullPath), + sessionExpired: 'true' } }; return next(location); diff --git a/src/services/auth.ts b/src/services/auth.ts index 9c45761..5818b62 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -9,54 +9,36 @@ interface Callback { export default { isAuthenticated() { - const user = localStorage.getItem('user'); const token = localStorage.getItem('token'); - return !isNil(user) && !isNil(token); + return !isNil(token); }, - // Only for testing - dummy_successful_login(creds, redirect: string, onSuccess: Callback, onError: Callback) { - const user = { - grant_type: "password", - client_id: "8okRYORQww9VK0x3UTHAe8rl0dDvCUL6s3d6T43z", - client_secret: "sOyagG4VyhuBuVjrxzPtO848IbLSk19q7xGKp83c7FDNnNRGJRKOX3RAFI0F4SQMNtIx2Gg9xQTxPPKaTMikniMSbci7nppISLKq8EoRB5U8RZslOPu85AB4H5hI5EsZ", - username: creds.username, - password: creds.password + get_access_token(creds, redirect: string, onSuccess: Callback, onError: Callback) { + const payload = { + grant_type: "authorization_code", + client_id: process.env.VUE_APP_CLIENT_ID, + code: creds.code, + redirect_uri: creds.redirect_uri }; - if (redirect) { - router.push({ path: redirect, hash: '#logged_in' }) - } - localStorage.setItem('user', JSON.stringify(user)); - localStorage.setItem('token', 'secret_token'); - localStorage.setItem('refresh_token', 'secret_refresh_token'); - - onSuccess(user); - }, - - login(creds, redirect: string, onSuccess: Callback, onError: Callback) { + const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; - const user = { - grant_type: "password", - client_id: "8XE5YdZbhRdcLLovx3LxxhUQbKl2QPO0rvGMpWHy", - client_secret: "tybRzdzdcPykS7z358UqEMZApKJc6V36YwLBWvswHohVwFATJWmELCrXOs6zVQkEAChNlP87s1aQDxzkqu8O00PBQ1Qxv8vEf5XvGdxnneryOqxQBPri295Zve0fw42J", - username: creds.username, - password: creds.password - }; + if (!apiTokenPath) { + throw new Error("API Token path is not defined in environment variables"); + } axios - .post('/oauth2/token/', user) + .post(apiTokenPath, payload) .then(response => { if (redirect) { router.push({ path: redirect, hash: '#logged_in' }) } - localStorage.setItem('user', JSON.stringify(user)); localStorage.setItem('token', response.data.access_token); localStorage.setItem('refresh_token', response.data.refresh_token); localStorage.setItem('token_valid_until_timestamp', String(response.data.expires_in + (Math.floor(Date.now() / 1000)))); - onSuccess(user.username); + onSuccess("user"); }) .catch(error => { onError(error); @@ -66,13 +48,18 @@ export default { refresh_access_token(onSuccess: Callback, onError: Callback) { const payload = { grant_type: "refresh_token", - client_id: "8XE5YdZbhRdcLLovx3LxxhUQbKl2QPO0rvGMpWHy", - client_secret: "tybRzdzdcPykS7z358UqEMZApKJc6V36YwLBWvswHohVwFATJWmELCrXOs6zVQkEAChNlP87s1aQDxzkqu8O00PBQ1Qxv8vEf5XvGdxnneryOqxQBPri295Zve0fw42J", + client_id: process.env.VUE_APP_CLIENT_ID, refresh_token: localStorage.getItem('refresh_token') }; + const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; + + if (!apiTokenPath) { + throw new Error("API Token path is not defined in environment variables"); + } + axios - .post('/oauth2/token/', payload) + .post(apiTokenPath, payload) .then(response => { localStorage.setItem('token', response.data.access_token); @@ -86,27 +73,7 @@ export default { onError(error); }) }, - - signUp(details, onSuccess: Callback, onError: Callback) { - - const user = { - email: details.email, - password: details.password, - first_name: details.firstName, - last_name: details.lastName, - }; - - axios - .post('/users/register', user) - .then(response => { - onSuccess(response); - }) - .catch(error => { - onError(error); - }) - }, - - // To log out + logout: function (callback?: Callback) { this.clearLocalStorage(); router.push({ path: '/', hash: '#logged_out' }) @@ -117,7 +84,6 @@ export default { }, clearLocalStorage() { - localStorage.removeItem("user"); localStorage.removeItem("token"); localStorage.removeItem("token_valid_until_timestamp"); localStorage.removeItem("refresh_token"); diff --git a/src/services/http-interceptor.ts b/src/services/http-interceptor.ts index b0f3215..715ce58 100644 --- a/src/services/http-interceptor.ts +++ b/src/services/http-interceptor.ts @@ -4,8 +4,7 @@ import router from "@/router"; export function httpInterceptor() { axios.interceptors.request.use(async (request) => { - // TODO: read from env variable - request.baseURL = "https://readerbench.com/api/v2"; + request.baseURL = `${process.env.VUE_APP_READERBENCH_API_BASE_URL}`; request.headers = request.headers ?? {}; if (request.data) { @@ -13,7 +12,7 @@ export function httpInterceptor() { return request; } - if (request.data.grant_type && request.data.grant_type == "password") { + if (request.data.grant_type && request.data.grant_type == "authorization_code") { return request; } } @@ -44,38 +43,11 @@ export function httpInterceptor() { }); } catch (error) { auth.clearLocalStorage(); - - // Redirect to login page - router.push({ path: "/login", hash: "#session_expired" }); - + router.push({ path: "/", hash: "#session_expired" }); return Promise.reject("Your session has expired. Please log in again to continue."); } } return request; }); - - //TODO: Could cause problems if 401 status is not emitted due to using an invalid token -> to check - // axios.interceptors.response.use( - // (response) => { - // return response; - // }, - // (error) => { - // if (error.response) { - // const status = error.response.status; - - // if (status === 401) { - // // Handle error due to invalid token usage - // auth.clearLocalStorage(); - - // // Redirect to login page - // router.push({ path: "/login", hash: "#session_expired" }); - - // return Promise.reject("Your session has expired. Please log in again to continue."); - // } - // } - - // return Promise.reject(error); - // } - // ); } diff --git a/src/views/Authorized.vue b/src/views/Authorized.vue new file mode 100644 index 0000000..4ba8100 --- /dev/null +++ b/src/views/Authorized.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 7c514ab..8b37fd4 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -1,153 +1,273 @@ diff --git a/src/views/Login.vue b/src/views/Login.vue deleted file mode 100644 index 84b7837..0000000 --- a/src/views/Login.vue +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - diff --git a/src/views/SignUp.vue b/src/views/SignUp.vue deleted file mode 100644 index ec2a981..0000000 --- a/src/views/SignUp.vue +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - From cd77bf6746c816311ca9e9200fb0a416fef572ee Mon Sep 17 00:00:00 2001 From: ManuTrt Date: Sun, 8 Dec 2024 22:55:05 +0200 Subject: [PATCH 2/7] integrated authorization flow --- .env.development | 6 + .env.production | 6 + src/components/partials/Nav.vue | 586 ++++++++++------- src/router/index.ts | 37 +- src/services/auth.ts | 78 +-- src/services/http-interceptor.ts | 34 +- src/views/Authorized.vue | 112 ++++ src/views/HomeView.vue | 1042 ++++++++++++++++++------------ src/views/Login.vue | 150 ----- src/views/SignUp.vue | 153 ----- 10 files changed, 1154 insertions(+), 1050 deletions(-) create mode 100644 .env.development create mode 100644 .env.production create mode 100644 src/views/Authorized.vue delete mode 100644 src/views/Login.vue delete mode 100644 src/views/SignUp.vue diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..ae0354c --- /dev/null +++ b/.env.development @@ -0,0 +1,6 @@ +VUE_APP_READERBENCH_API_BASE_URL=http://localhost:8000 +VUE_APP_CLIENT_ID=gGwEwuF2PDJjnVwhUda89pHFPdobdyduOznz2enY +VUE_APP_REDIRECT_URI=http://localhost:8080/authorized +VUE_APP_READERBENCH_API_LOGIN_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/login/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_SIGNUP_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/signup/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_TOKEN_PATH=/oauth2/token/ \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..54a8450 --- /dev/null +++ b/.env.production @@ -0,0 +1,6 @@ +VUE_APP_READERBENCH_API_BASE_URL=https://readerbench.com/api/v2 +VUE_APP_CLIENT_ID=q2w45itLPXzlApDZyLgsoDGLO3HAArySZeQaQt40 +VUE_APP_REDIRECT_URI=https://readerbench.com/authorized +VUE_APP_READERBENCH_API_LOGIN_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/login/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_SIGNUP_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/signup/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_TOKEN_PATH=/oauth2/token/ \ No newline at end of file diff --git a/src/components/partials/Nav.vue b/src/components/partials/Nav.vue index 511ec6e..cb50a34 100644 --- a/src/components/partials/Nav.vue +++ b/src/components/partials/Nav.vue @@ -1,274 +1,392 @@ diff --git a/src/router/index.ts b/src/router/index.ts index 7d4137d..e7ccf28 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -12,7 +12,10 @@ const routes: Array = [ { path: '/models', name: 'models', - component: () => import('../views/ModelsView.vue') + component: () => import('../views/ModelsView.vue'), + meta: { + requiresAuth: true, + }, }, { path: '/services', @@ -25,14 +28,9 @@ const routes: Array = [ component: () => import('../views/People.vue') }, { - path: '/login', - name: 'login', - component: () => import('@/views/Login.vue') - }, - { - path: '/sign-up', - name: 'sign-up', - component: () => import('@/views/SignUp.vue') + path: '/authorized', + name: 'authorized', + component: () => import('@/views/Authorized.vue') }, { path: '/projects', @@ -69,21 +67,33 @@ const routes: Array = [ path: '/datasets', name: 'datasets', component: () => import('@/views/DatasetsView.vue'), + meta: { + requiresAuth: true, + } }, { path: '/datasets/:dataset_id', name: 'dataset', - component: () => import('@/views/DatasetView.vue') + component: () => import('@/views/DatasetView.vue'), + meta: { + requiresAuth: true, + }, }, { path: '/processing-queue', name: 'processingqueue', component: () => import('@/views/ProcessingQueueView.vue'), + meta: { + requiresAuth: true, + }, }, { path: '/profile', name: 'userprofileView', component: () => import('@/views/UserProfileView.vue'), + meta: { + requiresAuth: true, + }, }, { path: '/services/stt', @@ -105,6 +115,9 @@ const routes: Array = [ path: '/models/:id/prediction', name: 'modelpredictionview', component: () => import('@/views/ModelPredictionView.vue'), + meta: { + requiresAuth: true, + }, } ] @@ -115,13 +128,13 @@ const router = createRouter({ router.beforeEach((to, from, next) => { const route: RouteRecordNormalized = first(to.matched); - if (!isNil(route)) { if (route.meta.requiresAuth && !auth.isAuthenticated()) { const location: RouteLocationRaw = { - name: 'login', + name: 'home', query: { redirect: encodeURIComponent(to.fullPath), + sessionExpired: 'true' } }; return next(location); diff --git a/src/services/auth.ts b/src/services/auth.ts index 9c45761..5818b62 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -9,54 +9,36 @@ interface Callback { export default { isAuthenticated() { - const user = localStorage.getItem('user'); const token = localStorage.getItem('token'); - return !isNil(user) && !isNil(token); + return !isNil(token); }, - // Only for testing - dummy_successful_login(creds, redirect: string, onSuccess: Callback, onError: Callback) { - const user = { - grant_type: "password", - client_id: "8okRYORQww9VK0x3UTHAe8rl0dDvCUL6s3d6T43z", - client_secret: "sOyagG4VyhuBuVjrxzPtO848IbLSk19q7xGKp83c7FDNnNRGJRKOX3RAFI0F4SQMNtIx2Gg9xQTxPPKaTMikniMSbci7nppISLKq8EoRB5U8RZslOPu85AB4H5hI5EsZ", - username: creds.username, - password: creds.password + get_access_token(creds, redirect: string, onSuccess: Callback, onError: Callback) { + const payload = { + grant_type: "authorization_code", + client_id: process.env.VUE_APP_CLIENT_ID, + code: creds.code, + redirect_uri: creds.redirect_uri }; - if (redirect) { - router.push({ path: redirect, hash: '#logged_in' }) - } - localStorage.setItem('user', JSON.stringify(user)); - localStorage.setItem('token', 'secret_token'); - localStorage.setItem('refresh_token', 'secret_refresh_token'); - - onSuccess(user); - }, - - login(creds, redirect: string, onSuccess: Callback, onError: Callback) { + const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; - const user = { - grant_type: "password", - client_id: "8XE5YdZbhRdcLLovx3LxxhUQbKl2QPO0rvGMpWHy", - client_secret: "tybRzdzdcPykS7z358UqEMZApKJc6V36YwLBWvswHohVwFATJWmELCrXOs6zVQkEAChNlP87s1aQDxzkqu8O00PBQ1Qxv8vEf5XvGdxnneryOqxQBPri295Zve0fw42J", - username: creds.username, - password: creds.password - }; + if (!apiTokenPath) { + throw new Error("API Token path is not defined in environment variables"); + } axios - .post('/oauth2/token/', user) + .post(apiTokenPath, payload) .then(response => { if (redirect) { router.push({ path: redirect, hash: '#logged_in' }) } - localStorage.setItem('user', JSON.stringify(user)); localStorage.setItem('token', response.data.access_token); localStorage.setItem('refresh_token', response.data.refresh_token); localStorage.setItem('token_valid_until_timestamp', String(response.data.expires_in + (Math.floor(Date.now() / 1000)))); - onSuccess(user.username); + onSuccess("user"); }) .catch(error => { onError(error); @@ -66,13 +48,18 @@ export default { refresh_access_token(onSuccess: Callback, onError: Callback) { const payload = { grant_type: "refresh_token", - client_id: "8XE5YdZbhRdcLLovx3LxxhUQbKl2QPO0rvGMpWHy", - client_secret: "tybRzdzdcPykS7z358UqEMZApKJc6V36YwLBWvswHohVwFATJWmELCrXOs6zVQkEAChNlP87s1aQDxzkqu8O00PBQ1Qxv8vEf5XvGdxnneryOqxQBPri295Zve0fw42J", + client_id: process.env.VUE_APP_CLIENT_ID, refresh_token: localStorage.getItem('refresh_token') }; + const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; + + if (!apiTokenPath) { + throw new Error("API Token path is not defined in environment variables"); + } + axios - .post('/oauth2/token/', payload) + .post(apiTokenPath, payload) .then(response => { localStorage.setItem('token', response.data.access_token); @@ -86,27 +73,7 @@ export default { onError(error); }) }, - - signUp(details, onSuccess: Callback, onError: Callback) { - - const user = { - email: details.email, - password: details.password, - first_name: details.firstName, - last_name: details.lastName, - }; - - axios - .post('/users/register', user) - .then(response => { - onSuccess(response); - }) - .catch(error => { - onError(error); - }) - }, - - // To log out + logout: function (callback?: Callback) { this.clearLocalStorage(); router.push({ path: '/', hash: '#logged_out' }) @@ -117,7 +84,6 @@ export default { }, clearLocalStorage() { - localStorage.removeItem("user"); localStorage.removeItem("token"); localStorage.removeItem("token_valid_until_timestamp"); localStorage.removeItem("refresh_token"); diff --git a/src/services/http-interceptor.ts b/src/services/http-interceptor.ts index b0f3215..715ce58 100644 --- a/src/services/http-interceptor.ts +++ b/src/services/http-interceptor.ts @@ -4,8 +4,7 @@ import router from "@/router"; export function httpInterceptor() { axios.interceptors.request.use(async (request) => { - // TODO: read from env variable - request.baseURL = "https://readerbench.com/api/v2"; + request.baseURL = `${process.env.VUE_APP_READERBENCH_API_BASE_URL}`; request.headers = request.headers ?? {}; if (request.data) { @@ -13,7 +12,7 @@ export function httpInterceptor() { return request; } - if (request.data.grant_type && request.data.grant_type == "password") { + if (request.data.grant_type && request.data.grant_type == "authorization_code") { return request; } } @@ -44,38 +43,11 @@ export function httpInterceptor() { }); } catch (error) { auth.clearLocalStorage(); - - // Redirect to login page - router.push({ path: "/login", hash: "#session_expired" }); - + router.push({ path: "/", hash: "#session_expired" }); return Promise.reject("Your session has expired. Please log in again to continue."); } } return request; }); - - //TODO: Could cause problems if 401 status is not emitted due to using an invalid token -> to check - // axios.interceptors.response.use( - // (response) => { - // return response; - // }, - // (error) => { - // if (error.response) { - // const status = error.response.status; - - // if (status === 401) { - // // Handle error due to invalid token usage - // auth.clearLocalStorage(); - - // // Redirect to login page - // router.push({ path: "/login", hash: "#session_expired" }); - - // return Promise.reject("Your session has expired. Please log in again to continue."); - // } - // } - - // return Promise.reject(error); - // } - // ); } diff --git a/src/views/Authorized.vue b/src/views/Authorized.vue new file mode 100644 index 0000000..4ba8100 --- /dev/null +++ b/src/views/Authorized.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 7c514ab..8b37fd4 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -1,153 +1,273 @@ diff --git a/src/views/Login.vue b/src/views/Login.vue deleted file mode 100644 index 84b7837..0000000 --- a/src/views/Login.vue +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - diff --git a/src/views/SignUp.vue b/src/views/SignUp.vue deleted file mode 100644 index ec2a981..0000000 --- a/src/views/SignUp.vue +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - From 5357b82477219ad024c37e491b350e6fab67d258 Mon Sep 17 00:00:00 2001 From: ManuTrt Date: Mon, 9 Dec 2024 22:44:52 +0200 Subject: [PATCH 3/7] handled logout for session/tokens deletion (both client and server) --- .env.production => .env | 1 + .env.development | 6 --- README.md | 8 ++- public/index.html | 2 +- src/services/auth.ts | 105 ++++++++++++++++++++++++---------------- 5 files changed, 71 insertions(+), 51 deletions(-) rename .env.production => .env (86%) delete mode 100644 .env.development diff --git a/.env.production b/.env similarity index 86% rename from .env.production rename to .env index 54a8450..88fce7b 100644 --- a/.env.production +++ b/.env @@ -2,5 +2,6 @@ VUE_APP_READERBENCH_API_BASE_URL=https://readerbench.com/api/v2 VUE_APP_CLIENT_ID=q2w45itLPXzlApDZyLgsoDGLO3HAArySZeQaQt40 VUE_APP_REDIRECT_URI=https://readerbench.com/authorized VUE_APP_READERBENCH_API_LOGIN_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/login/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} +VUE_APP_READERBENCH_API_LOGOUT_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/logout VUE_APP_READERBENCH_API_SIGNUP_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/signup/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} VUE_APP_READERBENCH_API_TOKEN_PATH=/oauth2/token/ \ No newline at end of file diff --git a/.env.development b/.env.development deleted file mode 100644 index ae0354c..0000000 --- a/.env.development +++ /dev/null @@ -1,6 +0,0 @@ -VUE_APP_READERBENCH_API_BASE_URL=http://localhost:8000 -VUE_APP_CLIENT_ID=gGwEwuF2PDJjnVwhUda89pHFPdobdyduOznz2enY -VUE_APP_REDIRECT_URI=http://localhost:8080/authorized -VUE_APP_READERBENCH_API_LOGIN_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/login/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} -VUE_APP_READERBENCH_API_SIGNUP_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/signup/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} -VUE_APP_READERBENCH_API_TOKEN_PATH=/oauth2/token/ \ No newline at end of file diff --git a/README.md b/README.md index 244c80d..81a80bd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # readerbench-vue +### Local development + +``` +Please create a **.env.development.local** or **.env.local** file and overwrite the variables from .env to suit the local environment +``` + ## Project setup ``` npm install @@ -31,4 +37,4 @@ npm run lint ``` ### Customize configuration -See [Configuration Reference](https://cli.vuejs.org/config/). +See [Configuration Reference](https://cli.vuejs.org/config/). \ No newline at end of file diff --git a/public/index.html b/public/index.html index 28fedb8..8b0f267 100644 --- a/public/index.html +++ b/public/index.html @@ -4,7 +4,7 @@ - + ReaderBench diff --git a/src/services/auth.ts b/src/services/auth.ts index 5818b62..fa0fd6f 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -1,25 +1,29 @@ -import { isNil } from 'lodash'; -import router from '@/router'; -import axios from 'axios'; +import { isNil } from "lodash"; +import router from "@/router"; +import axios from "axios"; interface Callback { (message: any): void; } export default { - isAuthenticated() { - const token = localStorage.getItem('token'); - + const token = localStorage.getItem("token"); + return !isNil(token); }, - get_access_token(creds, redirect: string, onSuccess: Callback, onError: Callback) { + get_access_token( + creds, + redirect: string, + onSuccess: Callback, + onError: Callback + ) { const payload = { grant_type: "authorization_code", client_id: process.env.VUE_APP_CLIENT_ID, code: creds.code, - redirect_uri: creds.redirect_uri + redirect_uri: creds.redirect_uri, }; const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; @@ -29,27 +33,30 @@ export default { } axios - .post(apiTokenPath, payload) - .then(response => { - if (redirect) { - router.push({ path: redirect, hash: '#logged_in' }) - } - localStorage.setItem('token', response.data.access_token); - localStorage.setItem('refresh_token', response.data.refresh_token); - localStorage.setItem('token_valid_until_timestamp', String(response.data.expires_in + (Math.floor(Date.now() / 1000)))); - - onSuccess("user"); - }) - .catch(error => { - onError(error); - }) + .post(apiTokenPath, payload) + .then((response) => { + if (redirect) { + router.push({ path: redirect, hash: "#logged_in" }); + } + localStorage.setItem("token", response.data.access_token); + localStorage.setItem("refresh_token", response.data.refresh_token); + localStorage.setItem( + "token_valid_until_timestamp", + String(response.data.expires_in + Math.floor(Date.now() / 1000)) + ); + + onSuccess("user"); + }) + .catch((error) => { + onError(error); + }); }, refresh_access_token(onSuccess: Callback, onError: Callback) { const payload = { grant_type: "refresh_token", client_id: process.env.VUE_APP_CLIENT_ID, - refresh_token: localStorage.getItem('refresh_token') + refresh_token: localStorage.getItem("refresh_token"), }; const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; @@ -59,33 +66,45 @@ export default { } axios - .post(apiTokenPath, payload) - .then(response => { - - localStorage.setItem('token', response.data.access_token); - if (response.data.refresh_token != undefined) - localStorage.setItem('refresh_token', response.data.refresh_token); - localStorage.setItem('token_valid_until_timestamp', String(response.data.expires_in + (Math.floor(Date.now() / 1000)))); - - onSuccess(response); - }) - .catch(error => { - onError(error); - }) + .post(apiTokenPath, payload) + .then((response) => { + localStorage.setItem("token", response.data.access_token); + if (response.data.refresh_token != undefined) + localStorage.setItem("refresh_token", response.data.refresh_token); + localStorage.setItem( + "token_valid_until_timestamp", + String(response.data.expires_in + Math.floor(Date.now() / 1000)) + ); + + onSuccess(response); + }) + .catch((error) => { + onError(error); + }); }, - + logout: function (callback?: Callback) { - this.clearLocalStorage(); - router.push({ path: '/', hash: '#logged_out' }) + const apiLogoutUrl = process.env.VUE_APP_READERBENCH_API_LOGOUT_URL; - if (callback) { - callback("You have been logged out"); + if (!apiLogoutUrl) { + throw new Error( + "API Logout path is not defined in environment variables" + ); } + + axios.get(apiLogoutUrl, { withCredentials: true }).then(() => { + this.clearLocalStorage(); + router.push({ path: "/", hash: "#logged_out" }); + + if (callback) { + callback("You have been logged out"); + } + }); }, clearLocalStorage() { localStorage.removeItem("token"); localStorage.removeItem("token_valid_until_timestamp"); localStorage.removeItem("refresh_token"); - } -} + }, +}; From db75daf70aa556c5ab33693cb989866eba2c959f Mon Sep 17 00:00:00 2001 From: ManuTrt Date: Mon, 9 Dec 2024 22:57:54 +0200 Subject: [PATCH 4/7] solved weird stuff --- src/services/auth.ts | 64 -------------------------------------------- 1 file changed, 64 deletions(-) diff --git a/src/services/auth.ts b/src/services/auth.ts index ecd747b..fa0fd6f 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -8,7 +8,6 @@ interface Callback { export default { isAuthenticated() { -<<<<<<< HEAD const token = localStorage.getItem("token"); return !isNil(token); @@ -20,23 +19,11 @@ export default { onSuccess: Callback, onError: Callback ) { -======= - const token = localStorage.getItem('token'); - - return !isNil(token); - }, - - get_access_token(creds, redirect: string, onSuccess: Callback, onError: Callback) { ->>>>>>> ed80866111329cac836d73154f83fa5855b103e7 const payload = { grant_type: "authorization_code", client_id: process.env.VUE_APP_CLIENT_ID, code: creds.code, -<<<<<<< HEAD redirect_uri: creds.redirect_uri, -======= - redirect_uri: creds.redirect_uri ->>>>>>> ed80866111329cac836d73154f83fa5855b103e7 }; const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; @@ -46,7 +33,6 @@ export default { } axios -<<<<<<< HEAD .post(apiTokenPath, payload) .then((response) => { if (redirect) { @@ -64,29 +50,12 @@ export default { .catch((error) => { onError(error); }); -======= - .post(apiTokenPath, payload) - .then(response => { - if (redirect) { - router.push({ path: redirect, hash: '#logged_in' }) - } - localStorage.setItem('token', response.data.access_token); - localStorage.setItem('refresh_token', response.data.refresh_token); - localStorage.setItem('token_valid_until_timestamp', String(response.data.expires_in + (Math.floor(Date.now() / 1000)))); - - onSuccess("user"); - }) - .catch(error => { - onError(error); - }) ->>>>>>> ed80866111329cac836d73154f83fa5855b103e7 }, refresh_access_token(onSuccess: Callback, onError: Callback) { const payload = { grant_type: "refresh_token", client_id: process.env.VUE_APP_CLIENT_ID, -<<<<<<< HEAD refresh_token: localStorage.getItem("refresh_token"), }; @@ -94,39 +63,6 @@ export default { if (!apiTokenPath) { throw new Error("API Token path is not defined in environment variables"); -======= - refresh_token: localStorage.getItem('refresh_token') - }; - - const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; - - if (!apiTokenPath) { - throw new Error("API Token path is not defined in environment variables"); - } - - axios - .post(apiTokenPath, payload) - .then(response => { - - localStorage.setItem('token', response.data.access_token); - if (response.data.refresh_token != undefined) - localStorage.setItem('refresh_token', response.data.refresh_token); - localStorage.setItem('token_valid_until_timestamp', String(response.data.expires_in + (Math.floor(Date.now() / 1000)))); - - onSuccess(response); - }) - .catch(error => { - onError(error); - }) - }, - - logout: function (callback?: Callback) { - this.clearLocalStorage(); - router.push({ path: '/', hash: '#logged_out' }) - - if (callback) { - callback("You have been logged out"); ->>>>>>> ed80866111329cac836d73154f83fa5855b103e7 } axios From e76287f459ac870e58f2a691ec258961df858e68 Mon Sep 17 00:00:00 2001 From: ManuTrt Date: Tue, 10 Dec 2024 23:46:42 +0200 Subject: [PATCH 5/7] added user details method --- .env | 5 +++-- src/services/auth.ts | 32 ++++++++++++++++++++++++++++---- src/views/Authorized.vue | 2 +- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/.env b/.env index 88fce7b..1cf9d80 100644 --- a/.env +++ b/.env @@ -1,7 +1,8 @@ -VUE_APP_READERBENCH_API_BASE_URL=https://readerbench.com/api/v2 VUE_APP_CLIENT_ID=q2w45itLPXzlApDZyLgsoDGLO3HAArySZeQaQt40 VUE_APP_REDIRECT_URI=https://readerbench.com/authorized +VUE_APP_READERBENCH_API_BASE_URL=https://readerbench.com/api/v2 VUE_APP_READERBENCH_API_LOGIN_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/login/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} VUE_APP_READERBENCH_API_LOGOUT_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/logout VUE_APP_READERBENCH_API_SIGNUP_URL=${VUE_APP_READERBENCH_API_BASE_URL}/accounts/signup/?client_id=${VUE_APP_CLIENT_ID}&redirect_uri=${VUE_APP_REDIRECT_URI} -VUE_APP_READERBENCH_API_TOKEN_PATH=/oauth2/token/ \ No newline at end of file +VUE_APP_READERBENCH_API_TOKEN_ENDPOINT=/oauth2/token/ +VUE_APP_READERBENCH_API_USER_DETAILS_ENDPOINT=/users/me \ No newline at end of file diff --git a/src/services/auth.ts b/src/services/auth.ts index fa0fd6f..dc769ff 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -13,6 +13,19 @@ export default { return !isNil(token); }, + get_user_details() { + const apiUserDetailsPath = + process.env.VUE_APP_READERBENCH_API_USER_DETAILS_ENDPOINT; + + if (!apiUserDetailsPath) { + throw new Error( + "API User details path is not defined in environment variables" + ); + } + + return axios.get(apiUserDetailsPath); + }, + get_access_token( creds, redirect: string, @@ -26,7 +39,7 @@ export default { redirect_uri: creds.redirect_uri, }; - const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; + const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_ENDPOINT; if (!apiTokenPath) { throw new Error("API Token path is not defined in environment variables"); @@ -45,7 +58,18 @@ export default { String(response.data.expires_in + Math.floor(Date.now() / 1000)) ); - onSuccess("user"); + try { + const call_user_details = this.get_user_details() + + call_user_details.then((response) => { + onSuccess(response.data.username); + }) + .catch((error) => { + onError(error); + }); + } catch (error) { + onError({response: {data: {error_description: error}}}) + } }) .catch((error) => { onError(error); @@ -59,7 +83,7 @@ export default { refresh_token: localStorage.getItem("refresh_token"), }; - const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_PATH; + const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_ENDPOINT; if (!apiTokenPath) { throw new Error("API Token path is not defined in environment variables"); @@ -92,7 +116,7 @@ export default { ); } - axios.get(apiLogoutUrl, { withCredentials: true }).then(() => { + axios.get(apiLogoutUrl, { withCredentials: true }).then(() => { this.clearLocalStorage(); router.push({ path: "/", hash: "#logged_out" }); diff --git a/src/views/Authorized.vue b/src/views/Authorized.vue index 4ba8100..2435449 100644 --- a/src/views/Authorized.vue +++ b/src/views/Authorized.vue @@ -97,7 +97,7 @@ export default { methods: { onGetTokenSuccess(response) { this.toastService && - this.toastService.success("Welcome to ReaderBench!", ""); + this.toastService.success("Welcome to ReaderBench!", `Hi, ${response}!`); }, onGetTokenFail(error) { console.log(error); From 4cabf582417c86e698941299443400349169b027 Mon Sep 17 00:00:00 2001 From: ManuTrt Date: Sat, 8 Mar 2025 15:44:29 +0200 Subject: [PATCH 6/7] pkce added --- package-lock.json | 8 +++++++- package.json | 1 + src/components/partials/Nav.vue | 2 +- src/services/auth.ts | 35 +++++++++++++++++++++++++++++++++ src/views/Authorized.vue | 5 ++++- 5 files changed, 48 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 731d028..a286713 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "core-js": "^3.8.3", "dotenv": "^16.0.3", "echarts": "^5.4.0", + "js-sha256": "^0.11.0", "json-as-xlsx": "^2.5.4", "lodash": "^4.17.21", "primeicons": "^6.0.1", @@ -10609,6 +10610,11 @@ "node": ">=0.6.0" } }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18510,4 +18516,4 @@ "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index a6425e8..008eb2b 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "core-js": "^3.8.3", "dotenv": "^16.0.3", "echarts": "^5.4.0", + "js-sha256": "^0.11.0", "json-as-xlsx": "^2.5.4", "lodash": "^4.17.21", "primeicons": "^6.0.1", diff --git a/src/components/partials/Nav.vue b/src/components/partials/Nav.vue index cb50a34..4d3a6ca 100644 --- a/src/components/partials/Nav.vue +++ b/src/components/partials/Nav.vue @@ -343,7 +343,7 @@ export default { }, methods: { redirectToLogin() { - window.location.href = process.env.VUE_APP_READERBENCH_API_LOGIN_URL; + auth.login(window); }, toggle(event) { this.$refs.menu.toggle(event); diff --git a/src/services/auth.ts b/src/services/auth.ts index dc769ff..f641667 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -1,6 +1,7 @@ import { isNil } from "lodash"; import router from "@/router"; import axios from "axios"; +import { sha256 } from "js-sha256"; interface Callback { (message: any): void; @@ -32,11 +33,14 @@ export default { onSuccess: Callback, onError: Callback ) { + const code_verifier = localStorage.getItem("code_verifier"); + localStorage.removeItem("code_verifier"); const payload = { grant_type: "authorization_code", client_id: process.env.VUE_APP_CLIENT_ID, code: creds.code, redirect_uri: creds.redirect_uri, + code_verifier: code_verifier }; const apiTokenPath = process.env.VUE_APP_READERBENCH_API_TOKEN_ENDPOINT; @@ -107,6 +111,37 @@ export default { }); }, + login(window) { + // Generate code verifier + const code_verifier = Array.from({ length: 128 }, () => + String.fromCharCode(Math.floor(Math.random() * 95) + 32) + ).join(""); + localStorage.setItem("code_verifier", code_verifier); + + // string of 64 hex characters -> 32 bytes -> 32 ascii characters + const code_verifier_hashed = sha256(code_verifier); + + // Because the code verifier is a string of 64 hex characters, + // we need to convert it to a string of the corresponding ascii characters + // because btoa expects a string of ascii characters. Otherwise it will treat a hex character as an ascii character. + let code_verifier_hashed_ascii = ""; + for (let i = 0; i < code_verifier_hashed.length; i += 2) { + const ascii_char = String.fromCharCode(parseInt(code_verifier_hashed.substring(i, i + 2), 16)); + code_verifier_hashed_ascii += ascii_char; + } + const code_verifier_hashed_base64 = btoa(code_verifier_hashed_ascii); + + // what this does is: + // 1. remove the padding characters '=' + // 2. replace '+' with '-' + // 3. replace '/' with '_' + // this is because the base64url encoding is a variant of base64 encoding that uses URL and filename safe alphabet + const code_challenge = code_verifier_hashed_base64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); + + // Redirect to the login page with the code challenge and the method used to generate it + window.location.href = process.env.VUE_APP_READERBENCH_API_LOGIN_URL + `&code_challenge=${code_challenge}` + `&code_challenge_method=S256`; + }, + logout: function (callback?: Callback) { const apiLogoutUrl = process.env.VUE_APP_READERBENCH_API_LOGOUT_URL; diff --git a/src/views/Authorized.vue b/src/views/Authorized.vue index 2435449..4e4a2de 100644 --- a/src/views/Authorized.vue +++ b/src/views/Authorized.vue @@ -88,6 +88,7 @@ export default { const code = this.$route.query.code; const redirectUri = process.env.VUE_APP_REDIRECT_URI; + // Proceed to get the access token if (code) { auth.get_access_token({ code, redirect_uri: redirectUri }, "/", this.onGetTokenSuccess, this.onGetTokenFail); } else { @@ -104,8 +105,10 @@ export default { this.toastService && this.toastService.error( error.response.data.error_description, - "Get access token failed" + "Authorization failed" ); + console.error("Get access token failed"); + this.$router.push({ name: "home" }); }, }, }; From cbd5db9f1f8d577899e26e8bb1c2dd7540b6f9f0 Mon Sep 17 00:00:00 2001 From: Stefan Ruseti Date: Mon, 10 Mar 2025 19:55:08 +0000 Subject: [PATCH 7/7] add Keywords service for English --- src/views/TextAnalysisView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/TextAnalysisView.vue b/src/views/TextAnalysisView.vue index 1fac6c2..40c6b0f 100644 --- a/src/views/TextAnalysisView.vue +++ b/src/views/TextAnalysisView.vue @@ -123,7 +123,7 @@ export default { { id: 4, name: 'Keyword Extraction', - languages: [3,8], + languages: [1, 3, 8], process: this.taService.getKeywords, payloadTemplate: { text: null,