Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GSLUX-749: Auto auth with cookies and adapt for v3 #165

Merged
merged 4 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,11 @@
}
]
],
"env": {
"dev": {
"compact": false,
"minified": false
}
},
"compact": true
}
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ VITE_ARROW_MODEL_URL="/static-ngeo/models/arrow5.glb"
VITE_ELEVATION_URL="/raster"

# Auth
VITE_CREDENTIALS_ORIGIN="same-origin"
VITE_LOGIN_URL="/login"
VITE_LOGOUT_URL="/logout"
VITE_USERINFO_URL="/getuserinfo"
Expand Down
7 changes: 4 additions & 3 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ VITE_ARROW_MODEL_URL="https://migration.geoportail.lu/static-ngeo/models/arrow5.
VITE_ELEVATION_URL="https://migration.geoportail.lu/raster"

# Auth
VITE_LOGIN_URL="https://migration.geoportail.lu/login"
VITE_LOGOUT_URL="https://migration.geoportail.lu/logout"
VITE_USERINFO_URL="https://migration.geoportail.lu/getuserinfo"
VITE_CREDENTIALS_ORIGIN="include"
VITE_LOGIN_URL="http://localhost:8080/login"
VITE_LOGOUT_URL="http://localhost:8080/logout"
VITE_USERINFO_URL="http://localhost:8080/getuserinfo"
VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu"
VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password"
VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user"
1 change: 1 addition & 0 deletions .env.e2e
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ VITE_ARROW_MODEL_URL="https://migration.geoportail.lu/static-ngeo/models/arrow5.
VITE_ELEVATION_URL="https://migration.geoportail.lu/raster"

# Auth
VITE_CREDENTIALS_ORIGIN="same-origin"
VITE_LOGIN_URL="https://migration.geoportail.lu/login"
VITE_LOGOUT_URL="https://migration.geoportail.lu/logout"
VITE_USERINFO_URL="https://migration.geoportail.lu/getuserinfo"
Expand Down
7 changes: 4 additions & 3 deletions .env.staging
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ VITE_ARROW_MODEL_URL="https://migration.geoportail.lu/static-ngeo/models/arrow5.
VITE_ELEVATION_URL="https://migration.geoportail.lu/raster"

# Auth
VITE_LOGIN_URL="https://migration.geoportail.lu/login"
VITE_LOGOUT_URL="https://migration.geoportail.lu/logout"
VITE_USERINFO_URL="https://migration.geoportail.lu/getuserinfo"
VITE_CREDENTIALS_ORIGIN="same-origin"
VITE_LOGIN_URL="/login"
VITE_LOGOUT_URL="/logout"
VITE_USERINFO_URL="/getuserinfo"
VITE_MYACCOUNT_URL="https://myaccount.geoportail.lu"
VITE_MYACCOUNT_RECOVER_URL="https://myaccount.geoportail.lu/recover-password"
VITE_MYACCOUNT_NEW_URL="https://myaccount.geoportail.lu/new-user"
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ You can include the built lib multiple ways in the `package.json`:
}
```

### Develop on lib within geoportailv3 environnement
### Develop on lib within geoportailv3 environment

A simple way to develop on the lib and test it directly from within the geoportailv3 context is to map your `luxembourg-geoportal` repository as a volume to webpack_dev_server service of the docker composition:

Expand Down Expand Up @@ -267,3 +267,54 @@ To create custom components in the application using the lib, adapt the followin
const LayerPanelElement = createElementInstance(LayerPanel, app)
customElements.define('layer-panel', LayerPanelElement)
```

## 🔒 Authenticate user

To authenticate inside the v4 standalone app, you will need the v3 composition to be running at the same time and make some adjustement on both sides:

- in v4, update `VITE_LOGIN_URL`, `VITE_LOGOUT_URL` and `VITE_USERINFO_URL` to point to your local v3 composition

```bash
# file: luxembourg-geoportail/.env.development
VITE_LOGIN_URL="http://localhost:8080/login"
VITE_LOGOUT_URL="http://localhost:8080/logout"
VITE_USERINFO_URL="http://localhost:8080/getuserinfo"
```

- in v4, activate cross origin for GET/POST requests with a custom `VITE_CREDENTIALS_ORIGIN`.

```bash
# file: luxembourg-geoportail/.env.development
VITE_CREDENTIALS_ORIGIN="include"
# ⚠️ WARNING: don't use `"include"` value in production (but use `"same-origin"` instead).
```

- in v3, add a new env variable in `docker-compose.yaml`: `ALLOW_CORS`

