Skip to content

Commit

Permalink
merge: #2920
Browse files Browse the repository at this point in the history
2920: feat(sdf-server, auth-portal, web): adding a page that we can send a post request to that forces auth cleanup r=stack72 a=stack72

This page is basic for now and we redirect both ways but we can make this better. It will compare the users from the auth-api against what’s in our DB - the auth-api is the source of truth!!

There are a number of improvements needed for this but we can start to use it immediately for internal use

In the future, this will be replaced by the use of websockets to do the same job

Co-authored-by: stack72 <[email protected]>
  • Loading branch information
si-bors-ng[bot] and stack72 authored Oct 31, 2023
2 parents e57e922 + 86f18b2 commit 5eea1d9
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 7 deletions.
7 changes: 6 additions & 1 deletion app/auth-portal/src/pages/WorkspaceDetailsPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,12 @@ const editWorkspace = async () => {
const deleteUserHandlerReq = workspacesStore.getRequestStatus("REMOVE_USER");
const deleteUserHandler = async (email: string) => {
if (email === "") return;
return await workspacesStore.REMOVE_USER(email, props.workspaceId);
const res = await workspacesStore.REMOVE_USER(email, props.workspaceId);
if (res.result.success) {
if (!draftWorkspace.instanceUrl.includes("localhost")) {
window.location.href = ` ${draftWorkspace.instanceUrl}/refresh-auth?workspaceId=${props.workspaceId}`;
}
}
};
const inviteButtonHandler = async () => {
Expand Down
22 changes: 22 additions & 0 deletions app/web/src/pages/auth/RefreshAuthPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template><div>Refresh auth</div></template>

<script setup lang="ts">
import { useRoute } from "vue-router";
import { onMounted } from "vue";
import { useAuthStore } from "@/store/auth.store";
const route = useRoute();
const authStore = useAuthStore();
const AUTH_PORTAL_URL = import.meta.env.VITE_AUTH_PORTAL_URL;
const workspaceId = route.query.workspaceId as string;
onMounted(async () => {
if (workspaceId) {
await authStore.FORCE_REFRESH_MEMBERS(workspaceId);
}
window.location.href = `${AUTH_PORTAL_URL}/workspace/${workspaceId}`;
});
</script>
6 changes: 6 additions & 0 deletions app/web/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ const routes: RouteRecordRaw[] = [
meta: { public: true },
component: () => import("@/pages/auth/AuthConnectPage.vue"),
},
{
path: "/refresh-auth",
name: "refresh-auth",
meta: { public: true },
component: () => import("@/pages/auth/RefreshAuthPage.vue"),
},
{
path: "/login",
name: "login",
Expand Down
9 changes: 9 additions & 0 deletions app/web/src/store/auth.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,5 +186,14 @@ export const useAuthStore = defineStore("auth", {
email: loginResponse.user.email,
});
},
async FORCE_REFRESH_MEMBERS(workspaceId: string) {
return new ApiRequest({
method: "post",
url: "/session/refresh_workspace_members",
params: {
workspaceId,
},
});
},
},
});
5 changes: 5 additions & 0 deletions lib/dal/src/queries/user/list_members_for_workspace.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
SELECT row_to_json(u.*) AS object
FROM users AS u
INNER JOIN user_belongs_to_workspaces bt ON bt.user_pk = u.pk
WHERE bt.workspace_pk = $1
ORDER BY u.created_at ASC
38 changes: 38 additions & 0 deletions lib/dal/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{

const USER_GET_BY_PK: &str = include_str!("queries/user/get_by_pk.sql");
const USER_GET_BY_EMAIL_RAW: &str = include_str!("queries/user/get_by_email_raw.sql");
const USER_LIST_FOR_WORKSPACE: &str = include_str!("queries/user/list_members_for_workspace.sql");

#[remain::sorted]
#[derive(Error, Debug)]
Expand Down Expand Up @@ -154,6 +155,43 @@ impl User {
.await?;
Ok(())
}

pub async fn delete_user_from_workspace(
ctx: &DalContext,
user_pk: UserPk,
workspace_pkg: String,
) -> UserResult<()> {
ctx.txns()
.await?
.pg()
.execute(
"DELETE from user_belongs_to_workspaces WHERE user_pk = $1 AND workspace_pk = $2",
&[&user_pk, &workspace_pkg],
)
.await?;
Ok(())
}

pub async fn list_members_for_workspace(
ctx: &DalContext,
workspace_pk: String,
) -> UserResult<Vec<Self>> {
let rows = ctx
.txns()
.await?
.pg()
.query(USER_LIST_FOR_WORKSPACE, &[&workspace_pk])
.await?;

let mut users: Vec<User> = Vec::new();
for row in rows.into_iter() {
let json: serde_json::Value = row.try_get("object")?;
let object = serde_json::from_value(json)?;
users.push(object);
}

Ok(users)
}
}

