Commit dded3b72 authored by Robert Czechowski's avatar Robert Czechowski

Merge branch 'sign-up' into 'master'

Sign up

Closes #23

See merge request !12
parents 24fee8a1 4203b9c0
Pipeline #811 passed with stages
in 13 minutes and 20 seconds
......@@ -17,7 +17,8 @@ server = ["rusqlite", "postgres"]
desktop = ["rusqlite", "webbrowser"]
watch = ["handlebars-iron/watch"]
strict = [] # Treat warnings as a build error
debug = []
debug = ["signup"]
signup = []
[dependencies]
time = "0.1.40"
......
......@@ -15,6 +15,8 @@
use time;
use db_conn::MedalConnection;
#[cfg(feature = "signup")]
use db_conn::SignupResult;
use db_objects::OptionSession;
use db_objects::SessionUser;
use db_objects::{Contest, Grade, Group, Participation, Submission, Taskgroup};
......@@ -53,6 +55,7 @@ pub enum MedalError {
DatabaseError,
PasswordHashingError,
UnmatchedPasswords,
NotFound,
}
pub struct LoginInfo {
......@@ -552,6 +555,35 @@ pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
session_token.map(|token| conn.logout(&token));
}
#[cfg(feature = "signup")]
pub fn signup<T: MedalConnection>(conn: &T, session_token:Option<String>, signup_data: (String, String, String)) -> MedalResult<SignupResult> {
let (username, email, password) = signup_data;
if username == "" || email == "" || password == "" {
return Ok(SignupResult::EmptyFields)
}
let salt = helpers::make_salt();
let hash = helpers::hash_password(&password, &salt)?;
let result = conn.signup(&session_token.unwrap(), &username, &email, hash, &salt);
Ok(result)
}
#[cfg(feature = "signup")]
pub fn signupdata (query_string: Option<String>) -> json_val::Map<String, json_val::Value> {
let mut data = json_val::Map::new();
if let Some(query) = query_string {
if query.starts_with("status=") {
let status: &str = &query[7..];
if ["EmailTaken", "UsernameTaken", "UserLoggedIn", "EmptyFields"].contains(&status) {
data.insert((status).to_string(), to_json(&true));
}
}
}
data
}
pub fn load_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, subtask: Option<String>)
-> MedalResult<String> {
let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
......@@ -888,7 +920,7 @@ pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id:
if let Some(query) = query_string {
if query.starts_with("status=") {
let status: &str = &query[7..];
if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin", "SignedUp"].contains(&status) {
data.insert((status).to_string(), to_json(&true));
}
}
......@@ -911,6 +943,7 @@ pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id:
.collect();
data.insert("participations".into(), to_json(&participations));
}
// Case user_id: teacher modifing a students profile
Some(user_id) => {
// TODO: Add test to check if this access restriction works
let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
......
......@@ -272,8 +272,10 @@ impl MedalConnection for Connection {
sex = $11,
is_admin = $12,
is_teacher = $13,
permanent_login = $14
WHERE id = $15",
permanent_login = $14,
email = $15,
email_unconfirmed = $16
WHERE id = $17",
&[&session.username,
&session.password,
&session.salt,
......@@ -288,6 +290,8 @@ impl MedalConnection for Connection {
&session.is_admin,
&session.is_teacher,
&session.permanent_login,
&session.email,
&session.email_unconfirmed,
&session.id])
.unwrap();
}
......@@ -565,6 +569,35 @@ impl MedalConnection for Connection {
self.execute(query, &[&session]).unwrap();
}
fn signup(&self, session_token: &str, username: &str, email: &str, password_hash: String, salt: &str) -> SignupResult {
let mut session_user = self.get_session_or_new(&session_token);
if session_user.is_logged_in() {
return SignupResult::UserLoggedIn
}
if let Ok(None) = self.query_map_one("SELECT username FROM session WHERE username = $1",
&[&username],
|row| -> Option<String> { row.get(0) }) {} else {
//This username already exists!
return SignupResult::UsernameTaken
}
if let Ok(None) = self.query_map_one("SELECT email, email_unconfirmed FROM session WHERE email = $1 OR email_unconfirmed = $1",
&[&email],
|row| -> (Option<String>, Option<String>) { (row.get(0), row.get(1)) }) {} else {
//This email already exists!
return SignupResult::EmailTaken
}
session_user.username = Some(username.to_string());
session_user.email_unconfirmed = Some(email.to_string());
session_user.password = Some(password_hash);
session_user.salt = Some(salt.to_string());
self.save_session(session_user);
SignupResult::SignedUp
}
fn load_submission(&self, session: &SessionUser, task: i32, subtask: Option<&str>) -> Option<Submission> {
match subtask {
None => {
......
......@@ -14,6 +14,15 @@
use db_objects::*;
#[derive(Debug)]
pub enum SignupResult {
SignedUp,
EmailTaken,
UsernameTaken,
UserLoggedIn,
EmptyFields,
}
/// This trait abstracts the database connection and provides function for all actions to be performed on the database
/// in the medal platform.
pub trait MedalConnection {
......@@ -64,6 +73,8 @@ pub trait MedalConnection {
/// to `NULL`.
fn logout(&self, session: &str);
fn signup(&self, session_token: &str, username: &str, email: &str, password_hash: String, salt: &str) -> SignupResult;
fn load_submission(&self, session: &SessionUser, task: i32, subtask: Option<&str>) -> Option<Submission>;
fn get_all_submissions(&self, session_id: i32, task: i32, subtask: Option<&str>) -> Vec<Submission>;
fn submit_submission(&self, submission: Submission);
......
......@@ -20,7 +20,7 @@ use postgres::Connection;
use time;
use time::Duration;
use db_conn::{MedalConnection, MedalObject};
use db_conn::{MedalConnection, MedalObject, SignupResult};
use db_objects::*;
use helpers;
......
......@@ -33,7 +33,7 @@ use postgres::Connection;
use time;
use time::Duration;
use db_conn::{MedalConnection, MedalObject};
use db_conn::{MedalConnection, MedalObject, SignupResult};
use db_objects::*;
use helpers;
......@@ -384,8 +384,10 @@ impl MedalConnection for Connection {
sex = $11,
is_admin = $12,
is_teacher = $13,
permanent_login = $14
WHERE id = $15",
permanent_login = $14,
email = $15,
email_unconfirmed = $16
WHERE id = $17",
&[&session.username,
&session.password,
&session.salt,
......@@ -400,6 +402,8 @@ impl MedalConnection for Connection {
&session.is_admin,
&session.is_teacher,
&session.permanent_login,
&session.email,
&session.email_unconfirmed,
&session.id])
.unwrap();
}
......@@ -677,6 +681,35 @@ impl MedalConnection for Connection {
self.execute(query, &[&session]).unwrap();
}
fn signup(&self, session_token: &str, username: &str, email: &str, password_hash: String, salt: &str) -> SignupResult {
let mut session_user = self.get_session_or_new(&session_token);
if session_user.is_logged_in() {
return SignupResult::UserLoggedIn
}
if let Ok(None) = self.query_map_one("SELECT username FROM session WHERE username = $1",
&[&username],
|row| -> Option<String> { row.get(0) }) {} else {
//This username already exists!
return SignupResult::UsernameTaken
}
if let Ok(None) = self.query_map_one("SELECT email, email_unconfirmed FROM session WHERE email = $1 OR email_unconfirmed = $1",
&[&email],
|row| -> (Option<String>, Option<String>) { (row.get(0), row.get(1)) }) {} else {
//This email already exists!
return SignupResult::EmailTaken
}
session_user.username = Some(username.to_string());
session_user.email_unconfirmed = Some(email.to_string());
session_user.password = Some(password_hash);
session_user.salt = Some(salt.to_string());
self.save_session(session_user);
SignupResult::SignedUp
}
fn load_submission(&self, session: &SessionUser, task: i32, subtask: Option<&str>) -> Option<Submission> {
match subtask {
None => {
......
......@@ -20,7 +20,7 @@ use rusqlite::Connection;
use time;
use time::Duration;
use db_conn::{MedalConnection, MedalObject};
use db_conn::{MedalConnection, MedalObject, SignupResult};
use db_objects::*;
use helpers;
......
......@@ -33,7 +33,7 @@ use rusqlite::Connection;
use time;
use time::Duration;
use db_conn::{MedalConnection, MedalObject};
use db_conn::{MedalConnection, MedalObject, SignupResult};
use db_objects::*;
use helpers;
......@@ -384,8 +384,10 @@ impl MedalConnection for Connection {
sex = ?11,
is_admin = ?12,
is_teacher = ?13,
permanent_login = ?14
WHERE id = ?15",
permanent_login = ?14,
email = ?15,
email_unconfirmed = ?16
WHERE id = ?17",
&[&session.username,
&session.password,
&session.salt,
......@@ -400,6 +402,8 @@ impl MedalConnection for Connection {
&session.is_admin,
&session.is_teacher,
&session.permanent_login,
&session.email,
&session.email_unconfirmed,
&session.id])
.unwrap();
}
......@@ -677,6 +681,35 @@ impl MedalConnection for Connection {
self.execute(query, &[&session]).unwrap();
}
fn signup(&self, session_token: &str, username: &str, email: &str, password_hash: String, salt: &str) -> SignupResult {
let mut session_user = self.get_session_or_new(&session_token);
if session_user.is_logged_in() {
return SignupResult::UserLoggedIn
}
if let Ok(None) = self.query_map_one("SELECT username FROM session WHERE username = ?1",
&[&username],
|row| -> Option<String> { row.get(0) }) {} else {
//This username already exists!
return SignupResult::UsernameTaken
}
if let Ok(None) = self.query_map_one("SELECT email, email_unconfirmed FROM session WHERE email = ?1 OR email_unconfirmed = ?1",
&[&email],
|row| -> (Option<String>, Option<String>) { (row.get(0), row.get(1)) }) {} else {
//This email already exists!
return SignupResult::EmailTaken
}
session_user.username = Some(username.to_string());
session_user.email_unconfirmed = Some(email.to_string());
session_user.password = Some(password_hash);
session_user.salt = Some(salt.to_string());
self.save_session(session_user);
SignupResult::SignedUp
}
fn load_submission(&self, session: &SessionUser, task: i32, subtask: Option<&str>) -> Option<Submission> {
match subtask {
None => {
......
......@@ -41,6 +41,9 @@ use db_conn::MedalConnection;
use iron::typemap::Key;
pub use serde_json::value as json_val;
#[cfg(feature = "signup")]
use db_conn::SignupResult;
static TASK_DIR: &str = "tasks";
macro_rules! mime {
......@@ -250,7 +253,7 @@ impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
fn from(AugMedalError(me, req): AugMedalError<'c, 'a, 'b>) -> Self {
match me {
core::MedalError::NotLoggedIn => {
IronError { error: Box::new(SessionError { message:
IronError { error: Box::new(SessionError { message:
"Not Logged in, redirecting to login page".to_string() }),
response: Response::with((status::Found,
RedirectRaw(format!("/login?{}", req.url.path().join("/"))))) }
......@@ -278,6 +281,10 @@ impl<'c, 'a, 'b> From<AugMedalError<'c, 'a, 'b>> for IronError {
"The two passwords did not match.".to_string() }),
response: Response::with(status::Forbidden) }
}
core::MedalError::NotFound => {
IronError { error: Box::new(SessionError { message: "Not found".to_string() }),
response: Response::with(status::NotFound) }
}
}
}
}
......@@ -573,9 +580,58 @@ fn logout<C>(req: &mut Request) -> IronResult<Response>
Ok(Response::with((status::Found, Redirect(url_for!(req, "greet")))))
}
#[cfg(feature = "signup")]
fn signup<C>(req: &mut Request) -> IronResult<Response>
where C: MedalConnection + std::marker::Send + 'static {
let query_string = req.url.query().map(|s| s.to_string());
let data = core::signupdata(query_string);
let mut resp = Response::new();
resp.set_mut(Template::new("signup", data)).set_mut(status::Ok);
Ok(resp)
}
#[cfg(feature = "signup")]
fn signup_post<C>(req: &mut Request) -> IronResult<Response>
where C: MedalConnection + std::marker::Send + 'static {
let session_token = req.get_session_token();
let signupdata = {
let formdata = itry!(req.get_ref::<UrlEncodedBody>());
(iexpect!(formdata.get("username"))[0].to_owned(), iexpect!(formdata.get("email"))[0].to_owned(), iexpect!(formdata.get("password"))[0].to_owned())
};
let signupresult = with_conn![core::signup, C, req, session_token, signupdata].aug(req)?;
match signupresult {
SignupResult::SignedUp =>
Ok(Response::with((status::Found,
Redirect(iron::Url::parse(&format!("{}?status={:?}",
&url_for!(req, "profile"),
signupresult)).unwrap())))),
_ =>
Ok(Response::with((status::Found,
Redirect(iron::Url::parse(&format!("{}?status={:?}",
&url_for!(req, "signup"),
signupresult)).unwrap()))))
}
}
#[cfg(not(feature = "signup"))]
fn signup<C>(req: &mut Request) -> IronResult<Response>
where C: MedalConnection + std::marker::Send + 'static {
Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
}
#[cfg(not(feature = "signup"))]
fn signup_post<C>(req: &mut Request) -> IronResult<Response>
where C: MedalConnection + std::marker::Send + 'static {
Err(core::MedalError::NotFound).aug(req).map_err(|x| x.into())
}
fn submission<C>(req: &mut Request) -> IronResult<Response>
where C: MedalConnection + std::marker::Send + 'static {
let task_id = req.expect_int::<i32>("taskid")?;
let session_token = req.expect_session_token()?;
let subtask: Option<String> = (|| -> Option<String> {
req.get_ref::<UrlEncodedQuery>().ok()?.get("subtask")?.get(0).map(|x| x.to_owned())
......@@ -1170,6 +1226,8 @@ pub fn start_server<C>(conn: C, config: Config) -> iron::error::HttpResult<iron:
login_post: post "/login" => login_post::<C>,
login_code_post: post "/clogin" => login_code_post::<C>,
logout: get "/logout" => logout::<C>,
signup: get "/signup" => signup::<C>,
signup_post: post "/signup" => signup_post::<C>,
subm: get "/submission/:taskid" => submission::<C>,
subm_post: post "/submission/:taskid" => submission_post::<C>,
subm_load: get "/load/:taskid" => submission::<C>,
......
......@@ -4,6 +4,11 @@
<h1>Benutzerdaten bearbeiten: {{profile_firstname}} {{profile_lastname}}</h1>
{{/if}}
{{#if SignedUp}}
<p style="color:green; font-weight:bold;">Du hast dir erfolgreich einen Account angelegt.<br/>
Auf dieser Seite kannst du weitere Angaben zu dir eintragen und dein Passwort ändern.</p>
{{/if}}
{{#if profile_username}}
<p>Benutzername: {{profile_username}}</p>
{{/if}}
......
<h1>Sign up</h1>
<div style="color:green; font-weight:bold">
{{#if UsernameTaken}}
<p>Dieser Benutzername existiert bereits. Bitte wähle einen anderen.</p>
{{/if}}
{{#if EmailTaken}}
<p>Diese E-Mail Adresse wird bereits von einem anderen Account verwendet. Bitte wähle eine andere.</p>
{{/if}}
{{#if UserLoggedIn}}
<p>Du bist bereits angemeldet. Wenn du einen neuen Account erstellen möchtest, dann melde dich zunächst ab.</p>
{{/if}}
{{#if EmptyFields}}
<p>Bitte fülle alle Felder aus, um dir einen Account anzulegen.</p>
{{/if}}
</div>
<p>
<form action="signup" method="post">
Beutzername:<br>
<input type="text" name="username" autofocus>
<br>
E-Mail Adresse:<br>
<input type="text" name="email">
<br>
Passwort:<br>
<input type="password" name="password" value="">
<br><br>
<input type="submit" value="sign up">
</form>
../default/signup.hbs
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment