Skip to content

Adding an automatic flag checking page #67

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion build/ansible/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
git:
repo: "https://github.com/TheCoreMan/make-git-better-2.git"
dest: /home/{{ ansible_facts['user_id'] }}/make-git-better-2
version: dev
version: 26/auto-hof-page
accept_hostkey: yes

- name: Compile rust
Expand All @@ -16,6 +16,13 @@
shell: docker build --tag mgb:0.1 --build-arg CACHE_DATE=$(date +%Y-%m-%d:%H:%M:%S) .
args:
chdir: /home/{{ ansible_facts['user_id'] }}/make-git-better-2
become: yes

- name: Docker housekeeping, delete untagged built docker images to save space
shell: docker rmi $(docker images | grep "^<none>" | awk '{ print $3 }')
# Can ignore errors if no such images exist
ignore_errors: true
become: yes

- name: Clone docker-tcp-switchboard
git:
Expand Down Expand Up @@ -53,3 +60,25 @@
# when it finishes running. The nohup is there to prevent it, and the redirections
# prevent breaking the process.
become: yes

- name: Submit Flags - Install wasm requirements 1
yum:
name:
- openssl-devel
- pkgconfig
become: yes

- name: Submit Flags - Install wasm-pack
command: /home/{{ ansible_facts['user_id'] }}/.cargo/bin/cargo install wasm-pack

- name: Submit Flags - Build wasm
shell: /home/{{ ansible_facts['user_id'] }}/.cargo/bin/wasm-pack build --target web --out-name wasm --out-dir ./static
args:
chdir: /home/{{ ansible_facts['user_id'] }}/make-git-better-2/submit-flags/submit-flags-app

- name: Submit Flags - Install HTTP server
command: /home/{{ ansible_facts['user_id'] }}/.cargo/bin/cargo install simple-http-server

- name: Submit Flags - Run HTTP server
shell: nohup /home/{{ ansible_facts['user_id'] }}/.cargo/bin/simple-http-server --index --nocache --port 1337 /home/{{ ansible_facts['user_id'] }}/make-git-better-2/submit-flags/submit-flags-app/static </dev/null >/dev/null 2>&1 &
become: yes
4 changes: 2 additions & 2 deletions levels/game-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ flags = ['log-5']
title = 'log-5'
branch = 'pamphletary-harnessing-petticoaterie'
solution_checker = 'hooks/checkers/log-5.sh'
flags = []
flags = ['log-flag-meteorical']

