Skip to content

Commit 559c38c

Browse files
authored
Merge pull request #4 from phipzzz/public-page
Public page
2 parents bee431e + 9769980 commit 559c38c

11 files changed

+128
-36
lines changed

.env

+8-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,14 @@ WEB_PASSWORD = 'password'
6969

7070

7171
# Lifetime of the login to the web ui (in minutes)
72-
WEB_APP_SESSION_MAX_LIFETIME = 30
72+
WEB_APP_SESSION_MAX_LIFETIME = 30
73+
74+
75+
# Dislay a publc page without authentication (default: False).
76+
# This page only diplays the Network name, Password and the QR code.
77+
# A manual password re-creation is not possible. It can be useful if
78+
# the system is operated in offices or public places e.g. cafes.
79+
WEB_PUBLIC_ENABLED = False
7380

7481

7582
# Time zone settings for the docker container (used for logging). See

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ The fritzbox-wlan-password-rotator is a simple tool which automatically updates
1010
- Password protected web ui to easily access WLAN credentials
1111
- Dynamic QR code generation for guest WLAN credentials
1212
- Trigger manual guest WLAN password renew
13+
- Public page with network credentials for offices or public places e.g. cafes (can be enabled/disabled)
1314
- Automatic data fetching if the guest WLAN credentials were changed directly on your FRITZ!Box (or somewhere else)
1415

1516
<img src="./docs/qr-code_and_credentials.png" alt="qr code in safari" style="width: 700px; margin-right: 20px;"/>
1617

17-
Further screenshots can be found [here](./docs/).
18+
Further screenshots can be found [here](./images/).
1819

1920
## Why
2021

@@ -115,9 +116,11 @@ It is also possible to run the application without Docker, but this requires man
115116
Following FRITZ!Boxes were tested successfully. Feel free to add further ones. I guess almost all current models will work but they haven't been tested so far.
116117

117118
- **FRITZ!Box 7590 AX**
119+
118120
- FRITZ!OS 8.00
119121

120122
- **FRITZ!Box 7530**
123+
121124
- FRITZ!OS 8.00
122125

123126
- **FRITZ!Box 7490**

app/config.py

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def get_bool_value(env, default):
4949

5050
WEB_PASSWORD = os.environ.get('WEB_PASSWORD', 'password')
5151

52+
WEB_PUBLIC_ENABLED = get_bool_value(os.environ.get('WEB_PUBLIC_ENABLED'), False)
53+
5254
GUNICORN_PORT = 5000
5355

5456
DEBUG = get_bool_value(os.environ.get('DEBUG'), False)
File renamed without changes.
File renamed without changes.

main.py

+69-29
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
WEB_PASSWORD,
33
WEB_PAGE_TITLE,
44
WEB_APP_SESSION_MAX_LIFETIME,
5+
WEB_PUBLIC_ENABLED,
56
CRON_MINUTE,
67
CRON_HOUR,
78
CRON_DAY,
@@ -24,7 +25,8 @@
2425
render_template,
2526
redirect,
2627
url_for,
27-
session
28+
session,
29+
g
2830
)
2931
from apscheduler.schedulers.background import BackgroundScheduler
3032
from apscheduler.triggers.cron import CronTrigger
@@ -78,66 +80,104 @@ def update_password():
7880
current_password = new_password
7981
app.logger.info(f"New password set: {current_password}")
8082

83+
def render_qr():
84+
85+
global current_password
86+
wifi_data = f"WIFI:S:{ssid};T:WPA;P:{current_password};H:false;"
87+
88+
qr = qrcode.QRCode(
89+
version = 1,
90+
error_correction = qrcode.constants.ERROR_CORRECT_L,
91+
box_size = 10,
92+
border = 4,
93+
)
94+
qr.add_data(wifi_data)
95+
qr.make(fit=True)
96+
97+
img = qr.make_image(fill_color = "black", back_color = "white")
98+
buffer = io.BytesIO()
99+
img.save(buffer, format = "PNG")
100+
buffer.seek(0)
101+
102+
return base64.b64encode(buffer.getvalue()).decode()
103+
104+
def render_page(page_name):
105+
106+
return render_template(page_name, qr_code = render_qr(), password = current_password, WLAN_SSID = ssid, WEB_PAGE_TITLE = WEB_PAGE_TITLE)
107+
81108
@app.before_request
82109
def check_session():
83-
if "authenticated" not in session and request.endpoint not in ["login", "static"]:
110+
111+
g.user_ip = request.remote_addr
112+
113+
if "authenticated" not in session and request.endpoint not in [
114+
"login",
115+
"static",
116+
"public",
117+
"public_disabled"
118+
]:
119+
84120
return redirect(url_for("login"))
85121

86122
@app.route("/", methods = ["GET", "POST"])
87123
def login():
124+
88125
if request.method == "POST":
89126
entered_password = request.form.get("password")
90-
user_ip = request.remote_addr
127+
91128
if entered_password == WEB_PASSWORD:
92129
session["authenticated"] = True
93130
session.permanent = True
94-
app.logger.info(f"{user_ip} - Successfully logged in")
95-
return redirect(url_for("qr"))
131+
app.logger.info(f"{g.user_ip} - Successfully logged in")
132+
133+
return redirect(url_for("admin"))
96134

