diff --git a/website/age/apps.py b/website/age/apps.py
index 2c9ce9a9..cbbc3652 100644
--- a/website/age/apps.py
+++ b/website/age/apps.py
@@ -18,6 +18,8 @@ def ready(self):
AgeOverviewView,
)
+ from age import signals # noqa
+
def filter_user_page(user_page_list: list):
"""Add age overview tab on accounts page."""
user_page_list.append(
diff --git a/website/age/migrations/0001_initial.py b/website/age/migrations/0001_initial.py
index e15a692d..9076ab39 100644
--- a/website/age/migrations/0001_initial.py
+++ b/website/age/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 4.2.4 on 2023-09-18 05:28
+# Generated by Django 4.2.4 on 2023-09-18 20:45
from django.conf import settings
from django.db import migrations, models
@@ -21,7 +21,11 @@ class Migration(migrations.Migration):
("created_at", models.DateTimeField(auto_now_add=True)),
(
"user",
- models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name="is_18_years_old",
+ to=settings.AUTH_USER_MODEL,
+ ),
),
],
),
diff --git a/website/age/migrations/0002_alter_is18yearsold_user.py b/website/age/migrations/0002_alter_is18yearsold_user.py
deleted file mode 100644
index bc1c3ad4..00000000
--- a/website/age/migrations/0002_alter_is18yearsold_user.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Generated by Django 4.2.4 on 2023-09-18 05:45
-
-from django.conf import settings
-from django.db import migrations, models
-import django.db.models.deletion
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- migrations.swappable_dependency(settings.AUTH_USER_MODEL),
- ("age", "0001_initial"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="is18yearsold",
- name="user",
- field=models.OneToOneField(
- on_delete=django.db.models.deletion.CASCADE,
- related_name="is_18_years_old",
- to=settings.AUTH_USER_MODEL,
- ),
- ),
- ]
diff --git a/website/age/migrations/0003_sessionmapping.py b/website/age/migrations/0003_sessionmapping.py
deleted file mode 100644
index c2de7edc..00000000
--- a/website/age/migrations/0003_sessionmapping.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Generated by Django 4.2.4 on 2023-09-18 14:53
-
-from django.db import migrations, models
-import uuid
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("age", "0002_alter_is18yearsold_user"),
- ]
-
- operations = [
- migrations.CreateModel(
- name="SessionMapping",
- fields=[
- ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
- ("session_token", models.CharField(max_length=20)),
- ],
- ),
- ]
diff --git a/website/age/migrations/0004_alter_sessionmapping_session_token.py b/website/age/migrations/0004_alter_sessionmapping_session_token.py
deleted file mode 100644
index ed9c1683..00000000
--- a/website/age/migrations/0004_alter_sessionmapping_session_token.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 4.2.4 on 2023-09-18 14:54
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("age", "0003_sessionmapping"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="sessionmapping",
- name="session_token",
- field=models.CharField(max_length=20, unique=True),
- ),
- ]
diff --git a/website/age/models.py b/website/age/models.py
index de62e272..560a06a0 100644
--- a/website/age/models.py
+++ b/website/age/models.py
@@ -12,10 +12,3 @@ class Is18YearsOld(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="is_18_years_old")
created_at = models.DateTimeField(auto_now_add=True)
-
-
-class SessionMapping(models.Model):
- """Session mapping class for Yivi."""
-
- id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
- session_token = models.CharField(max_length=20, unique=True)
diff --git a/website/age/signals.py b/website/age/signals.py
new file mode 100644
index 00000000..434ccbe2
--- /dev/null
+++ b/website/age/signals.py
@@ -0,0 +1,23 @@
+from django.conf import settings
+from django.dispatch import receiver
+
+from age import models
+from yivi.models import Session
+from yivi.signals import attributes_verified
+
+
+@receiver(attributes_verified)
+def update_is_over_18(sender, **kwargs):
+ session: Session = kwargs.get("session")
+ if session.user is None or models.Is18YearsOld.objects.filter(user=session.user).exists():
+ return
+
+ attributes = kwargs.get("attributes")
+ for attribute_conjuction_clause in attributes:
+ for attribute_disjunction_clause in attribute_conjuction_clause:
+ attribute_id = attribute_disjunction_clause["id"]
+ if (
+ attribute_id == settings.AGE_VERIFICATION_DISCLOSE_ATTRIBUTE
+ and attribute_disjunction_clause["status"] == "PRESENT"
+ ):
+ models.Is18YearsOld.objects.create(user=session.user)
diff --git a/website/age/templates/age/age_overview.html b/website/age/templates/age/age_overview.html
index 5482eceb..b128f5b7 100644
--- a/website/age/templates/age/age_overview.html
+++ b/website/age/templates/age/age_overview.html
@@ -22,14 +22,14 @@
Verify your age
×
-
-
+
{% endif %}
diff --git a/website/age/urls.py b/website/age/urls.py
deleted file mode 100644
index 9fcd9927..00000000
--- a/website/age/urls.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.urls import path
-
-from age import views
-
-
-urlpatterns = []
diff --git a/website/age/views.py b/website/age/views.py
index 468b1727..13a3c7ae 100644
--- a/website/age/views.py
+++ b/website/age/views.py
@@ -1,6 +1,10 @@
+import json
+
+from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render
from django.template.loader import render_to_string
+from django.utils.safestring import mark_safe
from django.views.generic import TemplateView
from age import models
@@ -15,10 +19,20 @@ def get(self, request, **kwargs):
"""Get Age Overview View."""
is_18_years_old = models.Is18YearsOld.objects.filter(user=request.user).exists()
- rendered_tab = render_to_string("age/age_overview.html", context={"is_over_18": is_18_years_old})
+ rendered_tab = render_to_string(
+ "age/age_overview.html",
+ context={
+ "is_over_18": is_18_years_old,
+ "disclose": mark_safe(json.dumps({"disclose": [[[settings.AGE_VERIFICATION_DISCLOSE_ATTRIBUTE]]]})),
+ },
+ )
return render(
request,
self.template_name,
- {"active": kwargs.get("active"), "tabs": kwargs.get("tabs"), "rendered_tab": rendered_tab},
+ {
+ "active": kwargs.get("active"),
+ "tabs": kwargs.get("tabs"),
+ "rendered_tab": rendered_tab,
+ },
)
diff --git a/website/tosti/api/v1/urls.py b/website/tosti/api/v1/urls.py
index e6fc0a1d..110a3c5c 100644
--- a/website/tosti/api/v1/urls.py
+++ b/website/tosti/api/v1/urls.py
@@ -12,7 +12,7 @@
path("associations/", include("associations.api.v1.urls")),
path("transactions/", include("transactions.api.v1.urls")),
path("users/", include("users.api.v1.urls")),
- path("age/", include("age.api.v1.urls")),
+ path("yivi/", include("yivi.api.v1.urls")),
path("fridges/", include("fridges.api.v1.urls")),
path(
"schema",
diff --git a/website/tosti/settings/base.py b/website/tosti/settings/base.py
index 3a4a0672..c68d8c72 100644
--- a/website/tosti/settings/base.py
+++ b/website/tosti/settings/base.py
@@ -41,6 +41,7 @@
"silvasoft",
"oauth2_provider",
"corsheaders",
+ "yivi",
"age",
"fridges",
]
@@ -260,3 +261,5 @@
]
DJANGO_CRON_DELETE_LOGS_OLDER_THAN = 14
+
+AGE_VERIFICATION_DISCLOSE_ATTRIBUTE = "irma-demo.MijnOverheid.ageLower.over18"
diff --git a/website/tosti/urls.py b/website/tosti/urls.py
index 2c411bf3..5514de71 100644
--- a/website/tosti/urls.py
+++ b/website/tosti/urls.py
@@ -46,10 +46,6 @@
"thaliedje/",
include(("thaliedje.urls", "thaliedje"), namespace="thaliedje"),
),
- path(
- "age/",
- include(("age.urls", "age"), namespace="age"),
- ),
path(
"fridges/",
include(("fridges.urls", "fridges"), namespace="fridges"),
diff --git a/website/age/api/__init__.py b/website/yivi/__init__.py
similarity index 100%
rename from website/age/api/__init__.py
rename to website/yivi/__init__.py
diff --git a/website/age/api/v1/__init__.py b/website/yivi/api/__init__.py
similarity index 100%
rename from website/age/api/v1/__init__.py
rename to website/yivi/api/__init__.py
diff --git a/website/yivi/api/v1/__init__.py b/website/yivi/api/v1/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/website/age/api/v1/serializers.py b/website/yivi/api/v1/serializers.py
similarity index 100%
rename from website/age/api/v1/serializers.py
rename to website/yivi/api/v1/serializers.py
diff --git a/website/age/api/v1/urls.py b/website/yivi/api/v1/urls.py
similarity index 75%
rename from website/age/api/v1/urls.py
rename to website/yivi/api/v1/urls.py
index f3292186..5d45e4d1 100644
--- a/website/age/api/v1/urls.py
+++ b/website/yivi/api/v1/urls.py
@@ -1,5 +1,5 @@
from django.urls import path
-from age.api.v1.views import YiviStartAPIView, YiviResultAPIView
+from yivi.api.v1.views import YiviStartAPIView, YiviResultAPIView
urlpatterns = [
diff --git a/website/age/api/v1/views.py b/website/yivi/api/v1/views.py
similarity index 63%
rename from website/age/api/v1/views.py
rename to website/yivi/api/v1/views.py
index ad71ae9a..8e344797 100644
--- a/website/age/api/v1/views.py
+++ b/website/yivi/api/v1/views.py
@@ -2,9 +2,10 @@
from rest_framework.response import Response
from rest_framework.views import APIView
-from age.models import SessionMapping
-from age.services import get_yivi_client
-from age.yivi import YiviException
+from yivi import signals
+from yivi.models import Session
+from yivi.services import get_yivi_client
+from yivi.yivi import YiviException
from tosti.api.permissions import IsAuthenticatedOrTokenHasScopeForMethod
@@ -23,19 +24,23 @@ class YiviStartAPIView(APIView):
def post(self, request, **kwargs):
"""Start a Yivi request."""
yivi_client = get_yivi_client()
+ disclose = request.data.get("disclose", None)
+ if disclose is None:
+ return Response(status=400, data="Parameter 'disclose' must be specified.")
+
try:
response = yivi_client.start_session(
{
"@context": "https://irma.app/ld/request/disclosure/v2",
- "disclose": [[["irma-demo.MijnOverheid.ageLower.over18"]]],
+ "disclose": disclose,
}
)
except YiviException as e:
return Response(status=e.http_status, data=e.msg)
token = response["token"]
- session_mapping = SessionMapping.objects.create(session_token=token)
- response["token"] = session_mapping.id
+ session = Session.objects.create(session_token=token, user=request.user)
+ response["token"] = session.id
return Response(data=response)
@@ -55,8 +60,15 @@ def get(self, request, **kwargs):
"""Get the result of a Yivi session."""
yivi_client = get_yivi_client()
session_uuid = kwargs.get("pk")
- session = get_object_or_404(SessionMapping, pk=session_uuid)
+ session = get_object_or_404(Session, pk=session_uuid, user=request.user)
try:
- return Response(data=yivi_client.session_result(session.session_token))
+ response = yivi_client.session_result(session.session_token)
except YiviException as e:
return Response(status=e.http_status, data=e.msg)
+
+ response["token"] = session.id
+
+ if response.get("proofStatus") == "VALID":
+ signals.attributes_verified.send_robust(self.__class__, session=session, attributes=response["disclosed"])
+
+ return Response(data=response)
diff --git a/website/yivi/apps.py b/website/yivi/apps.py
new file mode 100644
index 00000000..85209b54
--- /dev/null
+++ b/website/yivi/apps.py
@@ -0,0 +1,8 @@
+from django.apps import AppConfig
+
+
+class YiviConfig(AppConfig):
+ """Yivi App Config."""
+
+ default_auto_field = "django.db.models.BigAutoField"
+ name = "yivi"
diff --git a/website/yivi/migrations/0001_initial.py b/website/yivi/migrations/0001_initial.py
new file mode 100644
index 00000000..e75fc643
--- /dev/null
+++ b/website/yivi/migrations/0001_initial.py
@@ -0,0 +1,35 @@
+# Generated by Django 4.2.4 on 2023-09-18 20:29
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import uuid
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Session",
+ fields=[
+ ("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
+ ("session_token", models.CharField(max_length=20, unique=True)),
+ ("created_at", models.DateTimeField(auto_now_add=True)),
+ (
+ "user",
+ models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/website/yivi/migrations/__init__.py b/website/yivi/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/website/yivi/models.py b/website/yivi/models.py
new file mode 100644
index 00000000..2be25402
--- /dev/null
+++ b/website/yivi/models.py
@@ -0,0 +1,16 @@
+import uuid
+
+from django.contrib.auth import get_user_model
+from django.db import models
+
+
+User = get_user_model()
+
+
+class Session(models.Model):
+ """Session mapping class for Yivi."""
+
+ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
+ session_token = models.CharField(max_length=20, unique=True)
+ user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
+ created_at = models.DateTimeField(auto_now_add=True)
diff --git a/website/age/services.py b/website/yivi/services.py
similarity index 85%
rename from website/age/services.py
rename to website/yivi/services.py
index c9039783..d0e88fb5 100644
--- a/website/age/services.py
+++ b/website/yivi/services.py
@@ -1,6 +1,6 @@
from django.conf import settings
-from age.yivi import Yivi
+from yivi.yivi import Yivi
def get_yivi_client() -> Yivi:
diff --git a/website/yivi/signals.py b/website/yivi/signals.py
new file mode 100644
index 00000000..1f634e7a
--- /dev/null
+++ b/website/yivi/signals.py
@@ -0,0 +1,3 @@
+import django.dispatch
+
+attributes_verified = django.dispatch.Signal()
diff --git a/website/age/static/age/js/yivi.js b/website/yivi/static/yivi/js/yivi.js
similarity index 100%
rename from website/age/static/age/js/yivi.js
rename to website/yivi/static/yivi/js/yivi.js
diff --git a/website/age/yivi.py b/website/yivi/yivi.py
similarity index 100%
rename from website/age/yivi.py
rename to website/yivi/yivi.py