generated from PlexSheep/rs-base
Compare commits
No commits in common. "feat/meta-interface" and "master" have entirely different histories.
feat/meta-
...
master
|
@ -29,8 +29,11 @@ tokio = { version = "1.40.0", features = [
|
|||
"rt",
|
||||
"sync",
|
||||
] }
|
||||
tracing = "0.1.40"
|
||||
tracing-appender = "0.2.3"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
warp = { version = "0.3.7", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["meta-interface"]
|
||||
meta-interface = ["dep:warp", "dep:minijinja"]
|
||||
default = ["admin-interface"]
|
||||
admin-interface = ["dep:warp", "dep:minijinja"]
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
# Wooly-Vault
|
||||
|
||||
Managing Platform for Hacking challenges, written in Rust 🦀.
|
||||
|
||||
Wooly-Vault is a small platform for hosting CTF-like hacking challenges. It is
|
||||
currently in the early stages of development.
|
||||
A few small challenges for ⚒️ Rust Hackers 🦀
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
<!doctype html>
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<script src="https://getbootstrap.com/docs/5.3/assets/js/color-modes.js"></script>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="author" content="{{author}}">
|
||||
<title>{{title}}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3">
|
||||
<link href="https://getbootstrap.com/docs/5.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" href="https://icons.getbootstrap.com/assets/icons/safe-fill.svg">
|
||||
<meta name="theme-color" content="#712cf9">
|
||||
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- All the svgs we use later -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
|
||||
<symbol id="check2" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||
</symbol>
|
||||
<symbol id="circle-half" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z" />
|
||||
</symbol>
|
||||
<symbol id="moon-stars-fill" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z" />
|
||||
<path
|
||||
d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z" />
|
||||
</symbol>
|
||||
<symbol id="sun-fill" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
|
||||
</symbol>
|
||||
<symbol id="arrow-right-circle" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H4.5z" />
|
||||
</symbol>
|
||||
<symbol id="vault" viewBox="0 0 16 16">
|
||||
<title>Bootstrap</title>
|
||||
<path d="M9.778 9.414A2 2 0 1 1 6.95 6.586a2 2 0 0 1 2.828 2.828" />
|
||||
<path
|
||||
d="M2.5 0A1.5 1.5 0 0 0 1 1.5V3H.5a.5.5 0 0 0 0 1H1v3.5H.5a.5.5 0 0 0 0 1H1V12H.5a.5.5 0 0 0 0 1H1v1.5A1.5 1.5 0 0 0 2.5 16h12a1.5 1.5 0 0 0 1.5-1.5v-13A1.5 1.5 0 0 0 14.5 0zm3.036 4.464 1.09 1.09a3 3 0 0 1 3.476 0l1.09-1.09a.5.5 0 1 1 .707.708l-1.09 1.09c.74 1.037.74 2.44 0 3.476l1.09 1.09a.5.5 0 1 1-.707.708l-1.09-1.09a3 3 0 0 1-3.476 0l-1.09 1.09a.5.5 0 1 1-.708-.708l1.09-1.09a3 3 0 0 1 0-3.476l-1.09-1.09a.5.5 0 1 1 .708-.708M14 6.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 1 1 0" />
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<!-- theme switcher -->
|
||||
<div class="dropdown position-fixed bottom-0 end-0 mb-3 me-3 bd-mode-toggle">
|
||||
<button class="btn btn-bd-primary py-2 dropdown-toggle d-flex align-items-center" id="bd-theme" type="button"
|
||||
aria-expanded="false" data-bs-toggle="dropdown" aria-label="Toggle theme (auto)">
|
||||
<svg class="bi my-1 theme-icon-active" width="1em" height="1em">
|
||||
<use href="#circle-half"></use>
|
||||
</svg>
|
||||
<span class="visually-hidden" id="bd-theme-text">Toggle theme</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="bd-theme-text">
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light"
|
||||
aria-pressed="false">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#sun-fill"></use>
|
||||
</svg>
|
||||
Light
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark"
|
||||
aria-pressed="false">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#moon-stars-fill"></use>
|
||||
</svg>
|
||||
Dark
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto"
|
||||
aria-pressed="true">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#circle-half"></use>
|
||||
</svg>
|
||||
Auto
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- basically everything but with some margin -->
|
||||
<div class="col-lg-8 mx-auto p-4 py-md-5">
|
||||
<header class="d-flex align-items-center pb-3 mb-5 border-bottom">
|
||||
<a href="/" class="d-flex align-items-center text-body-emphasis text-decoration-none">
|
||||
<svg class="bi me-2" width="40" height="32">
|
||||
<use xlink:href="#vault" />
|
||||
</svg>
|
||||
<span class="fs-4">{{title}}</span>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<h1 class="text-body-emphasis">{{title}} Admin Interface</h1>
|
||||
<p class="fs-5 col-md-8">
|
||||
You have reached the {{title}} Admin Interface. This site can be used by
|
||||
the host of the challenge to see the challenge progress, solution, and
|
||||
hints for that challenge. This site is <b>NOT</b> part of the
|
||||
challenge.
|
||||
</p>
|
||||
|
||||
<hr class="mb-5">
|
||||
|
||||
<div class="row g-5 mb-5">
|
||||
<h2 class="text-body-emphasis">Challenge {{ challenge_idx}} — {{ challenge_title }}</h2>
|
||||
<p class="mt-1 mb-3">{{ challenge_description }}</p>
|
||||
<div class="col mt-1">
|
||||
<h3>Hints</h3>
|
||||
<button class="btn btn-primary my-2" type="button" data-bs-toggle="collapse" data-bs-target="#hints"
|
||||
aria-expanded="false" aria-controls="collapseExample">
|
||||
Show hints
|
||||
</button>
|
||||
<div class="collapse" id="hints">
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for hint in challenge_hints %}
|
||||
<li>
|
||||
<p>
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{hint}}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mt-1">
|
||||
<h3>Solution</h3>
|
||||
<button class="btn btn-primary my-2" type="button" data-bs-toggle="collapse" data-bs-target="#solution"
|
||||
aria-expanded="false" aria-controls="collapseExample">
|
||||
Show solution
|
||||
</button>
|
||||
<div class="collapse" id="solution">
|
||||
<p>
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{challenge_solution}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="mb-5">
|
||||
|
||||
<div class="row g-5">
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-body-emphasis">Contestants</h2>
|
||||
<p>
|
||||
There are cuttently {{ contestants_amount }} contestants.
|
||||
These contestants currently have had at least one connection to
|
||||
the challenge:
|
||||
</p>
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for contestant in contestants %}
|
||||
<li>
|
||||
<a class="icon-link mb-1" href="https://whatismyipaddress.com/ip/{{ contestant.ip }}">
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{ contestant.ip }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-body-emphasis">Winners</h2>
|
||||
<p>
|
||||
There are cuttently {{ winners_amount }} winners. These contestants currently have been sent the secret:
|
||||
</p>
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for winner in winners %}
|
||||
<li>
|
||||
<a class="icon-link mb-1" href="https://whatismyipaddress.com/ip/{{ winner.ip }}">
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{ winner.ip }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="pt-5 my-5 text-body-secondary border-top">
|
||||
Created by {{ author }} © {{year}}
|
||||
</footer>
|
||||
</div>
|
||||
<script src="https://getbootstrap.com/docs/5.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,16 +0,0 @@
|
|||
{% extends "base" %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block header_ext %}
|
||||
<span class="badge text-bg-danger rounded-pill">Admin</span>
|
||||
{% endblock %}
|
||||
{% block main %}
|
||||
<h1 class="text-body-emphasis">{{title}}</h1>
|
||||
<p class="fs-5 col-md-8">
|
||||
You have reached the {{title}}. This site can be used by
|
||||
the host of the challenge to see the challenge progress, solution, and
|
||||
hints for that challenge. This site is <b>NOT</b> part of the
|
||||
challenge.
|
||||
</p>
|
||||
<hr class="mb-5">
|
||||
{% block content %}{% endblock content %}
|
||||
{% endblock %}
|
|
@ -1,46 +0,0 @@
|
|||
{% extends "admin:base" %}
|
||||
{% block content %}
|
||||
<div class="row g-5 mb-5">
|
||||
<h2 class="text-body-emphasis">Challenge {{ challenge.id }} — {{ challenge.title }}</h2>
|
||||
<p class="mt-1 mb-3">{{ challenge.description }}</p>
|
||||
<div class="col mt-1">
|
||||
<h3>Hints</h3>
|
||||
<button class="btn btn-primary my-2" type="button" data-bs-toggle="collapse" data-bs-target="#hints"
|
||||
aria-expanded="false" aria-controls="collapseExample">
|
||||
Show hints
|
||||
</button>
|
||||
<div class="collapse" id="hints">
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for hint in challenge.hints %}
|
||||
<li>
|
||||
<p>
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{hint}}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mt-1">
|
||||
<h3>Solution</h3>
|
||||
<button class="btn btn-primary my-2" type="button" data-bs-toggle="collapse" data-bs-target="#solution"
|
||||
aria-expanded="false" aria-controls="collapseExample">
|
||||
Show solution
|
||||
</button>
|
||||
<div class="collapse" id="solution">
|
||||
<p>
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{challenge.solution}}
|
||||
</p>
|
||||
<code>{{secret}}</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mb-5">
|
||||
{% include "comp:contestants_winners" %}
|
||||
{% endblock %}
|
|
@ -1,26 +0,0 @@
|
|||
{% extends "admin:base" %}
|
||||
{% block content %}
|
||||
|
||||
<div class="row g-5">
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-body-emphasis">Challenges</h2>
|
||||
<p>
|
||||
There are cuttently {{ challenges_amount }} active challenges.
|
||||
</p>
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for challenge in challenges %}
|
||||
<li>
|
||||
<a class="icon-link mb-1" href="/admin/challenge/{{ challenge.id }}">
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{ challenge.id }} — {{ challenge.title }}
|
||||
</a>
|
||||
⇒ {{ challenge.addr }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,200 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en" data-bs-theme="auto">
|
||||
|
||||
<head>
|
||||
<script src="https://getbootstrap.com/docs/5.3/assets/js/color-modes.js"></script>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="author" content="{{author}}">
|
||||
<title>{{title}}</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3">
|
||||
<link href="https://getbootstrap.com/docs/5.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" href="https://icons.getbootstrap.com/assets/icons/safe-fill.svg">
|
||||
<meta name="theme-color" content="#712cf9">
|
||||
|
||||
<link rel="stylesheet" href="/styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- All the svgs we use later -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
|
||||
<symbol id="check2" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z" />
|
||||
</symbol>
|
||||
<symbol id="circle-half" viewBox="0 0 16 16">
|
||||
<path d="M8 15A7 7 0 1 0 8 1v14zm0 1A8 8 0 1 1 8 0a8 8 0 0 1 0 16z" />
|
||||
</symbol>
|
||||
<symbol id="moon-stars-fill" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z" />
|
||||
<path
|
||||
d="M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z" />
|
||||
</symbol>
|
||||
<symbol id="sun-fill" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
|
||||
</symbol>
|
||||
<symbol id="arrow-right-circle" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zM4.5 7.5a.5.5 0 0 0 0 1h5.793l-2.147 2.146a.5.5 0 0 0 .708.708l3-3a.5.5 0 0 0 0-.708l-3-3a.5.5 0 1 0-.708.708L10.293 7.5H4.5z" />
|
||||
</symbol>
|
||||
<symbol id="vault" viewBox="0 0 16 16">
|
||||
<title>{{app_name}}</title>
|
||||
<path d="M9.778 9.414A2 2 0 1 1 6.95 6.586a2 2 0 0 1 2.828 2.828" />
|
||||
<path
|
||||
d="M2.5 0A1.5 1.5 0 0 0 1 1.5V3H.5a.5.5 0 0 0 0 1H1v3.5H.5a.5.5 0 0 0 0 1H1V12H.5a.5.5 0 0 0 0 1H1v1.5A1.5 1.5 0 0 0 2.5 16h12a1.5 1.5 0 0 0 1.5-1.5v-13A1.5 1.5 0 0 0 14.5 0zm3.036 4.464 1.09 1.09a3 3 0 0 1 3.476 0l1.09-1.09a.5.5 0 1 1 .707.708l-1.09 1.09c.74 1.037.74 2.44 0 3.476l1.09 1.09a.5.5 0 1 1-.707.708l-1.09-1.09a3 3 0 0 1-3.476 0l-1.09 1.09a.5.5 0 1 1-.708-.708l1.09-1.09a3 3 0 0 1 0-3.476l-1.09-1.09a.5.5 0 1 1 .708-.708M14 6.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 1 1 0" />
|
||||
</symbol>
|
||||
<symbol id="admin" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M5.338 1.59a61 61 0 0 0-2.837.856.48.48 0 0 0-.328.39c-.554 4.157.726 7.19 2.253 9.188a10.7 10.7 0 0 0 2.287 2.233c.346.244.652.42.893.533q.18.085.293.118a1 1 0 0 0 .101.025 1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067c-.53 0-1.552.223-2.662.524zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56" />
|
||||
<path d="M5.5 7a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1H6a.5.5 0 0 1-.5-.5" />
|
||||
</symbol>
|
||||
<symbol id="user" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1zm-7.978-1L7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002-.014.002zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4m3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0M6.936 9.28a6 6 0 0 0-1.23-.247A7 7 0 0 0 5 9c-4 0-5 3-5 4q0 1 1 1h4.216A2.24 2.24 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816M4.92 10A5.5 5.5 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275ZM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0m3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4" />
|
||||
</symbol>
|
||||
|
||||
</svg>
|
||||
|
||||
<!-- theme switcher -->
|
||||
<div class="dropdown position-fixed bottom-0 end-0 mb-3 me-3 bd-mode-toggle">
|
||||
<button class="btn btn-bd-primary py-2 dropdown-toggle d-flex align-items-center" id="bd-theme" type="button"
|
||||
aria-expanded="false" data-bs-toggle="dropdown" aria-label="Toggle theme (auto)">
|
||||
<svg class="bi my-1 theme-icon-active" width="1em" height="1em">
|
||||
<use href="#circle-half"></use>
|
||||
</svg>
|
||||
<span class="visually-hidden" id="bd-theme-text">Toggle theme</span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow" aria-labelledby="bd-theme-text">
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="light"
|
||||
aria-pressed="false">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#sun-fill"></use>
|
||||
</svg>
|
||||
Light
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center" data-bs-theme-value="dark"
|
||||
aria-pressed="false">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#moon-stars-fill"></use>
|
||||
</svg>
|
||||
Dark
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" class="dropdown-item d-flex align-items-center active" data-bs-theme-value="auto"
|
||||
aria-pressed="true">
|
||||
<svg class="bi me-2 opacity-50" width="1em" height="1em">
|
||||
<use href="#circle-half"></use>
|
||||
</svg>
|
||||
Auto
|
||||
<svg class="bi ms-auto d-none" width="1em" height="1em">
|
||||
<use href="#check2"></use>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- basically everything but with some margin -->
|
||||
<div class="d-flex flex-nowrap m-0 container-fluid border-collapse" style="min-height: 90vh;">
|
||||
<div class="row mb-0 border-collapse">
|
||||
<!-- side panel -->
|
||||
<div class="col-sm-2 border-end border-collapse pt-2" style="min-width: 26vw;">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href="/"
|
||||
class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-decoration-none text-body-emphasis">
|
||||
<svg class="bi pe-none me-2" width="40" height="32">
|
||||
<use xlink:href="#vault"></use>
|
||||
</svg>
|
||||
<span class="fs-4">{{app_name}}</span>
|
||||
</a>
|
||||
{% block header_ext %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<ul class="nav nav-pills flex-column mb-auto">
|
||||
<li class="nav-item">
|
||||
<a href="/" class="nav-link text-body-emphasis active" aria-current="page">
|
||||
<svg class="bi pe-none me-2" width="16" height="16">
|
||||
<use xlink:href="#home"></use>
|
||||
</svg>
|
||||
Home
|
||||
</a>
|
||||
</li>
|
||||
<li class="border-top my-3"></li>
|
||||
<li>
|
||||
<a href="/" class="nav-link text-body-emphasis">
|
||||
<svg class="bi pe-none me-2" width="16" height="16">
|
||||
<use xlink:href="#user"></use>
|
||||
</svg>
|
||||
User Interface
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin" class="nav-link text-body-emphasis">
|
||||
<svg class="bi pe-none me-2" width="16" height="16">
|
||||
<use xlink:href="#admin"></use>
|
||||
</svg>
|
||||
Admin Interface
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<div class="dropdown">
|
||||
<a href="#"
|
||||
class="d-flex align-items-center text-decoration-none dropdown-toggle text-body-emphasis"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<img src="https://github.com/mdo.png" alt="" width="32" height="32" class="rounded-circle me-2">
|
||||
<strong>anon</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-dark text-small shadow">
|
||||
<li><a class="dropdown-item" href="#">Settings</a></li>
|
||||
<li><a class="dropdown-item" href="#">Profile</a></li>
|
||||
<li>
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li><a class="dropdown-item" href="#">Sign out</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col ms-3 mt-4">
|
||||
<main class="d-flex flex-nowrap">
|
||||
<!-- real content -->
|
||||
<div class="container-fluid">
|
||||
{% block main %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="pt-2 pb-2 text-body-secondary text-center border-top
|
||||
border-collapse">
|
||||
Created by {{ author }} © {{year}}
|
||||
</footer>
|
||||
<script src="https://getbootstrap.com/docs/5.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,41 +0,0 @@
|
|||
<div class="row g-5">
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-body-emphasis">Contestants</h2>
|
||||
<p>
|
||||
There are cuttently {{ contestants_amount }} contestants.
|
||||
These contestants currently have had at least one connection to
|
||||
the challenge:
|
||||
</p>
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for contestant in contestants %}
|
||||
<li>
|
||||
<a class="icon-link mb-1" href="https://whatismyipaddress.com/ip/{{ contestant.ip }}">
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{ contestant.ip }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-body-emphasis">Winners</h2>
|
||||
<p>
|
||||
There are cuttently {{ winners_amount }} winners. These contestants currently have been sent the secret:
|
||||
</p>
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for winner in winners %}
|
||||
<li>
|
||||
<a class="icon-link mb-1" href="https://whatismyipaddress.com/ip/{{ winner.ip }}">
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{ winner.ip }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -1,15 +1,3 @@
|
|||
body {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.nobr {
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{% extends "base" %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block main %}
|
||||
<h1 class="text-body-emphasis">{{title}}</h1>
|
||||
<p class="fs-5 col-md-8">
|
||||
You have reached the {{title}}. This site can be used by the
|
||||
contestants see the challenges and their progress and hints for that
|
||||
challenge. This site is <b>NOT</b> part of the challenge.
|
||||
</p>
|
||||
<hr class="mb-5">
|
||||
{% block content %}{% endblock content %}
|
||||
{% endblock %}
|
|
@ -1,74 +0,0 @@
|
|||
{% extends "user:base" %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row g-5 mb-5">
|
||||
<h2 class="text-body-emphasis">Challenge {{ challenge.idx}} — {{ challenge.title }}</h2>
|
||||
<p class="mt-1 mb-3">{{ challenge.description }}</p>
|
||||
<div class="col mt-1">
|
||||
<h3>Hints</h3>
|
||||
<button class="btn btn-primary my-2" type="button" data-bs-toggle="collapse" data-bs-target="#hints"
|
||||
aria-expanded="false" aria-controls="collapseExample">
|
||||
Show hints
|
||||
</button>
|
||||
<div class="collapse" id="hints">
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for hint in challenge.hints %}
|
||||
<li>
|
||||
<p>
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{hint}}
|
||||
</p>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="mb-5">
|
||||
|
||||
<div class="row g-5">
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-body-emphasis">Contestants</h2>
|
||||
<p>
|
||||
There are cuttently {{ contestants_amount }} contestants.
|
||||
These contestants currently have had at least one connection to
|
||||
the challenge:
|
||||
</p>
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for contestant in contestants %}
|
||||
<li>
|
||||
<a class="icon-link mb-1" href="https://whatismyipaddress.com/ip/{{ contestant.ip }}">
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{ contestant.ip }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-body-emphasis">Winners</h2>
|
||||
<p>
|
||||
There are cuttently {{ winners_amount }} winners. These contestants currently have been sent the
|
||||
secret:
|
||||
</p>
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for winner in winners %}
|
||||
<li>
|
||||
<a class="icon-link mb-1" href="https://whatismyipaddress.com/ip/{{ winner.ip }}">
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{ winner.ip }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,26 +0,0 @@
|
|||
{% extends "user:base" %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row g-5">
|
||||
<div class="col-md-6">
|
||||
<h2 class="text-body-emphasis">Challenges</h2>
|
||||
<p>
|
||||
There are cuttently {{ challenges_amount }} active challenges.
|
||||
</p>
|
||||
<ul class="list-unstyled ps-0">
|
||||
{% for challenge in challenges %}
|
||||
<li>
|
||||
<a class="icon-link mb-1" href="/challenge/{{ challenge.id }}">
|
||||
<svg class="bi" width="16" height="16">
|
||||
<use xlink:href="#arrow-right-circle" />
|
||||
</svg>
|
||||
{{ challenge.id }} — {{ challenge.title }}
|
||||
</a>
|
||||
⇒ {{ challenge.addr }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -2,7 +2,7 @@ from io import BufferedRWPair
|
|||
import socket
|
||||
|
||||
REMOTE = "127.0.0.1"
|
||||
PORT = 1338
|
||||
PORT = 1337
|
||||
|
||||
def main() -> int:
|
||||
s = socket.socket()
|
||||
|
@ -12,7 +12,6 @@ def main() -> int:
|
|||
pl = int.to_bytes(1337, 2, "big")
|
||||
|
||||
_ = send(sf, pl)
|
||||
_ = recv(sf).decode()
|
||||
|
||||
sf.close()
|
||||
return 0
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from io import TextIOWrapper
|
||||
import socket
|
||||
import time
|
||||
|
||||
REMOTE = "127.0.0.1"
|
||||
PORT = 1339
|
||||
PORT = 1337
|
||||
MAX_GUARD = 300
|
||||
|
||||
def calc(a: int, b: int, op: str) -> int:
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use libpt::log::error;
|
||||
use libpt::log::info;
|
||||
use libpt::log::tracing;
|
||||
use libpt::log::warn;
|
||||
use minijinja::context;
|
||||
use minijinja::Environment;
|
||||
use thiserror::Error;
|
||||
use warp::http::StatusCode;
|
||||
use warp::reject;
|
||||
use warp::reply::Response;
|
||||
use warp::Filter;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
use super::Descriptions;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Service<'tp> {
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
env: Environment<'tp>,
|
||||
text: Descriptions,
|
||||
}
|
||||
impl<'tp> Service<'tp> {
|
||||
fn new(
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
env: Environment<'tp>,
|
||||
text: Descriptions,
|
||||
) -> Arc<Self> {
|
||||
Self {
|
||||
vault,
|
||||
config,
|
||||
env,
|
||||
text,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serve(text: Descriptions, vault: VaultRef, config: Config) -> Result<()> {
|
||||
let mut env = Environment::new();
|
||||
env.add_template("index", include_str!("../../data/www/admin.html"))?;
|
||||
|
||||
let service = Service::new(vault, config, env, text);
|
||||
let service2 = service.clone();
|
||||
|
||||
let routes = warp::path::end()
|
||||
.map(move || service2.clone())
|
||||
.and_then(overview)
|
||||
.or(warp::path("styles.css").and_then(styles))
|
||||
.recover(handle_rejection)
|
||||
.with(warp::trace(|info| {
|
||||
// Create a span using tracing macros
|
||||
tracing::info_span!(
|
||||
"admin",
|
||||
method = %info.method(),
|
||||
path = %info.path(),
|
||||
)
|
||||
}));
|
||||
|
||||
warp::serve(routes)
|
||||
.run(service.config.addr_admin.unwrap())
|
||||
.await;
|
||||
|
||||
warn!("exited the admin interface");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
struct TemplateError(#[from] minijinja::Error);
|
||||
|
||||
impl reject::Reject for TemplateError {}
|
||||
impl Display for TemplateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "something went wrong with producing this page")
|
||||
}
|
||||
}
|
||||
|
||||
async fn overview(serv: Arc<Service<'_>>) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let contestants = serv
|
||||
.vault
|
||||
.contestants()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
let winners = serv.vault.winners().await.into_iter().collect::<Vec<_>>();
|
||||
let r = Response::new(
|
||||
serv.env
|
||||
.get_template("index")
|
||||
.map_err(TemplateError::from)?
|
||||
.render(context!(
|
||||
title => "Wooly-Vault",
|
||||
author => env!("CARGO_PKG_AUTHORS"),
|
||||
year => "2024",
|
||||
challenge_idx => serv.config.challenge,
|
||||
challenge_title => serv.text.title(),
|
||||
challenge_description => serv.text.description(),
|
||||
challenge_hints => serv.text.hints(),
|
||||
challenge_solution => serv.text.solution(),
|
||||
contestants => serv.vault.contestants().await.iter().collect::<Vec<_>>(),
|
||||
winners => serv.vault.winners().await.iter().collect::<Vec<_>>(),
|
||||
contestants => contestants,
|
||||
winners => winners,
|
||||
contestants_amount => contestants.len(),
|
||||
winners_amount => winners.len(),
|
||||
))
|
||||
.map_err(TemplateError::from)?
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(Box::new(r))
|
||||
}
|
||||
|
||||
async fn styles() -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let r = Response::new(include_str!("../../data/www/styles.css").to_string().into());
|
||||
Ok(Box::new(r))
|
||||
}
|
||||
|
||||
async fn handle_rejection(
|
||||
err: reject::Rejection,
|
||||
) -> Result<impl warp::reply::Reply, std::convert::Infallible> {
|
||||
let code;
|
||||
let message;
|
||||
info!("rejecting: {err:?}");
|
||||
|
||||
if err.is_not_found() {
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "page not found";
|
||||
} else if let Some(e) = err.find::<TemplateError>() {
|
||||
error!("templating error: {e}");
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "could not process data to make a page";
|
||||
} else {
|
||||
error!("unhandled rejection: {:?}", err);
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "unhandled rejection";
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(message, code))
|
||||
}
|
|
@ -3,27 +3,24 @@
|
|||
//! This challenge is designed to be simple and straightforward, requiring the contestant to only
|
||||
//! establish a TCP connection to the server to receive the secret.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use libpt::log::warn;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use super::{ChallengeDesc, ChallengeLike};
|
||||
use super::{Challenge, Descriptions};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
/// This struct holds the configuration and vault for the challenge.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct C1 {
|
||||
config: Config,
|
||||
vault: VaultRef,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ChallengeLike for C1 {
|
||||
impl Challenge for C1 {
|
||||
fn config(&self) -> Config {
|
||||
self.config.clone()
|
||||
}
|
||||
|
@ -34,19 +31,17 @@ impl ChallengeLike for C1 {
|
|||
Self { config, vault }
|
||||
}
|
||||
|
||||
fn text(&self) -> ChallengeDesc {
|
||||
ChallengeDesc::new(
|
||||
1,
|
||||
"dumb TCP",
|
||||
&["TCP connect to 1337."],
|
||||
"Connect by TCP, then the secret will be sent to you.",
|
||||
"Do you know how to TCP?",
|
||||
&self.config.addr,
|
||||
)
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
title: "dumb TCP".to_string(),
|
||||
hints: vec![String::from("TCP connect to 1337.")],
|
||||
solution: String::from("Connect by TCP, then the secret will be sent to you."),
|
||||
description: String::from("Do you know how to TCP?"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve(&self, addr: &SocketAddr) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
async fn serve(&self) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(self.config.addr).await?;
|
||||
|
||||
loop {
|
||||
let vault = self.vault.clone();
|
||||
|
|
|
@ -4,21 +4,18 @@
|
|||
//! This challenge is designed to be simple and straightforward, but still requires some thinkering
|
||||
//! for newbies, especially those that sill only think in text.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use libpt::log::{info, warn};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use super::{ChallengeDesc, ChallengeLike};
|
||||
use super::{Challenge, Descriptions};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
/// This struct holds the configuration and vault for the challenge.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct C2 {
|
||||
config: Config,
|
||||
vault: VaultRef,
|
||||
|
@ -49,7 +46,7 @@ impl C2 {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ChallengeLike for C2 {
|
||||
impl Challenge for C2 {
|
||||
fn config(&self) -> Config {
|
||||
self.config.clone()
|
||||
}
|
||||
|
@ -59,19 +56,19 @@ impl ChallengeLike for C2 {
|
|||
fn new(config: Config, vault: VaultRef) -> Self {
|
||||
Self { config, vault }
|
||||
}
|
||||
fn text(&self) -> ChallengeDesc {
|
||||
ChallengeDesc::new(
|
||||
2,
|
||||
"TCP dialogue",
|
||||
&["TCP connect to 1337 and give me a special u16"],
|
||||
"Connect by TCP, send 1337 as bytes (not text).",
|
||||
"Do you know how to TCP, but slightly cooler than for C1?",
|
||||
&self.config.addr,
|
||||
)
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
title: "TCP dialogue".to_string(),
|
||||
hints: vec![String::from(
|
||||
"TCP connect to 1337 and give me a special u16",
|
||||
)],
|
||||
solution: String::from("Connect by TCP, send 1337 as bytes (not text)."),
|
||||
description: String::from("Do you know how to TCP, but slightly cooler than for C1?"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve(&self, addr: &SocketAddr) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
async fn serve(&self) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(self.config.addr).await?;
|
||||
|
||||
loop {
|
||||
let vault = self.vault.clone();
|
||||
|
|
|
@ -22,14 +22,14 @@ use std::net::SocketAddr;
|
|||
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use libpt::log::{debug, warn};
|
||||
use libpt::log::{debug, info, warn};
|
||||
use rand::distributions::{Distribution, Standard};
|
||||
use rand::{random, Rng};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::time::Instant;
|
||||
|
||||
use super::{ChallengeDesc, ChallengeLike};
|
||||
use super::{Challenge, Descriptions};
|
||||
use crate::config::Config;
|
||||
use crate::has_won;
|
||||
use crate::vault::VaultRef;
|
||||
|
@ -40,7 +40,7 @@ pub const NEEDED_CORRECT: usize = 16;
|
|||
pub const MILLIS_PER_QUESTION: u128 = 200;
|
||||
|
||||
/// Signifies the operations that the randomiser can come up with.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone)]
|
||||
enum Operation {
|
||||
Add,
|
||||
Sub,
|
||||
|
@ -90,7 +90,6 @@ impl Distribution<Operation> for Standard {
|
|||
}
|
||||
|
||||
/// This struct holds the configuration and vault for the challenge.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct C3 {
|
||||
config: Config,
|
||||
vault: VaultRef,
|
||||
|
@ -207,7 +206,7 @@ impl C3 {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ChallengeLike for C3 {
|
||||
impl Challenge for C3 {
|
||||
fn config(&self) -> Config {
|
||||
self.config.clone()
|
||||
}
|
||||
|
@ -217,23 +216,22 @@ impl ChallengeLike for C3 {
|
|||
fn new(config: Config, vault: VaultRef) -> Self {
|
||||
Self { config, vault }
|
||||
}
|
||||
fn text(&self) -> ChallengeDesc {
|
||||
ChallengeDesc::new(
|
||||
3,
|
||||
"TCP math exam",
|
||||
&[
|
||||
"TCP connect to 1337 and answer the questions.",
|
||||
"The questions keep chaning.",
|
||||
"You should try to solve the questions in an automated way.",
|
||||
fn text() -> Descriptions {
|
||||
Descriptions {
|
||||
title: "TCP math exam".to_string(),
|
||||
hints: vec![
|
||||
"TCP connect to 1337 and answer the questions.".to_string(),
|
||||
"The questions keep chaning.".to_string(),
|
||||
"You should try to solve the questions in an automated way.".to_string(),
|
||||
],
|
||||
"Connect by TCP, programmatically answer the questions.",
|
||||
"How fast can you do arithmetics?",
|
||||
&self.config.addr,
|
||||
)
|
||||
solution: String::from("Connect by TCP, programmatically answer the questions."),
|
||||
description: String::from("How fast can you do arithmetics?"),
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve(&self, addr: &SocketAddr) -> anyhow::Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
async fn serve(&self) -> anyhow::Result<()> {
|
||||
info!("serving challenge 3");
|
||||
let listener = TcpListener::bind(self.config.addr).await?;
|
||||
|
||||
loop {
|
||||
let vault = self.vault.clone();
|
||||
|
|
|
@ -3,57 +3,28 @@
|
|||
//! This module is the core of the Wooly Vault application, as it defines the interface that all
|
||||
//! challenges must implement, and contains the challenge modules themselves.
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use libpt::log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
#[cfg(feature = "admin-interface")]
|
||||
pub mod admin;
|
||||
|
||||
pub mod c1;
|
||||
pub mod c2;
|
||||
pub mod c3;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct ChallengeDesc {
|
||||
id: usize,
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Descriptions {
|
||||
title: String,
|
||||
hints: Vec<String>,
|
||||
solution: String,
|
||||
description: String,
|
||||
addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl ChallengeDesc {
|
||||
pub fn new(
|
||||
id: usize,
|
||||
title: &str,
|
||||
hints: &[&str],
|
||||
solution: &str,
|
||||
description: &str,
|
||||
base_addr: &SocketAddr,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
title: title.to_string(),
|
||||
hints: hints.iter().map(|a| a.to_string()).collect(),
|
||||
solution: solution.to_string(),
|
||||
description: description.to_string(),
|
||||
addr: SocketAddr::new(base_addr.ip(), base_addr.port() + id as u16 - 1),
|
||||
}
|
||||
}
|
||||
/// Returns a [`SocketAddr`] for the challenge.
|
||||
///
|
||||
/// The challenge will be hosted on this [`SocketAddr`].
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The [`SocketAddr`] that the challenge is hosted on.
|
||||
pub fn addr(&self) -> &SocketAddr {
|
||||
&self.addr
|
||||
}
|
||||
impl Descriptions {
|
||||
/// Returns a list of hints for the challenge.
|
||||
///
|
||||
/// A hint is a short text to be given to the contestants in case the admin thinks they need
|
||||
|
@ -61,8 +32,8 @@ impl ChallengeDesc {
|
|||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A [`Vec<String>`] containing hints for the challenge.
|
||||
pub fn hints(&self) -> Vec<&str> {
|
||||
/// A vector of strings containing hints for the challenge.
|
||||
fn hints(&self) -> Vec<&str> {
|
||||
self.hints.iter().map(|a| a.as_ref()).collect()
|
||||
}
|
||||
/// Returns the solution to the challenge.
|
||||
|
@ -72,8 +43,8 @@ impl ChallengeDesc {
|
|||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A [`String`] containing the solution to the challenge.
|
||||
pub fn solution(&self) -> &str {
|
||||
/// A string containing the solution to the challenge.
|
||||
fn solution(&self) -> &str {
|
||||
&self.solution
|
||||
}
|
||||
/// Returns the description to the challenge.
|
||||
|
@ -83,8 +54,8 @@ impl ChallengeDesc {
|
|||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A [`String`] containing the description to the challenge.
|
||||
pub fn description(&self) -> &str {
|
||||
/// A string containing the description to the challenge.
|
||||
fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
/// Returns the title to the challenge.
|
||||
|
@ -93,40 +64,26 @@ impl ChallengeDesc {
|
|||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A [`String`] containing the title to the challenge.
|
||||
pub fn title(&self) -> &str {
|
||||
/// A string containing the title to the challenge.
|
||||
fn title(&self) -> &str {
|
||||
&self.title
|
||||
}
|
||||
/// Returns the id to the challenge.
|
||||
///
|
||||
/// The id is just the index of the challenge to distinguish them easily
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A [`usize`] containing the title to the challenge.
|
||||
pub fn id(&self) -> usize {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the behavior of a challenge.
|
||||
///
|
||||
/// Any type that implements this trait can be served as a challenge in the Wooly Vault application.
|
||||
#[async_trait]
|
||||
pub trait ChallengeLike
|
||||
pub trait Challenge
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
Self: Send,
|
||||
Self: Sync,
|
||||
Self: Clone,
|
||||
Self: std::fmt::Debug,
|
||||
Self: Sized,
|
||||
{
|
||||
/// Getter for the [vault](VaultRef).
|
||||
fn vault(&self) -> VaultRef;
|
||||
/// Getter for the [Config].
|
||||
fn config(&self) -> Config;
|
||||
/// Get the various texts for this challenge.
|
||||
fn text(&self) -> ChallengeDesc;
|
||||
fn text() -> Descriptions;
|
||||
/// Creates a new instance of the challenge with the given configuration and vault.
|
||||
///
|
||||
/// # Arguments
|
||||
|
@ -150,7 +107,7 @@ where
|
|||
/// # Errors
|
||||
///
|
||||
/// Will error when the challenge errors, for example when the network adress cannot be bound.
|
||||
async fn serve(&self, addr: &SocketAddr) -> anyhow::Result<()>;
|
||||
async fn serve(&self) -> anyhow::Result<()>;
|
||||
/// Serves a challenge and sets up the hint and monitoring service for the admin.
|
||||
///
|
||||
/// This method not only serves the challenge, but it also sets up a small webservice for the
|
||||
|
@ -165,16 +122,54 @@ where
|
|||
/// # Returns
|
||||
///
|
||||
/// A result indicating whether the challenge and the admin interface were successfully served.
|
||||
async fn start(&self, addr: &SocketAddr) -> anyhow::Result<()> {
|
||||
let c = self.clone();
|
||||
let data = self.text();
|
||||
let addr = addr.to_owned();
|
||||
tokio::spawn(async move {
|
||||
info!("starting C{}", data.id());
|
||||
if let Err(e) = c.serve(&addr).await {
|
||||
error!("challenge {} has crashed! {e:#?}", data.title());
|
||||
};
|
||||
});
|
||||
async fn setup_and_start(self) -> anyhow::Result<()> {
|
||||
#[cfg(feature = "admin-interface")]
|
||||
if self.config().addr_admin.is_some() {
|
||||
let vault = self.vault();
|
||||
let config = self.config();
|
||||
tokio::spawn(Self::admin_interface(Self::text(), vault, config));
|
||||
}
|
||||
|
||||
let challenge_handle = self.serve();
|
||||
Ok(challenge_handle.await?)
|
||||
}
|
||||
#[cfg(feature = "admin-interface")]
|
||||
async fn admin_interface(
|
||||
text: Descriptions,
|
||||
vault: VaultRef,
|
||||
config: Config,
|
||||
) -> anyhow::Result<()> {
|
||||
admin::serve(text, vault, config).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Selects a challenge by index and serves it with the given configuration and vault.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index` - The index of the challenge to select.
|
||||
/// * `config` - The configuration for the challenge.
|
||||
/// * `vault` - The vault that holds the secret for the challenge.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A result indicating whether the challenge has successfully ended.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if no challenge with the given index exists, or if the challenge that is being
|
||||
/// served errors.
|
||||
pub async fn select_and_start(index: u16, config: Config, vault: VaultRef) -> anyhow::Result<()> {
|
||||
match index {
|
||||
1 => c1::C1::new(config, vault).setup_and_start().await?,
|
||||
2 => c2::C2::new(config, vault).setup_and_start().await?,
|
||||
3 => c3::C3::new(config, vault).setup_and_start().await?,
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"no challenge with index {index} does currently exist"
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -46,14 +46,13 @@ pub const ENV_SECRET: &str = "WOOLY_SECRET";
|
|||
long_about,
|
||||
help_template = libpt::cli::args::HELP_TEMPLATE)]
|
||||
pub struct Config {
|
||||
/// Network address to host the challenges on
|
||||
///
|
||||
/// The port will be the port for challenge 0. Challenge 1 will be hosted on port+1 and so on.
|
||||
#[arg(short = 'c', long = "challenge")]
|
||||
/// Index of the challenge
|
||||
pub challenge: u16,
|
||||
/// Network address to host the challenge on
|
||||
pub addr: SocketAddr,
|
||||
/// Network address to host the admin interface on
|
||||
/// Network address to host the challenge on
|
||||
#[arg(short = 'a', long = "admin")]
|
||||
pub addr_admin: SocketAddr,
|
||||
pub addr_admin: Option<SocketAddr>,
|
||||
#[command(flatten)]
|
||||
pub verbosity: VerbosityLevel,
|
||||
}
|
||||
|
@ -61,9 +60,10 @@ pub struct Config {
|
|||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
challenge: 1,
|
||||
addr: SocketAddr::from_str("127.0.0.1:1337").unwrap(),
|
||||
verbosity: VerbosityLevel::default(),
|
||||
addr_admin: SocketAddr::from_str("127.0.0.1:8080").unwrap(),
|
||||
addr_admin: Option::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
77
src/lib.rs
77
src/lib.rs
|
@ -14,17 +14,10 @@
|
|||
//! Wooly Vault is programmed asynchronously with [tokio] to be able to handle many contestants at
|
||||
//! once if needed.
|
||||
|
||||
use anyhow::anyhow;
|
||||
use libpt::log::info;
|
||||
|
||||
use self::challenge::ChallengeLike;
|
||||
use self::config::Config;
|
||||
use self::vault::VaultRef;
|
||||
|
||||
pub mod challenge;
|
||||
pub mod config;
|
||||
#[cfg(feature = "meta-interface")]
|
||||
pub mod meta;
|
||||
pub mod vault;
|
||||
|
||||
#[inline]
|
||||
|
@ -36,73 +29,3 @@ pub(crate) async fn has_won(
|
|||
vault.add_winner(contestant.clone()).await;
|
||||
libpt::log::info!("Sending the secret to {addr}")
|
||||
}
|
||||
|
||||
/// Selects a single challenge by index and serves it with the given configuration and vault.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `index` - The index of the challenge to select.
|
||||
/// * `config` - The configuration for the challenge.
|
||||
/// * `vault` - The vault that holds the secret for the challenge.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A result indicating whether the challenge has successfully ended.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if no challenge with the given index exists, or if the challenge that is being
|
||||
/// served errors.
|
||||
pub async fn select_and_start_single(
|
||||
index: u16,
|
||||
config: Config,
|
||||
vault: VaultRef,
|
||||
) -> anyhow::Result<()> {
|
||||
info!("select+start");
|
||||
match index {
|
||||
1 => {
|
||||
challenge::c1::C1::new(config.clone(), vault)
|
||||
.start(&config.addr)
|
||||
.await?
|
||||
}
|
||||
2 => {
|
||||
challenge::c2::C2::new(config.clone(), vault)
|
||||
.start(&config.addr)
|
||||
.await?
|
||||
}
|
||||
3 => {
|
||||
challenge::c3::C3::new(config.clone(), vault)
|
||||
.start(&config.addr)
|
||||
.await?
|
||||
}
|
||||
_ => {
|
||||
return Err(anyhow!(
|
||||
"no challenge with index {index} does currently exist"
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: actually use different vaults for the challenges #7
|
||||
pub async fn start_all(config: Config, vault: VaultRef) -> anyhow::Result<()> {
|
||||
let c1 = challenge::c1::C1::new(config.clone(), vault.clone());
|
||||
let c2 = challenge::c2::C2::new(config.clone(), vault.clone());
|
||||
let c3 = challenge::c3::C3::new(config.clone(), vault.clone());
|
||||
|
||||
c1.start(c1.text().addr()).await?;
|
||||
c2.start(c2.text().addr()).await?;
|
||||
c3.start(c3.text().addr()).await?;
|
||||
|
||||
meta::serve(
|
||||
vec![
|
||||
(c1.text(), vault.clone()),
|
||||
(c2.text(), vault.clone()),
|
||||
(c3.text(), vault.clone()),
|
||||
],
|
||||
config.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@ use anyhow::Result;
|
|||
use clap::Parser;
|
||||
use libpt::log::{debug, info};
|
||||
|
||||
use wooly_vault::start_all;
|
||||
use wooly_vault::{config::Config, vault::Vault};
|
||||
use wooly_vault::{challenge::select_and_start, config::Config, vault::Vault};
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<()> {
|
||||
|
@ -22,9 +21,7 @@ async fn main() -> Result<()> {
|
|||
let secret = Config::secret()?;
|
||||
let v = Vault::new(&secret);
|
||||
|
||||
start_all(conf, v).await?;
|
||||
select_and_start(conf.challenge, conf, v).await?;
|
||||
|
||||
loop {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use libpt::log::tracing;
|
||||
use minijinja::context;
|
||||
use warp::reject::Rejection;
|
||||
use warp::reply::Reply;
|
||||
use warp::reply::Response;
|
||||
use warp::Filter;
|
||||
|
||||
use crate::challenge::ChallengeDesc;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
use super::default_context;
|
||||
use super::errors::TemplateError;
|
||||
use super::Service;
|
||||
|
||||
fn with_serv(
|
||||
serv: Arc<Service>,
|
||||
) -> impl Filter<Extract = (Arc<Service>,), Error = std::convert::Infallible> + Clone {
|
||||
warp::any().map(move || serv.clone())
|
||||
}
|
||||
|
||||
impl<'tp> Service<'tp> {
|
||||
pub fn admin_routes(
|
||||
this: Arc<Self>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone + 'tp {
|
||||
let serv = this.clone();
|
||||
warp::path!("admin" / "challenge" / usize)
|
||||
.map(move |id: usize| serv.challenges[id - 1].clone())
|
||||
.and(with_serv(this.clone()))
|
||||
.and_then(details)
|
||||
.with(warp::trace(|info| {
|
||||
// Create a span using tracing macros
|
||||
tracing::info_span!(
|
||||
"admin",
|
||||
method = %info.method(),
|
||||
path = %info.path(),
|
||||
)
|
||||
}))
|
||||
.or(warp::path!("admin").and(with_serv(this.clone()).and_then(index)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn details(
|
||||
data: (ChallengeDesc, VaultRef),
|
||||
serv: Arc<Service<'_>>,
|
||||
) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let challenge = data.0;
|
||||
let vault = data.1;
|
||||
let contestants = vault.contestants().await.into_iter().collect::<Vec<_>>();
|
||||
let winners = vault.winners().await.into_iter().collect::<Vec<_>>();
|
||||
let r = Response::new(
|
||||
serv.env
|
||||
.get_template("admin:details")
|
||||
.map_err(TemplateError::from)?
|
||||
.render(context!(
|
||||
..default_context(),
|
||||
..context!(
|
||||
challenge => challenge,
|
||||
secret => vault.secret(),
|
||||
title => "Wooly-Vault Admin Interface",
|
||||
contestants => vault.contestants().await.iter().collect::<Vec<_>>(),
|
||||
winners => vault.winners().await.iter().collect::<Vec<_>>(),
|
||||
contestants => contestants,
|
||||
winners => winners,
|
||||
contestants_amount => contestants.len(),
|
||||
winners_amount => winners.len(),
|
||||
)
|
||||
))
|
||||
.map_err(TemplateError::from)?
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(Box::new(r))
|
||||
}
|
||||
|
||||
async fn index(serv: Arc<Service<'_>>) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let challenges = serv
|
||||
.challenges
|
||||
.iter()
|
||||
.map(|v| v.0.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let r = Response::new(
|
||||
serv.env
|
||||
.get_template("admin:index")
|
||||
.map_err(TemplateError::from)?
|
||||
.render(context!(
|
||||
..default_context(),
|
||||
..context!(
|
||||
title => "Wooly-Vault Admin Interface",
|
||||
challenges => challenges,
|
||||
challenges_amount => challenges.len(),
|
||||
)
|
||||
))
|
||||
.map_err(TemplateError::from)?
|
||||
.into(),
|
||||
);
|
||||
Ok(Box::new(r))
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use libpt::log::{error, info};
|
||||
use thiserror::Error;
|
||||
use warp::http::StatusCode;
|
||||
use warp::reject;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct TemplateError(#[from] minijinja::Error);
|
||||
|
||||
impl reject::Reject for TemplateError {}
|
||||
impl Display for TemplateError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"something went wrong with producing this page: {self:#?}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_rejection(
|
||||
err: reject::Rejection,
|
||||
) -> Result<impl warp::reply::Reply, std::convert::Infallible> {
|
||||
let code;
|
||||
let message;
|
||||
info!("rejecting: {err:?}");
|
||||
|
||||
if err.is_not_found() {
|
||||
code = StatusCode::NOT_FOUND;
|
||||
message = "page not found";
|
||||
} else if let Some(e) = err.find::<TemplateError>() {
|
||||
error!("templating error: {e}");
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "could not process data to make a page";
|
||||
} else {
|
||||
error!("unhandled rejection: {:?}", err);
|
||||
code = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
message = "unhandled rejection";
|
||||
}
|
||||
|
||||
Ok(warp::reply::with_status(message, code))
|
||||
}
|
103
src/meta/mod.rs
103
src/meta/mod.rs
|
@ -1,103 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use libpt::log::tracing;
|
||||
use libpt::log::warn;
|
||||
use minijinja::context;
|
||||
use minijinja::Environment;
|
||||
use warp::reject::Rejection;
|
||||
use warp::reply::Reply;
|
||||
use warp::reply::Response;
|
||||
use warp::Filter;
|
||||
|
||||
use crate::challenge::ChallengeDesc;
|
||||
use crate::config::Config;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
use self::errors::handle_rejection;
|
||||
|
||||
pub mod admin;
|
||||
pub mod errors;
|
||||
pub mod user;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Service<'tp> {
|
||||
config: Config,
|
||||
env: Environment<'tp>,
|
||||
challenges: Vec<(ChallengeDesc, VaultRef)>,
|
||||
}
|
||||
impl<'tp> Service<'tp> {
|
||||
fn build(config: Config, challenges: Vec<(ChallengeDesc, VaultRef)>) -> Result<Arc<Self>> {
|
||||
let mut env = Environment::new();
|
||||
env.add_template("base", include_str!("../../data/www/base.html"))?;
|
||||
|
||||
env.add_template(
|
||||
"comp:contestants_winners",
|
||||
include_str!("../../data/www/components/contestants_winners.html"),
|
||||
)?;
|
||||
|
||||
env.add_template("admin:base", include_str!("../../data/www/admin/base.html"))?;
|
||||
env.add_template(
|
||||
"admin:index",
|
||||
include_str!("../../data/www/admin/index.html"),
|
||||
)?;
|
||||
env.add_template(
|
||||
"admin:details",
|
||||
include_str!("../../data/www/admin/details.html"),
|
||||
)?;
|
||||
|
||||
env.add_template("user:base", include_str!("../../data/www/user/base.html"))?;
|
||||
env.add_template("user:index", include_str!("../../data/www/user/index.html"))?;
|
||||
env.add_template(
|
||||
"user:details",
|
||||
include_str!("../../data/www/user/details.html"),
|
||||
)?;
|
||||
Ok(Self {
|
||||
config,
|
||||
env,
|
||||
challenges,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serve(challenges: Vec<(ChallengeDesc, VaultRef)>, config: Config) -> Result<()> {
|
||||
let service = Service::build(config, challenges)?;
|
||||
|
||||
let routes = Service::admin_routes(service.clone())
|
||||
.or(Service::ressources_routes())
|
||||
.or(Service::user_routes(service.clone()))
|
||||
.recover(handle_rejection)
|
||||
.with(warp::trace(|info| {
|
||||
// Create a span using tracing macros
|
||||
tracing::info_span!(
|
||||
"admin",
|
||||
method = %info.method(),
|
||||
path = %info.path(),
|
||||
)
|
||||
}));
|
||||
|
||||
warp::serve(routes).run(service.config.addr_admin).await;
|
||||
|
||||
warn!("exited the admin interface");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Service<'_> {
|
||||
fn ressources_routes() -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
|
||||
warp::path("styles.css").and_then(styles)
|
||||
}
|
||||
}
|
||||
|
||||
async fn styles() -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let r = Response::new(include_str!("../../data/www/styles.css").to_string().into());
|
||||
Ok(Box::new(r))
|
||||
}
|
||||
|
||||
pub fn default_context() -> minijinja::Value {
|
||||
context!(
|
||||
app_name => "Wooly-Vault",
|
||||
author => env!("CARGO_PKG_AUTHORS"),
|
||||
year => "2024",
|
||||
)
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use libpt::log::tracing;
|
||||
use minijinja::context;
|
||||
use warp::reject::Rejection;
|
||||
use warp::reply::Reply;
|
||||
use warp::reply::Response;
|
||||
use warp::Filter;
|
||||
|
||||
use crate::challenge::ChallengeDesc;
|
||||
use crate::vault::VaultRef;
|
||||
|
||||
use super::default_context;
|
||||
use super::errors::TemplateError;
|
||||
use super::Service;
|
||||
|
||||
fn with_serv(
|
||||
serv: Arc<Service>,
|
||||
) -> impl Filter<Extract = (Arc<Service>,), Error = std::convert::Infallible> + Clone {
|
||||
warp::any().map(move || serv.clone())
|
||||
}
|
||||
|
||||
impl<'tp> Service<'tp> {
|
||||
pub fn user_routes(
|
||||
this: Arc<Self>,
|
||||
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone + 'tp {
|
||||
let serv = this.clone();
|
||||
warp::path!("challenge" / usize)
|
||||
.map(move |id: usize| serv.challenges[id - 1].clone())
|
||||
.and(with_serv(this.clone()))
|
||||
.and_then(details)
|
||||
.with(warp::trace(|info| {
|
||||
// Create a span using tracing macros
|
||||
tracing::info_span!(
|
||||
"admin",
|
||||
method = %info.method(),
|
||||
path = %info.path(),
|
||||
)
|
||||
}))
|
||||
.or(warp::path::end().and(with_serv(this.clone()).and_then(index)))
|
||||
}
|
||||
}
|
||||
|
||||
async fn details(
|
||||
data: (ChallengeDesc, VaultRef),
|
||||
serv: Arc<Service<'_>>,
|
||||
) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let challenge = data.0;
|
||||
let vault = data.1;
|
||||
let contestants = vault.contestants().await.into_iter().collect::<Vec<_>>();
|
||||
let winners = vault.winners().await.into_iter().collect::<Vec<_>>();
|
||||
let r = Response::new(
|
||||
serv.env
|
||||
.get_template("user:details")
|
||||
.map_err(TemplateError::from)?
|
||||
.render(context!(
|
||||
..default_context(),
|
||||
..context!(
|
||||
challenge => challenge,
|
||||
title => "Wooly-Vault User Interface",
|
||||
contestants => vault.contestants().await.iter().collect::<Vec<_>>(),
|
||||
winners => vault.winners().await.iter().collect::<Vec<_>>(),
|
||||
contestants => contestants,
|
||||
winners => winners,
|
||||
contestants_amount => contestants.len(),
|
||||
winners_amount => winners.len(),
|
||||
)
|
||||
))
|
||||
.map_err(TemplateError::from)?
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(Box::new(r))
|
||||
}
|
||||
|
||||
async fn index(serv: Arc<Service<'_>>) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
|
||||
let challenges = serv
|
||||
.challenges
|
||||
.iter()
|
||||
.map(|v| v.0.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let r = Response::new(
|
||||
serv.env
|
||||
.get_template("user:index")
|
||||
.map_err(TemplateError::from)?
|
||||
.render(context!(
|
||||
..default_context(),
|
||||
..context!(
|
||||
title => "Wooly-Vault User Interface",
|
||||
challenges => challenges,
|
||||
challenges_amount => challenges.len(),
|
||||
)
|
||||
))
|
||||
.map_err(TemplateError::from)?
|
||||
.into(),
|
||||
);
|
||||
Ok(Box::new(r))
|
||||
}
|
|
@ -17,7 +17,7 @@ use tokio::sync::Mutex;
|
|||
/// This type is used to share a [`Vault`] instance across multiple parts of the application.
|
||||
pub type VaultRef = Arc<Vault>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Contestant {
|
||||
ip: IpAddr,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue