From 2dbc6ad68a2a725904be3b8c805417736ef86b5c Mon Sep 17 00:00:00 2001 From: emilis Date: Tue, 13 Sep 2022 17:51:23 +0100 Subject: [PATCH] initial profile page work --- migrate/1662887862_schema.up.sql | 4 ++- src/database/users.rs | 17 ++++----- src/servek/html.rs | 62 ++++++++++++++++++++------------ src/servek/servek.rs | 1 + src/svc/auth.rs | 14 ++++++-- src/svc/profiles.rs | 15 +++++++- static/style/main.css | 52 +++++++++++++++++++++++++-- templates/html/profile.html | 14 ++++++-- 8 files changed, 138 insertions(+), 41 deletions(-) diff --git a/migrate/1662887862_schema.up.sql b/migrate/1662887862_schema.up.sql index 26cc7a8..fb2f16b 100644 --- a/migrate/1662887862_schema.up.sql +++ b/migrate/1662887862_schema.up.sql @@ -6,7 +6,9 @@ CREATE TABLE users ( host TEXT, display_name TEXT, email TEXT NOT NULL, - password_hash TEXT NOT NULL + password_hash TEXT NOT NULL, + avatar_uri TEXT, + bio TEXT, ); CREATE UNIQUE INDEX u_username_host ON users (username, host); diff --git a/src/database/users.rs b/src/database/users.rs index 163c2e8..9d8cbda 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -17,17 +17,10 @@ impl Users { pub async fn create_user(&self, u: User) -> Result { let row = self.0.lock().await.query_one( - "insert into users (id, username, host, display_name, password_hash, email) values ($1, $2, $3, $4, $5, $6) returning id", + "insert into users (id, username, host, display_name, password_hash, email) values ($1, $2, $3, $4, $5, $6) returning (id, username, host, display_name, password_hash, email, avatar_uri, bio)", &[&sec::new_id(), &u.username, &u.host, &u.display_name, &u.password_hash, &u.email], ).await?; - Ok(User { - id: row.get("id"), - username: u.username, - host: u.host, - display_name: u.display_name, - password_hash: u.password_hash, - email: u.email, - }) + Ok(User::from(&row)) } pub async fn user(&self, by: UserSelect) -> Result { @@ -52,7 +45,7 @@ impl Users { .await .query( format!( - "select id, username, host, display_name, password_hash, email from users where {}", + "select id, username, host, display_name, password_hash, email, avatar_uri, bio from users where {}", where_clause ) .as_str(), @@ -75,6 +68,8 @@ pub struct User { pub display_name: Option, pub password_hash: String, pub email: String, + pub avatar_uri: Option, + pub bio: Option, } impl From<&Row> for User { @@ -86,6 +81,8 @@ impl From<&Row> for User { display_name: row.get("display_name"), password_hash: row.get("password_hash"), email: row.get("email"), + avatar_uri: row.get("avatar_uri"), + bio: row.get("bio"), } } } diff --git a/src/servek/html.rs b/src/servek/html.rs index c741f69..6f5ea5a 100644 --- a/src/servek/html.rs +++ b/src/servek/html.rs @@ -1,20 +1,16 @@ -use std::{collections::HashMap, str::FromStr}; +use std; use axum::{ body::Full, extract::Path, - handler::Handler, http::{header, HeaderValue, StatusCode}, - response::{self, Html, IntoResponse, Response}, - routing, Extension, Form, Json, Router, + response::{self, IntoResponse, Response}, + routing, Extension, Form, Router, }; use mime_guess::mime; use tower_cookies::{Cookie, Cookies}; -use crate::svc::{ - auth::{AuthError, Claims}, - profiles, -}; +use crate::svc::auth::{AuthError, Claims}; use super::{ servek::{Server, ServerError}, @@ -45,15 +41,26 @@ impl Server { .fallback(routing::get(Self::handler_404)) } - fn from_cookie(&self, cookie: Option) -> Result>, ServerError> { - if let Some(cookie) = cookie { - let claims = self.auth.get_claims(cookie.value().to_owned())?; + fn from_cookies(&self, cookies: Cookies) -> Result>, ServerError> { + const logged_out: Result>, ServerError> = Ok(WithNav { + obj: None, + nav_type: NavType::LoggedOut, + }); + if let Some(cookie) = cookies.get(AUTH_COOKIE_NAME) { + let claims = match self.auth.get_claims(cookie.value().to_owned()) { + Ok(claims) => claims, + Err(e) => { + if e.clone().expired() { + cookies.remove(Cookie::new(AUTH_COOKIE_NAME, "")); + return logged_out; + } else { + return Err(e.into()); + } + } + }; Ok(WithNav::new(Some(claims.clone()), claims.into())) } else { - Ok(WithNav { - nav_type: NavType::LoggedOut, - obj: None, - }) + logged_out } } @@ -61,7 +68,7 @@ impl Server { Extension(srv): Extension, cookies: Cookies, ) -> Result { - let user = srv.from_cookie(cookies.get(AUTH_COOKIE_NAME))?; + let user = srv.from_cookies(cookies)?; Ok(( StatusCode::OK, response::Html(srv.hb.render("index", &user)?), @@ -89,10 +96,7 @@ impl Server { ) -> Result { Ok(( StatusCode::OK, - response::Html( - srv.hb - .render("404", &srv.from_cookie(cookies.get(AUTH_COOKIE_NAME))?)?, - ), + response::Html(srv.hb.render("404", &srv.from_cookies(cookies)?)?), )) } @@ -149,7 +153,21 @@ impl Server { )?, )); } - let token = srv.auth.login(login.username, login.password).await?; + let token = match srv.auth.login(login.username, login.password).await { + Ok(token) => token, + Err(e) => match e { + AuthError::InvalidCredentials => { + return Ok(( + StatusCode::UNAUTHORIZED, + srv.login_page_with( + "error-partial".to_owned(), + "invalid credentials".to_owned(), + )?, + )) + } + e => return Err(e.into()), + }, + }; if cookies.get(AUTH_COOKIE_NAME).is_some() { cookies.remove(Cookie::new(AUTH_COOKIE_NAME, "")); @@ -178,7 +196,7 @@ impl Server { "profile", &WithNav::new( srv.profiler.profile(username).await?, - srv.from_cookie(cookies.get(AUTH_COOKIE_NAME))?.nav_type, + srv.from_cookies(cookies)?.nav_type, ), )?), )) diff --git a/src/servek/servek.rs b/src/servek/servek.rs index 5121349..395f48f 100644 --- a/src/servek/servek.rs +++ b/src/servek/servek.rs @@ -105,6 +105,7 @@ impl From for ServerError { ServerError::BadRequest("invalid credentials".to_owned()) } AuthError::ServerError(err) => ServerError::Internal(err), + AuthError::Expired => ServerError::BadRequest("expired token".to_owned()), } } } diff --git a/src/svc/auth.rs b/src/svc/auth.rs index bdf420e..3cc4f51 100644 --- a/src/svc/auth.rs +++ b/src/svc/auth.rs @@ -95,12 +95,19 @@ impl From for Claims { } } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] pub enum AuthError { InvalidCredentials, + Expired, ServerError(String), } +impl AuthError { + pub fn expired(self) -> bool { + self == Self::Expired + } +} + impl From for AuthError { fn from(_: DBError) -> Self { Self::InvalidCredentials @@ -109,6 +116,9 @@ impl From for AuthError { impl From for AuthError { fn from(e: jsonwebtoken::errors::Error) -> Self { - Self::ServerError(e.to_string()) + match e.kind() { + jsonwebtoken::errors::ErrorKind::ExpiredSignature => Self::Expired, + kind => Self::ServerError(e.to_string()), + } } } diff --git a/src/svc/profiles.rs b/src/svc/profiles.rs index 3db222d..cb7d010 100644 --- a/src/svc/profiles.rs +++ b/src/svc/profiles.rs @@ -42,6 +42,8 @@ impl Profiler { display_name: None, password_hash: sec::hash(password), email, + avatar_uri: None, + bio: None, }) .await?; @@ -55,15 +57,26 @@ pub struct User { pub username: String, pub display_name: Option, pub host: Option, + pub avatar_uri: Option, + pub bio: Option, } impl From for User { fn from(u: users::User) -> Self { Self { id: u.id, + display_name: u.display_name.or(Some(format!( + "@{}{}", + u.username, + u.host + .clone() + .map(|h| format!("@{}", h)) + .unwrap_or(String::new()), + ))), username: u.username, - display_name: u.display_name, host: u.host, + avatar_uri: u.avatar_uri, + bio: u.bio, } } } diff --git a/static/style/main.css b/static/style/main.css index 506b2b3..e700525 100644 --- a/static/style/main.css +++ b/static/style/main.css @@ -3,7 +3,6 @@ body { color: rebeccapurple; } -p, h1, h2, label { @@ -62,11 +61,58 @@ a { color: rebeccapurple; } +.main { + width: 85vw; + border: 5px solid rebeccapurple; + height: 85vh; + padding: 10px; + margin: 0 auto; + display: flex; + background-color: rgba(13, 6, 19, 0.4); + font-weight: bold; +} + +.avatar { + width: 128px; + height: 128px; + border: 3px solid rebeccapurple; +} + +.name { + font-size: 110%; + text-align: center; + margin: 0; +} + +.profile { + /* border: 3px solid rebeccapurple; */ + padding: 5px; + display: flex; + text-align: left; +} + +.profile-header { + text-align: left; + width: 128px; +} + +.profile-bio { + border: 3px solid rebeccapurple; + margin: auto; + text-align: center; +} nav { - /* background-color: #333; */ - margin: 0; overflow: hidden; + border: 5px solid rgba(102, 51, 153, 0.5); + width: 85vw; + padding-left: 10px; + padding-right: 10px; + margin-left: auto; + margin-right: auto; + margin-bottom: 10px; + background-color: rgba(13, 6, 19, 0.4); + font-weight: bold; } nav ul { diff --git a/templates/html/profile.html b/templates/html/profile.html index 2b52928..011eebe 100644 --- a/templates/html/profile.html +++ b/templates/html/profile.html @@ -2,14 +2,24 @@ - @{{username}} + @{{obj.username}} {{> (lookup this "nav_type") }} -

hi {{obj.username}}, your id is {{obj.id}}

+
+
+
+ {{obj.username}}'s avatar +

{{obj.display_name}}

+
+
+

{{obj.bio}}

+
+
+