mirror of https://github.com/LemmyNet/lemmy.git
Merge 45c0a0030a
into 78702b59fd
commit
ce191709d3
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
|
@ -44,7 +44,7 @@ console = [
|
||||||
"opentelemetry",
|
"opentelemetry",
|
||||||
"opentelemetry-otlp",
|
"opentelemetry-otlp",
|
||||||
"tracing-opentelemetry",
|
"tracing-opentelemetry",
|
||||||
"reqwest-tracing/opentelemetry_0_16",
|
"reqwest-tracing/opentelemetry_0_19",
|
||||||
]
|
]
|
||||||
json-log = ["tracing-subscriber/json"]
|
json-log = ["tracing-subscriber/json"]
|
||||||
default = []
|
default = []
|
||||||
|
@ -121,9 +121,14 @@ tracing-error = "0.2.0"
|
||||||
tracing-log = "0.2.0"
|
tracing-log = "0.2.0"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
url = { version = "2.5.0", features = ["serde"] }
|
url = { version = "2.5.0", features = ["serde"] }
|
||||||
reqwest = { version = "0.11.27", features = ["json", "blocking", "gzip"] }
|
reqwest = { version = "0.11.27", features = [
|
||||||
|
"json",
|
||||||
|
"blocking",
|
||||||
|
"gzip",
|
||||||
|
"rustls-tls-native-roots",
|
||||||
|
] }
|
||||||
reqwest-middleware = "0.2.5"
|
reqwest-middleware = "0.2.5"
|
||||||
reqwest-tracing = "0.4.8"
|
reqwest-tracing = { version = "0.4.8", features = ["opentelemetry_0_19"] }
|
||||||
clokwerk = "0.4.0"
|
clokwerk = "0.4.0"
|
||||||
doku = { version = "0.21.1", features = ["url-2"] }
|
doku = { version = "0.21.1", features = ["url-2"] }
|
||||||
bcrypt = "0.15.1"
|
bcrypt = "0.15.1"
|
||||||
|
@ -168,6 +173,8 @@ i-love-jesus = { version = "0.1.0" }
|
||||||
clap = { version = "4.5.6", features = ["derive", "env"] }
|
clap = { version = "4.5.6", features = ["derive", "env"] }
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
derive-new = "0.6.0"
|
derive-new = "0.6.0"
|
||||||
|
sha3 = "0.10.8"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lemmy_api = { workspace = true }
|
lemmy_api = { workspace = true }
|
||||||
|
|
|
@ -102,6 +102,9 @@
|
||||||
}
|
}
|
||||||
# the domain name of your instance (mandatory)
|
# the domain name of your instance (mandatory)
|
||||||
hostname: "unset"
|
hostname: "unset"
|
||||||
|
# the domain name of your lemmy-ui instance used for OAUTH2 (defaults to the backend instance
|
||||||
|
# hostname)
|
||||||
|
hostname_ui: "example.com"
|
||||||
# Address where lemmy should listen for incoming requests
|
# Address where lemmy should listen for incoming requests
|
||||||
bind: "0.0.0.0"
|
bind: "0.0.0.0"
|
||||||
# Port where lemmy should listen for incoming requests
|
# Port where lemmy should listen for incoming requests
|
||||||
|
|
|
@ -28,11 +28,13 @@ pub async fn change_password(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the old password
|
// Check the old password
|
||||||
let valid: bool = verify(
|
let valid: bool = if let Some(password_encrypted) = &local_user_view.local_user.password_encrypted
|
||||||
&data.old_password,
|
{
|
||||||
&local_user_view.local_user.password_encrypted,
|
verify(&data.old_password, password_encrypted).unwrap_or(false)
|
||||||
)
|
} else {
|
||||||
.unwrap_or(false);
|
data.old_password.is_empty()
|
||||||
|
};
|
||||||
|
|
||||||
if !valid {
|
if !valid {
|
||||||
Err(LemmyErrorType::IncorrectLogin)?
|
Err(LemmyErrorType::IncorrectLogin)?
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{check_totp_2fa_valid, local_user::check_email_verified};
|
use crate::check_totp_2fa_valid;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
web::{Data, Json},
|
web::{Data, Json},
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
|
@ -8,12 +8,7 @@ use lemmy_api_common::{
|
||||||
claims::Claims,
|
claims::Claims,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::{Login, LoginResponse},
|
person::{Login, LoginResponse},
|
||||||
utils::check_user_valid,
|
utils::{check_email_verified, check_registration_application, check_user_valid},
|
||||||
};
|
|
||||||
use lemmy_db_schema::{
|
|
||||||
source::{local_site::LocalSite, registration_application::RegistrationApplication},
|
|
||||||
utils::DbPool,
|
|
||||||
RegistrationMode,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
|
||||||
|
@ -36,11 +31,13 @@ pub async fn login(
|
||||||
.ok_or(LemmyErrorType::IncorrectLogin)?;
|
.ok_or(LemmyErrorType::IncorrectLogin)?;
|
||||||
|
|
||||||
// Verify the password
|
// Verify the password
|
||||||
let valid: bool = verify(
|
let valid: bool = if let Some(password_encrypted) = &local_user_view.local_user.password_encrypted
|
||||||
&data.password,
|
{
|
||||||
&local_user_view.local_user.password_encrypted,
|
verify(&data.password, password_encrypted).unwrap_or(false)
|
||||||
)
|
} else {
|
||||||
.unwrap_or(false);
|
false
|
||||||
|
};
|
||||||
|
|
||||||
if !valid {
|
if !valid {
|
||||||
Err(LemmyErrorType::IncorrectLogin)?
|
Err(LemmyErrorType::IncorrectLogin)?
|
||||||
}
|
}
|
||||||
|
@ -67,28 +64,3 @@ pub async fn login(
|
||||||
registration_created: false,
|
registration_created: false,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_registration_application(
|
|
||||||
local_user_view: &LocalUserView,
|
|
||||||
local_site: &LocalSite,
|
|
||||||
pool: &mut DbPool<'_>,
|
|
||||||
) -> LemmyResult<()> {
|
|
||||||
if (local_site.registration_mode == RegistrationMode::RequireApplication
|
|
||||||
|| local_site.registration_mode == RegistrationMode::Closed)
|
|
||||||
&& !local_user_view.local_user.accepted_application
|
|
||||||
&& !local_user_view.local_user.admin
|
|
||||||
{
|
|
||||||
// Fetch the registration application. If no admin id is present its still pending. Otherwise it
|
|
||||||
// was processed (either accepted or denied).
|
|
||||||
let local_user_id = local_user_view.local_user.id;
|
|
||||||
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id)
|
|
||||||
.await?
|
|
||||||
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
|
|
||||||
if registration.admin_id.is_some() {
|
|
||||||
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
|
|
||||||
} else {
|
|
||||||
Err(LemmyErrorType::RegistrationApplicationIsPending)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
|
||||||
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
|
|
||||||
|
|
||||||
pub mod add_admin;
|
pub mod add_admin;
|
||||||
pub mod ban_person;
|
pub mod ban_person;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
|
@ -20,15 +17,3 @@ pub mod save_settings;
|
||||||
pub mod update_totp;
|
pub mod update_totp;
|
||||||
pub mod validate_auth;
|
pub mod validate_auth;
|
||||||
pub mod verify_email;
|
pub mod verify_email;
|
||||||
|
|
||||||
/// Check if the user's email is verified if email verification is turned on
|
|
||||||
/// However, skip checking verification if the user is an admin
|
|
||||||
fn check_email_verified(local_user_view: &LocalUserView, site_view: &SiteView) -> LemmyResult<()> {
|
|
||||||
if !local_user_view.local_user.admin
|
|
||||||
&& site_view.local_site.require_email_verification
|
|
||||||
&& !local_user_view.local_user.email_verified
|
|
||||||
{
|
|
||||||
Err(LemmyErrorType::EmailNotVerified)?
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use crate::local_user::check_email_verified;
|
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
person::PasswordReset,
|
person::PasswordReset,
|
||||||
utils::send_password_reset_email,
|
utils::{check_email_verified, send_password_reset_email},
|
||||||
SuccessResponse,
|
SuccessResponse,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
|
|
|
@ -7,6 +7,7 @@ use lemmy_db_schema::{
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
local_user::{LocalUser, LocalUserUpdateForm},
|
local_user::{LocalUser, LocalUserUpdateForm},
|
||||||
moderator::{ModAdd, ModAddForm},
|
moderator::{ModAdd, ModAddForm},
|
||||||
|
oauth_provider::OAuthProvider,
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
|
@ -65,6 +66,7 @@ pub async fn leave_admin(
|
||||||
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
||||||
let custom_emojis =
|
let custom_emojis =
|
||||||
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
||||||
|
let oauth_providers = OAuthProvider::get_all(&mut context.pool()).await?;
|
||||||
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
||||||
|
|
||||||
Ok(Json(GetSiteResponse {
|
Ok(Json(GetSiteResponse {
|
||||||
|
@ -76,6 +78,7 @@ pub async fn leave_admin(
|
||||||
discussion_languages,
|
discussion_languages,
|
||||||
taglines,
|
taglines,
|
||||||
custom_emojis,
|
custom_emojis,
|
||||||
|
oauth_providers,
|
||||||
blocked_urls,
|
blocked_urls,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ mod tests {
|
||||||
|
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_person.id)
|
.person_id(inserted_person.id)
|
||||||
.password_encrypted("123456".to_string())
|
.password_encrypted(Some("123456".to_string()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
|
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub mod community;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
|
pub mod oauth_provider;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
use lemmy_db_schema::newtypes::OAuthProviderId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use ts_rs::TS;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Create an external auth method.
|
||||||
|
pub struct CreateOAuthProvider {
|
||||||
|
pub display_name: String,
|
||||||
|
pub issuer: String,
|
||||||
|
pub authorization_endpoint: String,
|
||||||
|
pub token_endpoint: String,
|
||||||
|
pub userinfo_endpoint: String,
|
||||||
|
pub id_claim: String,
|
||||||
|
pub name_claim: String,
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_secret: String,
|
||||||
|
pub scopes: String,
|
||||||
|
pub auto_verify_email: bool,
|
||||||
|
pub auto_approve_application: bool,
|
||||||
|
pub account_linking_enabled: bool,
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Edit an external auth method.
|
||||||
|
pub struct EditOAuthProvider {
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
pub display_name: String,
|
||||||
|
pub authorization_endpoint: String,
|
||||||
|
pub token_endpoint: String,
|
||||||
|
pub userinfo_endpoint: String,
|
||||||
|
pub id_claim: String,
|
||||||
|
pub name_claim: String,
|
||||||
|
pub client_secret: String,
|
||||||
|
pub scopes: String,
|
||||||
|
pub auto_verify_email: bool,
|
||||||
|
pub auto_approve_application: bool,
|
||||||
|
pub account_linking_enabled: bool,
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Delete an external auth method.
|
||||||
|
pub struct DeleteOAuthProvider {
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Logging in with an OAuth 2.0 authorization
|
||||||
|
pub struct AuthenticateWithOauth {
|
||||||
|
pub code: String,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub oauth_provider_id: OAuthProviderId,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub redirect_uri: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// Response from OAuth token endpoint
|
||||||
|
pub struct TokenResponse {
|
||||||
|
pub access_token: String,
|
||||||
|
pub token_type: String,
|
||||||
|
pub expires_in: Option<i64>,
|
||||||
|
pub refresh_token: Option<String>,
|
||||||
|
pub scope: Option<String>,
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ pub fn client_builder(settings: &Settings) -> ClientBuilder {
|
||||||
.user_agent(user_agent.clone())
|
.user_agent(user_agent.clone())
|
||||||
.timeout(REQWEST_TIMEOUT)
|
.timeout(REQWEST_TIMEOUT)
|
||||||
.connect_timeout(REQWEST_TIMEOUT)
|
.connect_timeout(REQWEST_TIMEOUT)
|
||||||
|
.use_rustls_tls()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches metadata for the given link and optionally generates thumbnail.
|
/// Fetches metadata for the given link and optionally generates thumbnail.
|
||||||
|
|
|
@ -7,6 +7,7 @@ use lemmy_db_schema::{
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
language::Language,
|
language::Language,
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
|
oauth_provider::OAuthProvider,
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
},
|
},
|
||||||
ListingType,
|
ListingType,
|
||||||
|
@ -192,6 +193,7 @@ pub struct CreateSite {
|
||||||
pub blocked_instances: Option<Vec<String>>,
|
pub blocked_instances: Option<Vec<String>>,
|
||||||
pub taglines: Option<Vec<String>>,
|
pub taglines: Option<Vec<String>>,
|
||||||
pub registration_mode: Option<RegistrationMode>,
|
pub registration_mode: Option<RegistrationMode>,
|
||||||
|
pub oauth_registration: Option<bool>,
|
||||||
pub content_warning: Option<String>,
|
pub content_warning: Option<String>,
|
||||||
pub default_post_listing_mode: Option<PostListingMode>,
|
pub default_post_listing_mode: Option<PostListingMode>,
|
||||||
}
|
}
|
||||||
|
@ -274,6 +276,8 @@ pub struct EditSite {
|
||||||
/// A list of taglines shown at the top of the front page.
|
/// A list of taglines shown at the top of the front page.
|
||||||
pub taglines: Option<Vec<String>>,
|
pub taglines: Option<Vec<String>>,
|
||||||
pub registration_mode: Option<RegistrationMode>,
|
pub registration_mode: Option<RegistrationMode>,
|
||||||
|
/// Whether or not external auth methods can auto-register users.
|
||||||
|
pub oauth_registration: Option<bool>,
|
||||||
/// Whether to email admins for new reports.
|
/// Whether to email admins for new reports.
|
||||||
pub reports_email_admins: Option<bool>,
|
pub reports_email_admins: Option<bool>,
|
||||||
/// If present, nsfw content is visible by default. Should be displayed by frontends/clients
|
/// If present, nsfw content is visible by default. Should be displayed by frontends/clients
|
||||||
|
@ -308,6 +312,8 @@ pub struct GetSiteResponse {
|
||||||
pub taglines: Vec<Tagline>,
|
pub taglines: Vec<Tagline>,
|
||||||
/// A list of custom emojis your site supports.
|
/// A list of custom emojis your site supports.
|
||||||
pub custom_emojis: Vec<CustomEmojiView>,
|
pub custom_emojis: Vec<CustomEmojiView>,
|
||||||
|
/// A list of external auth methods your site supports.
|
||||||
|
pub oauth_providers: Vec<OAuthProvider>,
|
||||||
pub blocked_urls: Vec<LocalSiteUrlBlocklist>,
|
pub blocked_urls: Vec<LocalSiteUrlBlocklist>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,14 +27,16 @@ use lemmy_db_schema::{
|
||||||
person::{Person, PersonUpdateForm},
|
person::{Person, PersonUpdateForm},
|
||||||
person_block::PersonBlock,
|
person_block::PersonBlock,
|
||||||
post::{Post, PostRead},
|
post::{Post, PostRead},
|
||||||
|
registration_application::RegistrationApplication,
|
||||||
site::Site,
|
site::Site,
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
utils::DbPool,
|
utils::DbPool,
|
||||||
|
RegistrationMode,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::{
|
use lemmy_db_views::{
|
||||||
comment_view::CommentQuery,
|
comment_view::CommentQuery,
|
||||||
structs::{LocalImageView, LocalUserView},
|
structs::{LocalImageView, LocalUserView, SiteView},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::structs::{
|
use lemmy_db_views_actor::structs::{
|
||||||
CommunityModeratorView,
|
CommunityModeratorView,
|
||||||
|
@ -192,6 +194,46 @@ pub fn check_user_valid(person: &Person) -> LemmyResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the user's email is verified if email verification is turned on
|
||||||
|
/// However, skip checking verification if the user is an admin
|
||||||
|
pub fn check_email_verified(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
site_view: &SiteView,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
if !local_user_view.local_user.admin
|
||||||
|
&& site_view.local_site.require_email_verification
|
||||||
|
&& !local_user_view.local_user.email_verified
|
||||||
|
{
|
||||||
|
Err(LemmyErrorType::EmailNotVerified)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn check_registration_application(
|
||||||
|
local_user_view: &LocalUserView,
|
||||||
|
local_site: &LocalSite,
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
) -> LemmyResult<()> {
|
||||||
|
if (local_site.registration_mode == RegistrationMode::RequireApplication
|
||||||
|
|| local_site.registration_mode == RegistrationMode::Closed)
|
||||||
|
&& !local_user_view.local_user.accepted_application
|
||||||
|
&& !local_user_view.local_user.admin
|
||||||
|
{
|
||||||
|
// Fetch the registration application. If no admin id is present its still pending. Otherwise it
|
||||||
|
// was processed (either accepted or denied).
|
||||||
|
let local_user_id = local_user_view.local_user.id;
|
||||||
|
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id)
|
||||||
|
.await?
|
||||||
|
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
|
||||||
|
if registration.admin_id.is_some() {
|
||||||
|
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
|
||||||
|
} else {
|
||||||
|
Err(LemmyErrorType::RegistrationApplicationIsPending)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks that a normal user action (eg posting or voting) is allowed in a given community.
|
/// Checks that a normal user action (eg posting or voting) is allowed in a given community.
|
||||||
///
|
///
|
||||||
/// In particular it checks that neither the user nor community are banned or deleted, and that
|
/// In particular it checks that neither the user nor community are banned or deleted, and that
|
||||||
|
|
|
@ -30,6 +30,9 @@ once_cell.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
webmention = "0.5.0"
|
webmention = "0.5.0"
|
||||||
accept-language = "3.1.0"
|
accept-language = "3.1.0"
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
sha3 = { workspace = true }
|
||||||
|
rand = { workspace = true }
|
||||||
|
|
||||||
[package.metadata.cargo-machete]
|
[package.metadata.cargo-machete]
|
||||||
ignored = ["futures"]
|
ignored = ["futures"]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod custom_emoji;
|
pub mod custom_emoji;
|
||||||
|
pub mod oauth_provider;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::Json;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
context::LemmyContext,
|
||||||
|
oauth_provider::CreateOAuthProvider,
|
||||||
|
utils::is_admin,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
newtypes::OAuthProviderId,
|
||||||
|
source::oauth_provider::{OAuthProvider, OAuthProviderInsertForm, UnsafeOAuthProvider},
|
||||||
|
traits::Crud,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyError;
|
||||||
|
use sha3::{
|
||||||
|
digest::{ExtendableOutput, Update, XofReader},
|
||||||
|
Shake128,
|
||||||
|
};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn create_oauth_provider(
|
||||||
|
data: Json<CreateOAuthProvider>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> Result<Json<OAuthProvider>, LemmyError> {
|
||||||
|
// Make sure user is an admin
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
// hash the issuer and client_id to create a deterministic i64 id
|
||||||
|
let mut hasher = Shake128::default();
|
||||||
|
hasher.update(data.issuer.as_bytes());
|
||||||
|
hasher.update(data.client_id.as_bytes());
|
||||||
|
let mut reader = hasher.finalize_xof();
|
||||||
|
let mut id_bytes = [0u8; 8];
|
||||||
|
reader.read(&mut id_bytes);
|
||||||
|
|
||||||
|
let cloned_data = data.clone();
|
||||||
|
let oauth_provider_form = OAuthProviderInsertForm {
|
||||||
|
id: OAuthProviderId(i64::from_ne_bytes(id_bytes)),
|
||||||
|
display_name: cloned_data.display_name,
|
||||||
|
issuer: Url::parse(&cloned_data.issuer)?.into(),
|
||||||
|
authorization_endpoint: Url::parse(&cloned_data.authorization_endpoint)?.into(),
|
||||||
|
token_endpoint: Url::parse(&cloned_data.token_endpoint)?.into(),
|
||||||
|
userinfo_endpoint: Url::parse(&cloned_data.userinfo_endpoint)?.into(),
|
||||||
|
id_claim: cloned_data.id_claim,
|
||||||
|
name_claim: cloned_data.name_claim,
|
||||||
|
client_id: data.client_id.to_string(),
|
||||||
|
client_secret: data.client_secret.to_string(),
|
||||||
|
scopes: data.scopes.to_string(),
|
||||||
|
auto_verify_email: data.auto_verify_email,
|
||||||
|
auto_approve_application: data.auto_approve_application,
|
||||||
|
account_linking_enabled: data.account_linking_enabled,
|
||||||
|
enabled: data.enabled,
|
||||||
|
};
|
||||||
|
let unsafe_oauth_provider =
|
||||||
|
UnsafeOAuthProvider::create(&mut context.pool(), &oauth_provider_form).await?;
|
||||||
|
Ok(Json(OAuthProvider::from_unsafe(&unsafe_oauth_provider)))
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::Json;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
context::LemmyContext,
|
||||||
|
oauth_provider::DeleteOAuthProvider,
|
||||||
|
utils::is_admin,
|
||||||
|
SuccessResponse,
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{source::oauth_provider::UnsafeOAuthProvider, traits::Crud};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyError;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn delete_oauth_provider(
|
||||||
|
data: Json<DeleteOAuthProvider>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> Result<Json<SuccessResponse>, LemmyError> {
|
||||||
|
// Make sure user is an admin
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
UnsafeOAuthProvider::delete(&mut context.pool(), data.id).await?;
|
||||||
|
Ok(Json(SuccessResponse::default()))
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod create;
|
||||||
|
pub mod delete;
|
||||||
|
pub mod update;
|
|
@ -0,0 +1,49 @@
|
||||||
|
use activitypub_federation::config::Data;
|
||||||
|
use actix_web::web::Json;
|
||||||
|
use lemmy_api_common::{context::LemmyContext, oauth_provider::EditOAuthProvider, utils::is_admin};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::oauth_provider::{OAuthProvider, OAuthProviderUpdateForm, UnsafeOAuthProvider},
|
||||||
|
traits::Crud,
|
||||||
|
utils::naive_now,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::{error::LemmyError, LemmyErrorType};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn update_oauth_provider(
|
||||||
|
data: Json<EditOAuthProvider>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> Result<Json<OAuthProvider>, LemmyError> {
|
||||||
|
// Make sure user is an admin
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let cloned_data = data.clone();
|
||||||
|
let oauth_provider_form = OAuthProviderUpdateForm {
|
||||||
|
display_name: cloned_data.display_name,
|
||||||
|
authorization_endpoint: Url::parse(&cloned_data.authorization_endpoint)?.into(),
|
||||||
|
token_endpoint: Url::parse(&cloned_data.token_endpoint)?.into(),
|
||||||
|
userinfo_endpoint: Url::parse(&cloned_data.userinfo_endpoint)?.into(),
|
||||||
|
id_claim: data.id_claim.to_string(),
|
||||||
|
name_claim: data.name_claim.to_string(),
|
||||||
|
client_secret: if !data.client_secret.is_empty() {
|
||||||
|
Some(data.client_secret.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
scopes: data.scopes.to_string(),
|
||||||
|
auto_verify_email: data.auto_verify_email,
|
||||||
|
auto_approve_application: data.auto_approve_application,
|
||||||
|
account_linking_enabled: data.account_linking_enabled,
|
||||||
|
enabled: data.enabled,
|
||||||
|
updated: naive_now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let update_result =
|
||||||
|
UnsafeOAuthProvider::update(&mut context.pool(), data.id, &oauth_provider_form).await?;
|
||||||
|
let unsafe_oauth_provider = UnsafeOAuthProvider::read(&mut context.pool(), update_result.id)
|
||||||
|
.await?
|
||||||
|
.ok_or(LemmyErrorType::CouldntFindOauthProvider)?;
|
||||||
|
Ok(Json(OAuthProvider::from_unsafe(&unsafe_oauth_provider)))
|
||||||
|
}
|
|
@ -231,6 +231,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -255,6 +256,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -279,6 +281,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -303,6 +306,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -327,6 +331,7 @@ mod tests {
|
||||||
Some(true),
|
Some(true),
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -351,6 +356,7 @@ mod tests {
|
||||||
Some(true),
|
Some(true),
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -375,6 +381,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
Some(RegistrationMode::RequireApplication),
|
Some(RegistrationMode::RequireApplication),
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -433,6 +440,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -456,6 +464,7 @@ mod tests {
|
||||||
Some(true),
|
Some(true),
|
||||||
Some(String::new()),
|
Some(String::new()),
|
||||||
Some(RegistrationMode::Open),
|
Some(RegistrationMode::Open),
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -479,6 +488,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -502,6 +512,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
Some(RegistrationMode::RequireApplication),
|
Some(RegistrationMode::RequireApplication),
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -552,6 +563,7 @@ mod tests {
|
||||||
site_is_federated: Option<bool>,
|
site_is_federated: Option<bool>,
|
||||||
site_application_question: Option<String>,
|
site_application_question: Option<String>,
|
||||||
site_registration_mode: Option<RegistrationMode>,
|
site_registration_mode: Option<RegistrationMode>,
|
||||||
|
site_oauth_registration: Option<bool>,
|
||||||
) -> CreateSite {
|
) -> CreateSite {
|
||||||
CreateSite {
|
CreateSite {
|
||||||
name: site_name,
|
name: site_name,
|
||||||
|
@ -594,6 +606,7 @@ mod tests {
|
||||||
blocked_instances: None,
|
blocked_instances: None,
|
||||||
taglines: None,
|
taglines: None,
|
||||||
registration_mode: site_registration_mode,
|
registration_mode: site_registration_mode,
|
||||||
|
oauth_registration: site_oauth_registration,
|
||||||
content_warning: None,
|
content_warning: None,
|
||||||
default_post_listing_mode: None,
|
default_post_listing_mode: None,
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ use lemmy_db_schema::source::{
|
||||||
actor_language::{LocalUserLanguage, SiteLanguage},
|
actor_language::{LocalUserLanguage, SiteLanguage},
|
||||||
language::Language,
|
language::Language,
|
||||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||||
|
oauth_provider::OAuthProvider,
|
||||||
tagline::Tagline,
|
tagline::Tagline,
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
|
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
|
||||||
|
@ -44,13 +45,14 @@ pub async fn get_site(
|
||||||
let site_view = SiteView::read_local(&mut context.pool())
|
let site_view = SiteView::read_local(&mut context.pool())
|
||||||
.await?
|
.await?
|
||||||
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||||
let admins = PersonView::admins(&mut context.pool()).await?;
|
let admins: Vec<PersonView> = PersonView::admins(&mut context.pool()).await?;
|
||||||
let all_languages = Language::read_all(&mut context.pool()).await?;
|
let all_languages = Language::read_all(&mut context.pool()).await?;
|
||||||
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
|
||||||
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
||||||
let custom_emojis =
|
let custom_emojis =
|
||||||
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
|
||||||
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
|
||||||
|
let oauth_providers = OAuthProvider::get_all(&mut context.pool()).await?;
|
||||||
Ok(GetSiteResponse {
|
Ok(GetSiteResponse {
|
||||||
site_view,
|
site_view,
|
||||||
admins,
|
admins,
|
||||||
|
@ -61,6 +63,7 @@ pub async fn get_site(
|
||||||
taglines,
|
taglines,
|
||||||
custom_emojis,
|
custom_emojis,
|
||||||
blocked_urls,
|
blocked_urls,
|
||||||
|
oauth_providers,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
@ -89,6 +92,11 @@ pub async fn get_site(
|
||||||
))
|
))
|
||||||
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
.with_lemmy_type(LemmyErrorType::SystemErrLogin)?;
|
||||||
|
|
||||||
|
// filter oauth_providers for normal users
|
||||||
|
if !local_user_view.local_user.admin {
|
||||||
|
filter_oauth_providers(&mut site_response.oauth_providers);
|
||||||
|
}
|
||||||
|
|
||||||
Some(MyUserInfo {
|
Some(MyUserInfo {
|
||||||
local_user_view,
|
local_user_view,
|
||||||
follows,
|
follows,
|
||||||
|
@ -99,8 +107,31 @@ pub async fn get_site(
|
||||||
discussion_languages,
|
discussion_languages,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
// filter oauth_providers for public access
|
||||||
|
filter_oauth_providers(&mut site_response.oauth_providers);
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Json(site_response))
|
Ok(Json(site_response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn filter_oauth_providers(oauth_providers: &mut Vec<OAuthProvider>) {
|
||||||
|
oauth_providers.retain_mut(|oauth_provider| {
|
||||||
|
if oauth_provider.enabled.unwrap_or(false) {
|
||||||
|
oauth_provider.issuer = None;
|
||||||
|
oauth_provider.token_endpoint = None;
|
||||||
|
oauth_provider.userinfo_endpoint = None;
|
||||||
|
oauth_provider.id_claim = None;
|
||||||
|
oauth_provider.name_claim = None;
|
||||||
|
oauth_provider.auto_verify_email = None;
|
||||||
|
oauth_provider.auto_approve_application = None;
|
||||||
|
oauth_provider.account_linking_enabled = None;
|
||||||
|
oauth_provider.enabled = None;
|
||||||
|
oauth_provider.published = None;
|
||||||
|
oauth_provider.updated = None;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -122,6 +122,7 @@ pub async fn update_site(
|
||||||
captcha_difficulty: data.captcha_difficulty.clone(),
|
captcha_difficulty: data.captcha_difficulty.clone(),
|
||||||
reports_email_admins: data.reports_email_admins,
|
reports_email_admins: data.reports_email_admins,
|
||||||
default_post_listing_mode: data.default_post_listing_mode,
|
default_post_listing_mode: data.default_post_listing_mode,
|
||||||
|
oauth_registration: data.oauth_registration,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -283,6 +284,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -306,6 +308,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -329,6 +332,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -352,6 +356,7 @@ mod tests {
|
||||||
Some(true),
|
Some(true),
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -375,6 +380,7 @@ mod tests {
|
||||||
Some(true),
|
Some(true),
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -398,6 +404,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
Some(RegistrationMode::RequireApplication),
|
Some(RegistrationMode::RequireApplication),
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -452,6 +459,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -474,6 +482,7 @@ mod tests {
|
||||||
Some(true),
|
Some(true),
|
||||||
Some(String::new()),
|
Some(String::new()),
|
||||||
Some(RegistrationMode::Open),
|
Some(RegistrationMode::Open),
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -496,6 +505,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
None::<RegistrationMode>,
|
None::<RegistrationMode>,
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -518,6 +528,7 @@ mod tests {
|
||||||
None::<bool>,
|
None::<bool>,
|
||||||
None::<String>,
|
None::<String>,
|
||||||
Some(RegistrationMode::RequireApplication),
|
Some(RegistrationMode::RequireApplication),
|
||||||
|
None::<bool>,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -566,6 +577,7 @@ mod tests {
|
||||||
site_is_federated: Option<bool>,
|
site_is_federated: Option<bool>,
|
||||||
site_application_question: Option<String>,
|
site_application_question: Option<String>,
|
||||||
site_registration_mode: Option<RegistrationMode>,
|
site_registration_mode: Option<RegistrationMode>,
|
||||||
|
site_oauth_registration: Option<bool>,
|
||||||
) -> EditSite {
|
) -> EditSite {
|
||||||
EditSite {
|
EditSite {
|
||||||
name: site_name,
|
name: site_name,
|
||||||
|
@ -612,6 +624,7 @@ mod tests {
|
||||||
reports_email_admins: None,
|
reports_email_admins: None,
|
||||||
content_warning: None,
|
content_warning: None,
|
||||||
default_post_listing_mode: None,
|
default_post_listing_mode: None,
|
||||||
|
oauth_registration: site_oauth_registration,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,12 @@ use actix_web::{web::Json, HttpRequest};
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
claims::Claims,
|
claims::Claims,
|
||||||
context::LemmyContext,
|
context::LemmyContext,
|
||||||
|
oauth_provider::{AuthenticateWithOauth, TokenResponse},
|
||||||
person::{LoginResponse, Register},
|
person::{LoginResponse, Register},
|
||||||
utils::{
|
utils::{
|
||||||
|
check_email_verified,
|
||||||
|
check_registration_application,
|
||||||
|
check_user_valid,
|
||||||
generate_inbox_url,
|
generate_inbox_url,
|
||||||
generate_local_apub_endpoint,
|
generate_local_apub_endpoint,
|
||||||
generate_shared_inbox_url,
|
generate_shared_inbox_url,
|
||||||
|
@ -18,11 +22,15 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
aggregates::structs::PersonAggregates,
|
aggregates::structs::PersonAggregates,
|
||||||
|
newtypes::{InstanceId, OAuthProviderId},
|
||||||
source::{
|
source::{
|
||||||
captcha_answer::{CaptchaAnswer, CheckCaptchaAnswer},
|
captcha_answer::{CaptchaAnswer, CheckCaptchaAnswer},
|
||||||
language::Language,
|
language::Language,
|
||||||
|
local_site::LocalSite,
|
||||||
local_user::{LocalUser, LocalUserInsertForm},
|
local_user::{LocalUser, LocalUserInsertForm},
|
||||||
local_user_vote_display_mode::LocalUserVoteDisplayMode,
|
local_user_vote_display_mode::LocalUserVoteDisplayMode,
|
||||||
|
oauth_account::{OAuthAccount, OAuthAccountInsertForm},
|
||||||
|
oauth_provider::UnsafeOAuthProvider,
|
||||||
person::{Person, PersonInsertForm},
|
person::{Person, PersonInsertForm},
|
||||||
registration_application::{RegistrationApplication, RegistrationApplicationInsertForm},
|
registration_application::{RegistrationApplication, RegistrationApplicationInsertForm},
|
||||||
},
|
},
|
||||||
|
@ -31,15 +39,15 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
use lemmy_db_views::structs::{LocalUserView, SiteView};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
|
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
|
||||||
utils::{
|
utils::{
|
||||||
slurs::{check_slurs, check_slurs_opt},
|
slurs::{check_slurs, check_slurs_opt},
|
||||||
validation::is_valid_actor_name,
|
validation::is_valid_actor_name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use rand::Rng;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[tracing::instrument(skip(context))]
|
|
||||||
pub async fn register(
|
pub async fn register(
|
||||||
data: Json<Register>,
|
data: Json<Register>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
|
@ -95,14 +103,6 @@ pub async fn register(
|
||||||
check_slurs(&data.username, &slur_regex)?;
|
check_slurs(&data.username, &slur_regex)?;
|
||||||
check_slurs_opt(&data.answer, &slur_regex)?;
|
check_slurs_opt(&data.answer, &slur_regex)?;
|
||||||
|
|
||||||
let actor_keypair = generate_actor_keypair()?;
|
|
||||||
is_valid_actor_name(&data.username, local_site.actor_name_max_length as usize)?;
|
|
||||||
let actor_id = generate_local_apub_endpoint(
|
|
||||||
EndpointType::Person,
|
|
||||||
&data.username,
|
|
||||||
&context.settings().get_protocol_and_hostname(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if let Some(email) = &data.email {
|
if let Some(email) = &data.email {
|
||||||
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
|
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
|
||||||
Err(LemmyErrorType::EmailAlreadyExists)?
|
Err(LemmyErrorType::EmailAlreadyExists)?
|
||||||
|
@ -110,24 +110,13 @@ pub async fn register(
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have to create both a person, and local_user
|
// We have to create both a person, and local_user
|
||||||
|
let inserted_person = create_person(
|
||||||
// Register the new person
|
data.username.clone(),
|
||||||
let person_form = PersonInsertForm {
|
&local_site,
|
||||||
actor_id: Some(actor_id.clone()),
|
site_view.site.instance_id,
|
||||||
inbox_url: Some(generate_inbox_url(&actor_id)?),
|
&context,
|
||||||
shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?),
|
)
|
||||||
private_key: Some(actor_keypair.private_key),
|
.await?;
|
||||||
..PersonInsertForm::new(
|
|
||||||
data.username.clone(),
|
|
||||||
actor_keypair.public_key,
|
|
||||||
site_view.site.instance_id,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// insert the person
|
|
||||||
let inserted_person = Person::create(&mut context.pool(), &person_form)
|
|
||||||
.await
|
|
||||||
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
|
|
||||||
|
|
||||||
// Automatically set their application as accepted, if they created this with open registration.
|
// Automatically set their application as accepted, if they created this with open registration.
|
||||||
// Also fixes a bug which allows users to log in when registrations are changed to closed.
|
// Also fixes a bug which allows users to log in when registrations are changed to closed.
|
||||||
|
@ -153,7 +142,7 @@ pub async fn register(
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_person.id)
|
.person_id(inserted_person.id)
|
||||||
.email(data.email.as_deref().map(str::to_lowercase))
|
.email(data.email.as_deref().map(str::to_lowercase))
|
||||||
.password_encrypted(data.password.to_string())
|
.password_encrypted(Some(data.password.to_string()))
|
||||||
.show_nsfw(Some(show_nsfw))
|
.show_nsfw(Some(show_nsfw))
|
||||||
.accepted_application(accepted_application)
|
.accepted_application(accepted_application)
|
||||||
.default_listing_type(Some(local_site.default_post_listing_type))
|
.default_listing_type(Some(local_site.default_post_listing_type))
|
||||||
|
@ -238,3 +227,312 @@ pub async fn register(
|
||||||
|
|
||||||
Ok(Json(login_response))
|
Ok(Json(login_response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn authenticate_with_oauth(
|
||||||
|
data: Json<AuthenticateWithOauth>,
|
||||||
|
req: HttpRequest,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
) -> LemmyResult<Json<LoginResponse>> {
|
||||||
|
let site_view = SiteView::read_local(&mut context.pool())
|
||||||
|
.await?
|
||||||
|
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
|
||||||
|
|
||||||
|
let local_site: LocalSite = site_view.local_site.clone();
|
||||||
|
|
||||||
|
// validate inputs
|
||||||
|
if data.oauth_provider_id == OAuthProviderId(0i64)
|
||||||
|
|| data.code.is_empty()
|
||||||
|
|| data.code.len() > 300
|
||||||
|
{
|
||||||
|
return Err(LemmyErrorType::OauthAuthorizationInvalid)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate the redirect_uri
|
||||||
|
let redirect_uri = &data.redirect_uri;
|
||||||
|
if !redirect_uri
|
||||||
|
.host_str()
|
||||||
|
.unwrap_or("")
|
||||||
|
.eq(context.settings().get_ui_hostname())
|
||||||
|
|| !redirect_uri
|
||||||
|
.scheme()
|
||||||
|
.eq(context.settings().get_protocol_string())
|
||||||
|
|| !redirect_uri.path().eq(&String::from("/oauth/callback"))
|
||||||
|
|| !redirect_uri.query().unwrap_or("").is_empty()
|
||||||
|
{
|
||||||
|
Err(LemmyErrorType::OauthAuthorizationInvalid)?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the OAUTH provider and make sure it's enabled
|
||||||
|
let oauth_provider_id = data.oauth_provider_id;
|
||||||
|
let oauth_provider = UnsafeOAuthProvider::read(&mut context.pool(), oauth_provider_id)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthAuthorizationInvalid)?
|
||||||
|
.ok_or(LemmyErrorType::OauthAuthorizationInvalid)?;
|
||||||
|
|
||||||
|
if !oauth_provider.enabled {
|
||||||
|
return Err(LemmyErrorType::OauthAuthorizationInvalid)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request an Access Token from the OAUTH provider
|
||||||
|
let response = context
|
||||||
|
.client()
|
||||||
|
.post(oauth_provider.token_endpoint.as_str())
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.form(&[
|
||||||
|
("grant_type", "authorization_code"),
|
||||||
|
("code", &data.code),
|
||||||
|
("redirect_uri", redirect_uri.as_str()),
|
||||||
|
("client_id", &oauth_provider.client_id),
|
||||||
|
("client_secret", &oauth_provider.client_secret),
|
||||||
|
])
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if response.is_err() {
|
||||||
|
return Err(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = response.expect("invalid oauth token endpoint response");
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the access token
|
||||||
|
let token_response = response
|
||||||
|
.json::<TokenResponse>()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
|
||||||
|
// Request the user info from the OAUTH provider
|
||||||
|
let response = context
|
||||||
|
.client()
|
||||||
|
.get(oauth_provider.userinfo_endpoint.as_str())
|
||||||
|
.header("Accept", "application/json")
|
||||||
|
.bearer_auth(token_response.access_token)
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if response.is_err() {
|
||||||
|
return Err(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = response.expect("invalid oauth userinfo response");
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the OAUTH user_id claim from the returned user_info
|
||||||
|
let user_info = response
|
||||||
|
.json::<serde_json::Value>()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
|
||||||
|
let oauth_user_id: String;
|
||||||
|
if let Some(oauth_user_id_value) = user_info.get(oauth_provider.id_claim) {
|
||||||
|
oauth_user_id = serde_json::from_value::<String>(oauth_user_id_value.clone())
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
} else {
|
||||||
|
return Err(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut login_response = LoginResponse {
|
||||||
|
jwt: None,
|
||||||
|
registration_created: false,
|
||||||
|
verify_email_sent: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lookup user by oauth_user_id
|
||||||
|
let mut local_user_view =
|
||||||
|
LocalUserView::find_by_oauth_id(&mut context.pool(), oauth_provider.id, &oauth_user_id).await?;
|
||||||
|
|
||||||
|
let local_user: LocalUser;
|
||||||
|
if let Some(user_view) = local_user_view {
|
||||||
|
// user found by oauth_user_id => Login user
|
||||||
|
local_user = user_view.clone().local_user;
|
||||||
|
|
||||||
|
check_user_valid(&user_view.person)?;
|
||||||
|
check_email_verified(&user_view, &site_view)?;
|
||||||
|
check_registration_application(&user_view, &site_view.local_site, &mut context.pool()).await?;
|
||||||
|
} else {
|
||||||
|
// user has never previously registered using oauth
|
||||||
|
|
||||||
|
// prevent registration if registration is closed
|
||||||
|
if local_site.registration_mode == RegistrationMode::Closed {
|
||||||
|
Err(LemmyErrorType::RegistrationClosed)?
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent registration if registration is closed for OAUTH providers
|
||||||
|
if !local_site.oauth_registration {
|
||||||
|
return Err(LemmyErrorType::OauthRegistrationClosed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the OAUTH email claim from the returned user_info
|
||||||
|
let email: String;
|
||||||
|
if let Some(email_value) = user_info.get("email") {
|
||||||
|
email = serde_json::from_value::<String>(email_value.clone())
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
} else {
|
||||||
|
return Err(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup user by OAUTH email and link accounts
|
||||||
|
local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email).await?;
|
||||||
|
|
||||||
|
let person;
|
||||||
|
if let Some(user_view) = local_user_view {
|
||||||
|
// user found by email => link and login if linking is allowed
|
||||||
|
|
||||||
|
if oauth_provider.account_linking_enabled {
|
||||||
|
// Link with OAUTH => Login user
|
||||||
|
let oauth_account_form =
|
||||||
|
OAuthAccountInsertForm::new(user_view.local_user.id, oauth_provider.id, oauth_user_id);
|
||||||
|
|
||||||
|
OAuthAccount::create(&mut context.pool(), &oauth_account_form)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
|
||||||
|
local_user = user_view.local_user;
|
||||||
|
person = user_view.person;
|
||||||
|
} else {
|
||||||
|
// email already registered
|
||||||
|
return Err(LemmyErrorType::EmailAlreadyExists)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No user was found by email => Register as new user
|
||||||
|
|
||||||
|
// Extract the OAUTH name claim from the returned user_info
|
||||||
|
let user_name: String;
|
||||||
|
if let Some(user_name_value) = user_info.get(oauth_provider.name_claim) {
|
||||||
|
user_name = serde_json::from_value::<String>(user_name_value.clone())
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
} else {
|
||||||
|
return Err(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove spaces from username
|
||||||
|
let username = str::replace(&user_name, " ", "_");
|
||||||
|
let username = username + &rand::thread_rng().gen_range(0..999).to_string();
|
||||||
|
|
||||||
|
// check for slurs
|
||||||
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
|
check_slurs(&username, &slur_regex)
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
|
||||||
|
// We have to create a person, a local_user, and an oauth_account
|
||||||
|
person = create_person(username, &local_site, site_view.site.instance_id, &context).await?;
|
||||||
|
|
||||||
|
// Create the local user
|
||||||
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
|
.person_id(person.id)
|
||||||
|
.email(Some(str::to_lowercase(&email)))
|
||||||
|
.password_encrypted(None)
|
||||||
|
.show_nsfw(Some(false))
|
||||||
|
.accepted_application(Some(oauth_provider.auto_approve_application))
|
||||||
|
.email_verified(Some(oauth_provider.auto_verify_email))
|
||||||
|
.default_listing_type(Some(local_site.default_post_listing_type))
|
||||||
|
// If its the initial site setup, they are an admin
|
||||||
|
.admin(Some(!local_site.site_setup))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
local_user = LocalUser::create(&mut context.pool(), &local_user_form, vec![])
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::OauthLoginFailed)?;
|
||||||
|
|
||||||
|
// Create the oauth account
|
||||||
|
let oauth_account_form =
|
||||||
|
OAuthAccountInsertForm::new(local_user.id, oauth_provider.id, oauth_user_id);
|
||||||
|
|
||||||
|
OAuthAccount::create(&mut context.pool(), &oauth_account_form)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.ok_or(LemmyErrorType::IncorrectLogin)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent sign in until application is accepted
|
||||||
|
if local_site.site_setup
|
||||||
|
&& local_site.registration_mode == RegistrationMode::RequireApplication
|
||||||
|
&& !local_user.accepted_application
|
||||||
|
&& !local_user.admin
|
||||||
|
{
|
||||||
|
// Create the registration application
|
||||||
|
let form = RegistrationApplicationInsertForm {
|
||||||
|
local_user_id: local_user.id,
|
||||||
|
answer: String::from("SSO ") + &oauth_provider.display_name,
|
||||||
|
};
|
||||||
|
|
||||||
|
RegistrationApplication::create(&mut context.pool(), &form).await?;
|
||||||
|
|
||||||
|
login_response.registration_created = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check email is verified when required
|
||||||
|
if !local_user.admin && local_site.require_email_verification && !local_user.email_verified {
|
||||||
|
let local_user_view = LocalUserView {
|
||||||
|
local_user: local_user.clone(),
|
||||||
|
local_user_vote_display_mode: LocalUserVoteDisplayMode::default(),
|
||||||
|
person,
|
||||||
|
counts: PersonAggregates::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
send_verification_email(
|
||||||
|
&local_user_view,
|
||||||
|
&local_user
|
||||||
|
.email
|
||||||
|
.clone()
|
||||||
|
.expect("invalid verification email"),
|
||||||
|
&mut context.pool(),
|
||||||
|
context.settings(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
login_response.verify_email_sent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !login_response.registration_created && !login_response.verify_email_sent {
|
||||||
|
let jwt = Claims::generate(local_user.id, req, &context).await?;
|
||||||
|
login_response.jwt = Some(jwt);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(Json(login_response));
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_person(
|
||||||
|
username: String,
|
||||||
|
local_site: &LocalSite,
|
||||||
|
instance_id: InstanceId,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
) -> Result<Person, LemmyError> {
|
||||||
|
let actor_keypair = generate_actor_keypair()?;
|
||||||
|
is_valid_actor_name(&username, local_site.actor_name_max_length as usize)?;
|
||||||
|
let actor_id = generate_local_apub_endpoint(
|
||||||
|
EndpointType::Person,
|
||||||
|
&username,
|
||||||
|
&context.settings().get_protocol_and_hostname(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Register the new person
|
||||||
|
let person_form = PersonInsertForm {
|
||||||
|
actor_id: Some(actor_id.clone()),
|
||||||
|
inbox_url: Some(generate_inbox_url(&actor_id)?),
|
||||||
|
shared_inbox_url: Some(generate_shared_inbox_url(context.settings())?),
|
||||||
|
private_key: Some(actor_keypair.private_key),
|
||||||
|
..PersonInsertForm::new(username.clone(), actor_keypair.public_key, instance_id)
|
||||||
|
};
|
||||||
|
|
||||||
|
// insert the person
|
||||||
|
let inserted_person = Person::create(&mut context.pool(), &person_form)
|
||||||
|
.await
|
||||||
|
.with_lemmy_type(LemmyErrorType::UserAlreadyExists)?;
|
||||||
|
|
||||||
|
Ok(inserted_person)
|
||||||
|
}
|
||||||
|
|
|
@ -19,11 +19,12 @@ pub async fn delete_account(
|
||||||
local_user_view: LocalUserView,
|
local_user_view: LocalUserView,
|
||||||
) -> LemmyResult<Json<SuccessResponse>> {
|
) -> LemmyResult<Json<SuccessResponse>> {
|
||||||
// Verify the password
|
// Verify the password
|
||||||
let valid: bool = verify(
|
let valid: bool = if let Some(password_encrypted) = &local_user_view.local_user.password_encrypted
|
||||||
&data.password,
|
{
|
||||||
&local_user_view.local_user.password_encrypted,
|
verify(&data.password, password_encrypted).unwrap_or(false)
|
||||||
)
|
} else {
|
||||||
.unwrap_or(false);
|
false
|
||||||
|
};
|
||||||
if !valid {
|
if !valid {
|
||||||
Err(LemmyErrorType::IncorrectLogin)?
|
Err(LemmyErrorType::IncorrectLogin)?
|
||||||
}
|
}
|
||||||
|
|
|
@ -347,7 +347,7 @@ mod tests {
|
||||||
|
|
||||||
let user_form = LocalUserInsertForm::builder()
|
let user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(person.id)
|
.person_id(person.id)
|
||||||
.password_encrypted("pass".to_string())
|
.password_encrypted(Some("pass".to_string()))
|
||||||
.build();
|
.build();
|
||||||
let local_user = LocalUser::create(&mut context.pool(), &user_form, vec![]).await?;
|
let local_user = LocalUser::create(&mut context.pool(), &user_form, vec![]).await?;
|
||||||
|
|
||||||
|
|
|
@ -535,7 +535,7 @@ mod tests {
|
||||||
let person = Person::create(pool, &person_form).await.unwrap();
|
let person = Person::create(pool, &person_form).await.unwrap();
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(person.id)
|
.person_id(person.id)
|
||||||
.password_encrypted("my_pw".to_string())
|
.password_encrypted(Some("my_pw".to_string()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let local_user = LocalUser::create(pool, &local_user_form, vec![])
|
let local_user = LocalUser::create(pool, &local_user_form, vec![])
|
||||||
|
@ -647,7 +647,7 @@ mod tests {
|
||||||
let person = Person::create(pool, &person_form).await.unwrap();
|
let person = Person::create(pool, &person_form).await.unwrap();
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(person.id)
|
.person_id(person.id)
|
||||||
.password_encrypted("my_pw".to_string())
|
.password_encrypted(Some("my_pw".to_string()))
|
||||||
.build();
|
.build();
|
||||||
let local_user = LocalUser::create(pool, &local_user_form, vec![])
|
let local_user = LocalUser::create(pool, &local_user_form, vec![])
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -32,9 +32,11 @@ impl LocalUser {
|
||||||
) -> Result<LocalUser, Error> {
|
) -> Result<LocalUser, Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
let mut form_with_encrypted_password = form.clone();
|
let mut form_with_encrypted_password = form.clone();
|
||||||
let password_hash =
|
|
||||||
hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
|
if let Some(password_encrypted) = &form.password_encrypted {
|
||||||
form_with_encrypted_password.password_encrypted = password_hash;
|
let password_hash = hash(password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
|
||||||
|
form_with_encrypted_password.password_encrypted = Some(password_hash);
|
||||||
|
}
|
||||||
|
|
||||||
let local_user_ = insert_into(local_user::table)
|
let local_user_ = insert_into(local_user::table)
|
||||||
.values(form_with_encrypted_password)
|
.values(form_with_encrypted_password)
|
||||||
|
@ -58,7 +60,7 @@ impl LocalUser {
|
||||||
form: &LocalUserUpdateForm,
|
form: &LocalUserUpdateForm,
|
||||||
) -> Result<usize, Error> {
|
) -> Result<usize, Error> {
|
||||||
let conn = &mut get_conn(pool).await?;
|
let conn = &mut get_conn(pool).await?;
|
||||||
let res = diesel::update(local_user::table.find(local_user_id))
|
let res: Result<usize, Error> = diesel::update(local_user::table.find(local_user_id))
|
||||||
.set(form)
|
.set(form)
|
||||||
.execute(conn)
|
.execute(conn)
|
||||||
.await;
|
.await;
|
||||||
|
@ -259,7 +261,7 @@ impl LocalUserInsertForm {
|
||||||
pub fn test_form(person_id: PersonId) -> Self {
|
pub fn test_form(person_id: PersonId) -> Self {
|
||||||
Self::builder()
|
Self::builder()
|
||||||
.person_id(person_id)
|
.person_id(person_id)
|
||||||
.password_encrypted(String::new())
|
.password_encrypted(Some(String::new()))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ pub mod local_user;
|
||||||
pub mod local_user_vote_display_mode;
|
pub mod local_user_vote_display_mode;
|
||||||
pub mod login_token;
|
pub mod login_token;
|
||||||
pub mod moderator;
|
pub mod moderator;
|
||||||
|
pub mod oauth_account;
|
||||||
|
pub mod oauth_provider;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod person_block;
|
pub mod person_block;
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::{
|
||||||
|
newtypes::OAuthAccountId,
|
||||||
|
schema::oauth_account,
|
||||||
|
source::oauth_account::{OAuthAccount, OAuthAccountInsertForm, OAuthAccountUpdateForm},
|
||||||
|
traits::Crud,
|
||||||
|
utils::{get_conn, DbPool},
|
||||||
|
};
|
||||||
|
use diesel::{dsl::insert_into, result::Error, QueryDsl};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Crud for OAuthAccount {
|
||||||
|
type InsertForm = OAuthAccountInsertForm;
|
||||||
|
type UpdateForm = OAuthAccountUpdateForm;
|
||||||
|
type IdType = OAuthAccountId;
|
||||||
|
|
||||||
|
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(oauth_account::table)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
async fn update(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
oauth_account_id: OAuthAccountId,
|
||||||
|
form: &Self::UpdateForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
diesel::update(oauth_account::table.find(oauth_account_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
use crate::{
|
||||||
|
newtypes::OAuthProviderId,
|
||||||
|
schema::oauth_provider,
|
||||||
|
source::oauth_provider::{
|
||||||
|
OAuthProvider,
|
||||||
|
OAuthProviderInsertForm,
|
||||||
|
OAuthProviderUpdateForm,
|
||||||
|
UnsafeOAuthProvider,
|
||||||
|
},
|
||||||
|
traits::Crud,
|
||||||
|
utils::{get_conn, DbPool},
|
||||||
|
};
|
||||||
|
use diesel::{dsl::insert_into, result::Error, QueryDsl};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Crud for UnsafeOAuthProvider {
|
||||||
|
type InsertForm = OAuthProviderInsertForm;
|
||||||
|
type UpdateForm = OAuthProviderUpdateForm;
|
||||||
|
type IdType = OAuthProviderId;
|
||||||
|
|
||||||
|
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
insert_into(oauth_provider::table)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
oauth_provider_id: OAuthProviderId,
|
||||||
|
form: &Self::UpdateForm,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
diesel::update(oauth_provider::table.find(oauth_provider_id))
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UnsafeOAuthProvider {
|
||||||
|
pub async fn get_all(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
let oauth_providers = oauth_provider::table
|
||||||
|
.order(oauth_provider::id)
|
||||||
|
.select(oauth_provider::all_columns)
|
||||||
|
.load::<UnsafeOAuthProvider>(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(oauth_providers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OAuthProvider {
|
||||||
|
pub async fn get_all(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
|
||||||
|
let oauth_providers = UnsafeOAuthProvider::get_all(pool).await?;
|
||||||
|
let mut result = Vec::<OAuthProvider>::new();
|
||||||
|
|
||||||
|
for oauth_provider in &oauth_providers {
|
||||||
|
result.push(Self::from_unsafe(oauth_provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_unsafe(unsafe_oauth_provider: &UnsafeOAuthProvider) -> Self {
|
||||||
|
OAuthProvider {
|
||||||
|
id: unsafe_oauth_provider.id,
|
||||||
|
display_name: unsafe_oauth_provider.display_name.clone(),
|
||||||
|
issuer: Some(unsafe_oauth_provider.issuer.clone()),
|
||||||
|
authorization_endpoint: unsafe_oauth_provider.authorization_endpoint.clone(),
|
||||||
|
token_endpoint: Some(unsafe_oauth_provider.token_endpoint.clone()),
|
||||||
|
userinfo_endpoint: Some(unsafe_oauth_provider.userinfo_endpoint.clone()),
|
||||||
|
id_claim: Some(unsafe_oauth_provider.id_claim.clone()),
|
||||||
|
name_claim: Some(unsafe_oauth_provider.name_claim.clone()),
|
||||||
|
client_id: unsafe_oauth_provider.client_id.clone(),
|
||||||
|
scopes: unsafe_oauth_provider.scopes.clone(),
|
||||||
|
auto_verify_email: Some(unsafe_oauth_provider.auto_verify_email),
|
||||||
|
auto_approve_application: Some(unsafe_oauth_provider.auto_approve_application),
|
||||||
|
account_linking_enabled: Some(unsafe_oauth_provider.account_linking_enabled),
|
||||||
|
enabled: Some(unsafe_oauth_provider.enabled),
|
||||||
|
published: Some(unsafe_oauth_provider.published),
|
||||||
|
updated: unsafe_oauth_provider.updated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -74,7 +74,7 @@ mod tests {
|
||||||
let inserted_person = Person::create(pool, &new_person).await?;
|
let inserted_person = Person::create(pool, &new_person).await?;
|
||||||
let new_local_user = LocalUserInsertForm::builder()
|
let new_local_user = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_person.id)
|
.person_id(inserted_person.id)
|
||||||
.password_encrypted("pass".to_string())
|
.password_encrypted(Some("pass".to_string()))
|
||||||
.build();
|
.build();
|
||||||
let inserted_local_user = LocalUser::create(pool, &new_local_user, vec![]).await?;
|
let inserted_local_user = LocalUser::create(pool, &new_local_user, vec![]).await?;
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,20 @@ pub struct LocalSiteId(i32);
|
||||||
/// The custom emoji id.
|
/// The custom emoji id.
|
||||||
pub struct CustomEmojiId(i32);
|
pub struct CustomEmojiId(i32);
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
#[serde(into = "String", from = "String")]
|
||||||
|
/// The oauth provider id.
|
||||||
|
pub struct OAuthProviderId(pub i64);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// The oauth account id.
|
||||||
|
pub struct OAuthAccountId(pub i32);
|
||||||
|
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(remote = "Ltree")]
|
#[serde(remote = "Ltree")]
|
||||||
|
@ -272,6 +286,21 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<OAuthProviderId> for String {
|
||||||
|
fn from(id: OAuthProviderId) -> Self {
|
||||||
|
id.0.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for OAuthProviderId {
|
||||||
|
fn from(id: String) -> Self {
|
||||||
|
OAuthProviderId(
|
||||||
|
id.parse::<i64>()
|
||||||
|
.expect("OAuthProviderID is not a valid integer"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl InstanceId {
|
impl InstanceId {
|
||||||
pub fn inner(self) -> i32 {
|
pub fn inner(self) -> i32 {
|
||||||
self.0
|
self.0
|
||||||
|
|
|
@ -393,6 +393,7 @@ diesel::table! {
|
||||||
federation_signed_fetch -> Bool,
|
federation_signed_fetch -> Bool,
|
||||||
default_post_listing_mode -> PostListingModeEnum,
|
default_post_listing_mode -> PostListingModeEnum,
|
||||||
default_sort_type -> SortTypeEnum,
|
default_sort_type -> SortTypeEnum,
|
||||||
|
oauth_registration -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,7 +437,7 @@ diesel::table! {
|
||||||
local_user (id) {
|
local_user (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
person_id -> Int4,
|
person_id -> Int4,
|
||||||
password_encrypted -> Text,
|
password_encrypted -> Nullable<Text>,
|
||||||
email -> Nullable<Text>,
|
email -> Nullable<Text>,
|
||||||
show_nsfw -> Bool,
|
show_nsfw -> Bool,
|
||||||
theme -> Text,
|
theme -> Text,
|
||||||
|
@ -613,6 +614,39 @@ diesel::table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
oauth_account (id) {
|
||||||
|
id -> Int4,
|
||||||
|
local_user_id -> Int4,
|
||||||
|
oauth_provider_id -> Int8,
|
||||||
|
oauth_user_id -> Text,
|
||||||
|
published -> Timestamptz,
|
||||||
|
updated -> Nullable<Timestamptz>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
oauth_provider (id) {
|
||||||
|
id -> Int8,
|
||||||
|
display_name -> Text,
|
||||||
|
issuer -> Text,
|
||||||
|
authorization_endpoint -> Text,
|
||||||
|
token_endpoint -> Text,
|
||||||
|
userinfo_endpoint -> Text,
|
||||||
|
id_claim -> Text,
|
||||||
|
name_claim -> Text,
|
||||||
|
client_id -> Text,
|
||||||
|
client_secret -> Text,
|
||||||
|
scopes -> Text,
|
||||||
|
auto_verify_email -> Bool,
|
||||||
|
auto_approve_application -> Bool,
|
||||||
|
account_linking_enabled -> Bool,
|
||||||
|
enabled -> Bool,
|
||||||
|
published -> Timestamptz,
|
||||||
|
updated -> Nullable<Timestamptz>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
password_reset_request (id) {
|
password_reset_request (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -1005,6 +1039,7 @@ diesel::joinable!(mod_remove_community -> person (mod_person_id));
|
||||||
diesel::joinable!(mod_remove_post -> person (mod_person_id));
|
diesel::joinable!(mod_remove_post -> person (mod_person_id));
|
||||||
diesel::joinable!(mod_remove_post -> post (post_id));
|
diesel::joinable!(mod_remove_post -> post (post_id));
|
||||||
diesel::joinable!(mod_transfer_community -> community (community_id));
|
diesel::joinable!(mod_transfer_community -> community (community_id));
|
||||||
|
diesel::joinable!(oauth_account -> local_user (local_user_id));
|
||||||
diesel::joinable!(password_reset_request -> local_user (local_user_id));
|
diesel::joinable!(password_reset_request -> local_user (local_user_id));
|
||||||
diesel::joinable!(person -> instance (instance_id));
|
diesel::joinable!(person -> instance (instance_id));
|
||||||
diesel::joinable!(person_aggregates -> person (person_id));
|
diesel::joinable!(person_aggregates -> person (person_id));
|
||||||
|
@ -1086,6 +1121,8 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||||
mod_remove_community,
|
mod_remove_community,
|
||||||
mod_remove_post,
|
mod_remove_post,
|
||||||
mod_transfer_community,
|
mod_transfer_community,
|
||||||
|
oauth_account,
|
||||||
|
oauth_provider,
|
||||||
password_reset_request,
|
password_reset_request,
|
||||||
person,
|
person,
|
||||||
person_aggregates,
|
person_aggregates,
|
||||||
|
|
|
@ -70,6 +70,8 @@ pub struct LocalSite {
|
||||||
pub default_post_listing_mode: PostListingMode,
|
pub default_post_listing_mode: PostListingMode,
|
||||||
/// Default value for [LocalUser.post_listing_mode]
|
/// Default value for [LocalUser.post_listing_mode]
|
||||||
pub default_sort_type: SortType,
|
pub default_sort_type: SortType,
|
||||||
|
/// Whether or not external auth methods can auto-register users.
|
||||||
|
pub oauth_registration: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, TypedBuilder)]
|
#[derive(Clone, TypedBuilder)]
|
||||||
|
@ -97,6 +99,7 @@ pub struct LocalSiteInsertForm {
|
||||||
pub captcha_enabled: Option<bool>,
|
pub captcha_enabled: Option<bool>,
|
||||||
pub captcha_difficulty: Option<String>,
|
pub captcha_difficulty: Option<String>,
|
||||||
pub registration_mode: Option<RegistrationMode>,
|
pub registration_mode: Option<RegistrationMode>,
|
||||||
|
pub oauth_registration: Option<bool>,
|
||||||
pub reports_email_admins: Option<bool>,
|
pub reports_email_admins: Option<bool>,
|
||||||
pub federation_signed_fetch: Option<bool>,
|
pub federation_signed_fetch: Option<bool>,
|
||||||
pub default_post_listing_mode: Option<PostListingMode>,
|
pub default_post_listing_mode: Option<PostListingMode>,
|
||||||
|
@ -125,6 +128,7 @@ pub struct LocalSiteUpdateForm {
|
||||||
pub captcha_enabled: Option<bool>,
|
pub captcha_enabled: Option<bool>,
|
||||||
pub captcha_difficulty: Option<String>,
|
pub captcha_difficulty: Option<String>,
|
||||||
pub registration_mode: Option<RegistrationMode>,
|
pub registration_mode: Option<RegistrationMode>,
|
||||||
|
pub oauth_registration: Option<bool>,
|
||||||
pub reports_email_admins: Option<bool>,
|
pub reports_email_admins: Option<bool>,
|
||||||
pub updated: Option<Option<DateTime<Utc>>>,
|
pub updated: Option<Option<DateTime<Utc>>>,
|
||||||
pub federation_signed_fetch: Option<bool>,
|
pub federation_signed_fetch: Option<bool>,
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub struct LocalUser {
|
||||||
/// The person_id for the local user.
|
/// The person_id for the local user.
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub password_encrypted: SensitiveString,
|
pub password_encrypted: Option<SensitiveString>,
|
||||||
pub email: Option<SensitiveString>,
|
pub email: Option<SensitiveString>,
|
||||||
/// Whether to show NSFW content.
|
/// Whether to show NSFW content.
|
||||||
pub show_nsfw: bool,
|
pub show_nsfw: bool,
|
||||||
|
@ -77,7 +77,7 @@ pub struct LocalUserInsertForm {
|
||||||
#[builder(!default)]
|
#[builder(!default)]
|
||||||
pub person_id: PersonId,
|
pub person_id: PersonId,
|
||||||
#[builder(!default)]
|
#[builder(!default)]
|
||||||
pub password_encrypted: String,
|
pub password_encrypted: Option<String>,
|
||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub show_nsfw: Option<bool>,
|
pub show_nsfw: Option<bool>,
|
||||||
pub theme: Option<String>,
|
pub theme: Option<String>,
|
||||||
|
|
|
@ -27,6 +27,8 @@ pub mod local_user;
|
||||||
pub mod local_user_vote_display_mode;
|
pub mod local_user_vote_display_mode;
|
||||||
pub mod login_token;
|
pub mod login_token;
|
||||||
pub mod moderator;
|
pub mod moderator;
|
||||||
|
pub mod oauth_account;
|
||||||
|
pub mod oauth_provider;
|
||||||
pub mod password_reset_request;
|
pub mod password_reset_request;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod person_block;
|
pub mod person_block;
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
use crate::newtypes::{LocalUserId, OAuthAccountId, OAuthProviderId};
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use crate::schema::oauth_account;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = oauth_account))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
/// An auth account method.
|
||||||
|
pub struct OAuthAccount {
|
||||||
|
pub id: OAuthAccountId,
|
||||||
|
pub local_user_id: LocalUserId,
|
||||||
|
pub oauth_provider_id: OAuthProviderId,
|
||||||
|
pub oauth_user_id: String,
|
||||||
|
pub published: DateTime<Utc>,
|
||||||
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, derive_new::new)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = oauth_account))]
|
||||||
|
pub struct OAuthAccountInsertForm {
|
||||||
|
pub local_user_id: LocalUserId,
|
||||||
|
pub oauth_provider_id: OAuthProviderId,
|
||||||
|
pub oauth_user_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, derive_new::new)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = oauth_account))]
|
||||||
|
pub struct OAuthAccountUpdateForm {
|
||||||
|
pub oauth_provider_id: OAuthProviderId,
|
||||||
|
pub oauth_user_id: String,
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
use crate::newtypes::{DbUrl, OAuthProviderId};
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use crate::schema::oauth_provider;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::skip_serializing_none;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = oauth_provider))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||||
|
/// oauth provider with client_secret - should never be sent to the client
|
||||||
|
pub struct UnsafeOAuthProvider {
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
/// The OAuth 2.0 provider name displayed to the user on the Login page
|
||||||
|
pub display_name: String,
|
||||||
|
/// The issuer url of the OAUTH provider.
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub issuer: DbUrl,
|
||||||
|
/// The authorization endpoint is used to interact with the resource owner and obtain an
|
||||||
|
/// authorization grant. This is usually provided by the OAUTH provider.
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub authorization_endpoint: DbUrl,
|
||||||
|
/// The token endpoint is used by the client to obtain an access token by presenting its
|
||||||
|
/// authorization grant or refresh token. This is usually provided by the OAUTH provider.
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub token_endpoint: DbUrl,
|
||||||
|
/// The UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns Claims about the
|
||||||
|
/// authenticated End-User. This is defined in the OIDC specification.
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub userinfo_endpoint: DbUrl,
|
||||||
|
/// The OAuth 2.0 claim containing the unique user ID returned by the provider. Usually this
|
||||||
|
/// should be set to "sub".
|
||||||
|
pub id_claim: String,
|
||||||
|
/// The OAuth 2.0 claim containing the user name returned by the provider. This depends on the
|
||||||
|
/// provider and could be "name", "username", ...
|
||||||
|
pub name_claim: String,
|
||||||
|
/// The client_id is provided by the OAuth 2.0 provider and is a unique identifier to this
|
||||||
|
/// service
|
||||||
|
pub client_id: String,
|
||||||
|
/// The client_secret is provided by the OAuth 2.0 provider and is used to authenticate this
|
||||||
|
/// service with the provider
|
||||||
|
#[serde(skip)]
|
||||||
|
pub client_secret: String,
|
||||||
|
/// Lists the scopes requested from users. Users will have to grant access to the requested scope
|
||||||
|
/// at sign up.
|
||||||
|
pub scopes: String,
|
||||||
|
/// Automatically sets email as verified on registration
|
||||||
|
pub auto_verify_email: bool,
|
||||||
|
/// Automatically approves user application on registration
|
||||||
|
pub auto_approve_application: bool,
|
||||||
|
/// Allows linking an OAUTH account to an existing user account by matching emails
|
||||||
|
pub account_linking_enabled: bool,
|
||||||
|
/// switch to enable or disable an oauth provider
|
||||||
|
pub enabled: bool,
|
||||||
|
pub published: DateTime<Utc>,
|
||||||
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct OAuthProvider {
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
/// The OAuth 2.0 provider name displayed to the user on the Login page
|
||||||
|
pub display_name: String,
|
||||||
|
/// The issuer url of the OAUTH provider.
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub issuer: Option<DbUrl>,
|
||||||
|
/// The authorization endpoint is used to interact with the resource owner and obtain an
|
||||||
|
/// authorization grant. This is usually provided by the OAUTH provider.
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub authorization_endpoint: DbUrl,
|
||||||
|
/// The token endpoint is used by the client to obtain an access token by presenting its
|
||||||
|
/// authorization grant or refresh token. This is usually provided by the OAUTH provider.
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub token_endpoint: Option<DbUrl>,
|
||||||
|
/// The UserInfo Endpoint is an OAuth 2.0 Protected Resource that returns Claims about the
|
||||||
|
/// authenticated End-User. This is defined in the OIDC specification.
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub userinfo_endpoint: Option<DbUrl>,
|
||||||
|
/// The OAuth 2.0 claim containing the unique user ID returned by the provider. Usually this
|
||||||
|
/// should be set to "sub".
|
||||||
|
pub id_claim: Option<String>,
|
||||||
|
/// The OAuth 2.0 claim containing the user name returned by the provider. This depends on the
|
||||||
|
/// provider and could be "name", "username", ...
|
||||||
|
pub name_claim: Option<String>,
|
||||||
|
/// The client_id is provided by the OAuth 2.0 provider and is a unique identifier to this
|
||||||
|
/// service
|
||||||
|
pub client_id: String,
|
||||||
|
/// Lists the scopes requested from users. Users will have to grant access to the requested scope
|
||||||
|
/// at sign up.
|
||||||
|
pub scopes: String,
|
||||||
|
/// Automatically sets email as verified on registration
|
||||||
|
pub auto_verify_email: Option<bool>,
|
||||||
|
/// Automatically approves user application on registration
|
||||||
|
pub auto_approve_application: Option<bool>,
|
||||||
|
/// Allows linking an OAUTH account to an existing user account by matching emails
|
||||||
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
/// switch to enable or disable an oauth provider
|
||||||
|
pub enabled: Option<bool>,
|
||||||
|
pub published: Option<DateTime<Utc>>,
|
||||||
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset, TS))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = oauth_provider))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct OAuthProviderInsertForm {
|
||||||
|
pub id: OAuthProviderId,
|
||||||
|
pub display_name: String,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub issuer: DbUrl,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub authorization_endpoint: DbUrl,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub token_endpoint: DbUrl,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub userinfo_endpoint: DbUrl,
|
||||||
|
pub id_claim: String,
|
||||||
|
pub name_claim: String,
|
||||||
|
pub client_id: String,
|
||||||
|
pub client_secret: String,
|
||||||
|
pub scopes: String,
|
||||||
|
pub auto_verify_email: bool,
|
||||||
|
pub auto_approve_application: bool,
|
||||||
|
pub account_linking_enabled: bool,
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset, TS))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = oauth_provider))]
|
||||||
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
pub struct OAuthProviderUpdateForm {
|
||||||
|
pub display_name: String,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub authorization_endpoint: DbUrl,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub token_endpoint: DbUrl,
|
||||||
|
#[cfg_attr(feature = "full", ts(type = "string"))]
|
||||||
|
pub userinfo_endpoint: DbUrl,
|
||||||
|
pub id_claim: String,
|
||||||
|
pub name_claim: String,
|
||||||
|
pub client_secret: Option<String>,
|
||||||
|
pub scopes: String,
|
||||||
|
pub auto_verify_email: bool,
|
||||||
|
pub auto_approve_application: bool,
|
||||||
|
pub account_linking_enabled: bool,
|
||||||
|
pub enabled: bool,
|
||||||
|
pub updated: DateTime<Utc>,
|
||||||
|
}
|
|
@ -303,7 +303,7 @@ mod tests {
|
||||||
|
|
||||||
let new_local_user = LocalUserInsertForm::builder()
|
let new_local_user = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_timmy.id)
|
.person_id(inserted_timmy.id)
|
||||||
.password_encrypted("123".to_string())
|
.password_encrypted(Some("123".to_string()))
|
||||||
.build();
|
.build();
|
||||||
let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![])
|
let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![])
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -493,7 +493,7 @@ mod tests {
|
||||||
let timmy_local_user_form = LocalUserInsertForm::builder()
|
let timmy_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_timmy_person.id)
|
.person_id(inserted_timmy_person.id)
|
||||||
.admin(Some(true))
|
.admin(Some(true))
|
||||||
.password_encrypted(String::new())
|
.password_encrypted(Some(String::new()))
|
||||||
.build();
|
.build();
|
||||||
let inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
|
let inserted_timmy_local_user = LocalUser::create(pool, &timmy_local_user_form, vec![]).await?;
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ use actix_web::{dev::Payload, FromRequest, HttpMessage, HttpRequest};
|
||||||
use diesel::{result::Error, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl};
|
use diesel::{result::Error, BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{LocalUserId, PersonId},
|
newtypes::{LocalUserId, OAuthProviderId, PersonId},
|
||||||
schema::{local_user, local_user_vote_display_mode, person, person_aggregates},
|
schema::{local_user, local_user_vote_display_mode, oauth_account, person, person_aggregates},
|
||||||
utils::{
|
utils::{
|
||||||
functions::{coalesce, lower},
|
functions::{coalesce, lower},
|
||||||
DbConn,
|
DbConn,
|
||||||
|
@ -23,6 +23,7 @@ enum ReadBy<'a> {
|
||||||
Name(&'a str),
|
Name(&'a str),
|
||||||
NameOrEmail(&'a str),
|
NameOrEmail(&'a str),
|
||||||
Email(&'a str),
|
Email(&'a str),
|
||||||
|
OAuthID(OAuthProviderId, &'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ListMode {
|
enum ListMode {
|
||||||
|
@ -58,12 +59,21 @@ fn queries<'a>(
|
||||||
),
|
),
|
||||||
_ => query,
|
_ => query,
|
||||||
};
|
};
|
||||||
query
|
let query = query
|
||||||
.inner_join(local_user_vote_display_mode::table)
|
.inner_join(local_user_vote_display_mode::table)
|
||||||
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)))
|
.inner_join(person_aggregates::table.on(person::id.eq(person_aggregates::person_id)));
|
||||||
.select(selection)
|
|
||||||
.first(&mut conn)
|
if let ReadBy::OAuthID(oauth_provider_id, oauth_user_id) = search {
|
||||||
.await
|
query
|
||||||
|
.inner_join(oauth_account::table)
|
||||||
|
.filter(oauth_account::oauth_provider_id.eq(oauth_provider_id))
|
||||||
|
.filter(oauth_account::oauth_user_id.eq(oauth_user_id))
|
||||||
|
.select(selection)
|
||||||
|
.first(&mut conn)
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
query.select(selection).first(&mut conn).await
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let list = move |mut conn: DbConn<'a>, mode: ListMode| async move {
|
let list = move |mut conn: DbConn<'a>, mode: ListMode| async move {
|
||||||
|
@ -120,6 +130,16 @@ impl LocalUserView {
|
||||||
queries().read(pool, ReadBy::Email(from_email)).await
|
queries().read(pool, ReadBy::Email(from_email)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn find_by_oauth_id(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
oauth_provider_id: OAuthProviderId,
|
||||||
|
oauth_user_id: &str,
|
||||||
|
) -> Result<Option<Self>, Error> {
|
||||||
|
queries()
|
||||||
|
.read(pool, ReadBy::OAuthID(oauth_provider_id, oauth_user_id))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_admins_with_emails(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
|
pub async fn list_admins_with_emails(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
|
||||||
queries().list(pool, ListMode::AdminsWithEmails).await
|
queries().list(pool, ListMode::AdminsWithEmails).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,7 +325,7 @@ mod tests {
|
||||||
|
|
||||||
let new_local_user = LocalUserInsertForm::builder()
|
let new_local_user = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_timmy.id)
|
.person_id(inserted_timmy.id)
|
||||||
.password_encrypted("123".to_string())
|
.password_encrypted(Some("123".to_string()))
|
||||||
.build();
|
.build();
|
||||||
let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![])
|
let timmy_local_user = LocalUser::create(pool, &new_local_user, vec![])
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -169,7 +169,7 @@ mod tests {
|
||||||
|
|
||||||
let timmy_local_user_form = LocalUserInsertForm::builder()
|
let timmy_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_timmy_person.id)
|
.person_id(inserted_timmy_person.id)
|
||||||
.password_encrypted("nada".to_string())
|
.password_encrypted(Some("nada".to_string()))
|
||||||
.admin(Some(true))
|
.admin(Some(true))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -183,7 +183,7 @@ mod tests {
|
||||||
|
|
||||||
let sara_local_user_form = LocalUserInsertForm::builder()
|
let sara_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_sara_person.id)
|
.person_id(inserted_sara_person.id)
|
||||||
.password_encrypted("nada".to_string())
|
.password_encrypted(Some("nada".to_string()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let inserted_sara_local_user = LocalUser::create(pool, &sara_local_user_form, vec![])
|
let inserted_sara_local_user = LocalUser::create(pool, &sara_local_user_form, vec![])
|
||||||
|
@ -211,7 +211,7 @@ mod tests {
|
||||||
|
|
||||||
let jess_local_user_form = LocalUserInsertForm::builder()
|
let jess_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_jess_person.id)
|
.person_id(inserted_jess_person.id)
|
||||||
.password_encrypted("nada".to_string())
|
.password_encrypted(Some("nada".to_string()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let inserted_jess_local_user = LocalUser::create(pool, &jess_local_user_form, vec![])
|
let inserted_jess_local_user = LocalUser::create(pool, &jess_local_user_form, vec![])
|
||||||
|
|
|
@ -290,7 +290,7 @@ mod tests {
|
||||||
|
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_person.id)
|
.person_id(inserted_person.id)
|
||||||
.password_encrypted(String::new())
|
.password_encrypted(Some(String::new()))
|
||||||
.build();
|
.build();
|
||||||
let local_user = LocalUser::create(pool, &local_user_form, vec![])
|
let local_user = LocalUser::create(pool, &local_user_form, vec![])
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -198,7 +198,7 @@ mod tests {
|
||||||
let alice = Person::create(pool, &alice_form).await?;
|
let alice = Person::create(pool, &alice_form).await?;
|
||||||
let alice_local_user_form = LocalUserInsertForm::builder()
|
let alice_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(alice.id)
|
.person_id(alice.id)
|
||||||
.password_encrypted(String::new())
|
.password_encrypted(Some(String::new()))
|
||||||
.build();
|
.build();
|
||||||
let alice_local_user = LocalUser::create(pool, &alice_local_user_form, vec![]).await?;
|
let alice_local_user = LocalUser::create(pool, &alice_local_user_form, vec![]).await?;
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ mod tests {
|
||||||
let bob = Person::create(pool, &bob_form).await?;
|
let bob = Person::create(pool, &bob_form).await?;
|
||||||
let bob_local_user_form = LocalUserInsertForm::builder()
|
let bob_local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(bob.id)
|
.person_id(bob.id)
|
||||||
.password_encrypted(String::new())
|
.password_encrypted(Some(String::new()))
|
||||||
.build();
|
.build();
|
||||||
let bob_local_user = LocalUser::create(pool, &bob_local_user_form, vec![]).await?;
|
let bob_local_user = LocalUser::create(pool, &bob_local_user_form, vec![]).await?;
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,7 @@ use actix_web::{
|
||||||
header::{HeaderName, ACCEPT_ENCODING, HOST},
|
header::{HeaderName, ACCEPT_ENCODING, HOST},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
},
|
},
|
||||||
web,
|
web::{self, Query},
|
||||||
web::Query,
|
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,6 +53,7 @@ pub enum LemmyErrorType {
|
||||||
CouldntFindCommentReply,
|
CouldntFindCommentReply,
|
||||||
CouldntFindPrivateMessage,
|
CouldntFindPrivateMessage,
|
||||||
CouldntFindActivity,
|
CouldntFindActivity,
|
||||||
|
CouldntFindOauthProvider,
|
||||||
PersonIsBlocked,
|
PersonIsBlocked,
|
||||||
CommunityIsBlocked,
|
CommunityIsBlocked,
|
||||||
InstanceIsBlocked,
|
InstanceIsBlocked,
|
||||||
|
@ -176,6 +177,9 @@ pub enum LemmyErrorType {
|
||||||
CantBlockLocalInstance,
|
CantBlockLocalInstance,
|
||||||
UrlWithoutDomain,
|
UrlWithoutDomain,
|
||||||
InboxTimeout,
|
InboxTimeout,
|
||||||
|
OauthAuthorizationInvalid,
|
||||||
|
OauthLoginFailed,
|
||||||
|
OauthRegistrationClosed,
|
||||||
Unknown(String),
|
Unknown(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,13 @@ impl Settings {
|
||||||
fs::read_to_string(Self::get_config_location())
|
fs::read_to_string(Self::get_config_location())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ui_hostname(&self) -> &str {
|
||||||
|
match &self.hostname_ui {
|
||||||
|
Some(domain) => domain,
|
||||||
|
_ => &self.hostname,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns either "http" or "https", depending on tls_enabled setting
|
/// Returns either "http" or "https", depending on tls_enabled setting
|
||||||
pub fn get_protocol_string(&self) -> &'static str {
|
pub fn get_protocol_string(&self) -> &'static str {
|
||||||
if self.tls_enabled {
|
if self.tls_enabled {
|
||||||
|
|
|
@ -28,6 +28,11 @@ pub struct Settings {
|
||||||
#[default("unset")]
|
#[default("unset")]
|
||||||
#[doku(example = "example.com")]
|
#[doku(example = "example.com")]
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
|
/// the domain name of your lemmy-ui instance used for OAUTH2 (defaults to the backend instance
|
||||||
|
/// hostname)
|
||||||
|
#[default(None)]
|
||||||
|
#[doku(example = "example.com")]
|
||||||
|
pub hostname_ui: Option<String>,
|
||||||
/// Address where lemmy should listen for incoming requests
|
/// Address where lemmy should listen for incoming requests
|
||||||
#[default(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))]
|
#[default(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)))]
|
||||||
#[doku(as = "String")]
|
#[doku(as = "String")]
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit ee2cffac809ad466644f061ad79ac577b6c2e4fd
|
Subproject commit 94f0c7e44e967ea6d003ee03b1753f08011fcf53
|
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE local_user
|
||||||
|
ALTER COLUMN password_encrypted SET NOT NULL;
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE local_user
|
||||||
|
ALTER COLUMN password_encrypted DROP NOT NULL;
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
DROP TABLE oauth_provider;
|
||||||
|
|
||||||
|
ALTER TABLE local_site
|
||||||
|
DROP COLUMN oauth_registration;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
CREATE TABLE oauth_provider (
|
||||||
|
id bigint UNIQUE,
|
||||||
|
display_name text NOT NULL,
|
||||||
|
issuer text NOT NULL,
|
||||||
|
authorization_endpoint text NOT NULL,
|
||||||
|
token_endpoint text NOT NULL,
|
||||||
|
userinfo_endpoint text NOT NULL,
|
||||||
|
id_claim text NOT NULL,
|
||||||
|
name_claim text NOT NULL,
|
||||||
|
client_id text NOT NULL UNIQUE,
|
||||||
|
client_secret text NOT NULL,
|
||||||
|
scopes text NOT NULL,
|
||||||
|
auto_verify_email boolean DEFAULT TRUE NOT NULL,
|
||||||
|
auto_approve_application boolean DEFAULT TRUE NOT NULL,
|
||||||
|
account_linking_enabled boolean DEFAULT FALSE NOT NULL,
|
||||||
|
enabled boolean DEFAULT FALSE NOT NULL,
|
||||||
|
published timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
updated timestamp with time zone,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE local_site
|
||||||
|
ADD COLUMN oauth_registration boolean DEFAULT FALSE NOT NULL;
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
DROP TABLE oauth_account;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
CREATE TABLE oauth_account (
|
||||||
|
id serial PRIMARY KEY,
|
||||||
|
local_user_id int REFERENCES local_user ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
|
oauth_provider_id bigint NOT NULL,
|
||||||
|
oauth_user_id text NOT NULL,
|
||||||
|
published timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
updated timestamp with time zone,
|
||||||
|
UNIQUE (oauth_provider_id, oauth_user_id),
|
||||||
|
UNIQUE (oauth_provider_id, local_user_id)
|
||||||
|
);
|
||||||
|
|
|
@ -108,6 +108,11 @@ use lemmy_api_crud::{
|
||||||
delete::delete_custom_emoji,
|
delete::delete_custom_emoji,
|
||||||
update::update_custom_emoji,
|
update::update_custom_emoji,
|
||||||
},
|
},
|
||||||
|
oauth_provider::{
|
||||||
|
create::create_oauth_provider,
|
||||||
|
delete::delete_oauth_provider,
|
||||||
|
update::update_oauth_provider,
|
||||||
|
},
|
||||||
post::{
|
post::{
|
||||||
create::create_post,
|
create::create_post,
|
||||||
delete::delete_post,
|
delete::delete_post,
|
||||||
|
@ -122,7 +127,10 @@ use lemmy_api_crud::{
|
||||||
update::update_private_message,
|
update::update_private_message,
|
||||||
},
|
},
|
||||||
site::{create::create_site, read::get_site, update::update_site},
|
site::{create::create_site, read::get_site, update::update_site},
|
||||||
user::{create::register, delete::delete_account},
|
user::{
|
||||||
|
create::{authenticate_with_oauth, register},
|
||||||
|
delete::delete_account,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use lemmy_apub::api::{
|
use lemmy_apub::api::{
|
||||||
list_comments::list_comments,
|
list_comments::list_comments,
|
||||||
|
@ -376,6 +384,18 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
|
||||||
.route("", web::post().to(create_custom_emoji))
|
.route("", web::post().to(create_custom_emoji))
|
||||||
.route("", web::put().to(update_custom_emoji))
|
.route("", web::put().to(update_custom_emoji))
|
||||||
.route("/delete", web::post().to(delete_custom_emoji)),
|
.route("/delete", web::post().to(delete_custom_emoji)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::scope("/oauth_provider")
|
||||||
|
.wrap(rate_limit.message())
|
||||||
|
.route("", web::post().to(create_oauth_provider))
|
||||||
|
.route("", web::put().to(update_oauth_provider))
|
||||||
|
.route("/delete", web::post().to(delete_oauth_provider)),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::scope("/oauth")
|
||||||
|
.wrap(rate_limit.register())
|
||||||
|
.route("/authenticate", web::post().to(authenticate_with_oauth)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
cfg.service(
|
cfg.service(
|
||||||
|
|
|
@ -470,7 +470,7 @@ async fn initialize_local_site_2022_10_10(
|
||||||
|
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(person_inserted.id)
|
.person_id(person_inserted.id)
|
||||||
.password_encrypted(setup.admin_password.clone())
|
.password_encrypted(Some(setup.admin_password.clone()))
|
||||||
.email(setup.admin_email.clone())
|
.email(setup.admin_email.clone())
|
||||||
.admin(Some(true))
|
.admin(Some(true))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
body::MessageBody,
|
body::MessageBody,
|
||||||
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
|
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
|
||||||
http::header::CACHE_CONTROL,
|
http::header::{HeaderValue, CACHE_CONTROL},
|
||||||
Error,
|
Error,
|
||||||
HttpMessage,
|
HttpMessage,
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,6 @@ use core::future::Ready;
|
||||||
use futures_util::future::LocalBoxFuture;
|
use futures_util::future::LocalBoxFuture;
|
||||||
use lemmy_api::{local_user_view_from_jwt, read_auth_token};
|
use lemmy_api::{local_user_view_from_jwt, read_auth_token};
|
||||||
use lemmy_api_common::context::LemmyContext;
|
use lemmy_api_common::context::LemmyContext;
|
||||||
use reqwest::header::HeaderValue;
|
|
||||||
use std::{future::ready, rc::Rc};
|
use std::{future::ready, rc::Rc};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -148,7 +147,7 @@ mod tests {
|
||||||
|
|
||||||
let local_user_form = LocalUserInsertForm::builder()
|
let local_user_form = LocalUserInsertForm::builder()
|
||||||
.person_id(inserted_person.id)
|
.person_id(inserted_person.id)
|
||||||
.password_encrypted("123456".to_string())
|
.password_encrypted(Some("123456".to_string()))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
|
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
|
||||||
|
|
Loading…
Reference in New Issue