feat: change to more complete token instead of just id
All checks were successful
ci/woodpecker/pr/test Pipeline was successful

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
Kasper Juul Hermansen 2023-11-04 17:32:51 +01:00
parent 74362f3b1c
commit 70915aec65
Signed by: kjuulh
GPG Key ID: 57B6E1465221F912
4 changed files with 49 additions and 24 deletions

View File

@ -6,7 +6,7 @@ use axum::http::{header::SET_COOKIE, HeaderMap};
use oauth2::url::Url; use oauth2::url::Url;
use crate::{ use crate::{
introspection::IntrospectionService, introspection::{IdToken, IntrospectionService},
login::{auth_clap::AuthEngine, config::ConfigClap, AuthClap}, login::{auth_clap::AuthEngine, config::ConfigClap, AuthClap},
oauth::{zitadel::ZitadelConfig, OAuth}, oauth::{zitadel::ZitadelConfig, OAuth},
session::{SessionService, User}, session::{SessionService, User},
@ -15,7 +15,7 @@ use crate::{
#[async_trait] #[async_trait]
pub trait Auth { pub trait Auth {
async fn login(&self) -> anyhow::Result<Url>; async fn login(&self) -> anyhow::Result<Url>;
async fn login_token(&self, user: &str, password: &str) -> anyhow::Result<String>; async fn login_token(&self, user: &str, password: &str) -> anyhow::Result<IdToken>;
async fn login_authorized(&self, code: &str, state: &str) -> anyhow::Result<(HeaderMap, Url)>; async fn login_authorized(&self, code: &str, state: &str) -> anyhow::Result<(HeaderMap, Url)>;
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User>; async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User>;
} }
@ -86,8 +86,8 @@ impl Auth for ZitadelAuthService {
} }
async fn login_authorized(&self, code: &str, _state: &str) -> anyhow::Result<(HeaderMap, Url)> { async fn login_authorized(&self, code: &str, _state: &str) -> anyhow::Result<(HeaderMap, Url)> {
let token = self.oauth.exchange(code).await?; let token = self.oauth.exchange(code).await?;
let user_id = self.introspection.get_id_token(token.as_str()).await?; let id_token = self.introspection.get_id_token(token.as_str()).await?;
let cookie_value = self.session.insert_user("user", user_id.as_str()).await?; let cookie_value = self.session.insert_user("user", id_token).await?;
let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie_value); let cookie = format!("{}={}; SameSite=Lax; Path=/", COOKIE_NAME, cookie_value);
@ -100,12 +100,12 @@ impl Auth for ZitadelAuthService {
.context("failed to parse login_authorized zitadel return url")?, .context("failed to parse login_authorized zitadel return url")?,
)) ))
} }
async fn login_token(&self, _user: &str, password: &str) -> anyhow::Result<String> { async fn login_token(&self, _user: &str, password: &str) -> anyhow::Result<IdToken> {
self.introspection.get_id_token(password).await self.introspection.get_id_token(password).await
} }
async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User> { async fn get_user_from_session(&self, cookie: &str) -> anyhow::Result<User> {
match self.session.get_user(cookie).await? { match self.session.get_user(cookie).await? {
Some(u) => Ok(User { id: u }), Some(u) => Ok(u),
None => Err(anyhow::anyhow!("failed to find user")), None => Err(anyhow::anyhow!("failed to find user")),
} }
} }
@ -126,7 +126,7 @@ impl Auth for NoopAuthService {
todo!() todo!()
} }
async fn login_token(&self, _user: &str, _password: &str) -> anyhow::Result<String> { async fn login_token(&self, _user: &str, _password: &str) -> anyhow::Result<IdToken> {
todo!() todo!()
} }

View File

@ -121,7 +121,11 @@ where
})?; })?;
return Ok(UserFromSession { return Ok(UserFromSession {
user: User { id: token }, user: User {
id: token.sub,
email: token.email,
name: token.name,
},
}); });
} }
@ -144,8 +148,6 @@ where
) )
})?; })?;
Ok(UserFromSession { Ok(UserFromSession { user })
user: User { id: user.id },
})
} }
} }

View File

