Development
This commit is contained in:
parent
fef78d7115
commit
bb07e67d53
4 changed files with 35 additions and 27 deletions
89
docker/routlin-portal/app/page.py
Normal file
89
docker/routlin-portal/app/page.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import ipaddress
|
||||
from flask import Blueprint, request, redirect
|
||||
import config_utils
|
||||
|
||||
bp = Blueprint('portal', __name__)
|
||||
|
||||
PORTAL_HTML = """\
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{title}</title>
|
||||
<style>
|
||||
body {{ font-family: sans-serif; max-width: 480px; margin: 60px auto; padding: 0 1rem; }}
|
||||
h1 {{ font-size: 1.5rem; margin-bottom: .5rem; }}
|
||||
.err {{ color: #c00; margin: .75rem 0; }}
|
||||
.terms label {{ display: block; margin: .4rem 0; cursor: pointer; }}
|
||||
button {{ margin-top: 1rem; padding: .55rem 1.4rem; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{title}</h1>
|
||||
{splash_html}
|
||||
{error_html}
|
||||
<form method="post">
|
||||
<input type="hidden" name="next" value="{next_url}">
|
||||
<div class="terms">{terms_html}</div>
|
||||
<button type="submit">Continue</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
def _vlan_for_ip(client_ip):
|
||||
cfg = config_utils.load_config()
|
||||
try:
|
||||
addr = ipaddress.ip_address(client_ip)
|
||||
except ValueError:
|
||||
return None
|
||||
for vlan in cfg.get('vlans', []):
|
||||
if vlan.get('restricted_vlan') != 'c':
|
||||
continue
|
||||
try:
|
||||
net = ipaddress.ip_network(f"{vlan['ip']}/{vlan['subnet']}", strict=False)
|
||||
if addr in net:
|
||||
return vlan
|
||||
except (KeyError, ValueError):
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def _render(vlan, error=None, next_url=''):
|
||||
terms = vlan.get('portal_terms', [])
|
||||
terms_html = ''.join(
|
||||
f'<label><input type="checkbox" name="term_{i}" required> {t}</label>'
|
||||
for i, t in enumerate(terms)
|
||||
) or '<p>No terms required.</p>'
|
||||
return PORTAL_HTML.format(
|
||||
title=vlan.get('portal_splash_title', 'Guest Portal'),
|
||||
splash_html=f'<p>{vlan["portal_splash_text"]}</p>' if vlan.get('portal_splash_text') else '',
|
||||
error_html=f'<p class="err">{error}</p>' if error else '',
|
||||
terms_html=terms_html,
|
||||
next_url=next_url,
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/', defaults={'path': ''}, methods=['GET', 'POST'])
|
||||
@bp.route('/<path:path>', methods=['GET', 'POST'])
|
||||
def portal(path):
|
||||
vlan = _vlan_for_ip(request.remote_addr)
|
||||
if vlan is None:
|
||||
return 'Portal unavailable.', 404
|
||||
|
||||
if request.method == 'POST':
|
||||
terms = vlan.get('portal_terms', [])
|
||||
for i in range(len(terms)):
|
||||
if not request.form.get(f'term_{i}'):
|
||||
return _render(vlan,
|
||||
error='You must accept all terms to continue.',
|
||||
next_url=request.form.get('next', '')), 200
|
||||
try:
|
||||
with open(config_utils.CAPTIVE_QUEUE, 'a') as f:
|
||||
f.write(f'allow {request.remote_addr}\n')
|
||||
except OSError:
|
||||
pass
|
||||
return redirect(request.form.get('next') or 'http://routlin.local/', 302)
|
||||
|
||||
return _render(vlan, next_url=request.args.get('next', '')), 200
|
||||
Loading…
Add table
Add a link
Reference in a new issue