Implement logout.

This commit is contained in:
Rasmus Kaj 2018-08-21 21:41:18 +02:00
parent acadbe6c0e
commit ec2f9266d1
4 changed files with 101 additions and 37 deletions

View File

@ -31,6 +31,22 @@ header {
position: sticky;
top: 0;
z-index: 9999;
.user {
form {
display: inline;
button {
border: 0;
font: inherit;
background: transparent;
padding: 0;
color: #805;
&:hover {
text-decoration: underline;
}
}
}
}
}
footer {
@ -64,26 +80,26 @@ header, footer {
main {
flex-grow: 1;
margin-bottom: 1em;
form {
border: $border;
margin: auto;
padding: 1em;
width: -moz-fit-content;
width: fit-content;
p {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
}
label {
padding: .2em 1em .2em 0;
}
}
}
header, footer, main {
flex-wrap: wrap;
padding: 0 1ex;
}
form {
border: $border;
margin: auto;
padding: 1em;
width: -moz-fit-content;
width: fit-content;
p {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
}
label {
padding: .2em 1em .2em 0;
}
}

View File

@ -60,11 +60,12 @@ fn main() {
.and(index())
.and_then(signup_form)),
)).or(post().and(
(s().and(path("login")).and(body::form()).and_then(do_login)).or(
s().and(path("signup"))
(s().and(path("login")).and(body::form()).and_then(do_login))
.or(s().and(path("logout")).and_then(do_logout))
.or(s()
.and(path("signup"))
.and(body::form())
.and_then(do_signup),
),
.and_then(do_signup)),
)).recover(customize_error);
warp::serve(routes).run(([127, 0, 0, 1], 3030));
}
@ -99,6 +100,18 @@ fn do_login(
}
}
fn do_logout(mut session: Session) -> Result<impl Reply, Rejection> {
session.clear();
Response::builder()
.status(StatusCode::FOUND)
.header(header::LOCATION, "/")
.header(
header::SET_COOKIE,
format!("EXAUTH=; Max-Age=0; SameSite=Strict; HttpOpnly"),
).body(b"".to_vec())
.map_err(|_| reject::server_error()) // TODO This seems ugly?
}
/// The data submitted by the login form.
/// This does not derive Debug or Serialize, as the password is plain text.
#[derive(Deserialize)]

View File

@ -1,4 +1,4 @@
use diesel::insert_into;
use diesel;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool, PooledConnection};
@ -22,6 +22,7 @@ type PgPool = Pool<ConnectionManager<PgConnection>>;
/// user data (e.g. a shopping cart in a web shop).
pub struct Session {
db: PooledPg,
id: Option<i32>,
user: Option<User>,
}
@ -37,14 +38,16 @@ impl Session {
) -> Option<String> {
if let Some(user) = User::authenticate(self.db(), username, password)
{
info!("User {:?} authenticated", user);
debug!("User {:?} authenticated", user);
let secret = random_key(48);
use schema::sessions::dsl::*;
let result = insert_into(sessions)
let result = diesel::insert_into(sessions)
.values((user_id.eq(user.id), cookie.eq(&secret)))
.execute(self.db());
if Ok(1) == result {
.returning(id)
.get_results(self.db());
if let Ok([a]) = result.as_ref().map(|v| &**v) {
self.id = Some(*a);
self.user = Some(user);
return Some(secret);
} else {
@ -57,20 +60,51 @@ impl Session {
None
}
/// Get a Session from a database pool and a session key.
///
/// The session key is checked against the database, and the
/// matching session is loaded.
/// The database pool handle is included in the session regardless
/// of if the session key is a valid session or not.
pub fn from_key(db: PooledPg, sessionkey: Option<&str>) -> Self {
use schema::sessions::dsl as s;
use schema::users::dsl as u;
let user = sessionkey.and_then(|sessionkey| {
u::users
.select((u::id, u::username, u::realname))
.inner_join(s::sessions)
.filter(s::cookie.eq(&sessionkey))
.first::<User>(&db)
.ok()
});
info!("Got: {:?}", user);
Session { db, user }
let (id, user) = sessionkey
.and_then(|sessionkey| {
u::users
.inner_join(s::sessions)
.select((s::id, (u::id, u::username, u::realname)))
.filter(s::cookie.eq(&sessionkey))
.first::<(i32, User)>(&db)
.ok()
}).map(|(i, u)| (Some(i), Some(u)))
.unwrap_or((None, None));
debug!("Got: #{:?} {:?}", id, user);
Session { db, id, user }
}
/// Clear the part of this session that is session-specific.
///
/// In effect, the database pool will remain, but the user will be
/// cleared, and the data in the sessions table for this session
/// will be deleted.
pub fn clear(&mut self) {
use schema::sessions::dsl as s;
if let Some(session_id) = self.id {
diesel::delete(s::sessions.filter(s::id.eq(session_id)))
.execute(self.db())
.map_err(|e| {
error!(
"Failed to delete session {}: {:?}",
session_id, e
);
}).ok();
}
self.id = None;
self.user = None;
}
pub fn user(&self) -> Option<&User> {
self.user.as_ref()
}

View File

@ -14,7 +14,8 @@
<body>
<header>
<span>Example app</span>
@if let Some(u) = session.user() {<span class="user">@u (<a href="/logout">log out</a>)</span>}
@if let Some(u) = session.user() {<span class="user">@u
(<form action="/logout" method="post"><button>log out</button></form>)</span>}
else { <span class="user">(<a href="/login">log in</a> or <a href="/signup">sign up</a>)</span> }
</header>