@ -0,0 +1,2 @@ | |||
DOMAIN=domain.com | |||
REVERSE_DNS=com.domain |
@ -0,0 +1 @@ | |||
secure |
@ -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 |
@ -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: |
@ -0,0 +1,7 @@ | |||
#!/bin/bash | |||
cd /haraka | |||
node haraka.js & | |||
cd /wildduck | |||
node server.js & | |||
cd /wildduck-mta | |||
npm start --production |
@ -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 |
@ -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" {} + |
@ -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 |
@ -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 |
@ -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> |
@ -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> |
@ -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. "Jaan Tamm"" 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. "username" or "user.name"" 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. "supersecret"" 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> |
@ -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">×</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> |
@ -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> |
@ -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> |
@ -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> </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> | |||
| |||
</th> | |||
<th> | |||
Identity name | |||
</th> | |||
<th> | |||
Alias Address | |||
</th> | |||
<th> | |||
Created | |||
</th> | |||
<th> | |||
| |||
</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">×</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> |
@ -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> </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> |
@ -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> </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> |
@ -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> </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> |
@ -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> |
@ -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> | |||
@ -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> |
@ -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> </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. "Jaan Tamm"" 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> |
@ -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> </p> | |||
<p> | |||
Future feature | |||
</p> | |||
</div> | |||
</div> | |||
</div> | |||
</div> |
@ -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> </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">×</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">×</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> |
@ -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> |
@ -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> </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> | |||
| |||
</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">×</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> |
@ -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> |
@ -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> |
@ -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> </p> | |||
<table class="table table-responsive"> | |||
<thead> | |||
<tr> | |||
<th> | |||
Environment | |||
</th> | |||
<th> | |||
Action | |||
</th> | |||
<th> | |||
Result | |||