OAuth callbacks
This commit is contained in:
parent
56ef7c9318
commit
0923427551
65
Cargo.lock
generated
65
Cargo.lock
generated
@ -86,9 +86,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "actix-jwt-session"
|
||||
version = "1.0.3"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "370a6266b24b472629abdbd222d9ea735c0ac86c81fa2a3851dee86c82d99521"
|
||||
checksum = "6317d3303618eea36d68898bf720ce94d8b5bb2bf1a23c8ffdf714024f0a49a6"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"argon2",
|
||||
@ -97,6 +97,7 @@ dependencies = [
|
||||
"cookie 0.17.0",
|
||||
"deadpool",
|
||||
"deadpool-redis",
|
||||
"derive_more",
|
||||
"futures",
|
||||
"futures-lite",
|
||||
"futures-util",
|
||||
@ -368,9 +369,9 @@ checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9"
|
||||
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
@ -687,9 +688,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.14.0"
|
||||
version = "1.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||
checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
@ -742,9 +743,9 @@ checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.31"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@ -753,7 +754,7 @@ dependencies = [
|
||||
"pure-rust-locales",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1075,6 +1076,12 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@ -1757,7 +1764,9 @@ dependencies = [
|
||||
"actix-web",
|
||||
"async-trait",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"derive_more",
|
||||
"dotenv",
|
||||
"entities",
|
||||
"figment",
|
||||
"futures",
|
||||
@ -2320,9 +2329,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.62"
|
||||
version = "0.10.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671"
|
||||
checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8"
|
||||
dependencies = [
|
||||
"bitflags 2.4.2",
|
||||
"cfg-if",
|
||||
@ -2352,9 +2361,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.98"
|
||||
version = "0.9.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7"
|
||||
checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@ -2626,9 +2635,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.76"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -2808,13 +2817,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.2"
|
||||
version = "1.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.3",
|
||||
"regex-automata 0.4.5",
|
||||
"regex-syntax 0.8.2",
|
||||
]
|
||||
|
||||
@ -2829,9 +2838,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.3"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
||||
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -3224,9 +3233,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm"
|
||||
version = "0.12.11"
|
||||
version = "0.12.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59f66e2129991acd51fcad7b59da1edd862edca69773cc9a19cb17b967fae2fb"
|
||||
checksum = "0cbf88748872fa54192476d6d49d0775e208566a72656e267e45f6980b926c8d"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
@ -3252,9 +3261,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sea-orm-macros"
|
||||
version = "0.12.11"
|
||||
version = "0.12.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b03da1864306242678ac3b6567e69f70dd1252a72742baa23a4d92d2d45da3fc"
|
||||
checksum = "e0dbc880d47aa53c6a572e39c99402c7fad59b50766e51e0b0fc1306510b0555"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@ -3553,9 +3562,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.0"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b187f0231d56fe41bfb12034819dd2bf336422a5866de41bc3fec4b2e3883e8"
|
||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
@ -4280,9 +4289,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "uncased"
|
||||
version = "0.9.9"
|
||||
version = "0.9.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68"
|
||||
checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
|
||||
use super::sea_orm_active_enums::ProjectMemberInviteRoles;
|
||||
use super::sea_orm_active_enums::ProjectMemberRoles;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -17,7 +17,7 @@ pub struct Model {
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub message: Option<String>,
|
||||
pub responded_at: Option<DateTimeWithTimeZone>,
|
||||
pub role: ProjectMemberInviteRoles,
|
||||
pub role: ProjectMemberRoles,
|
||||
pub created_by_id: Option<Uuid>,
|
||||
pub project_id: Uuid,
|
||||
pub updated_by_id: Option<Uuid>,
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11
|
||||
|
||||
use super::sea_orm_active_enums::Roles;
|
||||
use super::sea_orm_active_enums::ProjectMemberRoles;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@ -13,7 +13,7 @@ pub struct Model {
|
||||
pub id: Uuid,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub comment: Option<String>,
|
||||
pub role: Roles,
|
||||
pub role: ProjectMemberRoles,
|
||||
pub created_by_id: Option<Uuid>,
|
||||
pub member_id: Option<Uuid>,
|
||||
pub project_id: Uuid,
|
||||
|
@ -7,9 +7,9 @@ use serde::{Deserialize, Serialize};
|
||||
#[sea_orm(
|
||||
rs_type = "String",
|
||||
db_type = "Enum",
|
||||
enum_name = "project_member_invite_roles"
|
||||
enum_name = "project_member_roles"
|
||||
)]
|
||||
pub enum ProjectMemberInviteRoles {
|
||||
pub enum ProjectMemberRoles {
|
||||
#[sea_orm(string_value = "Admin")]
|
||||
Admin,
|
||||
#[sea_orm(string_value = "Guest")]
|
||||
|
@ -14,7 +14,7 @@ rumqttc = { version = "0.23.0", features = ["use-rustls"] }
|
||||
rust-s3 = { version = "0.33.0", features = ["tokio-rustls-tls", "futures-util", "futures-io"] }
|
||||
sea-orm = { version = "0.12.11", features = ["postgres-array", "sqlx-all"] }
|
||||
serde = "1.0.195"
|
||||
serde_json = "1.0.111"
|
||||
serde_json = { version = "1.0.111", features = ["raw_value", "alloc"] }
|
||||
tokio = { version = "1.35.1", features = ["full"] }
|
||||
jet-contract = { workspace = true }
|
||||
uuid = { version = "1.7.0", features = ["v4", "serde"] }
|
||||
@ -39,3 +39,5 @@ oauth2-signin = "0.2.0"
|
||||
oauth2-core = "0.2.0"
|
||||
reqwest = { version = "0.11.23", default-features = false, features = ["rustls", "tokio-rustls", "tokio-socks", "multipart"] }
|
||||
http-api-isahc-client = { version = "0.2.2", features = ["with-sleep-via-tokio"] }
|
||||
dotenv = "0.15.0"
|
||||
chrono = { version = "0.4.32", default-features = false, features = ["clock", "serde"] }
|
||||
|
@ -1,11 +1,14 @@
|
||||
use actix_web::web::{Data, ServiceConfig};
|
||||
use actix_web::{delete, get, HttpResponse};
|
||||
use sea_orm::DatabaseConnection;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod email_check;
|
||||
mod social_auth;
|
||||
|
||||
pub fn configure(config: &mut ServiceConfig) {
|
||||
pub fn configure(http_client: reqwest::Client, config: &mut ServiceConfig) {
|
||||
config.service(email_check::email_check).service(oauth);
|
||||
social_auth::configure(http_client, config);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, derive_more::Display)]
|
||||
@ -18,7 +21,17 @@ pub enum PublishError {
|
||||
MagicLinkEmail,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, derive_more::Display, derive_more::From)]
|
||||
#[derive(Debug, Clone, derive_more::Display)]
|
||||
pub enum OAuthError {
|
||||
#[display(fmt = "Failed to load workspace invides for {user_id} on {provider} callback")]
|
||||
FetchWorkspaceInvites { user_id: Uuid, provider: String },
|
||||
#[display(fmt = "Failed to load project invides for {user_id} on {provider} callback")]
|
||||
FetchProjectInvites { user_id: Uuid, provider: String },
|
||||
#[display(fmt = "Failed to connect user {user_id} to {provider}")]
|
||||
ConnectSocialMedia { user_id: Uuid, provider: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, derive_more::Display, derive_more::From)]
|
||||
pub enum AuthError {
|
||||
#[display(fmt = "New account creation is disabled. Please contact your site administrator")]
|
||||
RegisterOff,
|
||||
@ -37,6 +50,9 @@ pub enum AuthError {
|
||||
#[display(fmt = "{}", _0)]
|
||||
#[from]
|
||||
Publish(PublishError),
|
||||
#[display(fmt = "{}", _0)]
|
||||
#[from]
|
||||
Oauth(OAuthError),
|
||||
}
|
||||
|
||||
#[get("/social-auth")]
|
||||
|
623
crates/jet-api/src/http/api/authentication/social_auth.rs
Normal file
623
crates/jet-api/src/http/api/authentication/social_auth.rs
Normal file
@ -0,0 +1,623 @@
|
||||
use std::env::var as env_var;
|
||||
|
||||
use super::AuthError;
|
||||
use actix_jwt_session::{JwtTtl, RefreshTtl, JWT_HEADER_NAME, REFRESH_HEADER_NAME};
|
||||
use actix_web::{
|
||||
get,
|
||||
web::{self, Data, ServiceConfig},
|
||||
HttpRequest, HttpResponse,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use entities::project_member_invites::{
|
||||
Column as ProjectMemberInviteColumn, Model as ProjectMemberInvite,
|
||||
};
|
||||
use entities::users::{ActiveModel as UserModel, Column as UserColumn};
|
||||
use entities::workspace_member_invites::{
|
||||
Column as WorkspaceMemberInviteColumn, Model as WorkspaceMemberInvite,
|
||||
};
|
||||
use entities::workspace_members::ActiveModel as WorkspaceMemberModel;
|
||||
use entities::{
|
||||
prelude::{
|
||||
ProjectMemberInvites, ProjectMembers, Users, WorkspaceMemberInvites, WorkspaceMembers,
|
||||
},
|
||||
sea_orm_active_enums::Roles,
|
||||
};
|
||||
use entities::{
|
||||
project_members::ActiveModel as ProjectMemberModel, sea_orm_active_enums::ProjectMemberRoles,
|
||||
};
|
||||
use http_api_isahc_client::IsahcClient;
|
||||
use jet_contract::{
|
||||
event_bus::{Msg, SignInMedium, Topic, UserMsg},
|
||||
UserId,
|
||||
};
|
||||
use oauth2_amazon::{
|
||||
AmazonExtensionsBuilder, AmazonProviderWithWebServices, AmazonScope, AmazonTokenUrlRegion,
|
||||
};
|
||||
use oauth2_client::extensions::UserInfo;
|
||||
use oauth2_github::{GithubExtensionsBuilder, GithubProviderWithWebApplication, GithubScope};
|
||||
use oauth2_gitlab::{
|
||||
GitlabExtensionsBuilder, GitlabProviderForEndUsers, GitlabScope, BASE_URL_GITLAB_COM,
|
||||
};
|
||||
use oauth2_google::{
|
||||
GoogleExtensionsBuilder, GoogleProviderForWebServerApps,
|
||||
GoogleProviderForWebServerAppsAccessType, GoogleScope,
|
||||
};
|
||||
use oauth2_signin::web_app::{SigninFlow, SigninFlowHandleCallbackByQueryConfiguration};
|
||||
use reqwest::header::{LOCATION, USER_AGENT};
|
||||
use sea_orm::{
|
||||
sea_query::OnConflict, ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, DatabaseConnection,
|
||||
DatabaseTransaction, EntityTrait, QueryFilter, Set, TransactionTrait,
|
||||
};
|
||||
use tracing::{debug, error, warn};
|
||||
|
||||
use crate::{
|
||||
http::OAuthError,
|
||||
models::{Error, JsonError},
|
||||
session::AppClaims,
|
||||
};
|
||||
|
||||
macro_rules! oauth_envs {
|
||||
($env: expr, 2) => {{
|
||||
use std::concat;
|
||||
use std::env::var;
|
||||
var(concat!($env, "_CLIENT_ID"))
|
||||
.ok()
|
||||
.zip(var(concat!($env, "_CLIENT_SECRET")).ok())
|
||||
}};
|
||||
($env: expr, 1) => {{
|
||||
use std::concat;
|
||||
use std::env::var;
|
||||
var(concat!($env, "_CLIENT_ID")).ok()
|
||||
}};
|
||||
}
|
||||
macro_rules! init_flow_service {
|
||||
($provider: expr, $flow: expr) => {
|
||||
web::resource(std::concat!("/auth/", $provider)).get({
|
||||
let flow = $flow.clone();
|
||||
move || {
|
||||
let flow = flow.clone();
|
||||
async move {
|
||||
let flow = flow.clone();
|
||||
let Ok(uri) = flow.build_authorization_url(None) else {
|
||||
return HttpResponse::InternalServerError().finish();
|
||||
};
|
||||
HttpResponse::SeeOther()
|
||||
.append_header((LOCATION, uri.as_str()))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! flow_callback {
|
||||
($provider: expr, $flow: expr) => {{
|
||||
web::resource(std::concat!("/auth/", $provider, "/callback")).get(
|
||||
move |req: HttpRequest,
|
||||
db: Data<DatabaseConnection>,
|
||||
event_bus: Data<crate::EventBusClient>,
|
||||
session: Data<actix_jwt_session::SessionStorage>| {
|
||||
let flow = $flow.clone();
|
||||
async move {
|
||||
let res: Result<HttpResponse, Error> = handle_callback(
|
||||
$provider,
|
||||
req,
|
||||
db,
|
||||
flow.clone(),
|
||||
event_bus.clone(),
|
||||
session.clone(),
|
||||
)
|
||||
.await;
|
||||
res
|
||||
}
|
||||
},
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
pub fn configure(_http_client: reqwest::Client, config: &mut ServiceConfig) {
|
||||
let schema = env_var("JET_API_SCHEMA").unwrap();
|
||||
let host = env_var("JET_API_HOST").unwrap();
|
||||
let uri = format!("{schema}://{host}");
|
||||
let client = http_api_isahc_client::IsahcClient::new().unwrap();
|
||||
|
||||
if let Some((client_id, client_secret)) = oauth_envs!("GITHUB", 2) {
|
||||
let flow = Data::new(github_flow(client.clone(), client_id, client_secret, &uri));
|
||||
config
|
||||
.service(init_flow_service!("github", flow))
|
||||
.service(flow_callback!("github", flow));
|
||||
}
|
||||
if let Some((client_id, client_secret)) = oauth_envs!("GITLAB", 2) {
|
||||
let flow = Data::new(gitlab_flow(client.clone(), client_id, client_secret, &uri));
|
||||
config
|
||||
.service(init_flow_service!("gitlab", flow))
|
||||
.service(flow_callback!("gitlab", flow));
|
||||
}
|
||||
if let Some((client_id, client_secret)) = oauth_envs!("GOOGLE", 2) {
|
||||
let flow = Data::new(google_flow(client.clone(), client_id, client_secret, &uri));
|
||||
config
|
||||
.service(init_flow_service!("google", flow))
|
||||
.service(flow_callback!("google", flow));
|
||||
}
|
||||
if let Some((client_id, client_secret)) = oauth_envs!("AMAZON", 2) {
|
||||
let flow = Data::new(amazon_flow(client.clone(), client_id, client_secret, &uri));
|
||||
config
|
||||
.service(init_flow_service!("amazon", flow))
|
||||
.service(flow_callback!("amazon", flow));
|
||||
}
|
||||
|
||||
config.service(social_auth);
|
||||
}
|
||||
|
||||
#[get("/social-auth")]
|
||||
async fn social_auth() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
async fn handle_callback(
|
||||
provider: &str,
|
||||
req: HttpRequest,
|
||||
db: Data<DatabaseConnection>,
|
||||
flow: Data<SigninFlow<IsahcClient>>,
|
||||
event_bus: Data<crate::EventBusClient>,
|
||||
session: Data<actix_jwt_session::SessionStorage>,
|
||||
) -> std::result::Result<HttpResponse, Error> {
|
||||
debug!("Starting github callback");
|
||||
let query = req.query_string();
|
||||
let config = SigninFlowHandleCallbackByQueryConfiguration::new();
|
||||
let ret = flow.handle_callback_by_query(query, config).await;
|
||||
|
||||
use oauth2_signin::web_app::SigninFlowHandleCallbackRet as R;
|
||||
let response = match ret {
|
||||
R::Ok((access_token_body, user_info)) => {
|
||||
let mut tx = db.begin().await.map_err(|_| Error::DatabaseError)?;
|
||||
match handle_user_info(
|
||||
provider,
|
||||
req,
|
||||
&mut tx,
|
||||
access_token_body.id_token,
|
||||
user_info,
|
||||
event_bus,
|
||||
session,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => {
|
||||
tx.commit().await.map_err(|e| {
|
||||
error!("Failed to commit social_auth changes to postgres: {e}");
|
||||
Error::DatabaseError
|
||||
})?;
|
||||
v
|
||||
}
|
||||
Err(e) => {
|
||||
tx.rollback().await.map_err(|e| {
|
||||
error!("Failed to rollback social_auth changes to postgres: {e}");
|
||||
Error::DatabaseError
|
||||
})?;
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
R::OkButUserInfoNone(access_token_body) => {
|
||||
warn!("handle callback from {provider}");
|
||||
let _id = access_token_body.id_token;
|
||||
HttpResponse::FailedDependency().json(JsonError::new(
|
||||
"Authentication provider did not return user info",
|
||||
))
|
||||
}
|
||||
R::OkButUserInfoObtainError((access_token_body, error)) => {
|
||||
warn!("handle callback from {provider}: {error}");
|
||||
let _id = access_token_body.id_token;
|
||||
HttpResponse::FailedDependency().json(JsonError::new(
|
||||
"Authentication provider refused to returns user info",
|
||||
))
|
||||
}
|
||||
R::OkButUserInfoEndpointExecuteError((access_token_body, error)) => {
|
||||
warn!("handle callback from {provider}: {error}");
|
||||
let _id = access_token_body.id_token;
|
||||
HttpResponse::FailedDependency().json(JsonError::new(
|
||||
"Authentication provider refused to returns user info",
|
||||
))
|
||||
}
|
||||
R::FlowHandleCallbackError(error) => {
|
||||
warn!("handle callback from {provider} failed: {error}");
|
||||
HttpResponse::FailedDependency()
|
||||
.json(JsonError::new("Authentication provider returns an error"))
|
||||
}
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn handle_user_info(
|
||||
provider: &str,
|
||||
req: HttpRequest,
|
||||
db: &mut DatabaseTransaction,
|
||||
id_token: Option<String>,
|
||||
user_info: UserInfo,
|
||||
event_bus: Data<crate::EventBusClient>,
|
||||
session: Data<actix_jwt_session::SessionStorage>,
|
||||
) -> std::result::Result<HttpResponse, AuthError> {
|
||||
let UserInfo {
|
||||
uid: _,
|
||||
name: _,
|
||||
email,
|
||||
raw: _,
|
||||
} = user_info;
|
||||
|
||||
let Some(email) = email else {
|
||||
return Ok(HttpResponse::BadRequest().json(JsonError::new(
|
||||
"Something went wrong. Please try again later or contact the support team.",
|
||||
)));
|
||||
};
|
||||
if !email.contains('@') {
|
||||
return Ok(HttpResponse::BadRequest().json(JsonError::new(
|
||||
"Something went wrong. Please try again later or contact the support team.",
|
||||
)));
|
||||
}
|
||||
|
||||
let user = Users::find()
|
||||
.filter(UserColumn::Email.eq(&email))
|
||||
.one(&mut *db)
|
||||
.await;
|
||||
|
||||
let mut user: UserModel = match user {
|
||||
Ok(Some(user)) => user.into(),
|
||||
Ok(None) => {
|
||||
return Ok(HttpResponse::BadRequest().json(JsonError::new(
|
||||
"Something went wrong. Please try again later or contact the support team.",
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to find user for oauth {provider} sign-in: {e}");
|
||||
return Ok(HttpResponse::InternalServerError().finish());
|
||||
}
|
||||
};
|
||||
|
||||
let ip = req
|
||||
.peer_addr()
|
||||
.ok_or(AuthError::NoPeerAddr)?
|
||||
.ip()
|
||||
.to_string();
|
||||
let user_agent = req
|
||||
.headers()
|
||||
.get(USER_AGENT)
|
||||
.ok_or(AuthError::NoUserAgent)?
|
||||
.to_str()
|
||||
.map_err(|_| AuthError::InvalidUserAgent)?
|
||||
.to_string();
|
||||
|
||||
user.is_active = Set(true);
|
||||
user.last_active = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||
user.last_login_time = Set(Some(chrono::Utc::now().fixed_offset()));
|
||||
user.last_login_ip = Set(ip.clone());
|
||||
user.last_login_uagent = Set(user_agent.clone());
|
||||
user.last_login_medium = Set(SignInMedium::OAuth.to_string());
|
||||
user.is_email_verified = Set(true);
|
||||
|
||||
let user_id = user.id.clone().unwrap();
|
||||
let user = match user.update(&mut *db).await {
|
||||
Ok(user) => user,
|
||||
Err(e) => {
|
||||
error!("Failed to update user {user_id:?} on oauth {provider}: {e}");
|
||||
return Ok(HttpResponse::InternalServerError().finish());
|
||||
}
|
||||
};
|
||||
|
||||
let workspace_invites = WorkspaceMemberInvites::find()
|
||||
.filter(
|
||||
WorkspaceMemberInviteColumn::Accepted
|
||||
.eq(true)
|
||||
.and(WorkspaceMemberInviteColumn::Email.eq(&email)),
|
||||
)
|
||||
.all(&mut *db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to update user {user_id:?} on oauth {provider}: {e}");
|
||||
OAuthError::FetchWorkspaceInvites {
|
||||
user_id,
|
||||
provider: provider.to_owned(),
|
||||
}
|
||||
})?;
|
||||
let project_invites = ProjectMemberInvites::find()
|
||||
.filter(
|
||||
ProjectMemberInviteColumn::Accepted
|
||||
.eq(true)
|
||||
.and(ProjectMemberInviteColumn::Email.eq(&email)),
|
||||
)
|
||||
.all(&mut *db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to update user {user_id:?} on oauth {provider}: {e}");
|
||||
OAuthError::FetchProjectInvites {
|
||||
user_id,
|
||||
provider: provider.to_owned(),
|
||||
}
|
||||
})?;
|
||||
|
||||
// Create workspace members
|
||||
let insert = WorkspaceMembers::insert_many(workspace_invites.iter().map(
|
||||
|WorkspaceMemberInvite {
|
||||
role,
|
||||
created_by_id,
|
||||
workspace_id,
|
||||
..
|
||||
}| WorkspaceMemberModel {
|
||||
id: NotSet,
|
||||
created_at: Set(Utc::now().fixed_offset()),
|
||||
updated_at: Set(Utc::now().fixed_offset()),
|
||||
role: Set(match role {
|
||||
Roles::Admin => Roles::Member,
|
||||
_ => role.clone(),
|
||||
}),
|
||||
created_by_id: Set(created_by_id.clone()),
|
||||
member_id: Set(user_id),
|
||||
updated_by_id: NotSet,
|
||||
workspace_id: Set(workspace_id.clone()),
|
||||
company_role: NotSet,
|
||||
view_props: NotSet,
|
||||
default_props: NotSet,
|
||||
issue_props: NotSet,
|
||||
is_active: Set(true),
|
||||
},
|
||||
));
|
||||
if let Err(e) = insert
|
||||
.on_conflict(OnConflict::new().do_nothing().to_owned())
|
||||
.exec(&mut *db)
|
||||
.await
|
||||
{
|
||||
error!("Failed to add user {user_id:?} to workspace with provider {provider}: {e}");
|
||||
return Ok(HttpResponse::InternalServerError()
|
||||
.json(JsonError::new("Failed to add user to workspaces")));
|
||||
}
|
||||
|
||||
// Create project members
|
||||
let insert = ProjectMembers::insert_many(project_invites.iter().map(
|
||||
|ProjectMemberInvite {
|
||||
role,
|
||||
created_by_id,
|
||||
workspace_id,
|
||||
..
|
||||
}| ProjectMemberModel {
|
||||
id: NotSet,
|
||||
created_at: Set(Utc::now().fixed_offset()),
|
||||
updated_at: Set(Utc::now().fixed_offset()),
|
||||
role: Set(match role {
|
||||
ProjectMemberRoles::Admin => ProjectMemberRoles::Member,
|
||||
_ => role.clone(),
|
||||
}),
|
||||
created_by_id: Set(created_by_id.clone()),
|
||||
member_id: Set(Some(user_id)),
|
||||
updated_by_id: NotSet,
|
||||
workspace_id: Set(workspace_id.clone()),
|
||||
view_props: NotSet,
|
||||
default_props: NotSet,
|
||||
is_active: Set(true),
|
||||
comment: Set(None),
|
||||
project_id: NotSet,
|
||||
sort_order: NotSet,
|
||||
preferences: NotSet,
|
||||
},
|
||||
));
|
||||
if let Err(e) = insert
|
||||
.on_conflict(OnConflict::new().do_nothing().to_owned())
|
||||
.exec(&mut *db)
|
||||
.await
|
||||
{
|
||||
error!("Failed to add user {user_id:?} to project with provider {provider}: {e}");
|
||||
return Ok(HttpResponse::InternalServerError()
|
||||
.json(JsonError::new("Failed to add user to projects")));
|
||||
}
|
||||
|
||||
// cleanups
|
||||
if let Err(e) = WorkspaceMemberInvites::delete_many()
|
||||
.filter(
|
||||
WorkspaceMemberInviteColumn::Id.is_in(
|
||||
workspace_invites
|
||||
.into_iter()
|
||||
.map(|w| uuid::Uuid::from_u128(w.id.as_u128()))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
)
|
||||
.exec(&mut *db)
|
||||
.await
|
||||
{
|
||||
error!(
|
||||
"Failed clean up workspace invites for user {user_id:?} with provider {provider}: {e}"
|
||||
);
|
||||
};
|
||||
if let Err(e) = ProjectMemberInvites::delete_many()
|
||||
.filter(
|
||||
ProjectMemberInviteColumn::Id.is_in(
|
||||
project_invites
|
||||
.into_iter()
|
||||
.map(|w| uuid::Uuid::from_u128(w.id.as_u128()))
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
)
|
||||
.exec(&mut *db)
|
||||
.await
|
||||
{
|
||||
error!(
|
||||
"Failed clean up project invites for user {user_id:?} with provider {provider}: {e}"
|
||||
);
|
||||
};
|
||||
|
||||
entities::social_login_connections::ActiveModel {
|
||||
id: NotSet,
|
||||
created_at: NotSet,
|
||||
updated_at: NotSet,
|
||||
medium: Set(provider.to_owned()),
|
||||
last_login_at: Set(Some(chrono::Utc::now().fixed_offset())),
|
||||
last_received_at: NotSet,
|
||||
token_data: Set(Some(serde_json::json!({ "id_token": id_token }))),
|
||||
extra_data: NotSet,
|
||||
created_by_id: Set(Some(user_id)),
|
||||
updated_by_id: NotSet,
|
||||
user_id: Set(user_id),
|
||||
}
|
||||
.save(&mut *db)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Failed to create social media connection {provider:?}: {e}");
|
||||
OAuthError::ConnectSocialMedia {
|
||||
user_id,
|
||||
provider: provider.to_owned(),
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Err(e) = event_bus
|
||||
.publish(
|
||||
Topic::User,
|
||||
Msg::User(UserMsg::SignIn {
|
||||
user_id: UserId::new(user_id),
|
||||
email,
|
||||
user_agent,
|
||||
ip,
|
||||
medium: SignInMedium::OAuth,
|
||||
first_time: false,
|
||||
}),
|
||||
rumqttc::QoS::AtLeastOnce,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("Failed to publish sign-in msg after {provider} callback: {e}");
|
||||
};
|
||||
|
||||
let access_ttl = JwtTtl(actix_jwt_session::Duration::days(9_999));
|
||||
let refresh_ttl = RefreshTtl(actix_jwt_session::Duration::days(9_999));
|
||||
let claims = AppClaims::from_user(user, access_ttl);
|
||||
let pair = match session.store(claims, access_ttl, refresh_ttl).await {
|
||||
Ok(pair) => pair,
|
||||
Err(e) => {
|
||||
error!("Failed to store session: {e}");
|
||||
return Ok(HttpResponse::InternalServerError().finish());
|
||||
}
|
||||
};
|
||||
|
||||
let access_token = match pair.jwt.encode() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!("Failed to store session: {e}");
|
||||
return Ok(HttpResponse::InternalServerError().finish());
|
||||
}
|
||||
};
|
||||
let refresh_token = match pair.refresh.encode() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
error!("Failed to store session: {e}");
|
||||
return Ok(HttpResponse::InternalServerError().finish());
|
||||
}
|
||||
};
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
struct Response {
|
||||
access_token: String,
|
||||
refresh_token: String,
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Created()
|
||||
.append_header((JWT_HEADER_NAME, access_token.clone()))
|
||||
.append_header((REFRESH_HEADER_NAME, refresh_token.clone()))
|
||||
.json(Response {
|
||||
access_token,
|
||||
refresh_token,
|
||||
}))
|
||||
}
|
||||
|
||||
fn github_flow(
|
||||
client: IsahcClient,
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
uri: &str,
|
||||
) -> SigninFlow<IsahcClient> {
|
||||
SigninFlow::new(
|
||||
client.clone(),
|
||||
GithubProviderWithWebApplication::new(
|
||||
client_id.to_owned(),
|
||||
client_secret.to_owned(),
|
||||
format!("{uri}/api/auth/github/callback").parse().unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
vec![GithubScope::PublicRepo, GithubScope::UserEmail],
|
||||
GithubExtensionsBuilder,
|
||||
)
|
||||
}
|
||||
|
||||
fn gitlab_flow(
|
||||
client: IsahcClient,
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
uri: &str,
|
||||
) -> SigninFlow<IsahcClient> {
|
||||
SigninFlow::new(
|
||||
client.clone(),
|
||||
GitlabProviderForEndUsers::new(
|
||||
BASE_URL_GITLAB_COM,
|
||||
client_id.to_owned(),
|
||||
client_secret.to_owned(),
|
||||
oauth2_core::types::RedirectUri::Url(
|
||||
format!("{uri}/api/auth/gitlab/callback").parse().unwrap(),
|
||||
),
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
GitlabScope::Openid,
|
||||
GitlabScope::Profile,
|
||||
GitlabScope::Email,
|
||||
GitlabScope::ReadUser,
|
||||
],
|
||||
GitlabExtensionsBuilder,
|
||||
)
|
||||
}
|
||||
|
||||
fn google_flow(
|
||||
client: IsahcClient,
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
uri: &str,
|
||||
) -> SigninFlow<IsahcClient> {
|
||||
SigninFlow::new(
|
||||
client.clone(),
|
||||
GoogleProviderForWebServerApps::new(
|
||||
client_id.clone(),
|
||||
client_secret.clone(),
|
||||
oauth2_core::types::RedirectUri::Url(
|
||||
format!("{uri}/api/auth/google/callback").parse().unwrap(),
|
||||
),
|
||||
)
|
||||
.unwrap()
|
||||
.configure(|x| {
|
||||
x.access_type = Some(GoogleProviderForWebServerAppsAccessType::Offline);
|
||||
x.include_granted_scopes = Some(true);
|
||||
}),
|
||||
vec![
|
||||
GoogleScope::Email,
|
||||
GoogleScope::Profile,
|
||||
GoogleScope::Openid,
|
||||
],
|
||||
GoogleExtensionsBuilder,
|
||||
)
|
||||
}
|
||||
|
||||
fn amazon_flow(
|
||||
client: IsahcClient,
|
||||
client_id: String,
|
||||
client_secret: String,
|
||||
uri: &str,
|
||||
) -> SigninFlow<IsahcClient> {
|
||||
SigninFlow::new(
|
||||
client,
|
||||
AmazonProviderWithWebServices::new(
|
||||
client_id.to_owned(),
|
||||
client_secret.to_owned(),
|
||||
oauth2_core::types::RedirectUri::Url(
|
||||
format!("{uri}/api/auth/amazon/callback").parse().unwrap(),
|
||||
),
|
||||
AmazonTokenUrlRegion::NA,
|
||||
)
|
||||
.unwrap(),
|
||||
vec![AmazonScope::Profile, AmazonScope::PostalCode],
|
||||
AmazonExtensionsBuilder,
|
||||
)
|
||||
}
|
@ -3,15 +3,13 @@ pub use authentication::*;
|
||||
|
||||
mod authentication;
|
||||
mod config;
|
||||
mod social_auth;
|
||||
mod users;
|
||||
|
||||
pub fn configure(http_client: reqwest::Client, config: &mut ServiceConfig) {
|
||||
config.service(
|
||||
scope("/api")
|
||||
.configure(config::configure)
|
||||
.configure(users::configure)
|
||||
.configure(authentication::configure)
|
||||
.configure(|c| social_auth::configure(http_client, c)),
|
||||
.configure(users::configure),
|
||||
);
|
||||
authentication::configure(http_client, config);
|
||||
}
|
||||
|
@ -1,192 +0,0 @@
|
||||
use std::env::var as env_var;
|
||||
|
||||
use actix_web::{
|
||||
get,
|
||||
web::{Data, Query, ServiceConfig},
|
||||
HttpResponse,
|
||||
};
|
||||
use derive_more::Deref;
|
||||
use http_api_isahc_client::IsahcClient;
|
||||
use oauth2_amazon::{
|
||||
AmazonExtensionsBuilder, AmazonProviderWithWebServices, AmazonScope, AmazonTokenUrlRegion,
|
||||
};
|
||||
use oauth2_github::{GithubExtensionsBuilder, GithubProviderWithWebApplication, GithubScope};
|
||||
use oauth2_gitlab::{
|
||||
GitlabExtensionsBuilder, GitlabProviderForEndUsers, GitlabScope, BASE_URL_GITLAB_COM,
|
||||
};
|
||||
use oauth2_google::{
|
||||
GoogleExtensionsBuilder, GoogleProviderForWebServerApps,
|
||||
GoogleProviderForWebServerAppsAccessType, GoogleScope,
|
||||
};
|
||||
use oauth2_signin::web_app::{
|
||||
SigninFlow,
|
||||
SigninFlowHandleCallbackByQueryConfiguration,
|
||||
};
|
||||
use tracing::{debug, info};
|
||||
|
||||
macro_rules! oauth_envs {
|
||||
($env: expr, 2) => {{
|
||||
use std::concat;
|
||||
use std::env::var;
|
||||
var(concat!($env, "_CLIENT_ID"))
|
||||
.ok()
|
||||
.zip(var(concat!($env, "_CLIENT_SECRET")).ok())
|
||||
}};
|
||||
($env: expr, 1) => {{
|
||||
use std::concat;
|
||||
use std::env::var;
|
||||
var(concat!($env, "_CLIENT_ID")).ok()
|
||||
}};
|
||||
}
|
||||
#[derive(Debug, Deref)]
|
||||
struct Gitlab(SigninFlow<IsahcClient>);
|
||||
#[derive(Debug, Deref)]
|
||||
struct Github(SigninFlow<IsahcClient>);
|
||||
#[derive(Debug, Deref)]
|
||||
struct Google(SigninFlow<IsahcClient>);
|
||||
#[derive(Debug, Deref)]
|
||||
struct Amazon(SigninFlow<IsahcClient>);
|
||||
|
||||
struct OAuthSecrets {
|
||||
client_id: String,
|
||||
client_secret: Option<String>,
|
||||
}
|
||||
|
||||
impl OAuthSecrets {
|
||||
pub fn new1(client_id: String) -> Self {
|
||||
Self {
|
||||
client_secret: None,
|
||||
client_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new2(client_id: String, client_secret: String) -> Self {
|
||||
Self {
|
||||
client_secret: Some(client_secret),
|
||||
client_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure(http_client: reqwest::Client, config: &mut ServiceConfig) {
|
||||
let schema = env_var("JET_API_SCHEMA").unwrap();
|
||||
let host = env_var("JET_API_HOST").unwrap();
|
||||
let uri = format!("{schema}://{host}");
|
||||
let client = http_api_isahc_client::IsahcClient::new().unwrap();
|
||||
|
||||
if let Some((client_id, client_secret)) = oauth_envs!("GITHUB", 2) {
|
||||
config
|
||||
.app_data(Data::new(Github(SigninFlow::new(
|
||||
client.clone(),
|
||||
GithubProviderWithWebApplication::new(
|
||||
client_id.to_owned(),
|
||||
client_secret.to_owned(),
|
||||
format!("{uri}/auth/github/callback").parse().unwrap(),
|
||||
)
|
||||
.unwrap(),
|
||||
vec![GithubScope::PublicRepo, GithubScope::UserEmail],
|
||||
GithubExtensionsBuilder,
|
||||
))))
|
||||
.service(github_callback);
|
||||
}
|
||||
if let Some((client_id, client_secret)) = oauth_envs!("GITLAB", 2) {
|
||||
config
|
||||
.app_data(Data::new(Gitlab(SigninFlow::new(
|
||||
client.clone(),
|
||||
GitlabProviderForEndUsers::new(
|
||||
BASE_URL_GITLAB_COM,
|
||||
client_id.to_owned(),
|
||||
client_secret.to_owned(),
|
||||
oauth2_core::types::RedirectUri::Url(
|
||||
format!("{uri}/auth/gitlab/callback").parse().unwrap(),
|
||||
),
|
||||
)
|
||||
.unwrap(),
|
||||
vec![
|
||||
GitlabScope::Openid,
|
||||
GitlabScope::Profile,
|
||||
GitlabScope::Email,
|
||||
GitlabScope::ReadUser,
|
||||
],
|
||||
GitlabExtensionsBuilder,
|
||||
))))
|
||||
.service(gitlab_callback);
|
||||
}
|
||||
if let Some((client_id, client_secret)) = oauth_envs!("GOOGLE", 2) {
|
||||
config
|
||||
.app_data(Data::new(Google(SigninFlow::new(
|
||||
client.clone(),
|
||||
GoogleProviderForWebServerApps::new(
|
||||
client_id.clone(),
|
||||
client_secret.clone(),
|
||||
oauth2_core::types::RedirectUri::Url(
|
||||
format!("{uri}/auth/google/callback").parse().unwrap(),
|
||||
),
|
||||
)
|
||||
.unwrap()
|
||||
.configure(|x| {
|
||||
x.access_type = Some(GoogleProviderForWebServerAppsAccessType::Offline);
|
||||
x.include_granted_scopes = Some(true);
|
||||
}),
|
||||
vec![
|
||||
GoogleScope::Email,
|
||||
GoogleScope::Profile,
|
||||
GoogleScope::Openid,
|
||||
],
|
||||
GoogleExtensionsBuilder,
|
||||
))))
|
||||
.service(google_callback);
|
||||
}
|
||||
if let Some((client_id, client_secret)) = oauth_envs!("AMAZON", 2) {
|
||||
config
|
||||
.app_data(Data::new(Amazon(SigninFlow::new(
|
||||
client.clone(),
|
||||
AmazonProviderWithWebServices::new(
|
||||
client_id.to_owned(),
|
||||
client_secret.to_owned(),
|
||||
oauth2_core::types::RedirectUri::Url(
|
||||
format!("{uri}/auth/amazon/callback").parse().unwrap(),
|
||||
),
|
||||
AmazonTokenUrlRegion::NA,
|
||||
)
|
||||
.unwrap(),
|
||||
vec![AmazonScope::Profile, AmazonScope::PostalCode],
|
||||
AmazonExtensionsBuilder,
|
||||
))))
|
||||
.service(amazon_callback);
|
||||
}
|
||||
|
||||
config.service(social_auth);
|
||||
}
|
||||
|
||||
#[get("/social-auth")]
|
||||
async fn social_auth() -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[get("/auth/gitlab/callback")]
|
||||
async fn gitlab_callback(flow: Data<Gitlab>, query: Query<String>) -> HttpResponse {
|
||||
let query = query.into_inner();
|
||||
debug!("gitlab callback {query:?}");
|
||||
|
||||
let config = SigninFlowHandleCallbackByQueryConfiguration::new();
|
||||
let ret = flow.handle_callback_by_query(query, config).await;
|
||||
info!("gitlab {ret:?}");
|
||||
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[get("/auth/github/callback")]
|
||||
async fn github_callback(flow: Data<Github>) -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[get("/auth/google/callback")]
|
||||
async fn google_callback(flow: Data<Google>) -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
||||
|
||||
#[get("/auth/amazon/callback")]
|
||||
async fn amazon_callback(flow: Data<Amazon>) -> HttpResponse {
|
||||
HttpResponse::NotImplemented().finish()
|
||||
}
|
@ -17,6 +17,8 @@ pub const APPLICATION_NAME: &str = "jet-api";
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() {
|
||||
#[cfg(debug_assertions)]
|
||||
dotenv::from_filename(".env.development").ok();
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let addr = env::var("JET_API_ADDR").unwrap_or_else(|_| "0.0.0.0:7865".to_owned());
|
||||
|
@ -16,7 +16,7 @@ impl JsonError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, derive_more::Display)]
|
||||
#[derive(Debug, Clone, derive_more::Display)]
|
||||
pub enum Error {
|
||||
#[display(fmt = "Database connection error")]
|
||||
DatabaseError,
|
||||
|
@ -1,3 +1,7 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use actix_jwt_session::JwtTtl;
|
||||
use entities::users::Model as User;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -11,9 +15,9 @@ pub enum Audience {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct AppClaims {
|
||||
#[serde(rename = "exp")]
|
||||
pub expiration_time: u64,
|
||||
pub expiration_time: i64,
|
||||
#[serde(rename = "iat")]
|
||||
pub issues_at: usize,
|
||||
pub issued_at: i64,
|
||||
/// Account login
|
||||
#[serde(rename = "sub")]
|
||||
pub subject: String,
|
||||
@ -24,7 +28,7 @@ pub struct AppClaims {
|
||||
#[serde(rename = "aci")]
|
||||
pub account_id: Uuid,
|
||||
#[serde(rename = "nbf")]
|
||||
pub not_before: u64,
|
||||
pub not_before: i64,
|
||||
}
|
||||
|
||||
impl actix_jwt_session::Claims for AppClaims {
|
||||
@ -41,4 +45,19 @@ impl AppClaims {
|
||||
pub fn account_id(&self) -> Uuid {
|
||||
self.account_id
|
||||
}
|
||||
|
||||
pub fn from_user(user: User, ttl: JwtTtl) -> Self {
|
||||
let ttl = ttl.0;
|
||||
let ttl = chrono::Duration::seconds(ttl.whole_seconds());
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
expiration_time: now.naive_utc().add(ttl).timestamp(),
|
||||
issued_at: now.timestamp(),
|
||||
subject: user.email.clone().unwrap_or_default(),
|
||||
audience: Audience::Web,
|
||||
jwt_id: Uuid::new_v4(),
|
||||
account_id: user.id,
|
||||
not_before: now.timestamp(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,12 +56,15 @@ pub enum FileMsg {
|
||||
pub enum SignInMedium {
|
||||
#[display(fmt = "MAGIC_LINK")]
|
||||
MagicLink,
|
||||
#[display(fmt = "oauth")]
|
||||
OAuth,
|
||||
}
|
||||
|
||||
impl SignInMedium {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::MagicLink => "MAGIC_LINK",
|
||||
Self::OAuth => "oauth",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1937
plane_db.sql
1937
plane_db.sql
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user