Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(users): add support for tenant level users #6708

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

apoorvdixit88
Copy link
Contributor

@apoorvdixit88 apoorvdixit88 commented Dec 1, 2024

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

  • Add api to create tenant users
  • Add api to create orgs
  • Support tenant level entity for the user hierarchy

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

Closes #6707

How did you test it?

Api for create tenant:

curl --location 'http://localhost:8080/user/create_tenant' \
--header 'api-key: test_admin' \
--header 'Content-Type: application/json' \
--header 'x-tenant-id: test' \
--header 'Cookie: login_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiNTJhMmViYzItOWZiZS00MDI0LThjODEtOWYwMzMyZTJlZDMxIiwicHVycG9zZSI6InRvdHAiLCJvcmlnaW4iOiJzaWduX3VwIiwicGF0aCI6W10sImV4cCI6MTczMzMwOTI2NSwidGVuYW50X2lkIjoicHVibGljIn0.3B918rdY_XM0dRH4yx9O7Nujd3FbLdmKUmBY52KVKAo' \
--data-raw '{
    "name" : "tf2",
    "email" : "[email protected]",
    "password": "Pass@321"
}'

Response: 200 Ok, if tenant_admin got created successfully
A tenant can login with email and password and continue with 2FA, he will land into any one of organization existing for tenant.

Create Org Api, (works for tenant users only)

curl --location 'http://localhost:8080/user/create_org' \
--header 'x-tenant-id: test' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--header 'Cookie: Cookie' \
--data '{
    "organization_name": "ten",
    "merchant_name": "pp_ten"
}'

Response will be 200 OK if the org got created success fully (1 org with 1 merchant and 1 profile)

After getting the login token tenant admin can switch to any org in the tenancy.

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code
  • I added unit tests for my changes where possible

@apoorvdixit88 apoorvdixit88 added C-feature Category: Feature request or enhancement A-users Area: Users labels Dec 1, 2024
@apoorvdixit88 apoorvdixit88 self-assigned this Dec 1, 2024
@apoorvdixit88 apoorvdixit88 requested review from a team as code owners December 1, 2024 21:16
state: SessionState,
req: user_api::UserOrgCreateRequest,
) -> UserResponse<()> {
let db_organization = crate::types::transformers::ForeignFrom::foreign_from(req.clone());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean up import.

Comment on lines 2563 to 2595
let orgs = if matches!(role_info.get_entity_type(), EntityType::Tenant) {
let key_manager_state = &(&state).into();
state
.store
.list_all_merchant_accounts(key_manager_state, None, None)
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.map(|merchant_account| merchant_account.get_org_id().to_owned())
.collect::<HashSet<_>>()
} else {
state
.global_store
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
user_id: user_from_token.user_id.as_str(),
tenant_id: user_from_token
.tenant_id
.as_ref()
.unwrap_or(&state.tenant.tenant_id),
org_id: None,
merchant_id: None,
profile_id: None,
entity_id: None,
version: None,
status: Some(UserStatus::Active),
limit: None,
})
.await
.change_context(UserErrors::InternalServerError)?
.into_iter()
.filter_map(|user_role| user_role.org_id)
.collect::<HashSet<_>>()
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: Use match instead.

.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get merchant list for org")?
.first()
.ok_or(UserErrors::InternalServerError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be InternalServerError.

.await
.change_context(UserErrors::InternalServerError)
.attach_printable("Failed to get merchant list for org")?
.first()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use pop().


let token = utils::user::generate_jwt_auth_token_with_attributes(
&state,
user_from_token.user_id,
merchant_id.clone(),
request.org_id.clone(),
user_role.role_id.clone(),
user_from_token.role_id.clone(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should come from user_role.

Comment on lines +874 to +879
EntityType::Tenant => {
return Err(UserErrors::InvalidRoleOperationWithMessage(
"Tenant roles are not allowed for this operation".to_string(),
)
.into());
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we throw error here? Is dashboard handling this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The query above it will fetch 0 user_roles for entity type tenant where status is invitation_sent, so we should throw error here instead of sending empty vector, if we have such user role.

conn: &PgPooledConn,
limit: Option<u32>,
offset: Option<u32>,
) -> StorageResult<Vec<Self>> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of Self, can we get only merchant_id and org_id, which we don't have to decrypt?

Comment on lines 91 to 93
is_invitable: true,
is_deletable: true,
is_updatable: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be false.

Comment on lines +1675 to +1692
impl ForeignFrom<api_models::user::UserOrgCreateRequest>
for diesel_models::organization::OrganizationNew
{
fn foreign_from(item: api_models::user::UserOrgCreateRequest) -> Self {
let org_new = api_models::organization::OrganizationNew::new(None);
let api_models::user::UserOrgCreateRequest {
organization_name,
organization_details,
metadata,
..
} = item;
let mut org_new_db = Self::new(org_new.org_id, Some(organization_name));
org_new_db.organization_details = organization_details;
org_new_db.metadata = metadata;
org_new_db
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if we should we keep it here?

Comment on lines 129 to 174
let key_manager_state = &(&*state).into();

let merchant_account = state
.store
.list_all_merchant_accounts(key_manager_state, Some(1), None)
.await
.change_context(UserErrors::InternalServerError)?
.pop()
.ok_or(UserErrors::InternalServerError)?;

let merchant_id = merchant_account.get_id().to_owned();
let org_id = merchant_account.get_org_id().to_owned();

let key_store = state
.store
.get_merchant_key_store_by_merchant_id(
&state.into(),
&merchant_id,
&state.store.get_master_key().to_vec().into(),
)
.await
.change_context(UserErrors::InternalServerError)?;

let profile_id = state
.store
.list_profile_by_merchant_id(&state.into(), &key_store, &merchant_id)
.await
.change_context(UserErrors::InternalServerError)?
.pop()
.ok_or(UserErrors::InternalServerError)?
.get_id()
.to_owned();
(org_id, merchant_id, profile_id)
}
_ => {
let org_id = user_role
.org_id
.clone()
.ok_or(report!(UserErrors::InternalServerError))
.attach_printable("org_id not found")?;

let (merchant_id, profile_id) =
utils::user_role::get_single_merchant_id_and_profile_id(state, user_role)
.await?;
(org_id, merchant_id, profile_id)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to use util function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-users Area: Users C-feature Category: Feature request or enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat(users): support tenant level users
2 participants