Add google sign-in
Also, try to be a bit more correct with CSRF and CORS. It works on my machine.
This commit is contained in:
parent
69327d9edf
commit
c219091c2c
@ -1,7 +1,7 @@
|
|||||||
FROM alpine:3.18
|
FROM alpine:3.18
|
||||||
|
|
||||||
RUN apk --no-cache add python3 inkscape bash imagemagick ghostscript font-noto
|
RUN apk --no-cache add python3 inkscape bash imagemagick ghostscript font-noto py3-pip
|
||||||
RUN apk --no-cache add py3-pip && pip3 install --break-system-packages django tzdata gunicorn
|
RUN pip3 install --break-system-packages django tzdata gunicorn google-api-python-client
|
||||||
|
|
||||||
ADD backend /zetikettes
|
ADD backend /zetikettes
|
||||||
|
|
||||||
|
15
backend/zetikettes/tikette/middleware.py
Normal file
15
backend/zetikettes/tikette/middleware.py
Normal file
@ -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
|
@ -1,14 +1,44 @@
|
|||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
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 .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):
|
def quirk_bold_allergens(ingredients):
|
||||||
@ -21,6 +51,7 @@ def quirk_bold_allergens(ingredients):
|
|||||||
return ", ".join(out)
|
return ", ".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
@auth_only
|
||||||
def get_list(request):
|
def get_list(request):
|
||||||
tikettes = [{
|
tikettes = [{
|
||||||
'id': x.id,
|
'id': x.id,
|
||||||
@ -35,22 +66,21 @@ def get_list(request):
|
|||||||
'color': x.color,
|
'color': x.color,
|
||||||
'subs': {x.name: x.default for x in x.category.subs.all()},
|
'subs': {x.name: x.default for x in x.category.subs.all()},
|
||||||
} for x in Tikette.objects.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):
|
def get_categories(request):
|
||||||
tikats = [{
|
tikats = [{
|
||||||
'id': x.id,
|
'id': x.id,
|
||||||
'name': x.name,
|
'name': x.name,
|
||||||
} for x in Tikategory.objects.all()]
|
} 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):
|
def generate(request):
|
||||||
if request.method != "POST":
|
|
||||||
return JsonResponse({'status': 'notok', 'message': 'this isn\'t the way'})
|
|
||||||
|
|
||||||
payload = json.loads(request.body)
|
payload = json.loads(request.body)
|
||||||
|
|
||||||
subs = dict(payload['subs'])
|
subs = dict(payload['subs'])
|
||||||
@ -61,27 +91,47 @@ def generate(request):
|
|||||||
out_dir=settings.TIKETTE_OUT_DIR,
|
out_dir=settings.TIKETTE_OUT_DIR,
|
||||||
landscape=payload['landscape'])
|
landscape=payload['landscape'])
|
||||||
|
|
||||||
return JsonResponse({'status': 'ok', 'file': pdfpath}, headers=CORS)
|
return ok({'file': pdfpath})
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@auth_only
|
||||||
|
@post_please
|
||||||
def newtikette(request):
|
def newtikette(request):
|
||||||
if request.method != "POST":
|
|
||||||
return JsonResponse({'status': 'notok', 'message': 'this isn\'t the way'})
|
|
||||||
|
|
||||||
payload = json.loads(request.body)
|
payload = json.loads(request.body)
|
||||||
tikette = Tikette(**payload)
|
tikette = Tikette(**payload)
|
||||||
tikette.save()
|
tikette.save()
|
||||||
|
|
||||||
return JsonResponse({'status': 'ok'}, headers=CORS)
|
return ok()
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@auth_only
|
||||||
|
@post_please
|
||||||
def deletetikette(request):
|
def deletetikette(request):
|
||||||
if request.method != "POST":
|
|
||||||
return JsonResponse({'status': 'notok', 'message': 'this isn\'t the way'})
|
|
||||||
|
|
||||||
payload = json.loads(request.body)
|
payload = json.loads(request.body)
|
||||||
Tikette.objects.get(id=payload["id"]).delete()
|
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()
|
||||||
|
@ -53,6 +53,7 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'tikette.middleware.cors_everywhere',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'zetikettes.urls'
|
ROOT_URLCONF = 'zetikettes.urls'
|
||||||
@ -129,3 +130,6 @@ STATIC_ROOT = 'www_static'
|
|||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
# not a secret
|
||||||
|
GOOGLE_OAUTH_CLIENT_ID = '634510965520-c5l7f15fn4koraqhpqfe01ssn8v0q2qk.apps.googleusercontent.com'
|
||||||
|
@ -29,4 +29,6 @@ urlpatterns = [
|
|||||||
path('generate', tikette.views.generate),
|
path('generate', tikette.views.generate),
|
||||||
path('newtikette', tikette.views.newtikette),
|
path('newtikette', tikette.views.newtikette),
|
||||||
path('deletetikette', tikette.views.deletetikette),
|
path('deletetikette', tikette.views.deletetikette),
|
||||||
|
path('signin', tikette.views.signin),
|
||||||
|
path('signout', tikette.views.signout),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
@ -1 +1,2 @@
|
|||||||
const backend_api = '/zetikettes/srv/'
|
const backend_api = '/zetikettes/srv/'
|
||||||
|
const google_oauth_client_id = '634510965520-c5l7f15fn4koraqhpqfe01ssn8v0q2qk.apps.googleusercontent.com';
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<!-- Compiled and minified JavaScript -->
|
<!-- Compiled and minified JavaScript -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
|
||||||
|
<script src="https://accounts.google.com/gsi/client"></script>
|
||||||
|
|
||||||
<script src="jscolor.min.js"></script>
|
<script src="jscolor.min.js"></script>
|
||||||
<script src="config.js"></script>
|
<script src="config.js"></script>
|
||||||
@ -46,6 +47,7 @@ main {
|
|||||||
</nav>
|
</nav>
|
||||||
<main class="container row">
|
<main class="container row">
|
||||||
<p></p>
|
<p></p>
|
||||||
|
<div class="col s4 offset-s4" id="signin-prompt" style="display: none"></div>
|
||||||
<div class="col m6 offset-m3 s12">
|
<div class="col m6 offset-m3 s12">
|
||||||
<a class="modal-trigger btn orange hide-on-large-only" href="#newproduct"><i class="material-icons left">add</i>Nouveau produit</a>
|
<a class="modal-trigger btn orange hide-on-large-only" href="#newproduct"><i class="material-icons left">add</i>Nouveau produit</a>
|
||||||
<ul class="collapsible" id="appbody">
|
<ul class="collapsible" id="appbody">
|
||||||
|
@ -8,6 +8,28 @@ const params = {
|
|||||||
|
|
||||||
var tikats;
|
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) {
|
function addProduct(tikette) {
|
||||||
const zett = tikette;
|
const zett = tikette;
|
||||||
const appbody = $("#appbody");
|
const appbody = $("#appbody");
|
||||||
@ -38,7 +60,7 @@ function addProduct(tikette) {
|
|||||||
loader.show();
|
loader.show();
|
||||||
$('.btn').addClass("disabled");
|
$('.btn').addClass("disabled");
|
||||||
|
|
||||||
$.post(backend_api + 'generate', JSON.stringify(req))
|
post(backend_api + 'generate', req)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
const pdfbtn = $(`<a class="btn" href="${backend_api}data/${data.file}" target="_blank">open pdf</a>`);
|
const pdfbtn = $(`<a class="btn" href="${backend_api}data/${data.file}" target="_blank">open pdf</a>`);
|
||||||
action.append(pdfbtn);
|
action.append(pdfbtn);
|
||||||
@ -58,7 +80,7 @@ function addProduct(tikette) {
|
|||||||
const req = {
|
const req = {
|
||||||
id: zett.id,
|
id: zett.id,
|
||||||
};
|
};
|
||||||
$.post(backend_api + 'deletetikette', JSON.stringify(req)).then(reload);
|
post(backend_api + 'deletetikette', req).then(reload);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@ -115,7 +137,7 @@ function loadAll(zetikettes) {
|
|||||||
color,
|
color,
|
||||||
ab,
|
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() {
|
async function reload() {
|
||||||
try {
|
try {
|
||||||
const resp = await $.ajax({
|
const resp = await $.ajax({
|
||||||
url: backend_api + 'list',
|
url: backend_api + 'list',
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
});
|
});
|
||||||
tikats = (await $.ajax({
|
tikats = (await $.ajax({
|
||||||
url: backend_api + 'categories',
|
url: backend_api + 'categories',
|
||||||
timeout: 1000,
|
timeout: 1000,
|
||||||
|
xhrFields: { withCredentials: true },
|
||||||
})).tikats.sort((a, b) => a.name > b.name ? 1 : -1);
|
})).tikats.sort((a, b) => a.name > b.name ? 1 : -1);
|
||||||
loadAll(resp.tikettes.sort((a, b) => (a.title < b.title) ? -1 : 1));
|
loadAll(resp.tikettes.sort((a, b) => (a.title < b.title) ? -1 : 1));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
|
if (e.status === 403) {
|
||||||
|
$("#signin-prompt").show();
|
||||||
|
google.accounts.id.prompt(); // also display the One Tap dialog
|
||||||
|
return;
|
||||||
|
}
|
||||||
const appbody = $("#appbody");
|
const appbody = $("#appbody");
|
||||||
appbody.append(`<li>Could not reach backend server`);
|
appbody.append(`<li>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();
|
||||||
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user