Browse Source

initial commit

master
ale 3 years ago
commit
8bcc2dbbcd
161 changed files with 7253 additions and 0 deletions
  1. +2
    -0
      .env
  2. +1
    -0
      .gitignore
  3. +20
    -0
      README.md
  4. +78
    -0
      docker-compose.yml
  5. +7
    -0
      entrypoint.sh
  6. +0
    -0
      secure/.gitignore
  7. +9
    -0
      start.sh
  8. +13
    -0
      webmail/Dockerfile
  9. +78
    -0
      webmail/config/default.toml
  10. +28
    -0
      webmail/config/development.toml
  11. +85
    -0
      webmail/views/account/2fa.hbs
  12. +142
    -0
      webmail/views/account/autoreply.hbs
  13. +140
    -0
      webmail/views/account/create.hbs
  14. +88
    -0
      webmail/views/account/filters.hbs
  15. +18
    -0
      webmail/views/account/filters/create.hbs
  16. +18
    -0
      webmail/views/account/filters/edit.hbs
  17. +131
    -0
      webmail/views/account/identities.hbs
  18. +46
    -0
      webmail/views/account/identities/create.hbs
  19. +46
    -0
      webmail/views/account/identities/edit.hbs
  20. +103
    -0
      webmail/views/account/index.hbs
  21. +68
    -0
      webmail/views/account/login.hbs
  22. +52
    -0
      webmail/views/account/login.hbs.new
  23. +68
    -0
      webmail/views/account/login.hbs.orig
  24. +98
    -0
      webmail/views/account/profile.hbs
  25. +26
    -0
      webmail/views/account/security.hbs
  26. +131
    -0
      webmail/views/account/security/2fa.hbs
  27. +35
    -0
      webmail/views/account/security/asp.hbs
  28. +151
    -0
      webmail/views/account/security/asps.hbs
  29. +34
    -0
      webmail/views/account/security/enable-totp.hbs
  30. +45
    -0
      webmail/views/account/security/enable-u2f.hbs
  31. +115
    -0
      webmail/views/account/security/events.hbs
  32. +98
    -0
      webmail/views/account/security/gpg.hbs
  33. +67
    -0
      webmail/views/account/security/password.hbs
  34. +43
    -0
      webmail/views/account/update-password.hbs
  35. +7
    -0
      webmail/views/error.hbs
  36. +160
    -0
      webmail/views/help.hbs
  37. +3
    -0
      webmail/views/index.hbs
  38. +50
    -0
      webmail/views/layout-popup.hbs
  39. +67
    -0
      webmail/views/layout-webmail.hbs
  40. +30
    -0
      webmail/views/layout.hbs
  41. +3
    -0
      webmail/views/partials/accountmenu.hbs
  42. +150
    -0
      webmail/views/partials/filter.hbs
  43. +18
    -0
      webmail/views/partials/header.hbs
  44. +68
    -0
      webmail/views/partials/identity.hbs
  45. +44
    -0
      webmail/views/partials/mailbox.hbs
  46. +51
    -0
      webmail/views/partials/messagerow.hbs
  47. +85
    -0
      webmail/views/partials/navbar.hbs
  48. +71
    -0
      webmail/views/partials/scripts.hbs
  49. +8
    -0
      webmail/views/partials/searchfield.hbs
  50. +5
    -0
      webmail/views/partials/securitymenu.hbs
  51. +57
    -0
      webmail/views/partials/tos.hbs
  52. +13
    -0
      webmail/views/tos.hbs
  53. +134
    -0
      webmail/views/webmail/audit.hbs
  54. +46
    -0
      webmail/views/webmail/create.hbs
  55. +754
    -0
      webmail/views/webmail/index.hbs
  56. +75
    -0
      webmail/views/webmail/mailbox.hbs
  57. +630
    -0
      webmail/views/webmail/message.hbs
  58. +382
    -0
      webmail/views/webmail/send.hbs
  59. +24
    -0
      wildduck/Dockerfile
  60. +0
    -0
      wildduck/haraka/attachments/.gitignore
  61. +13
    -0
      wildduck/haraka/config/access.domains
  62. +6
    -0
      wildduck/haraka/config/access.ini
  63. +14
    -0
      wildduck/haraka/config/aliases
  64. +2
    -0
      wildduck/haraka/config/attachment.ctype.regex
  65. +1
    -0
      wildduck/haraka/config/attachment.filename.regex
  66. +5
    -0
      wildduck/haraka/config/auth_flat_file.ini
  67. +7
    -0
      wildduck/haraka/config/auth_vpopmaild.ini
  68. +5
    -0
      wildduck/haraka/config/avg.ini
  69. +18
    -0
      wildduck/haraka/config/bounce.ini
  70. +5
    -0
      wildduck/haraka/config/clamd.ini
  71. +62
    -0
      wildduck/haraka/config/data.headers.ini
  72. +202
    -0
      wildduck/haraka/config/data.uribl.excludes
  73. +37
    -0
      wildduck/haraka/config/data.uribl.ini
  74. +1
    -0
      wildduck/haraka/config/databytes
  75. +7
    -0
      wildduck/haraka/config/delay_deny.ini
  76. +8
    -0
      wildduck/haraka/config/dhparams.pem
  77. +5
    -0
      wildduck/haraka/config/dkim_sign.ini
  78. +23
    -0
      wildduck/haraka/config/dnsbl.ini
  79. +11
    -0
      wildduck/haraka/config/early_talker.ini
  80. +14
    -0
      wildduck/haraka/config/fcrdns.ini
  81. +43
    -0
      wildduck/haraka/config/greylist.ini
  82. +57
    -0
      wildduck/haraka/config/helo.checks.ini
  83. +2
    -0
      wildduck/haraka/config/host_list
  84. +6
    -0
      wildduck/haraka/config/host_list_regex
  85. +7
    -0
      wildduck/haraka/config/http.ini
  86. +1
    -0
      wildduck/haraka/config/internalcmd_key
  87. +7
    -0
      wildduck/haraka/config/lmtp.ini
  88. +11
    -0
      wildduck/haraka/config/log.ini
  89. +12
    -0
      wildduck/haraka/config/lookup_rdns.strict.ini
  90. +1
    -0
      wildduck/haraka/config/lookup_rdns.strict.timeout
  91. +1
    -0
      wildduck/haraka/config/lookup_rdns.strict.whitelist
  92. +5
    -0
      wildduck/haraka/config/lookup_rdns.strict.whitelist_regex
  93. +4
    -0
      wildduck/haraka/config/mail_from.is_resolvable.ini
  94. +1
    -0
      wildduck/haraka/config/max_unrecognized_commands
  95. +1
    -0
      wildduck/haraka/config/me
  96. +18
    -0
      wildduck/haraka/config/messagesniffer.ini
  97. +30
    -0
      wildduck/haraka/config/mongodb.ini
  98. +15
    -0
      wildduck/haraka/config/outbound.bounce_message
  99. +36
    -0
      wildduck/haraka/config/outbound.bounce_message_html
  100. +106
    -0
      wildduck/haraka/config/outbound.bounce_message_image