```yaml
# file: geoportailv3/docker-compose.yaml
geoportal:
extends: ...
volumes_from: ...
volumes: ...
environment: ...
- VECTORTILESURL
- ALLOW_CORS # <=== Add new var here!
ports:
- 8080:8080
```

- and set it to value = `1` in the `.env.project` file to allow cors and cross origin requests.

```bash
# file: geoportailv3/.env.project
ALLOW_CORS=1 # ⚠️WARNING: don't use this value in production
```

## 🛡️ By pass CORS in dev mode

Because v4 is a standalone app with no backend, it uses sometimes local v3's backend and sometimes migration platform https://migration.geoportail.lu to perform api calls (see dedicated `.env` files to check urls).

To ignore CORS errors when performing these calls, it is mandatory to use a plugin in your web browser (such as Use Allow CORS plugin for Chrome: https://mybrowseraddon.com/access-control-allow-origin.html). Without the plugin functionnalities such as MyMaps, authentication, MySymbols, ... won't work.

💡 NB. For e2e testing, CORS securities have been deactivated with the Cypress option: `chromeWebSecurity: false` (only avaialable for Chrome browser).
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build": "run-p type-check build-only",
"build-only": "vite build",
"build:lib:prod": "npx vite build --mode prod --config vite-dist.config.ts --minify=esbuild --debug && npx babel bundle/lux.dist.mjs --out-file bundle/lux.dist.js",
"build:lib:dev": "npx vite build --mode staging --config vite-dist.config.ts --minify=false --base=/dev/main.html/ --debug && cp bundle/lux.dist.mjs bundle/lux.dist.js",
"build:lib:dev": "npx vite build --mode staging --config vite-dist.config.ts --minify=false --base=/dev/main.html/ --debug && BABEL_ENV=dev npx babel bundle/lux.dist.mjs --out-file bundle/lux.dist.js",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so I just noticed that this is essential for the lib build to work in v3 once the updates in https://github.com/Geoportail-Luxembourg/luxembourg-geoportail/pull/163/files#diff-7ae45ad102eab3b6d7e7896acd08c427a9b25b346470d7bc6507b6481575d519 are installed (cc @mki-c2c, @tonio, so you don't fall in the same trap I did)

"preview": "vite preview",
"test": "npm run test:unit",
"test:unit": "vitest --environment jsdom --root .",
Expand Down
2 changes: 1 addition & 1 deletion src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@
}

.lux-account-tab {
@apply ml-1 bg-primary text-white after:content-['\E02E'] after:font-icons after:text-3xl after:ml-4 w-20 px-2 pt-1;
@apply ml-1 bg-primary text-white after:content-['\E02E'] after:font-icons after:text-3xl after:ml-4 w-20 px-2 pt-1 mb-0 border-none;
}

.lux-account-content {
Expand Down
4 changes: 4 additions & 0 deletions src/bundle/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import './lib.css' // Tell Vite to build the css
import '../assets/main.css' // Tell Vite to build the css

import AlertNotifications from '@/components/alert-notifications/alert-notifications.vue'
import AuthForm from '@/components/auth/auth-form.vue'
import DropdownList from '@/components/common/dropdown-list.vue'
import MapContainer from '@/components/map/map-container.vue'
import BackgroundSelector from '@/components/background-selector/background-selector.vue'
Expand All @@ -33,6 +34,7 @@ import { useAppStore } from '@/stores/app.store'
import { useMapStore } from '@/stores/map.store'
import { useStyleStore } from '@/stores/style.store'
import { useThemeStore } from '@/stores/config.store'
import { useUserManagerStore } from '@/stores/user-manager.store'
import { statePersistorBgLayerService } from '@/services/state-persistor/state-persistor-layer-background.service'
import { statePersistorLayersService } from '@/services/state-persistor/state-persistor-layers.service'
import { statePersistorThemeService } from '@/services/state-persistor/state-persistor-theme.service'
Expand Down Expand Up @@ -118,6 +120,7 @@ export {
VueDOMPurifyHTML,
I18NextVue,
AlertNotifications,
AuthForm,
DropdownList,
MapContainer,
BackgroundSelector,
Expand All @@ -142,6 +145,7 @@ export {
useMapStore,
useStyleStore,
useThemeStore,
useUserManagerStore,
statePersistorBgLayerService,
statePersistorLayersService,
statePersistorThemeService,
Expand Down
6 changes: 3 additions & 3 deletions src/components/auth/auth-form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { shallowMount, VueWrapper } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'

import { useUserManagerStore } from '@/stores/user-manager.store'
import * as AuthService from '@/services/auth/auth.service'
import { authService } from '@/services/auth/auth.service'
import AuthForm from './auth-form.vue'

describe('AuthForm', () => {
Expand Down Expand Up @@ -44,7 +44,7 @@ describe('AuthForm', () => {
})

it('should call AuthService.authenticate on submit', async () => {
const authenticateMock = vi.spyOn(AuthService, 'authenticate')
const authenticateMock = vi.spyOn(authService, 'authenticate')
const userNameInput = wrapper.find('input[name="userName"]')
const userPasswordInput = wrapper.find('input[name="userPassword"]')

Expand Down Expand Up @@ -83,7 +83,7 @@ describe('AuthForm', () => {
})

it('should call AuthService.logout on logout', async () => {
const logoutMock = vi.spyOn(AuthService, 'logout')
const logoutMock = vi.spyOn(authService, 'logout')

await wrapper.find('button').trigger('click')

Expand Down
24 changes: 17 additions & 7 deletions src/components/auth/auth-form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { onMounted, ref, watch } from 'vue'
import { useTranslation } from 'i18next-vue'
import { storeToRefs } from 'pinia'

import * as AuthService from '@/services/auth/auth.service'
import { authService } from '@/services/auth/auth.service'
import { useAlertNotificationsStore } from '@/stores/alert-notifications.store'
import { AlertNotificationType } from '@/stores/alert-notifications.store.model'
import { useAppStore } from '@/stores/app.store'
Expand All @@ -20,25 +20,31 @@ const { lang, isApp } = storeToRefs(useAppStore())
const userManagerStore = useUserManagerStore()
const { setCurrentUser, clearUser } = userManagerStore
const { authenticated, currentUser } = storeToRefs(userManagerStore)
const autoAuthenticated = ref(false) // Will be set to true if user is authenticated via cookie on first call AuthService.getUserInfo()
const userName = ref('')
const userPassword = ref('')

watch(authenticated, authenticated => {
if (authenticated) {
if (!autoAuthenticated.value && authenticated) {
addNotification(t('Vous êtes maintenant correctement connecté.'))
}
})

onMounted(() => {
AuthService.getUserInfo()
.then(onAuthenticateSuccess)
authService
.getUserInfo()
.then(user => {
autoAuthenticated.value = true
onAuthenticateSuccess(user)
})
.catch(() => {
// do nothing, don't display errors
})
})

function logout() {
AuthService.logout()
authService
.logout()
.then(() => clearUser())
.catch(() =>
addNotification(
Expand All @@ -50,8 +56,12 @@ function logout() {
}

function submit() {
AuthService.authenticate(userName.value, userPassword.value, isApp.value)
.then(onAuthenticateSuccess)
authService
.authenticate(userName.value, userPassword.value, isApp.value)
.then(user => {
autoAuthenticated.value = false
onAuthenticateSuccess(user)
})
.catch(onAuthenticateFailure)
resetAuthForm()
}
Expand Down
18 changes: 9 additions & 9 deletions src/services/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it, MockedFunction, vi } from 'vitest'
import { User, UserApi } from '@/stores/user-manager.store.model'
import { authenticate, logout, getUserInfo } from './auth.service'
import { authService } from './auth.service'

global.fetch = vi.fn()

Expand Down Expand Up @@ -51,16 +51,16 @@ describe('Auth service', () => {
it('should authenticate user successfully and return user info', async () => {
mockFetchUserApiSuccess()

const result = await authenticate('the_user', 'the_password')
const result = await authService.authenticate('the_user', 'the_password')
expect(result).toStrictEqual(resultUserInfo)
})

it('should throw error when authentication fails', async () => {
mockFetchError()

await expect(authenticate('the_user', 'the_password')).rejects.toThrow(
'Error while trying to authenticate user'
)
await expect(
authService.authenticate('the_user', 'the_password')
).rejects.toThrow('Error while trying to authenticate user')
})
})

Expand All @@ -71,14 +71,14 @@ describe('Auth service', () => {
text: vi.fn().mockResolvedValue('success'),
})

const result = await logout()
const result = await authService.logout()
expect(result).toBe('success')
})

it('should throw error when logout fails', async () => {
mockFetchError()

await expect(logout()).rejects.toThrow(
await expect(authService.logout()).rejects.toThrow(
'Error while trying to logout user'
)
})
Expand All @@ -88,14 +88,14 @@ describe('Auth service', () => {
it('should get the user info', async () => {
mockFetchUserApiSuccess()

const result = await getUserInfo()
const result = await authService.getUserInfo()
expect(result).toStrictEqual(resultUserInfo)
})

it('should throw error when getUserInfo fails', async () => {
mockFetchError()

await expect(getUserInfo()).rejects.toThrow(
await expect(authService.getUserInfo()).rejects.toThrow(
'Error while trying to get user info'
)
})
Expand Down
Loading
Loading