#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
Expand Down
12 changes: 12 additions & 0 deletions lib/sdf-server/src/server/service/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use dal::{
KeyPairError, StandardModelError, TransactionsError, UserError, UserPk, WorkspaceError,
WorkspacePk,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::server::state::AppState;

pub mod auth_connect;
pub mod load_workspaces;
mod refresh_workspace_members;
pub mod restore_authentication;

#[remain::sorted]
Expand Down Expand Up @@ -46,6 +48,12 @@ pub enum SessionError {
Workspace(#[from] WorkspaceError),
}

#[derive(Debug, Serialize, Deserialize)]
struct AuthApiErrBody {
pub kind: String,
pub message: String,
}

pub type SessionResult<T> = std::result::Result<T, SessionError>;

impl IntoResponse for SessionError {
Expand Down Expand Up @@ -81,4 +89,8 @@ pub fn routes() -> Router<AppState> {
get(restore_authentication::restore_authentication),
)
.route("/load_workspaces", get(load_workspaces::load_workspaces))
.route(
"/refresh_workspace_members",
post(refresh_workspace_members::refresh_workspace_members),
)
}
7 changes: 1 addition & 6 deletions lib/sdf-server/src/server/service/session/auth_connect.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::{SessionError, SessionResult};
use crate::server::extract::{HandlerContext, RawAccessToken};
use crate::service::session::AuthApiErrBody;
use axum::Json;
use dal::{DalContext, HistoryActor, KeyPair, Tenancy, User, UserPk, Workspace, WorkspacePk};
use serde::{Deserialize, Serialize};
Expand All @@ -26,12 +27,6 @@ pub struct AuthReconnectResponse {
pub workspace: Workspace,
}

#[derive(Debug, Serialize, Deserialize)]
struct AuthApiErrBody {
pub kind: String,
pub message: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthApiUser {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use super::{SessionError, SessionResult};
use crate::server::extract::{AccessBuilder, HandlerContext, RawAccessToken};
use crate::service::session::AuthApiErrBody;
use axum::Json;
use dal::User;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RefreshWorkspaceMembersRequest {
pub workspace_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceMember {
pub user_id: String,
pub email: String,
pub nickname: String,
pub role: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RefreshWorkspaceMembersResponse {
pub success: bool,
}

pub async fn refresh_workspace_members(
HandlerContext(builder): HandlerContext,
AccessBuilder(access_builder): AccessBuilder,
RawAccessToken(raw_access_token): RawAccessToken,
Json(request): Json<RefreshWorkspaceMembersRequest>,
) -> SessionResult<Json<RefreshWorkspaceMembersResponse>> {
let client = reqwest::Client::new();
let auth_api_url = match option_env!("LOCAL_AUTH_STACK") {
Some(_) => "http://localhost:9001",
None => "https://auth-api.systeminit.com",
};

let res = client
.get(format!(
"{}/workspace/{}/members",
auth_api_url,
request.workspace_id.clone()
))
.bearer_auth(&raw_access_token)
.send()
.await?;

if res.status() != reqwest::StatusCode::OK {
let res_err_body = res
.json::<AuthApiErrBody>()
.await
.map_err(|err| SessionError::AuthApiError(err.to_string()))?;
println!("code exchange failed = {:?}", res_err_body.message);
return Err(SessionError::AuthApiError(res_err_body.message));
}

let workspace_members = res.json::<Vec<WorkspaceMember>>().await?;

let ctx = builder.build_head(access_builder).await?;
let members = User::list_members_for_workspace(&ctx, request.workspace_id.clone()).await?;
let member_ids: Vec<_> = workspace_members.into_iter().map(|w| w.user_id).collect();
let users_to_remove: Vec<_> = members
.into_iter()
.filter(|u| !member_ids.contains(&u.pk().to_string()))
.collect();

for remove in users_to_remove {
println!("Removing User: {}", remove.pk().clone());
User::delete_user_from_workspace(&ctx, remove.pk(), request.workspace_id.clone()).await?;
}

Ok(Json(RefreshWorkspaceMembersResponse { success: true }))
}

0 comments on commit 5eea1d9

Please sign in to comment.