+ 2
- 0
.env View File

@ -0,0 +1,2 @@
DOMAIN=domain.com
REVERSE_DNS=com.domain

+ 1
- 0
.gitignore View File

@ -0,0 +1 @@
secure

+ 20
- 0
README.md View File

@ -0,0 +1,20 @@
# Haraka-Wildduck Docker Mail Server
## Instalar
- Ejecutar: `./start.sh` <dominio> - Configura los certificados en la carpeta ./secure
- Editar `.env` con el valor del dominio
## Arrancar
- Instalar `docker` y `docker-compose`
- Ejecutar: docker-compose up -d
- Abrir el navegador http://webmail:3000
## Persistencia
- Ejecutar: docker cp mongo:/data/db ./mongodb && chown -R 999.999 ./mongodb
- Ejecutar: docker cp redis:/data ./redis && chown -R 999.999 ./redis
- Descomentar las lineas del archivo `docker-compose.yml`
- Ejecutar: docker-compose down && docker-compose up -d
### Licencia
- MIT

+ 78
- 0
docker-compose.yml View File

@ -0,0 +1,78 @@
version: '3'
services:
wildduck:
build:
context: ./wildduck
args:
DOMAIN: $DOMAIN
REVERSE_DNS: $REVERSE_DNS
hostname: wildduck
container_name: wildduck
restart: always
entrypoint:
- /bin/bash
- /entrypoint.sh
ports:
- "25:25/tcp"
- "465:465/tcp"
- "993:993/tcp"
expose:
- 80
- 12080
volumes:
- ./entrypoint.sh:/entrypoint.sh:ro
- ./secure:/secure:ro
- ./wildduck/haraka/attachments:/home/node/Haraka/attachments
depends_on:
- redis
- mongo
networks:
mailnet:
redis:
image: redis
hostname: redis
container_name: redis
restart: always
# volumes:
# - ./redis:/data
expose:
- 6379
networks:
mailnet:
mongo:
image: mongo
hostname: mongo
container_name: mongo
restart: always
# volumes:
# - ./mongodb:/data/db
expose:
- 27017
networks:
mailnet:
webmail:
build:
context: ./webmail
args:
DOMAIN: $DOMAIN
hostname: webmail
container_name: webmail
restart: always
entrypoint:
- node
- server.js
- --config=/webmail/config/default.toml
ports:
- "3000:3000/tcp"
depends_on:
- redis
- mongo
- wildduck
networks:
mailnet:
networks:
mailnet:

+ 7
- 0
entrypoint.sh View File

@ -0,0 +1,7 @@
#!/bin/bash
cd /haraka
node haraka.js &
cd /wildduck
node server.js &
cd /wildduck-mta
npm start --production

+ 0
- 0
secure/.gitignore View File


+ 9
- 0
start.sh View File

