Development
This commit is contained in:
parent
8a8e947fcf
commit
450c0081f7
9 changed files with 59 additions and 28 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Blueprint, request, session, redirect, flash
|
from flask import Blueprint, request, session, redirect, flash
|
||||||
import bcrypt, secrets, smtplib
|
import secrets, smtplib
|
||||||
|
import settings
|
||||||
import time
|
import time
|
||||||
from email.message import EmailMessage
|
from email.message import EmailMessage
|
||||||
import auth
|
import auth
|
||||||
|
|
@ -78,8 +79,7 @@ def form_create():
|
||||||
flash('This account is already set up. Please log in instead.', 'error')
|
flash('This account is already set up. Please log in instead.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
salt = bcrypt.gensalt()
|
hashed = settings.hash_password(password)
|
||||||
hashed = bcrypt.hashpw(password.encode('utf-8'), salt).decode('utf-8')
|
|
||||||
code = f'{secrets.randbelow(1000000):06d}'
|
code = f'{secrets.randbelow(1000000):06d}'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Blueprint, request, session, redirect, flash
|
from flask import Blueprint, request, session, redirect, flash
|
||||||
import bcrypt
|
|
||||||
import auth
|
import auth
|
||||||
import config_utils
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
|
|
@ -29,7 +28,7 @@ def form_login():
|
||||||
if email != settings.get_initial_manager_email() or not stored_hash:
|
if email != settings.get_initial_manager_email() or not stored_hash:
|
||||||
flash('Invalid email address or password.', 'error')
|
flash('Invalid email address or password.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
if not bcrypt.checkpw(password.encode('utf-8'), stored_hash.encode('utf-8')):
|
if not settings.verify_password(password, stored_hash):
|
||||||
flash('Invalid email address or password.', 'error')
|
flash('Invalid email address or password.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
session.clear()
|
session.clear()
|
||||||
|
|
@ -76,7 +75,7 @@ def form_login():
|
||||||
flash('Account setup is not complete. Please use Create Account to set your password first.', 'error')
|
flash('Account setup is not complete. Please use Create Account to set your password first.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
if not bcrypt.checkpw(password.encode('utf-8'), account['hashed_password'].encode('utf-8')):
|
if not settings.verify_password(password, account['hashed_password']):
|
||||||
flash('Invalid email address or password.', 'error')
|
flash('Invalid email address or password.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import bcrypt
|
|
||||||
from cryptography.fernet import Fernet
|
from cryptography.fernet import Fernet
|
||||||
from flask import Blueprint, request, redirect, flash
|
from flask import Blueprint, request, redirect, flash
|
||||||
import auth
|
import auth
|
||||||
|
|
@ -19,12 +18,12 @@ USER_TYPE_CAPTIVE = 0
|
||||||
USER_TYPE_SUPPLICANT = 1
|
USER_TYPE_SUPPLICANT = 1
|
||||||
|
|
||||||
DIGEST_CYPHERTEXT_FERNET = 0
|
DIGEST_CYPHERTEXT_FERNET = 0
|
||||||
DIGEST_HASH_BCRYPT = 2
|
DIGEST_HASH_SCRYPT = 2
|
||||||
|
|
||||||
VALID_USER_TYPES = {USER_TYPE_CAPTIVE, USER_TYPE_SUPPLICANT}
|
VALID_USER_TYPES = {USER_TYPE_CAPTIVE, USER_TYPE_SUPPLICANT}
|
||||||
|
|
||||||
HASH_FOR_USER_TYPE = {
|
HASH_FOR_USER_TYPE = {
|
||||||
USER_TYPE_CAPTIVE: DIGEST_HASH_BCRYPT,
|
USER_TYPE_CAPTIVE: DIGEST_HASH_SCRYPT,
|
||||||
USER_TYPE_SUPPLICANT: DIGEST_CYPHERTEXT_FERNET,
|
USER_TYPE_SUPPLICANT: DIGEST_CYPHERTEXT_FERNET,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,8 +98,8 @@ def _get_by_index(conn, row_index):
|
||||||
def _hash_password(plaintext, digest_type):
|
def _hash_password(plaintext, digest_type):
|
||||||
if digest_type == DIGEST_CYPHERTEXT_FERNET:
|
if digest_type == DIGEST_CYPHERTEXT_FERNET:
|
||||||
return encrypt_password(plaintext)
|
return encrypt_password(plaintext)
|
||||||
if digest_type == DIGEST_HASH_BCRYPT:
|
if digest_type == DIGEST_HASH_SCRYPT:
|
||||||
return bcrypt.hashpw(plaintext.encode(), bcrypt.gensalt()).decode()
|
return settings.hash_password(plaintext)
|
||||||
raise ValueError(f"Unknown digest_type: {digest_type}")
|
raise ValueError(f"Unknown digest_type: {digest_type}")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from flask import Blueprint, request, session, redirect, flash
|
from flask import Blueprint, request, session, redirect, flash
|
||||||
import bcrypt
|
|
||||||
import auth
|
import auth
|
||||||
import config_utils
|
import config_utils
|
||||||
import sanitize
|
import sanitize
|
||||||
|
|
@ -131,11 +130,11 @@ def changepassword_save():
|
||||||
flash('New password must be at least 8 characters.', 'error')
|
flash('New password must be at least 8 characters.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
hashed = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
hashed = settings.hash_password(new_password)
|
||||||
|
|
||||||
if settings.is_single_user():
|
if settings.is_single_user():
|
||||||
stored_hash = settings.get_initial_manager_password_hash()
|
stored_hash = settings.get_initial_manager_password_hash()
|
||||||
if not stored_hash or not bcrypt.checkpw(current_password.encode('utf-8'), stored_hash.encode('utf-8')):
|
if not stored_hash or not settings.verify_password(current_password, stored_hash):
|
||||||
flash('Current password is incorrect.', 'error')
|
flash('Current password is incorrect.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
try:
|
try:
|
||||||
|
|
@ -156,7 +155,7 @@ def changepassword_save():
|
||||||
flash('Account not found. Please log in again.', 'error')
|
flash('Account not found. Please log in again.', 'error')
|
||||||
return redirect('/accountlogin')
|
return redirect('/accountlogin')
|
||||||
|
|
||||||
if not bcrypt.checkpw(current_password.encode('utf-8'), account['hashed_password'].encode('utf-8')):
|
if not settings.verify_password(current_password, account['hashed_password']):
|
||||||
flash('Current password is incorrect.', 'error')
|
flash('Current password is incorrect.', 'error')
|
||||||
return redirect(f'/{_PAGE}')
|
return redirect(f'/{_PAGE}')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,28 @@ def get_credentials_key():
|
||||||
return base64.urlsafe_b64encode(raw)
|
return base64.urlsafe_b64encode(raw)
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(plaintext):
|
||||||
|
import hashlib, os as _os
|
||||||
|
salt = _os.urandom(16)
|
||||||
|
h = hashlib.scrypt(plaintext.encode('utf-8'), salt=salt, n=16384, r=8, p=1, dklen=32)
|
||||||
|
return f'scrypt:16384:8:1:{salt.hex()}:{h.hex()}'
|
||||||
|
|
||||||
|
|
||||||
|
def verify_password(plaintext, stored):
|
||||||
|
import hashlib, hmac
|
||||||
|
try:
|
||||||
|
tag, n, r, p, salt_hex, hash_hex = stored.split(':')
|
||||||
|
if tag != 'scrypt':
|
||||||
|
return False
|
||||||
|
salt = bytes.fromhex(salt_hex)
|
||||||
|
expected = bytes.fromhex(hash_hex)
|
||||||
|
h = hashlib.scrypt(plaintext.encode('utf-8'), salt=salt,
|
||||||
|
n=int(n), r=int(r), p=int(p), dklen=len(expected))
|
||||||
|
return hmac.compare_digest(h, expected)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_smtp_config():
|
def get_smtp_config():
|
||||||
"""Return SMTP settings from app_config.json, falling back to env vars."""
|
"""Return SMTP settings from app_config.json, falling back to env vars."""
|
||||||
cfg = _load_app_config()
|
cfg = _load_app_config()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
flask
|
flask
|
||||||
bcrypt
|
|
||||||
cryptography
|
cryptography
|
||||||
manuf
|
manuf
|
||||||
mac-vendor-lookup
|
mac-vendor-lookup
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,29 @@
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import bcrypt
|
|
||||||
from flask import Blueprint, request, redirect
|
from flask import Blueprint, request, redirect
|
||||||
import config_utils
|
import config_utils
|
||||||
|
|
||||||
CREDENTIALS_DB = f'{config_utils.CONFIGS_DIR}/.client-credentials'
|
CREDENTIALS_DB = f'{config_utils.CONFIGS_DIR}/.client-credentials'
|
||||||
USER_TYPE_CAPTIVE = 0
|
USER_TYPE_CAPTIVE = 0
|
||||||
DIGEST_HASH_BCRYPT = 2
|
DIGEST_HASH_SCRYPT = 2
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_scrypt(plaintext, stored):
|
||||||
|
try:
|
||||||
|
tag, n, r, p, salt_hex, hash_hex = stored.split(':')
|
||||||
|
if tag != 'scrypt':
|
||||||
|
return False
|
||||||
|
salt = bytes.fromhex(salt_hex)
|
||||||
|
expected = bytes.fromhex(hash_hex)
|
||||||
|
h = hashlib.scrypt(plaintext.encode('utf-8'), salt=salt,
|
||||||
|
n=int(n), r=int(r), p=int(p), dklen=len(expected))
|
||||||
|
return hmac.compare_digest(h, expected)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
bp = Blueprint('portal', __name__)
|
bp = Blueprint('portal', __name__)
|
||||||
|
|
||||||
|
|
@ -85,11 +100,8 @@ def _verify_credential(username, password, vlan_name):
|
||||||
return False
|
return False
|
||||||
if row['expires_seconds'] > 0 and (row['date_set'] + row['expires_seconds']) < now:
|
if row['expires_seconds'] > 0 and (row['date_set'] + row['expires_seconds']) < now:
|
||||||
return False
|
return False
|
||||||
if row['digest_type'] == DIGEST_HASH_BCRYPT:
|
if row['digest_type'] == DIGEST_HASH_SCRYPT:
|
||||||
try:
|
return _verify_scrypt(password, row['password'])
|
||||||
return bcrypt.checkpw(password.encode(), row['password'].encode())
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1 @@
|
||||||
flask
|
flask
|
||||||
bcrypt
|
|
||||||
|
|
|
||||||
|
|
@ -373,7 +373,7 @@ def setup_docker_compose(reuse_config=False):
|
||||||
print()
|
print()
|
||||||
while True:
|
while True:
|
||||||
import getpass as _gp
|
import getpass as _gp
|
||||||
pw = _gp.getpass(f" Password for {manager_email}: ")
|
pw = _gp.getpass(f" Create password for {manager_email}: ")
|
||||||
pw2 = _gp.getpass(f" Confirm password: ")
|
pw2 = _gp.getpass(f" Confirm password: ")
|
||||||
if pw != pw2:
|
if pw != pw2:
|
||||||
print(" Passwords do not match. Try again.")
|
print(" Passwords do not match. Try again.")
|
||||||
|
|
@ -382,8 +382,10 @@ def setup_docker_compose(reuse_config=False):
|
||||||
print(" Password must be at least 8 characters.")
|
print(" Password must be at least 8 characters.")
|
||||||
continue
|
continue
|
||||||
break
|
break
|
||||||
import bcrypt as _bcrypt
|
import hashlib as _hashlib, os as _os
|
||||||
pw_hash = _bcrypt.hashpw(pw.encode('utf-8'), _bcrypt.gensalt()).decode('utf-8')
|
_salt = _os.urandom(16)
|
||||||
|
_h = _hashlib.scrypt(pw.encode('utf-8'), salt=_salt, n=16384, r=8, p=1, dklen=32)
|
||||||
|
pw_hash = f'scrypt:16384:8:1:{_salt.hex()}:{_h.hex()}'
|
||||||
app_config = {
|
app_config = {
|
||||||
"initial_manager_email": manager_email,
|
"initial_manager_email": manager_email,
|
||||||
"credentials_key": credentials_key,
|
"credentials_key": credentials_key,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue