3 Commits

Author SHA1 Message Date
7f0c75d4d8 doorboy claim: redirect to edit
Incentivize naming tags
2026-07-30 20:02:06 +03:00
7484471b6b doorboy: natu doku 2026-07-30 19:52:17 +03:00
57ba7d1f58 doorboy: fix enable on new tokens 2026-07-30 18:56:59 +03:00
4 changed files with 18 additions and 64 deletions

View File

@@ -8,7 +8,7 @@ from flask_wtf import FlaskForm
from oidc import login_required, read_user
from pymongo import MongoClient
from wtforms import BooleanField, IntegerField, SelectField, StringField, validators
from wtforms.validators import DataRequired, InputRequired
from wtforms.validators import DataRequired
page_doorboy = Blueprint("doorboy", __name__)
db = MongoClient(const.MONGO_URI).get_default_database()
@@ -34,13 +34,13 @@ def view_doorboy_claim(event_id):
"inventory.owner.username": { "$exists": False }
}, {
"$set": {
"token.enabled": datetime.utcnow(),
"token.enabled": True,
"inventory.owner.display_name": user["name"],
"inventory.owner.username": user["username"]
}
})
return redirect("/m/doorboy")
return redirect(f"/m/doorboy/{event_id }/edit")
class TokenEditForm(FlaskForm):
comment = StringField("Comment")
@@ -82,19 +82,9 @@ def save_doorboy_edit(token_id):
})
return redirect("/m/doorboy/me")
def hold_duration_check(form, field):
# 0 cancels/closes an active hold immediately; any real hold must last at
# least the documented 5-second minimum (and at most 6 hours). Reject 1-4
# so a typo cannot arm an unusably short hold. field.data is None when the
# submitted value was not a valid integer; leave that to the other validators.
if field.data is None:
return
if field.data != 0 and not (5 <= field.data <= 21600):
raise validators.ValidationError("Duration must be 0 (to close now) or between 5 and 21600 seconds")
class HoldDoorForm(FlaskForm):
door_name = SelectField("Door name", choices=[(j,j) for j in ["grounddoor", "frontdoor", "backdoor", "workshopdoor"]], validators=[DataRequired()])
duration = IntegerField('Duration in seconds', validators=[InputRequired(), hold_duration_check])
door_name = SelectField("Door name", choices=[(j,j) for j in ["grounddoor", "frontdoor", "backdoor"]], validators=[DataRequired()])
duration = IntegerField('Duration in seconds', validators=[DataRequired(), validators.NumberRange(min=5, max=21600)])
# duration=0 to override and close right away
@page_doorboy.route("/m/doorboy/hold", methods=["POST"])
@@ -102,32 +92,16 @@ class HoldDoorForm(FlaskForm):
def view_doorboy_hold():
user = read_user()
form = HoldDoorForm(request.form)
if not form.validate_on_submit():
# Validation or CSRF failure: report it instead of redirecting as if the
# hold had been accepted, so the user knows the door will not be held.
errors = "; ".join(m for messages in form.errors.values() for m in messages)
return "Invalid hold request: %s" % (errors or "bad or expired form"), 400
door = form.door_name.data
if door == "workshopdoor":
access_group = "k-space:workshop"
else:
access_group = "k-space:floor"
approved = access_group in g.users_lookup.get(user["username"], User()).groups
if form.validate_on_submit():
db.doorlog.insert_one({
"method": "hold",
"timestamp": datetime.utcnow(),
"door": door,
"approved": approved,
"door": form.door_name.data,
"approved": True,
"user": user["username"],
"expires": datetime.utcnow() + timedelta(seconds=form.duration.data)
})
if approved:
return redirect("/m/doorboy")
# Logged for audit, but the proxy only honors approved holds, so tell the
# user the door will not actually be held rather than redirecting silently.
return "You are not in the access group required to hold this door", 403
# Writes open event to log, which is picked up by doorboy-proxy.
@page_doorboy.route("/m/doorboy/<door>/open")
@@ -161,7 +135,6 @@ def view_doorboy():
user = read_user()
workshop_access = "k-space:workshop" in g.users_lookup.get(user["username"], User()).groups
hold_form = HoldDoorForm()
latest_swipes = db.inventory.find({"component": "doorboy", "type":"token"}).sort([("last_seen", -1)]).limit(10)
return render_template("doorboy.html", **locals())

View File

@@ -130,11 +130,6 @@ mongodb = mongoclient.get_default_database()
mongodb.inventory.create_index("shortener.slug", sparse=True, unique=True)
mongodb.inventory.create_index("token.uid_hash", sparse=True, unique=True)
#mongodb.inventory.create_index("token.uid_hash", unique=True)
# Supports doorboy-proxy's per-poll "newest active hold for this door" lookup
# (find_one({method,door,approved}, sort=timestamp desc)) without a collection scan.
# method is the leading equality field so the common no-active-hold poll does not
# scan every approved event for the door; timestamp trails for the descending sort.
mongodb.doorlog.create_index([("method", 1), ("door", 1), ("approved", 1), ("timestamp", -1)])
CATEGORY_COLORS = (
('membership-fee', '#acc236'),

View File

@@ -37,22 +37,6 @@
</div>
</div>
<div>
<h3>Hold door open</h3>
<form method="POST" action="/m/doorboy/hold" autocomplete="off">
{{ hold_form.csrf_token }}
<select name="door_name">
<option value="grounddoor">Ground door</option>
<option value="frontdoor">Front door</option>
<option value="backdoor">Back door</option>
{% if workshop_access %}<option value="workshopdoor">Workshop</option>{% endif %}
</select>
<input type="number" name="duration" min="0" max="21600" value="300">
<button class="waves-effect waves-light btn" type="submit">Hold</button>
<p>Duration in seconds: 300 = 5 min, 3600 = 1h, max 21600 = 6h, 0 = cancel/close now.</p>
</form>
</div>
<hr>
<div><ul>
<li>Doors locations: <a href="https://k-space.ee/where">k-space.ee/where</a></li>
@@ -72,7 +56,7 @@
</p>
<h4>Keycards last seen:</h4>
Does not include door opens by webhook.
Does not include door opens by webhook. Keycards are synced to doors every 3 minutes.
<table>
<thead>
@@ -106,6 +90,7 @@ Does not include door opens by webhook.
{% endfor %}
</tbody>
</table>
<p><a href="/m/doorboy/log">All logs</a> (link for admins only)</p>
{% if false %}
<!-- whole section commented out -->

View File

@@ -5,6 +5,7 @@
<h4>Doorboy info for {{subject_user.username | display_name}}</h4>
<br/>
<h5>Users cards:</h5>
<p>Keycards are synced to doors every 3 minutes.</p>
<table class="striped">
<thead>
<tr>