You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-07 17:03:01 +03:00
WIP: Refactor higher-level data-model to its own crate
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -1390,6 +1390,7 @@ dependencies = [
|
|||||||
"jwt-compact",
|
"jwt-compact",
|
||||||
"k256",
|
"k256",
|
||||||
"mas-config",
|
"mas-config",
|
||||||
|
"mas-data-model",
|
||||||
"mime",
|
"mime",
|
||||||
"oauth2-types",
|
"oauth2-types",
|
||||||
"password-hash",
|
"password-hash",
|
||||||
@@ -1413,6 +1414,16 @@ dependencies = [
|
|||||||
"warp",
|
"warp",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mas-data-model"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"oauth2-types",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
|
@@ -66,6 +66,7 @@ cookie = "0.15.1"
|
|||||||
|
|
||||||
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
||||||
mas-config = { path = "../config" }
|
mas-config = { path = "../config" }
|
||||||
|
mas-data-model = { path = "../data-model" }
|
||||||
|
|
||||||
[dependencies.jwt-compact]
|
[dependencies.jwt-compact]
|
||||||
# Waiting on the next release because of the bump of the `rsa` dependency
|
# Waiting on the next release because of the bump of the `rsa` dependency
|
||||||
|
@@ -93,8 +93,8 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"35bedaa6fdf7ac91d54b458b4637f2182c2f82be3e2f80cd2db934ee279a7f2a": {
|
"307fd9f71e7a94a0a0d9ce523ee9792e127485d0d12480c43f179dd9b75afbab": {
|
||||||
"query": "\n SELECT id, username\n FROM users\n WHERE id = $1\n ",
|
"query": "\n INSERT INTO user_sessions (user_id)\n VALUES ($1)\n RETURNING id, created_at\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -104,8 +104,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"name": "username",
|
"name": "created_at",
|
||||||
"type_info": "Text"
|
"type_info": "Timestamptz"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -169,56 +169,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"4f925a277d73df779360f81e0cf5d7983b50ebe744f461559dd561b7e36c20d4": {
|
|
||||||
"query": "\n SELECT\n s.id,\n u.id as user_id,\n u.username,\n s.active,\n s.created_at,\n a.created_at as \"last_authd_at?\"\n FROM user_sessions s\n INNER JOIN users u \n ON s.user_id = u.id\n LEFT JOIN user_session_authentications a\n ON a.session_id = s.id\n WHERE s.id = $1 AND s.active\n ORDER BY a.created_at DESC\n LIMIT 1\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "user_id",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "username",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "active",
|
|
||||||
"type_info": "Bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 4,
|
|
||||||
"name": "created_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 5,
|
|
||||||
"name": "last_authd_at?",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"562b0d4dcf857e99c20e9288e9c8bd46232290715c0d2459b0398a1c746cf65d": {
|
"562b0d4dcf857e99c20e9288e9c8bd46232290715c0d2459b0398a1c746cf65d": {
|
||||||
"query": "\n SELECT\n rt.id,\n rt.oauth2_session_id,\n rt.oauth2_access_token_id,\n os.client_id AS \"client_id!\",\n os.scope AS \"scope!\"\n FROM oauth2_refresh_tokens rt\n INNER JOIN oauth2_sessions os\n ON os.id = rt.oauth2_session_id\n WHERE rt.token = $1 AND rt.next_token_id IS NULL\n ",
|
"query": "\n SELECT\n rt.id,\n rt.oauth2_session_id,\n rt.oauth2_access_token_id,\n os.client_id AS \"client_id!\",\n os.scope AS \"scope!\"\n FROM oauth2_refresh_tokens rt\n INNER JOIN oauth2_sessions os\n ON os.id = rt.oauth2_session_id\n WHERE rt.token = $1 AND rt.next_token_id IS NULL\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -273,56 +223,6 @@
|
|||||||
"nullable": []
|
"nullable": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"62986972431bfc4649e3d8c8c7648f9049c4197773e53496422ad8b8aa15b459": {
|
|
||||||
"query": "\n SELECT\n s.id,\n u.id as user_id,\n u.username,\n s.active,\n s.created_at,\n a.created_at as \"last_authd_at?\"\n FROM user_sessions s\n INNER JOIN users u \n ON s.user_id = u.id\n LEFT JOIN user_session_authentications a\n ON a.session_id = s.id\n WHERE s.id = $1\n ORDER BY a.created_at DESC\n LIMIT 1\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 1,
|
|
||||||
"name": "user_id",
|
|
||||||
"type_info": "Int8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 2,
|
|
||||||
"name": "username",
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 3,
|
|
||||||
"name": "active",
|
|
||||||
"type_info": "Bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 4,
|
|
||||||
"name": "created_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ordinal": 5,
|
|
||||||
"name": "last_authd_at?",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"73f2d928f7bf88af79a3685bd6346652b4e4454b0ce75e38343840c9765e3f27": {
|
"73f2d928f7bf88af79a3685bd6346652b4e4454b0ce75e38343840c9765e3f27": {
|
||||||
"query": "\n INSERT INTO oauth2_refresh_tokens\n (oauth2_session_id, oauth2_access_token_id, token)\n VALUES\n ($1, $2, $3)\n RETURNING\n id, oauth2_session_id, oauth2_access_token_id, token, next_token_id, \n created_at, updated_at\n ",
|
"query": "\n INSERT INTO oauth2_refresh_tokens\n (oauth2_session_id, oauth2_access_token_id, token)\n VALUES\n ($1, $2, $3)\n RETURNING\n id, oauth2_session_id, oauth2_access_token_id, token, next_token_id, \n created_at, updated_at\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -381,6 +281,56 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"7fa7d001583e98f5e4626751fa2c8743e7b83a240d1a51de50a7dba95c4e8a6b": {
|
||||||
|
"query": "\n SELECT\n s.id,\n u.id as user_id,\n u.username,\n s.created_at,\n a.id as \"last_authentication_id?\",\n a.created_at as \"last_authd_at?\"\n FROM user_sessions s\n INNER JOIN users u \n ON s.user_id = u.id\n LEFT JOIN user_session_authentications a\n ON a.session_id = s.id\n WHERE s.id = $1 AND s.active\n ORDER BY a.created_at DESC\n LIMIT 1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "user_id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 2,
|
||||||
|
"name": "username",
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 3,
|
||||||
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 4,
|
||||||
|
"name": "last_authentication_id?",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 5,
|
||||||
|
"name": "last_authd_at?",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"88ac8783bd5881c42eafd9cf87a16fe6031f3153fd6a8618e689694584aeb2de": {
|
"88ac8783bd5881c42eafd9cf87a16fe6031f3153fd6a8618e689694584aeb2de": {
|
||||||
"query": "\n DELETE FROM oauth2_access_tokens\n WHERE id = $1\n ",
|
"query": "\n DELETE FROM oauth2_access_tokens\n WHERE id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -393,26 +343,6 @@
|
|||||||
"nullable": []
|
"nullable": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"9ba45ab114b656105cc46b0c10fb05769860fcdc05eaf54d6225640fb914dab9": {
|
|
||||||
"query": "\n INSERT INTO user_session_authentications (session_id)\n VALUES ($1)\n RETURNING created_at\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "created_at",
|
|
||||||
"type_info": "Timestamptz"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Int8"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"a09dfe1019110f2ec6eba0d35bafa467ab4b7980dd8b556826f03863f8edb0ab": {
|
"a09dfe1019110f2ec6eba0d35bafa467ab4b7980dd8b556826f03863f8edb0ab": {
|
||||||
"query": "UPDATE user_sessions SET active = FALSE WHERE id = $1",
|
"query": "UPDATE user_sessions SET active = FALSE WHERE id = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
@@ -611,6 +541,32 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"d9d27eb4a0c11818a636d407438c4bc567a39396e7e236b3e776504417988eab": {
|
||||||
|
"query": "\n INSERT INTO user_session_authentications (session_id)\n VALUES ($1)\n RETURNING id, created_at\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "created_at",
|
||||||
|
"type_info": "Timestamptz"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"eaddc1e33715ad31b4195fda72dbe870f179dd8da53a88d0543b72a278ed1d3d": {
|
"eaddc1e33715ad31b4195fda72dbe870f179dd8da53a88d0543b72a278ed1d3d": {
|
||||||
"query": "\n DELETE FROM oauth2_codes\n WHERE id = $1\n ",
|
"query": "\n DELETE FROM oauth2_codes\n WHERE id = $1\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
//! Load user sessions from the database
|
//! Load user sessions from the database
|
||||||
|
|
||||||
|
use mas_data_model::BrowserSession;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{pool::PoolConnection, Executor, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, Executor, PgPool, Postgres};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -30,7 +31,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
config::CookiesConfig,
|
config::CookiesConfig,
|
||||||
storage::{lookup_active_session, user::ActiveSessionLookupError, SessionInfo},
|
storage::{lookup_active_session, user::ActiveSessionLookupError, PostgresqlBackend},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The session is missing or failed to load
|
/// The session is missing or failed to load
|
||||||
@@ -58,19 +59,19 @@ pub struct SessionCookie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SessionCookie {
|
impl SessionCookie {
|
||||||
/// Forge the cookie from a [`SessionInfo`]
|
/// Forge the cookie from a [`BrowserSession`]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn from_session_info(info: &SessionInfo) -> Self {
|
pub fn from_session(session: &BrowserSession<PostgresqlBackend>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: info.key(),
|
current: session.data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the [`SessionInfo`] from database
|
/// Load the [`BrowserSession`] from database
|
||||||
pub async fn load_session_info(
|
pub async fn load_session(
|
||||||
&self,
|
&self,
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
executor: impl Executor<'_, Database = Postgres>,
|
||||||
) -> Result<SessionInfo, ActiveSessionLookupError> {
|
) -> Result<BrowserSession<PostgresqlBackend>, ActiveSessionLookupError> {
|
||||||
let res = lookup_active_session(executor, self.current).await?;
|
let res = lookup_active_session(executor, self.current).await?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
@@ -87,8 +88,11 @@ impl EncryptableCookieValue for SessionCookie {
|
|||||||
pub fn optional_session(
|
pub fn optional_session(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
cookies_config: &CookiesConfig,
|
cookies_config: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (Option<SessionInfo>,), Error = Rejection> + Clone + Send + Sync + 'static
|
) -> impl Filter<Extract = (Option<BrowserSession<PostgresqlBackend>>,), Error = Rejection>
|
||||||
{
|
+ Clone
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static {
|
||||||
session(pool, cookies_config)
|
session(pool, cookies_config)
|
||||||
.map(Some)
|
.map(Some)
|
||||||
.recover(none_on_error::<_, SessionLoadError>)
|
.recover(none_on_error::<_, SessionLoadError>)
|
||||||
@@ -106,7 +110,11 @@ pub fn optional_session(
|
|||||||
pub fn session(
|
pub fn session(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
cookies_config: &CookiesConfig,
|
cookies_config: &CookiesConfig,
|
||||||
) -> impl Filter<Extract = (SessionInfo,), Error = Rejection> + Clone + Send + Sync + 'static {
|
) -> impl Filter<Extract = (BrowserSession<PostgresqlBackend>,), Error = Rejection>
|
||||||
|
+ Clone
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ 'static {
|
||||||
encrypted(cookies_config)
|
encrypted(cookies_config)
|
||||||
.and(connection(pool))
|
.and(connection(pool))
|
||||||
.and_then(load_session)
|
.and_then(load_session)
|
||||||
@@ -117,8 +125,8 @@ pub fn session(
|
|||||||
async fn load_session(
|
async fn load_session(
|
||||||
session: SessionCookie,
|
session: SessionCookie,
|
||||||
mut conn: PoolConnection<Postgres>,
|
mut conn: PoolConnection<Postgres>,
|
||||||
) -> Result<SessionInfo, Rejection> {
|
) -> Result<BrowserSession<PostgresqlBackend>, Rejection> {
|
||||||
let session_info = session.load_session_info(&mut conn).await?;
|
let session_info = session.load_session(&mut conn).await?;
|
||||||
Ok(session_info)
|
Ok(session_info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,6 +24,7 @@ use hyper::{
|
|||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use mas_data_model::BrowserSession;
|
||||||
use oauth2_types::{
|
use oauth2_types::{
|
||||||
errors::{ErrorResponse, InvalidRequest, OAuth2Error},
|
errors::{ErrorResponse, InvalidRequest, OAuth2Error},
|
||||||
pkce,
|
pkce,
|
||||||
@@ -59,7 +60,7 @@ use crate::{
|
|||||||
refresh_token::add_refresh_token,
|
refresh_token::add_refresh_token,
|
||||||
session::{get_session_by_id, start_session},
|
session::{get_session_by_id, start_session},
|
||||||
},
|
},
|
||||||
SessionInfo,
|
PostgresqlBackend,
|
||||||
},
|
},
|
||||||
templates::{FormPostContext, Templates},
|
templates::{FormPostContext, Templates},
|
||||||
tokens::{AccessToken, RefreshToken},
|
tokens::{AccessToken, RefreshToken},
|
||||||
@@ -297,7 +298,7 @@ async fn actually_reply(
|
|||||||
async fn get(
|
async fn get(
|
||||||
clients: Vec<OAuth2ClientConfig>,
|
clients: Vec<OAuth2ClientConfig>,
|
||||||
params: Params,
|
params: Params,
|
||||||
maybe_session: Option<SessionInfo>,
|
maybe_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||||
mut txn: Transaction<'_, Postgres>,
|
mut txn: Transaction<'_, Postgres>,
|
||||||
) -> Result<ReplyOrBackToClient, Rejection> {
|
) -> Result<ReplyOrBackToClient, Rejection> {
|
||||||
// First, find out what client it is
|
// First, find out what client it is
|
||||||
@@ -307,7 +308,7 @@ async fn get(
|
|||||||
.ok_or_else(|| anyhow::anyhow!("could not find client"))
|
.ok_or_else(|| anyhow::anyhow!("could not find client"))
|
||||||
.wrap_error()?;
|
.wrap_error()?;
|
||||||
|
|
||||||
let maybe_session_id = maybe_session.as_ref().map(SessionInfo::key);
|
let maybe_session_id = maybe_session.as_ref().map(|s| s.data);
|
||||||
|
|
||||||
let scope: String = {
|
let scope: String = {
|
||||||
let it = params.auth.scope.iter().map(ToString::to_string);
|
let it = params.auth.scope.iter().map(ToString::to_string);
|
||||||
@@ -394,7 +395,7 @@ impl StepRequest {
|
|||||||
|
|
||||||
async fn step(
|
async fn step(
|
||||||
oauth2_session_id: i64,
|
oauth2_session_id: i64,
|
||||||
user_session: SessionInfo,
|
user_session: BrowserSession<PostgresqlBackend>,
|
||||||
mut txn: Transaction<'_, Postgres>,
|
mut txn: Transaction<'_, Postgres>,
|
||||||
) -> Result<ReplyOrBackToClient, Rejection> {
|
) -> Result<ReplyOrBackToClient, Rejection> {
|
||||||
let mut oauth2_session = get_session_by_id(&mut txn, oauth2_session_id)
|
let mut oauth2_session = get_session_by_id(&mut txn, oauth2_session_id)
|
||||||
@@ -411,8 +412,9 @@ async fn step(
|
|||||||
let redirect_uri = oauth2_session.redirect_uri().wrap_error()?;
|
let redirect_uri = oauth2_session.redirect_uri().wrap_error()?;
|
||||||
|
|
||||||
// Check if the active session is valid
|
// Check if the active session is valid
|
||||||
let reply = if user_session.active
|
// TODO: this is ugly & should check if the session is active
|
||||||
&& user_session.last_authd_at >= oauth2_session.max_auth_time()
|
let reply = if user_session.last_authentication.map(|x| x.created_at)
|
||||||
|
>= oauth2_session.max_auth_time()
|
||||||
{
|
{
|
||||||
// Yep! Let's complete the auth now
|
// Yep! Let's complete the auth now
|
||||||
let mut params = AuthorizationResponse::default();
|
let mut params = AuthorizationResponse::default();
|
||||||
|
@@ -12,6 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use mas_data_model::BrowserSession;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
@@ -24,7 +25,7 @@ use crate::{
|
|||||||
session::optional_session,
|
session::optional_session,
|
||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::SessionInfo,
|
storage::PostgresqlBackend,
|
||||||
templates::{IndexContext, TemplateContext, Templates},
|
templates::{IndexContext, TemplateContext, Templates},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,10 +52,10 @@ async fn get(
|
|||||||
templates: Templates,
|
templates: Templates,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_saver: EncryptedCookieSaver,
|
||||||
csrf_token: CsrfToken,
|
csrf_token: CsrfToken,
|
||||||
session: Option<SessionInfo>,
|
maybe_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let ctx = IndexContext::new(discovery_url)
|
let ctx = IndexContext::new(discovery_url)
|
||||||
.maybe_with_session(session)
|
.maybe_with_session(maybe_session)
|
||||||
.with_csrf(&csrf_token);
|
.with_csrf(&csrf_token);
|
||||||
|
|
||||||
let content = templates.render_index(&ctx)?;
|
let content = templates.render_index(&ctx)?;
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
||||||
|
use mas_data_model::BrowserSession;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
@@ -29,7 +30,7 @@ use crate::{
|
|||||||
session::{optional_session, SessionCookie},
|
session::{optional_session, SessionCookie},
|
||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::{login, SessionInfo},
|
storage::{login, PostgresqlBackend},
|
||||||
templates::{LoginContext, LoginFormField, TemplateContext, Templates},
|
templates::{LoginContext, LoginFormField, TemplateContext, Templates},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,7 +109,7 @@ async fn get(
|
|||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_saver: EncryptedCookieSaver,
|
||||||
csrf_token: CsrfToken,
|
csrf_token: CsrfToken,
|
||||||
query: LoginRequest,
|
query: LoginRequest,
|
||||||
maybe_session: Option<SessionInfo>,
|
maybe_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
) -> Result<Box<dyn Reply>, Rejection> {
|
||||||
if maybe_session.is_some() {
|
if maybe_session.is_some() {
|
||||||
Ok(Box::new(query.redirect()?))
|
Ok(Box::new(query.redirect()?))
|
||||||
@@ -133,7 +134,7 @@ async fn post(
|
|||||||
// TODO: recover
|
// TODO: recover
|
||||||
match login(&mut conn, &form.username, form.password).await {
|
match login(&mut conn, &form.username, form.password).await {
|
||||||
Ok(session_info) => {
|
Ok(session_info) => {
|
||||||
let session_cookie = SessionCookie::from_session_info(&session_info);
|
let session_cookie = SessionCookie::from_session(&session_info);
|
||||||
let reply = query.redirect()?;
|
let reply = query.redirect()?;
|
||||||
let reply = cookie_saver.save_encrypted(&session_cookie, reply)?;
|
let reply = cookie_saver.save_encrypted(&session_cookie, reply)?;
|
||||||
Ok(Box::new(reply))
|
Ok(Box::new(reply))
|
||||||
|
@@ -12,14 +12,15 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use mas_data_model::BrowserSession;
|
||||||
|
use sqlx::{PgPool, Postgres, Transaction};
|
||||||
use warp::{hyper::Uri, Filter, Rejection, Reply};
|
use warp::{hyper::Uri, Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::CookiesConfig,
|
config::CookiesConfig,
|
||||||
errors::WrapError,
|
errors::WrapError,
|
||||||
filters::{csrf::protected_form, database::connection, session::session},
|
filters::{csrf::protected_form, database::transaction, session::session},
|
||||||
storage::SessionInfo,
|
storage::{user::end_session, PostgresqlBackend},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) fn filter(
|
pub(super) fn filter(
|
||||||
@@ -29,16 +30,18 @@ pub(super) fn filter(
|
|||||||
warp::path!("logout")
|
warp::path!("logout")
|
||||||
.and(warp::post())
|
.and(warp::post())
|
||||||
.and(session(pool, cookies_config))
|
.and(session(pool, cookies_config))
|
||||||
.and(connection(pool))
|
.and(transaction(pool))
|
||||||
.and(protected_form(cookies_config))
|
.and(protected_form(cookies_config))
|
||||||
.and_then(post)
|
.and_then(post)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post(
|
async fn post(
|
||||||
session: SessionInfo,
|
session: BrowserSession<PostgresqlBackend>,
|
||||||
mut conn: PoolConnection<Postgres>,
|
mut txn: Transaction<'_, Postgres>,
|
||||||
_form: (),
|
_form: (),
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
session.end(&mut conn).await.wrap_error()?;
|
end_session(&mut txn, &session).await.wrap_error()?;
|
||||||
|
txn.commit().await.wrap_error()?;
|
||||||
|
|
||||||
Ok(warp::redirect(Uri::from_static("/login")))
|
Ok(warp::redirect(Uri::from_static("/login")))
|
||||||
}
|
}
|
||||||
|
@@ -12,8 +12,9 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
use mas_data_model::BrowserSession;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{PgPool, Postgres, Transaction};
|
||||||
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
use warp::{hyper::Uri, reply::html, Filter, Rejection, Reply};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -22,11 +23,11 @@ use crate::{
|
|||||||
filters::{
|
filters::{
|
||||||
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
cookies::{encrypted_cookie_saver, EncryptedCookieSaver},
|
||||||
csrf::{protected_form, updated_csrf_token},
|
csrf::{protected_form, updated_csrf_token},
|
||||||
database::connection,
|
database::transaction,
|
||||||
session::session,
|
session::session,
|
||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::SessionInfo,
|
storage::{user::authenticate_session, PostgresqlBackend},
|
||||||
templates::{EmptyContext, TemplateContext, Templates},
|
templates::{EmptyContext, TemplateContext, Templates},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ pub(super) fn filter(
|
|||||||
|
|
||||||
let post = warp::post()
|
let post = warp::post()
|
||||||
.and(session(pool, cookies_config))
|
.and(session(pool, cookies_config))
|
||||||
.and(connection(pool))
|
.and(transaction(pool))
|
||||||
.and(protected_form(cookies_config))
|
.and(protected_form(cookies_config))
|
||||||
.and_then(post);
|
.and_then(post);
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ async fn get(
|
|||||||
templates: Templates,
|
templates: Templates,
|
||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_saver: EncryptedCookieSaver,
|
||||||
csrf_token: CsrfToken,
|
csrf_token: CsrfToken,
|
||||||
session: SessionInfo,
|
session: BrowserSession<PostgresqlBackend>,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let ctx = EmptyContext.with_session(session).with_csrf(&csrf_token);
|
let ctx = EmptyContext.with_session(session).with_csrf(&csrf_token);
|
||||||
|
|
||||||
@@ -72,14 +73,14 @@ async fn get(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn post(
|
async fn post(
|
||||||
session: SessionInfo,
|
session: BrowserSession<PostgresqlBackend>,
|
||||||
mut conn: PoolConnection<Postgres>,
|
mut txn: Transaction<'_, Postgres>,
|
||||||
form: ReauthForm,
|
form: ReauthForm,
|
||||||
) -> Result<impl Reply, Rejection> {
|
) -> Result<impl Reply, Rejection> {
|
||||||
let _session = session
|
authenticate_session(&mut txn, &session, form.password)
|
||||||
.reauth(&mut conn, form.password)
|
|
||||||
.await
|
.await
|
||||||
.wrap_error()?;
|
.wrap_error()?;
|
||||||
|
txn.commit().await.wrap_error()?;
|
||||||
|
|
||||||
Ok(warp::redirect(Uri::from_static("/")))
|
Ok(warp::redirect(Uri::from_static("/")))
|
||||||
}
|
}
|
||||||
|
@@ -16,6 +16,7 @@ use std::convert::TryFrom;
|
|||||||
|
|
||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
use hyper::http::uri::{Parts, PathAndQuery, Uri};
|
||||||
|
use mas_data_model::BrowserSession;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
use sqlx::{pool::PoolConnection, PgPool, Postgres};
|
||||||
use warp::{reply::html, Filter, Rejection, Reply};
|
use warp::{reply::html, Filter, Rejection, Reply};
|
||||||
@@ -30,7 +31,7 @@ use crate::{
|
|||||||
session::{optional_session, SessionCookie},
|
session::{optional_session, SessionCookie},
|
||||||
with_templates, CsrfToken,
|
with_templates, CsrfToken,
|
||||||
},
|
},
|
||||||
storage::{register_user, user::start_session, SessionInfo},
|
storage::{register_user, user::start_session, PostgresqlBackend},
|
||||||
templates::{EmptyContext, TemplateContext, Templates},
|
templates::{EmptyContext, TemplateContext, Templates},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -110,7 +111,7 @@ async fn get(
|
|||||||
cookie_saver: EncryptedCookieSaver,
|
cookie_saver: EncryptedCookieSaver,
|
||||||
csrf_token: CsrfToken,
|
csrf_token: CsrfToken,
|
||||||
query: RegisterRequest,
|
query: RegisterRequest,
|
||||||
maybe_session: Option<SessionInfo>,
|
maybe_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||||
) -> Result<Box<dyn Reply>, Rejection> {
|
) -> Result<Box<dyn Reply>, Rejection> {
|
||||||
if maybe_session.is_some() {
|
if maybe_session.is_some() {
|
||||||
Ok(Box::new(query.redirect()?))
|
Ok(Box::new(query.redirect()?))
|
||||||
@@ -140,7 +141,7 @@ async fn post(
|
|||||||
|
|
||||||
let session_info = start_session(&mut conn, user).await.wrap_error()?;
|
let session_info = start_session(&mut conn, user).await.wrap_error()?;
|
||||||
|
|
||||||
let session_cookie = SessionCookie::from_session_info(&session_info);
|
let session_cookie = SessionCookie::from_session(&session_info);
|
||||||
let reply = query.redirect()?;
|
let reply = query.redirect()?;
|
||||||
let reply = cookie_saver.save_encrypted(&session_cookie, reply)?;
|
let reply = cookie_saver.save_encrypted(&session_cookie, reply)?;
|
||||||
Ok(reply)
|
Ok(reply)
|
||||||
|
@@ -16,12 +16,32 @@
|
|||||||
|
|
||||||
#![allow(clippy::used_underscore_binding)] // This is needed by sqlx macros
|
#![allow(clippy::used_underscore_binding)] // This is needed by sqlx macros
|
||||||
|
|
||||||
|
use mas_data_model::StorageBackend;
|
||||||
|
use serde::Serialize;
|
||||||
use sqlx::migrate::Migrator;
|
use sqlx::migrate::Migrator;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("databse query returned an inconsistent state")]
|
||||||
|
pub struct DatabaseInconsistencyError;
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug, Clone, PartialEq)]
|
||||||
|
pub struct PostgresqlBackend;
|
||||||
|
|
||||||
|
impl StorageBackend for PostgresqlBackend {
|
||||||
|
type AccessTokenData = i64;
|
||||||
|
type AuthenticationData = i64;
|
||||||
|
type AuthorizationCodeData = i64;
|
||||||
|
type BrowserSessionData = i64;
|
||||||
|
type ClientData = ();
|
||||||
|
type SessionData = i64;
|
||||||
|
type UserData = i64;
|
||||||
|
}
|
||||||
|
|
||||||
pub mod oauth2;
|
pub mod oauth2;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
pub use self::user::{login, lookup_active_session, register_user, SessionInfo, User};
|
pub use self::user::{login, lookup_active_session, register_user};
|
||||||
|
|
||||||
/// Embedded migrations, allowing them to run on startup
|
/// Embedded migrations, allowing them to run on startup
|
||||||
pub static MIGRATOR: Migrator = sqlx::migrate!();
|
pub static MIGRATOR: Migrator = sqlx::migrate!();
|
||||||
|
@@ -17,6 +17,7 @@ use std::{collections::HashSet, convert::TryFrom, str::FromStr, string::ToString
|
|||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use chrono::{DateTime, Duration, Utc};
|
use chrono::{DateTime, Duration, Utc};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use mas_data_model::BrowserSession;
|
||||||
use oauth2_types::{
|
use oauth2_types::{
|
||||||
pkce,
|
pkce,
|
||||||
requests::{ResponseMode, ResponseType},
|
requests::{ResponseMode, ResponseType},
|
||||||
@@ -25,10 +26,8 @@ use serde::Serialize;
|
|||||||
use sqlx::{Executor, FromRow, Postgres};
|
use sqlx::{Executor, FromRow, Postgres};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use super::{
|
use super::authorization_code::{add_code, OAuth2Code};
|
||||||
super::{user::lookup_session, SessionInfo},
|
use crate::storage::{lookup_active_session, PostgresqlBackend};
|
||||||
authorization_code::{add_code, OAuth2Code},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(FromRow, Serialize)]
|
#[derive(FromRow, Serialize)]
|
||||||
pub struct OAuth2Session {
|
pub struct OAuth2Session {
|
||||||
@@ -60,10 +59,11 @@ impl OAuth2Session {
|
|||||||
pub async fn fetch_session(
|
pub async fn fetch_session(
|
||||||
&self,
|
&self,
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
executor: impl Executor<'_, Database = Postgres>,
|
||||||
) -> anyhow::Result<Option<SessionInfo>> {
|
) -> anyhow::Result<Option<BrowserSession<PostgresqlBackend>>> {
|
||||||
match self.user_session_id {
|
match self.user_session_id {
|
||||||
Some(id) => {
|
Some(id) => {
|
||||||
let info = lookup_session(executor, id).await?;
|
// TODO: and if the session is inactive?
|
||||||
|
let info = lookup_active_session(executor, id).await?;
|
||||||
Ok(Some(info))
|
Ok(Some(info))
|
||||||
}
|
}
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -80,19 +80,19 @@ impl OAuth2Session {
|
|||||||
pub async fn match_or_set_session(
|
pub async fn match_or_set_session(
|
||||||
&mut self,
|
&mut self,
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
executor: impl Executor<'_, Database = Postgres>,
|
||||||
session: SessionInfo,
|
session: BrowserSession<PostgresqlBackend>,
|
||||||
) -> anyhow::Result<SessionInfo> {
|
) -> anyhow::Result<BrowserSession<PostgresqlBackend>> {
|
||||||
match self.user_session_id {
|
match self.user_session_id {
|
||||||
Some(id) if id == session.key() => Ok(session),
|
Some(id) if id == session.data => Ok(session),
|
||||||
Some(id) => Err(anyhow::anyhow!(
|
Some(id) => Err(anyhow::anyhow!(
|
||||||
"session mismatch, expected {}, got {}",
|
"session mismatch, expected {}, got {}",
|
||||||
id,
|
id,
|
||||||
session.key()
|
session.data
|
||||||
)),
|
)),
|
||||||
None => {
|
None => {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"UPDATE oauth2_sessions SET user_session_id = $1 WHERE id = $2",
|
"UPDATE oauth2_sessions SET user_session_id = $1 WHERE id = $2",
|
||||||
session.key(),
|
session.data,
|
||||||
self.id,
|
self.id,
|
||||||
)
|
)
|
||||||
.execute(executor)
|
.execute(executor)
|
||||||
|
@@ -12,76 +12,29 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use std::borrow::BorrowMut;
|
use std::{borrow::BorrowMut, convert::TryInto};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use argon2::Argon2;
|
use argon2::Argon2;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use mas_data_model::{Authentication, BrowserSession, User};
|
||||||
use password_hash::{PasswordHash, PasswordHasher, SaltString};
|
use password_hash::{PasswordHash, PasswordHasher, SaltString};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use serde::Serialize;
|
|
||||||
use sqlx::{Acquire, Executor, FromRow, Postgres, Transaction};
|
use sqlx::{Acquire, Executor, FromRow, Postgres, Transaction};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tokio::task;
|
use tokio::task;
|
||||||
use tracing::{info_span, Instrument};
|
use tracing::{info_span, Instrument};
|
||||||
use warp::reject::Reject;
|
use warp::reject::Reject;
|
||||||
|
|
||||||
|
use super::{DatabaseInconsistencyError, PostgresqlBackend};
|
||||||
use crate::errors::HtmlError;
|
use crate::errors::HtmlError;
|
||||||
|
|
||||||
#[derive(Serialize, Debug, Clone, FromRow)]
|
#[derive(Debug, Clone, FromRow)]
|
||||||
pub struct User {
|
struct UserLookup {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug, Clone, FromRow)]
|
|
||||||
pub struct SessionInfo {
|
|
||||||
id: i64,
|
|
||||||
user_id: i64,
|
|
||||||
username: String,
|
|
||||||
pub active: bool,
|
|
||||||
created_at: DateTime<Utc>,
|
|
||||||
pub last_authd_at: Option<DateTime<Utc>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SessionInfo {
|
|
||||||
#[must_use]
|
|
||||||
pub fn key(&self) -> i64 {
|
|
||||||
self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn reauth(
|
|
||||||
mut self,
|
|
||||||
conn: impl Acquire<'_, Database = Postgres>,
|
|
||||||
password: String,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
let mut txn = conn.begin().await?;
|
|
||||||
self.last_authd_at = Some(authenticate_session(&mut txn, self.id, password).await?);
|
|
||||||
txn.commit().await?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn end(
|
|
||||||
mut self,
|
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
end_session(executor, self.id).await?;
|
|
||||||
self.active = false;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn samples() -> Vec<Self> {
|
|
||||||
vec![SessionInfo {
|
|
||||||
id: 1,
|
|
||||||
user_id: 2,
|
|
||||||
username: "john".to_string(),
|
|
||||||
active: true,
|
|
||||||
created_at: Utc::now(),
|
|
||||||
last_authd_at: Some(Utc::now()),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum LoginError {
|
pub enum LoginError {
|
||||||
#[error("could not find user {username:?}")]
|
#[error("could not find user {username:?}")]
|
||||||
@@ -116,7 +69,7 @@ pub async fn login(
|
|||||||
conn: impl Acquire<'_, Database = Postgres>,
|
conn: impl Acquire<'_, Database = Postgres>,
|
||||||
username: &str,
|
username: &str,
|
||||||
password: String,
|
password: String,
|
||||||
) -> Result<SessionInfo, LoginError> {
|
) -> Result<BrowserSession<PostgresqlBackend>, LoginError> {
|
||||||
let mut txn = conn.begin().await.context("could not start transaction")?;
|
let mut txn = conn.begin().await.context("could not start transaction")?;
|
||||||
let user = lookup_user_by_username(&mut txn, username)
|
let user = lookup_user_by_username(&mut txn, username)
|
||||||
.await
|
.await
|
||||||
@@ -132,8 +85,8 @@ pub async fn login(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut session = start_session(&mut txn, user).await?;
|
let mut session = start_session(&mut txn, user).await?;
|
||||||
session.last_authd_at = Some(
|
session.last_authentication = Some(
|
||||||
authenticate_session(&mut txn, session.id, password)
|
authenticate_session(&mut txn, &session, password)
|
||||||
.await
|
.await
|
||||||
.map_err(|source| {
|
.map_err(|source| {
|
||||||
if matches!(source, AuthenticationError::Password { .. }) {
|
if matches!(source, AuthenticationError::Password { .. }) {
|
||||||
@@ -152,30 +105,73 @@ pub async fn login(
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
#[error("could not fetch session")]
|
#[error("could not fetch session")]
|
||||||
pub struct ActiveSessionLookupError(#[from] sqlx::Error);
|
pub enum ActiveSessionLookupError {
|
||||||
|
Fetch(#[from] sqlx::Error),
|
||||||
|
Conversion(#[from] DatabaseInconsistencyError),
|
||||||
|
}
|
||||||
|
|
||||||
impl Reject for ActiveSessionLookupError {}
|
impl Reject for ActiveSessionLookupError {}
|
||||||
|
|
||||||
impl ActiveSessionLookupError {
|
impl ActiveSessionLookupError {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn not_found(&self) -> bool {
|
pub fn not_found(&self) -> bool {
|
||||||
matches!(self.0, sqlx::Error::RowNotFound)
|
matches!(
|
||||||
|
self,
|
||||||
|
ActiveSessionLookupError::Fetch(sqlx::Error::RowNotFound)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SessionLookup {
|
||||||
|
id: i64,
|
||||||
|
user_id: i64,
|
||||||
|
username: String,
|
||||||
|
created_at: DateTime<Utc>,
|
||||||
|
last_authentication_id: Option<i64>,
|
||||||
|
last_authd_at: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<BrowserSession<PostgresqlBackend>> for SessionLookup {
|
||||||
|
type Error = DatabaseInconsistencyError;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<BrowserSession<PostgresqlBackend>, Self::Error> {
|
||||||
|
let user = User {
|
||||||
|
data: self.user_id,
|
||||||
|
username: self.username,
|
||||||
|
sub: format!("fake-sub-{}", self.user_id),
|
||||||
|
};
|
||||||
|
|
||||||
|
let last_authentication = match (self.last_authentication_id, self.last_authd_at) {
|
||||||
|
(Some(id), Some(created_at)) => Some(Authentication {
|
||||||
|
data: id,
|
||||||
|
created_at,
|
||||||
|
}),
|
||||||
|
(None, None) => None,
|
||||||
|
_ => return Err(DatabaseInconsistencyError),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(BrowserSession {
|
||||||
|
data: self.id,
|
||||||
|
user,
|
||||||
|
created_at: self.created_at,
|
||||||
|
last_authentication,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn lookup_active_session(
|
pub async fn lookup_active_session(
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
executor: impl Executor<'_, Database = Postgres>,
|
||||||
id: i64,
|
id: i64,
|
||||||
) -> Result<SessionInfo, ActiveSessionLookupError> {
|
) -> Result<BrowserSession<PostgresqlBackend>, ActiveSessionLookupError> {
|
||||||
let res = sqlx::query_as!(
|
let res = sqlx::query_as!(
|
||||||
SessionInfo,
|
SessionLookup,
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
s.id,
|
s.id,
|
||||||
u.id as user_id,
|
u.id as user_id,
|
||||||
u.username,
|
u.username,
|
||||||
s.active,
|
|
||||||
s.created_at,
|
s.created_at,
|
||||||
|
a.id as "last_authentication_id?",
|
||||||
a.created_at as "last_authd_at?"
|
a.created_at as "last_authd_at?"
|
||||||
FROM user_sessions s
|
FROM user_sessions s
|
||||||
INNER JOIN users u
|
INNER JOIN users u
|
||||||
@@ -189,65 +185,43 @@ pub async fn lookup_active_session(
|
|||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
.fetch_one(executor)
|
.fetch_one(executor)
|
||||||
.await?;
|
.await?
|
||||||
|
.try_into()?;
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn lookup_session(
|
#[derive(FromRow)]
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
struct SessionStartResult {
|
||||||
id: i64,
|
id: i64,
|
||||||
) -> anyhow::Result<SessionInfo> {
|
created_at: DateTime<Utc>,
|
||||||
sqlx::query_as!(
|
|
||||||
SessionInfo,
|
|
||||||
r#"
|
|
||||||
SELECT
|
|
||||||
s.id,
|
|
||||||
u.id as user_id,
|
|
||||||
u.username,
|
|
||||||
s.active,
|
|
||||||
s.created_at,
|
|
||||||
a.created_at as "last_authd_at?"
|
|
||||||
FROM user_sessions s
|
|
||||||
INNER JOIN users u
|
|
||||||
ON s.user_id = u.id
|
|
||||||
LEFT JOIN user_session_authentications a
|
|
||||||
ON a.session_id = s.id
|
|
||||||
WHERE s.id = $1
|
|
||||||
ORDER BY a.created_at DESC
|
|
||||||
LIMIT 1
|
|
||||||
"#,
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.fetch_one(executor)
|
|
||||||
.await
|
|
||||||
.context("could not fetch session")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start_session(
|
pub async fn start_session(
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
executor: impl Executor<'_, Database = Postgres>,
|
||||||
user: User,
|
user: User<PostgresqlBackend>,
|
||||||
) -> anyhow::Result<SessionInfo> {
|
) -> anyhow::Result<BrowserSession<PostgresqlBackend>> {
|
||||||
let (id, created_at): (i64, DateTime<Utc>) = sqlx::query_as(
|
let res = sqlx::query_as!(
|
||||||
|
SessionStartResult,
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO user_sessions (user_id)
|
INSERT INTO user_sessions (user_id)
|
||||||
VALUES ($1)
|
VALUES ($1)
|
||||||
RETURNING id, created_at
|
RETURNING id, created_at
|
||||||
"#,
|
"#,
|
||||||
|
user.data,
|
||||||
)
|
)
|
||||||
.bind(user.id)
|
|
||||||
.fetch_one(executor)
|
.fetch_one(executor)
|
||||||
.await
|
.await
|
||||||
.context("could not create session")?;
|
.context("could not create session")?;
|
||||||
|
|
||||||
Ok(SessionInfo {
|
let session = BrowserSession {
|
||||||
id,
|
data: res.id,
|
||||||
user_id: user.id,
|
user,
|
||||||
username: user.username,
|
created_at: res.created_at,
|
||||||
active: true,
|
last_authentication: None,
|
||||||
created_at,
|
};
|
||||||
last_authd_at: None,
|
|
||||||
})
|
Ok(session)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@@ -265,11 +239,17 @@ pub enum AuthenticationError {
|
|||||||
Internal(#[from] tokio::task::JoinError),
|
Internal(#[from] tokio::task::JoinError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
|
struct AuthenticationInsertionResult {
|
||||||
|
id: i64,
|
||||||
|
created_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn authenticate_session(
|
pub async fn authenticate_session(
|
||||||
txn: &mut Transaction<'_, Postgres>,
|
txn: &mut Transaction<'_, Postgres>,
|
||||||
session_id: i64,
|
session: &BrowserSession<PostgresqlBackend>,
|
||||||
password: String,
|
password: String,
|
||||||
) -> Result<DateTime<Utc>, AuthenticationError> {
|
) -> Result<Authentication<PostgresqlBackend>, AuthenticationError> {
|
||||||
// First, fetch the hashed password from the user associated with that session
|
// First, fetch the hashed password from the user associated with that session
|
||||||
let hashed_password: String = sqlx::query_scalar!(
|
let hashed_password: String = sqlx::query_scalar!(
|
||||||
r#"
|
r#"
|
||||||
@@ -279,7 +259,7 @@ pub async fn authenticate_session(
|
|||||||
ON u.id = s.user_id
|
ON u.id = s.user_id
|
||||||
WHERE s.id = $1
|
WHERE s.id = $1
|
||||||
"#,
|
"#,
|
||||||
session_id,
|
session.data,
|
||||||
)
|
)
|
||||||
.fetch_one(txn.borrow_mut())
|
.fetch_one(txn.borrow_mut())
|
||||||
.await
|
.await
|
||||||
@@ -297,19 +277,23 @@ pub async fn authenticate_session(
|
|||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
// That went well, let's insert the auth info
|
// That went well, let's insert the auth info
|
||||||
let created_at: DateTime<Utc> = sqlx::query_scalar!(
|
let res = sqlx::query_as!(
|
||||||
|
AuthenticationInsertionResult,
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO user_session_authentications (session_id)
|
INSERT INTO user_session_authentications (session_id)
|
||||||
VALUES ($1)
|
VALUES ($1)
|
||||||
RETURNING created_at
|
RETURNING id, created_at
|
||||||
"#,
|
"#,
|
||||||
session_id,
|
session.data,
|
||||||
)
|
)
|
||||||
.fetch_one(txn.borrow_mut())
|
.fetch_one(txn.borrow_mut())
|
||||||
.await
|
.await
|
||||||
.map_err(AuthenticationError::Save)?;
|
.map_err(AuthenticationError::Save)?;
|
||||||
|
|
||||||
Ok(created_at)
|
Ok(Authentication {
|
||||||
|
data: res.id,
|
||||||
|
created_at: res.created_at,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register_user(
|
pub async fn register_user(
|
||||||
@@ -317,7 +301,7 @@ pub async fn register_user(
|
|||||||
phf: impl PasswordHasher,
|
phf: impl PasswordHasher,
|
||||||
username: &str,
|
username: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
) -> anyhow::Result<User> {
|
) -> anyhow::Result<User<PostgresqlBackend>> {
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
let hashed_password = PasswordHash::generate(phf, password, salt.as_str())?;
|
let hashed_password = PasswordHash::generate(phf, password, salt.as_str())?;
|
||||||
|
|
||||||
@@ -336,16 +320,20 @@ pub async fn register_user(
|
|||||||
.context("could not insert user")?;
|
.context("could not insert user")?;
|
||||||
|
|
||||||
Ok(User {
|
Ok(User {
|
||||||
id,
|
data: id,
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
|
sub: format!("fake-sub-{}", id),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn end_session(
|
pub async fn end_session(
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
executor: impl Executor<'_, Database = Postgres>,
|
||||||
id: i64,
|
session: &BrowserSession<PostgresqlBackend>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let res = sqlx::query!("UPDATE user_sessions SET active = FALSE WHERE id = $1", id)
|
let res = sqlx::query!(
|
||||||
|
"UPDATE user_sessions SET active = FALSE WHERE id = $1",
|
||||||
|
session.data,
|
||||||
|
)
|
||||||
.execute(executor)
|
.execute(executor)
|
||||||
.instrument(info_span!("End session"))
|
.instrument(info_span!("End session"))
|
||||||
.await
|
.await
|
||||||
@@ -358,32 +346,12 @@ pub async fn end_session(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn lookup_user_by_id(
|
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
|
||||||
id: i64,
|
|
||||||
) -> anyhow::Result<User> {
|
|
||||||
sqlx::query_as!(
|
|
||||||
User,
|
|
||||||
r#"
|
|
||||||
SELECT id, username
|
|
||||||
FROM users
|
|
||||||
WHERE id = $1
|
|
||||||
"#,
|
|
||||||
id
|
|
||||||
)
|
|
||||||
.fetch_one(executor)
|
|
||||||
.instrument(info_span!("Fetch user"))
|
|
||||||
.await
|
|
||||||
.context("could not fetch user")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn lookup_user_by_username(
|
pub async fn lookup_user_by_username(
|
||||||
executor: impl Executor<'_, Database = Postgres>,
|
executor: impl Executor<'_, Database = Postgres>,
|
||||||
username: &str,
|
username: &str,
|
||||||
) -> Result<User, sqlx::Error> {
|
) -> Result<User<PostgresqlBackend>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
let res = sqlx::query_as!(
|
||||||
User,
|
UserLookup,
|
||||||
r#"
|
r#"
|
||||||
SELECT id, username
|
SELECT id, username
|
||||||
FROM users
|
FROM users
|
||||||
@@ -393,5 +361,11 @@ pub async fn lookup_user_by_username(
|
|||||||
)
|
)
|
||||||
.fetch_one(executor)
|
.fetch_one(executor)
|
||||||
.instrument(info_span!("Fetch user"))
|
.instrument(info_span!("Fetch user"))
|
||||||
.await
|
.await?;
|
||||||
|
|
||||||
|
Ok(User {
|
||||||
|
data: res.id,
|
||||||
|
username: res.username,
|
||||||
|
sub: format!("fake-sub-{}", res.id),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@@ -14,16 +14,17 @@
|
|||||||
|
|
||||||
//! Contexts used in templates
|
//! Contexts used in templates
|
||||||
|
|
||||||
|
use mas_data_model::BrowserSession;
|
||||||
use oauth2_types::errors::OAuth2Error;
|
use oauth2_types::errors::OAuth2Error;
|
||||||
use serde::{ser::SerializeStruct, Serialize};
|
use serde::{ser::SerializeStruct, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{errors::ErroredForm, filters::CsrfToken, storage::SessionInfo};
|
use crate::{errors::ErroredForm, filters::CsrfToken, storage::PostgresqlBackend};
|
||||||
|
|
||||||
/// Helper trait to construct context wrappers
|
/// Helper trait to construct context wrappers
|
||||||
pub trait TemplateContext {
|
pub trait TemplateContext {
|
||||||
/// Attach a user session to the template context
|
/// Attach a user session to the template context
|
||||||
fn with_session(self, current_session: SessionInfo) -> WithSession<Self>
|
fn with_session(self, current_session: BrowserSession<PostgresqlBackend>) -> WithSession<Self>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
@@ -34,7 +35,10 @@ pub trait TemplateContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Attach an optional user session to the template context
|
/// Attach an optional user session to the template context
|
||||||
fn maybe_with_session(self, current_session: Option<SessionInfo>) -> WithOptionalSession<Self>
|
fn maybe_with_session(
|
||||||
|
self,
|
||||||
|
current_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||||
|
) -> WithOptionalSession<Self>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
@@ -91,7 +95,7 @@ impl<T: TemplateContext> TemplateContext for WithCsrf<T> {
|
|||||||
/// Context with a user session in it
|
/// Context with a user session in it
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct WithSession<T> {
|
pub struct WithSession<T> {
|
||||||
current_session: SessionInfo,
|
current_session: BrowserSession<PostgresqlBackend>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
inner: T,
|
inner: T,
|
||||||
@@ -102,7 +106,7 @@ impl<T: TemplateContext> TemplateContext for WithSession<T> {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
SessionInfo::samples()
|
BrowserSession::<PostgresqlBackend>::samples()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flat_map(|session| {
|
.flat_map(|session| {
|
||||||
T::sample().into_iter().map(move |inner| WithSession {
|
T::sample().into_iter().map(move |inner| WithSession {
|
||||||
@@ -117,7 +121,7 @@ impl<T: TemplateContext> TemplateContext for WithSession<T> {
|
|||||||
/// Context with an optional user session in it
|
/// Context with an optional user session in it
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct WithOptionalSession<T> {
|
pub struct WithOptionalSession<T> {
|
||||||
current_session: Option<SessionInfo>,
|
current_session: Option<BrowserSession<PostgresqlBackend>>,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
inner: T,
|
inner: T,
|
||||||
@@ -128,7 +132,7 @@ impl<T: TemplateContext> TemplateContext for WithOptionalSession<T> {
|
|||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
SessionInfo::samples()
|
BrowserSession::<PostgresqlBackend>::samples()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Some) // Wrap all samples in an Option
|
.map(Some) // Wrap all samples in an Option
|
||||||
.chain(std::iter::once(None)) // Add the "None" option
|
.chain(std::iter::once(None)) // Add the "None" option
|
||||||
|
@@ -34,7 +34,7 @@ limitations under the License.
|
|||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
{% if current_session %}
|
{% if current_session %}
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
Howdy {{ current_session.username }}!
|
Howdy {{ current_session.user.username }}!
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<form method="POST" action="/logout">
|
<form method="POST" action="/logout">
|
||||||
|
13
crates/data-model/Cargo.toml
Normal file
13
crates/data-model/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "mas-data-model"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Quentin Gliech <quenting@element.io>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = "0.4.19"
|
||||||
|
thiserror = "1.0.29"
|
||||||
|
serde = "1.0.130"
|
||||||
|
|
||||||
|
oauth2-types = { path = "../oauth2-types" }
|
134
crates/data-model/src/lib.rs
Normal file
134
crates/data-model/src/lib.rs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
use chrono::{DateTime, Duration, Utc};
|
||||||
|
use oauth2_types::{pkce::CodeChallengeMethod, scope::Scope};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
pub trait StorageBackend {
|
||||||
|
type UserData: Clone + std::fmt::Debug + PartialEq;
|
||||||
|
type AuthenticationData: Clone + std::fmt::Debug + PartialEq;
|
||||||
|
type BrowserSessionData: Clone + std::fmt::Debug + PartialEq;
|
||||||
|
type ClientData: Clone + std::fmt::Debug + PartialEq;
|
||||||
|
type SessionData: Clone + std::fmt::Debug + PartialEq;
|
||||||
|
type AuthorizationCodeData: Clone + std::fmt::Debug + PartialEq;
|
||||||
|
type AccessTokenData: Clone + std::fmt::Debug + PartialEq;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
pub struct User<T: StorageBackend> {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub data: T::UserData,
|
||||||
|
pub username: String,
|
||||||
|
pub sub: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: StorageBackend> User<T>
|
||||||
|
where
|
||||||
|
T::UserData: Default,
|
||||||
|
{
|
||||||
|
pub fn samples() -> Vec<Self> {
|
||||||
|
vec![User {
|
||||||
|
data: Default::default(),
|
||||||
|
username: "john".to_string(),
|
||||||
|
sub: "123-456".to_string(),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
pub struct Authentication<T: StorageBackend> {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub data: T::AuthenticationData,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
pub struct BrowserSession<T: StorageBackend> {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub data: T::BrowserSessionData,
|
||||||
|
pub user: User<T>,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
pub last_authentication: Option<Authentication<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: StorageBackend> BrowserSession<T>
|
||||||
|
where
|
||||||
|
T::BrowserSessionData: Default,
|
||||||
|
T::UserData: Default,
|
||||||
|
{
|
||||||
|
pub fn samples() -> Vec<Self> {
|
||||||
|
User::<T>::samples()
|
||||||
|
.into_iter()
|
||||||
|
.map(|user| BrowserSession {
|
||||||
|
data: Default::default(),
|
||||||
|
user,
|
||||||
|
created_at: Utc::now(),
|
||||||
|
last_authentication: None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
pub struct Client<T: StorageBackend> {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub data: T::ClientData,
|
||||||
|
pub client_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||||
|
pub struct Session<T: StorageBackend> {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub data: T::SessionData,
|
||||||
|
pub browser_session: Option<BrowserSession<T>>,
|
||||||
|
pub client: Client<T>,
|
||||||
|
pub scope: Scope,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
|
pub struct Pkce {
|
||||||
|
challenge_method: CodeChallengeMethod,
|
||||||
|
challenge: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pkce {
|
||||||
|
pub fn new(challenge_method: CodeChallengeMethod, challenge: String) -> Self {
|
||||||
|
Pkce {
|
||||||
|
challenge_method,
|
||||||
|
challenge,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self, verifier: &str) -> bool {
|
||||||
|
self.challenge_method.verify(&self.challenge, verifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
|
pub struct AuthorizationCode<T: StorageBackend> {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub data: T::AuthorizationCodeData,
|
||||||
|
pub code: String,
|
||||||
|
pub pkce: Pkce,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct AccessToken<T: StorageBackend> {
|
||||||
|
pub data: T::AccessTokenData,
|
||||||
|
pub jti: String,
|
||||||
|
pub token: String,
|
||||||
|
pub expires_after: Duration,
|
||||||
|
pub created_at: DateTime<Utc>,
|
||||||
|
}
|
Reference in New Issue
Block a user