[[levels]]
title = 'merge-3'
Expand Down Expand Up @@ -107,7 +107,7 @@ flags = [
title = 'tag-2'
branch = 'individually-nonintroversive-chalcomancy'
solution_checker = 'hooks/checkers/tag-2.sh'
flags = []
flags = ['individually-nonintroversive-chalcomancy']

[[levels]]
title = 'hooks-1'
Expand Down
22 changes: 22 additions & 0 deletions submit-flags/submit-flags-app/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "submit-flags-app"
version = "0.1.0"
authors = ["Shay Nehmad <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
yew = "0.17"
wasm-bindgen = "0.2.67"
log = "0.4.6"
wasm-logger = "0.2.0"
sha2 = "0.9.1"
console_error_panic_hook = "0.1.6"
wasm-bindgen-futures = "0.4.18"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0.32"
30 changes: 30 additions & 0 deletions submit-flags/submit-flags-app/src/components/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use yew::callback::Callback;
use anyhow::Error;
use serde::{Serialize, Deserialize};
// todo move to same lib as scripts
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LevelInfo {
pub name: String,
pub flag: String,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct LevelsInfo {
pub levels: Vec<LevelInfo>,
}

#[derive(Debug)]
pub struct SingleFlagStatus {
pub level_name: String,
pub is_correct: bool
}

#[derive(Debug)]
pub enum MainPageMsg {
CheckSingleFlag(SingleFlagStatus),
// Fetch-related messages
GetFlagsResponse,
FlagsResponseReady(Result<LevelsInfo, Error>),
}

pub type CheckFlagCallback = Callback<SingleFlagStatus>;
58 changes: 58 additions & 0 deletions submit-flags/submit-flags-app/src/components/fetchflags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use anyhow::{anyhow, Error};

use yew::callback::Callback;
use yew::format::{Nothing};
use yew::services::fetch::{FetchService, FetchTask, Request, Response};

use super::common::LevelsInfo;

#[derive(Default)]
pub struct GetFlagsService {
file_path: &'static str,
}

impl GetFlagsService {
pub fn new(file_path: &'static str) -> Self {
Self {
file_path,
}
}

pub fn get_response(&mut self, callback: Callback<Result<LevelsInfo, Error>>) -> FetchTask {
let handler = move |response: Response<Result<String, Error>>| {
let (head, body) = response.into_parts();
if head.status.is_success() {
log::debug!("Response is a success");
let body_value = body.unwrap();
log::debug!("here's the body: {}", body_value);
let parsed = try_to_parse_levels_json(&body_value);
match parsed {
Ok(v) => {
log::debug!("JSON conversion went well! Found {} levels", v.levels.len());
callback.emit(Ok(v))
}
Err(e) => {
callback.emit(Err(anyhow!("{:?}", e)));
}
}
} else {
callback.emit(Err(anyhow!(
"{}: error getting levels from server",
head.status
)))
}
};

// Local server
let url = format!("/{}", self.file_path);

let request = Request::get(url.clone().as_str()).header("Cache-Control", "no-cache").body(Nothing).unwrap();
log::debug!("Created get request to URI {}", request.uri());
FetchService::fetch(request, handler.into()).unwrap()
}
}

fn try_to_parse_levels_json(data: &str) -> Result<LevelsInfo, serde_json::Error> {
let parsed: LevelsInfo = serde_json::from_str(data)?;
Ok(parsed)
}
176 changes: 176 additions & 0 deletions submit-flags/submit-flags-app/src/components/level.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
use log;

use sha2::{Sha256, Digest};

use yew::prelude::{Component, ComponentLink, Properties, html, Html, ShouldRender};
use yew::html::InputData;

use super::common::{SingleFlagStatus, CheckFlagCallback};


pub struct LevelComponent {
// The link enables us to interact (i.e. reqister callbacks and send messages) with the component itself. See https://yew.rs/docs/en/concepts/components/#create
link: ComponentLink<Self>,
// The level's name. This is so the user knows which flag belongs where
name: String,
// The flag itself. In the future this will become a hash so that the users can't get the flags using devtools.
flag: String,
// The user's guess for the flag, that they are typing
user_flag: String,
// Whether the correct flag has been entered.
is_flag_correct: bool,
// Callback to update parent that flag has been solved
check_callback: CheckFlagCallback,
}

// These are the messages (think "events") that can happen in this component.
pub enum LevelMsg {
// This message indicates that it's time to check the user flag to see if it's the correct one.
CheckFlag,
// This message indicates that the user changed the flg they're guessing (when they're typing). Since we need to pass a value, this message has a parameter - see the `view` and `update` methods to see how this is used.
UserFlagChanged(String),
}

// See https://yew.rs/docs/en/concepts/components/properties/
// The properties allow enable child and parent components to communicate with each other.
// The parent of a level component is the page itself.
#[derive(Clone, PartialEq, Properties)]
pub struct LevelProps {
// This prop is the level's name. Passed from parent and won't change
pub name: String,
// This prop is the level's flag. Passed from parent and won't change
pub flag: String,
// This prop indicates whether the user's flag is correct. Not passed from parent, but rather used to communicate back to it from the level.
#[prop_or(false)]
pub is_flag_correct: bool,
// Callback to update parent that flag has been solved
pub check_callback: CheckFlagCallback,
}

// See https://yew.rs/docs/en/concepts/components/
// `Component` is a Trait (see https://doc.rust-lang.org/book/ch10-02-traits.html), defined here: https://github.com/yewstack/yew/blob/master/yew/src/html/mod.rs#L30
impl Component for LevelComponent {
// Overriding properties since we have our own.
type Properties = LevelProps;
// Overriding `Message` since we have our own messages.
type Message = LevelMsg;

// See https://yew.rs/docs/en/concepts/components/#create
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
log::debug!("Creating level {} component", props.name.clone());
Self {
link: link,
// Pass the name from the parent component
name: props.name,
// Pass the flag from the parent component
flag: props.flag,
// The initial user flag is empty
user_flag: "".to_string(),
// This has a default value of `false`. Not passed from parent
is_flag_correct: props.is_flag_correct,
// parent has to pass the callback
check_callback: props.check_callback,
}
}

// See https://yew.rs/docs/en/concepts/components/#update
fn update(&mut self, msg: Self::Message) -> ShouldRender {
// Do something different depending on the update message.
match msg {
LevelMsg::CheckFlag => {
// Hash the user flag to an array
let hashed_user_flag_arr = Sha256::digest(self.user_flag.as_bytes());
// Cast the array to a string
let hashed_user_flag_str: String = format!("{:x}", hashed_user_flag_arr);
// Compare user hash to our hash
self.is_flag_correct = hashed_user_flag_str == self.flag;

// update parent via callback
let status = SingleFlagStatus { level_name: self.name.clone(), is_correct: self.is_flag_correct };
self.check_callback.emit(status);

true // Re-render
}
LevelMsg::UserFlagChanged(new_user_flag) => {
log::debug!("update::{}, User flag changed from {} to {}", self.name.clone(), self.user_flag.clone(), new_user_flag.clone());
self.user_flag = new_user_flag;
self.update(LevelMsg::CheckFlag);
true // Re-render
}
}
}

// See https://yew.rs/docs/en/concepts/components/#change
// We're not using "change"
fn change(&mut self, _props: Self::Properties) -> ShouldRender{
log::debug!("Changing level {} component", self.name.clone());
false
}

// See https://yew.rs/docs/en/concepts/components/#view
// In this method we're declaring what the element looks like. This is very reminiscent of JSX and React.
fn view(&self) -> Html {
log::debug!("Viewing level {} component", self.name.clone());

// TODO - move to "create"
let label_text = self.name.clone() + "'s flag goes here 🚩";
let input_id = self.name.clone() + "-id";

// Creating the element as variables makes it clearer - similar to functional elements in react

// This element just prints the component info to make it easier to develop.
let _debug_info_element = html! {
<pre>
{
format!("DEBUG: I am a level component! Name: {} | Flag: {} | Status: {}",
self.name.clone(),
self.flag.clone(),
self.is_flag_correct)
}
<br/>
</pre>
};

// This element is the input for the flag.
let input_element = html! {
<div class="input-effect">
<input
id={ input_id.clone() }
/* Change the background colour effect according to the status. If the flag is correct, the class will be "effect-8 effect-10-good",
* which paints the BG of the text box green (and stays). Otherwise, paint it in red (as long as it's in focus).
*/
class={ format!("effect-8 effect-10-{}", if self.is_flag_correct { "good" } else { "bad" }) }
type="text"
placeholder={label_text.clone()}
// Whenever the user inputs something into the box, notify this LevelComponent that the user flag has changed.
oninput=self.link.callback(|e: InputData| LevelMsg::UserFlagChanged(e.value)) // <-- important line!
/>
// Cosmetics
<span class="focus-bg"></span><span class="focus-border"><i></i></span>
</div>
};

// This element is for a11y - don't indicate status with color only, but with an emoji as well.
let status_element = html! {
<pre class="status"> { get_correct_emoji(self.is_flag_correct) }</pre>
};

// This is the complete HTML component we're returning from `view`.
html! {
<span>
<div>
{ input_element }
{ status_element }
</div>
</span>
}
}
}

fn get_correct_emoji(correct: bool) -> String {
if correct {
return "✔".to_string();
} else {
return "❌".to_string();
}
}
Loading