Implement /userinfo

This commit is contained in:
Olivier 'reivilibre' 2024-01-21 23:05:44 +00:00
parent 10c1fb522b
commit 500dfd401a
4 changed files with 147 additions and 2 deletions

View File

@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT user_name, user_id, session_id\n FROM application_access_tokens\n INNER JOIN application_sessions USING (session_id)\n INNER JOIN users USING (user_id)\n WHERE access_token_hash = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "user_name",
"type_info": "Text"
},
{
"ordinal": 1,
"name": "user_id",
"type_info": "Uuid"
},
{
"ordinal": 2,
"name": "session_id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Bytea"
]
},
"nullable": [
false,
false,
false
]
},
"hash": "50ccdaf65a2fe4a0596ed467890a1cd1722219fef41b80eaf5c7a9c20e2704a7"
}

View File

@ -9,6 +9,7 @@ use sqlx::{types::Uuid, Connection, PgPool, Postgres, Transaction};
use crate::web::login::{
LoginSession, LOGIN_SESSION_TOKEN_HASH_BYTES, LOGIN_SESSION_XSRF_SECRET_BYTES,
};
use crate::web::oauth_openid::application_session_access_token::ApplicationSession;
/// Postgres-backed storage for IdCoop
pub struct IdCoopStore {
@ -299,4 +300,31 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> {
.map_err(|_| eyre!("cannot retrieve login session: has invalid XSRF token"))?,
}))
}
pub async fn lookup_application_session(
&mut self,
access_token_hash: &[u8; 32],
) -> eyre::Result<Option<ApplicationSession>> {
let row_opt = sqlx::query!(
"
SELECT user_name, user_id, session_id
FROM application_access_tokens
INNER JOIN application_sessions USING (session_id)
INNER JOIN users USING (user_id)
WHERE access_token_hash = $1
",
access_token_hash
)
.fetch_optional(&mut **self.txn)
.await
.context("failed to lookup application session")?;
let Some(row) = row_opt else { return Ok(None); };
Ok(Some(ApplicationSession {
application_session_id: row.session_id,
user_name: row.user_name,
user_id: row.user_id,
}))
}
}

View File

@ -3,15 +3,31 @@ use std::sync::Arc;
use axum::{http::StatusCode, response::IntoResponse, Extension, Json};
use josekit::jwk::Jwk;
use serde::Serialize;
use sqlx::types::Uuid;
use crate::config::{Configuration, SecretConfig};
use self::application_session_access_token::ApplicationSession;
pub mod application_session_access_token;
pub mod authorisation;
pub mod ext_codes;
pub mod token;
pub async fn oidc_userinfo() -> impl IntoResponse {
(StatusCode::NOT_IMPLEMENTED, "NOT YET IMPLEMENTED")
#[derive(Serialize)]
struct UserInfoResponse {
sub: Uuid,
name: String,
preferred_username: String,
// TODO more...
}
pub async fn oidc_userinfo(application_session: ApplicationSession) -> impl IntoResponse {
Json(UserInfoResponse {
sub: application_session.user_id,
name: application_session.user_name.clone(),
preferred_username: application_session.user_name,
})
}
#[derive(Serialize)]

View File

@ -0,0 +1,67 @@
use std::sync::Arc;
use async_trait::async_trait;
use axum::{
extract::FromRequestParts,
headers::{authorization::Bearer, Authorization},
http::{request::Parts, StatusCode},
Extension, TypedHeader,
};
use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
use blake2::{Blake2s256, Digest};
use sqlx::types::Uuid;
use tracing::error;
use crate::store::IdCoopStore;
pub struct ApplicationSession {
pub application_session_id: i32,
pub user_name: String,
pub user_id: Uuid,
}
#[async_trait]
impl<S> FromRequestParts<S> for ApplicationSession
where
S: Send + Sync,
{
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
let Ok(TypedHeader(Authorization(bearer))) = TypedHeader::<Authorization<Bearer>>::from_request_parts(parts, state).await else {
return Err((StatusCode::UNAUTHORIZED, "No access token."));
};
let Ok(access_token) = BASE64_URL_SAFE_NO_PAD.decode(&bearer.token()) else {
return Err((
StatusCode::UNAUTHORIZED,
"Invalid access token."
));
};
let access_token_hash: [u8; 32] = Blake2s256::digest(&access_token).into();
let db_store = Extension::<Arc<IdCoopStore>>::from_request_parts(parts, state)
.await
.expect("no db store; this is a programming error");
match db_store
.txn(|mut txn| {
Box::pin(async move { txn.lookup_application_session(&access_token_hash).await })
})
.await
{
Ok(Some(session)) => Ok(session),
Ok(None) => {
return Err((StatusCode::UNAUTHORIZED, "Invalid application session."));
}
Err(err) => {
error!("failed to check application session: {err:?}");
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"A fault occurred when checking your application session. If the issue persists, please contact an administrator.",
));
}
}
// TODO do we want a middleware to renew the cookie?
}
}