Compare commits

..

3 Commits

Author SHA1 Message Date
e7f0f81d85 4.46.4
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 2m53s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m13s
Build-Release-Image / Merge-Images (push) Successful in 12s
Build-Release-Image / Create-Release (push) Successful in 9s
Build-Release-Image / Notify (push) Successful in 2s
2024-07-13 12:00:06 +01:00
e82190f227 4.46.3
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 2m51s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 3m20s
Build-Release-Image / Merge-Images (push) Successful in 13s
Build-Release-Image / Create-Release (push) Successful in 9s
Build-Release-Image / Notify (push) Successful in 4s
2024-07-12 12:00:06 +01:00
9002bbad09 4.46.2
All checks were successful
Build-Release-Image / Build-Image (linux/amd64) (push) Successful in 3m7s
Build-Release-Image / Build-Image (linux/arm64) (push) Successful in 4m35s
Build-Release-Image / Merge-Images (push) Successful in 21s
Build-Release-Image / Create-Release (push) Successful in 9s
Build-Release-Image / Notify (push) Successful in 3s
2024-07-11 12:00:06 +01:00
14 changed files with 77 additions and 33 deletions

View File

@ -19,6 +19,9 @@ def authorize_request() -> Optional[Tuple[str, int]]:
if not api_key:
if current_user.is_authenticated:
# if current_user.is_authenticated and request.headers.get(
# constants.HEADER_ALLOW_API_COOKIES
# ):
g.user = current_user
else:
return jsonify(error="Wrong api key"), 401

1
app/app/constants.py Normal file
View File

@ -0,0 +1 @@
HEADER_ALLOW_API_COOKIES = "X-Sl-Allowcookies"

View File

@ -76,7 +76,6 @@ class SendRequest:
file_path = os.path.join(config.SAVE_UNSENT_DIR, file_name)
self.save_request_to_file(file_path)
@staticmethod
def save_request_to_failed_dir(self, prefix: str = "DeliveryRetryFail"):
file_name = (
f"{prefix}-{int(time.time())}-{uuid.uuid4()}.{SendRequest.SAVE_EXTENSION}"

View File

@ -1,7 +1,13 @@
from app.onboarding.base import onboarding_bp
from flask import render_template
from flask import render_template, url_for, redirect
@onboarding_bp.route("/", methods=["GET"])
def index():
return render_template("onboarding/index.html")
# Do the redirect to ensure cookies are set because they are SameSite=lax/strict
return redirect(url_for("onboarding.setup"))
@onboarding_bp.route("/setup", methods=["GET"])
def setup():
return render_template("onboarding/setup.html")

View File

@ -87,6 +87,7 @@ class RedisSessionStore(SessionInterface):
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
samesite = self.get_cookie_samesite(app)
val = pickle.dumps(dict(session))
ttl = int(app.permanent_session_lifetime.total_seconds())
# Only 5 minutes for non-authenticated sessions.
@ -109,6 +110,7 @@ class RedisSessionStore(SessionInterface):
domain=domain,
path=path,
secure=secure,
samesite=samesite,
)

View File

