rls
This commit is contained in:
+58
-29
@@ -26,14 +26,17 @@ use crate::{
|
||||
},
|
||||
error::{AppError, AppResult},
|
||||
models::{
|
||||
MagicToken, MagicTokenKind, NewRefreshToken, NewUser, RefreshToken, Tenant, TenantStatus,
|
||||
User, UserMembership,
|
||||
MagicToken, MagicTokenKind, NewRefreshToken, NewUser, RefreshToken, TenantStatus, User,
|
||||
},
|
||||
schema::{
|
||||
magic_tokens::dsl as magic_dsl, refresh_tokens, tenants::dsl as tenant_dsl,
|
||||
user_memberships::dsl as memberships_dsl, user_passkeys::dsl as passkey_dsl, users::dsl,
|
||||
},
|
||||
state::AppState,
|
||||
tenants::{
|
||||
apply_refresh_token_hash, apply_tenant_guc, apply_user_guc, clear_refresh_token_hash,
|
||||
clear_user_guc,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::schema::refresh_tokens::dsl as refresh_dsl;
|
||||
@@ -337,6 +340,7 @@ pub async fn refresh(
|
||||
let now = Utc::now();
|
||||
let now_naive = now.naive_utc();
|
||||
|
||||
apply_refresh_token_hash(&mut conn, &hashed)?;
|
||||
let token = match refresh_dsl::refresh_tokens
|
||||
.filter(refresh_dsl::token_hash.eq(&hashed))
|
||||
.filter(refresh_dsl::revoked_at.is_null())
|
||||
@@ -348,6 +352,10 @@ pub async fn refresh(
|
||||
Err(err) => return Err(AppError::from(err)),
|
||||
};
|
||||
|
||||
clear_refresh_token_hash(&mut conn)?;
|
||||
apply_tenant_guc(&mut conn, token.tenant_id)?;
|
||||
clear_user_guc(&mut conn)?;
|
||||
|
||||
diesel::update(refresh_dsl::refresh_tokens.filter(refresh_dsl::id.eq(token.id)))
|
||||
.set((
|
||||
refresh_dsl::revoked_at.eq(now_naive),
|
||||
@@ -398,15 +406,17 @@ pub async fn select_tenant(
|
||||
};
|
||||
|
||||
let mut conn = state.db_unscoped()?;
|
||||
apply_user_guc(&mut conn, user_id)?;
|
||||
|
||||
let membership_exists = memberships_dsl::user_memberships
|
||||
.filter(memberships_dsl::user_id.eq(user_id))
|
||||
.filter(memberships_dsl::tenant_id.eq(payload.tenant_id))
|
||||
.inner_join(tenant_dsl::tenants)
|
||||
.select(memberships_dsl::id)
|
||||
.select(memberships_dsl::tenant_id)
|
||||
.first::<Uuid>(&mut conn)
|
||||
.optional()?;
|
||||
|
||||
clear_user_guc(&mut conn)?;
|
||||
|
||||
if membership_exists.is_none() {
|
||||
return Err(AppError::unauthorized());
|
||||
}
|
||||
@@ -430,7 +440,7 @@ pub async fn logout(
|
||||
user: AuthenticatedUser,
|
||||
jar: Option<TypedHeader<Cookie>>,
|
||||
) -> AppResult<(HeaderMap, StatusCode)> {
|
||||
let mut conn = state.db_unscoped()?;
|
||||
let mut conn = state.db_for_tenant(user.tenant_id)?;
|
||||
let now = Utc::now().naive_utc();
|
||||
let mut rows_affected = 0;
|
||||
|
||||
@@ -505,15 +515,26 @@ pub async fn list_tenants(
|
||||
};
|
||||
|
||||
let mut conn = state.db_unscoped()?;
|
||||
apply_user_guc(&mut conn, user_id)?;
|
||||
|
||||
let tenants = memberships_dsl::user_memberships
|
||||
.inner_join(tenant_dsl::tenants)
|
||||
let tenant_ids: Vec<Uuid> = memberships_dsl::user_memberships
|
||||
.filter(memberships_dsl::user_id.eq(user_id))
|
||||
.select((tenant_dsl::id, tenant_dsl::name))
|
||||
.load::<(Uuid, String)>(&mut conn)?
|
||||
.into_iter()
|
||||
.map(|(id, name)| TenantSnippet { id, name })
|
||||
.collect();
|
||||
.select(memberships_dsl::tenant_id)
|
||||
.load(&mut conn)?;
|
||||
|
||||
clear_user_guc(&mut conn)?;
|
||||
drop(conn);
|
||||
|
||||
let mut tenants = Vec::with_capacity(tenant_ids.len());
|
||||
for tenant_id in tenant_ids {
|
||||
let mut tenant_conn = state.db_for_tenant(tenant_id)?;
|
||||
let name: String = tenant_dsl::tenants
|
||||
.find(tenant_id)
|
||||
.select(tenant_dsl::name)
|
||||
.first(&mut tenant_conn)
|
||||
.map_err(AppError::from)?;
|
||||
tenants.push(TenantSnippet { id: tenant_id, name });
|
||||
}
|
||||
|
||||
Ok(Json(TenantListResponse { tenants }))
|
||||
}
|
||||
@@ -642,26 +663,27 @@ fn complete_login(
|
||||
user: &User,
|
||||
preferred_tenant_id: Option<Uuid>,
|
||||
) -> AppResult<Response> {
|
||||
let memberships: Vec<(UserMembership, Tenant)> = memberships_dsl::user_memberships
|
||||
.inner_join(tenant_dsl::tenants)
|
||||
apply_user_guc(conn, user.id)?;
|
||||
let tenant_ids: Vec<Uuid> = memberships_dsl::user_memberships
|
||||
.filter(memberships_dsl::user_id.eq(user.id))
|
||||
.select(memberships_dsl::tenant_id)
|
||||
.load(conn)?;
|
||||
clear_user_guc(conn)?;
|
||||
|
||||
if memberships.is_empty() {
|
||||
tracing::debug!(user_id = %user.id, tenants = tenant_ids.len(), "passkey login memberships");
|
||||
|
||||
if tenant_ids.is_empty() {
|
||||
return Err(AppError::unauthorized());
|
||||
}
|
||||
|
||||
if let Some(preferred_id) = preferred_tenant_id {
|
||||
if let Some((_, tenant)) = memberships
|
||||
.iter()
|
||||
.find(|(_, tenant)| tenant.id == preferred_id)
|
||||
{
|
||||
return issue_session(state, conn, user, tenant.id);
|
||||
if tenant_ids.iter().any(|id| *id == preferred_id) {
|
||||
return issue_session(state, conn, user, preferred_id);
|
||||
}
|
||||
}
|
||||
|
||||
if memberships.len() == 1 {
|
||||
return issue_session(state, conn, user, memberships[0].1.id);
|
||||
if tenant_ids.len() == 1 {
|
||||
return issue_session(state, conn, user, tenant_ids[0]);
|
||||
}
|
||||
|
||||
let selection_token = state
|
||||
@@ -669,13 +691,16 @@ fn complete_login(
|
||||
.generate_tenant_selector_token(user.id)
|
||||
.map_err(AppError::from)?;
|
||||
|
||||
let tenants = memberships
|
||||
.into_iter()
|
||||
.map(|(_, tenant)| TenantSnippet {
|
||||
id: tenant.id,
|
||||
name: tenant.name,
|
||||
})
|
||||
.collect();
|
||||
let mut tenants = Vec::with_capacity(tenant_ids.len());
|
||||
for tenant_id in tenant_ids {
|
||||
let mut tenant_conn = state.db_for_tenant(tenant_id)?;
|
||||
let name: String = tenant_dsl::tenants
|
||||
.find(tenant_id)
|
||||
.select(tenant_dsl::name)
|
||||
.first(&mut tenant_conn)
|
||||
.map_err(AppError::from)?;
|
||||
tenants.push(TenantSnippet { id: tenant_id, name });
|
||||
}
|
||||
|
||||
Ok(Json(TenantSelectionResponse {
|
||||
access_token: selection_token,
|
||||
@@ -747,6 +772,10 @@ fn issue_session(
|
||||
user: &User,
|
||||
tenant_id: Uuid,
|
||||
) -> AppResult<Response> {
|
||||
apply_tenant_guc(conn, tenant_id)?;
|
||||
clear_user_guc(conn)?;
|
||||
clear_refresh_token_hash(conn)?;
|
||||
|
||||
let now = Utc::now();
|
||||
let access_token = state
|
||||
.jwt
|
||||
|
||||
Reference in New Issue
Block a user