Merge branch 'develop' of bitbucket.org:iterativ/vbv_lernwelt into develop
This commit is contained in:
commit
5af1041a12
|
|
@ -13,10 +13,9 @@ set -ev
|
|||
npm run build
|
||||
|
||||
# create and push new docker container
|
||||
docker buildx build --platform=linux/amd64 -f compose/django/Dockerfile -t "$TAG" -t "$LATEST" --build-arg VERSION="$VERSION" --build-arg BUILD_TIMESTAMP="$BUILD_TIMESTAMP" .
|
||||
# docker buildx build --platform=linux/amd64 -f compose/django/Dockerfile -t "$TAG" -t "$LATEST" --build-arg VERSION="$VERSION" --build-arg BUILD_TIMESTAMP="$BUILD_TIMESTAMP" .
|
||||
docker buildx build --platform=linux/amd64 -f compose/django/Dockerfile .
|
||||
docker push iterativ/vbv-lernwelt-django
|
||||
docker push "$TAG"
|
||||
docker push "$LATEST"
|
||||
|
||||
# deploy to caprover
|
||||
caprover deploy -h https://captain.control.iterativ.ch -a vbv-lernwelt -i docker.io/iterativ/vbv-lernwelt-django
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ function toggleNav() {
|
|||
state.showMenu = !state.showMenu;
|
||||
}
|
||||
|
||||
function menuActive(checkPath) {
|
||||
return route.path.startsWith(checkPath);
|
||||
function isInRoutePath(checkPaths: string[]) {
|
||||
return checkPaths.some((path) => route.path.startsWith(path))
|
||||
}
|
||||
|
||||
function inLearningPath() {
|
||||
return route.path.startsWith('/learningpath/') || route.path.startsWith('/circle/');
|
||||
return isInRoutePath(['/learningpath/', '/circle/']);
|
||||
}
|
||||
|
||||
function getLearningPathStringProp (prop: 'title' | 'slug'): string {
|
||||
|
|
@ -58,13 +58,17 @@ function handleDropdownSelect(data) {
|
|||
router.push('/profile')
|
||||
break
|
||||
case 'logout':
|
||||
router.push('/logout')
|
||||
userStore.handleLogout();
|
||||
break
|
||||
default:
|
||||
console.log('no action')
|
||||
}
|
||||
}
|
||||
|
||||
function logout () {
|
||||
userStore.handleLogout();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
log.debug('MainNavigationBar mounted');
|
||||
})
|
||||
|
|
@ -96,7 +100,7 @@ const profileDropdownData = [
|
|||
<div>
|
||||
<Teleport to="body">
|
||||
<MobileMenu
|
||||
:user="userStore"
|
||||
:user-store="userStore"
|
||||
:show="state.showMenu"
|
||||
:learning-path-slug="learninPathSlug()"
|
||||
:learning-path-name="learningPathName()"
|
||||
|
|
@ -139,6 +143,7 @@ const profileDropdownData = [
|
|||
|
||||
<div class="flex items-center lg:hidden">
|
||||
<router-link
|
||||
v-if="userStore.loggedIn"
|
||||
to="/messages"
|
||||
class="nav-item flex flex-row items-center"
|
||||
>
|
||||
|
|
@ -176,7 +181,7 @@ const profileDropdownData = [
|
|||
v-if="inLearningPath()"
|
||||
to="/learningpath/versicherungsvermittlerin"
|
||||
class="nav-item"
|
||||
:class="{'nav-item--active': menuActive('/learningpath/')}"
|
||||
:class="{'nav-item--active': inLearningPath()}"
|
||||
>
|
||||
Lernpfad
|
||||
</router-link>
|
||||
|
|
@ -185,7 +190,7 @@ const profileDropdownData = [
|
|||
v-if="inLearningPath()"
|
||||
to="/competences/"
|
||||
class="nav-item"
|
||||
:class="{'nav-item--active': menuActive('/competences/')}"
|
||||
:class="{'nav-item--active': isInRoutePath(['/competences/'])}"
|
||||
>
|
||||
Kompetenzprofil
|
||||
</router-link>
|
||||
|
|
@ -194,14 +199,14 @@ const profileDropdownData = [
|
|||
<router-link
|
||||
to="/shop"
|
||||
class="nav-item"
|
||||
:class="{'nav-item--active': menuActive('/shop')}"
|
||||
:class="{'nav-item--active': isInRoutePath(['/shop'])}"
|
||||
>
|
||||
Shop
|
||||
</router-link>
|
||||
<router-link
|
||||
to="/mediacenter"
|
||||
class="nav-item"
|
||||
:class="{'nav-item--active': menuActive('/mediacenter')}"
|
||||
:class="{'nav-item--active': isInRoutePath(['/mediacenter'])}"
|
||||
>
|
||||
Mediathek
|
||||
</router-link>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const router = useRouter()
|
|||
|
||||
const props = defineProps<{
|
||||
show: boolean,
|
||||
user: object,
|
||||
userStore: object,
|
||||
learningPathName: string,
|
||||
learningPathSlug: string
|
||||
}>()
|
||||
|
|
@ -29,28 +29,34 @@ const clickLink = (to: string) => {
|
|||
>
|
||||
<div>
|
||||
<div>
|
||||
<div class="flex border-b border-gray-500 -mx-8 px-8 pb-4">
|
||||
<div v-if="user.avatar_url">
|
||||
<div
|
||||
v-if="userStore.loggedIn"
|
||||
class="border-b border-gray-500 -mx-4 px-8 pb-4">
|
||||
<div class="-ml-4 flex">
|
||||
<div
|
||||
v-if="userStore.avatar_url"
|
||||
>
|
||||
<img class="inline-block h-16 w-16 rounded-full"
|
||||
:src="user.avatar_url"
|
||||
:src="userStore.avatar_url"
|
||||
alt=""/>
|
||||
</div>
|
||||
<div class="ml-6">
|
||||
<h3>{{user.first_name}} {{user.last_name}}</h3>
|
||||
<h3>{{userStore.first_name}} {{userStore.last_name}}</h3>
|
||||
<button
|
||||
@click="clickLink('/profile')"
|
||||
class="mt-2 inline-block items-center">
|
||||
class="mt-2 inline-block flex items-center">
|
||||
<IconSettings class="inline-block" /><span class="ml-3">Kontoeinstellungen</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="mt-6 pb-6 border-b border-gray-500"
|
||||
v-if="learningPathName">
|
||||
<h4 class="text-gray-900 text-sm">Kurs: {{learningPathName}}</h4>
|
||||
<ul class="mt-6">
|
||||
<li><button @click="clickLink('/learningpath')">Lernpfad</button></li>
|
||||
<li><button @click="clickLink(`/learningpath/${learningPathSlug}`)">Lernpfad</button></li>
|
||||
<li class="mt-6">Kompetenzprofil</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -60,9 +66,13 @@ const clickLink = (to: string) => {
|
|||
<li class="mt-6">Mediathek</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-6 items-center">
|
||||
<button
|
||||
v-if="userStore.loggedIn"
|
||||
type="button"
|
||||
@click="userStore.handleLogout()"
|
||||
class="mt-6 items-center flex">
|
||||
<IconLogout class="inline-block" /><span class="ml-1">Abmelden</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,43 @@
|
|||
<script setup lang="ts">
|
||||
// inspiration https://vuejs.org/examples/#modal
|
||||
|
||||
import {onMounted, watch} from "vue";
|
||||
import {HTMLElement} from "happy-dom";
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
}>()
|
||||
|
||||
const emits = defineEmits(['closemodal'])
|
||||
|
||||
let appElement: HTMLElement | null = null;
|
||||
|
||||
watch(() => props.show,
|
||||
(isShown) => isShown && appElement ? appElement.classList.add('no-scroll') : null
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
appElement = document.getElementById('app');
|
||||
})
|
||||
|
||||
const closeModal = () => {
|
||||
if (appElement) {
|
||||
appElement.classList.remove('no-scroll')
|
||||
}
|
||||
emits('closemodal')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition mode="in-out">
|
||||
<div
|
||||
v-if="show"
|
||||
class="circle-overview px-4 py-16 lg:px-16 lg:py-24 fixed top-0 overflow-y-scroll bg-white h-full w-full">
|
||||
class="px-4 py-16 lg:px-16 lg:py-24 fixed top-0 overflow-y-scroll bg-white h-full w-full">
|
||||
<button
|
||||
type="button"
|
||||
class="w-8 h-8 absolute right-4 top-4 cursor-pointer"
|
||||
@click="$emit('closemodal')"
|
||||
@click="closeModal"
|
||||
>
|
||||
<it-icon-close></it-icon-close>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -14,16 +14,18 @@ export type UserState = {
|
|||
loggedIn: boolean;
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'user',
|
||||
state: () => ({
|
||||
const initialUserState: UserState = {
|
||||
email: '',
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
username: '',
|
||||
avatar_url: '',
|
||||
loggedIn: false
|
||||
} as UserState),
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'user',
|
||||
state: () => (initialUserState as UserState),
|
||||
getters: {
|
||||
getFullName(): string {
|
||||
return `${this.first_name} ${this.last_name}`.trim();
|
||||
|
|
@ -46,6 +48,13 @@ export const useUserStore = defineStore({
|
|||
});
|
||||
}
|
||||
},
|
||||
handleLogout() {
|
||||
itPost('/core/logout/', {})
|
||||
.then(data => {
|
||||
Object.assign(this, initialUserState);
|
||||
window.location.href = '/';
|
||||
})
|
||||
},
|
||||
fetchUser() {
|
||||
const appStore = useAppStore();
|
||||
itGet('/api/core/me/').then((data) => {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,15 @@ onMounted(async () => {
|
|||
<div v-else>
|
||||
<div class="circle">
|
||||
<div class="flex flex-col lg:flex-row">
|
||||
<div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:py-8">
|
||||
<div class="flex-initial lg:w-128 px-4 py-4 lg:px-8 lg:pt-4">
|
||||
<router-link
|
||||
to="/learningpath/versicherungsvermittlerin"
|
||||
class="btn-text inline-flex items-center px-3 py-4 font-normal"
|
||||
>
|
||||
<it-icon-arrow-left class="-ml-1 mr-1 h-5 w-5"></it-icon-arrow-left>
|
||||
<span class="inline">zurück zum Lernpfad</span>
|
||||
</router-link>
|
||||
|
||||
<h1 class="text-blue-dark text-7xl">
|
||||
{{ circleStore.circle?.title }}
|
||||
</h1>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ const userStore = useUserStore();
|
|||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="pt-8"><a href="/sso/login/">Login mit SSO</a></p>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -90,3 +90,10 @@ svg {
|
|||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.no-scroll {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -526,15 +526,17 @@ OAUTH = {
|
|||
"client_name": env("IT_OAUTH_CLIENT_NAME", default="lernetz"),
|
||||
"client_id": env("IT_OAUTH_CLIENT_ID", default="iterativ"),
|
||||
"client_secret": env("IT_OAUTH_CLIENT_SECRET", default=""),
|
||||
"access_token_url": env("IT_OAUTH_ACCESS_TOKEN_URL", default="https://sso.test.b.lernetz.host/auth/realms/vbv/protocol/openid-connect/token"),
|
||||
"authorize_url": env("IT_OAUTH_AUTHORIZE_URL", default="https://sso.test.b.lernetz.host/auth/realms/vbv/protocol/openid-connect/auth"),
|
||||
# "access_token_url": env("IT_OAUTH_ACCESS_TOKEN_URL", default="https://sso.test.b.lernetz.host/auth/realms/vbv/protocol/openid-connect/token"),
|
||||
# "authorize_url": env("IT_OAUTH_AUTHORIZE_URL", default="https://sso.test.b.lernetz.host/auth/realms/vbv/protocol/openid-connect/auth"),
|
||||
"authorize_params": IT_OAUTH_AUTHORIZE_PARAMS,
|
||||
"access_token_params": IT_OAUTH_AUTHORIZE_PARAMS,
|
||||
"api_base_url": env("IT_OAUTH_API_BASE_URL", default="https://sso.test.b.lernetz.host/auth/realms/vbv/protocol/openid-connect/"),
|
||||
"local_redirect_uri": env("IT_OAUTH_LOCAL_DIRECT_URI", default="http://localhost:8000/sso/callback/"),
|
||||
"server_metadata_url": env("IT_OAUTH_SERVER_METADATA_URL", default="https://sso.test.b.lernetz.host/auth/realms/vbv/.well-known/openid-configuration"),
|
||||
"client_kwargs": {
|
||||
'scope': env("IT_OAUTH_SCOPE", default=''),
|
||||
'token_endpoint_auth_method': 'client_secret_post',
|
||||
'token_placement': 'header',
|
||||
'token_placement': 'body',
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ from vbv_lernwelt.core.middleware.auth import django_view_authentication_exempt
|
|||
from vbv_lernwelt.core.views import (
|
||||
rate_limit_exceeded_view,
|
||||
permission_denied_view,
|
||||
check_rate_limit, cypress_reset_view, vue_home, vue_login, me_user_view, )
|
||||
check_rate_limit, cypress_reset_view, vue_home, vue_login, me_user_view, vue_logout, )
|
||||
from .wagtail_api import wagtail_api_router
|
||||
|
||||
|
||||
|
|
@ -42,6 +42,7 @@ urlpatterns = [
|
|||
path('learnpath/', include("vbv_lernwelt.learnpath.urls")),
|
||||
path('api/completion/', include("vbv_lernwelt.completion.urls")),
|
||||
re_path(r'api/core/me/$', me_user_view, name='me_user_view'),
|
||||
re_path(r'core/logout/$', vue_logout, name='vue_logout'),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
if settings.DEBUG:
|
||||
# Static file serving when using Gunicorn + Uvicorn for local web socket development
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import requests
|
||||
import structlog
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.core.management import call_command
|
||||
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
|
|
@ -69,6 +69,12 @@ def me_user_view(request):
|
|||
return Response(status=403)
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
def vue_logout(request):
|
||||
logout(request)
|
||||
return Response({'success': True}, 200)
|
||||
|
||||
|
||||
def permission_denied_view(request, exception):
|
||||
return render(request, "403.html", status=403)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,6 @@ from authlib.integrations.django_client import OAuth
|
|||
from django.conf import settings
|
||||
|
||||
# # https://docs.authlib.org/en/latest/client/frameworks.html#frameworks-clients
|
||||
# def fetch_token(_name, request):
|
||||
# try:
|
||||
# token = OAuth2Token.objects.get(
|
||||
# user=request.user
|
||||
# )
|
||||
# return token.to_token()
|
||||
# except (OAuth2Token.DoesNotExist, TypeError):
|
||||
# return None
|
||||
|
||||
|
||||
# oauth = OAuth(fetch_token=fetch_token)
|
||||
oauth = OAuth()
|
||||
oauth.register(
|
||||
name=settings.OAUTH["client_name"],
|
||||
|
|
@ -20,10 +9,7 @@ oauth.register(
|
|||
client_secret=settings.OAUTH["client_secret"],
|
||||
request_token_url=None,
|
||||
request_token_params=None,
|
||||
access_token_url=settings.OAUTH["access_token_url"],
|
||||
access_token_params=None,
|
||||
authorize_url=settings.OAUTH["authorize_url"],
|
||||
authorize_params=settings.OAUTH["authorize_params"],
|
||||
api_base_url=settings.OAUTH["api_base_url"],
|
||||
client_kwargs=settings.OAUTH["client_kwargs"]
|
||||
client_kwargs=settings.OAUTH["client_kwargs"],
|
||||
server_metadata_url=settings.OAUTH["server_metadata_url"],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import structlog as structlog
|
|||
from authlib.integrations.base_client import OAuthError
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect
|
||||
from django.http import HttpResponse
|
||||
from sentry_sdk import capture_exception
|
||||
from django.contrib.auth import login as dj_login, get_user_model
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ def authorize(request):
|
|||
try:
|
||||
logger.debug(request)
|
||||
token = getattr(oauth, settings.OAUTH["client_name"]).authorize_access_token(request)
|
||||
deocded_token = decode_jwt(token["access_token"])
|
||||
deocded_token = decode_jwt(token["id_token"])
|
||||
except OAuthError as e:
|
||||
logger.error(f'OAuth error: {e}')
|
||||
if not settings.DEBUG:
|
||||
|
|
@ -45,5 +46,5 @@ def _user_data_from_token_data(token: dict) -> dict:
|
|||
"first_name": token.get("given_name", ""),
|
||||
"last_name": token.get("family_name", ""),
|
||||
"username": token.get("preferred_username", ""),
|
||||
"email": token.get("email", ""),
|
||||
"email": token.get("emails", [''])[0],
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue