diff --git a/app/config.py b/app/config.py index 82516489..7b7307a7 100644 --- a/app/config.py +++ b/app/config.py @@ -414,3 +414,6 @@ PHONE_PROVIDER_1_SECRET = os.environ.get("PHONE_PROVIDER_1_SECRET") PHONE_PROVIDER_2_HEADER = os.environ.get("PHONE_PROVIDER_2_HEADER") PHONE_PROVIDER_2_SECRET = os.environ.get("PHONE_PROVIDER_2_SECRET") + +ZENDESK_HOST = os.environ.get("ZENDESK_HOST") +ZENDESK_API_TOKEN = os.environ.get("ZENDESK_API_TOKEN") diff --git a/app/dashboard/__init__.py b/app/dashboard/__init__.py index 875b203a..a1c50611 100644 --- a/app/dashboard/__init__.py +++ b/app/dashboard/__init__.py @@ -31,4 +31,5 @@ from .views import ( app, delete_account, notification, + support, ) diff --git a/app/dashboard/views/support.py b/app/dashboard/views/support.py new file mode 100644 index 00000000..9ef81805 --- /dev/null +++ b/app/dashboard/views/support.py @@ -0,0 +1,106 @@ +import json +import urllib.parse +from typing import Union + +import requests +from flask import render_template, request, flash, url_for, redirect, g +from flask_login import login_required, current_user +from werkzeug.datastructures import FileStorage + +from app.dashboard.base import dashboard_bp +from app.extensions import limiter +from app.log import LOG +from app.config import ZENDESK_HOST, ZENDESK_API_TOKEN + +VALID_MIME_TYPES = ["text/plain", "message/rfc822"] + + +def check_zendesk_response_status(response_code: int) -> bool: + if response_code != 201: + if response_code in (401, 422): + LOG.e("Could not authenticate") + else: + LOG.e("Problem with the request. Status {}".format(response_code)) + return False + return True + + +def upload_file_to_zendesk_and_get_upload_token( + email: str, file: FileStorage +) -> Union[None, str]: + if file.mimetype not in VALID_MIME_TYPES and not file.mimetype.startswith("image/"): + flash( + "File {} is not an image, text or an email".format(file.filename), "warning" + ) + return + escaped_filename = urllib.parse.urlencode({"filename": file.filename}) + url = "https://{}/api/v2/uploads?{}".format(ZENDESK_HOST, escaped_filename) + headers = {"content-type": file.mimetype} + auth = ("{}/token".format(email), ZENDESK_API_TOKEN) + response = requests.post(url, headers=headers, data=file.stream, auth=auth) + if not check_zendesk_response_status(response.status_code): + return + data = response.json() + return data["upload"]["token"] + + +def create_zendesk_request(email: str, content: str, files: [FileStorage]) -> bool: + tokens = [] + for file in files: + if not file.filename: + continue + token = upload_file_to_zendesk_and_get_upload_token(email, file) + if token is None: + return False + tokens.append(token) + data = { + "request": { + "subject": "Ticket created for user {}".format(current_user.id), + "comment": {"type": "Comment", "body": content, "uploads": tokens}, + "requester": { + "name": "SimpleLogin user {}".format(current_user.id), + "email": email, + }, + } + } + url = "https://{}/api/v2/requests.json".format(ZENDESK_HOST) + headers = {"content-type": "application/json"} + auth = ("{}/token".format(email), ZENDESK_API_TOKEN) + response = requests.post(url, data=json.dumps(data), headers=headers, auth=auth) + if not check_zendesk_response_status(response.status_code): + return False + LOG.d("Ticket created") + return True + + +@dashboard_bp.route("/support", methods=["GET", "POST"]) +@login_required +@limiter.limit( + "2/hour", + methods=["POST"], + deduct_when=lambda r: hasattr(g, "deduct_limit") and g.deduct_limit, +) +def process_support_dialog(): + if not ZENDESK_HOST: + return render_template("dashboard/support_disabled.html") + if request.method == "GET": + return render_template( + "dashboard/support.html", ticket_email=current_user.email + ) + content = request.form.get("ticket_content") + email = request.form.get("ticket_email") + if not content: + flash("Please add a description", "error") + return render_template("dashboard/support.html", ticket_email=email) + if not email: + flash("Please add an email", "error") + return render_template("dashboard/support.html", ticket_content=content) + if not create_zendesk_request( + email, content, request.files.getlist("ticket_files") + ): + return render_template( + "dashboard/support.html", ticket_email=email, ticket_content=content + ) + g.deduct_limit = True + flash("Ticket created. You should have received an email notification.", "success") + return redirect(url_for("dashboard.index")) diff --git a/templates/dashboard/support.html b/templates/dashboard/support.html new file mode 100644 index 00000000..ca4bf758 --- /dev/null +++ b/templates/dashboard/support.html @@ -0,0 +1,97 @@ +{% extends 'default.html' %} + +{% set active_page = 'dashboard' %} + +{% block title %} + Support +{% endblock %} + +{% block head %} + +{% endblock %} + +{% block script %} + +{% endblock %} + +{% block default_content %} + +
+
+
+
Report a problem
+
+ If an email cannot be delivered to your mailbox, please check your notifications for error messages +
+
+ A support ticket will be created in Zendesk. Please do not include any sensitive information in the ticket. +
+
+
+ + +
+
+ Attach files to support request +
+
Only images, text and emails are accepted
+
+ + +
+
+ Where can we reach you? +
+
+ Conversations related to this ticket will be sent to this address. Feel free to use an alias here. +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+ +{% endblock %} + + + diff --git a/templates/header.html b/templates/header.html index 73f6393e..61fab722 100644 --- a/templates/header.html +++ b/templates/header.html @@ -71,11 +71,25 @@ -