mirror of
https://github.com/Mailu/Mailu.git
synced 2025-03-05 14:55:20 +02:00
v1
This commit is contained in:
parent
62c919da09
commit
afbaabd8cd
@ -3,6 +3,56 @@ require('./app.css');
|
||||
import logo from './mailu.png';
|
||||
import modules from "./*.json";
|
||||
|
||||
// Inspired from https://github.com/mehdibo/hibp-js/blob/master/hibp.js
|
||||
function sha1(string){
|
||||
var buffer = new TextEncoder("utf-8").encode(string);
|
||||
return crypto.subtle.digest("SHA-1", buffer).then(function (buffer) {
|
||||
// Get the hex code
|
||||
var hexCodes = [];
|
||||
var view = new DataView(buffer);
|
||||
for (var i = 0; i < view.byteLength; i += 4) {
|
||||
// Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
|
||||
var value = view.getUint32(i)
|
||||
// toString(16) will give the hex representation of the number without padding
|
||||
var stringValue = value.toString(16)
|
||||
// We use concatenation and slice for padding
|
||||
var padding = '00000000'
|
||||
var paddedValue = (padding + stringValue).slice(-padding.length)
|
||||
hexCodes.push(paddedValue);
|
||||
}
|
||||
// Join all the hex strings into one
|
||||
return hexCodes.join("");
|
||||
});
|
||||
}
|
||||
|
||||
function hibpCheck(pwd){
|
||||
// We hash the pwd first
|
||||
sha1(pwd).then(function(hash){
|
||||
// We send the first 5 chars of the hash to hibp's API
|
||||
const req = new XMLHttpRequest();
|
||||
req.addEventListener("load", function(){
|
||||
// When we get back a response from the server
|
||||
// We create an array of lines and loop through them
|
||||
const resp = this.responseText.split('\n');
|
||||
const hashSub = hash.slice(5).toUpperCase();
|
||||
for(index in resp){
|
||||
// Check if the line matches the rest of the hash
|
||||
if(resp[index].substring(0, 35) == hashSub){
|
||||
var val = resp[index].split(":")[1]
|
||||
if (val > 0) {
|
||||
$("#pwned").value = val;
|
||||
}
|
||||
return; // If found no need to continue the loop
|
||||
}
|
||||
}
|
||||
$("#pwned").value = 0;
|
||||
});
|
||||
req.open('GET', 'https://api.pwnedpasswords.com/range/'+hash.substr(0, 5));
|
||||
req.setRequestHeader('Add-Padding', 'true');
|
||||
req.send();
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: conditionally (or lazy) load select2 and dataTable
|
||||
$('document').ready(function() {
|
||||
|
||||
@ -75,5 +125,12 @@ $('document').ready(function() {
|
||||
$('form :input').prop('disabled', true);
|
||||
}
|
||||
|
||||
if (window.isSecureContext) {
|
||||
$("#HIBPpw").change(function(){
|
||||
hibpCheck($("#HIBPpw").value);
|
||||
return true;
|
||||
})
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
@ -7,5 +7,6 @@ class LoginForm(flask_wtf.FlaskForm):
|
||||
csrf = False
|
||||
email = fields.StringField(_('E-mail'), [validators.Email(), validators.DataRequired()])
|
||||
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
||||
pwned = fields.HiddenField(label='',default=-1)
|
||||
submitWebmail = fields.SubmitField(_('Sign in'))
|
||||
submitAdmin = fields.SubmitField(_('Sign in'))
|
||||
|
@ -40,7 +40,10 @@ def login():
|
||||
flask_login.login_user(user)
|
||||
response = flask.redirect(destination)
|
||||
response.set_cookie('rate_limit', utils.limiter.device_cookie(username), max_age=31536000, path=flask.url_for('sso.login'), secure=app.config['SESSION_COOKIE_SECURE'], httponly=True)
|
||||
flask.current_app.logger.info(f'Login succeeded for {username} from {client_ip}.')
|
||||
flask.current_app.logger.info(f'Login succeeded for {username} from {client_ip} pwned={form.pwned.data}.')
|
||||
breaches = int(form.pwned.data)
|
||||
if breaches > 0:
|
||||
flask.flash(f"Your password appears in {breaches} data breaches! Please change it.", "error")
|
||||
return response
|
||||
else:
|
||||
utils.limiter.rate_limit_user(username, client_ip, device_cookie, device_cookie_username) if models.User.get(username) else utils.limiter.rate_limit_ip(client_ip)
|
||||
|
@ -59,6 +59,7 @@ class DomainSignupForm(flask_wtf.FlaskForm):
|
||||
localpart = fields.StringField(_('Initial admin'), [validators.DataRequired()])
|
||||
pw = fields.PasswordField(_('Admin password'), [validators.DataRequired()])
|
||||
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
||||
pwned = fields.HiddenField(label='',default=-1)
|
||||
captcha = flask_wtf.RecaptchaField()
|
||||
submit = fields.SubmitField(_('Create'))
|
||||
|
||||
@ -79,6 +80,7 @@ class UserForm(flask_wtf.FlaskForm):
|
||||
localpart = fields.StringField(_('E-mail'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
|
||||
pw = fields.PasswordField(_('Password'))
|
||||
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
||||
pwned = fields.HiddenField(label='',default=-1)
|
||||
quota_bytes = fields_.IntegerSliderField(_('Quota'), default=10**9)
|
||||
enable_imap = fields.BooleanField(_('Allow IMAP access'), default=True)
|
||||
enable_pop = fields.BooleanField(_('Allow POP3 access'), default=True)
|
||||
@ -92,6 +94,7 @@ class UserSignupForm(flask_wtf.FlaskForm):
|
||||
localpart = fields.StringField(_('Email address'), [validators.DataRequired(), validators.Regexp(LOCALPART_REGEX)])
|
||||
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
||||
pw2 = fields.PasswordField(_('Confirm password'), [validators.EqualTo('pw')])
|
||||
pwned = fields.HiddenField(label='',default=-1)
|
||||
submit = fields.SubmitField(_('Sign up'))
|
||||
|
||||
class UserSignupFormCaptcha(UserSignupForm):
|
||||
@ -111,6 +114,7 @@ class UserSettingsForm(flask_wtf.FlaskForm):
|
||||
class UserPasswordForm(flask_wtf.FlaskForm):
|
||||
pw = fields.PasswordField(_('Password'), [validators.DataRequired()])
|
||||
pw2 = fields.PasswordField(_('Password check'), [validators.DataRequired()])
|
||||
pwned = fields.HiddenField(label='',default=-1)
|
||||
submit = fields.SubmitField(_('Update password'))
|
||||
|
||||
|
||||
|
@ -93,6 +93,10 @@ def domain_signup(domain_name=None):
|
||||
del form.pw
|
||||
del form.pw2
|
||||
if form.validate_on_submit():
|
||||
breaches = int(form.pwned.data)
|
||||
if breaches > 0:
|
||||
flask.flash(f"This password appears in {breaches} data breaches! Please change it.", "error")
|
||||
return flask.render_template('domain/signup.html', form=form)
|
||||
conflicting_domain = models.Domain.query.get(form.name.data)
|
||||
conflicting_alternative = models.Alternative.query.get(form.name.data)
|
||||
conflicting_relay = models.Relay.query.get(form.name.data)
|
||||
|
@ -28,6 +28,11 @@ def user_create(domain_name):
|
||||
form.quota_bytes.validators = [
|
||||
wtforms.validators.NumberRange(max=domain.max_quota_bytes)]
|
||||
if form.validate_on_submit():
|
||||
breaches = int(form.pwned.data)
|
||||
if breaches > 0:
|
||||
flask.flash(f"This password appears in {breaches} data breaches! Please change it.", "error")
|
||||
return flask.render_template('user/create.html',
|
||||
domain=domain, form=form)
|
||||
if domain.has_email(form.localpart.data):
|
||||
flask.flash('Email is already used', 'error')
|
||||
else:
|
||||
@ -60,6 +65,11 @@ def user_edit(user_email):
|
||||
form.quota_bytes.validators = [
|
||||
wtforms.validators.NumberRange(max=max_quota_bytes)]
|
||||
if form.validate_on_submit():
|
||||
breaches = int(form.pwned.data)
|
||||
if breaches > 0:
|
||||
flask.flash(f"This password appears in {breaches} data breaches! Please change it.", "error")
|
||||
return flask.render_template('user/edit.html', form=form, user=user,
|
||||
domain=user.domain, max_quota_bytes=max_quota_bytes)
|
||||
form.populate_obj(user)
|
||||
if form.pw.data:
|
||||
user.set_password(form.pw.data)
|
||||
@ -119,6 +129,10 @@ def user_password(user_email):
|
||||
if form.pw.data != form.pw2.data:
|
||||
flask.flash('Passwords do not match', 'error')
|
||||
else:
|
||||
breaches = int(form.pwned.data)
|
||||
if breaches > 0:
|
||||
flask.flash(f"This password appears in {breaches} data breaches! Please change it.", "error")
|
||||
return flask.render_template('user/password.html', form=form, user=user)
|
||||
flask.session.regenerate()
|
||||
user.set_password(form.pw.data)
|
||||
models.db.session.commit()
|
||||
@ -170,6 +184,10 @@ def user_signup(domain_name=None):
|
||||
if domain.has_email(form.localpart.data) or models.Alias.resolve(form.localpart.data, domain_name):
|
||||
flask.flash('Email is already used', 'error')
|
||||
else:
|
||||
breaches = int(form.pwned.data)
|
||||
if breaches > 0:
|
||||
flask.flash(f"This password appears in {breaches} data breaches! Please change it.", "error")
|
||||
return flask.render_template('user/signup.html', domain=domain, form=form)
|
||||
flask.session.regenerate()
|
||||
user = models.User(domain=domain)
|
||||
form.populate_obj(user)
|
||||
|
Loading…
x
Reference in New Issue
Block a user