diff --git a/docker-compose.yml b/docker-compose.yml index e626edee0..90fa8da7a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,8 @@ services: dockerfile: docker/Dockerfile.dev ports: - 8000:8000 + env_file: + - test_project/oidc_key environment: PYTHONPATH: ".." DB: "SQLITE" diff --git a/frontend/src/components/login/LoginForm.vue b/frontend/src/components/login/LoginForm.vue deleted file mode 100644 index aa23fae72..000000000 --- a/frontend/src/components/login/LoginForm.vue +++ /dev/null @@ -1,105 +0,0 @@ - - - diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 4163a0df9..0013cb7e5 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -17,6 +17,11 @@ const routes = [ name: 'TwoFA', component: () => import('@/views/login/TwoFA.vue'), }, + { + path: 'logged', + name: 'LoginCallback', + component: () => import('@/views/login/LoginCallbackView.vue'), + }, { path: 'password_recovery', name: 'PasswordRecovery', @@ -291,8 +296,14 @@ const router = createRouter({ router.beforeEach((to, from, next) => { if (to.meta.requiresAuth !== undefined) { + const previousPage = window.location.href + sessionStorage.setItem('previousPage', previousPage) const authStore = useAuthStore() - authStore.initialize().then(() => { + authStore.initialize().then((res) => { + if (res === null) { + next({name: 'Login'}) + return + } authStore.validateAccess() if (to.meta.allowedRoles !== undefined) { if (to.meta.allowedRoles.indexOf(authStore.authUser.role) === -1) { diff --git a/frontend/src/stores/auth.store.js b/frontend/src/stores/auth.store.js index 09f748451..530a898ea 100644 --- a/frontend/src/stores/auth.store.js +++ b/frontend/src/stores/auth.store.js @@ -3,6 +3,7 @@ import Cookies from 'js-cookie' import { defineStore } from 'pinia' import { computed, ref } from 'vue' import gettext from '@/plugins/gettext' +import router from '@/router/index.js' import { UserManager } from 'oidc-client-ts' import repository from '@/api/repository' @@ -14,16 +15,16 @@ export const useAuthStore = defineStore('auth', () => { const authUser = ref({}) const isAuthenticated = ref(false) const manager = new UserManager({ - authority: '/api/o', + authority: 'http://localhost:8000/api/o', client_id: 'LVQbfIIX3khWR3nDvix1u9yEGHZUxcx53bhJ7FlD', - // Is client_secret necessary? - client_secret: - 'fXzG7tq23eXrM7RK41pXHRseuRWnxe4gV0p4A4YXJlF9jva7QfsDft1g7ESYU9AXR35QE0lo2Ti696OXsqb9JHLjtrPxaYmgMSRmRwLCce5hVr3FB2QhfSOThFYhcuuR', - metadata: { - authorization_endpoint: '/api/o/authorize/', - token_endpoint: '/api/o/token/', - }, - scope: 'read write', + redirect_uri: 'http://localhost:3000/login/logged', + response_type: 'code', + scope: 'openid read write', + automaticSilentRenew: true, + accessTokenExpiringNotificationTime: 60, + monitorSession: true, + filterProtocolClaims: true, + loadUserInfo: true, }) const userHasMailbox = computed(() => { @@ -40,11 +41,11 @@ export const useAuthStore = defineStore('auth', () => { async function initialize() { if (isAuthenticated.value) { - return + return null } const user = await manager.getUser() if (!user) { - return + return null } repository.defaults.headers.common.Authorization = `Bearer ${user.access_token}` repository.defaults.headers.post['Content-Type'] = 'application/json' @@ -58,18 +59,38 @@ export const useAuthStore = defineStore('auth', () => { } } - async function login(payload) { - await manager.signinResourceOwnerCredentials(payload) - return initialize() - // const cookiesAttributes = { sameSite: 'strict' } - // if (payload.rememberMe) { - // cookiesAttributes.expires = 90 - // } + async function login() { + try { + await manager.signinRedirect(); + } catch (error) { + console.error('Error logging in:', error) + } } + + async function completeLogin() { + try { + const user = await manager.signinRedirectCallback() + isAuthenticated.value = true + const previousPage = sessionStorage.getItem('previousPage'); + // Redirect the user to the previous page if available + if (previousPage) { + window.location.href = previousPage; + } else { + // Redirect to a default page if the previous page is not available + router.push({name : 'Dashboard'}); + } + return user + } catch (error) { + console.error('Error completing login:', error) + return null + } + } + async function $reset() { delete repository.defaults.headers.common.Authorization authUser.value = {} isAuthenticated.value = false + //TODO: Call the logout callback of OIDC and log out from the IdP } async function updateAccount(data) { @@ -101,6 +122,7 @@ export const useAuthStore = defineStore('auth', () => { return { authUser, + completeLogin, isAuthenticated, userHasMailbox, validateAccess, diff --git a/frontend/src/views/login/LoginCallbackView.vue b/frontend/src/views/login/LoginCallbackView.vue new file mode 100644 index 000000000..740aa2ea0 --- /dev/null +++ b/frontend/src/views/login/LoginCallbackView.vue @@ -0,0 +1,10 @@ + + + diff --git a/frontend/src/views/login/LoginView.vue b/frontend/src/views/login/LoginView.vue index 8350fbe8c..042f797c5 100644 --- a/frontend/src/views/login/LoginView.vue +++ b/frontend/src/views/login/LoginView.vue @@ -1,26 +1,21 @@ + + - - diff --git a/test_project/oidc_key b/test_project/oidc_key new file mode 100644 index 000000000..9c591e4a1 --- /dev/null +++ b/test_project/oidc_key @@ -0,0 +1 @@ +OIDC_RSA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n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\nBdzfBSJDSvm22iXUM6TnKy5TksSayQ==\n-----END PRIVATE KEY-----\n" diff --git a/test_project/test_project/settings.py b/test_project/test_project/settings.py index aa9271543..1b1751506 100644 --- a/test_project/test_project/settings.py +++ b/test_project/test_project/settings.py @@ -194,12 +194,17 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "media") # oAuth2 settings OAUTH2_PROVIDER = { + "OIDC_ENABLED": True, + "OIDC_RP_INITIATED_LOGOUT_ENABLED": True, + "OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT": True, + "OIDC_RSA_PRIVATE_KEY": os.environ.get("OIDC_RSA_PRIVATE_KEY"), "SCOPES": { + "openid": "OpenID Connect scope", "read": "Read scope", "write": "Write scope", "introspection": "Introspect token scope", }, - "DEFAULT_SCOPES": ["read", "write"], + "DEFAULT_SCOPES": ["openid", "read", "write"], } # Rest framework settings