Development
This commit is contained in:
parent
5071f06624
commit
a3bab5ff1f
5 changed files with 53 additions and 50 deletions
|
|
@ -1,9 +1,8 @@
|
|||
import hashlib
|
||||
import sqlite3
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import bcrypt
|
||||
from cryptography.fernet import Fernet
|
||||
from flask import Blueprint, request, redirect, flash
|
||||
import auth
|
||||
import config_utils
|
||||
|
|
@ -20,19 +19,39 @@ USER_TYPE_CAPTIVE = 0
|
|||
USER_TYPE_SUPPLICANT = 1
|
||||
|
||||
HASH_CLEARTEXT = 0
|
||||
HASH_NT = 1
|
||||
HASH_BCRYPT = 2
|
||||
|
||||
VALID_USER_TYPES = {USER_TYPE_CAPTIVE, USER_TYPE_SUPPLICANT}
|
||||
VALID_HASH_TYPES = {HASH_CLEARTEXT, HASH_NT, HASH_BCRYPT}
|
||||
VALID_USER_TYPES = {USER_TYPE_CAPTIVE, USER_TYPE_SUPPLICANT}
|
||||
|
||||
# Compatible hash types per user type
|
||||
COMPATIBLE_HASHES = {
|
||||
USER_TYPE_CAPTIVE: {HASH_CLEARTEXT, HASH_BCRYPT},
|
||||
USER_TYPE_SUPPLICANT: {HASH_CLEARTEXT, HASH_NT},
|
||||
HASH_FOR_USER_TYPE = {
|
||||
USER_TYPE_CAPTIVE: HASH_BCRYPT,
|
||||
USER_TYPE_SUPPLICANT: HASH_CLEARTEXT,
|
||||
}
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# Encryption helpers (cleartext passwords only)
|
||||
# ===================================================================
|
||||
|
||||
_credentials_key = settings.get_credentials_key()
|
||||
_FERNET = Fernet(_credentials_key) if _credentials_key else None
|
||||
|
||||
|
||||
def encrypt_password(plaintext):
|
||||
if _FERNET is None:
|
||||
return plaintext
|
||||
return _FERNET.encrypt(plaintext.encode()).decode()
|
||||
|
||||
|
||||
def decrypt_password(stored):
|
||||
if _FERNET is None:
|
||||
return stored
|
||||
try:
|
||||
return _FERNET.decrypt(stored.encode()).decode()
|
||||
except Exception:
|
||||
return stored
|
||||
|
||||
|
||||
# ===================================================================
|
||||
# DB helpers
|
||||
# ===================================================================
|
||||
|
|
@ -78,12 +97,7 @@ def _get_by_index(conn, row_index):
|
|||
|
||||
def _hash_password(plaintext, hash_type):
|
||||
if hash_type == HASH_CLEARTEXT:
|
||||
return plaintext
|
||||
if hash_type == HASH_NT:
|
||||
try:
|
||||
return hashlib.new('md4', plaintext.encode('utf-16-le')).hexdigest()
|
||||
except ValueError:
|
||||
raise ValueError("NT-Password requires MD4 support. It may be disabled on this system's OpenSSL build.")
|
||||
return encrypt_password(plaintext)
|
||||
if hash_type == HASH_BCRYPT:
|
||||
return bcrypt.hashpw(plaintext.encode(), bcrypt.gensalt()).decode()
|
||||
raise ValueError(f"Unknown hash_type: {hash_type}")
|
||||
|
|
@ -144,20 +158,15 @@ def addedit():
|
|||
|
||||
try:
|
||||
user_type = int(request.form.get('user_type', ''))
|
||||
hash_type = int(request.form.get('hash_type', ''))
|
||||
except (ValueError, TypeError):
|
||||
flash('Invalid user type or hash type.', 'error')
|
||||
flash('Invalid user type.', 'error')
|
||||
return redirect(f'/{_PAGE}')
|
||||
|
||||
if user_type not in VALID_USER_TYPES:
|
||||
flash('Invalid user type.', 'error')
|
||||
return redirect(f'/{_PAGE}')
|
||||
if hash_type not in VALID_HASH_TYPES:
|
||||
flash('Invalid hash type.', 'error')
|
||||
return redirect(f'/{_PAGE}')
|
||||
if hash_type not in COMPATIBLE_HASHES[user_type]:
|
||||
flash('Selected hash type is not compatible with the selected user type.', 'error')
|
||||
return redirect(f'/{_PAGE}')
|
||||
|
||||
hash_type = HASH_FOR_USER_TYPE[user_type]
|
||||
|
||||
vlan = sanitize.name(request.form.get('vlan', ''))
|
||||
if not vlan:
|
||||
|
|
|
|||
|
|
@ -37,11 +37,6 @@
|
|||
"field": "user_type_label",
|
||||
"class": "col-narrow"
|
||||
},
|
||||
{
|
||||
"label": "Hash",
|
||||
"field": "hash_type_label",
|
||||
"class": "col-narrow"
|
||||
},
|
||||
{
|
||||
"label": "VLAN",
|
||||
"field": "vlan",
|
||||
|
|
@ -126,26 +121,13 @@
|
|||
"type": "hr"
|
||||
},
|
||||
{
|
||||
"type": "field_row",
|
||||
"cols": 2,
|
||||
"items": [
|
||||
{
|
||||
"type": "field",
|
||||
"label": "User Type",
|
||||
"name": "user_type",
|
||||
"input_type": "select",
|
||||
"options": [
|
||||
{"value": "0", "label": "Captive Portal"},
|
||||
{"value": "1", "label": "802.1X Supplicant"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "field",
|
||||
"label": "Hash Type",
|
||||
"name": "hash_type",
|
||||
"input_type": "select",
|
||||
"options": []
|
||||
}
|
||||
"type": "field",
|
||||
"label": "User Type",
|
||||
"name": "user_type",
|
||||
"input_type": "select",
|
||||
"options": [
|
||||
{"value": "0", "label": "Captive Portal"},
|
||||
{"value": "1", "label": "802.1X Supplicant"}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import settings
|
|||
PRO_LICENSE = settings.is_pro()
|
||||
|
||||
USER_TYPE_LABELS = {0: 'Captive Portal', 1: '802.1X'}
|
||||
HASH_TYPE_LABELS = {0: 'Cleartext', 1: 'NT-Password', 2: 'Bcrypt'}
|
||||
|
||||
|
||||
def _load_credentials():
|
||||
|
|
@ -88,7 +87,6 @@ def collect_tokens(cfg):
|
|||
r = dict(row)
|
||||
r.pop('password', None)
|
||||
r['user_type_label'] = USER_TYPE_LABELS.get(r.get('user_type'), str(r.get('user_type', '')))
|
||||
r['hash_type_label'] = HASH_TYPE_LABELS.get(r.get('hash_type'), str(r.get('hash_type', '')))
|
||||
r['expires_label'] = _format_expiry(r.get('date_set', 0), r.get('valid_for'))
|
||||
display_rows.append(r)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,3 +7,16 @@ def is_production():
|
|||
|
||||
def is_pro():
|
||||
return bool(os.environ.get('LICENSE', '').strip())
|
||||
|
||||
|
||||
def get_credentials_key():
|
||||
"""Return a Fernet-compatible key derived from the CREDENTIALS_KEY environment variable,
|
||||
or None if not set. SHA-256 hashes the raw string to produce 32 bytes, which are then
|
||||
URL-safe base64-encoded as required by Fernet."""
|
||||
import base64
|
||||
import hashlib
|
||||
key_str = os.environ.get('CREDENTIALS_KEY', '')
|
||||
if not key_str:
|
||||
return None
|
||||
raw = hashlib.sha256(key_str.encode()).digest()
|
||||
return base64.urlsafe_b64encode(raw)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ services:
|
|||
- WEB_APP_DISPLAY_NAME=Routlin Dashboard
|
||||
- INITIAL_MANAGER_EMAIL=mgrotke@gmail.com
|
||||
- SECRET_KEY=ey8hSQCCYE5kQXV8nOg1CB44LSd3AoUet2ZBc3aZlFrwBbazE7aHcxXWyuT97eAObet5jmOL0CjMg0rB1hE4d2SBVYHPfl8De55EiFv307r1QP3Mf5XgOSSCxD3TuD
|
||||
- CREDENTIALS_KEY=TwnRAoORr7OaMVeS3q4JJP3NYvBDlyPB8qgl2ovAlm2OGsNf0qsnv0a67MXgaozKWf5Gc1CM0Z1m0xdTQeiw4R0RKK0fmLKMKfttOp2sfKg9lDsMZavJWzn5VS8dyD
|
||||
- SMTP_HOST=smtp.gmail.com
|
||||
- SMTP_PORT=587
|
||||
- SMTP_USER=grotek.industries@gmail.com
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue