feat: sso saml admin frontend
This commit is contained in:
parent
c35e121ccf
commit
8984a2421c
|
|
@ -28,3 +28,10 @@ pub struct WebApiAdminCreateUserRequest {
|
||||||
pub struct WebApiInviteUserRequest {
|
pub struct WebApiInviteUserRequest {
|
||||||
pub email: String,
|
pub email: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct WebApiCreateSSOProviderRequest {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub type_: String,
|
||||||
|
pub metadata_url: String,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,21 @@
|
||||||
use askama::Template;
|
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)]
|
#[derive(Template)]
|
||||||
#[template(path = "components/admin_sso_list.html")]
|
#[template(path = "components/admin_sso_list.html")]
|
||||||
pub struct SsoList {
|
pub struct SsoList {
|
||||||
// TODO
|
pub sso_providers: Vec<SSOProvider>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::error::WebApiError;
|
use crate::error::WebApiError;
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
WebApiAdminCreateUserRequest, WebApiChangePasswordRequest, WebApiInviteUserRequest,
|
WebApiAdminCreateUserRequest, WebApiChangePasswordRequest, WebApiCreateSSOProviderRequest,
|
||||||
WebApiPutUserRequest,
|
WebApiInviteUserRequest, WebApiPutUserRequest,
|
||||||
};
|
};
|
||||||
use crate::response::WebApiResponse;
|
use crate::response::WebApiResponse;
|
||||||
use crate::session::{self, UserSession};
|
use crate::session::{self, UserSession};
|
||||||
|
|
@ -14,7 +14,10 @@ use axum::Form;
|
||||||
use axum::{extract::State, routing::post, Router};
|
use axum::{extract::State, routing::post, Router};
|
||||||
use axum_extra::extract::cookie::Cookie;
|
use axum_extra::extract::cookie::Cookie;
|
||||||
use axum_extra::extract::CookieJar;
|
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::dto::{GotrueTokenResponse, SignUpResponse, UpdateGotrueUserParams, User};
|
||||||
use gotrue_entity::error::GoTrueError;
|
use gotrue_entity::error::GoTrueError;
|
||||||
|
|
||||||
|
|
@ -40,6 +43,40 @@ pub fn router() -> Router<AppState> {
|
||||||
"/admin/user/:email/generate-link",
|
"/admin/user/:email/generate-link",
|
||||||
post(post_user_generate_link_handler),
|
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
|
// provide a link which when open in browser, opens the appflowy app
|
||||||
|
|
|
||||||
|
|
@ -37,26 +37,61 @@ pub fn component_router() -> Router<AppState> {
|
||||||
.route("/admin/users/create", get(admin_users_create_handler))
|
.route("/admin/users/create", get(admin_users_create_handler))
|
||||||
// SSO
|
// SSO
|
||||||
.route("/admin/sso", get(admin_sso_handler))
|
.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> {
|
pub async fn admin_sso_detail_handler(
|
||||||
render_template(templates::SsoList {})
|
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> {
|
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> {
|
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> {
|
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> {
|
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(
|
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> {
|
pub async fn user_change_password_handler() -> Result<Html<String>, WebAppError> {
|
||||||
render_template(templates::ChangePassword {})
|
render_template(templates::ChangePassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn home_handler(
|
pub async fn home_handler(
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -1,3 +1,35 @@
|
||||||
<div>
|
<div id="sso-list">
|
||||||
<!--> TODO <-->
|
<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>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct SSOProviders {
|
pub struct SSOProviders {
|
||||||
pub items: Vec<SSOProvider>,
|
pub items: Option<Vec<SSOProvider>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
@ -24,12 +24,12 @@ pub struct SAMLProvider {
|
||||||
pub attribute_mapping: SAMLAttributeMapping,
|
pub attribute_mapping: SAMLAttributeMapping,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct SAMLAttributeMapping {
|
pub struct SAMLAttributeMapping {
|
||||||
pub keys: Option<BTreeMap<String, SAMLAttribute>>,
|
pub keys: Option<BTreeMap<String, SAMLAttribute>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct SAMLAttribute {
|
pub struct SAMLAttribute {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub names: Option<Vec<String>>,
|
pub names: Option<Vec<String>>,
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,7 @@ pub struct GenerateLinkResponse {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Default)]
|
#[derive(Debug, Serialize, Default)]
|
||||||
pub struct CreateSSOProviderParams {
|
pub struct CreateSSOProviderParams {
|
||||||
|
#[serde(rename = "type")]
|
||||||
pub type_: String,
|
pub type_: String,
|
||||||
pub metadata_url: String,
|
pub metadata_url: String,
|
||||||
pub metadata_xml: String,
|
pub metadata_xml: String,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue