feat: sso saml admin frontend

This commit is contained in:
Fu Zi Xiang 2023-11-28 00:39:36 +08:00
parent c35e121ccf
commit 8984a2421c
No known key found for this signature in database
10 changed files with 214 additions and 18 deletions

View File

@ -28,3 +28,10 @@ pub struct WebApiAdminCreateUserRequest {
pub struct WebApiInviteUserRequest {
pub email: String,
}
#[derive(Deserialize)]
pub struct WebApiCreateSSOProviderRequest {
#[serde(rename = "type")]
pub type_: String,
pub metadata_url: String,
}

View File

@ -1,10 +1,21 @@
use askama::Template;
use gotrue_entity::dto::User;
use gotrue_entity::{dto::User, sso::SSOProvider};
#[derive(Template)]
#[template(path = "components/admin_sso_detail.html")]
pub struct SsoDetail {
pub sso_provider: SSOProvider,
pub mapping_json: String,
}
#[derive(Template)]
#[template(path = "components/admin_sso_create.html")]
pub struct SsoCreate;
#[derive(Template)]
#[template(path = "components/admin_sso_list.html")]
pub struct SsoList {
// TODO
pub sso_providers: Vec<SSOProvider>,
}
#[derive(Template)]

View File

@ -1,7 +1,7 @@
use crate::error::WebApiError;
use crate::models::{
WebApiAdminCreateUserRequest, WebApiChangePasswordRequest, WebApiInviteUserRequest,
WebApiPutUserRequest,
WebApiAdminCreateUserRequest, WebApiChangePasswordRequest, WebApiCreateSSOProviderRequest,
WebApiInviteUserRequest, WebApiPutUserRequest,
};
use crate::response::WebApiResponse;
use crate::session::{self, UserSession};
@ -14,7 +14,10 @@ use axum::Form;
use axum::{extract::State, routing::post, Router};
use axum_extra::extract::cookie::Cookie;
use axum_extra::extract::CookieJar;
use gotrue::params::{AdminDeleteUserParams, AdminUserParams, GenerateLinkParams, MagicLinkParams};
use gotrue::params::{
AdminDeleteUserParams, AdminUserParams, CreateSSOProviderParams, GenerateLinkParams,
MagicLinkParams,
};
use gotrue_entity::dto::{GotrueTokenResponse, SignUpResponse, UpdateGotrueUserParams, User};
use gotrue_entity::error::GoTrueError;
@ -40,6 +43,40 @@ pub fn router() -> Router<AppState> {
"/admin/user/:email/generate-link",
post(post_user_generate_link_handler),
)
.route("/admin/sso", post(admin_create_sso_handler))
.route("/admin/sso/:provider_id", delete(admin_delete_sso_handler))
}
pub async fn admin_delete_sso_handler(
State(state): State<AppState>,
session: UserSession,
Path(provider_id): Path<String>,
) -> Result<WebApiResponse<()>, WebApiError<'static>> {
let _ = state
.gotrue_client
.admin_delete_sso_provider(&session.token.access_token, &provider_id)
.await?;
Ok(WebApiResponse::<()>::from_str("SSO Deleted".into()))
}
pub async fn admin_create_sso_handler(
State(state): State<AppState>,
session: UserSession,
Form(param): Form<WebApiCreateSSOProviderRequest>,
) -> Result<WebApiResponse<()>, WebApiError<'static>> {
let provider_params = CreateSSOProviderParams {
type_: param.type_,
metadata_url: param.metadata_url,
..Default::default()
};
let _ = state
.gotrue_client
.admin_create_sso_providers(&session.token.access_token, &provider_params)
.await?;
Ok(WebApiResponse::<()>::from_str("SSO Added".into()))
}
// provide a link which when open in browser, opens the appflowy app

View File

@ -37,26 +37,61 @@ pub fn component_router() -> Router<AppState> {
.route("/admin/users/create", get(admin_users_create_handler))
// SSO
.route("/admin/sso", get(admin_sso_handler))
.route("/admin/sso/create", get(admin_sso_create_handler))
.route("/admin/sso/:sso_provider_id", get(admin_sso_detail_handler))
}
pub async fn admin_sso_handler() -> Result<Html<String>, WebAppError> {
render_template(templates::SsoList {})
pub async fn admin_sso_detail_handler(
State(state): State<AppState>,
session: UserSession,
Path(sso_provider_id): Path<String>,
) -> Result<Html<String>, WebAppError> {
let sso_provider = state
.gotrue_client
.admin_get_sso_provider(&session.token.access_token, &sso_provider_id)
.await?;
let mapping_json =
serde_json::to_string_pretty(&sso_provider.saml.attribute_mapping).unwrap_or("".to_owned());
render_template(templates::SsoDetail {
sso_provider,
mapping_json,
})
}
pub async fn admin_sso_create_handler() -> Result<Html<String>, WebAppError> {
render_template(templates::SsoCreate)
}
pub async fn admin_sso_handler(
State(state): State<AppState>,
session: UserSession,
) -> Result<Html<String>, WebAppError> {
let sso_providers = state
.gotrue_client
.admin_list_sso_providers(&session.token.access_token)
.await?
.items
.unwrap_or_default();
render_template(templates::SsoList { sso_providers })
}
pub async fn user_navigate_handler() -> Result<Html<String>, WebAppError> {
render_template(templates::Navigate {})
render_template(templates::Navigate)
}
pub async fn admin_navigate_handler() -> Result<Html<String>, WebAppError> {
render_template(templates::AdminNavigate {})
render_template(templates::AdminNavigate)
}
pub async fn user_invite_handler() -> Result<Html<String>, WebAppError> {
render_template(templates::Invite {})
render_template(templates::Invite)
}
pub async fn admin_users_create_handler() -> Result<Html<String>, WebAppError> {
render_template(templates::CreateUser {})
render_template(templates::CreateUser)
}
pub async fn user_user_handler(
@ -77,7 +112,7 @@ pub async fn login_handler(State(state): State<AppState>) -> Result<Html<String>
}
pub async fn user_change_password_handler() -> Result<Html<String>, WebAppError> {
render_template(templates::ChangePassword {})
render_template(templates::ChangePassword)
}
pub async fn home_handler(

View File

@ -0,0 +1,31 @@
<div>
<h4>Please enter the following information to create new SSO</h4>
<form hx-post="/web-api/admin/sso" hx-target="#none">
<table>
<tr>
<td>Email</td>
<td>
<select name="type" class="input">
<option value="saml">saml</option>
</select>
</td>
</tr>
<tr>
<td>Metadata Url</td>
<td>
<input
class="input"
name="metadata_url"
placeholder="https://example.com/metadata"
/>
</td>
</tr>
<tr>
<td></td>
<td style="text-align: right">
<button class="button cyan" type="submit">Create</button>
</td>
</tr>
</table>
</form>
</div>

View File

@ -0,0 +1,42 @@
<div>
<table>
<tr>
<td style="white-space: nowrap">ID</td>
<td>{{ sso_provider.id|escape }}</td>
</tr>
<tr>
<td style="white-space: nowrap">Entity ID</td>
<td>{{ sso_provider.saml.entity_id|escape }}</td>
</tr>
<tr>
<td style="white-space: nowrap">Domains</td>
<td>
<ul>
{% for domain in sso_provider.domains %}
<li>{{ domain|escape }}</li>
{% endfor %}
</ul>
</td>
</tr>
<tr>
<td style="white-space: nowrap">Created At</td>
<td>{{ sso_provider.created_at|escape }}</td>
</tr>
<tr>
<td style="white-space: nowrap">Updated At</td>
<td>{{ sso_provider.updated_at|escape }}</td>
</tr>
<tr>
<td style="white-space: nowrap; align">Metadata XML</td>
<td>
<code> {{ sso_provider.saml.metadata_xml|default("")|escape }} </code>
</td>
</tr>
<tr>
<td style="white-space: nowrap">Attribute Mapping</td>
<td>
<code> {{ mapping_json|escape }} </code>
</td>
</tr>
</table>
</div>

View File

@ -1,3 +1,35 @@
<div>
<!--> TODO <-->
<div id="sso-list">
<table>
<tr>
<th>Entity ID</th>
<th>Created At</th>
<th>Actions</th>
</tr>
{% for sso_provider in sso_providers %}
<tr>
<td>{{ sso_provider.saml.entity_id|escape }}</td>
<td>{{ sso_provider.created_at|escape }}</td>
<td>
<button
class="button cyan"
hx-target="#sso-list"
hx-get="/web/components/admin/sso/{{ sso_provider.id }}"
>
More Info
</button>
<button
class="deletUserBtn button red"
hx-delete="/web-api/admin/sso/{{ sso_provider.id }}"
hx-confirm="Are you sure?"
hx-target="closest tr"
hx-swap="delete"
>
Delete
</button>
</td>
</tr>
{% endfor %}
</table>
</div>

View File

@ -1,10 +1,10 @@
use std::collections::BTreeMap;
use serde::Deserialize;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct SSOProviders {
pub items: Vec<SSOProvider>,
pub items: Option<Vec<SSOProvider>>,
}
#[derive(Debug, Deserialize)]
@ -24,12 +24,12 @@ pub struct SAMLProvider {
pub attribute_mapping: SAMLAttributeMapping,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
pub struct SAMLAttributeMapping {
pub keys: Option<BTreeMap<String, SAMLAttribute>>,
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
pub struct SAMLAttribute {
pub name: Option<String>,
pub names: Option<Vec<String>>,

View File

@ -108,6 +108,7 @@ pub struct GenerateLinkResponse {
#[derive(Debug, Serialize, Default)]
pub struct CreateSSOProviderParams {
#[serde(rename = "type")]
pub type_: String,
pub metadata_url: String,
pub metadata_xml: String,

0
tem Normal file
View File