Merge from upstream branch 'staging'
# Conflicts: # data/web/inc/vars.inc.php
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM clamav/clamav:0.105.0_base
|
||||
FROM clamav/clamav:0.105.1_base
|
||||
|
||||
LABEL maintainer "André Peters <andre.peters@servercow.de>"
|
||||
|
||||
@@ -8,8 +8,14 @@ RUN apk upgrade --no-cache \
|
||||
bind-tools \
|
||||
bash
|
||||
|
||||
COPY clamd.sh ./
|
||||
# init
|
||||
COPY clamd.sh /clamd.sh
|
||||
RUN chmod +x /sbin/tini
|
||||
|
||||
# healthcheck
|
||||
COPY healthcheck.sh /healthcheck.sh
|
||||
RUN chmod +x /healthcheck.sh
|
||||
HEALTHCHECK --start-period=6m CMD "/healthcheck.sh"
|
||||
|
||||
ENTRYPOINT []
|
||||
CMD ["/sbin/tini", "-g", "--", "/clamd.sh"]
|
9
data/Dockerfiles/clamd/healthcheck.sh
Executable file
9
data/Dockerfiles/clamd/healthcheck.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ "${SKIP_CLAMD}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
|
||||
echo "SKIP_CLAMD=y, skipping ClamAV..."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# run clamd healthcheck
|
||||
/usr/local/bin/clamdcheck.sh
|
@@ -2,7 +2,7 @@ FROM debian:bullseye-slim
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG DOVECOT=2.3.18
|
||||
ARG DOVECOT=2.3.19.1
|
||||
ENV LC_ALL C
|
||||
ENV GOSU_VERSION 1.14
|
||||
|
||||
|
@@ -307,6 +307,7 @@ namespace {
|
||||
}
|
||||
EOF
|
||||
|
||||
|
||||
cat <<EOF > /etc/dovecot/sogo_trusted_ip.conf
|
||||
# Autogenerated by mailcow
|
||||
remote ${IPV4_NETWORK}.248 {
|
||||
@@ -349,6 +350,14 @@ sievec /var/vmail/sieve/global_sieve_after.sieve
|
||||
sievec /usr/lib/dovecot/sieve/report-spam.sieve
|
||||
sievec /usr/lib/dovecot/sieve/report-ham.sieve
|
||||
|
||||
for file in /var/vmail/*/*/sieve/*.sieve ; do
|
||||
if [[ "$file" == "/var/vmail/*/*/sieve/*.sieve" ]]; then
|
||||
continue
|
||||
fi
|
||||
sievec "$file" "$(dirname "$file")/../.dovecot.svbin"
|
||||
chown vmail:vmail "$(dirname "$file")/../.dovecot.svbin"
|
||||
done
|
||||
|
||||
# Fix permissions
|
||||
chown root:root /etc/dovecot/sql/*.conf
|
||||
chown root:dovecot /etc/dovecot/sql/dovecot-dict-sql-sieve* /etc/dovecot/sql/dovecot-dict-sql-quota* /etc/dovecot/lua/passwd-verify.lua
|
||||
|
@@ -50,7 +50,7 @@ try:
|
||||
def query_mysql(query, headers = True, update = False):
|
||||
while True:
|
||||
try:
|
||||
cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user=os.environ.get('DBUSER'), passwd=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8")
|
||||
cnx = mysql.connector.connect(unix_socket = '/var/run/mysqld/mysqld.sock', user=os.environ.get('DBUSER'), passwd=os.environ.get('DBPASS'), database=os.environ.get('DBNAME'), charset="utf8mb4", collation="utf8mb4_general_ci")
|
||||
except Exception as ex:
|
||||
print('%s - trying again...' % (ex))
|
||||
time.sleep(3)
|
||||
@@ -166,4 +166,4 @@ try:
|
||||
notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category'])
|
||||
|
||||
finally:
|
||||
os.unlink(pidfile)
|
||||
os.unlink(pidfile)
|
||||
|
@@ -323,7 +323,19 @@ hosts = unix:/var/run/mysqld/mysqld.sock
|
||||
dbname = ${DBNAME}
|
||||
# First select queries domain and alias_domain to determine if domains are active.
|
||||
query = SELECT goto FROM alias
|
||||
WHERE address='%s'
|
||||
WHERE id IN (
|
||||
SELECT COALESCE (
|
||||
(
|
||||
SELECT id FROM alias
|
||||
WHERE address='%s'
|
||||
AND (active='1' OR active='2')
|
||||
), (
|
||||
SELECT id FROM alias
|
||||
WHERE address='@%d'
|
||||
AND (active='1' OR active='2')
|
||||
)
|
||||
)
|
||||
)
|
||||
AND active='1'
|
||||
AND (domain IN
|
||||
(SELECT domain FROM domain
|
||||
@@ -354,7 +366,7 @@ query = SELECT goto FROM alias
|
||||
WHERE alias_domain.alias_domain = '%d'
|
||||
AND mailbox.username = CONCAT('%u','@',alias_domain.target_domain)
|
||||
AND (mailbox.active = '1' OR mailbox.active ='2')
|
||||
AND alias_domain.active='1'
|
||||
AND alias_domain.active='1';
|
||||
EOF
|
||||
|
||||
# MX based routing
|
||||
|
@@ -2,7 +2,7 @@ FROM debian:bullseye-slim
|
||||
LABEL maintainer "Andre Peters <andre.peters@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG SOGO_DEBIAN_REPOSITORY=http://packages.inverse.ca/SOGo/nightly/5/debian/
|
||||
ARG SOGO_DEBIAN_REPOSITORY=http://packages.sogo.nu/nightly/5/debian/
|
||||
ENV LC_ALL C
|
||||
ENV GOSU_VERSION 1.14
|
||||
|
||||
@@ -30,7 +30,7 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
|
||||
&& gosu nobody true \
|
||||
&& mkdir /usr/share/doc/sogo \
|
||||
&& touch /usr/share/doc/sogo/empty.sh \
|
||||
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-key 0x810273C4 \
|
||||
&& apt-key adv --keyserver keys.openpgp.org --recv-key 74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 \
|
||||
&& echo "deb ${SOGO_DEBIAN_REPOSITORY} bullseye bullseye" > /etc/apt/sources.list.d/sogo.list \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||
sogo \
|
||||
@@ -52,4 +52,4 @@ RUN chmod +x /bootstrap-sogo.sh \
|
||||
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
|
||||
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
||||
CMD exec /usr/bin/supervisord -c /etc/supervisor/supervisord.conf
|
@@ -142,6 +142,10 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
||||
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_acl</string>
|
||||
<key>SOGoIMAPServer</key>
|
||||
<string>imap://${IPV4_NETWORK}.250:143/?TLS=YES&tlsVerifyMode=none</string>
|
||||
<key>SOGoSieveServer</key>
|
||||
<string>sieve://${IPV4_NETWORK}.250:4190/?TLS=YES&tlsVerifyMode=none</string>
|
||||
<key>SOGoSMTPServer</key>
|
||||
<string>smtp://${IPV4_NETWORK}.253:588/?TLS=YES&tlsVerifyMode=none</string>
|
||||
<key>SOGoTrustProxyAuthentication</key>
|
||||
<string>YES</string>
|
||||
<key>SOGoEncryptionKey</key>
|
||||
|
@@ -18,6 +18,9 @@ symbols {
|
||||
"ENCRYPTED_CHAT" {
|
||||
score = -20.0;
|
||||
}
|
||||
"SOGO_CONTACT" {
|
||||
score = -99.0;
|
||||
}
|
||||
}
|
||||
|
||||
group "MX" {
|
||||
|
@@ -32,8 +32,6 @@
|
||||
// );
|
||||
|
||||
// self-signed is not trusted anymore
|
||||
SOGoSieveServer = "sieve://dovecot:4190/?TLS=YES&tlsVerifyMode=none";
|
||||
SOGoSMTPServer = "smtp://postfix:588/?TLS=YES&tlsVerifyMode=none";
|
||||
WOPort = "0.0.0.0:20000";
|
||||
SOGoMemcachedHost = "memcached";
|
||||
|
||||
|
16
data/web/api/index.css
Normal file
16
data/web/api/index.css
Normal file
@@ -0,0 +1,16 @@
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
@@ -5,56 +5,15 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||
<link rel="stylesheet" type="text/css" href="index.css" />
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
urls: [{url: "/api/openapi.yaml", name: "mailcow API"}],
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -13,7 +13,7 @@
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
qp = window.location.hash.substring(1).replace('?', '&');
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
@@ -38,7 +38,7 @@
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -67,9 +67,13 @@
|
||||
window.close();
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
if (document.readyState !== 'loading') {
|
||||
run();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -518,21 +518,23 @@ paths:
|
||||
- domain.tld
|
||||
type: success
|
||||
schema:
|
||||
properties:
|
||||
log:
|
||||
description: contains request object
|
||||
items: {}
|
||||
type: array
|
||||
msg:
|
||||
items: {}
|
||||
type: array
|
||||
type:
|
||||
enum:
|
||||
- success
|
||||
- danger
|
||||
- error
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
log:
|
||||
description: contains request object
|
||||
items: {}
|
||||
type: array
|
||||
msg:
|
||||
items: {}
|
||||
type: array
|
||||
type:
|
||||
enum:
|
||||
- success
|
||||
- danger
|
||||
- error
|
||||
type: string
|
||||
description: OK
|
||||
headers: {}
|
||||
tags:
|
||||
@@ -579,6 +581,11 @@ paths:
|
||||
domain:
|
||||
description: Fully qualified domain name
|
||||
type: string
|
||||
gal:
|
||||
description: >-
|
||||
is domain global address list active or not, it enables
|
||||
shared contacts accross domain in SOGo webmail
|
||||
type: boolean
|
||||
mailboxes:
|
||||
description: limit count of mailboxes associated with this domain
|
||||
type: number
|
||||
@@ -596,6 +603,9 @@ paths:
|
||||
if not, them you have to create "dummy" mailbox for each
|
||||
address to relay
|
||||
type: boolean
|
||||
relay_unknown_only:
|
||||
description: Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.
|
||||
type: boolean
|
||||
rl_frame:
|
||||
enum:
|
||||
- s
|
||||
@@ -606,6 +616,11 @@ paths:
|
||||
rl_value:
|
||||
description: rate limit value
|
||||
type: number
|
||||
tags:
|
||||
description: tags for this Domain
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
type: object
|
||||
summary: Create domain
|
||||
/api/v1/add/domain-admin:
|
||||
@@ -1952,21 +1967,23 @@ paths:
|
||||
- domain2.tld
|
||||
type: success
|
||||
schema:
|
||||
properties:
|
||||
log:
|
||||
description: contains request object
|
||||
items: {}
|
||||
type: array
|
||||
msg:
|
||||
items: {}
|
||||
type: array
|
||||
type:
|
||||
enum:
|
||||
- success
|
||||
- danger
|
||||
- error
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
log:
|
||||
description: contains request object
|
||||
items: {}
|
||||
type: array
|
||||
msg:
|
||||
items: {}
|
||||
type: array
|
||||
type:
|
||||
enum:
|
||||
- success
|
||||
- danger
|
||||
- error
|
||||
type: string
|
||||
description: OK
|
||||
headers: {}
|
||||
tags:
|
||||
@@ -1977,14 +1994,15 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
example:
|
||||
- domain.tld
|
||||
- domain2.tld
|
||||
properties:
|
||||
items:
|
||||
description: contains list of domains you want to delete
|
||||
type: object
|
||||
type: object
|
||||
items:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
summary: Delete domain
|
||||
/api/v1/delete/domain-admin:
|
||||
post:
|
||||
@@ -2972,23 +2990,25 @@ paths:
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"200":
|
||||
content:
|
||||
"*/*":
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
log:
|
||||
description: contains request object
|
||||
items: {}
|
||||
type: array
|
||||
msg:
|
||||
items: {}
|
||||
type: array
|
||||
type:
|
||||
enum:
|
||||
- success
|
||||
- danger
|
||||
- error
|
||||
type: string
|
||||
type: object
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
log:
|
||||
type: array
|
||||
description: contains request object
|
||||
items: {}
|
||||
msg:
|
||||
type: array
|
||||
items: {}
|
||||
type:
|
||||
enum:
|
||||
- success
|
||||
- danger
|
||||
- error
|
||||
type: string
|
||||
description: OK
|
||||
headers: {}
|
||||
tags:
|
||||
@@ -3056,13 +3076,33 @@ paths:
|
||||
if not, them you have to create "dummy" mailbox for each
|
||||
address to relay
|
||||
type: boolean
|
||||
relay_unknown_only:
|
||||
description: Relay non-existing mailboxes only. Existing mailboxes will be delivered locally.
|
||||
type: boolean
|
||||
relayhost:
|
||||
description: id of relayhost
|
||||
type: number
|
||||
rl_frame:
|
||||
enum:
|
||||
- s
|
||||
- m
|
||||
- h
|
||||
- d
|
||||
type: string
|
||||
rl_value:
|
||||
description: rate limit value
|
||||
type: number
|
||||
tags:
|
||||
description: tags for this Domain
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
type: object
|
||||
items:
|
||||
description: contains list of domain names you want update
|
||||
type: object
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
type: object
|
||||
summary: Update domain
|
||||
/api/v1/edit/fail2ban:
|
||||
@@ -3953,6 +3993,8 @@ paths:
|
||||
in: query
|
||||
name: tags
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- description: e.g. api-key-string
|
||||
example: api-key-string
|
||||
in: header
|
||||
@@ -4512,6 +4554,8 @@ paths:
|
||||
in: query
|
||||
name: tags
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- description: e.g. api-key-string
|
||||
example: api-key-string
|
||||
in: header
|
||||
|
19
data/web/api/swagger-initializer.js
Normal file
19
data/web/api/swagger-initializer.js
Normal file
@@ -0,0 +1,19 @@
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
urls: [{url: "/api/openapi.yaml", name: "mailcow API"}],
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui;
|
||||
};
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -260,6 +260,18 @@ code {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.list-group-item.webauthn-authenticator-selection,
|
||||
.list-group-item.totp-authenticator-selection,
|
||||
.list-group-item.yubi_otp-authenticator-selection {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
.pending-tfa-collapse {
|
||||
padding: 10px;
|
||||
background: #fbfbfb;
|
||||
border: 1px solid #ededed;
|
||||
min-height: 110px;
|
||||
}
|
||||
|
||||
.tag-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -296,4 +308,3 @@ code {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
|
@@ -2,5 +2,5 @@
|
||||
session_start();
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_method']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
?>
|
||||
|
@@ -23,14 +23,43 @@ if (is_array($alertbox_log_parser)) {
|
||||
unset($_SESSION['return']);
|
||||
}
|
||||
|
||||
// map tfa details for twig
|
||||
$pending_tfa_authmechs = [];
|
||||
foreach($_SESSION['pending_tfa_methods'] as $authdata){
|
||||
$pending_tfa_authmechs[$authdata['authmech']] = false;
|
||||
}
|
||||
if (isset($pending_tfa_authmechs['webauthn'])) {
|
||||
$pending_tfa_authmechs['webauthn'] = true;
|
||||
}
|
||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||
&& isset($pending_tfa_authmechs['yubi_otp'])) {
|
||||
$pending_tfa_authmechs['yubi_otp'] = true;
|
||||
}
|
||||
if (!isset($pending_tfa_authmechs['webauthn'])
|
||||
&& !isset($pending_tfa_authmechs['yubi_otp'])
|
||||
&& isset($pending_tfa_authmechs['totp'])) {
|
||||
$pending_tfa_authmechs['totp'] = true;
|
||||
}
|
||||
if (isset($pending_tfa_authmechs['u2f'])) {
|
||||
$pending_tfa_authmechs['u2f'] = true;
|
||||
}
|
||||
|
||||
// globals
|
||||
$globalVariables = [
|
||||
'mailcow_info' => array(
|
||||
'version_tag' => $GLOBALS['MAILCOW_GIT_VERSION'],
|
||||
'git_project_url' => $GLOBALS['MAILCOW_GIT_URL']
|
||||
'last_version_tag' => $GLOBALS['MAILCOW_LAST_GIT_VERSION'],
|
||||
'git_owner' => $GLOBALS['MAILCOW_GIT_OWNER'],
|
||||
'git_repo' => $GLOBALS['MAILCOW_GIT_REPO'],
|
||||
'git_project_url' => $GLOBALS['MAILCOW_GIT_URL'],
|
||||
'git_commit' => $GLOBALS['MAILCOW_GIT_COMMIT'],
|
||||
'git_commit_date' => $GLOBALS['MAILCOW_GIT_COMMIT_DATE'],
|
||||
'mailcow_branch' => $GLOBALS['MAILCOW_BRANCH'],
|
||||
'updated_at' => $GLOBALS['MAILCOW_UPDATEDAT']
|
||||
),
|
||||
'js_path' => '/cache/'.basename($JSPath),
|
||||
'pending_tfa_method' => @$_SESSION['pending_tfa_method'],
|
||||
'pending_tfa_methods' => @$_SESSION['pending_tfa_methods'],
|
||||
'pending_tfa_authmechs' => $pending_tfa_authmechs,
|
||||
'pending_mailcow_cc_username' => @$_SESSION['pending_mailcow_cc_username'],
|
||||
'lang_footer' => json_encode($lang['footer']),
|
||||
'lang_acl' => json_encode($lang['acl']),
|
||||
|
@@ -830,11 +830,15 @@ function check_login($user, $pass, $app_passwd_data = false) {
|
||||
$stmt->execute(array(':user' => $user));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $row) {
|
||||
// verify password
|
||||
if (verify_hash($row['password'], $pass)) {
|
||||
if (get_tfa($user)['name'] != "none") {
|
||||
// check for tfa authenticators
|
||||
$authenticators = get_tfa($user);
|
||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
||||
// active tfa authenticators found, set pending user login
|
||||
$_SESSION['pending_mailcow_cc_username'] = $user;
|
||||
$_SESSION['pending_mailcow_cc_role'] = "admin";
|
||||
$_SESSION['pending_tfa_method'] = get_tfa($user)['name'];
|
||||
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
||||
unset($_SESSION['ldelay']);
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'info',
|
||||
@@ -842,8 +846,7 @@ function check_login($user, $pass, $app_passwd_data = false) {
|
||||
'msg' => 'awaiting_tfa_confirmation'
|
||||
);
|
||||
return "pending";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
unset($_SESSION['ldelay']);
|
||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||
@@ -866,11 +869,14 @@ function check_login($user, $pass, $app_passwd_data = false) {
|
||||
$stmt->execute(array(':user' => $user));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $row) {
|
||||
// verify password
|
||||
if (verify_hash($row['password'], $pass) !== false) {
|
||||
if (get_tfa($user)['name'] != "none") {
|
||||
// check for tfa authenticators
|
||||
$authenticators = get_tfa($user);
|
||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
|
||||
$_SESSION['pending_mailcow_cc_username'] = $user;
|
||||
$_SESSION['pending_mailcow_cc_role'] = "domainadmin";
|
||||
$_SESSION['pending_tfa_method'] = get_tfa($user)['name'];
|
||||
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
||||
unset($_SESSION['ldelay']);
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'info',
|
||||
@@ -929,15 +935,37 @@ function check_login($user, $pass, $app_passwd_data = false) {
|
||||
$stmt->execute(array(':user' => $user));
|
||||
$rows = array_merge($rows, $stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
}
|
||||
foreach ($rows as $row) {
|
||||
foreach ($rows as $row) {
|
||||
// verify password
|
||||
if (verify_hash($row['password'], $pass) !== false) {
|
||||
unset($_SESSION['ldelay']);
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $user, '*'),
|
||||
'msg' => array('logged_in_as', $user)
|
||||
);
|
||||
if ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {
|
||||
if (!array_key_exists("app_passwd_id", $row)){
|
||||
// password is not a app password
|
||||
// check for tfa authenticators
|
||||
$authenticators = get_tfa($user);
|
||||
if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0 &&
|
||||
$app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true) {
|
||||
// authenticators found, init TFA flow
|
||||
$_SESSION['pending_mailcow_cc_username'] = $user;
|
||||
$_SESSION['pending_mailcow_cc_role'] = "user";
|
||||
$_SESSION['pending_tfa_methods'] = $authenticators['additional'];
|
||||
unset($_SESSION['ldelay']);
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
'log' => array(__FUNCTION__, $user, '*'),
|
||||
'msg' => array('logged_in_as', $user)
|
||||
);
|
||||
return "pending";
|
||||
} else if (!isset($authenticators['additional']) || !is_array($authenticators['additional']) || count($authenticators['additional']) == 0) {
|
||||
// no authenticators found, login successfull
|
||||
// Reactivate TFA if it was set to "deactivate TFA for next login"
|
||||
$stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
|
||||
$stmt->execute(array(':user' => $user));
|
||||
|
||||
unset($_SESSION['ldelay']);
|
||||
return "user";
|
||||
}
|
||||
} elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {
|
||||
// password is a app password
|
||||
$service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV';
|
||||
$stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
|
||||
$stmt->execute(array(
|
||||
@@ -946,8 +974,10 @@ function check_login($user, $pass, $app_passwd_data = false) {
|
||||
':username' => $user,
|
||||
':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
|
||||
));
|
||||
|
||||
unset($_SESSION['ldelay']);
|
||||
return "user";
|
||||
}
|
||||
return "user";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1142,47 +1172,46 @@ function set_tfa($_data) {
|
||||
global $yubi;
|
||||
global $tfa;
|
||||
$_data_log = $_data;
|
||||
$access_denied = null;
|
||||
!isset($_data_log['confirm_password']) ?: $_data_log['confirm_password'] = '*';
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_data_log),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
if (!empty($num_results)) {
|
||||
if (!verify_hash($row['password'], $_data["confirm_password"])) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_data_log),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
if (!empty($num_results)) {
|
||||
if (!verify_hash($row['password'], $_data["confirm_password"])) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_data_log),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
|
||||
// check for empty user and role
|
||||
if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true;
|
||||
|
||||
// check admin confirm password
|
||||
if ($access_denied === null) {
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `admin`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row) {
|
||||
if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true;
|
||||
else $access_denied = false;
|
||||
}
|
||||
}
|
||||
|
||||
// check mailbox confirm password
|
||||
if ($access_denied === null) {
|
||||
$stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
|
||||
WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row) {
|
||||
if (!verify_hash($row['password'], $_data["confirm_password"])) $access_denied = true;
|
||||
else $access_denied = false;
|
||||
}
|
||||
}
|
||||
|
||||
// set access_denied error
|
||||
if ($access_denied){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_data_log),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($_data["tfa_method"]) {
|
||||
case "yubi_otp":
|
||||
@@ -1220,8 +1249,7 @@ function set_tfa($_data) {
|
||||
$yubico_modhex_id = substr($_data["otp_token"], 0, 12);
|
||||
$stmt = $pdo->prepare("DELETE FROM `tfa`
|
||||
WHERE `username` = :username
|
||||
AND (`authmech` != 'yubi_otp')
|
||||
OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)");
|
||||
AND (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)");
|
||||
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
|
||||
$stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES
|
||||
(:key_id, :username, 'yubi_otp', '1', :secret)");
|
||||
@@ -1265,9 +1293,6 @@ function set_tfa($_data) {
|
||||
case "webauthn":
|
||||
$key_id = (!isset($_data["key_id"])) ? 'unidentified' : $_data["key_id"];
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'webauthn'");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
|
||||
$stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`)
|
||||
VALUES (?, ?, 'webauthn', ?, ?, ?, ?, '1')");
|
||||
$stmt->execute(array(
|
||||
@@ -1439,25 +1464,27 @@ function unset_tfa_key($_data) {
|
||||
global $pdo;
|
||||
global $lang;
|
||||
$_data_log = $_data;
|
||||
$access_denied = null;
|
||||
$id = intval($_data['unset_tfa_key']);
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_data_log),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for empty user and role
|
||||
if (!isset($_SESSION['mailcow_cc_role']) || empty($username)) $access_denied = true;
|
||||
|
||||
try {
|
||||
if (!is_numeric($id)) {
|
||||
$_SESSION['return'][] = array(
|
||||
if (!is_numeric($id)) $access_denied = true;
|
||||
|
||||
// set access_denied error
|
||||
if ($access_denied){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $_data_log),
|
||||
'msg' => 'access_denied'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// check if it's last key
|
||||
$stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
|
||||
WHERE `username` = :username AND `active` = '1'");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
@@ -1470,6 +1497,8 @@ function unset_tfa_key($_data) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// delete key
|
||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id");
|
||||
$stmt->execute(array(':username' => $username, ':id' => $id));
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -1487,7 +1516,7 @@ function unset_tfa_key($_data) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function get_tfa($username = null) {
|
||||
function get_tfa($username = null, $id = null) {
|
||||
global $pdo;
|
||||
if (isset($_SESSION['mailcow_cc_username'])) {
|
||||
$username = $_SESSION['mailcow_cc_username'];
|
||||
@@ -1495,95 +1524,119 @@ function get_tfa($username = null) {
|
||||
elseif (empty($username)) {
|
||||
return false;
|
||||
}
|
||||
$stmt = $pdo->prepare("SELECT * FROM `tfa`
|
||||
WHERE `username` = :username AND `active` = '1'");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (isset($row["authmech"])) {
|
||||
switch ($row["authmech"]) {
|
||||
case "yubi_otp":
|
||||
$data['name'] = "yubi_otp";
|
||||
$data['pretty'] = "Yubico OTP";
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$data['additional'][] = $row;
|
||||
if (!isset($id)){
|
||||
// fetch all tfa methods - just get information about possible authenticators
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `authmech` FROM `tfa`
|
||||
WHERE `username` = :username AND `active` = '1'");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
// no tfa methods found
|
||||
if (count($results) == 0) {
|
||||
$data['name'] = 'none';
|
||||
$data['pretty'] = "-";
|
||||
$data['additional'] = array();
|
||||
return $data;
|
||||
}
|
||||
|
||||
$data['additional'] = $results;
|
||||
return $data;
|
||||
} else {
|
||||
// fetch specific authenticator details by id
|
||||
$stmt = $pdo->prepare("SELECT * FROM `tfa`
|
||||
WHERE `username` = :username AND `id` = :id AND `active` = '1'");
|
||||
$stmt->execute(array(':username' => $username, ':id' => $id));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (isset($row["authmech"])) {
|
||||
switch ($row["authmech"]) {
|
||||
case "yubi_otp":
|
||||
$data['name'] = "yubi_otp";
|
||||
$data['pretty'] = "Yubico OTP";
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username AND `id` = :id");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':id' => $id
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$data['additional'][] = $row;
|
||||
}
|
||||
return $data;
|
||||
break;
|
||||
// u2f - deprecated, should be removed
|
||||
case "u2f":
|
||||
$data['name'] = "u2f";
|
||||
$data['pretty'] = "Fido U2F";
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username AND `id` = :id");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':id' => $id
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$data['additional'][] = $row;
|
||||
}
|
||||
return $data;
|
||||
break;
|
||||
case "hotp":
|
||||
$data['name'] = "hotp";
|
||||
$data['pretty'] = "HMAC-based OTP";
|
||||
return $data;
|
||||
break;
|
||||
case "totp":
|
||||
$data['name'] = "totp";
|
||||
$data['pretty'] = "Time-based OTP";
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username AND `id` = :id");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':id' => $id
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$data['additional'][] = $row;
|
||||
}
|
||||
return $data;
|
||||
break;
|
||||
case "webauthn":
|
||||
$data['name'] = "webauthn";
|
||||
$data['pretty'] = "WebAuthn";
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username AND `id` = :id");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
':id' => $id
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$data['additional'][] = $row;
|
||||
}
|
||||
return $data;
|
||||
break;
|
||||
default:
|
||||
$data['name'] = 'none';
|
||||
$data['pretty'] = "-";
|
||||
return $data;
|
||||
break;
|
||||
}
|
||||
return $data;
|
||||
break;
|
||||
// u2f - deprecated, should be removed
|
||||
case "u2f":
|
||||
$data['name'] = "u2f";
|
||||
$data['pretty'] = "Fido U2F";
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$data['additional'][] = $row;
|
||||
}
|
||||
return $data;
|
||||
break;
|
||||
case "hotp":
|
||||
$data['name'] = "hotp";
|
||||
$data['pretty'] = "HMAC-based OTP";
|
||||
return $data;
|
||||
break;
|
||||
case "totp":
|
||||
$data['name'] = "totp";
|
||||
$data['pretty'] = "Time-based OTP";
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$data['additional'][] = $row;
|
||||
}
|
||||
return $data;
|
||||
break;
|
||||
case "webauthn":
|
||||
$data['name'] = "webauthn";
|
||||
$data['pretty'] = "WebAuthn";
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'webauthn' AND `username` = :username");
|
||||
$stmt->execute(array(
|
||||
':username' => $username,
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$data['additional'][] = $row;
|
||||
}
|
||||
return $data;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
else {
|
||||
$data['name'] = 'none';
|
||||
$data['pretty'] = "-";
|
||||
return $data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$data['name'] = 'none';
|
||||
$data['pretty'] = "-";
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
global $pdo;
|
||||
global $yubi;
|
||||
global $u2f;
|
||||
global $tfa;
|
||||
$stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
|
||||
WHERE `username` = :username AND `active` = '1'");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
function verify_tfa_login($username, $_data) {
|
||||
global $pdo;
|
||||
global $yubi;
|
||||
global $u2f;
|
||||
global $tfa;
|
||||
global $WebAuthn;
|
||||
|
||||
switch ($row["authmech"]) {
|
||||
if ($_data['tfa_method'] != 'u2f'){
|
||||
|
||||
switch ($_data["tfa_method"]) {
|
||||
case "yubi_otp":
|
||||
if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) {
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -1597,7 +1650,7 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
|
||||
WHERE `username` = :username
|
||||
AND `authmech` = 'yubi_otp'
|
||||
AND `active`='1'
|
||||
AND `active` = '1'
|
||||
AND `secret` LIKE :modhex");
|
||||
$stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
@@ -1632,15 +1685,16 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
return false;
|
||||
break;
|
||||
case "totp":
|
||||
try {
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
|
||||
WHERE `username` = :username
|
||||
AND `authmech` = 'totp'
|
||||
AND `id` = :id
|
||||
AND `active`='1'");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$stmt->execute(array(':username' => $username, ':id' => $_data['id']));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($rows as $row) {
|
||||
if ($tfa->verifyCode($row['secret'], $_data['token']) === true) {
|
||||
if ($tfa->verifyCode($row['secret'], $_data['token']) === true) {
|
||||
$_SESSION['tfa_id'] = $row['id'];
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
@@ -1648,7 +1702,7 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
'msg' => 'verified_totp_login'
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -1656,23 +1710,16 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
'msg' => 'totp_verification_failed'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $username, '*'),
|
||||
'msg' => array('mysql_error', $e)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
// u2f - deprecated, should be removed
|
||||
case "u2f":
|
||||
// delete old keys that used u2f
|
||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = :authmech AND `username` = :username");
|
||||
$stmt->execute(array(':authmech' => 'u2f', ':username' => $username));
|
||||
|
||||
return true;
|
||||
case "webauthn":
|
||||
$tokenData = json_decode($_data['token']);
|
||||
$clientDataJSON = base64_decode($tokenData->clientDataJSON);
|
||||
@@ -1681,13 +1728,20 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
$id = base64_decode($tokenData->id);
|
||||
$challenge = $_SESSION['challenge'];
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `keyHandle` = :tokenId");
|
||||
$stmt->execute(array(':tokenId' => $tokenData->id));
|
||||
$stmt = $pdo->prepare("SELECT `id`, `key_id`, `keyHandle`, `username`, `publicKey` FROM `tfa` WHERE `id` = :id AND `active`='1'");
|
||||
$stmt->execute(array(':id' => $_data['id']));
|
||||
$process_webauthn = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($process_webauthn) || empty($process_webauthn['publicKey']) || empty($process_webauthn['username'])) return false;
|
||||
if (empty($process_webauthn)){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $username, '*'),
|
||||
'msg' => array('webauthn_verification_failed', 'authenticator not found')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($process_webauthn['publicKey'] === false) {
|
||||
if (empty($process_webauthn['publicKey']) || $process_webauthn['publicKey'] === false) {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $username, '*'),
|
||||
@@ -1695,6 +1749,7 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$WebAuthn->processGet($clientDataJSON, $authenticatorData, $signature, $process_webauthn['publicKey'], $challenge, null, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN'], $GLOBALS['WEBAUTHN_USER_PRESENT_FLAG']);
|
||||
}
|
||||
@@ -1707,26 +1762,31 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$stmt = $pdo->prepare("SELECT `superadmin` FROM `admin` WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $process_webauthn['username']));
|
||||
$obj_props = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($obj_props['superadmin'] === 1) {
|
||||
$_SESSION["mailcow_cc_role"] = "admin";
|
||||
$_SESSION["mailcow_cc_role"] = "admin";
|
||||
}
|
||||
elseif ($obj_props['superadmin'] === 0) {
|
||||
$_SESSION["mailcow_cc_role"] = "domainadmin";
|
||||
$_SESSION["mailcow_cc_role"] = "domainadmin";
|
||||
}
|
||||
else {
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $process_webauthn['username']));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row['username'] == $process_webauthn['username']) {
|
||||
$stmt = $pdo->prepare("SELECT `username` FROM `mailbox` WHERE `username` = :username");
|
||||
$stmt->execute(array(':username' => $process_webauthn['username']));
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
if (!empty($row['username'])) {
|
||||
$_SESSION["mailcow_cc_role"] = "user";
|
||||
}
|
||||
} else {
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
'log' => array(__FUNCTION__, $username, '*'),
|
||||
'msg' => array('webauthn_verification_failed', 'could not determine user role')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($process_webauthn['username'] != $_SESSION['pending_mailcow_cc_username']){
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'danger',
|
||||
@@ -1736,9 +1796,8 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$_SESSION["mailcow_cc_username"] = $process_webauthn['username'];
|
||||
$_SESSION['tfa_id'] = $process_webauthn['key_id'];
|
||||
$_SESSION['tfa_id'] = $process_webauthn['id'];
|
||||
$_SESSION['authReq'] = null;
|
||||
unset($_SESSION["challenge"]);
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -1759,6 +1818,17 @@ function verify_tfa_login($username, $_data, $WebAuthn) {
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// delete old keys that used u2f
|
||||
$stmt = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
if (count($rows) == 0) return false;
|
||||
|
||||
$stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
|
||||
$stmt->execute(array(':username' => $username));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
function admin_api($access, $action, $data = null) {
|
||||
global $pdo;
|
||||
|
@@ -338,9 +338,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$custom_params = (empty(trim($_data['custom_params']))) ? '' : trim($_data['custom_params']);
|
||||
|
||||
// validate custom params
|
||||
foreach (explode(' -', $custom_params) as $param){
|
||||
foreach (explode('-', $custom_params) as $param){
|
||||
if(empty($param)) continue;
|
||||
|
||||
// extract option
|
||||
if (str_contains($param, '=')) $param = explode('=', $param)[0];
|
||||
else $param = rtrim($param, ' ');
|
||||
// remove first char if first char is -
|
||||
if ($param[0] == '-') $param = ltrim($param, $param[0]);
|
||||
|
||||
if (str_contains($param, ' ')) {
|
||||
// bad char
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -351,11 +357,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// extract option
|
||||
if (str_contains($param, '=')) $param = explode('=', $param)[0];
|
||||
// remove first char if first char is -
|
||||
if ($param[0] == '-') $param = ltrim($param, $param[0]);
|
||||
|
||||
// check if param is whitelisted
|
||||
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
|
||||
// bad option
|
||||
@@ -1793,9 +1794,15 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
}
|
||||
|
||||
// validate custom params
|
||||
foreach (explode(' -', $custom_params) as $param){
|
||||
foreach (explode('-', $custom_params) as $param){
|
||||
if(empty($param)) continue;
|
||||
|
||||
// extract option
|
||||
if (str_contains($param, '=')) $param = explode('=', $param)[0];
|
||||
else $param = rtrim($param, ' ');
|
||||
// remove first char if first char is -
|
||||
if ($param[0] == '-') $param = ltrim($param, $param[0]);
|
||||
|
||||
if (str_contains($param, ' ')) {
|
||||
// bad char
|
||||
$_SESSION['return'][] = array(
|
||||
@@ -1806,11 +1813,6 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// extract option
|
||||
if (str_contains($param, '=')) $param = explode('=', $param)[0];
|
||||
// remove first char if first char is -
|
||||
if ($param[0] == '-') $param = ltrim($param, $param[0]);
|
||||
|
||||
// check if param is whitelisted
|
||||
if (!in_array(strtolower($param), $GLOBALS["IMAPSYNC_OPTIONS"]["whitelist"])){
|
||||
// bad option
|
||||
|
@@ -3,7 +3,7 @@ function init_db_schema() {
|
||||
try {
|
||||
global $pdo;
|
||||
|
||||
$db_version = "18062022_1153";
|
||||
$db_version = "25072022_2300";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@@ -738,8 +738,8 @@ function init_db_schema() {
|
||||
"username" => "VARCHAR(255) NOT NULL",
|
||||
"authmech" => "ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')",
|
||||
"secret" => "VARCHAR(255) DEFAULT NULL",
|
||||
"keyHandle" => "VARCHAR(255) DEFAULT NULL",
|
||||
"publicKey" => "VARCHAR(255) DEFAULT NULL",
|
||||
"keyHandle" => "VARCHAR(1023) DEFAULT NULL",
|
||||
"publicKey" => "VARCHAR(4096) DEFAULT NULL",
|
||||
"counter" => "INT NOT NULL DEFAULT '0'",
|
||||
"certificate" => "TEXT",
|
||||
"active" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
||||
@@ -1227,7 +1227,7 @@ function init_db_schema() {
|
||||
$pdo->query($create);
|
||||
}
|
||||
|
||||
// Mitigate imapsync pipemess issue
|
||||
// Mitigate imapsync argument injection issue
|
||||
$pdo->query("UPDATE `imapsync` SET `custom_params` = ''
|
||||
WHERE `custom_params` LIKE '%pipemess%'
|
||||
OR custom_params LIKE '%skipmess%'
|
||||
@@ -1237,8 +1237,7 @@ function init_db_schema() {
|
||||
OR custom_params LIKE '%pipemess%'
|
||||
OR custom_params LIKE '%regextrans2%'
|
||||
OR custom_params LIKE '%maxlinelengthcmd%';");
|
||||
|
||||
|
||||
|
||||
// Migrate webauthn tfa
|
||||
$stmt = $pdo->query("ALTER TABLE `tfa` MODIFY COLUMN `authmech` ENUM('yubi_otp', 'u2f', 'hotp', 'totp', 'webauthn')");
|
||||
|
||||
|
@@ -66,8 +66,9 @@ $qrprovider = new RobThree\Auth\Providers\Qr\QRServerProvider();
|
||||
$tfa = new RobThree\Auth\TwoFactorAuth($OTP_LABEL, 6, 30, 'sha1', $qrprovider);
|
||||
|
||||
// FIDO2
|
||||
$server_name = parse_url('https://' . $_SERVER['HTTP_HOST'], PHP_URL_HOST);
|
||||
$formats = $GLOBALS['FIDO2_FORMATS'];
|
||||
$WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $_SERVER['HTTP_HOST'], $formats);
|
||||
$WebAuthn = new lbuchs\WebAuthn\WebAuthn('WebAuthn Library', $server_name, $formats);
|
||||
// only include root ca's when needed
|
||||
if (getenv('WEBAUTHN_ONLY_TRUSTED_VENDORS') == 'y') $WebAuthn->addRootCertificates($_SERVER['DOCUMENT_ROOT'] . '/inc/lib/WebAuthn/rootCertificates');
|
||||
|
||||
|
@@ -1,24 +1,24 @@
|
||||
<?php
|
||||
if (isset($_POST["verify_tfa_login"])) {
|
||||
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST, $WebAuthn)) {
|
||||
if (verify_tfa_login($_SESSION['pending_mailcow_cc_username'], $_POST)) {
|
||||
$_SESSION['mailcow_cc_username'] = $_SESSION['pending_mailcow_cc_username'];
|
||||
$_SESSION['mailcow_cc_role'] = $_SESSION['pending_mailcow_cc_role'];
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_method']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
|
||||
header("Location: /user");
|
||||
} else {
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_method']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($_GET["cancel_tfa_login"])) {
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_method']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
|
||||
header("Location: /");
|
||||
}
|
||||
@@ -34,6 +34,7 @@ if (isset($_POST["quick_delete"])) {
|
||||
if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||
$login_user = strtolower(trim($_POST["login_user"]));
|
||||
$as = check_login($login_user, $_POST["pass_user"]);
|
||||
|
||||
if ($as == "admin") {
|
||||
$_SESSION['mailcow_cc_username'] = $login_user;
|
||||
$_SESSION['mailcow_cc_role'] = "admin";
|
||||
@@ -47,22 +48,22 @@ if (isset($_POST["login_user"]) && isset($_POST["pass_user"])) {
|
||||
elseif ($as == "user") {
|
||||
$_SESSION['mailcow_cc_username'] = $login_user;
|
||||
$_SESSION['mailcow_cc_role'] = "user";
|
||||
$http_parameters = explode('&', $_SESSION['index_query_string']);
|
||||
unset($_SESSION['index_query_string']);
|
||||
if (in_array('mobileconfig', $http_parameters)) {
|
||||
if (in_array('only_email', $http_parameters)) {
|
||||
header("Location: /mobileconfig.php?email_only");
|
||||
die();
|
||||
}
|
||||
header("Location: /mobileconfig.php");
|
||||
die();
|
||||
}
|
||||
$http_parameters = explode('&', $_SESSION['index_query_string']);
|
||||
unset($_SESSION['index_query_string']);
|
||||
if (in_array('mobileconfig', $http_parameters)) {
|
||||
if (in_array('only_email', $http_parameters)) {
|
||||
header("Location: /mobileconfig.php?email_only");
|
||||
die();
|
||||
}
|
||||
header("Location: /mobileconfig.php");
|
||||
die();
|
||||
}
|
||||
header("Location: /user");
|
||||
}
|
||||
elseif ($as != "pending") {
|
||||
unset($_SESSION['pending_mailcow_cc_username']);
|
||||
unset($_SESSION['pending_mailcow_cc_role']);
|
||||
unset($_SESSION['pending_tfa_method']);
|
||||
unset($_SESSION['pending_tfa_methods']);
|
||||
unset($_SESSION['mailcow_cc_username']);
|
||||
unset($_SESSION['mailcow_cc_role']);
|
||||
}
|
||||
|
@@ -101,6 +101,7 @@ $AVAILABLE_LANGUAGES = array(
|
||||
'ru-ru' => 'Pусский (Russian)',
|
||||
'sk-sk' => 'Slovenčina (Slovak)',
|
||||
'sv-se' => 'Svenska (Swedish)',
|
||||
'tr-tr' => 'Türkçe (Turkish)',
|
||||
'uk-ua' => 'Українська (Ukrainian)',
|
||||
'zh-cn' => '简体中文 (Simplified Chinese)',
|
||||
'zh-tw' => '繁體中文 (Traditional Chinese)',
|
||||
@@ -234,131 +235,127 @@ $RSPAMD_MAPS = array(
|
||||
|
||||
$IMAPSYNC_OPTIONS = array(
|
||||
'whitelist' => array(
|
||||
'log',
|
||||
'showpasswords',
|
||||
'nossl1',
|
||||
'nossl2',
|
||||
'ssl2',
|
||||
'notls1',
|
||||
'notls2',
|
||||
'tls2',
|
||||
'debugssl',
|
||||
'sslargs1',
|
||||
'sslargs2',
|
||||
'authmech1',
|
||||
'authmech2',
|
||||
'authuser1',
|
||||
'authuser2',
|
||||
'proxyauth1',
|
||||
'proxyauth2',
|
||||
'authmd51',
|
||||
'authmd52',
|
||||
'domain1',
|
||||
'domain2',
|
||||
'oauthaccesstoken1',
|
||||
'oauthaccesstoken2',
|
||||
'oauthdirect1',
|
||||
'oauthdirect2',
|
||||
'folder',
|
||||
'folder',
|
||||
'folderrec',
|
||||
'folderrec',
|
||||
'folderfirst',
|
||||
'folderfirst',
|
||||
'folderlast',
|
||||
'folderlast',
|
||||
'nomixfolders',
|
||||
'skipemptyfolders',
|
||||
'include',
|
||||
'include',
|
||||
'subfolder1',
|
||||
'subscribed',
|
||||
'subscribe',
|
||||
'prefix1',
|
||||
'prefix2',
|
||||
'sep1',
|
||||
'sep2',
|
||||
'nofoldersizesatend',
|
||||
'justfoldersizes',
|
||||
'pidfile',
|
||||
'pidfilelocking',
|
||||
'nolog',
|
||||
'logfile',
|
||||
'logdir',
|
||||
'debugcrossduplicates',
|
||||
'disarmreadreceipts',
|
||||
'truncmess',
|
||||
'synclabels',
|
||||
'resynclabels',
|
||||
'resyncflags',
|
||||
'noresyncflags',
|
||||
'filterbuggyflags',
|
||||
'expunge1',
|
||||
'noexpunge1',
|
||||
'delete1emptyfolders',
|
||||
'delete2folders',
|
||||
'noexpunge2',
|
||||
'nouidexpunge2',
|
||||
'syncinternaldates',
|
||||
'idatefromheader',
|
||||
'maxsize',
|
||||
'minsize',
|
||||
'minage',
|
||||
'search',
|
||||
'search1',
|
||||
'search2',
|
||||
'noabletosearch',
|
||||
'noabletosearch1',
|
||||
'noabletosearch2',
|
||||
'maxlinelength',
|
||||
'useheader',
|
||||
'useheader',
|
||||
'syncduplicates',
|
||||
'usecache',
|
||||
'nousecache',
|
||||
'useuid',
|
||||
'syncacls',
|
||||
'nosyncacls',
|
||||
'debug',
|
||||
'debugfolders',
|
||||
'debugcontent',
|
||||
'debugflags',
|
||||
'debugimap1',
|
||||
'debugimap2',
|
||||
'debugimap',
|
||||
'debugmemory',
|
||||
'errorsmax',
|
||||
'tests',
|
||||
'testslive',
|
||||
'testslive6',
|
||||
'gmail1',
|
||||
'gmail2',
|
||||
'office1',
|
||||
'office2',
|
||||
'exchange1',
|
||||
'exchange2',
|
||||
'domino1',
|
||||
'domino2',
|
||||
'keepalive1',
|
||||
'keepalive2',
|
||||
'maxmessagespersecond',
|
||||
'maxbytesafter',
|
||||
'maxsleep',
|
||||
'abort',
|
||||
'exitwhenover',
|
||||
'noid',
|
||||
'justconnect',
|
||||
'justlogin',
|
||||
'justfolders'
|
||||
'authmech1',
|
||||
'authmech2',
|
||||
'authuser1',
|
||||
'authuser2',
|
||||
'debugcontent',
|
||||
'disarmreadreceipts',
|
||||
'logdir',
|
||||
'debugcrossduplicates',
|
||||
'maxsize',
|
||||
'minsize',
|
||||
'minage',
|
||||
'search',
|
||||
'noabletosearch',
|
||||
'pidfile',
|
||||
'pidfilelocking',
|
||||
'search1',
|
||||
'search2',
|
||||
'sslargs1',
|
||||
'sslargs2',
|
||||
'syncduplicates',
|
||||
'usecache',
|
||||
'synclabels',
|
||||
'truncmess',
|
||||
'domino2',
|
||||
'expunge1',
|
||||
'filterbuggyflags',
|
||||
'justconnect',
|
||||
'justfolders',
|
||||
'maxlinelength',
|
||||
'useheader',
|
||||
'noabletosearch1',
|
||||
'nolog',
|
||||
'prefix1',
|
||||
'prefix2',
|
||||
'sep1',
|
||||
'sep2',
|
||||
'nofoldersizesatend',
|
||||
'justfoldersizes',
|
||||
'proxyauth1',
|
||||
'skipemptyfolders',
|
||||
'include',
|
||||
'subfolder1',
|
||||
'subscribed',
|
||||
'subscribe',
|
||||
'debug',
|
||||
'debugimap2',
|
||||
'domino1',
|
||||
'exchange1',
|
||||
'exchange2',
|
||||
'justlogin',
|
||||
'keepalive1',
|
||||
'keepalive2',
|
||||
'noabletosearch2',
|
||||
'noexpunge2',
|
||||
'noresyncflags',
|
||||
'nossl1',
|
||||
'nouidexpunge2',
|
||||
'syncinternaldates',
|
||||
'idatefromheader',
|
||||
'useuid',
|
||||
'debugflags',
|
||||
'debugimap',
|
||||
'delete1emptyfolders',
|
||||
'delete2folders',
|
||||
'gmail2',
|
||||
'office1',
|
||||
'testslive6',
|
||||
'debugimap1',
|
||||
'errorsmax',
|
||||
'tests',
|
||||
'gmail1',
|
||||
'maxmessagespersecond',
|
||||
'maxbytesafter',
|
||||
'maxsleep',
|
||||
'abort',
|
||||
'resyncflags',
|
||||
'resynclabels',
|
||||
'syncacls',
|
||||
'nosyncacls',
|
||||
'nousecache',
|
||||
'office2',
|
||||
'testslive',
|
||||
'debugmemory',
|
||||
'exitwhenover',
|
||||
'noid',
|
||||
'noexpunge1',
|
||||
'authmd51',
|
||||
'logfile',
|
||||
'proxyauth2',
|
||||
'domain1',
|
||||
'domain2',
|
||||
'oauthaccesstoken1',
|
||||
'oauthaccesstoken2',
|
||||
'oauthdirect1',
|
||||
'oauthdirect2',
|
||||
'folder',
|
||||
'folderrec',
|
||||
'folderfirst',
|
||||
'folderlast',
|
||||
'nomixfolders',
|
||||
'authmd52',
|
||||
'debugfolders',
|
||||
'nossl2',
|
||||
'ssl2',
|
||||
'tls2',
|
||||
'notls2',
|
||||
'debugssl',
|
||||
'notls1',
|
||||
'inet4',
|
||||
'inet6',
|
||||
'log',
|
||||
'showpasswords'
|
||||
),
|
||||
'blacklist' => array(
|
||||
'skipmess',
|
||||
'delete2foldersonly',
|
||||
'delete2foldersbutnot',
|
||||
'regexflag',
|
||||
'regexmess',
|
||||
'pipemess',
|
||||
'regextrans2',
|
||||
'maxlinelengthcmd'
|
||||
'skipmess',
|
||||
'delete2foldersonly',
|
||||
'delete2foldersbutnot',
|
||||
'regexflag',
|
||||
'regexmess',
|
||||
'pipemess',
|
||||
'regextrans2',
|
||||
'maxlinelengthcmd'
|
||||
)
|
||||
);
|
||||
|
@@ -178,15 +178,22 @@ if (isset($_GET['query'])) {
|
||||
// parse post data
|
||||
$post = trim(file_get_contents('php://input'));
|
||||
if ($post) $post = json_decode($post);
|
||||
|
||||
// decode base64 strings
|
||||
$clientDataJSON = base64_decode($post->clientDataJSON);
|
||||
$attestationObject = base64_decode($post->attestationObject);
|
||||
|
||||
// process registration data from authenticator
|
||||
try {
|
||||
// decode base64 strings
|
||||
$clientDataJSON = base64_decode($post->clientDataJSON);
|
||||
$attestationObject = base64_decode($post->attestationObject);
|
||||
|
||||
// processCreate($clientDataJSON, $attestationObject, $challenge, $requireUserVerification=false, $requireUserPresent=true, $failIfRootMismatch=true)
|
||||
$data = $WebAuthn->processCreate($clientDataJSON, $attestationObject, $_SESSION['challenge'], false, true);
|
||||
|
||||
// safe authenticator in mysql `tfa` table
|
||||
$_data['tfa_method'] = $post->tfa_method;
|
||||
$_data['key_id'] = $post->key_id;
|
||||
$_data['confirm_password'] = $post->confirm_password;
|
||||
$_data['registration'] = $data;
|
||||
set_tfa($_data);
|
||||
}
|
||||
catch (Throwable $ex) {
|
||||
// err
|
||||
@@ -197,11 +204,6 @@ if (isset($_GET['query'])) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// safe authenticator in mysql `tfa` table
|
||||
$_data['tfa_method'] = $post->tfa_method;
|
||||
$_data['key_id'] = $post->key_id;
|
||||
$_data['registration'] = $data;
|
||||
set_tfa($_data);
|
||||
|
||||
// send response
|
||||
$return = new stdClass();
|
||||
@@ -419,7 +421,7 @@ if (isset($_GET['query'])) {
|
||||
// }
|
||||
$ids = NULL;
|
||||
|
||||
$getArgs = $WebAuthn->getGetArgs($ids, 30, true, true, true, true, $GLOBALS['FIDO2_UV_FLAG_LOGIN']);
|
||||
$getArgs = $WebAuthn->getGetArgs($ids, 30, false, false, false, false, $GLOBALS['FIDO2_UV_FLAG_LOGIN']);
|
||||
print(json_encode($getArgs));
|
||||
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||
return;
|
||||
@@ -428,8 +430,11 @@ if (isset($_GET['query'])) {
|
||||
case "webauthn-tfa-registration":
|
||||
if (isset($_SESSION["mailcow_cc_role"])) {
|
||||
// Exclude existing CredentialIds, if any
|
||||
$stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username");
|
||||
$stmt->execute(array(':username' => $_SESSION['mailcow_cc_username']));
|
||||
$stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username AND authmech = :authmech");
|
||||
$stmt->execute(array(
|
||||
':username' => $_SESSION['mailcow_cc_username'],
|
||||
':authmech' => 'webauthn'
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$excludeCredentialIds[] = base64_decode($row['keyHandle']);
|
||||
@@ -450,20 +455,24 @@ if (isset($_GET['query'])) {
|
||||
}
|
||||
break;
|
||||
case "webauthn-tfa-get-args":
|
||||
$stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username");
|
||||
$stmt->execute(array(':username' => $_SESSION['pending_mailcow_cc_username']));
|
||||
$stmt = $pdo->prepare("SELECT `keyHandle` FROM `tfa` WHERE username = :username AND authmech = :authmech");
|
||||
$stmt->execute(array(
|
||||
':username' => $_SESSION['pending_mailcow_cc_username'],
|
||||
':authmech' => 'webauthn'
|
||||
));
|
||||
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
while($row = array_shift($rows)) {
|
||||
$cids[] = base64_decode($row['keyHandle']);
|
||||
}
|
||||
if (count($cids) == 0) {
|
||||
if (count($rows) == 0) {
|
||||
print(json_encode(array(
|
||||
'type' => 'error',
|
||||
'msg' => 'Cannot find matching credentialIds'
|
||||
)));
|
||||
exit;
|
||||
}
|
||||
while($row = array_shift($rows)) {
|
||||
$cids[] = base64_decode($row['keyHandle']);
|
||||
}
|
||||
|
||||
$getArgs = $WebAuthn->getGetArgs($cids, 30, true, true, true, true, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN']);
|
||||
$getArgs = $WebAuthn->getGetArgs($cids, 30, false, false, false, false, $GLOBALS['WEBAUTHN_UV_FLAG_LOGIN']);
|
||||
$getArgs->publicKey->extensions = array('appid' => "https://".$getArgs->publicKey->rpId);
|
||||
print(json_encode($getArgs));
|
||||
$_SESSION['challenge'] = $WebAuthn->getChallenge();
|
||||
|
@@ -839,7 +839,7 @@
|
||||
"confirm_delete": "Potvrdit smazání prvku.",
|
||||
"danger": "Nebezpečí",
|
||||
"deliver_inbox": "Doručit do schránky",
|
||||
"disabled_by_config": "Funkce karanténa je momentálně vypnuta v nastavení systému.",
|
||||
"disabled_by_config": "Funkce karanténa je momentálně vypnuta v nastavení systému. Nastavte, prosím, prvkům karantény hodnoty \"počet zadržených zpráv\" a \"maximální velikost\".",
|
||||
"download_eml": "Stáhnout (.eml)",
|
||||
"empty": "Žádné výsledky",
|
||||
"high_danger": "Vysoké nebezpečí",
|
||||
|
@@ -102,7 +102,8 @@
|
||||
"timeout2": "Délai d'expiration pour la connexion à l'hôte local",
|
||||
"username": "Nom d'utilisateur",
|
||||
"validate": "Valider",
|
||||
"validation_success": "Validation réussie"
|
||||
"validation_success": "Validation réussie",
|
||||
"bcc_dest_format": "La destination Cci doit être une seule adresse e-mail valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici."
|
||||
},
|
||||
"admin": {
|
||||
"access": "Accès",
|
||||
@@ -322,7 +323,9 @@
|
||||
"yes": "✓",
|
||||
"api_read_write": "Accès Lecture-Écriture",
|
||||
"oauth2_add_client": "Ajouter un client OAuth2",
|
||||
"password_policy": "Politique de mots de passe"
|
||||
"password_policy": "Politique de mots de passe",
|
||||
"admins": "Administrateurs",
|
||||
"api_read_only": "Accès lecture-seule"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Accès refusé ou données de formulaire non valides",
|
||||
|
@@ -973,7 +973,8 @@
|
||||
"verified_fido2_login": "Verified FIDO2 login",
|
||||
"verified_totp_login": "Verified TOTP login",
|
||||
"verified_webauthn_login": "Verified WebAuthn login",
|
||||
"verified_yotp_login": "Verified Yubico OTP login"
|
||||
"verified_yotp_login": "Verified Yubico OTP login",
|
||||
"domain_add_dkim_available": "Esisteva già una chiave DKIM"
|
||||
},
|
||||
"tfa": {
|
||||
"api_register": "%s usa le API Yubico Cloud. Richiedi una chiave API <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">qui</a>",
|
||||
|
@@ -979,7 +979,8 @@
|
||||
"verified_totp_login": "Autentificarea TOTP verificată",
|
||||
"verified_webauthn_login": "Autentificarea WebAuthn verificată",
|
||||
"verified_fido2_login": "Conectare FIDO2 verificată",
|
||||
"verified_yotp_login": "Autentificarea Yubico OTP verificată"
|
||||
"verified_yotp_login": "Autentificarea Yubico OTP verificată",
|
||||
"domain_add_dkim_available": "O cheie DKIM deja a existat"
|
||||
},
|
||||
"tfa": {
|
||||
"api_register": "%s utilizează API-ul Yubico Cloud. Obțineți o cheie API pentru cheia dvs. de <a href=\"https://upgrade.yubico.com/getapikey/\" target=\"_blank\">aici</a>",
|
||||
@@ -990,7 +991,7 @@
|
||||
"enter_qr_code": "Codul tău TOTP dacă dispozitivul tău nu poate scana codurile QR",
|
||||
"error_code": "Cod de eroare",
|
||||
"init_webauthn": "Inițializare, vă rugăm așteptați...",
|
||||
"key_id": "Un identificator pentru YubiKey",
|
||||
"key_id": "Un identificator pentru dispozitiv",
|
||||
"key_id_totp": "Un identificator pentru cheia ta",
|
||||
"none": "Dezactivează",
|
||||
"reload_retry": "- (reîncărcați browserul dacă eroarea persistă)",
|
||||
|
@@ -988,7 +988,7 @@
|
||||
"enter_qr_code": "Ваш код TOTP, если устройство не может отсканировать QR-код",
|
||||
"error_code": "Код ошибки",
|
||||
"init_webauthn": "Инициализация, пожалуйста, подождите...",
|
||||
"key_id": "Идентификатор YubiKey ключа",
|
||||
"key_id": "Идентификатор вашего устройства",
|
||||
"key_id_totp": "Идентификатор TOTP ключа",
|
||||
"none": "Отключить",
|
||||
"reload_retry": "- (перезагрузить страницу браузера или почистите кеш/cookies, если ошибка повторяется)",
|
||||
@@ -1002,7 +1002,8 @@
|
||||
"webauthn": "WebAuthn аутентификация",
|
||||
"waiting_usb_auth": "<i>Ожидание устройства USB...</i><br><br>Пожалуйста, нажмите кнопку на USB устройстве сейчас.",
|
||||
"waiting_usb_register": "<i>Ожидание устройства USB...</i><br><br>Пожалуйста, введите пароль выше и подтвердите регистрацию, нажав кнопку на USB устройстве.",
|
||||
"yubi_otp": "Yubico OTP аутентификация"
|
||||
"yubi_otp": "Yubico OTP аутентификация",
|
||||
"u2f_deprecated": "Похоже, что ваш ключ был зарегистрирован с использованием устаревшего метода U2F. Мы деактивируем для вас двухфакторную аутентификацию и удалим ваш ключ."
|
||||
},
|
||||
"user": {
|
||||
"action": "Действия",
|
||||
|
86
data/web/lang/lang.tr.json
Normal file
86
data/web/lang/lang.tr.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"acl": {
|
||||
"alias_domains": "Takma alan adı ekle",
|
||||
"app_passwds": "Uygulama şifrelerini yönet",
|
||||
"delimiter_action": "Sınırlama işlemi",
|
||||
"domain_relayhost": "Bir alan adı için relayhost sunucusunu değiştir",
|
||||
"eas_reset": "EAS cihazlarını sıfırla",
|
||||
"mailbox_relayhost": "Bir posta kutusunun relayhost sunucularını değiştir",
|
||||
"pushover": "Bildirim",
|
||||
"quarantine": "Karantina işlemleri",
|
||||
"quarantine_attachments": "Ekleri karantinaya al",
|
||||
"quarantine_notification": "Karantina bildirimlerini değiştir",
|
||||
"smtp_ip_access": "SMTP sunucularının değiştirilmesine izin ver",
|
||||
"sogo_access": "SOGo erişiminin yönetilmesine izin ver",
|
||||
"domain_desc": "Alan adı açıklamasını değiştir",
|
||||
"extend_sender_acl": "Gönderenin acl'sini harici adreslere göre genişletmeye izin ver",
|
||||
"spam_policy": "Engellenenler / İzin verilenler",
|
||||
"filters": "Fitreler"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "Aktif edilirse diğer tüm filtreler devre dışı bırakılacak.",
|
||||
"add_domain_only": "Sadece alan adı ekle",
|
||||
"alias_address": "Takma ad adres(leri)",
|
||||
"alias_domain": "Takma alan adı",
|
||||
"alias_domain_info": "<small>Sadece geçerli alan adları (virgülle ayırın).</small>",
|
||||
"backup_mx_options": "İletme ayarları",
|
||||
"delete2": "Kaynakta olmayan hedefteki mesajları sil",
|
||||
"delete2duplicates": "Hedefteki kopyaları sil",
|
||||
"disable_login": "Giriş yapmaya izin verme ( Gelen mailler yine de kabul edilir)",
|
||||
"domain": "Alan adı",
|
||||
"domain_matches_hostname": "Alan adı %s ana bilgisayar adıyla eşleşiyor",
|
||||
"add_domain_restart": "Alan adı ekleyin ve SOGo'yu yeniden başlatın",
|
||||
"alias_address_info": "<small>Bir alan adına ilişkin tüm iletileri yakalamak için tam e-posta adresi veya @example.com olacak şeklinde girin (virgülle ayırın).<b>sadece mailcow alan adları</b>.</small>",
|
||||
"domain_quota_m": "Toplam alan adı kotası (MiB)",
|
||||
"generate": "oluştur",
|
||||
"goto_ham": "Ham olarak<span class=\"text-success\"><b>işaretle</b></span>",
|
||||
"goto_null": "Postaları sessizce çöpe at",
|
||||
"goto_spam": "Spam olarak<span class=\"text-danger\"><b>işaretle</b></span>",
|
||||
"hostname": "Ana sunucu",
|
||||
"kind": "Tür",
|
||||
"mailbox_quota_m": "Posta kutusu başına maksimum kota (MiB)",
|
||||
"max_aliases": "Maksimum olası takma adı",
|
||||
"max_mailboxes": "Maksimum olası posta kutusu",
|
||||
"nexthop": "Sonraki atlama",
|
||||
"port": "Port",
|
||||
"public_comment": "Genel yorum",
|
||||
"relay_all": "Tüm alıcılara ilet",
|
||||
"relay_all_info": "Eğer <b>hiçbir</b> alıcıya iletilmemesini seçerseniz, aktarılması gereken her alıcı için bir (\"kör\") posta kutusu eklemeniz gerekecektir.",
|
||||
"relay_domain": "Bu alan adını ilet",
|
||||
"relay_transport_info": "<div class=\"label label-info\">Bilgi</div> Bu etki alanı için özel bir hedef için aktarım eşlemeleri tanımlayabilirsiniz. Ayarlanmazsa, bir MX araması yapılacaktır.",
|
||||
"relay_unknown_only": "Yalnızca mevcut olmayan posta kutularını ilet. Mevcut posta kutuları yerel olarak teslim edilecektir.",
|
||||
"relayhost_wrapped_tls_info": "Lütfen TLS ile örtülmüş portları <b> kullanmayın</b> (çoğu 465 portunda çalışır).<br>\nÖrtülmemiş port kullan ve STARTTLS üzerinden yayınla. TLS'yi zorlamak için bir TLS ilkesi \"TLS ilke eşlemeleri\" sayfası içinde oluşturulabilir.",
|
||||
"skipcrossduplicates": "Klasörler arasında yinelenen mesajları atlayın (ilk mesaj seçilir)",
|
||||
"target_address": "Adreslere git",
|
||||
"target_address_info": "<small>Tam e-posta adres(leri) girin ( virgülle ayırın).</small>",
|
||||
"target_domain": "Hedef alan adı",
|
||||
"timeout1": "Uzak ana bilgisayara bağlantısı zaman aşımına uğradı",
|
||||
"timeout2": "Yerel ana bilgisayara bağlantı zaman aşımına uğradı"
|
||||
},
|
||||
"admin": {
|
||||
"action": "İşlem",
|
||||
"add_forwarding_host": "Yönlendirme sunucusu ekle",
|
||||
"add_transport": "İletim ekle",
|
||||
"admin_details": "Yönetici detaylarını düzenle",
|
||||
"admin_domains": "Alan adı atamaları",
|
||||
"add_domain_admin": "Alan adı yöneticisi ekle",
|
||||
"api_info": "API üzerinde çalışmalar devam etmektedir. Belgeler <a href=\"/api\">/api</a>adresinde bulunabilir",
|
||||
"apps_name": "\"mailcow Uygulamaları\" adı",
|
||||
"authed_user": "Yetkili kullanıcı",
|
||||
"ban_list_info": "Aşağıdaki yasaklı IP'lerin listesine bakın: <b>ağ (kalan yasak süresi) - [işlemler]</b>.<br />Yasağı kaldırılmak üzere sıraya alınan IP'ler birkaç saniye içinde aktif yasak listesinden kaldırılacaktır.<br />Kırmızı etiketler, kara listeye alınarak aktif kalıcı yasakları gösterir.",
|
||||
"configuration": "Yapılandırma",
|
||||
"dkim_from_title": "Verilerin kopyalanacağı kaynak alan adı",
|
||||
"dkim_to": "Kime",
|
||||
"dkim_to_title": "Hedef alan ad(ları) üzerinde yazılacak",
|
||||
"dkim_domains_wo_keys": "Eksik anahtarları olan alan adlarını seçin",
|
||||
"domain": "Alan adı",
|
||||
"domain_admin": "Alan adı yöneticisi",
|
||||
"domain_admins": "Alan adı yöneticileri",
|
||||
"domain_s": "Alan ad(ları)",
|
||||
"duplicate": "Çift",
|
||||
"duplicate_dkim": "Çift DKIM kayıtları",
|
||||
"f2b_ban_time": "Yasaklama süresi (saniye)",
|
||||
"f2b_max_attempts": "Maksimum giriş denemesi",
|
||||
"f2b_retry_window": "Maksimum girişim için deneme pencere(leri)"
|
||||
}
|
||||
}
|
@@ -980,7 +980,8 @@
|
||||
"resource_modified": "Зміни поштового акаунту %s збережено",
|
||||
"settings_map_added": "Правило додано",
|
||||
"tls_policy_map_entry_deleted": "Політику TLS ID %s видалено",
|
||||
"verified_totp_login": "Авторизацію TOTP пройдено"
|
||||
"verified_totp_login": "Авторизацію TOTP пройдено",
|
||||
"domain_add_dkim_available": "Ключ DKIM вже існує"
|
||||
},
|
||||
"tfa": {
|
||||
"confirm": "Підтвердьте",
|
||||
|
@@ -176,15 +176,75 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
{% endfor %}
|
||||
|
||||
// Confirm TFA modal
|
||||
{% if pending_tfa_method %}
|
||||
{% if pending_tfa_methods %}
|
||||
$('#ConfirmTFAModal').modal({
|
||||
backdrop: 'static',
|
||||
keyboard: false
|
||||
});
|
||||
|
||||
|
||||
// validate Time based OTP tfa
|
||||
$("#pending_tfa_tab_totp").click(function(){
|
||||
$(".webauthn-authenticator-selection").removeClass("active");
|
||||
$("#collapseWebAuthnTFA").collapse('hide');
|
||||
|
||||
// select default if only one authenticator exists
|
||||
if ($('.totp-authenticator-selection').length == 1){
|
||||
$('.totp-authenticator-selection').addClass("active");
|
||||
var id = $('.totp-authenticator-selection').children('input').first().val();
|
||||
$("#totp_selected_id").val(id);
|
||||
$("#collapseTotpTFA").collapse('show');
|
||||
}
|
||||
});
|
||||
$(".totp-authenticator-selection").click(function(){
|
||||
$(".totp-authenticator-selection").removeClass("active");
|
||||
$(this).addClass("active");
|
||||
|
||||
var id = $(this).children('input').first().val();
|
||||
$("#totp_selected_id").val(id);
|
||||
|
||||
$("#collapseTotpTFA").collapse('show');
|
||||
});
|
||||
if ($('.totp-authenticator-selection').length == 1 &&
|
||||
$('#pending_tfa_tab_yubi_otp').length == 0 &&
|
||||
$('.webauthn-authenticator-selection').length == 0){
|
||||
|
||||
// select default if only one authenticator exists
|
||||
$('.totp-authenticator-selection').addClass("active");
|
||||
|
||||
var id = $('.totp-authenticator-selection').children('input').first().val();
|
||||
$("#totp_selected_id").val(id);
|
||||
|
||||
$("#collapseTotpTFA").collapse('show');
|
||||
setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 1000);
|
||||
}
|
||||
$('#pending_tfa_tab_totp').on('shown.bs.tab', function() {
|
||||
// autofocus
|
||||
setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200);
|
||||
});
|
||||
// validate Yubi OTP tfa
|
||||
if ($('.webauthn-authenticator-selection').length == 0){
|
||||
// autofocus
|
||||
setTimeout(function() { $("#collapseYubiTFA").find('input[name="token"]').focus(); }, 1000);
|
||||
}
|
||||
$('#pending_tfa_tab_yubi_otp').on('shown.bs.tab', function() {
|
||||
// autofocus
|
||||
$("#collapseYubiTFA").find('input[name="token"]').focus();
|
||||
});
|
||||
// validate WebAuthn tfa
|
||||
$('#start_webauthn_confirmation').click(function(){
|
||||
$('#webauthn_status_auth').html('<p><i class="bi bi-arrow-repeat icon-spin"></i> ' + lang_tfa.init_webauthn + '</p>');
|
||||
$("#pending_tfa_tab_webauthn").click(function(){
|
||||
$(".totp-authenticator-selection").removeClass("active");
|
||||
|
||||
$("#collapseTotpTFA").collapse('hide');
|
||||
});
|
||||
$(".webauthn-authenticator-selection").click(function(){
|
||||
$(".webauthn-authenticator-selection").removeClass("active");
|
||||
$(this).addClass("active");
|
||||
|
||||
var id = $(this).children('input').first().val();
|
||||
$("#webauthn_selected_id").val(id);
|
||||
|
||||
$("#collapseWebAuthnTFA").collapse('show');
|
||||
|
||||
$(this).find('input[name=token]').focus();
|
||||
if(document.getElementById("webauthn_auth_data") !== null) {
|
||||
@@ -198,30 +258,32 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
window.fetch("/api/v1/get/webauthn-tfa-get-args", {method:'GET',cache:'no-cache'}).then(response => {
|
||||
return response.json();
|
||||
}).then(json => {
|
||||
if (json.success === false) throw new Error();
|
||||
console.log(json);
|
||||
if (json.success === false) throw new Error();
|
||||
if (json.type === "error") throw new Error(json.msg);
|
||||
|
||||
recursiveBase64StrToArrayBuffer(json);
|
||||
return json;
|
||||
recursiveBase64StrToArrayBuffer(json);
|
||||
return json;
|
||||
}).then(getCredentialArgs => {
|
||||
// get credentials
|
||||
return navigator.credentials.get(getCredentialArgs);
|
||||
// get credentials
|
||||
return navigator.credentials.get(getCredentialArgs);
|
||||
}).then(cred => {
|
||||
return {
|
||||
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
||||
signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null
|
||||
};
|
||||
return {
|
||||
id: cred.rawId ? arrayBufferToBase64(cred.rawId) : null,
|
||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||
authenticatorData: cred.response.authenticatorData ? arrayBufferToBase64(cred.response.authenticatorData) : null,
|
||||
signature : cred.response.signature ? arrayBufferToBase64(cred.response.signature) : null
|
||||
};
|
||||
}).then(JSON.stringify).then(function(AuthenticatorAttestationResponse) {
|
||||
// send request by submit
|
||||
var form = document.getElementById('webauthn_auth_form');
|
||||
var auth = document.getElementById('webauthn_auth_data');
|
||||
auth.value = AuthenticatorAttestationResponse;
|
||||
form.submit();
|
||||
// send request by submit
|
||||
var form = document.getElementById('webauthn_auth_form');
|
||||
var auth = document.getElementById('webauthn_auth_data');
|
||||
auth.value = AuthenticatorAttestationResponse;
|
||||
form.submit();
|
||||
}).catch(function(err) {
|
||||
var webauthn_return_code = document.getElementById('webauthn_return_code');
|
||||
webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null;
|
||||
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
|
||||
var webauthn_return_code = document.getElementById('webauthn_return_code');
|
||||
webauthn_return_code.style.display = webauthn_return_code.style.display === 'none' ? '' : null;
|
||||
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -237,7 +299,9 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
}
|
||||
});
|
||||
});
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
// Validate FIDO2
|
||||
$("#fido2-login").click(function(){
|
||||
$('#fido2-alerts').html();
|
||||
@@ -358,11 +422,13 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
|
||||
$("#start_webauthn_register").click(() => {
|
||||
var key_id = document.getElementsByName('key_id')[1].value;
|
||||
var confirm_password = document.getElementsByName('confirm_password')[1].value;
|
||||
|
||||
// fetch WebAuthn create args
|
||||
window.fetch("/api/v1/get/webauthn-tfa-registration/{{ mailcow_cc_username|url_encode(true)|default('null') }}", {method:'GET',cache:'no-cache'}).then(response => {
|
||||
return response.json();
|
||||
}).then(json => {
|
||||
console.log(json);
|
||||
if (json.success === false) throw new Error(json.msg);
|
||||
recursiveBase64StrToArrayBuffer(json);
|
||||
|
||||
@@ -375,7 +441,8 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
clientDataJSON: cred.response.clientDataJSON ? arrayBufferToBase64(cred.response.clientDataJSON) : null,
|
||||
attestationObject: cred.response.attestationObject ? arrayBufferToBase64(cred.response.attestationObject) : null,
|
||||
key_id: key_id,
|
||||
tfa_method: "webauthn"
|
||||
tfa_method: "webauthn",
|
||||
confirm_password: confirm_password
|
||||
};
|
||||
}).then(JSON.stringify).then(AuthenticatorAttestationResponse => {
|
||||
// send request
|
||||
@@ -423,13 +490,20 @@ function recursiveBase64StrToArrayBuffer(obj) {
|
||||
{% if ui_texts.ui_footer %}
|
||||
<hr><span class="rot-enc">{{ ui_texts.ui_footer|rot13|raw }}</span>
|
||||
{% endif %}
|
||||
{% if mailcow_cc_username and mailcow_info.version_tag|default %}
|
||||
{% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "master" and mailcow_info.version_tag|default %}
|
||||
<span class="version">
|
||||
🐮 + 🐋 = 💕
|
||||
<a href="{{ mailcow_info.git_project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">
|
||||
Version: {{ mailcow_info.version_tag }}
|
||||
Version: <a href="{{ mailcow_info.git_project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">{{ mailcow_info.version_tag }}
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %}
|
||||
<span class="version">
|
||||
🛠️🐮 + 🐋 = 💕
|
||||
Nightly: <a href="{{ mailcow_info.git_project_url }}/commit/{{ mailcow_info.git_commit }}" target="_blank">{{ mailcow_info.version_tag }}
|
||||
</a><br>
|
||||
<span style="text-align:right;display:block;">Build: {{ mailcow_info.git_commit_date }}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
|
@@ -28,7 +28,7 @@
|
||||
<div class="col-sm-9 col-xs-7">
|
||||
<select id="selectTFA" class="selectpicker" title="{{ lang.tfa.select }}">
|
||||
<option value="yubi_otp">{{ lang.tfa.yubi_otp }}</option>
|
||||
<option value="u2f">{{ lang.tfa.u2f }}</option>
|
||||
<option value="webauthn">{{ lang.tfa.webauthn }}</option>
|
||||
<option value="totp">{{ lang.tfa.totp }}</option>
|
||||
<option value="none">{{ lang.tfa.none }}</option>
|
||||
</select>
|
||||
|
@@ -133,73 +133,163 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if pending_tfa_method %}
|
||||
{% if pending_tfa_methods %}
|
||||
<div class="modal fade" id="ConfirmTFAModal" tabindex="-1" role="dialog" aria-labelledby="ConfirmTFAModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span></button>
|
||||
<h3 class="modal-title">{{ lang.tfa[pending_tfa_method] }}</h3>
|
||||
<h3 class="modal-title">{{ lang.tfa.tfa }}</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if pending_tfa_method == 'yubi_otp' %}
|
||||
<form role="form" method="post">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>
|
||||
<input type="text" name="token" class="form-control" autocomplete="off" placeholder="Touch Yubikey" aria-describedby="yubi-addon">
|
||||
<input type="hidden" name="tfa_method" value="yubi_otp">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-sm btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if pending_tfa_method == 'totp' %}
|
||||
<form role="form" method="post">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" id="tfa-addon"><i class="bi bi-shield-lock-fill"></i></span>
|
||||
<input type="number" min="000000" max="999999" name="token" class="form-control" placeholder="123456" autocomplete="one-time-code" aria-describedby="tfa-addon">
|
||||
<input type="hidden" name="tfa_method" value="totp">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if pending_tfa_method == 'hotp' %}
|
||||
<div class="empty"></div>
|
||||
{% endif %}
|
||||
|
||||
<ul class="nav nav-tabs" id="tabContent">
|
||||
{% if pending_tfa_authmechs["webauthn"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
|
||||
<li class="active"><a href="#tfa_tab_webauthn" data-toggle="tab" id="pending_tfa_tab_webauthn"><i class="bi bi-fingerprint"></i> WebAuthn</a></li>
|
||||
{% endif %}
|
||||
|
||||
{% if pending_tfa_method == 'webauthn' %}
|
||||
<form role="form" method="post" id="webauthn_auth_form">
|
||||
<center>
|
||||
<div style="cursor:pointer" id="start_webauthn_confirmation">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24">
|
||||
<path d="M17.81 4.47c-.08 0-.16-.02-.23-.06C15.66 3.42 14 3 12.01 3c-1.98 0-3.86.47-5.57 1.41-.24.13-.54.04-.68-.2-.13-.24-.04-.55.2-.68C7.82 2.52 9.86 2 12.01 2c2.13 0 3.99.47 6.03 1.52.25.13.34.43.21.67-.09.18-.26.28-.44.28zM3.5 9.72c-.1 0-.2-.03-.29-.09-.23-.16-.28-.47-.12-.7.99-1.4 2.25-2.5 3.75-3.27C9.98 4.04 14 4.03 17.15 5.65c1.5.77 2.76 1.86 3.75 3.25.16.22.11.54-.12.7-.23.16-.54.11-.7-.12-.9-1.26-2.04-2.25-3.39-2.94-2.87-1.47-6.54-1.47-9.4.01-1.36.7-2.5 1.7-3.4 2.96-.08.14-.23.21-.39.21zm6.25 12.07c-.13 0-.26-.05-.35-.15-.87-.87-1.34-1.43-2.01-2.64-.69-1.23-1.05-2.73-1.05-4.34 0-2.97 2.54-5.39 5.66-5.39s5.66 2.42 5.66 5.39c0 .28-.22.5-.5.5s-.5-.22-.5-.5c0-2.42-2.09-4.39-4.66-4.39-2.57 0-4.66 1.97-4.66 4.39 0 1.44.32 2.77.93 3.85.64 1.15 1.08 1.64 1.85 2.42.19.2.19.51 0 .71-.11.1-.24.15-.37.15zm7.17-1.85c-1.19 0-2.24-.3-3.1-.89-1.49-1.01-2.38-2.65-2.38-4.39 0-.28.22-.5.5-.5s.5.22.5.5c0 1.41.72 2.74 1.94 3.56.71.48 1.54.71 2.54.71.24 0 .64-.03 1.04-.1.27-.05.53.13.58.41.05.27-.13.53-.41.58-.57.11-1.07.12-1.21.12zM14.91 22c-.04 0-.09-.01-.13-.02-1.59-.44-2.63-1.03-3.72-2.1-1.4-1.39-2.17-3.24-2.17-5.22 0-1.62 1.38-2.94 3.08-2.94 1.7 0 3.08 1.32 3.08 2.94 0 1.07.93 1.94 2.08 1.94s2.08-.87 2.08-1.94c0-3.77-3.25-6.83-7.25-6.83-2.84 0-5.44 1.58-6.61 4.03-.39.81-.59 1.76-.59 2.8 0 .78.07 2.01.67 3.61.1.26-.03.55-.29.64-.26.1-.55-.04-.64-.29-.49-1.31-.73-2.61-.73-3.96 0-1.2.23-2.29.68-3.24 1.33-2.79 4.28-4.6 7.51-4.6 4.55 0 8.25 3.51 8.25 7.83 0 1.62-1.38 2.94-3.08 2.94s-3.08-1.32-3.08-2.94c0-1.07-.93-1.94-2.08-1.94s-2.08.87-2.08 1.94c0 1.71.66 3.31 1.87 4.51.95.94 1.86 1.46 3.27 1.85.27.07.42.35.35.61-.05.23-.26.38-.47.38z"></path>
|
||||
</svg>
|
||||
<p>{{ lang.tfa.start_webauthn_validation }}</p>
|
||||
<hr>
|
||||
{% if pending_tfa_authmechs["yubi_otp"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
|
||||
<li class="tab-pane {% if pending_tfa_authmechs["yubi_otp"] %}active{% endif %}">
|
||||
<a href="#tfa_tab_yubi_otp" data-toggle="tab" id="pending_tfa_tab_yubi_otp"><i class="bi bi-usb-drive"></i> Yubi OTP</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if pending_tfa_authmechs["totp"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
|
||||
<li class="tab-pane {% if pending_tfa_authmechs["totp"] %}active{% endif %}">
|
||||
<a href="#tfa_tab_totp" data-toggle="tab" id="pending_tfa_tab_totp"><i class="bi bi-clock-history"></i> Time based OTP</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<!-- <li><a href="#tfa_tab_hotp" data-toggle="tab">HOTP</a></li> -->
|
||||
{% if pending_tfa_authmechs["u2f"] is defined %}
|
||||
<li class="active"><a href="#tfa_tab_u2f" data-toggle="tab"><i class="bi bi-x-octagon"></i> U2F</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
{% if pending_tfa_authmechs["webauthn"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
|
||||
<div role="tabpanel" class="tab-pane active" id="tfa_tab_webauthn">
|
||||
<div class="panel panel-default" style="margin-bottom: 0px;">
|
||||
<div class="panel-body">
|
||||
<form role="form" method="post" id="webauthn_auth_form">
|
||||
<legend>
|
||||
<i class="bi bi-shield-fill-check"></i>
|
||||
Authenticators
|
||||
</legend>
|
||||
<div class="list-group">
|
||||
{% for authenticator in pending_tfa_methods %}
|
||||
{% if authenticator["authmech"] == "webauthn" %}
|
||||
<a href="#" class="list-group-item webauthn-authenticator-selection">
|
||||
<i class="bi bi-key-fill" style="margin-right: 5px"></i>
|
||||
<span>{{ authenticator["key_id"] }}</span>
|
||||
<input type="hidden" value="{{ authenticator["id"] }}" /><br/>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="collapse pending-tfa-collapse" id="collapseWebAuthnTFA">
|
||||
<p id="webauthn_status_auth"><p><i class="bi bi-arrow-repeat icon-spin"></i> {{ lang.tfa.init_webauthn }}</p></p>
|
||||
<div class="alert alert-danger" style="display:none" id="webauthn_return_code"></div>
|
||||
</div>
|
||||
<input type="hidden" name="token" id="webauthn_auth_data"/>
|
||||
<input type="hidden" name="tfa_method" value="webauthn">
|
||||
<input type="hidden" name="verify_tfa_login"/><br/>
|
||||
<input type="hidden" name="id" id="webauthn_selected_id" /><br/>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
<p id="webauthn_status_auth"></p>
|
||||
<div class="alert alert-danger" style="display:none" id="webauthn_return_code"></div>
|
||||
<input type="hidden" name="token" id="webauthn_auth_data"/>
|
||||
<input type="hidden" name="tfa_method" value="webauthn">
|
||||
<input type="hidden" name="verify_tfa_login"/><br/>
|
||||
</form>
|
||||
{% endif %}
|
||||
{# leave this here to inform users that u2f is deprecated #}
|
||||
{% if pending_tfa_method == 'u2f' %}
|
||||
<form role="form" method="post" id="u2f_auth_form">
|
||||
<p>{{ lang.tfa.u2f_deprecated }}</p>
|
||||
<p><b>{{ lang.tfa.u2f_deprecated_important }}</b></p>
|
||||
<input type="hidden" name="token" value="destroy" />
|
||||
<input type="hidden" name="tfa_method" value="u2f">
|
||||
<input type="hidden" name="verify_tfa_login"/><br/>
|
||||
<button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if pending_tfa_authmechs["yubi_otp"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
|
||||
<div role="tabpanel" class="tab-pane {% if pending_tfa_authmechs["yubi_otp"] %}active{% endif %}" id="tfa_tab_yubi_otp">
|
||||
<div class="panel panel-default" style="margin-bottom: 0px;">
|
||||
<div class="panel-body">
|
||||
<form role="form" method="post">
|
||||
<legend>
|
||||
<i class="bi bi-shield-fill-check"></i>
|
||||
Authenticate
|
||||
</legend>
|
||||
<div class="collapse in pending-tfa-collapse" id="collapseYubiTFA">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>
|
||||
<input type="text" name="token" class="form-control" autocomplete="off" placeholder="Touch Yubikey" aria-describedby="yubi-addon">
|
||||
<input type="hidden" name="tfa_method" value="yubi_otp">
|
||||
<input type="hidden" name="id" id="yubi_selected_id" />
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-sm btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if pending_tfa_authmechs["totp"] is defined and pending_tfa_authmechs["u2f"] is not defined %}
|
||||
<div role="tabpanel" class="tab-pane {% if pending_tfa_authmechs["totp"] %}active{% endif %}" id="tfa_tab_totp">
|
||||
<div class="panel panel-default" style="margin-bottom: 0px;">
|
||||
<div class="panel-body">
|
||||
<form role="form" method="post">
|
||||
<legend>
|
||||
<i class="bi bi-shield-fill-check"></i>
|
||||
Authenticators
|
||||
</legend>
|
||||
<div class="list-group">
|
||||
{% for authenticator in pending_tfa_methods %}
|
||||
{% if authenticator["authmech"] == "totp" %}
|
||||
<a href="#" class="list-group-item totp-authenticator-selection">
|
||||
<i class="bi bi-key-fill" style="margin-right: 5px"></i>
|
||||
<span>{{ authenticator["key_id"] }}</span>
|
||||
<input type="hidden" value="{{ authenticator["id"] }}" />
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="collapse pending-tfa-collapse" id="collapseTotpTFA">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon" id="tfa-addon"><i class="bi bi-shield-lock-fill"></i></span>
|
||||
<input type="number" min="000000" max="999999" name="token" class="form-control" placeholder="123456" autocomplete="one-time-code" aria-describedby="tfa-addon">
|
||||
<input type="hidden" name="tfa_method" value="totp">
|
||||
<input type="hidden" name="id" id="totp_selected_id" /><br/>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-sm visible-xs-block visible-sm-inline visible-md-inline visible-lg-inline btn-default" type="submit" name="verify_tfa_login">{{ lang.login.login }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<!--
|
||||
<div role="tabpanel" class="tab-pane" id="tfa_tab_hotp">
|
||||
<div class="panel panel-default" style="margin-bottom: 0px;">
|
||||
<div class="panel-body">
|
||||
<div class="empty"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
{% if pending_tfa_authmechs["u2f"] is defined %}
|
||||
<div role="tabpanel" class="tab-pane active" id="tfa_tab_u2f">
|
||||
<div class="panel panel-default" style="margin-bottom: 0px;">
|
||||
<div class="panel-body">
|
||||
{# leave this here to inform users that u2f is deprecated #}
|
||||
<form role="form" method="post" id="u2f_auth_form">
|
||||
<div>
|
||||
<p>{{ lang.tfa.u2f_deprecated }}</p>
|
||||
<p><b>{{ lang.tfa.u2f_deprecated_important }}</b></p>
|
||||
<input type="hidden" name="token" value="destroy" />
|
||||
<input type="hidden" name="tfa_method" value="u2f">
|
||||
<input type="hidden" name="verify_tfa_login"/><br/>
|
||||
<button type="submit" class="btn btn-xs-lg btn-success" value="Login">{{ lang.login.login }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -435,11 +435,11 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="help-block">{{ lang.add.syncjob_hint }}</p>
|
||||
<form class="form-horizontal" data-cached-form="true" role="form" data-id="add_syncjob">
|
||||
<form class="form-horizontal" data-cached-form="false" role="form" data-id="add_syncjob">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-sm-2" for="username">{{ lang.add.username }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select data-live-search="true" name="username" required>
|
||||
<select data-live-search="true" name="username" title="{{ lang.add.select }}" required>
|
||||
{% for mailbox in mailboxes %}
|
||||
<option>{{ mailbox }}</option>
|
||||
{% endfor %}
|
||||
|
@@ -48,6 +48,27 @@
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
{# TFA #}
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-5 text-right">{{ lang.tfa.tfa }}:</div>
|
||||
<div class="col-sm-9 col-xs-7">
|
||||
<p id="tfa_pretty">{{ tfa_data.pretty }}</p>
|
||||
{% include 'tfa_keys.twig' %}
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-5 text-right">{{ lang.tfa.set_tfa }}:</div>
|
||||
<div class="col-sm-9 col-xs-7">
|
||||
<select data-style="btn btn-sm dropdown-toggle bs-placeholder btn-default" data-width="fit" id="selectTFA" class="selectpicker" title="{{ lang.tfa.select }}">
|
||||
<option value="yubi_otp">{{ lang.tfa.yubi_otp }}</option>
|
||||
<option value="webauthn">{{ lang.tfa.webauthn }}</option>
|
||||
<option value="totp">{{ lang.tfa.totp }}</option>
|
||||
<option value="none">{{ lang.tfa.none }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
{# FIDO2 #}
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-12 text-right text-xs-left">
|
||||
|
@@ -76,6 +76,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
|
||||
'acl_json' => json_encode($_SESSION['acl']),
|
||||
'user_spam_score' => mailbox('get', 'spam_score', $username),
|
||||
'tfa_data' => $tfa_data,
|
||||
'tfa_id' => @$_SESSION['tfa_id'],
|
||||
'fido2_data' => $fido2_data,
|
||||
'mailboxdata' => $mailboxdata,
|
||||
'clientconfigstr' => $clientconfigstr,
|
||||
@@ -90,8 +91,7 @@ elseif (isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == '
|
||||
'number_of_app_passwords' => $number_of_app_passwords,
|
||||
];
|
||||
}
|
||||
|
||||
if (!isset($_SESSION['mailcow_cc_role']) && $_SESSION['mailcow_cc_role'] == 'admin') {
|
||||
else {
|
||||
header('Location: /');
|
||||
exit();
|
||||
}
|
||||
|
Reference in New Issue
Block a user