Skip to content
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
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
VITE_API_BASE=http://test
VITE_API_VERSION=/api/v1
VITE_WL_API_KEY=testWelearn
VITE_ENVIRONMENT = 'test'

VITE_MIXPANEL_TOKEN=testMixpanelToken
23 changes: 13 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
VITE_ENVIRONMENT=$DOCKER_INJECT_VITE_ENVIRONMENT
VITE_WL_API_KEY=$DOCKER_INJECT_VITE_WL_API_KEY
VITE_MIXPANEL_TOKEN=$DOCKER_INJECT_VITE_MIXPANEL_TOKEN
VITE_KEYCLOAK_URL=$DOCKER_INJECT_VITE_KEYCLOAK_URL
VITE_KEYCLOAK_REALM=$DOCKER_INJECT_VITE_KEYCLOAK_REALM
VITE_KEYCLOAK_CLIENT_ID=$DOCKER_INJECT_VITE_KEYCLOAK_CLIENT_ID
secrets:
registry-username: ${{ secrets.DOCKER_PROD_USERNAME }}
registry-password: ${{ secrets.DOCKER_PROD_PASSWORD }}
Expand Down Expand Up @@ -53,16 +56,16 @@ jobs:
- name: Run unit tests
run: yarn test:unit

- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Run Playwright tests
run: yarn playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
# - name: Install Playwright Browsers
# run: yarn playwright install --with-deps
# - name: Run Playwright tests
# run: yarn playwright test
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: playwright-report
# path: playwright-report/
# retention-days: 30

tag-deploy:
needs:
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

ARG VITE_API_BASE
ARG VITE_ENVIRONMENT
ARG VITE_WL_API_KEY

Check warning on line 5 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-docker / build-push

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "VITE_WL_API_KEY") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ARG VITE_MIXPANEL_TOKEN

Check warning on line 6 in Dockerfile

View workflow job for this annotation

GitHub Actions / build-docker / build-push

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "VITE_MIXPANEL_TOKEN") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
ARG VITE_KEYCLOAK_URL
ARG VITE_KEYCLOAK_REALM
ARG VITE_KEYCLOAK_CLIENT_ID