@ -4,6 +4,7 @@ use async_trait::async_trait;
use axum::extract::FromRef; use axum::extract::FromRef;
use oauth2::TokenIntrospectionResponse; use oauth2::TokenIntrospectionResponse;
use openidconnect::IntrospectionUrl; use openidconnect::IntrospectionUrl;
use serde::{Deserialize, Serialize};
use zitadel::{ use zitadel::{
axum::introspection::IntrospectionStateBuilderError, axum::introspection::IntrospectionStateBuilderError,
credentials::Application, credentials::Application,
@ -15,10 +16,17 @@ use zitadel::{
use crate::login::AuthClap; use crate::login::AuthClap;
#[derive(Clone, Serialize, Deserialize)]
pub struct IdToken {
pub sub: String,
pub email: String,
pub name: String,
}
#[async_trait] #[async_trait]
pub trait Introspection { pub trait Introspection {
async fn get_user(&self) -> anyhow::Result<()>; async fn get_user(&self) -> anyhow::Result<()>;
async fn get_id_token(&self, token: &str) -> anyhow::Result<String>; async fn get_id_token(&self, token: &str) -> anyhow::Result<IdToken>;
} }
pub struct IntrospectionService(Arc<dyn Introspection + Send + Sync + 'static>); pub struct IntrospectionService(Arc<dyn Introspection + Send + Sync + 'static>);
@ -61,7 +69,7 @@ impl Introspection for ZitadelIntrospection {
async fn get_user(&self) -> anyhow::Result<()> { async fn get_user(&self) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
async fn get_id_token(&self, token: &str) -> anyhow::Result<String> { async fn get_id_token(&self, token: &str) -> anyhow::Result<IdToken> {
let config = &self.state.config; let config = &self.state.config;
let res = introspect( let res = introspect(
&config.introspection_uri, &config.introspection_uri,
@ -71,10 +79,21 @@ impl Introspection for ZitadelIntrospection {
) )
.await?; .await?;
Ok(res let sub = res
.sub() .sub()
.ok_or(anyhow::anyhow!("could not find a userid (sub) in token"))? .ok_or(anyhow::anyhow!("could not find a userid (sub) in token"))?
.to_string()) .to_string();
let extra = res.extra_fields();
let email = extra.email.clone().ok_or(anyhow::anyhow!(
"could not find a email (scope email) in token"
))?;
let name = extra.name.clone().ok_or(anyhow::anyhow!(
"could not find a name (scope profile) in token"
))?;
Ok(IdToken { sub, email, name })
} }
} }

View File

@ -11,7 +11,7 @@ use async_trait::async_trait;
use axum_sessions::async_session::{Session as AxumSession, SessionStore as AxumSessionStore}; use axum_sessions::async_session::{Session as AxumSession, SessionStore as AxumSessionStore};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::login::AuthClap; use crate::{introspection::IdToken, login::AuthClap};
#[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(clap::Args, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SessionClap { pub struct SessionClap {
@ -27,8 +27,8 @@ pub struct PostgresqlSessionClap {
#[async_trait] #[async_trait]
pub trait Session { pub trait Session {
async fn insert_user(&self, id: &str, user_id: &str) -> anyhow::Result<String>; async fn insert_user(&self, id: &str, id_token: IdToken) -> anyhow::Result<String>;
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<String>>; async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<User>>;
} }
pub struct SessionService(Arc<dyn Session + Send + Sync + 'static>); pub struct SessionService(Arc<dyn Session + Send + Sync + 'static>);
@ -73,16 +73,20 @@ pub struct PostgresSessionService {
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct User { pub struct User {
pub id: String, pub id: String,
pub email: String,
pub name: String,
} }
#[async_trait] #[async_trait]
impl Session for PostgresSessionService { impl Session for PostgresSessionService {
async fn insert_user(&self, _id: &str, user_id: &str) -> anyhow::Result<String> { async fn insert_user(&self, _id: &str, id_token: IdToken) -> anyhow::Result<String> {
let mut session = AxumSession::new(); let mut session = AxumSession::new();
session.insert( session.insert(
"user", "user",
User { User {
id: user_id.to_string(), id: id_token.sub,
email: id_token.email,
name: id_token.name,
}, },
)?; )?;
@ -94,14 +98,14 @@ impl Session for PostgresSessionService {
Ok(cookie) Ok(cookie)
} }
async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<String>> { async fn get_user(&self, cookie: &str) -> anyhow::Result<Option<User>> {
if let Some(session) = self.store.load_session(cookie.to_string()).await.unwrap() { if let Some(session) = self.store.load_session(cookie.to_string()).await.unwrap() {
if let Some(user) = session.get::<User>("user") { if let Some(user) = session.get::<User>("user") {
tracing::debug!( tracing::debug!(
"UserFromSession: session decoded success, user_id={:?}", "UserFromSession: session decoded success, user_id={:?}",
user.id user.id
); );
Ok(Some(user.id)) Ok(Some(user))
} else { } else {
Ok(None) Ok(None)
} }
@ -119,11 +123,11 @@ pub struct InMemorySessionService {}
#[async_trait] #[async_trait]
impl Session for InMemorySessionService { impl Session for InMemorySessionService {
async fn insert_user(&self, _id: &str, _user_id: &str) -> anyhow::Result<String> { async fn insert_user(&self, _id: &str, _id_token: IdToken) -> anyhow::Result<String> {
todo!() todo!()
} }
async fn get_user(&self, _cookie: &str) -> anyhow::Result<Option<String>> { async fn get_user(&self, _cookie: &str) -> anyhow::Result<Option<User>> {
todo!() todo!()
} }
} }