diff --git a/Dockerfile b/Dockerfile
index e7843b0..8a2722a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
FROM alpine:3.18
-RUN apk --no-cache add python3 inkscape bash imagemagick ghostscript font-noto
-RUN apk --no-cache add py3-pip && pip3 install --break-system-packages django tzdata gunicorn
+RUN apk --no-cache add python3 inkscape bash imagemagick ghostscript font-noto py3-pip
+RUN pip3 install --break-system-packages django tzdata gunicorn google-api-python-client
ADD backend /zetikettes
diff --git a/backend/zetikettes/tikette/middleware.py b/backend/zetikettes/tikette/middleware.py
new file mode 100644
index 0000000..fd0a7e0
--- /dev/null
+++ b/backend/zetikettes/tikette/middleware.py
@@ -0,0 +1,15 @@
+from django.http import HttpResponse
+
+
+def cors_everywhere(get_response):
+ def middleware(request):
+ if request.method == 'OPTIONS':
+ response = HttpResponse()
+ else:
+ response = get_response(request)
+ response["Access-Control-Allow-Origin"] = request.headers.get("Origin", "*")
+ response["Access-Control-Allow-Credentials"] = "true"
+ response["Access-Control-Allow-Headers"] = "x-csrftoken"
+ return response
+
+ return middleware
diff --git a/backend/zetikettes/tikette/views.py b/backend/zetikettes/tikette/views.py
index 99a7835..5dc52b4 100644
--- a/backend/zetikettes/tikette/views.py
+++ b/backend/zetikettes/tikette/views.py
@@ -1,14 +1,44 @@
import json
+import sys
from django.conf import settings
+from django.core.exceptions import PermissionDenied
from django.http import JsonResponse
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
+from google.auth.transport import requests
+from google.oauth2 import id_token
from .planche import generate as stickersheet
-from .models import Tikette, Tikategory
+from .models import Tikette, Tikategory, Tizer
-CORS={'access-control-allow-origin': '*'}
+
+def ok(payload=None):
+ return JsonResponse({'status': 'ok', **(payload or {})})
+
+
+def post_please(f):
+ def __f(request):
+ if request.method != "POST":
+ return JsonResponse({'status': 'notok', 'message': 'this isn\'t the way'},
+ status=405, headers={'Allow': 'POST'})
+ return f(request)
+ return __f
+
+
+def auth_only(f):
+ def __f(request):
+ # check that email is valid
+ # exp?
+ print(request.META, file=sys.stderr)
+ if 'user_data' not in request.session:
+ raise PermissionDenied('Not logged in')
+ email = request.session['user_data']['email']
+ resp = Tizer.objects.filter(email=email)
+ if not resp:
+ raise PermissionDenied('User not found')
+ return f(request)
+ return __f
def quirk_bold_allergens(ingredients):
@@ -21,6 +51,7 @@ def quirk_bold_allergens(ingredients):
return ", ".join(out)
+@auth_only
def get_list(request):
tikettes = [{
'id': x.id,
@@ -35,22 +66,21 @@ def get_list(request):
'color': x.color,
'subs': {x.name: x.default for x in x.category.subs.all()},
} for x in Tikette.objects.all()]
- return JsonResponse({'status': 'ok', 'tikettes': tikettes}, headers=CORS)
+ return ok({'tikettes': tikettes})
+@auth_only
def get_categories(request):
tikats = [{
'id': x.id,
'name': x.name,
} for x in Tikategory.objects.all()]
- return JsonResponse({'status': 'ok', 'tikats': tikats}, headers=CORS)
+ return ok({'tikats': tikats})
-@csrf_exempt
+@auth_only
+@post_please
def generate(request):
- if request.method != "POST":
- return JsonResponse({'status': 'notok', 'message': 'this isn\'t the way'})
-
payload = json.loads(request.body)
subs = dict(payload['subs'])
@@ -61,27 +91,47 @@ def generate(request):
out_dir=settings.TIKETTE_OUT_DIR,
landscape=payload['landscape'])
- return JsonResponse({'status': 'ok', 'file': pdfpath}, headers=CORS)
+ return ok({'file': pdfpath})
-@csrf_exempt
+@auth_only
+@post_please
def newtikette(request):
- if request.method != "POST":
- return JsonResponse({'status': 'notok', 'message': 'this isn\'t the way'})
-
payload = json.loads(request.body)
tikette = Tikette(**payload)
tikette.save()
- return JsonResponse({'status': 'ok'}, headers=CORS)
+ return ok()
-@csrf_exempt
+@auth_only
+@post_please
def deletetikette(request):
- if request.method != "POST":
- return JsonResponse({'status': 'notok', 'message': 'this isn\'t the way'})
-
payload = json.loads(request.body)
Tikette.objects.get(id=payload["id"]).delete()
- return JsonResponse({'status': 'ok'}, headers=CORS)
+ return ok()
+
+
+@csrf_exempt
+@post_please
+def signin(request):
+ payload = json.loads(request.body)
+ token = payload['token']
+
+ try:
+ user_data = id_token.verify_oauth2_token(
+ token, requests.Request(), settings.GOOGLE_OAUTH_CLIENT_ID
+ )
+ except ValueError:
+ raise
+ return JsonResponse({'status': 'notok'}, status=403)
+
+ request.session['user_data'] = user_data
+
+ return ok(user_data)
+
+
+def signout(request):
+ del request.session['user_data']
+ return ok()
diff --git a/backend/zetikettes/zetikettes/settings.py b/backend/zetikettes/zetikettes/settings.py
index 59f63fd..f6ce435 100644
--- a/backend/zetikettes/zetikettes/settings.py
+++ b/backend/zetikettes/zetikettes/settings.py
@@ -53,6 +53,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'tikette.middleware.cors_everywhere',
]
ROOT_URLCONF = 'zetikettes.urls'
@@ -129,3 +130,6 @@ STATIC_ROOT = 'www_static'
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+
+# not a secret
+GOOGLE_OAUTH_CLIENT_ID = '634510965520-c5l7f15fn4koraqhpqfe01ssn8v0q2qk.apps.googleusercontent.com'
diff --git a/backend/zetikettes/zetikettes/urls.py b/backend/zetikettes/zetikettes/urls.py
index 4059fad..b0b1195 100644
--- a/backend/zetikettes/zetikettes/urls.py
+++ b/backend/zetikettes/zetikettes/urls.py
@@ -29,4 +29,6 @@ urlpatterns = [
path('generate', tikette.views.generate),
path('newtikette', tikette.views.newtikette),
path('deletetikette', tikette.views.deletetikette),
+ path('signin', tikette.views.signin),
+ path('signout', tikette.views.signout),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
diff --git a/frontend/config.js b/frontend/config.js
index d63e462..1cbf26f 100644
--- a/frontend/config.js
+++ b/frontend/config.js
@@ -1 +1,2 @@
const backend_api = '/zetikettes/srv/'
+const google_oauth_client_id = '634510965520-c5l7f15fn4koraqhpqfe01ssn8v0q2qk.apps.googleusercontent.com';
diff --git a/frontend/index.html b/frontend/index.html
index e6d8810..dee14dc 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -14,6 +14,7 @@
+
@@ -46,6 +47,7 @@ main {
+
addNouveau produit
diff --git a/frontend/zetikettes.js b/frontend/zetikettes.js
index b7b8376..1b08cbf 100644
--- a/frontend/zetikettes.js
+++ b/frontend/zetikettes.js
@@ -8,6 +8,28 @@ const params = {
var tikats;
+function getCookie(name) {
+ const cookies = document.cookie.split(';');
+ for (let cookie of cookies) {
+ cookie = cookie.trim();
+ if (cookie.startsWith(name + '=')) {
+ return cookie.substring(name.length + 1);
+ }
+ }
+ return null;
+}
+
+function post(url, data) {
+ const csrf_token = getCookie('csrftoken');
+ return $.ajax({
+ url,
+ data: JSON.stringify(data),
+ method: 'POST',
+ xhrFields: { withCredentials: true },
+ headers: { 'X-CSRFToken': csrf_token },
+ });
+}
+
function addProduct(tikette) {
const zett = tikette;
const appbody = $("#appbody");
@@ -38,7 +60,7 @@ function addProduct(tikette) {
loader.show();
$('.btn').addClass("disabled");
- $.post(backend_api + 'generate', JSON.stringify(req))
+ post(backend_api + 'generate', req)
.then(data => {
const pdfbtn = $(`open pdf`);
action.append(pdfbtn);
@@ -58,7 +80,7 @@ function addProduct(tikette) {
const req = {
id: zett.id,
};
- $.post(backend_api + 'deletetikette', JSON.stringify(req)).then(reload);
+ post(backend_api + 'deletetikette', req).then(reload);
return false;
});
@@ -115,7 +137,7 @@ function loadAll(zetikettes) {
color,
ab,
};
- $.post(backend_api + 'newtikette', JSON.stringify(req)).then(reload);
+ post(backend_api + 'newtikette', req).then(reload);
});
}
@@ -134,22 +156,45 @@ function konami() {
});
}
+async function googleCred(creds) {
+ const token = creds.credential;
+ await post(backend_api + 'signin', {token});
+ $('#signin-prompt').hide();
+ reload();
+}
+
async function reload() {
try {
const resp = await $.ajax({
url: backend_api + 'list',
timeout: 1000,
+ xhrFields: { withCredentials: true },
});
tikats = (await $.ajax({
url: backend_api + 'categories',
timeout: 1000,
+ xhrFields: { withCredentials: true },
})).tikats.sort((a, b) => a.name > b.name ? 1 : -1);
loadAll(resp.tikettes.sort((a, b) => (a.title < b.title) ? -1 : 1));
} catch(e) {
+ if (e.status === 403) {
+ $("#signin-prompt").show();
+ google.accounts.id.prompt(); // also display the One Tap dialog
+ return;
+ }
const appbody = $("#appbody");
appbody.append(`- Could not reach backend server`);
- throw e;
}
}
-$(document).ready(reload);
+$(document).ready(() => {
+ google.accounts.id.initialize({
+ client_id: google_oauth_client_id,
+ callback: googleCred,
+ });
+ google.accounts.id.renderButton(
+ document.getElementById("signin-prompt"),
+ { theme: "outline", size: "large" } // customization attributes
+ );
+ reload();
+});