feature/project-new-structure
claude/smbcloud-github-app-k9641p
development
feature/apps
feature/build-release
feature/ci-docs
feature/ci-ruby-gems
feature/cicd
feature/completion
feature/dist-nuget
feature/fix-ci-sdk-pypi-rubygem
feature/fix-clippy-on-stable-channel
feature/fix-deployment-script
feature/github-email
feature/gresiq-v1
feature/project-new-structure
feature/rsync-deploy
feature/ruby-runner
feature/sdk-npm
feature/sdk-workflows
feature/smbcloud-cli-release-npm
feature/smbcloud-deploy
feature/wasm
main
release/v0.7.4
| 1 | uniffi::setup_scaffolding!(); |
| 2 | |
| 3 | use smbcloud_auth_sdk::{ |
| 4 | apple, client_credentials::ClientCredentials, login, logout, me, oidc, remove, signup, |
| 5 | }; |
| 6 | use smbcloud_model::error_codes::ErrorResponse; |
| 7 | use smbcloud_network::environment::Environment; |
| 8 | |
| 9 | // ── Error ──────────────────────────────────────────────────────────────────── |
| 10 | |
| 11 | #[derive(Debug, thiserror::Error, uniffi::Error)] |
| 12 | pub enum SmbCloudAuthError { |
| 13 | #[error("[{error_code}] {message}")] |
| 14 | Api { error_code: i32, message: String }, |
| 15 | } |
| 16 | |
| 17 | impl From<ErrorResponse> for SmbCloudAuthError { |
| 18 | fn from(error: ErrorResponse) -> Self { |
| 19 | match error { |
| 20 | ErrorResponse::Error { |
| 21 | error_code, |
| 22 | message, |
| 23 | } => Self::Api { |
| 24 | error_code: error_code as i32, |
| 25 | message, |
| 26 | }, |
| 27 | } |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | // ── Environment ────────────────────────────────────────────────────────────── |
| 32 | |
| 33 | #[derive(uniffi::Enum)] |
| 34 | pub enum SmbCloudEnvironment { |
| 35 | Dev, |
| 36 | Production, |
| 37 | } |
| 38 | |
| 39 | impl From<SmbCloudEnvironment> for Environment { |
| 40 | fn from(environment: SmbCloudEnvironment) -> Self { |
| 41 | match environment { |
| 42 | SmbCloudEnvironment::Dev => Self::Dev, |
| 43 | SmbCloudEnvironment::Production => Self::Production, |
| 44 | } |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | // ── Account status ─────────────────────────────────────────────────────────── |
| 49 | |
| 50 | #[derive(uniffi::Enum)] |
| 51 | pub enum SmbCloudAccountStatus { |
| 52 | NotFound, |
| 53 | Ready { access_token: String }, |
| 54 | Incomplete { error_code: u32 }, |
| 55 | } |
| 56 | |
| 57 | impl From<smbcloud_model::login::AccountStatus> for SmbCloudAccountStatus { |
| 58 | fn from(status: smbcloud_model::login::AccountStatus) -> Self { |
| 59 | match status { |
| 60 | smbcloud_model::login::AccountStatus::NotFound => Self::NotFound, |
| 61 | smbcloud_model::login::AccountStatus::Ready { access_token } => { |
| 62 | Self::Ready { access_token } |
| 63 | } |
| 64 | smbcloud_model::login::AccountStatus::Incomplete { status } => Self::Incomplete { |
| 65 | error_code: status as u32, |
| 66 | }, |
| 67 | } |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | // ── User ───────────────────────────────────────────────────────────────────── |
| 72 | |
| 73 | #[derive(uniffi::Record)] |
| 74 | pub struct SmbCloudUser { |
| 75 | pub id: i32, |
| 76 | pub email: String, |
| 77 | pub created_at: String, |
| 78 | pub updated_at: String, |
| 79 | } |
| 80 | |
| 81 | impl From<smbcloud_model::account::User> for SmbCloudUser { |
| 82 | fn from(user: smbcloud_model::account::User) -> Self { |
| 83 | Self { |
| 84 | id: user.id, |
| 85 | email: user.email, |
| 86 | created_at: user.created_at.to_rfc3339(), |
| 87 | updated_at: user.updated_at.to_rfc3339(), |
| 88 | } |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | // ── Signup result ──────────────────────────────────────────────────────────── |
| 93 | |
| 94 | #[derive(uniffi::Record)] |
| 95 | pub struct SmbCloudSignupResult { |
| 96 | pub code: Option<i32>, |
| 97 | pub message: String, |
| 98 | pub user_id: Option<i32>, |
| 99 | pub user_email: Option<String>, |
| 100 | pub user_created_at: Option<String>, |
| 101 | } |
| 102 | |
| 103 | impl From<smbcloud_model::signup::SignupResult> for SmbCloudSignupResult { |
| 104 | fn from(result: smbcloud_model::signup::SignupResult) -> Self { |
| 105 | Self { |
| 106 | code: result.code, |
| 107 | message: result.message, |
| 108 | user_id: result.data.as_ref().map(|data| data.id), |
| 109 | user_email: result.data.as_ref().map(|data| data.email.clone()), |
| 110 | user_created_at: result.data.map(|data| data.created_at), |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | // ── OIDC types ─────────────────────────────────────────────────────────────── |
| 116 | |
| 117 | #[derive(uniffi::Record)] |
| 118 | pub struct SmbCloudAuthorizationRequest { |
| 119 | pub authorize_url: String, |
| 120 | pub redirect_uri: String, |
| 121 | pub state: String, |
| 122 | pub code_verifier: String, |
| 123 | } |
| 124 | |
| 125 | impl From<oidc::AuthorizationRequest> for SmbCloudAuthorizationRequest { |
| 126 | fn from(request: oidc::AuthorizationRequest) -> Self { |
| 127 | Self { |
| 128 | authorize_url: request.authorize_url, |
| 129 | redirect_uri: request.redirect_uri, |
| 130 | state: request.state, |
| 131 | code_verifier: request.code_verifier, |
| 132 | } |
| 133 | } |
| 134 | } |
| 135 | |
| 136 | #[derive(uniffi::Record)] |
| 137 | pub struct SmbCloudCallbackPayload { |
| 138 | pub code: String, |
| 139 | pub state: String, |
| 140 | } |
| 141 | |
| 142 | impl From<oidc::CallbackPayload> for SmbCloudCallbackPayload { |
| 143 | fn from(payload: oidc::CallbackPayload) -> Self { |
| 144 | Self { |
| 145 | code: payload.code, |
| 146 | state: payload.state, |
| 147 | } |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | #[derive(uniffi::Record)] |
| 152 | pub struct SmbCloudTokenResponse { |
| 153 | pub access_token: String, |
| 154 | pub token_type: String, |
| 155 | pub expires_in: Option<i32>, |
| 156 | pub refresh_token: Option<String>, |
| 157 | pub scope: Option<String>, |
| 158 | pub id_token: Option<String>, |
| 159 | } |
| 160 | |
| 161 | impl From<oidc::TokenResponse> for SmbCloudTokenResponse { |
| 162 | fn from(response: oidc::TokenResponse) -> Self { |
| 163 | Self { |
| 164 | access_token: response.access_token, |
| 165 | token_type: response.token_type, |
| 166 | expires_in: response.expires_in, |
| 167 | refresh_token: response.refresh_token, |
| 168 | scope: response.scope, |
| 169 | id_token: response.id_token, |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | #[derive(uniffi::Record)] |
| 175 | pub struct SmbCloudUserInfo { |
| 176 | pub sub: String, |
| 177 | pub email: Option<String>, |
| 178 | pub email_verified: Option<bool>, |
| 179 | pub tenant_id: Option<u64>, |
| 180 | pub tenant_slug: Option<String>, |
| 181 | } |
| 182 | |
| 183 | impl From<oidc::UserInfo> for SmbCloudUserInfo { |
| 184 | fn from(info: oidc::UserInfo) -> Self { |
| 185 | Self { |
| 186 | sub: info.sub, |
| 187 | email: info.email, |
| 188 | email_verified: info.email_verified, |
| 189 | tenant_id: info.tenant_id, |
| 190 | tenant_slug: info.tenant_slug, |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | // ── Apple Sign-In types ────────────────────────────────────────────────────── |
| 196 | |
| 197 | #[derive(uniffi::Record)] |
| 198 | pub struct SmbCloudAppleAuthorizationRequest { |
| 199 | pub authorize_url: String, |
| 200 | pub redirect_uri: String, |
| 201 | pub state: String, |
| 202 | } |
| 203 | |
| 204 | impl From<apple::AppleAuthorizationRequest> for SmbCloudAppleAuthorizationRequest { |
| 205 | fn from(request: apple::AppleAuthorizationRequest) -> Self { |
| 206 | Self { |
| 207 | authorize_url: request.authorize_url, |
| 208 | redirect_uri: request.redirect_uri, |
| 209 | state: request.state, |
| 210 | } |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | #[derive(uniffi::Record)] |
| 215 | pub struct SmbCloudAppleAuthSession { |
| 216 | pub access_token: String, |
| 217 | pub refresh_token: Option<String>, |
| 218 | pub email: Option<String>, |
| 219 | pub name: Option<String>, |
| 220 | pub provider: String, |
| 221 | pub provider_account_id: String, |
| 222 | pub state: Option<String>, |
| 223 | } |
| 224 | |
| 225 | impl From<apple::AppleAuthSession> for SmbCloudAppleAuthSession { |
| 226 | fn from(session: apple::AppleAuthSession) -> Self { |
| 227 | Self { |
| 228 | access_token: session.access_token, |
| 229 | refresh_token: session.refresh_token, |
| 230 | email: session.email, |
| 231 | name: session.name, |
| 232 | provider: session.provider, |
| 233 | provider_account_id: session.provider_account_id, |
| 234 | state: session.state, |
| 235 | } |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | // ── Main client object ────────────────────────────────────────────────────── |
| 240 | |
| 241 | #[derive(uniffi::Object)] |
| 242 | pub struct SmbCloudAuth { |
| 243 | environment: Environment, |
| 244 | app_id: String, |
| 245 | app_secret: String, |
| 246 | } |
| 247 | |
| 248 | impl SmbCloudAuth { |
| 249 | fn credentials(&self) -> ClientCredentials<'_> { |
| 250 | ClientCredentials { |
| 251 | app_id: &self.app_id, |
| 252 | app_secret: &self.app_secret, |
| 253 | } |
| 254 | } |
| 255 | } |
| 256 | |
| 257 | #[uniffi::export] |
| 258 | impl SmbCloudAuth { |
| 259 | #[uniffi::constructor] |
| 260 | pub fn new(environment: SmbCloudEnvironment, app_id: String, app_secret: String) -> Self { |
| 261 | Self { |
| 262 | environment: environment.into(), |
| 263 | app_id, |
| 264 | app_secret, |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | // ── Email/password auth ────────────────────────────────────────────── |
| 269 | |
| 270 | pub async fn login( |
| 271 | &self, |
| 272 | email: String, |
| 273 | password: String, |
| 274 | ) -> Result<SmbCloudAccountStatus, SmbCloudAuthError> { |
| 275 | login::login_with_client(self.environment, self.credentials(), email, password) |
| 276 | .await |
| 277 | .map(Into::into) |
| 278 | .map_err(Into::into) |
| 279 | } |
| 280 | |
| 281 | pub async fn signup( |
| 282 | &self, |
| 283 | email: String, |
| 284 | password: String, |
| 285 | ) -> Result<SmbCloudSignupResult, SmbCloudAuthError> { |
| 286 | signup::signup_with_client(self.environment, self.credentials(), email, password) |
| 287 | .await |
| 288 | .map(Into::into) |
| 289 | .map_err(Into::into) |
| 290 | } |
| 291 | |
| 292 | pub async fn logout(&self, access_token: String) -> Result<(), SmbCloudAuthError> { |
| 293 | logout::logout_with_client(self.environment, self.credentials(), access_token) |
| 294 | .await |
| 295 | .map_err(Into::into) |
| 296 | } |
| 297 | |
| 298 | pub async fn me(&self, access_token: String) -> Result<SmbCloudUser, SmbCloudAuthError> { |
| 299 | me::me_with_client(self.environment, self.credentials(), &access_token) |
| 300 | .await |
| 301 | .map(Into::into) |
| 302 | .map_err(Into::into) |
| 303 | } |
| 304 | |
| 305 | pub async fn remove_account(&self, access_token: String) -> Result<(), SmbCloudAuthError> { |
| 306 | remove::remove_with_client(self.environment, self.credentials(), &access_token) |
| 307 | .await |
| 308 | .map_err(Into::into) |
| 309 | } |
| 310 | |
| 311 | // ── Apple Sign-In ──────────────────────────────────────────────────── |
| 312 | |
| 313 | pub fn build_apple_authorization_request( |
| 314 | &self, |
| 315 | redirect_uri: String, |
| 316 | state: Option<String>, |
| 317 | ) -> Result<SmbCloudAppleAuthorizationRequest, SmbCloudAuthError> { |
| 318 | apple::build_authorization_request_with_client( |
| 319 | self.environment, |
| 320 | self.credentials(), |
| 321 | redirect_uri, |
| 322 | state, |
| 323 | ) |
| 324 | .map(Into::into) |
| 325 | .map_err(Into::into) |
| 326 | } |
| 327 | |
| 328 | pub fn parse_apple_callback_url( |
| 329 | &self, |
| 330 | callback_url: String, |
| 331 | expected_state: Option<String>, |
| 332 | ) -> Result<SmbCloudAppleAuthSession, SmbCloudAuthError> { |
| 333 | apple::parse_callback_url(&callback_url, expected_state.as_deref()) |
| 334 | .map(Into::into) |
| 335 | .map_err(Into::into) |
| 336 | } |
| 337 | |
| 338 | // ── OIDC ───────────────────────────────────────────────────────────── |
| 339 | |
| 340 | pub fn build_oidc_authorization_request( |
| 341 | &self, |
| 342 | oidc_client_id: String, |
| 343 | redirect_uri: String, |
| 344 | ) -> Result<SmbCloudAuthorizationRequest, SmbCloudAuthError> { |
| 345 | oidc::build_authorization_request(self.environment, &oidc_client_id, redirect_uri) |
| 346 | .map(Into::into) |
| 347 | .map_err(Into::into) |
| 348 | } |
| 349 | |
| 350 | pub fn parse_oidc_callback_url( |
| 351 | &self, |
| 352 | callback_url: String, |
| 353 | ) -> Result<SmbCloudCallbackPayload, SmbCloudAuthError> { |
| 354 | oidc::parse_callback_url(&callback_url) |
| 355 | .map(Into::into) |
| 356 | .map_err(Into::into) |
| 357 | } |
| 358 | |
| 359 | pub async fn exchange_oidc_code( |
| 360 | &self, |
| 361 | oidc_client_id: String, |
| 362 | redirect_uri: String, |
| 363 | code: String, |
| 364 | code_verifier: String, |
| 365 | ) -> Result<SmbCloudTokenResponse, SmbCloudAuthError> { |
| 366 | oidc::exchange_code( |
| 367 | self.environment, |
| 368 | &oidc_client_id, |
| 369 | &redirect_uri, |
| 370 | &code, |
| 371 | &code_verifier, |
| 372 | ) |
| 373 | .await |
| 374 | .map(Into::into) |
| 375 | .map_err(Into::into) |
| 376 | } |
| 377 | |
| 378 | pub async fn get_oidc_userinfo( |
| 379 | &self, |
| 380 | access_token: String, |
| 381 | tenant_id: Option<String>, |
| 382 | ) -> Result<SmbCloudUserInfo, SmbCloudAuthError> { |
| 383 | oidc::get_userinfo(self.environment, &access_token, tenant_id.as_deref()) |
| 384 | .await |
| 385 | .map(Into::into) |
| 386 | .map_err(Into::into) |
| 387 | } |
| 388 | } |