WORKDIR /app
COPY package.json yarn.lock /app/
Expand Down
12 changes: 10 additions & 2 deletions k8s/welearn-client/secrets.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ injectedEnv:
sensitive:
VITE_WL_API_KEY: ENC[AES256_GCM,data:fYHIbraevA==,iv:deMmZhvmTfm6RBN5wyzOoyQtReI0rU4wCP8uXI1DYvo=,tag:iFzENYrC9/r0SAI0fnHCjg==,type:str]
VITE_MIXPANEL_TOKEN: ENC[AES256_GCM,data:E/0dg/cAZQbVC2oQAyNkk7WgPXFW6xiObsgWG59v1hY=,iv:E9ArEU0NRDEgauJQ5jDAjKJqyuqriS+kb0Pzj4wNMTE=,tag:gDzv+s5nqe9yvs5if92AvA==,type:str]
VITE_KEYCLOAK_URL: ENC[AES256_GCM,data:o4uJKcG0h0c9s4ZbWQAlcn5uWzBoxGRtjy2ER7Su,iv:6qx0oKFV8RcQYviylSYoColnjDBy/IEYChEKpSUJFoQ=,tag:/1XVQ6ewPmjsQgKeWZik6g==,type:str]
VITE_KEYCLOAK_REALM: ENC[AES256_GCM,data:A7g=,iv:zjk16js6k1AVJbeu/Zxguw/MQLd6nmYwiVCPZGUcLQ8=,tag:kgCLkfoPXZwGmnyQhusYFg==,type:str]
VITE_KEYCLOAK_CLIENT_ID: ENC[AES256_GCM,data:P7tDDQHq2ZonTkM=,iv:AEjZnJTi71smjrfewQb1zqSvKhlIaxbt2YexMfHu2G0=,tag:qZ5AE1SG1OFeStUltA4jVg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1dvu205sflecfu6uewgwmgs83nkm2c2fzfxs55zfkjs2gmqjya50qmm6lck
enc: |
Expand Down Expand Up @@ -67,7 +74,8 @@ sops:
NEFLTDYzR0VEdDRFNFhETUxhTWNzTGcK23vzyC1g1GKvgX13ue4YgKxApku06WZs
K7QchttgRGm9ZPtcqBvsbirTPmboVPw0MfF1DtBCpgQC9O/5idVOQQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-06-03T15:24:07Z"
mac: ENC[AES256_GCM,data:DlHnrkN3hA8+TYr9ZMOoTQ9TPed4fPylbGAS7FFwEkZkRS839X/7eOjp2B6ITERYEgD6sF99W6uhAMzfGn0aoy395FdgIvK7lZrnsPAkTDlCJ7JTQE1C2vOXlbloHw1lcO0jBW4kTWPsPGwBAHThIK9mV+4ezcHN1XRkULfZyic=,iv:HqLSLyUz0UZwedXjLsZOWOatvrvVrU2EbVweOfbn3pg=,tag:cwdBPcS+nnizrXz3larldg==,type:str]
lastmodified: "2026-09-24T15:19:27Z"
mac: ENC[AES256_GCM,data:ajRpuHDdPWGwK9z1QbCdCDRcuanECdl+MMG/CbjavNCufSfGVOWg/z9fU+Ml11S9AXarBifGnG9Ddc8c6AQoXRTkkMsJaq3lffFIA1et3T6pdnLgc2ps5gkmGGGw6MfOLkvikSAx9iCPRMGYxouJP9D7FNxoenk/4jcxs4ERoEQ=,iv:tueUY+TBxIymBc8/ntAqWkCwSc4ofFDd6f0zVtD5yss=,tag:JsxHU4OPeaC/IJdFwUt68Q==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"docx": "^9.3.0",
"file-saver": "^2.0.5",
"json2md": "^2.0.3",
"keycloak-js": "^26.2.4",
"lodash.isequal": "^4.5.0",
"marked": "^12.0.2",
"mixpanel-browser": "^2.80.0",
Expand All @@ -45,10 +46,12 @@
"@vue/eslint-config-typescript": "^11.0.3",
"@vue/test-utils": "^2.4.1",
"@vue/tsconfig": "^0.4.0",
"dotenv": "^18.0.3",
"eslint": "^8.45.0",
"eslint-plugin-vue": "^9.15.1",
"jsdom": "^22.1.0",
"npm-run-all": "^4.1.5",
"path": "^0.12.7",
"prettier": "^3.0.0",
"typescript": "~5.1.6",
"vite": "^6.4.2",
Expand Down
3 changes: 3 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
import path from 'path';

dotenv.config({ path: path.resolve(__dirname, '..', '.env.test') });
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
Expand Down
8 changes: 8 additions & 0 deletions public/silent-check-sso.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!doctype html>
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>
7 changes: 6 additions & 1 deletion src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import { useSourcesStore } from '@/stores/sources';
import { useBookmarksStore } from '@/stores/bookmarks';
import { getQueryParamValue } from '@/utils/urlsUtils';
import { getUserAndSession } from '@/utils/auth';
import { useAuthStore } from './stores/auth';

const { getSourcesList, getInfoPerCorpus } = useSourcesStore();
const { getBookmarks } = useBookmarksStore();
const fetchError = ref(false);

const authStore = useAuthStore();

async function initCalls() {
try {
await Promise.all([
Expand All @@ -30,7 +33,9 @@ async function initCalls() {

onMounted(async () => {
try {
await initCalls();
if (authStore.isAuthenticated || !authStore.shouldAuthenticate) {
await initCalls();
}
} catch (error) {
fetchError.value = true;
}
Expand Down
9 changes: 8 additions & 1 deletion src/components/FooterComponent.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<template>
<div class="footer-wrapper">
<div>
<a href="mailto:welearn@learningplanetinstitute.org">welearn@learningplanetinstitute.org</a>
<a href="mailto:welearn@learningplanetinstitute.org">{{ $t('contactUs') }}</a>
</div>
<div>
<a target="_blank" :href="WL_FEEDBACK_FORM_URL" class="gform">{{ $t('giveFeedback') }}</a>
<router-link class="terms" to="/terms">
<span class="item-name">{{ $t('terms') }}</span>
</router-link>
Expand Down Expand Up @@ -41,6 +44,10 @@ a:hover {
font-size: 0.8rem;
}

.gform {
display: none;
}

a:first-child {
margin-right: 0.5rem;
padding-right: 0.5rem;
Expand Down
11 changes: 11 additions & 0 deletions src/components/icons/EmailIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<svg width="20" height="16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.688 2.75 9.36 7.54c.385.277.895.277 1.28 0l6.672-4.79M3.25 15h13.5c1.243 0 2.25-1.045 2.25-2.333V3.333C19 2.045 17.993 1 16.75 1H3.25C2.007 1 1 2.045 1 3.333v9.334C1 13.955 2.007 15 3.25 15Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
20 changes: 20 additions & 0 deletions src/components/icons/HidePassIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<svg
width="24px"
height="24px"
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M22.6928 1.55018C22.3102 1.32626 21.8209 1.45915 21.6 1.84698L19.1533 6.14375C17.4864 5.36351 15.7609 4.96457 14.0142 4.96457C9.32104 4.96457 4.781 7.84644 1.11993 13.2641L1.10541 13.2854L1.09271 13.3038C0.970762 13.4784 0.967649 13.6837 1.0921 13.8563C3.79364 17.8691 6.97705 20.4972 10.3484 21.6018L8.39935 25.0222C8.1784 25.4101 8.30951 25.906 8.69214 26.1299L9.03857 26.3326C9.4212 26.5565 9.91046 26.4237 10.1314 26.0358L23.332 2.86058C23.553 2.47275 23.4219 1.97684 23.0392 1.75291L22.6928 1.55018ZM18.092 8.00705C16.7353 7.40974 15.3654 7.1186 14.0142 7.1186C10.6042 7.1186 7.07416 8.97311 3.93908 12.9239C3.63812 13.3032 3.63812 13.8561 3.93908 14.2354C6.28912 17.197 8.86102 18.9811 11.438 19.689L12.7855 17.3232C11.2462 16.8322 9.97333 15.4627 9.97333 13.5818C9.97333 11.2026 11.7969 9.27368 14.046 9.27368C15.0842 9.27368 16.0317 9.68468 16.7511 10.3612L18.092 8.00705ZM15.639 12.3137C15.2926 11.7767 14.7231 11.4277 14.046 11.4277C12.9205 11.4277 12 12.3906 12 13.5802C12 14.3664 12.8432 15.2851 13.9024 15.3624L15.639 12.3137Z"
fill="currentColor"
fill-rule="evenodd"
/>
<path
d="M14.6873 22.1761C19.1311 21.9148 23.4056 19.0687 26.8864 13.931C26.9593 13.8234 27 13.7121 27 13.5797C27 13.4535 26.965 13.3481 26.8956 13.2455C25.5579 11.2677 24.1025 9.62885 22.5652 8.34557L21.506 10.2052C22.3887 10.9653 23.2531 11.87 24.0894 12.9239C24.3904 13.3032 24.3904 13.8561 24.0894 14.2354C21.5676 17.4135 18.7903 19.2357 16.0254 19.827L14.6873 22.1761Z"
fill="currentColor"
/>
</svg>
</template>
11 changes: 11 additions & 0 deletions src/components/icons/LogoutIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<svg width="21" height="19" viewBox="0 0 21 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6.82353 1H3.11765C2.55601 1 2.01738 1.22125 1.62024 1.61508C1.22311 2.0089 1 2.54305 1 3.1V15.7C1 16.257 1.22311 16.7911 1.62024 17.1849C2.01738 17.5787 2.55601 17.8 3.11765 17.8H6.82353M7.08672 9.4H19.0867M14.5016 14.2L19.0867 9.4L14.5016 4.6"
stroke="black"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</template>
10 changes: 10 additions & 0 deletions src/components/icons/PasswordIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<svg width="17" height="22" viewBox="0 0 17 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.8 7.4V6.48571C2.8 3.4468 5.20857 1 8.2 1C11.1914 1 13.6 3.4468 13.6 6.48571V7.4M2.8 7.4C1.81 7.4 1 8.22286 1 9.22857V18.3714C1 19.3771 1.81 20.2 2.8 20.2H13.6C14.59 20.2 15.4 19.3771 15.4 18.3714V9.22857C15.4 8.22286 14.59 7.4 13.6 7.4M2.8 7.4H13.6M8.2 14.8V12.4"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
/>
</svg>
</template>
22 changes: 22 additions & 0 deletions src/components/icons/ShowPassIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<svg
width="24px"
height="24px"
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M17.7469 15.4149C17.9855 14.8742 18.1188 14.2724 18.1188 14.0016C18.1188 11.6544 16.2952 9.7513 14.046 9.7513C11.7969 9.7513 9.97332 11.6544 9.97332 14.0016C9.97332 16.3487 12.0097 17.8886 14.046 17.8886C15.3486 17.8886 16.508 17.2515 17.2517 16.2595C17.4466 16.0001 17.6137 15.7168 17.7469 15.4149ZM14.046 15.7635C14.5551 15.7635 15.0205 15.5684 15.3784 15.2457C15.81 14.8566 16 14.2807 16 14.0016C16 12.828 15.1716 11.8764 14.046 11.8764C12.9205 11.8764 12 12.8264 12 14C12 14.8104 12.9205 15.7635 14.046 15.7635Z"
fill="currentColor"
fill-rule="evenodd"
/>
<path
clip-rule="evenodd"
d="M1.09212 14.2724C1.07621 14.2527 1.10803 14.2931 1.09212 14.2724C0.96764 14.1021 0.970773 13.8996 1.09268 13.7273C1.10161 13.7147 1.11071 13.7016 1.11993 13.6882C4.781 8.34319 9.32105 5.5 14.0142 5.5C18.7025 5.5 23.2385 8.33554 26.8956 13.6698C26.965 13.771 27 13.875 27 13.9995C27 14.1301 26.9593 14.2399 26.8863 14.3461C23.2302 19.6702 18.6982 22.5 14.0142 22.5C9.30912 22.5 4.75717 19.6433 1.09212 14.2724ZM3.93909 13.3525C3.6381 13.7267 3.6381 14.2722 3.93908 14.6465C7.07417 18.5443 10.6042 20.3749 14.0142 20.3749C17.4243 20.3749 20.9543 18.5443 24.0894 14.6465C24.3904 14.2722 24.3904 13.7267 24.0894 13.3525C20.9543 9.45475 17.4243 7.62513 14.0142 7.62513C10.6042 7.62513 7.07417 9.45475 3.93909 13.3525Z"
fill="currentColor"
fill-rule="evenodd"
/>
</svg>
</template>
7 changes: 1 addition & 6 deletions src/components/nav/LanguageSelector.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
<template>
<div class="language-selector">
<select v-model="$i18n.locale" @change="storeLanguage">
<option
class="nav-lang"
v-for="locale in $i18n.availableLocales"
:key="`locale-${locale}`"
:value="locale"
>
<option v-for="locale in $i18n.availableLocales" :key="`locale-${locale}`" :value="locale">
{{ locale.toLocaleUpperCase() }}
</option>
</select>
Expand Down
31 changes: 31 additions & 0 deletions src/components/nav/NavComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ import QnAIcon from '@/components/icons/QnAIcon.vue';
import AboutIcon from '@/components/icons/AboutIcon.vue';

import BookIcon from '@/components/icons/BookIcon.vue';
import LogoutIcon from '@/components/icons/LogoutIcon.vue';
import NavBookmarkIcon from '@/components/icons/NavBookmarkIcon.vue';
import { useFeatureFlipStore } from '@/stores/featureFlip';
import BaseNavItem from './BaseNavItem.vue';
import LanguageSelector from './LanguageSelector.vue';
import { useAuth } from '@/composables/useAuth';

const featureFlip = useFeatureFlipStore();
const { isAuthenticated, login, logout } = useAuth();

// Features (tools) on the left; personal/utility items on the right.
const toolEntries = [
Expand Down Expand Up @@ -79,11 +82,37 @@ const utilityEntries = [
</template>

<LanguageSelector />
<div class="nav-auth mr-3">
<template v-if="isAuthenticated">
<button
class="button is-text"
aria-haspopup="true"
aria-controls="dropdown-menu"
@click="logout()"
>
<span class="mr-2 logout-label">{{ $t('logout') }}</span>
<span class="icon is-small">
<LogoutIcon />
</span>
</button>
</template>
<template v-else>
<button class="button is-text" @click="login()">
<span class="mr-2 logout-label">{{ $t('login') }}</span>
</button>
</template>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.nav-auth {
display: flex;
margin: auto;
gap: 1rem;
}

.nav {
display: flex;
z-index: 2;
Expand All @@ -99,6 +128,8 @@ const utilityEntries = [
opacity: 1;
display: flex;
flex-direction: row;
justify-content: space-between;

padding-top: 1.5rem;
text-align: start;

Expand Down
52 changes: 52 additions & 0 deletions src/composables/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useAuthStore } from '@/stores/auth';
import { storeToRefs } from 'pinia';
import keycloak from '@/utils/keycloak';

export function useAuth() {
const authStore = useAuthStore();
const { isAuthenticated, user, token, roles, isLoading, userName } = storeToRefs(authStore);

function getToken(): string | undefined {
return keycloak.token;
}

async function getValidToken(): Promise<string | undefined> {
try {
await keycloak.updateToken(30);
return keycloak.token;
} catch {
authStore.logout();
return undefined;
}
}

async function authenticatedFetch(url: string, options: RequestInit = {}): Promise<Response> {
const validToken = await getValidToken();
if (!validToken) {
throw new Error('Not authenticated');
}

return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${validToken}`,
'Content-Type': 'application/json'
}
});
}

return {
isAuthenticated,
user,
token,
roles,
isLoading,
userName,
login: authStore.login,
logout: authStore.logout,
getToken,
getValidToken,
authenticatedFetch
};
}
5 changes: 5 additions & 0 deletions src/localisation/authentication/en.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const authentication = {
registerTitle: 'Create account',
registerButton: 'Sign up',
cancelButton: 'Cancel'
};
5 changes: 5 additions & 0 deletions src/localisation/authentication/fr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const authentication = {
registerTitle: 'Creer votre compte',
registerButton: 'Inscription',
cancelButton: 'Annuler'
};
Loading
Loading