|
@@ -1,5 +1,5 @@
|
|
use crate::{ext::ExternalAuthenticator, schema, user::UserExt, UIDCError};
|
|
use crate::{ext::ExternalAuthenticator, schema, user::UserExt, UIDCError};
|
|
-use microrm::{prelude::*, schema::Stored};
|
|
|
|
|
|
+use microrm::{prelude::*, schema::Stored, ConnectionLease};
|
|
use serde::Deserialize;
|
|
use serde::Deserialize;
|
|
use tide::http::Cookie;
|
|
use tide::http::Cookie;
|
|
|
|
|
|
@@ -22,17 +22,18 @@ impl<'l> SessionHelper<'l> {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
- pub fn get_realm(&self) -> tide::Result<Stored<schema::Realm>> {
|
|
|
|
|
|
+ pub fn get_realm(&self, lease: &mut ConnectionLease) -> tide::Result<Stored<schema::Realm>> {
|
|
self.state
|
|
self.state
|
|
.db
|
|
.db
|
|
.realms
|
|
.realms
|
|
.keyed(self.realm_str)
|
|
.keyed(self.realm_str)
|
|
- .get()?
|
|
|
|
|
|
+ .get(lease)?
|
|
.ok_or(tide::Error::from_str(404, "No such realm"))
|
|
.ok_or(tide::Error::from_str(404, "No such realm"))
|
|
}
|
|
}
|
|
|
|
|
|
fn build_session(
|
|
fn build_session(
|
|
&self,
|
|
&self,
|
|
|
|
+ lease: &mut ConnectionLease,
|
|
) -> tide::Result<(schema::Session, Option<tide::http::Cookie<'static>>)> {
|
|
) -> tide::Result<(schema::Session, Option<tide::http::Cookie<'static>>)> {
|
|
let rng = ring::rand::SystemRandom::new();
|
|
let rng = ring::rand::SystemRandom::new();
|
|
let session_id: [u8; 32] = ring::rand::generate(&rng)
|
|
let session_id: [u8; 32] = ring::rand::generate(&rng)
|
|
@@ -40,34 +41,45 @@ impl<'l> SessionHelper<'l> {
|
|
.expose();
|
|
.expose();
|
|
let session_id = base64::encode_config(session_id, base64::URL_SAFE_NO_PAD);
|
|
let session_id = base64::encode_config(session_id, base64::URL_SAFE_NO_PAD);
|
|
|
|
|
|
- let session = self.state.db.sessions.insert_and_return(schema::Session {
|
|
|
|
- session_id: session_id.clone(),
|
|
|
|
- auth: Default::default(),
|
|
|
|
- expiry: time::OffsetDateTime::now_utc() + time::Duration::minutes(10),
|
|
|
|
- })?;
|
|
|
|
|
|
+ let session = self.state.db.sessions.insert_and_return(
|
|
|
|
+ lease,
|
|
|
|
+ schema::Session {
|
|
|
|
+ session_id: session_id.clone(),
|
|
|
|
+ auth: Default::default(),
|
|
|
|
+ expiry: time::OffsetDateTime::now_utc() + time::Duration::minutes(10),
|
|
|
|
+ },
|
|
|
|
+ )?;
|
|
let session_cookie = Cookie::build(SESSION_COOKIE_NAME, session_id)
|
|
let session_cookie = Cookie::build(SESSION_COOKIE_NAME, session_id)
|
|
.path("/")
|
|
.path("/")
|
|
.finish();
|
|
.finish();
|
|
Ok((session.wrapped(), Some(session_cookie)))
|
|
Ok((session.wrapped(), Some(session_cookie)))
|
|
}
|
|
}
|
|
|
|
|
|
- pub fn verify_session(&self, req: &Request) -> Option<(Stored<schema::Realm>, schema::UserID)> {
|
|
|
|
- self.get_or_build_session(req)
|
|
|
|
|
|
+ pub fn verify_session(
|
|
|
|
+ &self,
|
|
|
|
+ lease: &mut ConnectionLease,
|
|
|
|
+ req: &Request,
|
|
|
|
+ ) -> Option<(Stored<schema::Realm>, schema::UserID)> {
|
|
|
|
+ self.get_or_build_session(lease, req)
|
|
.ok()
|
|
.ok()
|
|
- .zip(self.get_realm().ok())
|
|
|
|
|
|
+ .zip(self.get_realm(lease).ok())
|
|
.and_then(|((sid, _cookie), realm)| {
|
|
.and_then(|((sid, _cookie), realm)| {
|
|
- self.get_auth_for_session(realm.id(), &sid)
|
|
|
|
|
|
+ self.get_auth_for_session(lease, realm.id(), &sid)
|
|
.and_then(|auth| auth.user.map(|user| (realm, user)))
|
|
.and_then(|auth| auth.user.map(|user| (realm, user)))
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
- pub fn get_session(&self, req: &Request) -> Option<schema::Session> {
|
|
|
|
|
|
+ pub fn get_session(
|
|
|
|
+ &self,
|
|
|
|
+ lease: &mut ConnectionLease,
|
|
|
|
+ req: &Request,
|
|
|
|
+ ) -> Option<schema::Session> {
|
|
req.cookie(SESSION_COOKIE_NAME).and_then(|sid| {
|
|
req.cookie(SESSION_COOKIE_NAME).and_then(|sid| {
|
|
self.state
|
|
self.state
|
|
.db
|
|
.db
|
|
.sessions
|
|
.sessions
|
|
.keyed(sid.value())
|
|
.keyed(sid.value())
|
|
- .get()
|
|
|
|
|
|
+ .get(lease)
|
|
.ok()
|
|
.ok()
|
|
.flatten()
|
|
.flatten()
|
|
.map(|v| v.wrapped())
|
|
.map(|v| v.wrapped())
|
|
@@ -76,16 +88,18 @@ impl<'l> SessionHelper<'l> {
|
|
|
|
|
|
pub fn get_or_build_session(
|
|
pub fn get_or_build_session(
|
|
&self,
|
|
&self,
|
|
|
|
+ lease: &mut ConnectionLease,
|
|
req: &Request,
|
|
req: &Request,
|
|
) -> tide::Result<(schema::Session, Option<tide::http::Cookie<'static>>)> {
|
|
) -> tide::Result<(schema::Session, Option<tide::http::Cookie<'static>>)> {
|
|
- match self.get_session(req) {
|
|
|
|
|
|
+ match self.get_session(lease, req) {
|
|
Some(s) => Ok((s, None)),
|
|
Some(s) => Ok((s, None)),
|
|
- None => self.build_session(),
|
|
|
|
|
|
+ None => self.build_session(lease),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
pub fn get_auth_for_session(
|
|
pub fn get_auth_for_session(
|
|
&self,
|
|
&self,
|
|
|
|
+ lease: &mut ConnectionLease,
|
|
realm: schema::RealmID,
|
|
realm: schema::RealmID,
|
|
session: &schema::Session,
|
|
session: &schema::Session,
|
|
) -> Option<Stored<schema::SessionAuth>> {
|
|
) -> Option<Stored<schema::SessionAuth>> {
|
|
@@ -93,12 +107,13 @@ impl<'l> SessionHelper<'l> {
|
|
.auth
|
|
.auth
|
|
.with(schema::SessionAuth::Realm, realm)
|
|
.with(schema::SessionAuth::Realm, realm)
|
|
.first()
|
|
.first()
|
|
- .get()
|
|
|
|
|
|
+ .get(lease)
|
|
.ok()?
|
|
.ok()?
|
|
}
|
|
}
|
|
|
|
|
|
pub fn destroy_auth(
|
|
pub fn destroy_auth(
|
|
&self,
|
|
&self,
|
|
|
|
+ lease: &mut ConnectionLease,
|
|
realm: schema::RealmID,
|
|
realm: schema::RealmID,
|
|
session: &schema::Session,
|
|
session: &schema::Session,
|
|
) -> Result<(), UIDCError> {
|
|
) -> Result<(), UIDCError> {
|
|
@@ -106,7 +121,7 @@ impl<'l> SessionHelper<'l> {
|
|
.auth
|
|
.auth
|
|
.with(schema::SessionAuth::Realm, realm)
|
|
.with(schema::SessionAuth::Realm, realm)
|
|
.first()
|
|
.first()
|
|
- .delete()?;
|
|
|
|
|
|
+ .delete(lease)?;
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -199,8 +214,9 @@ async fn v1_onetime(req: Request) -> tide::Result<tide::Response> {
|
|
|
|
|
|
let shelper = SessionHelper::new(&req);
|
|
let shelper = SessionHelper::new(&req);
|
|
|
|
|
|
- let realm = shelper.get_realm()?;
|
|
|
|
- let (session, cookie) = shelper.get_or_build_session(&req)?;
|
|
|
|
|
|
+ let mut lease = req.state().pool.acquire()?;
|
|
|
|
+ let realm = shelper.get_realm(&mut lease)?;
|
|
|
|
+ let (session, cookie) = shelper.get_or_build_session(&mut lease, &req)?;
|
|
if let Some(c) = cookie {
|
|
if let Some(c) = cookie {
|
|
response.insert_cookie(c)
|
|
response.insert_cookie(c)
|
|
}
|
|
}
|
|
@@ -218,7 +234,12 @@ async fn v1_onetime(req: Request) -> tide::Result<tide::Response> {
|
|
|
|
|
|
println!("looking for single_use_auth with code {}", otq.code);
|
|
println!("looking for single_use_auth with code {}", otq.code);
|
|
|
|
|
|
- let Some(authinfo) = realm.single_use_auth.keyed(otq.code).first().remove()? else {
|
|
|
|
|
|
+ let Some(authinfo) = realm
|
|
|
|
+ .single_use_auth
|
|
|
|
+ .keyed(otq.code)
|
|
|
|
+ .first()
|
|
|
|
+ .remove(&mut lease)?
|
|
|
|
+ else {
|
|
return Ok(shelper.render_login_from_auth(
|
|
return Ok(shelper.render_login_from_auth(
|
|
response,
|
|
response,
|
|
otq.redirect,
|
|
otq.redirect,
|
|
@@ -241,20 +262,23 @@ async fn v1_onetime(req: Request) -> tide::Result<tide::Response> {
|
|
));
|
|
));
|
|
}
|
|
}
|
|
|
|
|
|
- match shelper.get_auth_for_session(realm.id(), &session) {
|
|
|
|
|
|
+ match shelper.get_auth_for_session(&mut lease, realm.id(), &session) {
|
|
Some(mut sauth) => {
|
|
Some(mut sauth) => {
|
|
sauth.user = Some(authinfo.user);
|
|
sauth.user = Some(authinfo.user);
|
|
sauth.pending_user = None;
|
|
sauth.pending_user = None;
|
|
sauth.pending_challenges = vec![].into_serialized();
|
|
sauth.pending_challenges = vec![].into_serialized();
|
|
- sauth.sync()?;
|
|
|
|
|
|
+ sauth.sync(&mut lease)?;
|
|
}
|
|
}
|
|
None => {
|
|
None => {
|
|
- session.auth.insert(schema::SessionAuth {
|
|
|
|
- realm: realm.id(),
|
|
|
|
- user: Some(authinfo.user),
|
|
|
|
- pending_user: None,
|
|
|
|
- pending_challenges: vec![].into_serialized(),
|
|
|
|
- })?;
|
|
|
|
|
|
+ session.auth.insert(
|
|
|
|
+ &mut lease,
|
|
|
|
+ schema::SessionAuth {
|
|
|
|
+ realm: realm.id(),
|
|
|
|
+ user: Some(authinfo.user),
|
|
|
|
+ pending_user: None,
|
|
|
|
+ pending_challenges: vec![].into_serialized(),
|
|
|
|
+ },
|
|
|
|
+ )?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -269,14 +293,15 @@ async fn v1_login(req: Request) -> tide::Result<tide::Response> {
|
|
let mut response = tide::Response::builder(200).build();
|
|
let mut response = tide::Response::builder(200).build();
|
|
|
|
|
|
let shelper = SessionHelper::new(&req);
|
|
let shelper = SessionHelper::new(&req);
|
|
|
|
+ let mut lease = req.state().pool.acquire()?;
|
|
|
|
|
|
- let realm = shelper.get_realm()?;
|
|
|
|
- let (session, cookie) = shelper.get_or_build_session(&req)?;
|
|
|
|
|
|
+ let realm = shelper.get_realm(&mut lease)?;
|
|
|
|
+ let (session, cookie) = shelper.get_or_build_session(&mut lease, &req)?;
|
|
if let Some(c) = cookie {
|
|
if let Some(c) = cookie {
|
|
response.insert_cookie(c)
|
|
response.insert_cookie(c)
|
|
}
|
|
}
|
|
|
|
|
|
- let auth = shelper.get_auth_for_session(realm.id(), &session);
|
|
|
|
|
|
+ let auth = shelper.get_auth_for_session(&mut lease, realm.id(), &session);
|
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[derive(serde::Deserialize)]
|
|
struct LoginQuery {
|
|
struct LoginQuery {
|
|
@@ -307,18 +332,19 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
|
|
let body: ResponseBody = req.body_form().await?;
|
|
let body: ResponseBody = req.body_form().await?;
|
|
|
|
|
|
let shelper = SessionHelper::new(&req);
|
|
let shelper = SessionHelper::new(&req);
|
|
|
|
+ let mut lease = req.state().pool.acquire()?;
|
|
|
|
|
|
- let realm = shelper.get_realm()?;
|
|
|
|
- let (session, cookie) = shelper.get_or_build_session(&req)?;
|
|
|
|
|
|
+ let realm = shelper.get_realm(&mut lease)?;
|
|
|
|
+ let (session, cookie) = shelper.get_or_build_session(&mut lease, &req)?;
|
|
if let Some(c) = cookie {
|
|
if let Some(c) = cookie {
|
|
response.insert_cookie(c)
|
|
response.insert_cookie(c)
|
|
}
|
|
}
|
|
|
|
|
|
- let mut auth = shelper.get_auth_for_session(realm.id(), &session);
|
|
|
|
|
|
+ let mut auth = shelper.get_auth_for_session(&mut lease, realm.id(), &session);
|
|
|
|
|
|
// check if a login reset was requested; if so, we start again from the top
|
|
// check if a login reset was requested; if so, we start again from the top
|
|
if body.reset.is_some() {
|
|
if body.reset.is_some() {
|
|
- shelper.destroy_auth(realm.id(), &session)?;
|
|
|
|
|
|
+ shelper.destroy_auth(&mut lease, realm.id(), &session)?;
|
|
// TODO: include original redirect URL here
|
|
// TODO: include original redirect URL here
|
|
return Ok(tide::Redirect::new("login").into());
|
|
return Ok(tide::Redirect::new("login").into());
|
|
}
|
|
}
|
|
@@ -347,13 +373,13 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
|
|
match challenge {
|
|
match challenge {
|
|
// handle the username challenge specially because this sets up the pending challenges.
|
|
// handle the username challenge specially because this sets up the pending challenges.
|
|
ChallengeType::Username => {
|
|
ChallengeType::Username => {
|
|
- shelper.destroy_auth(realm.id(), &session)?;
|
|
|
|
|
|
+ shelper.destroy_auth(&mut lease, realm.id(), &session)?;
|
|
|
|
|
|
let user = realm
|
|
let user = realm
|
|
.users
|
|
.users
|
|
.with(schema::User::Username, &body.challenge)
|
|
.with(schema::User::Username, &body.challenge)
|
|
.first()
|
|
.first()
|
|
- .get()?;
|
|
|
|
|
|
+ .get(&mut lease)?;
|
|
if user.is_none() {
|
|
if user.is_none() {
|
|
error = Some(format!("No such user {}", body.challenge));
|
|
error = Some(format!("No such user {}", body.challenge));
|
|
} else {
|
|
} else {
|
|
@@ -365,25 +391,28 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
|
|
schema::AuthChallenge::ChallengeType,
|
|
schema::AuthChallenge::ChallengeType,
|
|
microrm::schema::Serialized::from(schema::AuthChallengeType::Totp),
|
|
microrm::schema::Serialized::from(schema::AuthChallengeType::Totp),
|
|
)
|
|
)
|
|
- .count()?
|
|
|
|
|
|
+ .count(&mut lease)?
|
|
> 0;
|
|
> 0;
|
|
|
|
|
|
// TODO: support more flows than just username,password[,totp]
|
|
// TODO: support more flows than just username,password[,totp]
|
|
auth = Some(
|
|
auth = Some(
|
|
- session.auth.insert_and_return(schema::SessionAuth {
|
|
|
|
- realm: realm.id(),
|
|
|
|
- user: None,
|
|
|
|
- pending_user: Some(user.id()),
|
|
|
|
- pending_challenges: if has_totp {
|
|
|
|
- vec![
|
|
|
|
- schema::AuthChallengeType::Password,
|
|
|
|
- schema::AuthChallengeType::Totp,
|
|
|
|
- ]
|
|
|
|
- } else {
|
|
|
|
- vec![schema::AuthChallengeType::Password]
|
|
|
|
- }
|
|
|
|
- .into(),
|
|
|
|
- })?,
|
|
|
|
|
|
+ session.auth.insert_and_return(
|
|
|
|
+ &mut lease,
|
|
|
|
+ schema::SessionAuth {
|
|
|
|
+ realm: realm.id(),
|
|
|
|
+ user: None,
|
|
|
|
+ pending_user: Some(user.id()),
|
|
|
|
+ pending_challenges: if has_totp {
|
|
|
|
+ vec![
|
|
|
|
+ schema::AuthChallengeType::Password,
|
|
|
|
+ schema::AuthChallengeType::Totp,
|
|
|
|
+ ]
|
|
|
|
+ } else {
|
|
|
|
+ vec![schema::AuthChallengeType::Password]
|
|
|
|
+ }
|
|
|
|
+ .into(),
|
|
|
|
+ },
|
|
|
|
+ )?,
|
|
);
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -393,11 +422,11 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
|
|
let user = realm
|
|
let user = realm
|
|
.users
|
|
.users
|
|
.with_id(user_id)
|
|
.with_id(user_id)
|
|
- .get()?
|
|
|
|
|
|
+ .get(&mut lease)?
|
|
.ok_or(UIDCError::Abort("session auth refers to nonexistent user"))?;
|
|
.ok_or(UIDCError::Abort("session auth refers to nonexistent user"))?;
|
|
|
|
|
|
let verification =
|
|
let verification =
|
|
- user.verify_challenge_by_type(ctype, body.challenge.as_bytes());
|
|
|
|
|
|
+ user.verify_challenge_by_type(&mut lease, ctype, body.challenge.as_bytes());
|
|
|
|
|
|
match verification {
|
|
match verification {
|
|
Ok(true) => {
|
|
Ok(true) => {
|
|
@@ -408,7 +437,7 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
|
|
auth.user = auth.pending_user.take();
|
|
auth.user = auth.pending_user.take();
|
|
}
|
|
}
|
|
|
|
|
|
- auth.sync()?;
|
|
|
|
|
|
+ auth.sync(&mut lease)?;
|
|
}
|
|
}
|
|
Ok(false) => {
|
|
Ok(false) => {
|
|
error = Some("Incorrect response. Please try again".into());
|
|
error = Some("Incorrect response. Please try again".into());
|
|
@@ -431,6 +460,7 @@ async fn v1_login_post(mut req: Request) -> tide::Result<tide::Response> {
|
|
|
|
|
|
async fn v1_logout(req: Request) -> tide::Result<tide::Response> {
|
|
async fn v1_logout(req: Request) -> tide::Result<tide::Response> {
|
|
let shelper = SessionHelper::new(&req);
|
|
let shelper = SessionHelper::new(&req);
|
|
|
|
+ let mut lease = req.state().pool.acquire()?;
|
|
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[derive(serde::Deserialize)]
|
|
struct LogoutQuery {
|
|
struct LogoutQuery {
|
|
@@ -439,10 +469,10 @@ async fn v1_logout(req: Request) -> tide::Result<tide::Response> {
|
|
|
|
|
|
let query: LogoutQuery = req.query().unwrap();
|
|
let query: LogoutQuery = req.query().unwrap();
|
|
|
|
|
|
- let realm = shelper.get_realm()?;
|
|
|
|
|
|
+ let realm = shelper.get_realm(&mut lease)?;
|
|
shelper
|
|
shelper
|
|
- .get_session(&req)
|
|
|
|
- .map(|sid| shelper.destroy_auth(realm.id(), &sid));
|
|
|
|
|
|
+ .get_session(&mut lease, &req)
|
|
|
|
+ .map(|sid| shelper.destroy_auth(&mut lease, realm.id(), &sid));
|
|
Ok(tide::Redirect::new(query.redirect.unwrap_or_else(|| "../..".into())).into())
|
|
Ok(tide::Redirect::new(query.redirect.unwrap_or_else(|| "../..".into())).into())
|
|
}
|
|
}
|
|
|
|
|