Compare commits
No commits in common. "master" and "add153d8a6589b5d25eaef40b398e9def7b67fc5" have entirely different histories.
master
...
add153d8a6
@ -1,12 +1,7 @@
|
||||
FROM python:3.8.12-slim-buster
|
||||
FROM python:3.8.1
|
||||
|
||||
RUN apt-get -y update && apt-get -y upgrade && \
|
||||
apt-get install --no-install-recommends -y wait-for-it lsb-release wget gnupg2 gcc libpq-dev libc-dev
|
||||
|
||||
# install psql 13 (11 is the default with debian)
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install postgresql-client-13 && apt-get clean all
|
||||
apt-get install --no-install-recommends -y wait-for-it postgresql-client
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@ -1,12 +1,7 @@
|
||||
FROM python:3.8.12-slim-buster
|
||||
FROM python:3.8.1
|
||||
|
||||
RUN apt-get -y update && apt-get -y upgrade && \
|
||||
apt-get install --no-install-recommends -y wait-for-it lsb-release wget gnupg2 gcc libpq-dev libc-dev vim
|
||||
|
||||
# install psql 13 (11 is the default with debian)
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
RUN apt-get update && apt-get --no-install-recommends -y install postgresql-client-13 && apt-get clean all
|
||||
apt-get install --no-install-recommends -y wait-for-it postgresql-client vim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
8
Makefile
8
Makefile
@ -14,17 +14,11 @@ run-bash: | build-dev
|
||||
docker-compose -f dc.dev.yml exec -u $$(id -u $${USER}):$$(id -g $${USER}) max bash || true
|
||||
docker-compose -f dc.dev.yml down
|
||||
|
||||
run-bash-root: | build-dev
|
||||
MAX_COMMAND="sleep infinity" docker-compose -f dc.dev.yml up -d
|
||||
docker-compose -f dc.dev.yml exec max "./entrypoint-dev.sh"
|
||||
docker-compose -f dc.dev.yml exec max bash || true
|
||||
docker-compose -f dc.dev.yml down
|
||||
|
||||
stop-dev:
|
||||
docker-compose -f dc.dev.yml down
|
||||
|
||||
run-cleanup: | build-dev
|
||||
docker run -ti -v `pwd`/max:/app/max --entrypoint="" max-dev ./cleanup.sh
|
||||
docker run -ti -v `pwd`/max:/app/max max-dev ./cleanup.sh
|
||||
|
||||
# PROD
|
||||
|
||||
|
||||
@ -2,4 +2,4 @@
|
||||
|
||||
A small little program to manage emails.
|
||||
|
||||
The proxy should add X-Real-IP to get a better logging.
|
||||
The proxy should add X-RealIp to get a better logging.
|
||||
|
||||
@ -27,10 +27,7 @@ SELECT {select_fields} FROM aliases a INNER JOIN emails e ON a.source_email_id=e
|
||||
|
||||
@with_cursor
|
||||
def get_aliases(cur, user_id):
|
||||
cur.execute(
|
||||
f"{base_alias_query} WHERE a.destination_user_id=%s ORDER BY e.email ASC",
|
||||
[user_id],
|
||||
)
|
||||
cur.execute(f"{base_alias_query} WHERE a.destination_user_id=%s", [user_id])
|
||||
return [Alias(*row) for row in cur.fetchall()]
|
||||
|
||||
|
||||
@ -39,8 +36,7 @@ def get_alias_with_user_by_id(cur, alias_id):
|
||||
cur.execute(
|
||||
"""\
|
||||
SELECT a.id, a.source_email_id, ea.email, a.destination_user_id, a.enabled, a.note,
|
||||
u.id, u.email_id, eu.email, u.passwordhash, u.enabled, u.is_admin,
|
||||
u.can_use_different_alias_domain, u.note
|
||||
u.id, u.email_id, eu.email, u.passwordhash, u.enabled, u.is_admin, u.note
|
||||
FROM aliases a
|
||||
INNER JOIN emails ea ON a.source_email_id=ea.id
|
||||
INNER JOIN users u ON a.destination_user_id=u.id
|
||||
|
||||
@ -6,17 +6,15 @@ class EmailAlreadyExists(Exception):
|
||||
|
||||
class NoteLinesMixin:
|
||||
def note_lines(self):
|
||||
if not self.note.strip():
|
||||
return []
|
||||
return [line.strip() for line in self.note.strip().split("\n")]
|
||||
return [line.strip() for line in self.note.split("\n")]
|
||||
|
||||
|
||||
def create_email(cur, email):
|
||||
cur.execute("SELECT EXISTS (SELECT 1 FROM emails WHERE email=lower(%s))", [email])
|
||||
cur.execute("SELECT EXISTS (SELECT 1 FROM emails WHERE email=%s)", [email])
|
||||
email_exists = cur.fetchone()[0]
|
||||
|
||||
if email_exists:
|
||||
cur.execute("SELECT id FROM emails WHERE email=lower(%s)", [email])
|
||||
cur.execute("SELECT id FROM emails WHERE email=%s", [email])
|
||||
email_id = cur.fetchone()[0]
|
||||
|
||||
# Get user?
|
||||
@ -37,8 +35,6 @@ def create_email(cur, email):
|
||||
raise EmailAlreadyExists("Used as an user.")
|
||||
|
||||
else:
|
||||
cur.execute(
|
||||
"INSERT INTO emails (email) VALUES (lower(%s)) RETURNING id", [email]
|
||||
)
|
||||
cur.execute("INSERT INTO emails (email) VALUES (%s) RETURNING id", [email])
|
||||
email_id = cur.fetchone()[0]
|
||||
return email_id
|
||||
|
||||
@ -7,20 +7,15 @@ CREATE TABLE IF NOT EXISTS emails (
|
||||
email varchar(255) NOT NULL UNIQUE
|
||||
);
|
||||
|
||||
-- Make emails unique by lower-case comparison
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS email_unique_idx on emails (LOWER(email));
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id serial PRIMARY KEY,
|
||||
email_id integer REFERENCES emails(id) ON DELETE RESTRICT NOT NULL UNIQUE,
|
||||
passwordhash varchar(255) NOT NULL,
|
||||
enabled boolean NOT NULL DEFAULT TRUE,
|
||||
is_admin boolean NOT NULL DEFAULT FALSE,
|
||||
can_use_different_alias_domain boolean NOT NULL DEFAULT FALSE,
|
||||
note text NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
-- source=me2@finn.st destination=me@finn.st: me2 is an alias of me
|
||||
CREATE TABLE IF NOT EXISTS aliases (
|
||||
id serial PRIMARY KEY,
|
||||
source_email_id integer REFERENCES emails(id) ON DELETE RESTRICT NOT NULL,
|
||||
@ -29,3 +24,10 @@ CREATE TABLE IF NOT EXISTS aliases (
|
||||
note text,
|
||||
UNIQUE(source_email_id, destination_user_id)
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM domains) THEN
|
||||
INSERT INTO domains (name) VALUES ('finn.st');
|
||||
END IF;
|
||||
END$$;
|
||||
|
||||
@ -12,7 +12,6 @@ class User(NoteLinesMixin):
|
||||
passwordhash: str
|
||||
enabled: bool
|
||||
is_admin: bool
|
||||
can_use_different_alias_domain: bool
|
||||
note: str
|
||||
|
||||
|
||||
@ -47,13 +46,13 @@ def get_user(cur, query, param):
|
||||
|
||||
@with_cursor
|
||||
def get_users(cur):
|
||||
cur.execute(f"{base_user_query} ORDER BY e.email ASC")
|
||||
cur.execute(base_user_query)
|
||||
return [User(*row) for row in cur.fetchall()]
|
||||
|
||||
|
||||
@with_cursor
|
||||
def update_user(cur, id, **fields):
|
||||
"""only pwhash, enabled, is_admin, can_use_different_alias_domain, note"""
|
||||
"""only pwhash, enabled, is_admin, note"""
|
||||
cur.execute(
|
||||
"UPDATE users SET "
|
||||
+ ", ".join(f"{field}=%s" for field in fields.keys())
|
||||
@ -63,23 +62,13 @@ def update_user(cur, id, **fields):
|
||||
|
||||
|
||||
@with_cursor
|
||||
def create_user(
|
||||
cur, email, passwordhash, enabled, is_admin, can_use_different_alias_domain, note
|
||||
):
|
||||
def create_user(cur, email, passwordhash, enabled, is_admin, note):
|
||||
email_id = create_email(cur, email)
|
||||
cur.execute(
|
||||
"""\
|
||||
INSERT INTO users
|
||||
(email_id, passwordhash, enabled, is_admin, can_use_different_alias_domain, note)
|
||||
VALUES (%s, %s, %s, %s, %s, %s)""",
|
||||
[
|
||||
email_id,
|
||||
passwordhash,
|
||||
enabled,
|
||||
is_admin,
|
||||
can_use_different_alias_domain,
|
||||
note,
|
||||
],
|
||||
INSERT INTO users (email_id, passwordhash, enabled, is_admin, note)
|
||||
VALUES (%s, %s, %s, %s, %s)""",
|
||||
[email_id, passwordhash, enabled, is_admin, note],
|
||||
)
|
||||
|
||||
|
||||
@ -116,8 +105,8 @@ def reset_or_create_user(cur, email, hash, is_admin):
|
||||
cur.execute("INSERT INTO emails (email) VALUES (%s) RETURNING id", [email])
|
||||
email_id = cur.fetchone()[0]
|
||||
cur.execute(
|
||||
"INSERT INTO users (email_id, passwordhash, enabled, is_admin, can_use_different_alias_domain, note) VALUES (%s, %s, %s, %s, %s, %s)", # noqa
|
||||
[email_id, hash, True, is_admin, is_admin, "Created by commandline"],
|
||||
"INSERT INTO users (email_id, passwordhash, enabled, is_admin, note) VALUES (%s, %s, %s, %s, %s)", # noqa
|
||||
[email_id, hash, True, is_admin, "Created by commandline"],
|
||||
)
|
||||
user_type = "admin" if is_admin else "user"
|
||||
print(f"Created {user_type} {email}")
|
||||
|
||||
@ -22,7 +22,7 @@ class BaseMiddleware:
|
||||
|
||||
class LoggingMiddleware(BaseMiddleware):
|
||||
def __call__(self, *args, **kwargs):
|
||||
real_ip = request.headers.get("X-Real-IP", None)
|
||||
real_ip = request.headers.get("X-RealIp", None)
|
||||
if real_ip is None and self.app.debug:
|
||||
real_ip = request.remote_addr
|
||||
setattr(request, "real_remote_addr", real_ip)
|
||||
@ -58,7 +58,7 @@ class ExceptionMiddleware(BaseMiddleware):
|
||||
def __call__(self, *args, **kwargs):
|
||||
try:
|
||||
return self.handler(*args, **kwargs)
|
||||
except (NotFound, PermissionDenied):
|
||||
except (NotFound, PermissionDenied) as e:
|
||||
notFoundView = NotFoundView(**kwargs)
|
||||
return notFoundView.render(), 404
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from max.permissions import AllowAnyAccess
|
||||
from max.translations import t
|
||||
from max.views import BaseTemplateGetView
|
||||
from max.translations import t
|
||||
|
||||
|
||||
class NotFoundView(AllowAnyAccess, BaseTemplateGetView):
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"test.test": "LOL",
|
||||
"Hi": "Moin",
|
||||
"You are not logged in": "Du bist nicht angemeldet",
|
||||
"Not found": "Nicht gefunden",
|
||||
@ -40,9 +41,10 @@
|
||||
"Edit note for alias {alias}": "Notiz für Alias {alias} bearbeiten",
|
||||
"Email or password was wrong!": "Email oder Password ist falsch!",
|
||||
"This account is not enabled!": "Dieser Account ist nicht aktiviert!",
|
||||
"Login was sucessfull!": "Erfolgreich angemeldet!",
|
||||
"Login was sucessfull!": "Erfolgreich anbemeldet!",
|
||||
"Logout was sucessfull!": "Erfolgreich abgemeldet!",
|
||||
"The email is not valid": "Die Email ist nicht valide",
|
||||
"The domain is invalid. Must be one of: {domains}": "Die Domain ist nacht valide. Dies sind die erlaubten Domains: {domains}",
|
||||
"Email must be given": "Die Email muss gegeben sein",
|
||||
"Enabled is not set": "Aktiviert ist nicht gesetzt",
|
||||
"Note must be a string": "Die Notiz muss eine Zeichenkette sein",
|
||||
@ -62,8 +64,5 @@
|
||||
"Email already exists.": "Email existiert bereits.",
|
||||
"Email already exists: {msg}": "Email existiert bereits: {msg}",
|
||||
"Used as an user.": "Wird für einen Nutzer verwendet",
|
||||
"Used as an alias by {alias}.": "Word für den Alias {alias} verwendet",
|
||||
"Invalid domain": "Invalide Domain",
|
||||
"Can use different alias domains": "Kann unterschiedliche Aliasdomains benutzen",
|
||||
"Can use different alias domains is not set": "Kann unterschiedliche Aliasdomains benutzen ist nicht gesetzt"
|
||||
"Used as an alias by {alias}.": "Word für den Alias {alias} verwendet"
|
||||
}
|
||||
@ -15,16 +15,12 @@ from .user.detail import UserDetail
|
||||
from .user.edit_note import UserEditNote
|
||||
from .user.list import UserList
|
||||
from .user.toggle_admin import UserToggleAdmin
|
||||
from .user.toggle_can_use_different_alias_domain import (
|
||||
UserToggleCanUseDifferentAliasDomain,
|
||||
)
|
||||
from .user.toggle_enabled import UserToggleEnabled
|
||||
|
||||
|
||||
def init_routes(app):
|
||||
app.add_url_rule("/", "own-user-detail", UserDetail.as_view())
|
||||
app.add_url_rule("/list", "user-list", UserList.as_view())
|
||||
|
||||
app.add_url_rule("/user/<int:user_id>", "user-detail", UserDetail.as_view())
|
||||
app.add_url_rule("/user/create", "user-create", UserCreate.as_view())
|
||||
app.add_url_rule(
|
||||
@ -45,16 +41,10 @@ def init_routes(app):
|
||||
"user-toggle-admin",
|
||||
UserToggleAdmin.as_view(),
|
||||
)
|
||||
app.add_url_rule(
|
||||
"/user/<int:user_id>/ser-toggle-can-use-different-alias-domain",
|
||||
"user-toggle-can-use-different-alias-domain",
|
||||
UserToggleCanUseDifferentAliasDomain.as_view(),
|
||||
)
|
||||
app.add_url_rule("/user/<int:user_id>/delete", "user-delete", UserDelete.as_view())
|
||||
app.add_url_rule(
|
||||
"/user/<int:user_id>/aliases/create", "alias-create", AliasCreate.as_view()
|
||||
)
|
||||
|
||||
app.add_url_rule("/alias/<int:alias_id>", "alias-detail", AliasDetail.as_view())
|
||||
app.add_url_rule(
|
||||
"/alias/<int:alias_id>/delete", "alias-delete", AliasDelete.as_view()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from flask import flash
|
||||
|
||||
from max.db import EmailAlreadyExists, create_alias, get_domains
|
||||
from max.db import EmailAlreadyExists, create_alias
|
||||
from max.permissions import AllowAdminOrSelf
|
||||
from max.translations import t
|
||||
|
||||
@ -26,21 +26,7 @@ class AliasCreate(
|
||||
else:
|
||||
self.email = email
|
||||
|
||||
if self.user.can_use_different_alias_domain:
|
||||
domain = self.request.form.get("domain")
|
||||
if not isinstance(domain, str) or not domain:
|
||||
flash(t("Domain must be given"), category="error")
|
||||
error = True
|
||||
elif domain not in get_domains():
|
||||
flash(t("Invalid domain"), category="error")
|
||||
error = True
|
||||
else:
|
||||
self.domain = domain
|
||||
else:
|
||||
self.domain = self.get_user_domain()
|
||||
|
||||
full_email = f"{self.email}@{self.domain}"
|
||||
if not self.is_email_valid(full_email):
|
||||
if not self.is_email_valid(email):
|
||||
error = True
|
||||
|
||||
enabled = self.request.form.get("enabled")
|
||||
@ -63,7 +49,7 @@ class AliasCreate(
|
||||
return # force re-rendering
|
||||
|
||||
try:
|
||||
create_alias(full_email, self.user, self.enabled, self.note)
|
||||
create_alias(self.email, self.user, self.enabled, self.note)
|
||||
except EmailAlreadyExists as e:
|
||||
if self.auth_user.is_admin:
|
||||
flash(
|
||||
@ -75,24 +61,14 @@ class AliasCreate(
|
||||
return
|
||||
|
||||
flash(
|
||||
t("Creation of alias {email} was successful!", email=full_email),
|
||||
t("Creation of alias {email} was successful!", email=email),
|
||||
category="success",
|
||||
)
|
||||
return self.redirect()
|
||||
|
||||
def get_context(self):
|
||||
context = super().get_context()
|
||||
for key in ("email", "domain", "enabled", "note"):
|
||||
for key in ("email", "enabled", "note"):
|
||||
if hasattr(self, key):
|
||||
context[key] = getattr(self, key)
|
||||
context[
|
||||
"can_use_different_alias_domain"
|
||||
] = self.user.can_use_different_alias_domain
|
||||
if self.user.can_use_different_alias_domain:
|
||||
context["domains"] = get_domains()
|
||||
else:
|
||||
context["domain"] = self.get_user_domain()
|
||||
return context
|
||||
|
||||
def get_user_domain(self):
|
||||
return self.user.email.split("@")[1]
|
||||
|
||||
@ -6,14 +6,26 @@ from max.db import get_domains
|
||||
from max.translations import t
|
||||
|
||||
|
||||
EMAIL_REGEX = re.compile(r"^[a-zA-Z0-9+_-]+@(?P<domain>[a-zA-Z0-9-]+\.[a-z]{2,61})$")
|
||||
|
||||
|
||||
class CheckEmailMixin:
|
||||
def is_email_valid(self, email: str):
|
||||
"""Checks for the email being valid and that it has a valid domain."""
|
||||
regex = re.compile(f"^[a-zA-Z0-9+_-]+@({ '|'.join(get_domains()) })$")
|
||||
print(regex, email)
|
||||
match = regex.match(email)
|
||||
match = EMAIL_REGEX.match(email)
|
||||
if not match:
|
||||
flash(t("The email is not valid"), category="error")
|
||||
return False
|
||||
|
||||
domains = get_domains()
|
||||
if match.group("domain") not in domains:
|
||||
flash(
|
||||
t(
|
||||
"The domain is invalid. Must be one of: {domains}",
|
||||
domains=",".join(domains),
|
||||
),
|
||||
category="error",
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from flask import flash
|
||||
|
||||
from max.auth import hash_password
|
||||
from max.db import EmailAlreadyExists, create_user, get_domains
|
||||
from max.db import EmailAlreadyExists, create_user
|
||||
from max.permissions import AllowAdmin
|
||||
from max.translations import t
|
||||
from max.views import BaseTemplateGetView
|
||||
@ -22,18 +22,7 @@ class UserCreate(AllowAdmin, CheckEmailMixin, BackToUsersMixin, BaseTemplateGetV
|
||||
else:
|
||||
self.email = email
|
||||
|
||||
domain = self.request.form.get("domain")
|
||||
if not isinstance(domain, str) or not domain:
|
||||
flash(t("Domain must be given"), category="error")
|
||||
error = True
|
||||
elif domain not in get_domains():
|
||||
flash(t("Invalid domain"), category="error")
|
||||
error = True
|
||||
else:
|
||||
self.domain = domain
|
||||
|
||||
full_email = f"{self.email}@{self.domain}"
|
||||
if not self.is_email_valid(full_email):
|
||||
if not self.is_email_valid(email):
|
||||
error = True
|
||||
|
||||
pw1 = self.request.form.get("password1")
|
||||
@ -68,17 +57,6 @@ class UserCreate(AllowAdmin, CheckEmailMixin, BackToUsersMixin, BaseTemplateGetV
|
||||
flash(t("Admin is not set"), category="error")
|
||||
error = True
|
||||
|
||||
can_use_different_alias_domain = self.request.form.get(
|
||||
"can_use_different_alias_domain"
|
||||
)
|
||||
if can_use_different_alias_domain == "on":
|
||||
self.can_use_different_alias_domain = True
|
||||
elif can_use_different_alias_domain is None:
|
||||
self.can_use_different_alias_domain = False
|
||||
else:
|
||||
flash(t("Can use different alias domains is not set"), category="error")
|
||||
error = True
|
||||
|
||||
note = self.request.form.get("note", "")
|
||||
if not isinstance(note, str):
|
||||
flash(t("Note must be a string"), category="error")
|
||||
@ -92,12 +70,7 @@ class UserCreate(AllowAdmin, CheckEmailMixin, BackToUsersMixin, BaseTemplateGetV
|
||||
passwordhash = hash_password(self.password)
|
||||
try:
|
||||
create_user(
|
||||
full_email,
|
||||
passwordhash,
|
||||
self.enabled,
|
||||
self.is_admin,
|
||||
self.can_use_different_alias_domain,
|
||||
self.note,
|
||||
self.email, passwordhash, self.enabled, self.is_admin, self.note
|
||||
)
|
||||
except EmailAlreadyExists as e:
|
||||
if self.auth_user.is_admin:
|
||||
@ -109,26 +82,14 @@ class UserCreate(AllowAdmin, CheckEmailMixin, BackToUsersMixin, BaseTemplateGetV
|
||||
flash(t("Email already exists."))
|
||||
return
|
||||
|
||||
flash(
|
||||
t("Creation of {email} was successful!", email=full_email),
|
||||
category="success",
|
||||
)
|
||||
flash(t("Creation of {email} was successful!", email=email), category="success")
|
||||
return self.redirect()
|
||||
|
||||
def get_context(self):
|
||||
context = super().get_context()
|
||||
for key in (
|
||||
"email",
|
||||
"domain",
|
||||
"password",
|
||||
"enabled",
|
||||
"is_admin",
|
||||
"can_use_different_alias_domain",
|
||||
"note",
|
||||
):
|
||||
for key in ("email", "password", "enabled", "is_admin", "note"):
|
||||
if hasattr(self, key):
|
||||
context[key] = getattr(self, key)
|
||||
context["domains"] = get_domains()
|
||||
return context
|
||||
|
||||
def get_webpagetitle(self):
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
from max.db import update_user
|
||||
from max.permissions import AllowAdmin
|
||||
from max.views import BaseView
|
||||
|
||||
from ..base_user_views import BackToUsersMixin, FetchUserMixin
|
||||
|
||||
|
||||
class UserToggleCanUseDifferentAliasDomain(
|
||||
AllowAdmin, FetchUserMixin, BackToUsersMixin, BaseView
|
||||
):
|
||||
def post(self):
|
||||
update_user(
|
||||
self.user.id,
|
||||
can_use_different_alias_domain=(
|
||||
not self.user.can_use_different_alias_domain
|
||||
),
|
||||
)
|
||||
_, back_url = self.get_back_text_and_url()
|
||||
return self.redirect(back_url)
|
||||
@ -144,21 +144,14 @@ form {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.detail b {
|
||||
width: 4.5em;
|
||||
display: inline-block;
|
||||
}
|
||||
.detail .note a {
|
||||
margin-right: 1em;
|
||||
margin-left: 1em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.detail .note b {
|
||||
margin-right: 1em;
|
||||
margin-top: 0.5em;
|
||||
vertical-align: top;
|
||||
}
|
||||
.detail .note div {
|
||||
display: inline-block;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
.detail form {
|
||||
margin: 0;
|
||||
@ -181,9 +174,6 @@ form {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
.connection-properties .indent {
|
||||
margin-left: 1.5em;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
padding: 8px;
|
||||
@ -193,10 +183,6 @@ form {
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.domain-selector {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
body {
|
||||
font-size: 13px;
|
||||
|
||||
@ -7,18 +7,7 @@
|
||||
<form id="form" class="pure-form pure-form-aligned" action="{{ url_for('alias-create', user_id=user.id) }}" method="post">
|
||||
<div class="pure-control-group">
|
||||
<label for="email">{{ t("Email") }}</label>
|
||||
<input id="email" type="text" name="email" {% if email is defined %}value="{{ email }}"{% endif %} placeholder="{{ t("Email") }}">
|
||||
|
||||
<div class="domain-selector">
|
||||
{% if can_use_different_alias_domain %}
|
||||
@
|
||||
<select id="domain" name="domain">
|
||||
{% for d in domains %}<option{% if d == domain %} selected{% endif %}>{{ d }}</option>{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
@{{ domain }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<input id="email" type="email" name="email" {% if email is defined %}value="{{ email }}"{% endif %} placeholder="{{ t("Email") }}">
|
||||
</div>
|
||||
|
||||
<div class="pure-controls checkbox">
|
||||
|
||||
@ -8,15 +8,13 @@
|
||||
<div>
|
||||
<b>{{ t("Enabled") }}:</b>
|
||||
{{ macros.checkbox(alias, "enabled", url_for('alias-toggle-enabled', alias_id=alias.id, return='detail')) }}
|
||||
</div>
|
||||
<div class="note">
|
||||
</div><div class="note">
|
||||
<b>{{ t("Note") }}:</b><div>{{ macros.format_note(alias) }}</div>
|
||||
<a href="{{ url_for('alias-edit-note', alias_id=alias.id, return='detail') }}" class="button-small pure-button optional">
|
||||
<i class="fa fa-pen"></i>
|
||||
{{ t("Edit") }}
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
</div><div>
|
||||
<a href="{{ url_for('alias-delete', alias_id=alias.id, return='detail') }}" class="button-small button-error pure-button">
|
||||
<i class="fa fa-trash"></i>
|
||||
{{ t("Delete") }}
|
||||
|
||||
@ -7,16 +7,9 @@
|
||||
<form id="form" class="pure-form pure-form-aligned" action="{{ url_for('user-create') }}" method="post">
|
||||
<div class="pure-control-group">
|
||||
<label for="email">{{ t("Email") }}</label>
|
||||
<input id="email" type="text" name="email" {% if email is defined %}value="{{ email }}"{% endif %} placeholder="{{ t("Email") }}">
|
||||
|
||||
<div class="domain-selector">
|
||||
@
|
||||
<select id="domain" name="domain">
|
||||
{% for d in domains %}<option{% if d == domain %} selected{% endif %}>{{ d }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<input id="email" type="email" name="email" {% if email is defined %}value="{{ email }}"{% endif %} placeholder="{{ t("Email") }}">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="password1">{{ t("Password") }}</label>
|
||||
<input id="password1" type="password" name="password1" {% if password is defined %}value="{{ password }}"{% endif %} placeholder="{{ t("Password") }}">
|
||||
@ -41,13 +34,6 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pure-controls checkbox">
|
||||
<label for="can_use_different_alias_domain">
|
||||
<input id="can_use_different_alias_domain" type="checkbox" name="can_use_different_alias_domain" {% if can_use_different_alias_domain %}checked{% endif %}>
|
||||
{{ t("Can use different alias domains") }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="pure-control-group">
|
||||
<label for="note">{{ t("Note") }}</label>
|
||||
<textarea id="note" name="note" form="form">{% if note is defined %}{{ note }}{% endif %}</textarea>
|
||||
|
||||
@ -24,10 +24,6 @@
|
||||
<b>{{ t("Admin") }}:</b>
|
||||
{{ macros.checkbox(user, "is_admin", url_for('user-toggle-admin', user_id=user.id, return='detail')) }}
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ t("Can use different alias domains") }}:</b>
|
||||
{{ macros.checkbox(user, "can_use_different_alias_domain", url_for('user-toggle-can-use-different-alias-domain', user_id=user.id, return='detail')) }}
|
||||
</div>
|
||||
<div class="note">
|
||||
<b>{{ t("Note") }}:</b><div>{{ macros.format_note(user) }}</div>
|
||||
<a href="{{ url_for('user-edit-note', user_id=user.id, return='detail') }}" class="button-small pure-button optional">
|
||||
@ -57,14 +53,10 @@
|
||||
|
||||
<details class="connection-properties">
|
||||
<summary>{{ t("Email connection properties") }}</summary>
|
||||
<p>IMAP: finn.st, Port 993</p>
|
||||
<p class="indent">Username: {{ user.email }}</p>
|
||||
<p class="indent">Connection security: SSL/TLS</p>
|
||||
<p class="indent">Authentication method: password</p>
|
||||
<p>SMTP: finn.st, Port 587</p>
|
||||
<p class="indent">Username: {{ user.email }}</p>
|
||||
<p class="indent">Connection security: STARTTLS</p>
|
||||
<p class="indent">Authentication method: password</p>
|
||||
<p>IMAP: mail.finn.st, Port: 993</p>
|
||||
<p>username: {{ user.email }}</p>
|
||||
<p>Connection security: SSL/TLS</p>
|
||||
<p>Authentication method: password</p>
|
||||
</details>
|
||||
|
||||
{% if aliases|length > 0 %}
|
||||
|
||||
@ -5,7 +5,6 @@ set -e
|
||||
source db-setup.sh
|
||||
|
||||
flask add-domain finn.st
|
||||
flask add-domain stutzenste.in
|
||||
flask reset-admin admin@finn.st admin
|
||||
|
||||
exec "$@"
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
PGPASSWORD="$PGSQL_PASSWORD" psql -h "$PGSQL_HOST" -p "$PGSQL_PORT" -U "$PGSQL_USER" -d "$PGSQL_NAME"
|
||||
Loading…
x
Reference in New Issue
Block a user