@ -0,0 +1,9 @@
#!/bin/bash
if [[ ! -z $1 ]]; then
sudo apt install -y opendkim-tools openssl
rm -f ./secure/*
openssl req -newkey rsa:2048 -nodes -keyout ./secure/privkey.pem -x509 -days 365 -subj "/CN=$1" -out ./secure/fullchain.pem
opendkim-genkey -b 2048 -h rsa-sha256 -r -s dkim -d "$1" --directory ./secure
else
echo -e "- Necesita indicar un dominio\nEjemplo: ./start.sh domain.com"
fi

+ 13
- 0
webmail/Dockerfile View File

@ -0,0 +1,13 @@
FROM node:8-slim
ARG DOMAIN
RUN apt update && apt -y install git python make
RUN git clone https://github.com/nodemailer/wildduck-webmail /webmail
WORKDIR /webmail
RUN git checkout 5c54625a8b192823184ba7f5da41f3414e76db94
COPY ./config /webmail/config
COPY ./views /webmail/views
RUN chown node.node -R /webmail
USER node
RUN npm install
RUN npm run bowerdeps
RUN find ./config ./views -type f -exec sed -i "s/{{DOMAIN}}/$DOMAIN/g" {} +

+ 78
- 0
webmail/config/default.toml View File

@ -0,0 +1,78 @@
name="webmail.{{DOMAIN}}"
title="Wild Duck Mail"
[service]
# email domain for new users
domain="{{DOMAIN}}"
# default quotas for new users
quota=1024
recipients=2000
forwards=2000
identities=10
allowIdentityEdit=true
allowJoin=true
enableSpecial=true # if true the allow creating addresses with special usernames
# allowed domains for new addresses
domains=["{{DOMAIN}}"]
[api]
# url="http://127.0.0.1:8080"
# accessToken=""
url="http://wildduck"
accessToken="notoken"
[dbs]
# mongodb connection string for the main database
mongo="mongodb://mongo:27017/wildduck"
# redis connection string for Express sessions
redis="redis://redis:6379/3"
[www]
host="webmail"
port=3000
proxy=true
postsize="5MB"
log="dev"
secret="secret times"
secure=false
# baseurl="https://webmail.{{DOMAIN}}"
listSize=20
[recaptcha]
enabled=false
siteKey=""
secretKey=""
[totp]
# Issuer name for TOTP, defaults to config.name
issuer=false
# once setup do not change as it would invalidate all existing 2fa sessions
secret="a secret cat"
[u2f]
# set to false if not using HTTPS
enabled=false
# must be https url or use default
#appId="https://127.0.0.1:8080"
appId="https://webmail.{{DOMAIN}}"
[log]
level="silly"
mail=true
[setup]
# these values are shown in the configuration help page
[setup.imap]
hostname="imap.{{DOMAIN}}"
secure=true
port=993
[setup.pop3]
hostname="imap.{{DOMAIN}}"
secure=true
port=993
[setup.smtp]
hostname="smtp.{{DOMAIN}}"
secure=true
port=465

+ 28
- 0
webmail/config/development.toml View File

@ -0,0 +1,28 @@
name="Wild Duck Mail Temporary"
[service]
# email domain for new users
domain="local.tahvel.info"
# default quotas for new users
quota=102400
# allowed domains for new addresses
domains=["local.tahvel.info", "example.com"]
[www]
proxy=true
baseurl="https://local.tahvel.info"
[setup]
# these values are shown in the configuration help page
[setup.imap]
hostname="local.tahvel.info"
secure=true
port=993
[setup.pop3]
hostname="local.tahvel.info"
secure=true
port=995
[setup.smtp]
hostname="local.tahvel.info"
secure=false
port=587

+ 85
- 0
webmail/views/account/2fa.hbs View File

@ -0,0 +1,85 @@
<input type="hidden" id="_csrf" value="{{csrfToken}}" />
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Two factor authentication</h3>
</div>
<div class="panel-body">
<div id="show-u2f" style="display:{{#if enabledU2f}}block{{else}}none{{/if}}">
<div style="margin:10px 0;">
<div id="u2f-wait">
<img src="/images/u2f-wait.png" />
</div>
<div id="u2f-fail" style="display: none">
<img src="/images/u2f-fail.png" />
</div>
<div id="u2f-success" style="display: none">
<img src="/images/u2f-success.png" />
</div>
</div>
<p id="message">
Initializing...
</p>
<div>
<div class="pull-right">
<a href="#" id="enable-totp">or use security code</a>
</div>
<a href="/account/logout"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
<div id="show-totp" style="display:{{#if enabledU2f}}none{{else}}block{{/if}}">
<form id="totp-form">
<p>
Open your authentication app and enter the code to log in
</p>
<div class="form-group" id="totp-token-field">
<label for="token">Security code</label>
<input type="number" class="form-control" id="token" placeholder="6 digit code" required autofocus>
<span class="help-block" id="totp-token-error" style="display: none"></span>
</div>
<div>
<div class="pull-right">
<button type="submit" id="totp-btn" class="btn btn-success" data-loading-text="Checking..."><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Verify</button>
</div>
<a href="/account/logout"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
<div class="clearfix"></div>
</form>
</div>
<div class="checkbox form-footer">
<label>
<input type="checkbox" id="remember2fa"> Trust this device for 30 days
</label>
</div>
</div>
</div>
<script>
// U2F support must be checked *before* loading /u2f-api.js
{{! only check if user has enabled u2f, otherwise no reason to use it }}
var U2FSUPPORT = {{#if enabledU2f}}typeof u2f === 'object' || typeof chrome === 'object'{{else}}false{{/if}};
</script>
<script src="/login-key-handler.js"></script>
<script src="/u2f-api.js"></script>
<script src="/2fa.js"></script>

+ 142
- 0
webmail/views/account/autoreply.hbs View File

@ -0,0 +1,142 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-calendar" aria-hidden="true"></span> Autoreply</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post" action="/account/autoreply">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="start" name="start" value="{{#if values.start}}{{values.start}}{{/if}}" />
<input type="hidden" id="end" name="end" value="{{#if values.end}}{{values.end}}{{/if}}" />
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Autoreply settings</h3>
</div>
<div class="panel-body">
<p>
If enabled then an autoreply message is sent to all incoming messages. If a contact sends multiple messages then the autoreply is sent at most once in every four hours.
</p>
<div class="radio">
<label>
<input type="radio" name="status" value="false" {{#unless values.status}}checked{{/unless}}>
Autoreply is {{#unless values.status}}<span class="label label-default">disabled</span>{{else}}disabled{{/unless}}
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="status" value="true" {{#if values.status}}checked{{/if}}>
Autoreply is {{#if values.status}}<span class="label label-info">enabled</span>{{else}}enabled{{/if}}
</label>
</div>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" name="name" value="{{values.name}}" placeholder="Sender name in the autoreply From: header">
</div>
<div class="form-group">
<label for="subject">Subject</label>
<input type="text" class="form-control" id="subject" name="subject" value="{{values.subject}}" placeholder="Leave blank to use the default subject">
</div>
<div class="form-group">
<label for="daterange">Time</label>
<div class="form-group-sm daterangeElm" style="position: relative">
<input type="text" id="daterange" class="form-control" value="">
<i class="glyphicon glyphicon-calendar fa fa-calendar" style="position: absolute; bottom: 10px; right: 24px; top: auto; cursor: pointer;"></i>
</div>
</div>
<div class="form-group">
<label for="message">Message</label>
<textarea class="form-control" name="text" value="{{values.text}}" rows="3">{{values.text}}</textarea>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Update</button>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
$('#daterange').daterangepicker({
"showDropdowns": true,
"showISOWeekNumbers": true,
"timePicker": true,
"timePicker24Hour": true,
"autoApply": true,
"autoUpdateInput": false,
"locale": {
"direction": "ltr",
"format": "DD/MM/YYYY HH:mm",
"separator": " - ",
"applyLabel": "Select",
"cancelLabel": "Cancel",
"fromLabel": "From",
"toLabel": "To",
"customRangeLabel": "Custom",
"daysOfWeek": [
"Su",
"Mo",
"Tu",
"We",
"Th",
"Fr",
"Sa"
],
"monthNames": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
"firstDay": 1
},
{{#if values.start}}
"startDate": moment("{{values.start}}").format('DD/MM/YYYY HH:mm'),
{{/if}}
{{#if values.end}}
"endDate": moment("{{values.end}}").format('DD/MM/YYYY HH:mm'),
{{/if}}
"alwaysShowCalendars": true
}, function(start, end, label) {
document.getElementById('start').value = start.valueOf();
document.getElementById('end').value = end.valueOf();
document.getElementById('daterange').value = start.format('DD/MM/YYYY HH:mm') + ' โ€“ ' + end.format('DD/MM/YYYY HH:mm');
});
$('.daterangeElm i').click(function() {
$(this).parent().find('input').click();
});
{{#if values.start}}
document.getElementById('daterange').value = moment("{{values.start}}").format('DD/MM/YYYY HH:mm') + ' โ€“ ' + moment("{{values.end}}").format('DD/MM/YYYY HH:mm');
{{/if}}
});
</script>

+ 140
- 0
webmail/views/account/create.hbs View File

@ -0,0 +1,140 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Create new account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post" id="create-form" action="/account/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="domain" name="domain" value="{{values.domain}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Account information</h3>
</div>
<div class="panel-body">
<p>
Enter your account details. Account username is allowed to include latin characters only. Activated accounts can add extra identity addresses that may contain unicode characters as well.
</p>
<div class="row">
<div class="col-md-6">
<div class="form-group{{#if errors.name}} has-error{{/if}}">
<label for="name">Your name</label>
<input type="text" class="form-control" name="name" id="name" placeholder="eg. &quot;Jaan Tamm&quot;" value="{{values.name}}" required>
{{#if errors.name}}
<span class="help-block">{{errors.name}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.username}} has-error{{/if}}">
<label for="name">Your new address (also the username)</label>
<div class="input-group">
<input type="text" class="form-control" name="username" id="username" placeholder="eg. &quot;username&quot; or &quot;user.name&quot;" value="{{values.username}}" pattern="^[A-Za-z0-9][A-Za-z\-\.0-9]*$" required>
<span class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@<span class="selected-domain" style="text-transform: lowercase;">{{values.domain}}</span><span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
{{#each domains}}
<li><a href="#" class="change-domain-link" data-domain="{{this}}">{{this}}</a></li>
{{/each}}
</ul>
</span>
</div>
{{#if errors.username}}
<span class="help-block">{{errors.username}}</span>
{{else}}
<span class="help-block">Latin letters and numbers only. Dots and dashes are allowed as separators.<span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">Your password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="eg. &quot;supersecret&quot;" required>
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password2">Repeat password</label>
<input type="password" class="form-control" name="password2" id="password2" placeholder="repeat password" required>
</div>
<div class="form-group{{#if errors.remember}} has-error{{/if}}">
<div class="checkbox">
<label>
<input type="checkbox" name="remember" required> Agree to <a href="/tos" target="_blank">terms of service</a>
{{#if errors.remember}}
<span class="help-block">{{errors.remember}}</span>
{{/if}}
</label>
</div>
</div>
</div>
</div>
<div class="form-group">
{{#if recaptcha}}
<button
class="g-recaptcha btn btn-success"
data-sitekey="{{recaptcha}}"
data-callback="onCreateSubmit">
<span class="glyphicon glyphicon-user" aria-hidden="true"></span>Create new account
</button>
{{else}}
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-user" aria-hidden="true"></span>Create new account</button>
{{/if}}
</div>
</div>
</div>
</form>
</div>
</div>
{{#if recaptcha}}
<script src='https://www.google.com/recaptcha/api.js'></script>
<script>
function onCreateSubmit(token) {
document.getElementById("create-form").submit();
}
</script>
{{/if}}
<script>
document.addEventListener('DOMContentLoaded', function() {
var domainElement = document.getElementById('domain');
var updateDomain = function(e,elm){
e.preventDefault();
var domain = elm.dataset.domain;
if(domain){
domainElement.value = domain;
document.querySelector('.selected-domain').textContent = domain;
}
};
var setupDomainButton = function(elm){
elm.addEventListener('click', function(e){updateDomain(e,elm)}, false);
elm.addEventListener('touch', function(e){updateDomain(e,elm)}, false);
}
var domainLinks = document.querySelectorAll('.change-domain-link');
for(var i=0, len = domainLinks.length; i<len; i++){
setupDomainButton(domainLinks[i]);
}
}, false);
</script>

+ 88
- 0
webmail/views/account/filters.hbs View File

@ -0,0 +1,88 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Filters</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Mail Filters</h3></div>
<div class="panel-body">
<p>Here you can create and modify filters that apply on all incoming messages.</p>
</div>
<table class="table table-responsive">
<tbody>
{{#if filters}}
{{#each filters}}
<tr>
<th>
{{index}}
</th>
<td>
<div class="pull-right">
<a href="/account/filters/edit?id={{id}}" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit</a>
<button type="button" data-filter="{{id}}" class="btn btn-danger btn-xs" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
</div>
<div>
Query: <strong>{{query}}</strong><br /> Action: {{action}}
</div>
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="3">
There are no filters created
</td>
</tr>
{{/if}}
</tbody>
</table>
<div class="panel-body">
<div class="form-group">
<a href="/account/filters/create" class="btn btn-success"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Add new filter</a>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete filter</h4>
</div>
<div class="modal-body">
Are you sure you want to permanently delete selected filter?
</div>
<div class="modal-footer">
<form method="post" action="/account/filters/delete">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="delete-form-filter" name="id" value="">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, delete</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
$('#deleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var filter = button.data('filter'); // Extract info from data-* attributes
document.getElementById('delete-form-filter').value = filter;
});
}, false);
</script>

+ 18
- 0
webmail/views/account/filters/create.hbs View File

@ -0,0 +1,18 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Create filter</h1>
</div>
</div>
<form method="post" action="/account/filters/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
{{> filter}}
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Create filter</button>
<a href="/account/filters" class="btn btn-warning"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span> Cancel</a>
</div>
</form>

+ 18
- 0
webmail/views/account/filters/edit.hbs View File

@ -0,0 +1,18 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> Updated filter</h1>
</div>
</div>
<form method="post" action="/account/filters/edit">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{values.id}}">
{{> filter}}
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-filter" aria-hidden="true"></span> Update filter</button>
<a href="/account/filters" class="btn btn-warning"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span> Cancel</a>
</div>
</form>

+ 131
- 0
webmail/views/account/identities.hbs View File

@ -0,0 +1,131 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Manage identities</h3></div>
<div class="panel-body">
<p>Here you can add and modify alias addresses for your account. Aliases act just like your main address. You can not send out emails from identities that you do not own.</p>
</div>
<table class="table table-responsive">
<thead>
<th>
&nbsp;
</th>
<th>
Identity name
</th>
<th>
Alias Address
</th>
<th>
Created
</th>
<th>
&nbsp;
</th>
</thead>
<tbody>
{{#each identities}}
<tr class="{{#if main}}identity-main{{/if}}">
<th>
{{index}}
</th>
<td>
{{#if name}}
{{name}}
{{else}}
<em>โ€“</em>
{{/if}}
</td>
<td>
{{#if main}}
{{address}} <span>(default)</span>
{{else}}
{{address}}
{{/if}}
</td>
<td class="datestring" title="{{created}}">
{{created}}
</td>
<td class="text-right">
{{#if ../canEdit}}
<a href="/account/identities/edit?id={{id}}" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit</a>
{{/if}}
<button class="btn btn-danger btn-xs" data-address="{{address}}" {{#if main}}disabled{{else}}data-identity="{{id}}" data-toggle="modal" data-target="#deleteModal"{{/if}}><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
</td>
</tr>
{{/each}}
</tbody>
</table>
<div class="panel-body">
<div class="form-group">
{{#if canCreate}}
<a href="/account/identities/create" class="btn btn-success"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> Add new address</a>
{{else}}
<p class="text-muted">
Maximum amount of identities created
</p>
{{/if}}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete address</h4>
</div>
<div class="modal-body">
Are you sure you want to permanently delete <strong id="delete-form-identity-val">this address</strong>?
</div>
<div class="modal-footer">
<form method="post" action="/account/identities/delete">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="delete-form-identity" name="id" value="">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, delete</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
$('#deleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var identity = button.data('identity'); // Extract info from data-* attributes
document.getElementById('delete-form-identity').value = identity;
document.getElementById('delete-form-identity-val').textContent = button.data('address');
});
}, false);
</script>

+ 46
- 0
webmail/views/account/identities/create.hbs View File

@ -0,0 +1,46 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<form method="post" action="/account/identities/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="domain" name="domain" value="{{values.domain}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Identity information</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
{{> identity}}
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> Add new address</button>
<a href="/account/identities" class="btn btn-warning"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

+ 46
- 0
webmail/views/account/identities/edit.hbs View File

@ -0,0 +1,46 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<form method="post" action="/account/identities/edit">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="id" value="{{values.id}}">
<input type="hidden" id="domain" name="domain" value="{{values.domain}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Identity information</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
{{> identity}}
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-list-alt" aria-hidden="true"></span> Edit address</button>
<a href="/account/identities" class="btn btn-warning"><span class="glyphicon glyphicon-menu-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

+ 103
- 0
webmail/views/account/index.hbs View File

@ -0,0 +1,103 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<div class="row">
<div class="col-md-8">
<div class="form-group">
<label class="control-label">Address</label>
<div class="input-group">
<p class="form-control-static">
<a href="mailto:{{address}}">{{address}}</a>
</p>
</div>
</div>
<div class="form-group ">
<div class="input-group ">
<button type="button" id="reg-proto" class="btn btn-primary btn-xs"><span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> Register {{serviceName}} as default webmail handler</button>
</div>
</div>
<div class="form-group ">
<label class="control-label ">Quota</label>
<div class="input-group ">
<p class="form-control-static ">
Used <strong>{{storageUsed}}</strong> of <strong>{{quota}}</strong>
</p>
</div>
</div>
<div class="progress ">
<div class="progress-bar " role="progressbar " aria-valuenow=" {{storageOverview}} " aria-valuemin="0 " aria-valuemax="100 " style="min-width: 2em; ">
{{storageOverview}}%
</div>
</div>
<div class="form-group ">
<label class="control-label ">Messages sent</label>
<div class="input-group ">
<p class="form-control-static ">
Sent <strong>{{recipientsSent}}</strong> messages, daily allowed quota <strong>{{recipients}}</strong> messages
</p>
</div>
</div>
<div class="progress ">
<div class="progress-bar " role="progressbar " aria-valuenow=" {{recipientsOverview}} " aria-valuemin="0 " aria-valuemax="100 " style="min-width: 2em; ">
{{recipientsOverview}}%
</div>
</div>
<div class="form-group ">
<label class="control-label ">Forwarded messages</label>
<div class="input-group ">
<p class="form-control-static ">
Forwarded <strong>{{forwardsSent}}</strong> messages, daily allowed quota <strong>{{forwards}}</strong> messages
</p>
</div>
</div>
<div class="progress ">
<div class="progress-bar " role="progressbar " aria-valuenow=" {{forwardsOverview}} " aria-valuemin="0 " aria-valuemax="100 " style="min-width: 2em; ">
{{forwardsOverview}}%
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<input type="hidden" id="service-name" value="{{serviceName}}" />
<script>
function registerMailClient(){
var origin = window.location.origin;
if (!origin) {
origin= window.location.protocol + '//' + window.location.hostname + (window.location.port ? (':' + window.location.port) : '');
}
navigator.registerProtocolHandler('mailto', origin + '/webmail/send?to=%s', document.getElementById('service-name').value);
}
document.getElementById('reg-proto').addEventListener('click', registerMailClient, false);
</script>

+ 68
- 0
webmail/views/account/login.hbs View File

@ -0,0 +1,68 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Log in</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post" action="/account/login">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="_2faToken" id="_2faToken" value="">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Account information</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="form-group{{#if errors.username}} has-error{{/if}}">
<label class="control-label" for="username">Username</label>
<input type="text" class="form-control lowercase" name="username" id="username" placeholder="username" value="{{values.username}}" required>
{{#if errors.username}}
<span class="help-block">{{errors.username}}{{#if errors.username_action}} โ€“ <a href="{{errors.username_action.target}}">{{errors.username_action.title}}</a>{{/if}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">Your password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="v3rys3cret" required>
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember me
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-log-in" aria-hidden="true"></span> Log in</button>
</div>
</div>
</div>
</form>
</div>
</div>
<script src="/login-key-handler.js"></script>
<script>
loginKeyHandler.setup(
document.getElementById('username'),
document.getElementById('_2faToken'),
'2fa'
);
</script>

+ 52
- 0
webmail/views/account/login.hbs.new View File

@ -0,0 +1,52 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Log in <small>(Autoconfig with <a href="https://www.mozilla.org/thunderbird/" target="_blank">thunderbird</a>)</small></h1>
</div>
</div>
<div class="row">
<div class="col-md-6">
<form method="post" action="/account/login">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<fieldset>
<div class="form-group{{#if errors.username}} has-error{{/if}}">
<label class="control-label" for="username">Username</label>
<div class="input-group">
<input type="text" class="form-control lowercase" name="username" id="username" placeholder="username" value="{{values.username}}" pattern="^[A-Za-z0-9][A-Za-z\-\.0-9]*[A-Za-z0-9]$" required title="Valid email address user">
<span class="input-group-addon">@{{serviceDomain}}</span>
</div>
{{#if errors.username}}
<span class="help-block">{{errors.username}}{{#if errors.username_action}} โ€“ <a href="{{errors.username_action.target}}">{{errors.username_action.title}}</a>{{/if}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">Password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="v3rys3cret" required>
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember me
</label>
</div>
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-success">Log in</button>
</div>
</form>
</div>
</div>

+ 68
- 0
webmail/views/account/login.hbs.orig View File

@ -0,0 +1,68 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Log in</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<form method="post" action="/account/login">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="_2faToken" id="_2faToken" value="">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Account information</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-6">
<div class="form-group{{#if errors.username}} has-error{{/if}}">
<label class="control-label" for="username">Username</label>
<input type="text" class="form-control lowercase" name="username" id="username" placeholder="username" value="{{values.username}}" required>
{{#if errors.username}}
<span class="help-block">{{errors.username}}{{#if errors.username_action}} โ€“ <a href="{{errors.username_action.target}}">{{errors.username_action.title}}</a>{{/if}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.password}} has-error{{/if}}">
<label for="password">Your password</label>
<input type="password" class="form-control" name="password" id="password" placeholder="v3rys3cret" required>
{{#if errors.password}}
<span class="help-block">{{errors.password}}</span>
{{/if}}
</div>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" name="remember"> Remember me
</label>
</div>
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-log-in" aria-hidden="true"></span> Log in</button>
</div>
</div>
</div>
</form>
</div>
</div>
<script src="/login-key-handler.js"></script>
<script>
loginKeyHandler.setup(
document.getElementById('username'),
document.getElementById('_2faToken'),
'2fa'
);
</script>

+ 98
- 0
webmail/views/account/profile.hbs View File

@ -0,0 +1,98 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-user" aria-hidden="true"></span> Account</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> accountmenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<form method="post" action="/account/profile">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">General</h3>
</div>
<div class="panel-body">
<div class="form-group">
<label class="control-label">Username</label>
<div class="input-group">
<p class="form-control-static">{{values.username}}</p>
</div>
</div>
<div class="form-group{{#if errors.name}} has-error{{/if}}">
<label for="name">Your name</label>
<input type="text" class="form-control" name="name" id="name" placeholder="eg. &quot;Jaan Tamm&quot;" value="{{values.name}}">
{{#if errors.name}}
<span class="help-block">{{errors.name}}</span>
{{/if}}
</div>
<div class="form-group{{#if errors.spamLevel}} has-error{{/if}}">
<label for="name">Spam detection level</label>
<select class="form-control" name="spamLevel">
<option value="">
-- Select --
</option>
{{#each spamLevels}}
<option value="{{value}}" {{#if selected}}selected{{/if}}>
{{description}}
</option>
{{/each}}
</select>
{{#if errors.spamLevel}}
<span class="help-block">{{errors.spamLevel}}</span>
{{/if}}
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Message forwarding</h3>
</div>
<div class="panel-body">
<p>
Leave the following fields blank if you do not wish to forward all incoming emails
</p>
<div class="form-group{{#if errors.targets}} has-error{{/if}}">
<label for="targets">Forward incoming messages to:</label>
<input type="text" class="form-control" name="targets" id="targets" placeholder="user@example.com" value="{{values.targets}}">
{{#if errors.targets}}
<span class="help-block">{{errors.targets}}</span>
{{/if}}
<span class="help-block">Use comma separated list of addresses for multiple recipients</span>
</div>
</div>
</div>
</fieldset>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-wrench" aria-hidden="true"></span> Update</button>
</div>
</form>
</div>
</div>
</div>
</div>

+ 26
- 0
webmail/views/account/security.hbs View File

@ -0,0 +1,26 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<p>
Future feature
</p>
</div>
</div>
</div>
</div>

+ 131
- 0
webmail/views/account/security/2fa.hbs View File

@ -0,0 +1,131 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Two factor authentication</h3>
</div>
<div class="panel-body">
<p>
If two-factor authentication is enabled then you will be required to enter a code from an authenticator app when logging in.
TOTP compatible authenticator app like Google Authenticator is needed to use two-factor authentication.
</p>
<p>
<a href='https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&pcampaignid=MKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1' style="display:inline-block;overflow:hidden;background:url(/images/en_badge_web_generic.png) no-repeat;width:135px;height:40px;background-size:contain;background-position: center;" target="_blank"></a>
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8" style="display:inline-block;overflow:hidden;background:url(//linkmaker.itunes.apple.com/assets/shared/badges/en-us/appstore-lrg.svg) no-repeat;width:135px;height:40px;background-size:contain;" target="_blank"></a>
</p>
<p>
External applications can not access IMAP, POP3 ja SMTP using the account password if two-factor authentication is enabled. <a href="/account/security/asps">Application specific passwords</a> must be generated instead for these applications.
</p>
</div>
<table class="table table-responsive">
<tr>
<td>
{{#if enabled2fa}}
Two factor authentication is <span class="label label-success"><span class="glyphicon glyphicon-qrcode" aria-hidden="true"></span> Enabled</span>
{{else}}
Two factor authentication is <span class="label label-default"><span class="glyphicon glyphicon-qrcode" aria-hidden="true"></span> Disabled</span>
{{/if}}
</td>
<td class="text-right">
{{#if enabled2fa}}
<button type="button" class="btn btn-danger btn-xs" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-minus-sign" aria-hidden="true"></span> Disable</button>
{{else}}
<form method="post" id="enable-2fa" action="/account/security/2fa/enable-totp">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="submit" class="btn btn-success btn-xs"><span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> Enable</button>
</form>
{{/if}}
</td>
</tr>
{{#if enabled2fa}}
<tr>
<td>
{{#if enabledU2f}}
U2F security key is <span class="label label-success"><span class="glyphicon glyphicon-flash" aria-hidden="true"></span> Enabled</span>
{{else}}
U2F security key is <span class="label label-default"><span class="glyphicon glyphicon-flash" aria-hidden="true"></span> Disabled</span>
{{/if}}
</td>
<td class="text-right">
{{#if enabledU2f}}
<button type="button" class="btn btn-danger btn-xs" data-toggle="modal" data-target="#revokeModal"><span class="glyphicon glyphicon-minus-sign" aria-hidden="true"></span> Disable</button>
{{else}}
<form method="post" id="enable-u2f" action="/account/security/2fa/enable-u2f">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="submit" class="btn btn-success btn-xs"><span class="glyphicon glyphicon-plus-sign" aria-hidden="true"></span> Enable</button>
</form>
{{/if}}
</td>
</tr>
{{/if}}
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Disable 2FA</h4>
</div>
<div class="modal-body">
Are you sure you want to disable two factor authentication?
</div>
<div class="modal-footer">
<form method="post" action="/account/security/2fa/disable-totp">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, disable</button>
</form>
</div>
</div>
</div>
</div>
<div class="modal" id="revokeModal" tabindex="-1" role="dialog" aria-labelledby="revokeModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="revokeModalLabel">Revoke key</h4>
</div>
<div class="modal-body">
Are you sure you want to revoke U2F security key?
</div>
<div class="modal-footer">
<form method="post" action="/account/security/2fa/disable-u2f">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, revoke</button>
</form>
</div>
</div>
</div>
</div>

+ 35
- 0
webmail/views/account/security/asp.hbs View File

@ -0,0 +1,35 @@
<form method="post" id="generate-autoconfig" action="/account/autoconfig">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" name="password" value="{{password}}">
</form>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Application specific password</h3>
</div>
<div class="panel-body">
<p>
Use the generated password in external application for IMAP, POP3 or SMTP
</p>
<p>
<strong>{{description}}</strong>
</p>
<p class="lead bg-info text-center">
{{passwordFormatted}}
</p>
<p>
For OSX and iOS you can download configuration profile to auto-configure your email application
</p>
<p>
<div class="pull-right">
<a href="data:application/x-apple-aspen-config;base64,{{mobileconfig}}" download="{{user.username}}.mobileconfig" class="btn btn-info btn-xs"><span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> OSX / iOS</a>
</div>
<a href="/account/security/asps"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Go back</a>
</p>
</div>
</div>

+ 151
- 0
webmail/views/account/security/asps.hbs View File

@ -0,0 +1,151 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Application specific passwords</h3></div>
<div class="panel-body">
<p>Here are listed passwords generated for specific applications. If the password is leaked then delete it and generate a new one.</p>
<p>
Application Specific Passwords must be used for external applications if two factor authentication is enabled.
</p>
</div>
<table class="table table-responsive">
<thead>
<tr>
<th>
#
</th>
<th>
Description
</th>
<th>
Created
</th>
<th>
Used
</th>
<th>
&nbsp;
</th>
</tr>
</thead>
<tbody>
{{#if asps}}
{{#each asps}}
<tr>
<th>
{{index}}
</th>
<td>
{{description}}
</td>
<td class="datestring" title="{{created}}">
{{created}}
</td>
<td>
{{#if lastUse.time}}
<a href="/account/security/events?event={{lastUse.event}}"><span class="datestring" title="{{lastUse.time}}">{{lastUse.time}}</span></a>
{{else}}
never
{{/if}}
</td>
<td>
<div class="pull-right">
<button type="button" data-asp="{{id}}" class="btn btn-danger btn-xs" data-toggle="modal" data-target="#deleteModal"><span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Delete</button>
</div>
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="4">
No application specific passwords generated
</td>
</tr>
{{/if}}
</tbody>
</table>
</div>
<form method="post" action="/account/security/asps/create">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<fieldset>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Create new application specific password</h3>
</div>
<div class="panel-body">
<div class="form-group{{#if errors.description}} has-error{{/if}}">
<label for="description">Application description</label>
<input type="text" class="form-control" name="description" id="description" placeholder="Password for Outlook ..." required>
{{#if errors.description}}
<span class="help-block">{{errors.description}}</span>
{{/if}}
</div>
<div class="form-group">
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Generate password</button>
</div>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
<!-- Modal -->
<div class="modal" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="deleteModalLabel">Delete password</h4>
</div>
<div class="modal-body">
Are you sure you want to permanently delete Application Specific Password?
</div>
<div class="modal-footer">
<form method="post" action="/account/security/asps/delete">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<input type="hidden" id="delete-form-asp" name="id" value="">
<button type="button" class="btn btn-default" data-dismiss="modal">No, cancel</button>
<button type="submit" class="btn btn-danger bulk-delete-confirm">Yes, delete</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
$('#deleteModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget); // Button that triggered the modal
var asp = button.data('asp'); // Extract info from data-* attributes
document.getElementById('delete-form-asp').value = asp;
});
}, false);
</script>

+ 34
- 0
webmail/views/account/security/enable-totp.hbs View File

@ -0,0 +1,34 @@
<form id="totp-form">
<input type="hidden" id="_csrf" value="{{csrfToken}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Two factor authentication</h3>
</div>
<div class="panel-body">
<p>
Scan the code with an authenticator app and enter resulting security code below to verify
</p>
<p class="lead text-center">
<img src="{{imageUrl}}" style="width: 200px;" width="200">
</p>
<div class="form-group" id="totp-token-field">
<label for="token">Security code</label>
<input type="number" class="form-control" id="token" placeholder="6 digit code" required autofocus>
<span class="help-block" id="totp-token-error" style="display: none"></span>
</div>
<div>
<div class="pull-right">
<button type="submit" id="totp-btn" class="btn btn-success" data-loading-text="Checking..."><span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Verify</button>
</div>
<a href="/account/security"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
</div>
</form>
<script src="/enable-totp.js"></script>

+ 45
- 0
webmail/views/account/security/enable-u2f.hbs View File

@ -0,0 +1,45 @@
<input type="hidden" id="_csrf" value="{{csrfToken}}">
<input id="version" type="hidden" id="version" value="{{u2fRegRequest.version}}">
<input id="appId" type="hidden" id="appId" value="{{u2fRegRequest.appId}}">
<input id="challenge" type="hidden" id="challenge" value="{{u2fRegRequest.challenge}}">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Two factor authentication</h3>
</div>
<div class="panel-body">
<div style="margin:10px 0;">
<div id="u2f-wait">
<img src="/images/u2f-wait.png" />
</div>
<div id="u2f-fail" style="display: none">
<img src="/images/u2f-fail.png" />
</div>
<div id="u2f-success" style="display: none">
<img src="/images/u2f-success.png" />
</div>
</div>
<p id="message">
Initializing...
</p>
<div>
<a href="/account/security"><span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> Cancel</a>
</div>
</div>
</div>
<script>
// U2F support must be checked *before* loading /u2f-api.js
var U2FSUPPORT = typeof u2f === 'object' || typeof chrome === 'object';
</script>
<script src="/u2f-api.js"></script>
<script src="/enable-u2f.js"></script>

+ 115
- 0
webmail/views/account/security/events.hbs View File

@ -0,0 +1,115 @@
<div class="row">
<div class="col-md-12">
<h1><span class="glyphicon glyphicon-lock" aria-hidden="true"></span> Security</h1>
</div>
</div>
<div class="row">
<div class="col-md-12">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
{{> securitymenu}}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="overview">
<p>&nbsp;</p>
<table class="table table-responsive">
<thead>
<tr>
<th>
Environment
</th>
<th>
Action
</th>
<th>
Result