mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2025-09-10 18:22:49 +02:00
support OIDC private clients
This commit is contained in:
parent
14f6d07b27
commit
511e60b41d
7 changed files with 46 additions and 11 deletions
|
@ -2,7 +2,7 @@ use axum::{Json, extract::State};
|
||||||
use conduwuit::{Result, err};
|
use conduwuit::{Result, err};
|
||||||
use oxide_auth::primitives::prelude::Client;
|
use oxide_auth::primitives::prelude::Client;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use ruma::DeviceId;
|
use ruma::{DeviceId, identifiers_validation};
|
||||||
|
|
||||||
/// The required parameters to register a new client for OAuth2 application.
|
/// The required parameters to register a new client for OAuth2 application.
|
||||||
#[derive(serde::Deserialize, Clone, Debug)]
|
#[derive(serde::Deserialize, Clone, Debug)]
|
||||||
|
@ -35,6 +35,8 @@ pub(crate) struct ClientQuery {
|
||||||
#[derive(serde::Serialize, Debug)]
|
#[derive(serde::Serialize, Debug)]
|
||||||
pub(crate) struct ClientResponse {
|
pub(crate) struct ClientResponse {
|
||||||
client_id: String,
|
client_id: String,
|
||||||
|
client_secret: Option<String>,
|
||||||
|
client_secret_expires_at: Option<u32>,
|
||||||
client_name: String,
|
client_name: String,
|
||||||
client_uri: Url,
|
client_uri: Url,
|
||||||
logo_uri: Option<Url>,
|
logo_uri: Option<Url>,
|
||||||
|
@ -68,18 +70,38 @@ pub(crate) async fn register_client(
|
||||||
let scope = format!(
|
let scope = format!(
|
||||||
"urn:matrix:org.matrix.msc2967.client:api:* \
|
"urn:matrix:org.matrix.msc2967.client:api:* \
|
||||||
urn:matrix:org.matrix.msc2967.client:device:{device_id}"
|
urn:matrix:org.matrix.msc2967.client:device:{device_id}"
|
||||||
);
|
).parse().expect("parseable default Matrix scope");
|
||||||
// TODO check if the users service needs an update.
|
// TODO check if the users service needs an update.
|
||||||
//services.users.update_device_metadata();
|
//services.users.update_device_metadata();
|
||||||
services.oidc.register_client(&Client::public(
|
|
||||||
device_id.as_ref(),
|
// If the client cannot authenticate itself at the token endpoint, then
|
||||||
redirect_uri.into(),
|
// it's a public client.
|
||||||
scope
|
let is_private = client.token_endpoint_auth_method != "none";
|
||||||
.parse()
|
// TODO generate a device secret.
|
||||||
.expect("device ID should parse in Matrix scope"),
|
let secret = "cacestdubonsecretmonlouou=--".to_string();
|
||||||
))?;
|
if let Err(err) = identifiers_validation::client_secret::validate(&secret) {
|
||||||
|
tracing::warn!("oops, we generated an invalid client_secret: {err}");
|
||||||
|
}
|
||||||
|
let registerable = match is_private {
|
||||||
|
| true => &Client::confidential(
|
||||||
|
device_id.as_ref(),
|
||||||
|
redirect_uri,
|
||||||
|
scope,
|
||||||
|
secret.as_bytes(),
|
||||||
|
).with_additional_redirect_uris(remaining_uris),
|
||||||
|
| _ => &Client::public(
|
||||||
|
device_id.as_ref(),
|
||||||
|
redirect_uri,
|
||||||
|
scope,
|
||||||
|
).with_additional_redirect_uris(remaining_uris)
|
||||||
|
};
|
||||||
|
tracing::trace!("registering OIDC device : {registerable:#?}");
|
||||||
|
services.oidc.register_client(®isterable)?;
|
||||||
|
|
||||||
let client_response = ClientResponse {
|
let client_response = ClientResponse {
|
||||||
client_id: device_id.to_string(),
|
client_id: device_id.to_string(),
|
||||||
|
client_secret: if is_private { Some(secret) } else { None },
|
||||||
|
client_secret_expires_at: if is_private { Some(0) } else { None },
|
||||||
client_name: client.client_name.clone(),
|
client_name: client.client_name.clone(),
|
||||||
client_uri: client.client_uri.clone(),
|
client_uri: client.client_uri.clone(),
|
||||||
redirect_uris: client.redirect_uris.clone(),
|
redirect_uris: client.redirect_uris.clone(),
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub(crate) struct LoginPageTemplate<'a> {
|
||||||
hostname: &'a str,
|
hostname: &'a str,
|
||||||
route: &'a str,
|
route: &'a str,
|
||||||
client_id: &'a str,
|
client_id: &'a str,
|
||||||
|
client_secret: Option<&'a str>,
|
||||||
redirect_uri: &'a str,
|
redirect_uri: &'a str,
|
||||||
scope: &'a str,
|
scope: &'a str,
|
||||||
state: &'a str,
|
state: &'a str,
|
||||||
|
@ -43,6 +44,7 @@ pub(crate) struct ConsentPageTemplate<'a> {
|
||||||
hostname: &'a str,
|
hostname: &'a str,
|
||||||
route: &'a str,
|
route: &'a str,
|
||||||
client_id: &'a str,
|
client_id: &'a str,
|
||||||
|
client_secret: Option<&'a str>,
|
||||||
redirect_uri: &'a str,
|
redirect_uri: &'a str,
|
||||||
scope: &'a str,
|
scope: &'a str,
|
||||||
state: &'a str,
|
state: &'a str,
|
||||||
|
|
|
@ -6,6 +6,7 @@ use super::LoginQuery;
|
||||||
#[derive(serde::Deserialize, Debug)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
pub struct AuthorizationQuery {
|
pub struct AuthorizationQuery {
|
||||||
pub client_id: String,
|
pub client_id: String,
|
||||||
|
pub client_secret: Option<String>,
|
||||||
pub redirect_uri: Url,
|
pub redirect_uri: Url,
|
||||||
pub scope: String,
|
pub scope: String,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
|
@ -19,6 +20,7 @@ impl From<LoginQuery> for AuthorizationQuery {
|
||||||
fn from(value: LoginQuery) -> Self {
|
fn from(value: LoginQuery) -> Self {
|
||||||
let LoginQuery {
|
let LoginQuery {
|
||||||
client_id,
|
client_id,
|
||||||
|
client_secret,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
scope,
|
scope,
|
||||||
state,
|
state,
|
||||||
|
@ -31,6 +33,7 @@ impl From<LoginQuery> for AuthorizationQuery {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
client_id,
|
client_id,
|
||||||
|
client_secret,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
scope,
|
scope,
|
||||||
state,
|
state,
|
||||||
|
|
|
@ -37,6 +37,7 @@ fn consent_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce:
|
||||||
hostname,
|
hostname,
|
||||||
route,
|
route,
|
||||||
client_id: &encode(query.client_id.as_str()),
|
client_id: &encode(query.client_id.as_str()),
|
||||||
|
client_secret: query.client_secret.as_deref(),
|
||||||
redirect_uri: &encode(query.redirect_uri.as_str()),
|
redirect_uri: &encode(query.redirect_uri.as_str()),
|
||||||
scope: &encode(query.scope.as_str()),
|
scope: &encode(query.scope.as_str()),
|
||||||
state: &encode(query.state.as_str()),
|
state: &encode(query.state.as_str()),
|
||||||
|
|
|
@ -13,6 +13,7 @@ pub struct LoginQuery {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub client_id: String,
|
pub client_id: String,
|
||||||
|
pub client_secret: Option<String>,
|
||||||
pub redirect_uri: Url,
|
pub redirect_uri: Url,
|
||||||
pub scope: String,
|
pub scope: String,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
|
@ -68,11 +69,13 @@ impl TryFrom<OidcRequest> for LoginQuery {
|
||||||
| "https" => Cow::Borrowed("fragment"),
|
| "https" => Cow::Borrowed("fragment"),
|
||||||
| _ => Cow::Borrowed("query")
|
| _ => Cow::Borrowed("query")
|
||||||
});
|
});
|
||||||
|
let client_secret = body.unique_value("client_secret").map(|s| s.to_string());
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
password: password.to_string(),
|
password: password.to_string(),
|
||||||
client_id: client_id.to_string(),
|
client_id: client_id.to_string(),
|
||||||
|
client_secret,
|
||||||
redirect_uri,
|
redirect_uri,
|
||||||
scope: scope.to_string(),
|
scope: scope.to_string(),
|
||||||
state: state.to_string(),
|
state: state.to_string(),
|
||||||
|
@ -116,6 +119,7 @@ fn login_page(hostname: &str, query: &AuthorizationQuery, route: &str, nonce: &s
|
||||||
hostname,
|
hostname,
|
||||||
route,
|
route,
|
||||||
client_id: query.client_id.as_str(),
|
client_id: query.client_id.as_str(),
|
||||||
|
client_secret: query.client_secret.as_deref(),
|
||||||
redirect_uri: query.redirect_uri.as_str(),
|
redirect_uri: query.redirect_uri.as_str(),
|
||||||
scope: query.scope.as_str(),
|
scope: query.scope.as_str(),
|
||||||
state: query.state.as_str(),
|
state: query.state.as_str(),
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
'{{ client_id }}' (at {{ redirect_uri }}) is requesting permission for '{{ scope }}'
|
'{{ client_id }}' (at {{ redirect_uri }}) is requesting permission for '{{ scope }}'
|
||||||
</p>
|
</p>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<input type="submit" value="Accept" formaction="{{ route }}?client_id={{ client_id }}&redirect_uri={{ redirect_uri }}&scope={{scope }}&state={{ state }}&code_challenge={{ code_challenge }}&code_challenge_method={{ code_challenge_method }}&response_type={{ response_type }}&response_mode={{ response_mode }}&allow=true">
|
<input type="submit" value="Accept" formaction="{{ route }}?client_id={{ client_id }}{%- if let Some(secret) = client_secret -%}&client_secret={{ secret }}{%- endif -%}&redirect_uri={{ redirect_uri }}&scope={{ scope }}&state={{ state }}&code_challenge={{ code_challenge }}&code_challenge_method={{ code_challenge_method }}&response_type={{ response_type }}&response_mode={{ response_mode }}&allow=true">
|
||||||
<input type="submit" value="Deny" formaction="{{ route }}?client_id={{ client_id }}&redirect_uri={{ redirect_uri }}&scope={{scope }}&state={{ state }}&code_challenge={{ code_challenge }}&code_challenge_method={{ code_challenge_method }}&response_type={{ response_type }}&response_mode={{ response_mode }}&deny=true">
|
<input type="submit" value="Deny" formaction="{{ route }}?client_id={{ client_id }}{%- if let Some(secret) = client_secret -%}&client_secret={{ secret }}{%- endif -%}&redirect_uri={{ redirect_uri }}&scope={{scope }}&state={{ state }}&code_challenge={{ code_challenge }}&code_challenge_method={{ code_challenge_method }}&response_type={{ response_type }}&response_mode={{ response_mode }}&deny=true">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{%- endblock content -%}
|
{%- endblock content -%}
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
<input type="text" name="username" placeholder="Username" required>
|
<input type="text" name="username" placeholder="Username" required>
|
||||||
<input type="password" name="password" placeholder="Password" required>
|
<input type="password" name="password" placeholder="Password" required>
|
||||||
<input type="hidden" name="client_id" value="{{ client_id }}">
|
<input type="hidden" name="client_id" value="{{ client_id }}">
|
||||||
|
{%- if let Some(secret) = client_secret -%}
|
||||||
|
<input type="hidden" name="client_secret" value="{{ secret }}">
|
||||||
|
{%- endif -%}
|
||||||
<input type="hidden" name="redirect_uri" value="{{ redirect_uri }}">
|
<input type="hidden" name="redirect_uri" value="{{ redirect_uri }}">
|
||||||
<input type="hidden" name="scope" value="{{ scope }}">
|
<input type="hidden" name="scope" value="{{ scope }}">
|
||||||
<input type="hidden" name="state" value="{{ state }}">
|
<input type="hidden" name="state" value="{{ state }}">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue