Skip to content

Commit

Permalink
Merge pull request #165 from Geoportail-Luxembourg/GSLUX-749-auth-fin…
Browse files Browse the repository at this point in the history
…alize

GSLUX-749: Auto auth with cookies and adapt for v3
  • Loading branch information
AlitaBernachot authored Oct 23, 2024
2 parents a151c5b + 0c1d712 commit dac4327
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 113 deletions.
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",
"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

0 comments on commit dac4327

Please sign in to comment.