@ -235,13 +235,14 @@ def get_or_create_contact(from_header: str, mail_from: str, alias: Alias) -> Con
contact.mail_from = mail_from
Session.commit()
else:
alias_id = alias.id
try:
contact_email_for_reply = (
contact_email if is_valid_email(contact_email) else ""
)
contact = Contact.create(
user_id=alias.user_id,
alias_id=alias.id,
alias_id=alias_id,
website_email=contact_email,
name=contact_name,
mail_from=mail_from,
@ -261,9 +262,11 @@ def get_or_create_contact(from_header: str, mail_from: str, alias: Alias) -> Con
Session.commit()
except IntegrityError:
LOG.w(f"Contact with email {contact_email} for alias {alias} already exist")
Session.rollback()
contact = Contact.get_by(alias_id=alias.id, website_email=contact_email)
# No need to manually rollback, as IntegrityError already rolls back
LOG.info(
f"Contact with email {contact_email} for alias_id {alias_id} already existed, fetching from DB"
)
contact = Contact.get_by(alias_id=alias_id, website_email=contact_email)
return contact
@ -662,6 +665,9 @@ def handle_forward(envelope, msg: Message, rcpt_to: str) -> List[Tuple[bool, str
from_header = get_header_unicode(msg[headers.FROM])
LOG.d("Create or get contact for from_header:%s", from_header)
contact = get_or_create_contact(from_header, envelope.mail_from, alias)
alias = (
contact.alias
) # In case the Session was closed in the get_or_create we re-fetch the alias
reply_to_contact = None
if msg[headers.REPLY_TO]:

View File

@ -29,7 +29,7 @@ from sentry_sdk.integrations.flask import FlaskIntegration
from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration
from werkzeug.middleware.proxy_fix import ProxyFix
from app import paddle_utils, config, paddle_callback
from app import paddle_utils, config, paddle_callback, constants
from app.admin_model import (
SLAdminIndexView,
UserAdmin,
@ -430,6 +430,7 @@ def jinja2_filter(app):
PAGE_LIMIT=PAGE_LIMIT,
ZENDESK_ENABLED=ZENDESK_ENABLED,
MAX_NB_EMAIL_FREE_PLAN=MAX_NB_EMAIL_FREE_PLAN,
HEADER_ALLOW_API_COOKIES=constants.HEADER_ALLOW_API_COOKIES,
)
@ -702,7 +703,12 @@ def setup_coinbase_commerce(app):
def handle_coinbase_event(event) -> bool:
user_id = int(event["data"]["metadata"]["user_id"])
server_user_id = event["data"]["metadata"]["user_id"]
try:
user_id = int(server_user_id)
except ValueError:
user_id = int(float(server_user_id))
code = event["data"]["code"]
user = User.get(user_id)
if not user:

View File

@ -51,14 +51,19 @@ $(".enable-disable-alias").change(async function () {
await disableAlias(aliasId, alias);
});
function getHeaders() {
return {
"Content-Type": "application/json",
"X-Sl-Allowcookies": 'allow',
}
}
async function disableAlias(aliasId, alias) {
let oldValue;
try {
let res = await fetch(`/api/aliases/${aliasId}/toggle`, {
method: "POST",
headers: {
"Content-Type": "application/json",
}
headers: getHeaders()
});
if (res.ok) {
@ -94,9 +99,7 @@ $(".enable-disable-pgp").change(async function (e) {
try {
let res = await fetch(`/api/aliases/${aliasId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
headers: getHeaders(),
body: JSON.stringify({
disable_pgp: oldValue,
}),
@ -129,9 +132,7 @@ $(".pin-alias").change(async function () {
try {
let res = await fetch(`/api/aliases/${aliasId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
headers: getHeaders(),
body: JSON.stringify({
pinned: newValue,
}),
@ -161,9 +162,7 @@ async function handleNoteChange(aliasId, aliasEmail) {
try {
let res = await fetch(`/api/aliases/${aliasId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
headers: getHeaders(),
body: JSON.stringify({
note: note,
}),
@ -200,9 +199,7 @@ async function handleMailboxChange(aliasId, aliasEmail) {
try {
let res = await fetch(`/api/aliases/${aliasId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
headers: getHeaders(),
body: JSON.stringify({
mailbox_ids: mailbox_ids,
}),
@ -225,9 +222,7 @@ async function handleDisplayNameChange(aliasId, aliasEmail) {
try {
let res = await fetch(`/api/aliases/${aliasId}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
headers: getHeaders(),
body: JSON.stringify({
name: name,
}),

View File

@ -264,6 +264,7 @@
method: "POST",
headers: {
"Content-Type": "application/json",
'{{HEADER_ALLOW_API_COOKIES}}': 'allow'
}
});

View File

@ -80,7 +80,10 @@
},
methods: {
generateRandomAlias: async function (event) {
let result = await fetch('/api/alias/random/new', {method: 'POST'});
let result = await fetch('/api/alias/random/new', {method: 'POST',
headers: {
'{{HEADER_ALLOW_API_COOKIES}}': 'allow'
}});
if (result.ok) {
let data = await result.json();
this.ticket_email = data.alias;

View File

@ -216,6 +216,7 @@
method: "POST",
headers: {
"Content-Type": "application/json",
'{{HEADER_ALLOW_API_COOKIES}}': 'allow'
}
});
@ -232,6 +233,7 @@
method: "GET",
headers: {
"Content-Type": "application/json",
'{{HEADER_ALLOW_API_COOKIES}}': 'allow'
}
});
if (res.ok) {
@ -249,6 +251,7 @@
method: "GET",
headers: {
"Content-Type": "application/json",
'{{HEADER_ALLOW_API_COOKIES}}': 'allow'
}
});
if (res.ok) {

View File

@ -19,7 +19,10 @@
<div class="mt-8 text-center">
{% if current_user != None and current_user.is_authenticated %}
<h2 class="text-black-50" style="font-size:2rem">Performing the extension setup...</h2>
<h2 class="text-black-50" style="font-size:2rem">
Automatically performing extension setup.
If the setup doesn't start in a couple seconds click <a onclick="sendSetupMessage()" class="text-primary">here</a>
</h2>
{% else %}
<a class="mx-6 p-4 text-decoration-none"
style="background:black;
@ -41,6 +44,10 @@
{% if current_user != None and current_user.is_authenticated %}
<script type="text/javascript">
function sendSetupMessage(){
const data = { tag: "PERFORM_EXTENSION_SETUP" };
window.postMessage(data, "/");
}
let counterIterations = 5;
let extensionSetupIntervalId = setInterval(function() {
counterIterations--;
@ -48,9 +55,7 @@
clearInterval(extensionSetupIntervalId);
return;
}
const data = { tag: "PERFORM_EXTENSION_SETUP" };
window.postMessage(data, "/");
sendSetupMessage()
}, 300); // Send it many times, in case the extension had not registered the listener yet
</script>
{% endif %}

View File

@ -87,6 +87,7 @@
method: "GET",
headers: {
"Content-Type": "application/json",
'{{HEADER_ALLOW_API_COOKIES}}': 'allow'
}
});
if (res.ok) {

View File

@ -1,5 +1,7 @@
import os
from flask import testing
# use the tests/test.env config fle
# flake8: noqa: E402
@ -42,7 +44,16 @@ def flask_app():
yield app
from app import config
from app import config, constants
class CustomTestClient(testing.FlaskClient):
def open(self, *args, **kwargs):
if isinstance(args[0], str):
headers = kwargs.pop("headers", {})
headers.update({constants.HEADER_ALLOW_API_COOKIES: "allow"})
kwargs["headers"] = headers
return super().open(*args, **kwargs)
@pytest.fixture
@ -53,7 +64,9 @@ def flask_client():
# disable rate limit during test
config.DISABLE_RATE_LIMIT = True
try:
app.test_client_class = CustomTestClient
client = app.test_client()
client.environ_base[constants.HEADER_ALLOW_API_COOKIES] = "allow"
yield client
finally:
# disable rate limit again as some tests might enable rate limit