feat: expose workspace and profile api (#32)
* feat: expose workspace and profile api * feat: add impl for client_api * feat: add test case for workspace and profile
This commit is contained in:
parent
9ac53dca8e
commit
0d59211e55
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "\n SELECT * FROM public.af_workspace WHERE owner_uid = $1\n ",
|
"query": "\n SELECT * FROM public.af_workspace WHERE owner_uid = (\n SELECT uid FROM public.af_user WHERE uuid = $1\n )\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"Left": [
|
"Left": [
|
||||||
"Int8"
|
"Uuid"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
|
|
@ -54,5 +54,5 @@
|
||||||
true
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "23697d21dc4aa7a71027ac5671d415f3ead15394a18414911475fb946a00dd01"
|
"hash": "d37119628f436d151a5559e692a3d6a7a6bc3c8226631ce6a0bcd0c7f1c0a8c5"
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +161,7 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"mio",
|
"mio",
|
||||||
"socket2 0.5.3",
|
"socket2 0.5.4",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
@ -263,7 +263,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"socket2 0.5.3",
|
"socket2 0.5.4",
|
||||||
"time",
|
"time",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
@ -477,6 +477,7 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
"uuid",
|
||||||
"validator",
|
"validator",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -824,6 +825,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"shared_entity",
|
"shared_entity",
|
||||||
|
"storage",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3105,9 +3107,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.3"
|
version = "0.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
|
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
|
|
@ -3371,6 +3373,7 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uuid",
|
||||||
"validator",
|
"validator",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3537,7 +3540,7 @@ dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2 0.5.3",
|
"socket2 0.5.4",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
@ -3856,6 +3859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.2.10",
|
"getrandom 0.2.10",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ tracing-bunyan-formatter = "0.3.6"
|
||||||
tracing-actix-web = "0.7"
|
tracing-actix-web = "0.7"
|
||||||
tracing-log = "0.1.1"
|
tracing-log = "0.1.1"
|
||||||
sqlx = { version = "0.7", default-features = false, features = ["runtime-tokio-rustls", "macros", "postgres", "uuid", "chrono", "migrate"] }
|
sqlx = { version = "0.7", default-features = false, features = ["runtime-tokio-rustls", "macros", "postgres", "uuid", "chrono", "migrate"] }
|
||||||
|
uuid = { version= "1.4.1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
#Local crate
|
#Local crate
|
||||||
token = { path = "libs/token" }
|
token = { path = "libs/token" }
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,4 @@ gotrue = { path = "../gotrue" }
|
||||||
infra = { path = "../infra" }
|
infra = { path = "../infra" }
|
||||||
serde_json = "1.0.105"
|
serde_json = "1.0.105"
|
||||||
shared_entity = { path = "../shared-entity" }
|
shared_entity = { path = "../shared-entity" }
|
||||||
|
storage = { path = "../storage" }
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ use gotrue::models::{AccessTokenResponse, User};
|
||||||
use shared_entity::error::AppError;
|
use shared_entity::error::AppError;
|
||||||
|
|
||||||
use shared_entity::server_error::ErrorCode;
|
use shared_entity::server_error::ErrorCode;
|
||||||
|
use storage::entities::AfUserProfileView;
|
||||||
|
use storage::entities::AfWorkspace;
|
||||||
|
use storage::entities::AfWorkspaces;
|
||||||
|
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
http_client: reqwest::Client,
|
http_client: reqwest::Client,
|
||||||
|
|
@ -28,6 +31,32 @@ impl Client {
|
||||||
self.token.as_ref()
|
self.token.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn profile(&self) -> Result<AfUserProfileView, AppError> {
|
||||||
|
let url = format!("{}/api/user/profile", self.base_url);
|
||||||
|
let resp = self
|
||||||
|
.http_client_with_auth(Method::GET, &url)?
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
let profile = AppResponse::<AfUserProfileView>::from_response(resp)
|
||||||
|
.await?
|
||||||
|
.into_data()?
|
||||||
|
.ok_or::<AppError>(ErrorCode::MissingPayload.into())?;
|
||||||
|
Ok(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn workspaces(&mut self) -> Result<AfWorkspaces, AppError> {
|
||||||
|
let url = format!("{}/api/user/workspaces", self.base_url);
|
||||||
|
let resp = self
|
||||||
|
.http_client_with_auth(Method::GET, &url)?
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
let workspaces = AppResponse::<Vec<AfWorkspace>>::from_response(resp)
|
||||||
|
.await?
|
||||||
|
.into_data()?
|
||||||
|
.ok_or::<AppError>(ErrorCode::MissingPayload.into())?;
|
||||||
|
Ok(AfWorkspaces(workspaces))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn sign_in_password(&mut self, email: &str, password: &str) -> Result<(), AppError> {
|
pub async fn sign_in_password(&mut self, email: &str, password: &str) -> Result<(), AppError> {
|
||||||
let url = format!("{}/api/user/sign_in/password", self.base_url);
|
let url = format!("{}/api/user/sign_in/password", self.base_url);
|
||||||
let payload = serde_json::json!({
|
let payload = serde_json::json!({
|
||||||
|
|
@ -72,7 +101,7 @@ impl Client {
|
||||||
let new_user = AppResponse::<User>::from_response(resp)
|
let new_user = AppResponse::<User>::from_response(resp)
|
||||||
.await?
|
.await?
|
||||||
.into_data()?
|
.into_data()?
|
||||||
.ok_or::<Error>(ErrorCode::Unhandled.into())?;
|
.ok_or::<AppError>(ErrorCode::Unhandled.into())?;
|
||||||
if let Some(t) = self.token.as_mut() {
|
if let Some(t) = self.token.as_mut() {
|
||||||
t.user = new_user;
|
t.user = new_user;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,9 @@ pub enum ErrorCode {
|
||||||
|
|
||||||
#[error("OAuth authentication error.")]
|
#[error("OAuth authentication error.")]
|
||||||
OAuthError = 1003,
|
OAuthError = 1003,
|
||||||
|
|
||||||
|
#[error("Missing Payload")]
|
||||||
|
MissingPayload = 1004,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implements conversion from `anyhow::Error` to `ErrorCode`.
|
/// Implements conversion from `anyhow::Error` to `ErrorCode`.
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@ serde = { version = "1.0.130", features = ["derive"] }
|
||||||
serde_json = "1.0.68"
|
serde_json = "1.0.68"
|
||||||
thiserror = "1.0.47"
|
thiserror = "1.0.47"
|
||||||
secrecy = { version = "0.8", features = ["serde"] }
|
secrecy = { version = "0.8", features = ["serde"] }
|
||||||
|
|
||||||
sqlx = { version = "0.7", default-features = false, features = ["postgres", "chrono", "uuid", "macros", "runtime-tokio-rustls"] }
|
sqlx = { version = "0.7", default-features = false, features = ["postgres", "chrono", "uuid", "macros", "runtime-tokio-rustls"] }
|
||||||
tracing = { version = "0.1.37" }
|
tracing = { version = "0.1.37" }
|
||||||
validator = { version = "0.16", features = ["validator_derive", "derive"] }
|
validator = { version = "0.16", features = ["validator_derive", "derive"] }
|
||||||
|
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|
@ -53,7 +53,7 @@ pub struct QueryCollabParams {
|
||||||
pub collab_type: CollabType,
|
pub collab_type: CollabType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
#[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)]
|
||||||
pub struct AfWorkspace {
|
pub struct AfWorkspace {
|
||||||
pub workspace_id: uuid::Uuid,
|
pub workspace_id: uuid::Uuid,
|
||||||
pub database_storage_id: Option<sqlx::types::uuid::Uuid>,
|
pub database_storage_id: Option<sqlx::types::uuid::Uuid>,
|
||||||
|
|
@ -64,21 +64,23 @@ pub struct AfWorkspace {
|
||||||
pub workspace_name: Option<String>,
|
pub workspace_name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, sqlx::FromRow)]
|
#[derive(Debug, sqlx::FromRow, Deserialize, Serialize)]
|
||||||
pub struct AfUserProfileView {
|
pub struct AfUserProfileView {
|
||||||
pub uid: Option<i64>, // Made this field nullable based on the error
|
pub uid: Option<i64>,
|
||||||
pub uuid: Option<uuid::Uuid>, // Made this field nullable based on the error
|
pub uuid: Option<uuid::Uuid>,
|
||||||
pub email: Option<String>, // Made this field nullable based on the error
|
pub email: Option<String>,
|
||||||
pub password: Option<String>, // Made this field nullable based on the error
|
pub password: Option<String>,
|
||||||
pub name: Option<String>, // Made this field nullable based on the error
|
pub name: Option<String>,
|
||||||
pub encryption_sign: Option<String>, // Made this field nullable based on the error
|
pub encryption_sign: Option<String>,
|
||||||
pub deleted_at: Option<DateTime<Utc>>,
|
pub deleted_at: Option<DateTime<Utc>>,
|
||||||
pub updated_at: Option<DateTime<Utc>>,
|
pub updated_at: Option<DateTime<Utc>>,
|
||||||
pub created_at: Option<DateTime<Utc>>,
|
pub created_at: Option<DateTime<Utc>>,
|
||||||
pub latest_workspace_id: Option<uuid::Uuid>,
|
pub latest_workspace_id: Option<uuid::Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, sqlx::FromRow, Deserialize, Serialize)]
|
||||||
pub struct AfWorkspaces(pub Vec<AfWorkspace>);
|
pub struct AfWorkspaces(pub Vec<AfWorkspace>);
|
||||||
|
|
||||||
impl AfWorkspaces {
|
impl AfWorkspaces {
|
||||||
pub fn get_latest(&self, profile: AfUserProfileView) -> Option<AfWorkspace> {
|
pub fn get_latest(&self, profile: AfUserProfileView) -> Option<AfWorkspace> {
|
||||||
match profile.latest_workspace_id {
|
match profile.latest_workspace_id {
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,16 @@ pub async fn create_user_if_not_exists(
|
||||||
|
|
||||||
pub async fn select_all_workspaces_owned(
|
pub async fn select_all_workspaces_owned(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
owner_uid: i64,
|
owner_uuid: &Uuid,
|
||||||
) -> Result<Vec<AfWorkspace>, sqlx::Error> {
|
) -> Result<Vec<AfWorkspace>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
AfWorkspace,
|
AfWorkspace,
|
||||||
r#"
|
r#"
|
||||||
SELECT * FROM public.af_workspace WHERE owner_uid = $1
|
SELECT * FROM public.af_workspace WHERE owner_uid = (
|
||||||
|
SELECT uid FROM public.af_user WHERE uuid = $1
|
||||||
|
)
|
||||||
"#,
|
"#,
|
||||||
owner_uid
|
owner_uuid
|
||||||
)
|
)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await
|
.await
|
||||||
|
|
@ -41,7 +43,7 @@ pub async fn select_all_workspaces_owned(
|
||||||
|
|
||||||
pub async fn select_user_profile_view_by_uuid(
|
pub async fn select_user_profile_view_by_uuid(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
gotrue_uuid: Uuid,
|
user_uuid: &Uuid,
|
||||||
) -> Result<Option<AfUserProfileView>, sqlx::Error> {
|
) -> Result<Option<AfUserProfileView>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
AfUserProfileView,
|
AfUserProfileView,
|
||||||
|
|
@ -49,7 +51,7 @@ pub async fn select_user_profile_view_by_uuid(
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM public.af_user_profile_view WHERE uuid = $1
|
FROM public.af_user_profile_view WHERE uuid = $1
|
||||||
"#,
|
"#,
|
||||||
gotrue_uuid
|
user_uuid
|
||||||
)
|
)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
.await
|
.await
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ use crate::domain::{UserEmail, UserName, UserPassword};
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
use gotrue::models::{AccessTokenResponse, User};
|
use gotrue::models::{AccessTokenResponse, User};
|
||||||
use shared_entity::data::{AppResponse, JsonAppResponse};
|
use shared_entity::data::{AppResponse, JsonAppResponse};
|
||||||
|
use storage::entities::{AfUserProfileView, AfWorkspaces};
|
||||||
|
|
||||||
use crate::component::auth::jwt::Authorization;
|
use crate::component::auth::jwt::{Authorization, UserUuid};
|
||||||
use actix_web::web::{Data, Json};
|
use actix_web::web::{Data, Json};
|
||||||
use actix_web::HttpRequest;
|
use actix_web::HttpRequest;
|
||||||
use actix_web::Result;
|
use actix_web::Result;
|
||||||
|
|
@ -25,6 +26,9 @@ pub fn user_scope() -> Scope {
|
||||||
.service(web::resource("/sign_out").route(web::post().to(sign_out_handler)))
|
.service(web::resource("/sign_out").route(web::post().to(sign_out_handler)))
|
||||||
.service(web::resource("/update").route(web::post().to(update_handler)))
|
.service(web::resource("/update").route(web::post().to(update_handler)))
|
||||||
|
|
||||||
|
.service(web::resource("/workspaces").route(web::get().to(workspaces_handler)))
|
||||||
|
.service(web::resource("/profile").route(web::get().to(profile_handler)))
|
||||||
|
|
||||||
// native
|
// native
|
||||||
.service(web::resource("/login").route(web::post().to(login_handler)))
|
.service(web::resource("/login").route(web::post().to(login_handler)))
|
||||||
.service(web::resource("/logout").route(web::get().to(logout_handler)))
|
.service(web::resource("/logout").route(web::get().to(logout_handler)))
|
||||||
|
|
@ -32,6 +36,22 @@ pub fn user_scope() -> Scope {
|
||||||
.service(web::resource("/password").route(web::post().to(change_password_handler)))
|
.service(web::resource("/password").route(web::post().to(change_password_handler)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn profile_handler(
|
||||||
|
uuid: UserUuid,
|
||||||
|
state: Data<State>,
|
||||||
|
) -> Result<JsonAppResponse<AfUserProfileView>> {
|
||||||
|
let profile = biz::user::user_profile(&state.pg_pool, &uuid.0).await?;
|
||||||
|
Ok(AppResponse::Ok().with_data(profile).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn workspaces_handler(
|
||||||
|
uuid: UserUuid,
|
||||||
|
state: Data<State>,
|
||||||
|
) -> Result<JsonAppResponse<AfWorkspaces>> {
|
||||||
|
let workspaces = biz::user::user_workspaces(&state.pg_pool, &uuid.0).await?;
|
||||||
|
Ok(AppResponse::Ok().with_data(workspaces).into())
|
||||||
|
}
|
||||||
|
|
||||||
async fn update_handler(
|
async fn update_handler(
|
||||||
auth: Authorization,
|
auth: Authorization,
|
||||||
req: Json<LoginRequest>,
|
req: Json<LoginRequest>,
|
||||||
|
|
@ -71,7 +91,13 @@ async fn sign_up_handler(
|
||||||
req: Json<LoginRequest>,
|
req: Json<LoginRequest>,
|
||||||
state: Data<State>,
|
state: Data<State>,
|
||||||
) -> Result<JsonAppResponse<()>> {
|
) -> Result<JsonAppResponse<()>> {
|
||||||
biz::user::sign_up(&state.gotrue_client, &req.email, &req.password).await?;
|
biz::user::sign_up(
|
||||||
|
&state.pg_pool,
|
||||||
|
&state.gotrue_client,
|
||||||
|
&req.email,
|
||||||
|
&req.password,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(AppResponse::Ok().into())
|
Ok(AppResponse::Ok().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,41 @@ use gotrue::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use shared_entity::{error::AppError, server_error};
|
use shared_entity::{error::AppError, server_error};
|
||||||
|
use storage::entities::{AfUserProfileView, AfWorkspaces};
|
||||||
use validator::validate_email;
|
use validator::validate_email;
|
||||||
|
|
||||||
use crate::domain::validate_password;
|
use crate::domain::validate_password;
|
||||||
use sqlx::{types::uuid, PgPool};
|
use sqlx::{types::uuid, PgPool};
|
||||||
|
|
||||||
pub async fn sign_up(gotrue_client: &Client, email: &str, password: &str) -> Result<(), AppError> {
|
pub async fn user_workspaces(
|
||||||
|
pg_pool: &PgPool,
|
||||||
|
uuid: &uuid::Uuid,
|
||||||
|
) -> Result<AfWorkspaces, AppError> {
|
||||||
|
let workspaces = storage::workspace::select_all_workspaces_owned(pg_pool, uuid).await?;
|
||||||
|
Ok(AfWorkspaces(workspaces))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn user_profile(
|
||||||
|
pg_pool: &PgPool,
|
||||||
|
uuid: &uuid::Uuid,
|
||||||
|
) -> Result<AfUserProfileView, AppError> {
|
||||||
|
let profile = storage::workspace::select_user_profile_view_by_uuid(pg_pool, uuid)
|
||||||
|
.await?
|
||||||
|
.ok_or(sqlx::Error::RowNotFound)?;
|
||||||
|
Ok(profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sign_up(
|
||||||
|
pg_pool: &PgPool,
|
||||||
|
gotrue_client: &Client,
|
||||||
|
email: &str,
|
||||||
|
password: &str,
|
||||||
|
) -> Result<(), AppError> {
|
||||||
validate_email_password(email, password)?;
|
validate_email_password(email, password)?;
|
||||||
let user = gotrue_client.sign_up(email, password).await??;
|
let user = gotrue_client.sign_up(email, password).await??;
|
||||||
tracing::info!("user sign up: {:?}", user);
|
if user.confirmed_at.is_some() {
|
||||||
|
storage::workspace::create_user_if_not_exists(pg_pool, uuid::Uuid::from_str(&user.id)?).await?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,44 +10,84 @@ lazy_static::lazy_static! {
|
||||||
pub static ref VALIDATION: Validation = Validation::new(Algorithm::HS256);
|
pub static ref VALIDATION: Validation = Validation::new(Algorithm::HS256);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct UserUuid(pub uuid::Uuid);
|
||||||
|
|
||||||
|
impl FromRequest for UserUuid {
|
||||||
|
type Error = actix_web::Error;
|
||||||
|
|
||||||
|
type Future = std::future::Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||||
|
let auth = get_auth_from_request(req);
|
||||||
|
match auth {
|
||||||
|
Ok(auth) => {
|
||||||
|
let sub = auth.claims.sub.ok_or(actix_web::error::ErrorUnauthorized(
|
||||||
|
"Invalid Authorization header, missing sub(uuid)",
|
||||||
|
));
|
||||||
|
match sub {
|
||||||
|
Ok(sub) => std::future::ready(Ok(UserUuid(uuid::Uuid::parse_str(&sub).unwrap()))),
|
||||||
|
Err(e) => std::future::ready(Err(e)),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => std::future::ready(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Authorization {
|
pub struct Authorization {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub claims: GoTrueJWTClaims,
|
pub claims: GoTrueJWTClaims,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Authorization {
|
||||||
|
pub fn uuid(&self) -> Option<String> {
|
||||||
|
self.claims.sub.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromRequest for Authorization {
|
impl FromRequest for Authorization {
|
||||||
type Error = actix_web::Error;
|
type Error = actix_web::Error;
|
||||||
|
|
||||||
type Future = std::future::Ready<Result<Self, Self::Error>>;
|
type Future = std::future::Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
|
||||||
|
let auth = get_auth_from_request(req);
|
||||||
|
match auth {
|
||||||
|
Ok(auth) => std::future::ready(Ok(auth)),
|
||||||
|
Err(e) => std::future::ready(Err(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_auth_from_request(req: &HttpRequest) -> Result<Authorization, actix_web::Error> {
|
||||||
let state = req.app_data::<Data<State>>().unwrap();
|
let state = req.app_data::<Data<State>>().unwrap();
|
||||||
let bearer = req.headers().get("Authorization");
|
let bearer = req.headers().get("Authorization");
|
||||||
match bearer {
|
match bearer {
|
||||||
None => std::future::ready(Err(actix_web::error::ErrorUnauthorized(
|
None => Err(actix_web::error::ErrorUnauthorized(
|
||||||
"No Authorization header",
|
"No Authorization header",
|
||||||
))),
|
)),
|
||||||
Some(bearer) => {
|
Some(bearer) => {
|
||||||
let bearer = bearer.to_str();
|
let bearer = bearer.to_str();
|
||||||
match bearer {
|
match bearer {
|
||||||
Err(e) => std::future::ready(Err(actix_web::error::ErrorUnauthorized(e))),
|
Err(e) => Err(actix_web::error::ErrorUnauthorized(e)),
|
||||||
Ok(bearer) => {
|
Ok(bearer) => {
|
||||||
let pair_opt = bearer.split_once("Bearer "); // Authorization: Bearer <token>
|
let pair_opt = bearer.split_once("Bearer "); // Authorization: Bearer <token>
|
||||||
match pair_opt {
|
match pair_opt {
|
||||||
None => std::future::ready(Err(actix_web::error::ErrorUnauthorized(
|
None => Err(actix_web::error::ErrorUnauthorized(
|
||||||
"Invalid Authorization header, missing Bearer",
|
"Invalid Authorization header, missing Bearer",
|
||||||
))),
|
)),
|
||||||
Some(pair) => {
|
Some(pair) => {
|
||||||
match GoTrueJWTClaims::verify(
|
match GoTrueJWTClaims::verify(
|
||||||
pair.1,
|
pair.1,
|
||||||
state.config.gotrue.jwt_secret.expose_secret().as_bytes(),
|
state.config.gotrue.jwt_secret.expose_secret().as_bytes(),
|
||||||
) {
|
) {
|
||||||
Err(e) => std::future::ready(Err(actix_web::error::ErrorUnauthorized(e))),
|
Err(e) => Err(actix_web::error::ErrorUnauthorized(e)),
|
||||||
Ok(t) => std::future::ready(Ok(Authorization {
|
Ok(t) => Ok(Authorization {
|
||||||
token: pair.1.to_string(),
|
token: pair.1.to_string(),
|
||||||
claims: t,
|
claims: t,
|
||||||
})),
|
}),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -55,35 +95,34 @@ impl FromRequest for Authorization {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct GoTrueJWTClaims {
|
pub struct GoTrueJWTClaims {
|
||||||
// JWT standard claims
|
// JWT standard claims
|
||||||
aud: Option<String>,
|
pub aud: Option<String>,
|
||||||
exp: Option<i64>,
|
pub exp: Option<i64>,
|
||||||
jti: Option<String>,
|
pub jti: Option<String>,
|
||||||
iat: Option<i64>,
|
pub iat: Option<i64>,
|
||||||
iss: Option<String>,
|
pub iss: Option<String>,
|
||||||
nbf: Option<i64>,
|
pub nbf: Option<i64>,
|
||||||
sub: Option<String>,
|
pub sub: Option<String>,
|
||||||
|
|
||||||
email: String,
|
pub email: String,
|
||||||
phone: String,
|
pub phone: String,
|
||||||
app_metadata: serde_json::Value,
|
pub app_metadata: serde_json::Value,
|
||||||
user_metadata: serde_json::Value,
|
pub user_metadata: serde_json::Value,
|
||||||
role: String,
|
pub role: String,
|
||||||
aal: Option<String>,
|
pub aal: Option<String>,
|
||||||
amr: Option<Vec<Amr>>,
|
pub amr: Option<Vec<Amr>>,
|
||||||
session_id: Option<String>,
|
pub session_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Amr {
|
pub struct Amr {
|
||||||
method: String,
|
pub method: String,
|
||||||
timestamp: u64,
|
pub timestamp: u64,
|
||||||
provider: Option<String>,
|
pub provider: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GoTrueJWTClaims {
|
impl GoTrueJWTClaims {
|
||||||
|
|
|
||||||
|
|
@ -58,5 +58,9 @@ async fn sign_in_success() {
|
||||||
let token = c.token().unwrap();
|
let token = c.token().unwrap();
|
||||||
assert!(token.user.confirmed_at.is_some());
|
assert!(token.user.confirmed_at.is_some());
|
||||||
|
|
||||||
// TODO: check that workspace is created for user
|
let workspaces = c.workspaces().await.unwrap();
|
||||||
|
assert!(!workspaces.0.is_empty());
|
||||||
|
let profile = c.profile().await.unwrap();
|
||||||
|
let latest_workspace = workspaces.get_latest(profile);
|
||||||
|
assert!(latest_workspace.is_some());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue