From dbf06d8c64201366410a87f65591db22458a66f7 Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Fri, 7 Feb 2025 23:06:59 +0100 Subject: [PATCH 1/5] feat: configured authentification --- Makefile | 8 +- .../myinpulse/MyinpulseApplication.java | 43 ++------ .../WebSecurityCustomConfiguration.java | 50 ++++++++++ .../security/KeycloakJwtRolesConverter.java | 98 +++++++++++++++++++ .../src/main/resources/application.properties | 3 +- front/MyINPulse-front/src/helpers.ts | 26 ----- 6 files changed, 161 insertions(+), 67 deletions(-) create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java create mode 100644 MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java delete mode 100644 front/MyINPulse-front/src/helpers.ts diff --git a/Makefile b/Makefile index a5606be..6944ef4 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ help: clean: @cp config/prod.docker-compose.yaml docker-compose.yaml - @docker compose down 2> /dev/null + @docker compose down @rm -f docker-compose.yaml @rm -f .env @rm -f front/MyINPulse-front/.env @@ -21,14 +21,14 @@ dev-front: clean vite @cp config/frontdev.front.env front/MyINPulse-front/.env @cp config/frontdev.main.env .env @cp config/frontdev.docker-compose.yaml docker-compose.yaml - @docker compose up -d + @docker compose up -d --build @cd ./front/MyINPulse-front/ && npm run dev prod: clean @cp config/prod.front.env front/MyINPulse-front/.env @cp config/prod.main.env .env @cp config/frontdev.docker-compose.yaml docker-compose.yaml - @docker compose up -d + @docker compose up -d --build @@ -36,6 +36,6 @@ dev-back: @cp config/backdev.front.env front/MyINPulse-front/.env @cp config/backdev.main.env .env @cp config/backdev.docker-compose.yaml docker-compose.yaml - @docker compose up -d + @docker compose up -d --build @echo "cd MyINPulse-back" @echo "./gradlew bootRun --args='--server.port=8081'" \ No newline at end of file diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/MyinpulseApplication.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/MyinpulseApplication.java index b4b5e7b..23c2f28 100644 --- a/MyINPulse-back/src/main/java/enseirb/myinpulse/MyinpulseApplication.java +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/MyinpulseApplication.java @@ -1,22 +1,22 @@ package enseirb.myinpulse; +import enseirb.myinpulse.security.KeycloakJwtRolesConverter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.security.config.Customizer; +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; -import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; +import org.springframework.security.oauth2.jwt.*; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.util.Arrays; +import java.util.*; +import java.util.stream.Collectors; -import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope; +import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole; @SpringBootApplication public class MyinpulseApplication { @@ -25,34 +25,5 @@ public class MyinpulseApplication { SpringApplication.run(MyinpulseApplication.class, args); } - // CORS configuration - // TODO: make sure to only accept our own domains - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("*")); - configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); - configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", - "x-auth-token")); // Do not remove, this fixes the CORS errors when unauthenticated - //configuration.setExposedHeaders(Arrays.asList("x-auth-token")); - UrlBasedCorsConfigurationSource source = new - UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); - - return source; - } - - // TODO: add unauthenticated endpoint with server status - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/random2").access(hasScope("contacts")) - .requestMatchers("/getUserInfo").access(hasScope("messages")) - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults())); - return http.build(); - } } diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java new file mode 100644 index 0000000..14c46b3 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/config/WebSecurityCustomConfiguration.java @@ -0,0 +1,50 @@ +package enseirb.myinpulse.config; + +import enseirb.myinpulse.security.KeycloakJwtRolesConverter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.Arrays; +import java.util.List; + +import static org.springframework.security.authorization.AuthorityAuthorizationManager.hasRole; + +@Configuration +public class WebSecurityCustomConfiguration { + // CORS configuration + // TODO: make sure to only accept our own domains + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOrigins(List.of("*")); + configuration.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); + configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type", + "x-auth-token")); // Do not remove, this fixes the CORS errors when unauthenticated + UrlBasedCorsConfigurationSource source = new + UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + + return source; + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(authorize -> authorize + .requestMatchers("/random2").access(hasRole("REALM_MyINPulse-entrepreneur")) + .requestMatchers("/random").access(hasRole("REALM_MyINPulse-admin")) + .requestMatchers("/random3").permitAll() + .anyRequest().authenticated() + ) + .oauth2ResourceServer(oauth2 -> oauth2 + .jwt(jwt -> jwt. + jwtAuthenticationConverter(new KeycloakJwtRolesConverter()))); + return http.build(); + + } +} \ No newline at end of file diff --git a/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java b/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java new file mode 100644 index 0000000..fafbef5 --- /dev/null +++ b/MyINPulse-back/src/main/java/enseirb/myinpulse/security/KeycloakJwtRolesConverter.java @@ -0,0 +1,98 @@ +package enseirb.myinpulse.security; + +import org.springframework.core.convert.converter.Converter; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toSet; + + +public class KeycloakJwtRolesConverter implements Converter { + /** + * Prefix used for realm level roles. + */ + public static final String PREFIX_REALM_ROLE = "ROLE_REALM_"; + /** + * Prefix used in combination with the resource (client) name for resource level roles. + */ + public static final String PREFIX_RESOURCE_ROLE = "ROLE_"; + + /** + * Name of the claim containing the realm level roles + */ + private static final String CLAIM_REALM_ACCESS = "realm_access"; + /** + * Name of the claim containing the resources (clients) the user has access to. + */ + private static final String CLAIM_RESOURCE_ACCESS = "resource_access"; + /** + * Name of the claim containing roles. (Applicable to realm and resource level.) + */ + private static final String CLAIM_ROLES = "roles"; + + @Override + public AbstractAuthenticationToken convert(Jwt source) + { + return new JwtAuthenticationToken(source, Stream.concat(new JwtGrantedAuthoritiesConverter().convert(source) + .stream(), TEMPORARNAME(source).stream()) + .collect(toSet())); + } + + /** + * Extracts the realm and resource level roles from a JWT token distinguishing between them using prefixes. + */ + public Collection TEMPORARNAME(Jwt jwt) { + // Collection that will hold the extracted roles + Collection grantedAuthorities = new ArrayList<>(); + + // Realm roles + // Get the part of the access token that holds the roles assigned on realm level + Map> realmAccess = jwt.getClaim(CLAIM_REALM_ACCESS); + + // Verify that the claim exists and is not empty + if (realmAccess != null && !realmAccess.isEmpty()) { + // From the realm_access claim get the roles + Collection roles = realmAccess.get(CLAIM_ROLES); + // Check if any roles are present + if (roles != null && !roles.isEmpty()) { + // Iterate of the roles and add them to the granted authorities + Collection realmRoles = roles.stream() + // Prefix all realm roles with "ROLE_realm_" + .map(role -> new SimpleGrantedAuthority(PREFIX_REALM_ROLE + role)) + .collect(Collectors.toList()); + grantedAuthorities.addAll(realmRoles); + } + } + + // Resource (client) roles + // A user might have access to multiple resources all containing their own roles. Therefore, it is a map of + // resource each possibly containing a "roles" property. + Map>> resourceAccess = jwt.getClaim(CLAIM_RESOURCE_ACCESS); + + // Check if resources are assigned + if (resourceAccess != null && !resourceAccess.isEmpty()) { + // Iterate of all the resources + resourceAccess.forEach((resource, resourceClaims) -> { + // Iterate of the "roles" claim inside the resource claims + resourceClaims.get(CLAIM_ROLES).forEach( + // Add the role to the granted authority prefixed with ROLE_ and the name of the resource + role -> grantedAuthorities.add(new SimpleGrantedAuthority(PREFIX_RESOURCE_ROLE + resource + "_" + role)) + ); + }); + } + + return grantedAuthorities; + } + + +} diff --git a/MyINPulse-back/src/main/resources/application.properties b/MyINPulse-back/src/main/resources/application.properties index 9264594..6d6825f 100644 --- a/MyINPulse-back/src/main/resources/application.properties +++ b/MyINPulse-back/src/main/resources/application.properties @@ -1,3 +1,4 @@ spring.application.name=myinpulse spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://localhost:7080/realms/test/protocol/openid-connect/certs -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test \ No newline at end of file +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:7080/realms/test +logging.level.org.springframework.security=DEBUG diff --git a/front/MyINPulse-front/src/helpers.ts b/front/MyINPulse-front/src/helpers.ts deleted file mode 100644 index 0bd894a..0000000 --- a/front/MyINPulse-front/src/helpers.ts +++ /dev/null @@ -1,26 +0,0 @@ -import axios from "axios"; - -const backendUrl = "http://localhost:8080/" - -// TODO: spawn a error modal -function defaultApiErrorHandler(err: String){ - console.log(err) -} - -function defaultApiSuccessHandler(response: () => void){ - console.log(response) -} -/* -function callApi(endpoint: string, onSuccessHandler: () => void, onErrorHandler: (err: String) => void): void { - console.log("callApi: "+ endpoint) - axios.get(backendUrl + endpoint).then( - onSuccessHandler == null ? defaultApiSuccessHandler : onSuccessHandler - ).catch( - (err) => { - onErrorHandler == null ? defaultApiErrorHandler(err): onErrorHandler(err); - } - ) -} - -export {callApi} - */ \ No newline at end of file From 3de90295bd7902cb011dc4f3cb2b398f0dc481a3 Mon Sep 17 00:00:00 2001 From: Pierre Tellier Date: Sat, 8 Feb 2025 12:08:02 +0100 Subject: [PATCH 2/5] fix: changed configs --- config/backdev.front.env | 1 + config/frontdev.front.env | 3 +- config/prod.front.env | 1 + .../src/components/HelloWorld.vue | 41 -------- .../src/components/TheWelcome.vue | 94 ------------------- .../src/components/WelcomeItem.vue | 87 ----------------- .../src/components/errorModal.vue | 94 +++++++++++++++++++ .../src/components/icons/IconCommunity.vue | 7 -- .../components/icons/IconDocumentation.vue | 7 -- .../src/components/icons/IconEcosystem.vue | 7 -- .../src/components/icons/IconSupport.vue | 7 -- .../src/components/icons/IconTooling.vue | 19 ---- front/MyINPulse-front/src/main.ts | 74 +++++---------- .../src/router/{index.ts => router.ts} | 0 front/MyINPulse-front/src/services/api.ts | 56 +++++++++-- .../src/services/popupDisplayer.ts | 17 ++++ .../MyINPulse-front/src/views/errorModal.vue | 32 ------- .../src/views/errorWrapper.vue | 11 +++ .../src/views/{Home.vue => test.vue} | 28 ++++-- 19 files changed, 218 insertions(+), 368 deletions(-) delete mode 100644 front/MyINPulse-front/src/components/HelloWorld.vue delete mode 100644 front/MyINPulse-front/src/components/TheWelcome.vue delete mode 100644 front/MyINPulse-front/src/components/WelcomeItem.vue create mode 100644 front/MyINPulse-front/src/components/errorModal.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconCommunity.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconDocumentation.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconEcosystem.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconSupport.vue delete mode 100644 front/MyINPulse-front/src/components/icons/IconTooling.vue rename front/MyINPulse-front/src/router/{index.ts => router.ts} (100%) create mode 100644 front/MyINPulse-front/src/services/popupDisplayer.ts delete mode 100644 front/MyINPulse-front/src/views/errorModal.vue create mode 100644 front/MyINPulse-front/src/views/errorWrapper.vue rename front/MyINPulse-front/src/views/{Home.vue => test.vue} (58%) diff --git a/config/backdev.front.env b/config/backdev.front.env index 9db91ea..27cf54e 100644 --- a/config/backdev.front.env +++ b/config/backdev.front.env @@ -2,3 +2,4 @@ VITE_KEYCLOAK_URL=http://localhost:7080 VITE_KEYCLOAK_CLIENT_ID=myinpulse VITE_KEYCLOAK_REALM=test VITE_APP_URL=http://localhost:8080 +VITE_BACKEND_URL=http://localhost:8081/ diff --git a/config/frontdev.front.env b/config/frontdev.front.env index d6b7016..5eba221 100644 --- a/config/frontdev.front.env +++ b/config/frontdev.front.env @@ -1,4 +1,5 @@ VITE_KEYCLOAK_URL=http://localhost:7080 -VITE_KEYCLOAK_CLIENT_ID=myinpulse +VITE_KEYCLOAK_CLIENT_ID=myinpulse-dev VITE_KEYCLOAK_REALM=test VITE_APP_URL=http://localhost:5173 +VITE_BACKEND_URL=http://localhost:8081/ diff --git a/config/prod.front.env b/config/prod.front.env index d5007dc..cb42a37 100644 --- a/config/prod.front.env +++ b/config/prod.front.env @@ -2,3 +2,4 @@ VITE_KEYCLOAK_URL=https://0549cd63f912d5dc9b31278d6f.eirb.fr VITE_KEYCLOAK_CLIENT_ID=myinpulse-eirb VITE_KEYCLOAK_REALM=test VITE_APP_URL=https://0549cd63f912d5dc9b31278d6f.piair.dev +VITE_BACKEND_URL=http://TODO/ diff --git a/front/MyINPulse-front/src/components/HelloWorld.vue b/front/MyINPulse-front/src/components/HelloWorld.vue deleted file mode 100644 index d174cf8..0000000 --- a/front/MyINPulse-front/src/components/HelloWorld.vue +++ /dev/null @@ -1,41 +0,0 @@ - - - - - diff --git a/front/MyINPulse-front/src/components/TheWelcome.vue b/front/MyINPulse-front/src/components/TheWelcome.vue deleted file mode 100644 index 674b490..0000000 --- a/front/MyINPulse-front/src/components/TheWelcome.vue +++ /dev/null @@ -1,94 +0,0 @@ - - - diff --git a/front/MyINPulse-front/src/components/WelcomeItem.vue b/front/MyINPulse-front/src/components/WelcomeItem.vue deleted file mode 100644 index 6d7086a..0000000 --- a/front/MyINPulse-front/src/components/WelcomeItem.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - diff --git a/front/MyINPulse-front/src/components/errorModal.vue b/front/MyINPulse-front/src/components/errorModal.vue new file mode 100644 index 0000000..0d7dd57 --- /dev/null +++ b/front/MyINPulse-front/src/components/errorModal.vue @@ -0,0 +1,94 @@ + + + + + \ No newline at end of file diff --git a/front/MyINPulse-front/src/components/icons/IconCommunity.vue b/front/MyINPulse-front/src/components/icons/IconCommunity.vue deleted file mode 100644 index 2dc8b05..0000000 --- a/front/MyINPulse-front/src/components/icons/IconCommunity.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/front/MyINPulse-front/src/components/icons/IconDocumentation.vue b/front/MyINPulse-front/src/components/icons/IconDocumentation.vue deleted file mode 100644 index 6d4791c..0000000 --- a/front/MyINPulse-front/src/components/icons/IconDocumentation.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/front/MyINPulse-front/src/components/icons/IconEcosystem.vue b/front/MyINPulse-front/src/components/icons/IconEcosystem.vue deleted file mode 100644 index c3a4f07..0000000 --- a/front/MyINPulse-front/src/components/icons/IconEcosystem.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/front/MyINPulse-front/src/components/icons/IconSupport.vue b/front/MyINPulse-front/src/components/icons/IconSupport.vue deleted file mode 100644 index 7452834..0000000 --- a/front/MyINPulse-front/src/components/icons/IconSupport.vue +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/front/MyINPulse-front/src/components/icons/IconTooling.vue b/front/MyINPulse-front/src/components/icons/IconTooling.vue deleted file mode 100644 index 660598d..0000000 --- a/front/MyINPulse-front/src/components/icons/IconTooling.vue +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/front/MyINPulse-front/src/main.ts b/front/MyINPulse-front/src/main.ts index e33d5c1..198447d 100644 --- a/front/MyINPulse-front/src/main.ts +++ b/front/MyINPulse-front/src/main.ts @@ -1,69 +1,37 @@ import { createApp } from 'vue' import App from './App.vue' import router from './router' -//import VueKeyCloak from '@dsb-norge/vue-keycloak-js' -//import { useKeycloak } from '@dsb-norge/vue-keycloak-js' -//import axios from 'axios' import {createPinia} from "pinia"; import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import AuthStorePlugin from './plugins/authStore'; import keycloakService from './services/keycloak'; +import axios from "axios"; +import {useAuthStore} from "@/stores/authStore.ts"; +let store: any; keycloakService.CallInit(() => { + try { + const app = createApp(App) - const app = createApp(App) + // Setup pinia store, allowing user to keep logged in status after refresh + const pinia = createPinia(); + pinia.use(piniaPluginPersistedstate); + app.use(pinia); + app.use(AuthStorePlugin, { pinia }); + store = useAuthStore(); + app.use(router) - -// Setup pinia store, allowing user to keep logged in status after refresh - const pinia = createPinia(); - pinia.use(piniaPluginPersistedstate); - - app.use(pinia); - app.use(AuthStorePlugin, { pinia }); - app.use(router) - app.mount('#app'); + app.mount('#app'); + } catch (e) { + console.error("Error while initiating Keycloak.") + console.error(e) + createApp(App).mount('#app'); + } }) -createApp(App).mount('#app'); - -// TODO: fix the comment -/* -function tokenInterceptor () { - axios.interceptors.request.use(config => { - const keycloak = useKeycloak() - if (keycloak.authenticated) { - // Note that this is a simple example. - // you should be careful not to leak tokens to third parties. - // in this example the token is added to all usage of axios. - config.headers.Authorization = `Bearer ${keycloak.token}` - } - return config - }, error => { - console.error("tokenInterceptor: Rejected") - return Promise.reject(error) - }) -} -*/ - -/* -app.use(VueKeyCloak,{ - onReady: (keycloak) => { - console.log("Ready !") - tokenInterceptor() - }, - init: { - onLoad: 'login-required', - checkLoginIframe: false, - - }, - - config: { - realm: 'test', - url: 'http://localhost:7080', - clientId: 'myinpulse' - } -} ); -*/ + + +export {store}; \ No newline at end of file diff --git a/front/MyINPulse-front/src/router/index.ts b/front/MyINPulse-front/src/router/router.ts similarity index 100% rename from front/MyINPulse-front/src/router/index.ts rename to front/MyINPulse-front/src/router/router.ts diff --git a/front/MyINPulse-front/src/services/api.ts b/front/MyINPulse-front/src/services/api.ts index eb75333..d29273a 100644 --- a/front/MyINPulse-front/src/services/api.ts +++ b/front/MyINPulse-front/src/services/api.ts @@ -1,13 +1,55 @@ -// file: src/services/api.js - import axios from "axios"; +import {store} from "@/main.ts"; -// Creating an instance for axios to be used by the token interceptor service -const instance = axios.create({ - baseURL: `${import.meta.env.VITE_BE_API_URL}/api`, +const axiosInstance = axios.create({ + baseURL: import.meta.env.VITE_BACKEND_URL, headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, }); -export default instance; \ No newline at end of file +axiosInstance.interceptors.response.use( + response => response, // Directly return successful responses. + async error => { + const originalRequest = error.config; + if (error.response.status === 401 && !originalRequest._retry && store.authenticated) { + originalRequest._retry = true; // Mark the request as retried to avoid infinite loops. + try { + await store.refreshUserToken(); + // Update the authorization header with the new access token. + axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${store.user.token}`; + return axiosInstance(originalRequest); // Retry the original request with the new access token. + } catch (refreshError) { + // Handle refresh token errors by clearing stored tokens and redirecting to the login page. + console.error('Token refresh failed:', refreshError); + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + window.location.href = '/login'; + return Promise.reject(refreshError); + } + } + return Promise.reject(error); // For all other errors, return the error as is. + } +); + +// TODO: spawn a error modal +function defaultApiErrorHandler(err: String){ + console.log(err) +} + +function defaultApiSuccessHandler(response: () => void){ + console.log(response) +} +function callApi(endpoint: string, onSuccessHandler?: any, onErrorHandler?: any): void { + axiosInstance.get(endpoint).then( + onSuccessHandler == null ? defaultApiSuccessHandler : onSuccessHandler + ).catch( + (err) => { + onErrorHandler == null ? defaultApiErrorHandler(err): onErrorHandler(err); + throw err; + } + ) +} + + +export {callApi} \ No newline at end of file diff --git a/front/MyINPulse-front/src/services/popupDisplayer.ts b/front/MyINPulse-front/src/services/popupDisplayer.ts new file mode 100644 index 0000000..61d02b9 --- /dev/null +++ b/front/MyINPulse-front/src/services/popupDisplayer.ts @@ -0,0 +1,17 @@ +import {ref} from "vue"; +enum errorType {Error, Warning} + +function addNewError(errorMessage: string, timeout?: number, type?: errorType){ + if (timeout == null){ + timeout = 5000; + } + if (type == null){ + type = Error; + } + errorList.value.push({errorMessage: errorMessage, timeout: timeout, type: type}) + setTimeout(() => errorList.value.pop(errorMessage), 5000) +} + +const errorList = ref([]) + +export {addNewError, errorList} \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/errorModal.vue b/front/MyINPulse-front/src/views/errorModal.vue deleted file mode 100644 index 92722f1..0000000 --- a/front/MyINPulse-front/src/views/errorModal.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - - \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/errorWrapper.vue b/front/MyINPulse-front/src/views/errorWrapper.vue new file mode 100644 index 0000000..96c0baf --- /dev/null +++ b/front/MyINPulse-front/src/views/errorWrapper.vue @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/Home.vue b/front/MyINPulse-front/src/views/test.vue similarity index 58% rename from front/MyINPulse-front/src/views/Home.vue rename to front/MyINPulse-front/src/views/test.vue index 7fbe869..e879aef 100644 --- a/front/MyINPulse-front/src/views/Home.vue +++ b/front/MyINPulse-front/src/views/test.vue @@ -1,7 +1,16 @@ @@ -26,19 +35,26 @@ const store = useAuthStore() {{store.user.refreshToken}} - Unauthenticated API call - + Entrepreneur API call + res - Authenticated API call - + Admin API call + res + + Unauth API call + + res + + + \ No newline at end of file diff --git a/front/MyINPulse-front/src/views/test.vue b/front/MyINPulse-front/src/views/test.vue index e879aef..90db6d1 100644 --- a/front/MyINPulse-front/src/views/test.vue +++ b/front/MyINPulse-front/src/views/test.vue @@ -1,9 +1,9 @@