Compare commits
No commits in common. "master" and "v1.1.2" have entirely different histories.
@ -4,16 +4,13 @@ WORKDIR /usr/src/app
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
RUN apk add python3-dev build-base linux-headers pcre-dev spamassassin
|
||||
RUN apk add python3-dev build-base linux-headers pcre-dev
|
||||
RUN pip install uwsgi
|
||||
#spamassassin_client
|
||||
|
||||
ENV UID=0
|
||||
ENV MOUNT=/
|
||||
|
||||
# Since the package maintainer doesnt merge PR
|
||||
# https://github.com/petermat/spamassassin_client/pull/2
|
||||
COPY ./main.py ./spamassassin/spamassasin_client.py ./
|
||||
COPY ./main.py ./list.tpl ./
|
||||
|
||||
# I juste wanted to change the socket owner but it turned out I needed to change thu uwsgi user
|
||||
#CMD uwsgi --exec-asap 'chown $UID:$UID /tmp/uwsgi/ ; mkdir -p $BASE_PATH && chown $UID:$UID $BASE_PATH' -s /tmp/uwsgi/uwsgi.sock --uid $UID --manage-script-name --mount /=server:app
|
@ -1,81 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" src="style.css" />
|
||||
<title>Contact mailer admin interface</title>
|
||||
<meta charset="utf-8" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main id="app">
|
||||
<section>
|
||||
<h3>Athentification</h3>
|
||||
<div v-if="!loggedin" class="loginform">
|
||||
<form v-on:submit.prevent="login">
|
||||
<select v-model="type">
|
||||
<option value="token">Utilisateur</option>
|
||||
<option value="admin_pass">Administrateur</option>
|
||||
</select>
|
||||
<input type="password" v-model="password" />
|
||||
<input type="submit" value="connect" />
|
||||
</form>
|
||||
</div>
|
||||
<div v-else="">
|
||||
<p>Connecté en tant que {{ type }}</p>
|
||||
<button v-on:click="logout">Se déconnecter</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div v-if="loggedin && type=='admin_pass'">
|
||||
<h3>Utilisateurices</h3>
|
||||
<form v-on:submit.prevent="addUser">
|
||||
<input type="text" v-model="newUser" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
<button v-on:click="getUsers">Rafraichir les utilisateurs</button>
|
||||
<ul>
|
||||
<li v-for="user in users">{{user.token}} — {{user.username}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Formulaires</h3>
|
||||
<button v-on:click="getForms">Rafraichir les formulaires</button>
|
||||
<ul>
|
||||
<li v-for="form in forms">
|
||||
<div>À {{form.mail}}</div>
|
||||
<div>Objet {{form.subject}}</div>
|
||||
<div>{{form.content}}</div>
|
||||
<div>{{form.token}} — {{form.honeypotfield}} — {{form.timerdelay}}</div>
|
||||
<button v-on:click="deleteForm(form.token)">Supprimer</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="page=='new_user'">
|
||||
<form v-on:submit.prevent="addForm">
|
||||
<label for="mail">Mail :</label>
|
||||
<input v-model="newForm.mail" type="text" name="mail" id="mail" />
|
||||
<br />
|
||||
<label for="content">Contenu :</label>
|
||||
<textarea v-model="newForm.content" name="content" id="content">
|
||||
</textarea>
|
||||
<br />
|
||||
<label for="subject">Objet :</label>
|
||||
<input v-model="newForm.subject" type="text" name="subject" id="subject" />
|
||||
<br />
|
||||
<label for="honeypot">Honeypot (ne pas toucher) :</label>
|
||||
<input v-model="newForm.honeypotfield" type="text" name="honeypot" id="honeypot" />
|
||||
<br />
|
||||
<label for="timerdelay">Timer delay :</label>
|
||||
<input v-model="newForm.timerdelay" type="number" name="timerdelay" id="timerdelay" />
|
||||
<br />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="./vue.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
</body>
|
||||
</html>
|
118
adminer/index.js
118
adminer/index.js
@ -1,118 +0,0 @@
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
type: 'admin_pass', /* admin_pass or token */
|
||||
password: 'test',
|
||||
loggedin: false,
|
||||
mailerHost: 'https://mailer.jean-cloud.net',
|
||||
//mailerHost: 'http://localhost:8080',
|
||||
//mailerHost: '/api',
|
||||
forms: [],
|
||||
users: [],
|
||||
newUser: '',
|
||||
page:'new_user',
|
||||
newForm: {
|
||||
'content': '{{message}}',
|
||||
'subject': '[contact jean-cloud.net] {{nom|annonyme}} — {{objet}}',
|
||||
'mail': 'contact@jean-cloud.org',
|
||||
'honeypotfield': 'prenom',
|
||||
'timerdelay': 5,
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
login: function () {
|
||||
if (!this.type) {
|
||||
console.log('missing type')
|
||||
return
|
||||
}
|
||||
if (!this.password) {
|
||||
console.log('missing password')
|
||||
return
|
||||
}
|
||||
this.loggedin = true
|
||||
this.getForms()
|
||||
if ( this.type == 'admin_pass' )
|
||||
this.getUsers()
|
||||
},
|
||||
logout: function () {
|
||||
this.type = 'token'
|
||||
this.password = null
|
||||
this.loggedin = false
|
||||
},
|
||||
getForms: function () {
|
||||
fetch(this.mailerHost + '/form/list', {
|
||||
method: 'POST',
|
||||
body: this.type + '=' + this.password
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.forms = data.data
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
},
|
||||
getUsers: function () {
|
||||
fetch(this.mailerHost + '/user/list', {
|
||||
method: 'POST',
|
||||
body: this.type + '=' + this.password
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.users = data.data
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
},
|
||||
addForm: function () {
|
||||
fetch(this.mailerHost + '/form', {
|
||||
method: 'post',
|
||||
body: this.type + '=' + this.password
|
||||
+ '&subject=' + this.newForm.subject
|
||||
+ '&mail=' + this.newForm.mail
|
||||
+ '&content=' + this.newForm.content
|
||||
+ '&honeypotfield=' + this.newForm.honeypotfield
|
||||
+ '&timerdelay=' + this.newForm.timerdelay
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data)
|
||||
this.getForms()
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error)
|
||||
})
|
||||
},
|
||||
addUser: function () {
|
||||
if (!this.newUser) {
|
||||
console.log('need username')
|
||||
return
|
||||
}
|
||||
fetch(this.mailerHost + '/user/' + this.newUser, {
|
||||
method: 'put',
|
||||
body: this.type + '=' + this.password
|
||||
})
|
||||
.then(data => {
|
||||
this.newUser = ''
|
||||
this.getUsers()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
|
||||
},
|
||||
deleteForm: function (formId) {
|
||||
fetch(this.mailerHost + '/form/' + formId, {
|
||||
method: 'delete',
|
||||
body: this.type + '=' + this.password
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data)
|
||||
this.getForms()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
})
|
||||
},
|
||||
},
|
||||
})
|
11965
adminer/vue.js
11965
adminer/vue.js
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
set -e
|
||||
version=2.1.0
|
||||
version=2.0.1
|
||||
docker build -t jeancloud/contact-mailer:latest -t jeancloud/contact-mailer:$version .
|
||||
docker push jeancloud/contact-mailer:latest
|
||||
docker push jeancloud/contact-mailer:$version
|
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@ -1 +0,0 @@
|
||||
dist
|
@ -1,6 +1,3 @@
|
||||
/* Executed after page loading */
|
||||
(function () {
|
||||
|
||||
class JeanCloudContactFormNotifier {
|
||||
constructor (theme, messageContainer) {
|
||||
/* Choose the theme */
|
||||
@ -61,13 +58,10 @@ function jeanCloudContactFormIntercept (formId, notifier) {
|
||||
*/
|
||||
const formElem = document.getElementById(formId)
|
||||
if (!formElem) {
|
||||
console.error('You tried to intercept form with id:"' + formId + '" but it was not found.')
|
||||
console.error('You tried to intercept form id:"' + formId + '" but it was not found.')
|
||||
return
|
||||
}
|
||||
|
||||
if (!notifier)
|
||||
console.log('No notifier given, no message can be displayed')
|
||||
|
||||
/* Intercept the submit event */
|
||||
formElem.onsubmit = async (e) => {
|
||||
e.preventDefault()
|
||||
@ -77,14 +71,6 @@ function jeanCloudContactFormIntercept (formId, notifier) {
|
||||
loadingText.classList.add("contact-mailer-sending");
|
||||
loadingText.textContent = 'Envoi en cours…'
|
||||
submitButton.after(loadingText)
|
||||
|
||||
/* Add the filling timer in seconds */
|
||||
const timerField = document.createElement('input')
|
||||
timerField.value = Math.round((Date.now() - contactMailerPageLoadedTime) / 1000)
|
||||
timerField.name = 'timerfield'
|
||||
timerField.hidden = 'hidden'
|
||||
formElem.appendChild(timerField)
|
||||
|
||||
/* XHR */
|
||||
fetch(formElem.action, {
|
||||
method: formElem.method,
|
||||
@ -105,16 +91,13 @@ function jeanCloudContactFormIntercept (formId, notifier) {
|
||||
})
|
||||
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
loadingText.parentNode.removeChild(loadingText)
|
||||
notifier.error('Impossible d’envoyer le formulaire. Vérifiez votre connexion internet ou réessayez plus tard.')
|
||||
})
|
||||
|
||||
/* Remove timer field after xhr. So we can try again. */
|
||||
formElem.removeChild(timerField)
|
||||
}
|
||||
}
|
||||
|
||||
(function () {
|
||||
/* Get the current js file location */
|
||||
const path = (document.currentScript.src[-1] == '/' ? document.currentScript.src : document.currentScript.src.replace(/\/[^\/]*$/, ''))
|
||||
|
||||
@ -124,18 +107,7 @@ function jeanCloudContactFormIntercept (formId, notifier) {
|
||||
link.rel = "stylesheet";
|
||||
link.crossOrigin = 'anonymous';
|
||||
link.href = path + "/style.css";
|
||||
link.integrity = 'sha384-D12RSMaIURTgZZljhdQqYlQzgEfXvOFwtiqzkWnNcDbKFwMWXcmsCRFO5BNii0MB'
|
||||
link.integrity = 'sha384-8PWvFCRowSxssUyiGirvpq/Nh6TTzYrsbAmpC0cw/OUKkZibNdI5L1gFiHxfrTZT'
|
||||
// cat style.css | openssl dgst -sha384 -binary | openssl base64 -A
|
||||
document.head.appendChild(link);
|
||||
|
||||
/* Load the targeted forms */
|
||||
var configs = document.getElementsByClassName('contact-form-config')
|
||||
for (var i=0; i<configs.length; i++) {
|
||||
var formId = configs[i].getAttribute('form-id')
|
||||
var theme = configs[i].getAttribute('notify-theme')
|
||||
jeanCloudContactFormIntercept(formId, new JeanCloudContactFormNotifier(theme))
|
||||
}
|
||||
|
||||
var contactMailerPageLoadedTime = Date.now()
|
||||
})()
|
||||
|
||||
|
2
client/package-lock.json
generated
2
client/package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jean-cloud-contact-mailer-client",
|
||||
"version": "1.1.6",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -1,18 +1,16 @@
|
||||
{
|
||||
"name": "jean-cloud-contact-mailer-client",
|
||||
"version": "1.1.6",
|
||||
"version": "1.0.16",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"prepublishOnly": "npm-auto-version",
|
||||
"postpublish": "git push origin --tags",
|
||||
"build": "mkdir -p dist && cp index.js style.css dist"
|
||||
"postpublish": "git push origin --tags"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"npm-auto-version": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@
|
||||
bottom: 0;
|
||||
width: 20%;
|
||||
min-width: 320px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.contact-mailer-message {
|
||||
@ -60,6 +59,5 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: unset;
|
||||
}
|
||||
|
||||
|
6
list.tpl
Normal file
6
list.tpl
Normal file
@ -0,0 +1,6 @@
|
||||
<h2>Liste</h2>
|
||||
<ul>
|
||||
% for item in data:
|
||||
<li>{{item}}</li>
|
||||
% end
|
||||
</ul>
|
@ -4,7 +4,6 @@ response = bottle.response
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.message import EmailMessage
|
||||
import os # for environ vars
|
||||
import sys # to print ot stderr
|
||||
import re # to match our template system
|
||||
@ -12,9 +11,7 @@ import pymongo # database
|
||||
from dotenv import load_dotenv
|
||||
import random, string # for tokens
|
||||
import html # for sanitization
|
||||
from bson.json_util import dumps
|
||||
import datetime # For email date
|
||||
from spamassasin_client import SpamAssassin
|
||||
import datetime # to name unsent mails
|
||||
|
||||
|
||||
##################################################### Bottle stuff ############################################
|
||||
@ -29,8 +26,10 @@ class StripPathMiddleware(object):
|
||||
e['PATH_INFO'] = e['PATH_INFO'].rstrip('/')
|
||||
return self.a(e, h)
|
||||
|
||||
|
||||
app = application = bottle.Bottle(catchall=False)
|
||||
|
||||
|
||||
##################################################### Configuration ############################################
|
||||
|
||||
# The exception that is thrown when an argument is missing
|
||||
@ -76,9 +75,6 @@ mongodb_dbname = get_env('MONGODB_DBNAME', 'contact_mailer')
|
||||
# Security
|
||||
admin_password = get_env('ADMIN_PASSWORD')
|
||||
|
||||
# Test purpose, do not send mail
|
||||
do_not_send = get_env('do_not_send', 'false') == 'true'
|
||||
|
||||
if 'SMTP_SSL' in os.environ and os.environ['SMTP_SSL'] == 'true':
|
||||
security = 'ssl'
|
||||
elif 'SMTP_STARTTLS' in os.environ and os.onviron['SMTP_STARTTLS'] == 'true':
|
||||
@ -89,7 +85,6 @@ else:
|
||||
# mongodb initialization
|
||||
mongodb_client = pymongo.MongoClient("mongodb://{}:{}/".format(mongodb_host, mongodb_port), connect=False, serverSelectionTimeoutMS=10000, connectTimeoutMS=10000)
|
||||
mongodb_database = mongodb_client[mongodb_dbname]
|
||||
print(mongodb_database)
|
||||
|
||||
|
||||
##################################################### main route: mail submission ############################################
|
||||
@ -100,7 +95,8 @@ def submission ():
|
||||
if 'token' in request.forms:
|
||||
token = request.forms.getunicode('token')
|
||||
else:
|
||||
return resp(400, 'Le jeton d’autentification est requis')
|
||||
response.status = 400
|
||||
return 'Le jeton d’autentification est requis'
|
||||
|
||||
# Getting mail address
|
||||
if 'mail' in request.forms:
|
||||
@ -113,52 +109,52 @@ def submission ():
|
||||
try:
|
||||
form = mongodb_database['forms'].find({'token': token})[0]
|
||||
except IndexError as e:
|
||||
return resp(400, 'Le formulaire demandé est introuvable, merci de vérifier que le token utilisé est le bon')
|
||||
response.status = 400
|
||||
return 'Le formulaire demandé est introuvable, merci de vérifier que le token utilisé est le bon'
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500, 'La base de donnée n’est pas accessible.')
|
||||
|
||||
# Did the bot filled the honeypot field?
|
||||
if 'honeypotfield' in form and form['honeypotfield'] in request.forms and request.forms.get(form['honeypotfield']) != '':
|
||||
return resp(400, 'We identified you as a bot. If this is an error, try to contact us via another way.')
|
||||
# Is the js timer enabled?
|
||||
if 'timerdelay' in form:
|
||||
# Did it work?
|
||||
if 'timerfield' not in request.forms or int(request.forms.get('timerfield')) < int(form['timerdelay']):
|
||||
print('timer : {}/{}'.format(request.forms.get('timerfield'), form['timerdelay']))
|
||||
return resp(400, 'We identified you as a bot. If this is an error, try to contact us via another way.')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible.'
|
||||
|
||||
try:
|
||||
subject_fields = fill_fields(request, get_fields(form['subject']))
|
||||
content_fields = fill_fields(request, get_fields(form['content']))
|
||||
except MissingParameterException as e:
|
||||
return resp(400, str(e))
|
||||
response.status = 404
|
||||
return str(e)
|
||||
|
||||
subject = re.sub(form_regex, r'{\1}', form['subject']).format(**subject_fields)
|
||||
content = re.sub(form_regex, r'{\1}', form['content']).format(**content_fields)
|
||||
|
||||
try:
|
||||
msg = build_mail(from_address, form['mail'], subject, content)
|
||||
if is_spam(msg):
|
||||
return resp(400, 'Votre message semble être du spam !')
|
||||
if not send_mail(form['mail'], msg):
|
||||
return resp(500, 'Le mail n’a pas pu être envoyé.')
|
||||
if not send_mail(from_address, form['mail'], subject, content):
|
||||
response.status = 500
|
||||
return 'Le mail n’a pas pu être envoyé.'
|
||||
except smtplib.SMTPDataError as e:
|
||||
save_mail (token, form['mail'], from_address, subject, content)
|
||||
response.status = 500
|
||||
error = 'Le mail a été refusé. Merci de réessayer plus tard.'
|
||||
error = 'Le mail a été refusé. Votre message a été enregistré, il sera remis manuellement à son destinataire.'
|
||||
except smtplib.SMTPRecipientsRefused as e:
|
||||
save_mail (token, form['mail'], from_address, subject, content)
|
||||
response.status = 500
|
||||
error = 'Impossible de trouver le destinataire du mail. Merci de réessayer plus tard'
|
||||
error = 'Impossible de trouver le destinataire du mail. Votre message a été enregistré, il sera remis manuellement à son destinataire.'
|
||||
except Exception as e:
|
||||
save_mail (token, form['mail'], from_address, subject, content)
|
||||
raise
|
||||
|
||||
|
||||
# Redirection
|
||||
#bottle.redirect(success_redirect_default)
|
||||
origin = request.headers.get('origin')
|
||||
return resp(200, 'Mail envoyé !')
|
||||
return '<p>Mail envoyé !</p>' + ('<p>Retour au <a href="{}">formulaire de contact</a></p>'.format(origin) if origin else '')
|
||||
|
||||
##################################################### Helpers ############################################
|
||||
|
||||
def resp (status, msg, data='{}'):
|
||||
response.status = status
|
||||
return '{{"status": "{}", "msg": "{}", "data": {}}}'.format(status, msg, data)
|
||||
def save_mail (token, to, from_address, subject, content):
|
||||
with open('unsent/unsent_{}_{}_{}.txt'.format(str(datetime.datetime.now()), token, to), 'w') as f:
|
||||
f.write("Unsent mail\nSubject: {}\nFrom: {}Content:\n{}".format(
|
||||
subject,
|
||||
from_address,
|
||||
content
|
||||
))
|
||||
|
||||
def get_fields (string):
|
||||
""" Parse the string looking for template elements and create an array with template to fill and their default values. None if mandatory. """
|
||||
@ -171,40 +167,19 @@ def fill_fields(request, fields):
|
||||
"""Look for fields in request and fill fields dict with values or let default ones. If the value is required, throw exception."""
|
||||
for field in fields:
|
||||
if field in request.forms:
|
||||
if request.forms.get(field).strip() == '' and fields[field] is None: # If empty and mandatory
|
||||
raise MissingParameterException("Le champs {} doit être rempli".format(field))
|
||||
fields[field] = request.forms.getunicode(field)
|
||||
if fields[field] is None: # if unicode failed
|
||||
fields[field] = request.forms.get(field)
|
||||
if fields[field] is None: # if get failed too
|
||||
raise Exception("Error, field '{}' not gettable".format(field))
|
||||
elif fields[field] is None:
|
||||
elif fields[field] == None:
|
||||
raise MissingParameterException("Le champs {} est obligatoire".format(field))
|
||||
return fields
|
||||
|
||||
def build_mail(from_address, to, subject, content):
|
||||
msg = EmailMessage()
|
||||
def send_mail(from_address, to, subject, content):
|
||||
"""Actually connect to smtp server, build a message object and send it as a mail"""
|
||||
msg = MIMEMultipart()
|
||||
msg['From'] = smtp_server_sender
|
||||
msg.add_header('reply-to', from_address)
|
||||
msg['To'] = to
|
||||
msg['Subject'] = subject
|
||||
msg['Date'] = datetime.datetime.now()
|
||||
msg.set_content(MIMEText(content, 'plain', "utf-8"))
|
||||
#or
|
||||
#msg.set_content(content)
|
||||
return msg
|
||||
|
||||
def is_spam(msg):
|
||||
assassin = SpamAssassin(msg.as_string().encode(), 'spamassassin')
|
||||
return assassin.is_spam()
|
||||
|
||||
def send_mail (to, msg):
|
||||
"""Actually connect to smtp server and send the mail"""
|
||||
if do_not_send:
|
||||
print('-------------------------- Following message sent. But only to stdout ----------------------------------')
|
||||
print(msg.as_string())
|
||||
print('--------------------------------------------------------------------------------------------------------')
|
||||
return True
|
||||
msg.attach(MIMEText(content, 'plain', "utf-8"))
|
||||
|
||||
# SMTP preambles
|
||||
if security == 'ssl':
|
||||
@ -231,7 +206,7 @@ def login(request):
|
||||
Privileges : 0=admin 1=loggedIn 1000=guest
|
||||
"""
|
||||
if 'admin_pass' in request.forms and request.forms['admin_pass'] == admin_password:
|
||||
return {'_privilege':0, '_id':'-1'}
|
||||
return {'_privilege':0}
|
||||
if 'token' in request.forms:
|
||||
token = request.forms.getunicode('token')
|
||||
try:
|
||||
@ -240,9 +215,9 @@ def login(request):
|
||||
return user
|
||||
except IndexError as e:
|
||||
pass
|
||||
#except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
# response.status = 500
|
||||
# return {'_error': True} # anonymous
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
|
||||
return {'_privilege': 1000} # anonymous
|
||||
|
||||
@ -257,45 +232,43 @@ def create_form ():
|
||||
elif mail_default_subject != '':
|
||||
subject = mail_default_subject
|
||||
else:
|
||||
return resp(400, 'Le champs « sujet » est requis')
|
||||
response.status = 400
|
||||
return 'Le champs « sujet » est requis'
|
||||
|
||||
# Getting mail content
|
||||
if 'content' in request.forms:
|
||||
content = request.forms.getunicode('content')
|
||||
else:
|
||||
return resp(400, 'Le champs « contenu » est requis')
|
||||
|
||||
response.status = 400
|
||||
return 'Le champs « contenu » est requis'
|
||||
|
||||
# Getting from address
|
||||
if 'mail' in request.forms:
|
||||
mail = request.forms.getunicode('mail')
|
||||
else:
|
||||
return resp(400, 'Le champs « adresse » est requis')
|
||||
response.status = 400
|
||||
return 'Le champs « adresse » est requis'
|
||||
|
||||
user = login(request)
|
||||
if user['_privilege'] > 1:
|
||||
return resp(400, 'Privilèges insufisants')
|
||||
response.status = 400
|
||||
return 'Privilèges insufisants'
|
||||
|
||||
# TODO limit the insertion rate
|
||||
token = ''.join(random.sample(token_chars, token_len))
|
||||
try:
|
||||
newEntry = {
|
||||
inserted = mongodb_database['forms'].insert_one({
|
||||
'mail': mail,
|
||||
'content': content,
|
||||
'subject': subject,
|
||||
'user_id': user['_id'],
|
||||
'token': token,
|
||||
}
|
||||
if 'honeypotfield' in request.forms:
|
||||
newEntry['honeypotfield'] = request.forms.getunicode('honeypotfield')
|
||||
if 'timerdelay' in request.forms:
|
||||
newEntry['timerdelay'] = request.forms.getunicode('timerdelay')
|
||||
|
||||
inserted = mongodb_database['forms'].insert_one(newEntry)
|
||||
})
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500, 'La base de donnée n’est pas accessible')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
|
||||
return resp(200, 'Créé : ' + token)
|
||||
return 'Créé : ' + token
|
||||
|
||||
@app.post('/form/list')
|
||||
def list_forms ():
|
||||
@ -306,28 +279,33 @@ def list_forms ():
|
||||
elif user['_privilege'] == 1:
|
||||
filt = {'user_id': user['_id']}
|
||||
else:
|
||||
return resp(400, 'Privilèges insufisants')
|
||||
response.status = 400
|
||||
return 'Privilèges insufisants'
|
||||
data = mongodb_database['forms'].find(filt)
|
||||
return resp(200,'', dumps(list(data)))
|
||||
return bottle.template("list.tpl", data=data)
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500,'La base de donnée n’est pas accessible')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
|
||||
|
||||
|
||||
@app.delete('/form/<token>')
|
||||
def delete_form(token):
|
||||
# TODO If admin or form owner
|
||||
# If admin or form owner
|
||||
user = login(request)
|
||||
if user['_privilege'] > 1:
|
||||
return resp(400, 'Privilèges insufisants')
|
||||
response.status = 400
|
||||
return 'Privilèges insufisants'
|
||||
|
||||
# Actually delete
|
||||
try:
|
||||
form = mongodb_database['forms'].find({'token':token })[0]
|
||||
except IndexError as e:
|
||||
return resp(400, 'Le token n’est pas valide')
|
||||
response.status = 400
|
||||
return 'Le token n’est pas valide'
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500, 'La base de donnée n’est pas accessible')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
|
||||
if user['_privilege'] == 0 or (form['user_id'] == user['_id']):
|
||||
try:
|
||||
@ -335,9 +313,11 @@ def delete_form(token):
|
||||
'token': token,
|
||||
})
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500, 'La base de donnée n’est pas accessible')
|
||||
return resp(200, 'Supprimé ' + token)
|
||||
return resp(400, 'Privilèges insufisants')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
return 'Supprimé ' + token
|
||||
response.status = 400
|
||||
return 'Privilèges insufisants'
|
||||
|
||||
|
||||
##################################################### Users ############################################
|
||||
@ -346,53 +326,63 @@ def delete_form(token):
|
||||
def list_users ():
|
||||
user = login(request)
|
||||
if user['_privilege'] > 0:
|
||||
return resp(400, 'Privilèges insufisants')
|
||||
response.status = 400
|
||||
return 'Privilèges insufisants'
|
||||
try:
|
||||
data = mongodb_database['users'].find()
|
||||
return resp(200, '', dumps(list(data)))
|
||||
return bottle.template("list.tpl", data=data)
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500, 'La base de donnée n’est pas accessible')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
|
||||
|
||||
@app.route('/user/<username>', method=['OPTIONS', 'PUT'])
|
||||
@app.put('/user/<username>')
|
||||
def create_user (username):
|
||||
user = login(request)
|
||||
if user['_privilege'] > 0:
|
||||
return resp(400, 'Privilèges insufisants')
|
||||
response.status = 400
|
||||
return 'Privilèges insufisants'
|
||||
try:
|
||||
mongodb_database['users'].find({'username': username})[0]
|
||||
return resp(400, 'L’utilisateur existe déjà')
|
||||
return 'L’utilisateur existe déjà'
|
||||
except IndexError as e:
|
||||
try:
|
||||
inserted = mongodb_database['users'].insert_one({
|
||||
'username': username,
|
||||
'token': ''.join(random.sample(token_chars, token_len))
|
||||
})
|
||||
return resp(200, 'Créé : ' + username)
|
||||
return 'Créé : ' + username
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500, 'La base de donnée n’est pas accessible')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500,'La base de donnée n’est pas accessible')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
|
||||
|
||||
@app.delete('/user/<username>')
|
||||
def delete_user (username):
|
||||
user = login(request)
|
||||
if user['_privilege'] > 0:
|
||||
return resp(400, 'Privilèges insufisants')
|
||||
response.status = 400
|
||||
return 'Privilèges insufisants'
|
||||
try:
|
||||
mongodb_database['users'].find({'username': username})[0]
|
||||
mongodb_database['users'].delete_one({
|
||||
'username': username,
|
||||
})
|
||||
return resp(200, 'Supprimé ' + username)
|
||||
return 'Supprimé ' + username
|
||||
except IndexError as e:
|
||||
return resp(400, 'L’utilisateur n’existe pas')
|
||||
response.status = 400
|
||||
return 'L’utilisateur n’existe pas'
|
||||
except pymongo.errors.ServerSelectionTimeoutError as e:
|
||||
return resp(500, 'La base de donnée n’est pas accessible')
|
||||
response.status = 500
|
||||
return 'La base de donnée n’est pas accessible'
|
||||
|
||||
|
||||
|
||||
##################################################### app startup ############################################
|
||||
prod_app = StripPathMiddleware(app)
|
||||
if __name__ == '__main__':
|
||||
bottle.run(app=prod_app, host=listen_address, port=listen_port, debug=True)
|
||||
bottle.run(app=StripPathMiddleware(app), host=listen_address, port=listen_port, debug=True)
|
||||
else:
|
||||
prod_app = StripPathMiddleware(app)
|
14
readme.md
14
readme.md
@ -1,6 +1,3 @@
|
||||
THIS REPO IS DISCONTINUED
|
||||
- Too much spam through our smtp server
|
||||
- It is simpler and more friendly to juste put your mail address on your website
|
||||
# Contact Mailer
|
||||
A minimal python app to send mail when people fills in your contact form!
|
||||
|
||||
@ -62,9 +59,9 @@ The app needs a lot of env vars to run :
|
||||
SMTP_SERVER_ADDRESS=mail.gandi.net
|
||||
SMTP_SERVER_PORT=465
|
||||
SMTP_SSL=true
|
||||
SMTP_SERVER_USERNAME=noreply@example.net
|
||||
SMTP_SERVER_PASSWORD=bigpass
|
||||
SMTP_SERVER_SENDER=noreply@example.net
|
||||
SMTP_SERVER_USERNAME=nepasrepondre@jean-cloud.org
|
||||
SMTP_SERVER_PASSWORD=B9UZtOnIlJcRzx8mh2jCsPTQujwTr9I6XyiA
|
||||
SMTP_SERVER_SENDER=nepasrepondre@jean-cloud.org
|
||||
MONGODB_HOST=mongodb
|
||||
ADMIN_PASSWORD=test
|
||||
UID=1000
|
||||
@ -84,14 +81,13 @@ plain or light theme.
|
||||
|
||||
## Roadmap
|
||||
### Near future
|
||||
- go on docker hub
|
||||
- use a standart logger (used by bottle and uwsgi) to log error on mail fail
|
||||
- [unit tests](https://bottlepy.org/docs/dev/recipes.html#unit-testing-bottle-applications)
|
||||
- add redirection urls to form config
|
||||
- Include some [capcha](https://alternativeto.net/software/recaptcha/) support
|
||||
- Correctly escape html entities
|
||||
- Sign mails with the server key
|
||||
- Use a dedicated SMTP server
|
||||
|
||||
### Ameliorations
|
||||
- Use real user/passwords accounts
|
||||
- Create a gui client
|
||||
- Créate a gui client
|
||||
|
@ -1,13 +0,0 @@
|
||||
[[source]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[packages]
|
||||
email = "*"
|
||||
bottle = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.6"
|
@ -1,2 +0,0 @@
|
||||
Since the package maintainer doesnt merge PR
|
||||
https://github.com/petermat/spamassassin_client/pull/2
|
@ -1,115 +0,0 @@
|
||||
import socket, select, re, logging
|
||||
from io import BytesIO
|
||||
|
||||
divider_pattern = re.compile(br'^(.*?)\r?\n(.*?)\r?\n\r?\n', re.DOTALL)
|
||||
first_line_pattern = re.compile(br'^SPAMD/[^ ]+ 0 EX_OK$')
|
||||
|
||||
|
||||
class SpamAssassin(object):
|
||||
def __init__(self, message, host='127.0.0.1', port=783, timeout=20):
|
||||
self.score = None
|
||||
self.symbols = None
|
||||
|
||||
# Connecting
|
||||
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
client.settimeout(timeout)
|
||||
client.connect((host, port))
|
||||
|
||||
# Sending
|
||||
client.sendall(self._build_message(message))
|
||||
client.shutdown(socket.SHUT_WR)
|
||||
|
||||
# Reading
|
||||
resfp = BytesIO()
|
||||
while True:
|
||||
ready = select.select([client], [], [], timeout)
|
||||
if ready[0] is None:
|
||||
# Kill with Timeout!
|
||||
logging.info('[SpamAssassin] - Timeout ({0}s)!'.format(str(timeout)))
|
||||
break
|
||||
|
||||
data = client.recv(4096)
|
||||
if data == b'':
|
||||
break
|
||||
|
||||
resfp.write(data)
|
||||
|
||||
# Closing
|
||||
client.close()
|
||||
client = None
|
||||
|
||||
self._parse_response(resfp.getvalue())
|
||||
|
||||
def _build_message(self, message):
|
||||
reqfp = BytesIO()
|
||||
data_len = str(len(message)).encode()
|
||||
reqfp.write(b'REPORT SPAMC/1.2\r\n')
|
||||
reqfp.write(b'Content-Length: ' + data_len + b'\r\n')
|
||||
reqfp.write(b'User: cx42\r\n\r\n')
|
||||
reqfp.write(message)
|
||||
return reqfp.getvalue()
|
||||
|
||||
def _parse_response(self, response):
|
||||
if response == b'':
|
||||
logging.info("[SPAM ASSASSIN] Empty response")
|
||||
return None
|
||||
|
||||
match = divider_pattern.match(response)
|
||||
if not match:
|
||||
logging.error("[SPAM ASSASSIN] Response error:")
|
||||
logging.error(response)
|
||||
return None
|
||||
|
||||
first_line = match.group(1)
|
||||
headers = match.group(2)
|
||||
body = response[match.end(0):]
|
||||
|
||||
# Checking response is good
|
||||
match = first_line_pattern.match(first_line)
|
||||
if not match:
|
||||
logging.error("[SPAM ASSASSIN] invalid response:")
|
||||
logging.error(first_line)
|
||||
return None
|
||||
|
||||
report_list = [s.strip() for s in body.decode('utf-8').strip().split('\n')]
|
||||
linebreak_num = report_list.index([s for s in report_list if "---" in s][0])
|
||||
tablelists = [s for s in report_list[linebreak_num + 1:]]
|
||||
|
||||
self.report_fulltext = '\n'.join(report_list)
|
||||
|
||||
|
||||
# join line when current one is only wrap of previous
|
||||
tablelists_temp = []
|
||||
if tablelists:
|
||||
for counter, tablelist in enumerate(tablelists):
|
||||
if len(tablelist)>1:
|
||||
if (tablelist[0].isnumeric() or tablelist[0] == '-') and (tablelist[1].isnumeric() or tablelist[1] == '.'):
|
||||
tablelists_temp.append(tablelist)
|
||||
else:
|
||||
if tablelists_temp:
|
||||
tablelists_temp[-1] += " " + tablelist
|
||||
tablelists = tablelists_temp
|
||||
|
||||
# create final json
|
||||
self.report_json = dict()
|
||||
for tablelist in tablelists:
|
||||
wordlist = re.split('\s+', tablelist)
|
||||
self.report_json[wordlist[1]] = {'partscore': float(wordlist[0]), 'description': ' '.join(wordlist[1:])}
|
||||
|
||||
headers = headers.decode('utf-8').replace(' ', '').replace(':', ';').replace('/', ';').split(';')
|
||||
self.score = float(headers[2])
|
||||
|
||||
def get_report_json(self):
|
||||
return self.report_json
|
||||
|
||||
def get_score(self):
|
||||
return self.score
|
||||
|
||||
def is_spam(self, level=5):
|
||||
return self.score is None or self.score > level
|
||||
|
||||
def get_fulltext(self):
|
||||
return self.report_fulltext
|
||||
|
||||
|
||||
|
@ -1,383 +0,0 @@
|
||||
%!PS-Adobe-3.0
|
||||
%%Creator: (ImageMagick)
|
||||
%%Title: (spamassassin_client)
|
||||
%%CreationDate: (2021-05-17T23:08:26+00:00)
|
||||
%%BoundingBox: 428 345 508 391
|
||||
%%HiResBoundingBox: 428 345 508 391
|
||||
%%DocumentData: Clean7Bit
|
||||
%%LanguageLevel: 1
|
||||
%%Orientation: Portrait
|
||||
%%PageOrder: Ascend
|
||||
%%Pages: 1
|
||||
%%EndComments
|
||||
|
||||
%%BeginDefaults
|
||||
%%EndDefaults
|
||||
|
||||
%%BeginProlog
|
||||
%
|
||||
% Display a color image. The image is displayed in color on
|
||||
% Postscript viewers or printers that support color, otherwise
|
||||
% it is displayed as grayscale.
|
||||
%
|
||||
/DirectClassPacket
|
||||
{
|
||||
%
|
||||
% Get a DirectClass packet.
|
||||
%
|
||||
% Parameters:
|
||||
% red.
|
||||
% green.
|
||||
% blue.
|
||||
% length: number of pixels minus one of this color (optional).
|
||||
%
|
||||
currentfile color_packet readhexstring pop pop
|
||||
compression 0 eq
|
||||
{
|
||||
/number_pixels 3 def
|
||||
}
|
||||
{
|
||||
currentfile byte readhexstring pop 0 get
|
||||
/number_pixels exch 1 add 3 mul def
|
||||
} ifelse
|
||||
0 3 number_pixels 1 sub
|
||||
{
|
||||
pixels exch color_packet putinterval
|
||||
} for
|
||||
pixels 0 number_pixels getinterval
|
||||
} bind def
|
||||
|
||||
/DirectClassImage
|
||||
{
|
||||
%
|
||||
% Display a DirectClass image.
|
||||
%
|
||||
systemdict /colorimage known
|
||||
{
|
||||
columns rows 8
|
||||
[
|
||||
columns 0 0
|
||||
rows neg 0 rows
|
||||
]
|
||||
{ DirectClassPacket } false 3 colorimage
|
||||
}
|
||||
{
|
||||
%
|
||||
% No colorimage operator; convert to grayscale.
|
||||
%
|
||||
columns rows 8
|
||||
[
|
||||
columns 0 0
|
||||
rows neg 0 rows
|
||||
]
|
||||
{ GrayDirectClassPacket } image
|
||||
} ifelse
|
||||
} bind def
|
||||
|
||||
/GrayDirectClassPacket
|
||||
{
|
||||
%
|
||||
% Get a DirectClass packet; convert to grayscale.
|
||||
%
|
||||
% Parameters:
|
||||
% red
|
||||
% green
|
||||
% blue
|
||||
% length: number of pixels minus one of this color (optional).
|
||||
%
|
||||
currentfile color_packet readhexstring pop pop
|
||||
color_packet 0 get 0.299 mul
|
||||
color_packet 1 get 0.587 mul add
|
||||
color_packet 2 get 0.114 mul add
|
||||
cvi
|
||||
/gray_packet exch def
|
||||
compression 0 eq
|
||||
{
|
||||
/number_pixels 1 def
|
||||
}
|
||||
{
|
||||
currentfile byte readhexstring pop 0 get
|
||||
/number_pixels exch 1 add def
|
||||
} ifelse
|
||||
0 1 number_pixels 1 sub
|
||||
{
|
||||
pixels exch gray_packet put
|
||||
} for
|
||||
pixels 0 number_pixels getinterval
|
||||
} bind def
|
||||
|
||||
/GrayPseudoClassPacket
|
||||
{
|
||||
%
|
||||
% Get a PseudoClass packet; convert to grayscale.
|
||||
%
|
||||
% Parameters:
|
||||
% index: index into the colormap.
|
||||
% length: number of pixels minus one of this color (optional).
|
||||
%
|
||||
currentfile byte readhexstring pop 0 get
|
||||
/offset exch 3 mul def
|
||||
/color_packet colormap offset 3 getinterval def
|
||||
color_packet 0 get 0.299 mul
|
||||
color_packet 1 get 0.587 mul add
|
||||
color_packet 2 get 0.114 mul add
|
||||
cvi
|
||||
/gray_packet exch def
|
||||
compression 0 eq
|
||||
{
|
||||
/number_pixels 1 def
|
||||
}
|
||||
{
|
||||
currentfile byte readhexstring pop 0 get
|
||||
/number_pixels exch 1 add def
|
||||
} ifelse
|
||||
0 1 number_pixels 1 sub
|
||||
{
|
||||
pixels exch gray_packet put
|
||||
} for
|
||||
pixels 0 number_pixels getinterval
|
||||
} bind def
|
||||
|
||||
/PseudoClassPacket
|
||||
{
|
||||
%
|
||||
% Get a PseudoClass packet.
|
||||
%
|
||||
% Parameters:
|
||||
% index: index into the colormap.
|
||||
% length: number of pixels minus one of this color (optional).
|
||||
%
|
||||
currentfile byte readhexstring pop 0 get
|
||||
/offset exch 3 mul def
|
||||
/color_packet colormap offset 3 getinterval def
|
||||
compression 0 eq
|
||||
{
|
||||
/number_pixels 3 def
|
||||
}
|
||||
{
|
||||
currentfile byte readhexstring pop 0 get
|
||||
/number_pixels exch 1 add 3 mul def
|
||||
} ifelse
|
||||
0 3 number_pixels 1 sub
|
||||
{
|
||||
pixels exch color_packet putinterval
|
||||
} for
|
||||
pixels 0 number_pixels getinterval
|
||||
} bind def
|
||||
|
||||
/PseudoClassImage
|
||||
{
|
||||
%
|
||||
% Display a PseudoClass image.
|
||||
%
|
||||
% Parameters:
|
||||
% class: 0-PseudoClass or 1-Grayscale.
|
||||
%
|
||||
currentfile buffer readline pop
|
||||
token pop /class exch def pop
|
||||
class 0 gt
|
||||
{
|
||||
currentfile buffer readline pop
|
||||
token pop /depth exch def pop
|
||||
/grays columns 8 add depth sub depth mul 8 idiv string def
|
||||
columns rows depth
|
||||
[
|
||||
columns 0 0
|
||||
rows neg 0 rows
|
||||
]
|
||||
{ currentfile grays readhexstring pop } image
|
||||
}
|
||||
{
|
||||
%
|
||||
% Parameters:
|
||||
% colors: number of colors in the colormap.
|
||||
% colormap: red, green, blue color packets.
|
||||
%
|
||||
currentfile buffer readline pop
|
||||
token pop /colors exch def pop
|
||||
/colors colors 3 mul def
|
||||
/colormap colors string def
|
||||
currentfile colormap readhexstring pop pop
|
||||
systemdict /colorimage known
|
||||
{
|
||||
columns rows 8
|
||||
[
|
||||
columns 0 0
|
||||
rows neg 0 rows
|
||||
]
|
||||
{ PseudoClassPacket } false 3 colorimage
|
||||
}
|
||||
{
|
||||
%
|
||||
% No colorimage operator; convert to grayscale.
|
||||
%
|
||||
columns rows 8
|
||||
[
|
||||
columns 0 0
|
||||
rows neg 0 rows
|
||||
]
|
||||
{ GrayPseudoClassPacket } image
|
||||
} ifelse
|
||||
} ifelse
|
||||
} bind def
|
||||
|
||||
/DisplayImage
|
||||
{
|
||||
%
|
||||
% Display a DirectClass or PseudoClass image.
|
||||
%
|
||||
% Parameters:
|
||||
% x & y translation.
|
||||
% x & y scale.
|
||||
% label pointsize.
|
||||
% image label.
|
||||
% image columns & rows.
|
||||
% class: 0-DirectClass or 1-PseudoClass.
|
||||
% compression: 0-none or 1-RunlengthEncoded.
|
||||
% hex color packets.
|
||||
%
|
||||
gsave
|
||||
/buffer 512 string def
|
||||
/byte 1 string def
|
||||
/color_packet 3 string def
|
||||
/pixels 768 string def
|
||||
|
||||
currentfile buffer readline pop
|
||||
token pop /x exch def
|
||||
token pop /y exch def pop
|
||||
x y translate
|
||||
currentfile buffer readline pop
|
||||
token pop /x exch def
|
||||
token pop /y exch def pop
|
||||
currentfile buffer readline pop
|
||||
token pop /pointsize exch def pop
|
||||
x y scale
|
||||
currentfile buffer readline pop
|
||||
token pop /columns exch def
|
||||
token pop /rows exch def pop
|
||||
currentfile buffer readline pop
|
||||
token pop /class exch def pop
|
||||
currentfile buffer readline pop
|
||||
token pop /compression exch def pop
|
||||
class 0 gt { PseudoClassImage } { DirectClassImage } ifelse
|
||||
grestore
|
||||
showpage
|
||||
} bind def
|
||||
%%EndProlog
|
||||
%%Page: 1 1
|
||||
%%PageBoundingBox: 428 345 508 391
|
||||
DisplayImage
|
||||
428 345
|
||||
80 46
|
||||
12
|
||||
80 46
|
||||
1
|
||||
1
|
||||
1
|
||||
8
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C
|
||||
1C1C1C1C1C1C1C1C
|
||||
%%PageTrailer
|
||||
%%Trailer
|
||||
%%EOF
|
32
test.html
Normal file
32
test.html
Normal file
@ -0,0 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="./client/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="contact-mailer-message"></div>
|
||||
<form action="https://mailer.jean-cloud.net/submit" method="POST" id="contact-mailer-form">
|
||||
<input type="hidden" name="token" value="s0y6WANzU1XnYERoJxMwekP9pqilSVLK5Gbf3hmZadHB2rQ4u8" />
|
||||
<div>
|
||||
<label for="nom">Votre nom :</label>
|
||||
<input type="text" name="nom" required="required"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="mail">Adresse mail :</label>
|
||||
<input type="email" name="mail" required="required"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="objet">Objet :</label>
|
||||
<input type="text" name="objet" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="objet">Votre message :</label>
|
||||
<textarea name="message" required="required"></textarea>
|
||||
</div>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
<script src="./client/index.js"></script>
|
||||
<script> jeanCloudContactFormIntercept ('contact-mailer-form', new JeanCloudContactFormNotifier()) </script>
|
||||
</body>
|
||||
</html>
|
@ -4,40 +4,22 @@ services:
|
||||
image: mongo
|
||||
|
||||
mailer:
|
||||
build: ../server
|
||||
build: ..
|
||||
volumes:
|
||||
- ../server/main.py:/usr/src/app/main.py
|
||||
- ../main.py:/usr/src/app/main.py
|
||||
- ./uwsgi:/tmp/uwsgi
|
||||
depends_on:
|
||||
- db
|
||||
- spamassassin
|
||||
environment:
|
||||
MONGODB_HOST: db
|
||||
SMTP_SERVER_ADDRESS: toto.mail
|
||||
SMTP_SERVER_ADDRESS: 'lol'
|
||||
SMTP_SERVER_PORT: 994
|
||||
SMTP_SERVER_USERNAME: toto@toto.mail
|
||||
SMTP_SERVER_PASSWORD: password
|
||||
SMTP_SERVER_SENDER: toto@toto.mail
|
||||
ADMIN_PASSWORD: test
|
||||
SMTP_SERVER_USERNAME: toto
|
||||
SMTP_SERVER_PASSWORD: lol
|
||||
SMTP_SERVER_SENDER: moi
|
||||
ADMIN_PASSWORD: admin
|
||||
SMTP_SSL: 'true'
|
||||
UID: 101
|
||||
MOUNT: /api
|
||||
do_not_send: 'true'
|
||||
|
||||
|
||||
proxy:
|
||||
image: nginx
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
- ../:/usr/app
|
||||
- ./uwsgi:/tmp/uwsgi
|
||||
environment:
|
||||
nginx_uid: 1000
|
||||
depends_on:
|
||||
- mailer
|
||||
|
||||
spamassassin:
|
||||
image: dinkel/spamassassin
|
||||
restart: unless-stopped
|
||||
|
@ -1,39 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="./client/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="contact-mailer-message"></div>
|
||||
<form action="/api/submit" method="POST" id="contact-mailer-form">
|
||||
<noscript>Les protections anti-spam, nécéssitent l’utilisation de javascript. Rien d’intrusif normalement.</noscript>
|
||||
<div>
|
||||
<label for="token">Token :</label>
|
||||
<input type="text" name="token"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="nom">Votre nom :</label>
|
||||
<input type="text" name="nom" required="required"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="prenom">Votre prénom :</label>
|
||||
<input type="text" name="prenom"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="mail">Adresse mail :</label>
|
||||
<input type="email" name="mail" required="required"/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="objet">Objet :</label>
|
||||
<input type="text" name="objet" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="message">Votre message :</label>
|
||||
<textarea name="message"></textarea>
|
||||
</div>
|
||||
<input type="submit" />
|
||||
</form>
|
||||
<script class="contact-form-config" form-id="contact-mailer-form" notify-theme="plain" src="../client/index.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,37 +0,0 @@
|
||||
worker_processes auto;
|
||||
include /etc/nginx/modules-enabled/*.conf;
|
||||
|
||||
events {
|
||||
worker_connections 768;
|
||||
# multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
error_log stderr;
|
||||
access_log /dev/stdout;
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
types_hash_max_size 2048;
|
||||
types_hash_bucket_size 128;
|
||||
gzip on;
|
||||
|
||||
server {
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, OPTIONS';
|
||||
listen 8080;
|
||||
location / {
|
||||
root /usr/app/;
|
||||
index index.html;
|
||||
}
|
||||
location /api/ {
|
||||
include uwsgi_params;
|
||||
uwsgi_pass unix:/tmp/uwsgi/uwsgi.sock;
|
||||
#uwsgi_param PATH_INFO "$1";
|
||||
#uwsgi_param SCRIPT_NAME /;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user