97135
else:
98-
app.logger.warning(f"{user_ip} - Failed login attempt")
136+
app.logger.warning(f"{g.user_ip} - Failed login attempt")
137+
99138
return render_template("login.html", WEB_PAGE_TITLE = WEB_PAGE_TITLE, error = "Wrong password!")
139+
100140
return render_template("login.html", WEB_PAGE_TITLE = WEB_PAGE_TITLE)
101141

102-
@app.route("/qr")
103-
def qr():
142+
@app.route("/admin")
143+
def admin():
104144

105145
if not session.get("authenticated"):
106146
return redirect(url_for("login"))
107147

108-
global current_password
109-
wifi_data = f"WIFI:S:{ssid};T:WPA;P:{current_password};H:false;"
148+
return render_page("admin.html")
110149

111-
qr = qrcode.QRCode(
112-
version = 1,
113-
error_correction = qrcode.constants.ERROR_CORRECT_L,
114-
box_size = 10,
115-
border = 4,
116-
)
117-
qr.add_data(wifi_data)
118-
qr.make(fit=True)
119-
120-
img = qr.make_image(fill_color = "black", back_color = "white")
121-
buffer = io.BytesIO()
122-
img.save(buffer, format = "PNG")
123-
buffer.seek(0)
124-
img_base64 = base64.b64encode(buffer.getvalue()).decode()
150+
@app.route("/public")
151+
def public():
125152

126-
return render_template("qr.html", qr_code = img_base64, password = current_password, WLAN_SSID = ssid, WEB_PAGE_TITLE = WEB_PAGE_TITLE)
153+
if not WEB_PUBLIC_ENABLED:
154+
return redirect(url_for("public_disabled"))
155+
156+
return render_page("public.html")
127157

128158
@app.route("/update-password", methods=["POST"])
129159
def trigger_update_password():
130-
app.logger.info(f"Password update maually triggered")
160+
161+
app.logger.info(f"Password update maually triggered by {g.user_ip}")
131162
update_password()
132-
return redirect(url_for("qr"))
163+
164+
return redirect(url_for("admin"))
133165

134166
@app.route("/logout")
135167
def logout():
168+
136169
session.pop("authenticated", None)
137-
user_ip = request.remote_addr
138-
app.logger.info(f"{user_ip} - Successfully logged out")
170+
app.logger.info(f"{g.user_ip} - Successfully logged out")
171+
139172
return redirect(url_for("login"))
140173

174+
@app.route("/public_disabled")
175+
def public_disabled():
176+
177+
app.logger.info(f"{g.user_ip} - Public page disabled")
178+
179+
return render_template("public_disabled.html")
180+
141181
scheduler = BackgroundScheduler()
142182

143183
def start_scheduler():

static/src/styles/styles.css

+1-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ body {
1212
padding: 0 15px;
1313
}
1414

15-
h1 {
15+
.container > h1 {
1616
text-align: center;
1717
font-size: 2.8rem;
1818
}
@@ -28,9 +28,6 @@ img {
2828
margin: 0 auto;
2929
}
3030

31-
/* .container > form {
32-
} */
33-
3431
.btn {
3532
display: block;
3633
width: 100%;

templates/qr.html renamed to templates/admin.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<div class="container">
1414
<h1>{{ WEB_PAGE_TITLE }}</h1>
1515
<p>
16-
SSID: <strong>{{ WLAN_SSID }}</strong><br />
16+
Network name: <strong>{{ WLAN_SSID }}</strong><br />
1717
Password: <strong>{{ password }}</strong>
1818
</p>
1919
<img src="data:image/png;base64,{{ qr_code }}" alt="QR-Code" />

templates/public.html

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link
7+
rel="stylesheet"
8+
href="{{ url_for('static', filename='src/styles/styles.css') }}"
9+
/>
10+
<title>{{ WEB_PAGE_TITLE }}</title>
11+
</head>
12+
<body>
13+
<div class="container">
14+
<h1>{{ WEB_PAGE_TITLE }}</h1>
15+
<p>
16+
Network name: <strong>{{ WLAN_SSID }}</strong><br />
17+
Password: <strong>{{ password }}</strong>
18+
</p>
19+
<img src="data:image/png;base64,{{ qr_code }}" alt="QR-Code" />
20+
</div>
21+
<script src="{{ url_for('static', filename='src/script/main.js') }}"></script>
22+
</body>
23+
</html>

templates/public_disabled.html

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link
7+
rel="stylesheet"
8+
href="{{ url_for('static', filename='src/styles/styles.css') }}"
9+
/>
10+
<title>{{ WEB_PAGE_TITLE }} - Error</title>
11+
</head>
12+
<body>
13+
<h1>Error: Public page not enabled</h1>
14+
<p>
15+
The public page has not been enabled. Please contact your
16+
administrator.
17+
</p>
18+
<script src="{{ url_for('static', filename='src/script/main.js') }}"></script>
19+
</body>
20+
</html>

0 commit comments

Comments
 (0)