@smbcloud / cli / commits / 2cad3eb

Add smbcloud-auth-sdk-apple crate with UniFFI Apple bindings

- Introduce new crate `smbcloud-auth-sdk-apple` for Apple platform bindings - Implement UniFFI scaffolding and FFI-safe types for Auth SDK - Expose async and sync methods for login, signup, logout, OIDC, and Apple Sign-In - Add workspace dependency on `uniffi` - Make `Data` fields in `account.rs` public for FFI access

Seto Elkahfi committed May 16, 2026 at 16:21 UTC 2cad3eb45be92a7649107ed156f668f3eff4b501
6 files changed +774 -7
Cargo.lock
+350 -4
@@ -104,6 +104,61 @@ version = "1.0.102"
104 source = "registry+https://github.com/rust-lang/crates.io-index"
105 checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
106
107 +[[package]]
108 +name = "askama"
109 +version = "0.13.1"
110 +source = "registry+https://github.com/rust-lang/crates.io-index"
111 +checksum = "5d4744ed2eef2645831b441d8f5459689ade2ab27c854488fbab1fbe94fce1a7"
112 +dependencies = [
113 + "askama_derive",
114 + "itoa",
115 + "percent-encoding",
116 + "serde",
117 + "serde_json",
118 +]
119 +
120 +[[package]]
121 +name = "askama_derive"
122 +version = "0.13.1"
123 +source = "registry+https://github.com/rust-lang/crates.io-index"
124 +checksum = "d661e0f57be36a5c14c48f78d09011e67e0cb618f269cca9f2fd8d15b68c46ac"
125 +dependencies = [
126 + "askama_parser",
127 + "basic-toml",
128 + "memchr",
129 + "proc-macro2",
130 + "quote",
131 + "rustc-hash",
132 + "serde",
133 + "serde_derive",
134 + "syn 2.0.117",
135 +]
136 +
137 +[[package]]
138 +name = "askama_parser"
139 +version = "0.13.0"
140 +source = "registry+https://github.com/rust-lang/crates.io-index"
141 +checksum = "cf315ce6524c857bb129ff794935cf6d42c82a6cff60526fe2a63593de4d0d4f"
142 +dependencies = [
143 + "memchr",
144 + "serde",
145 + "serde_derive",
146 + "winnow",
147 +]
148 +
149 +[[package]]
150 +name = "async-compat"
151 +version = "0.2.5"
152 +source = "registry+https://github.com/rust-lang/crates.io-index"
153 +checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590"
154 +dependencies = [
155 + "futures-core",
156 + "futures-io",
157 + "once_cell",
158 + "pin-project-lite",
159 + "tokio",
160 +]
161 +
162 [[package]]
163 name = "async-trait"
164 version = "0.1.89"
@@ -144,6 +199,15 @@ version = "0.22.1"
199 source = "registry+https://github.com/rust-lang/crates.io-index"
200 checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
201
202 +[[package]]
203 +name = "basic-toml"
204 +version = "0.1.10"
205 +source = "registry+https://github.com/rust-lang/crates.io-index"
206 +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
207 +dependencies = [
208 + "serde",
209 +]
210 +
211 [[package]]
212 name = "bitflags"
213 version = "1.3.2"
@@ -198,6 +262,38 @@ version = "1.11.1"
262 source = "registry+https://github.com/rust-lang/crates.io-index"
263 checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
264
265 +[[package]]
266 +name = "camino"
267 +version = "1.2.2"
268 +source = "registry+https://github.com/rust-lang/crates.io-index"
269 +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48"
270 +dependencies = [
271 + "serde_core",
272 +]
273 +
274 +[[package]]
275 +name = "cargo-platform"
276 +version = "0.1.9"
277 +source = "registry+https://github.com/rust-lang/crates.io-index"
278 +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
279 +dependencies = [
280 + "serde",
281 +]
282 +
283 +[[package]]
284 +name = "cargo_metadata"
285 +version = "0.19.2"
286 +source = "registry+https://github.com/rust-lang/crates.io-index"
287 +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
288 +dependencies = [
289 + "camino",
290 + "cargo-platform",
291 + "semver",
292 + "serde",
293 + "serde_json",
294 + "thiserror 2.0.18",
295 +]
296 +
297 [[package]]
298 name = "cassowary"
299 version = "0.3.0"
@@ -272,7 +368,7 @@ dependencies = [
368 "atty",
369 "bitflags 1.3.2",
370 "strsim 0.8.0",
275 - "textwrap",
371 + "textwrap 0.11.0",
372 "unicode-width 0.1.14",
373 "vec_map",
374 ]
@@ -672,6 +768,15 @@ dependencies = [
768 "percent-encoding",
769 ]
770
771 +[[package]]
772 +name = "fs-err"
773 +version = "2.11.0"
774 +source = "registry+https://github.com/rust-lang/crates.io-index"
775 +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41"
776 +dependencies = [
777 + "autocfg",
778 +]
779 +
780 [[package]]
781 name = "futures-channel"
782 version = "0.3.32"
@@ -688,6 +793,12 @@ version = "0.3.32"
793 source = "registry+https://github.com/rust-lang/crates.io-index"
794 checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
795
796 +[[package]]
797 +name = "futures-io"
798 +version = "0.3.32"
799 +source = "registry+https://github.com/rust-lang/crates.io-index"
800 +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
801 +
802 [[package]]
803 name = "futures-sink"
804 version = "0.3.32"
@@ -802,6 +913,23 @@ dependencies = [
913 "url",
914 ]
915
916 +[[package]]
917 +name = "glob"
918 +version = "0.3.3"
919 +source = "registry+https://github.com/rust-lang/crates.io-index"
920 +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
921 +
922 +[[package]]
923 +name = "goblin"
924 +version = "0.8.2"
925 +source = "registry+https://github.com/rust-lang/crates.io-index"
926 +checksum = "1b363a30c165f666402fe6a3024d3bec7ebc898f96a4a23bd1c99f8dbf3f4f47"
927 +dependencies = [
928 + "log",
929 + "plain",
930 + "scroll",
931 +]
932 +
933 [[package]]
934 name = "hashbrown"
935 version = "0.15.5"
@@ -1420,6 +1548,12 @@ dependencies = [
1548 "autocfg",
1549 ]
1550
1551 +[[package]]
1552 +name = "minimal-lexical"
1553 +version = "0.2.1"
1554 +source = "registry+https://github.com/rust-lang/crates.io-index"
1555 +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
1556 +
1557 [[package]]
1558 name = "mio"
1559 version = "1.2.0"
@@ -1449,6 +1583,16 @@ dependencies = [
1583 "tempfile",
1584 ]
1585
1586 +[[package]]
1587 +name = "nom"
1588 +version = "7.1.3"
1589 +source = "registry+https://github.com/rust-lang/crates.io-index"
1590 +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
1591 +dependencies = [
1592 + "memchr",
1593 + "minimal-lexical",
1594 +]
1595 +
1596 [[package]]
1597 name = "nu-ansi-term"
1598 version = "0.50.3"
@@ -1654,7 +1798,7 @@ version = "0.13.1"
1798 source = "registry+https://github.com/rust-lang/crates.io-index"
1799 checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
1800 dependencies = [
1657 - "siphasher",
1801 + "siphasher 1.0.2",
1802 ]
1803
1804 [[package]]
@@ -1669,6 +1813,12 @@ version = "0.3.33"
1813 source = "registry+https://github.com/rust-lang/crates.io-index"
1814 checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
1815
1816 +[[package]]
1817 +name = "plain"
1818 +version = "0.2.3"
1819 +source = "registry+https://github.com/rust-lang/crates.io-index"
1820 +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
1821 +
1822 [[package]]
1823 name = "portable-atomic"
1824 version = "1.13.1"
@@ -2250,6 +2400,26 @@ version = "1.2.0"
2400 source = "registry+https://github.com/rust-lang/crates.io-index"
2401 checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
2402
2403 +[[package]]
2404 +name = "scroll"
2405 +version = "0.12.0"
2406 +source = "registry+https://github.com/rust-lang/crates.io-index"
2407 +checksum = "6ab8598aa408498679922eff7fa985c25d58a90771bd6be794434c5277eab1a6"
2408 +dependencies = [
2409 + "scroll_derive",
2410 +]
2411 +
2412 +[[package]]
2413 +name = "scroll_derive"
2414 +version = "0.12.1"
2415 +source = "registry+https://github.com/rust-lang/crates.io-index"
2416 +checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d"
2417 +dependencies = [
2418 + "proc-macro2",
2419 + "quote",
2420 + "syn 2.0.117",
2421 +]
2422 +
2423 [[package]]
2424 name = "security-framework"
2425 version = "3.7.0"
@@ -2278,6 +2448,10 @@ name = "semver"
2448 version = "1.0.28"
2449 source = "registry+https://github.com/rust-lang/crates.io-index"
2450 checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
2451 +dependencies = [
2452 + "serde",
2453 + "serde_core",
2454 +]
2455
2456 [[package]]
2457 name = "serde"
@@ -2439,6 +2613,12 @@ dependencies = [
2613 "libc",
2614 ]
2615
2616 +[[package]]
2617 +name = "siphasher"
2618 +version = "0.3.11"
2619 +source = "registry+https://github.com/rust-lang/crates.io-index"
2620 +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
2621 +
2622 [[package]]
2623 name = "siphasher"
2624 version = "1.0.2"
@@ -2457,6 +2637,12 @@ version = "1.15.1"
2637 source = "registry+https://github.com/rust-lang/crates.io-index"
2638 checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
2639
2640 +[[package]]
2641 +name = "smawk"
2642 +version = "0.3.2"
2643 +source = "registry+https://github.com/rust-lang/crates.io-index"
2644 +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
2645 +
2646 [[package]]
2647 name = "smbcloud-auth"
2648 version = "0.4.1"
@@ -2487,6 +2673,17 @@ dependencies = [
2673 "uuid",
2674 ]
2675
2676 +[[package]]
2677 +name = "smbcloud-auth-sdk-apple"
2678 +version = "0.4.1"
2679 +dependencies = [
2680 + "smbcloud-auth-sdk",
2681 + "smbcloud-model",
2682 + "smbcloud-network",
2683 + "thiserror 2.0.18",
2684 + "uniffi",
2685 +]
2686 +
2687 [[package]]
2688 name = "smbcloud-auth-sdk-py"
2689 version = "0.4.1"
@@ -2550,7 +2747,7 @@ dependencies = [
2747 "tempfile",
2748 "thiserror 2.0.18",
2749 "tokio",
2553 - "toml",
2750 + "toml 0.8.23",
2751 "tracing",
2752 "tracing-bunyan-formatter",
2753 "tracing-subscriber",
@@ -2668,7 +2865,7 @@ dependencies = [
2865 "serde",
2866 "serde_json",
2867 "smbcloud-model",
2671 - "toml",
2868 + "toml 0.8.23",
2869 ]
2870
2871 [[package]]
@@ -2931,6 +3128,15 @@ dependencies = [
3128 "unicode-width 0.1.14",
3129 ]
3130
3131 +[[package]]
3132 +name = "textwrap"
3133 +version = "0.16.2"
3134 +source = "registry+https://github.com/rust-lang/crates.io-index"
3135 +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
3136 +dependencies = [
3137 + "smawk",
3138 +]
3139 +
3140 [[package]]
3141 name = "thiserror"
3142 version = "1.0.69"
@@ -3123,6 +3329,15 @@ dependencies = [
3329 "tokio",
3330 ]
3331
3332 +[[package]]
3333 +name = "toml"
3334 +version = "0.5.11"
3335 +source = "registry+https://github.com/rust-lang/crates.io-index"
3336 +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
3337 +dependencies = [
3338 + "serde",
3339 +]
3340 +
3341 [[package]]
3342 name = "toml"
3343 version = "0.8.23"
@@ -3396,6 +3611,128 @@ version = "0.2.6"
3611 source = "registry+https://github.com/rust-lang/crates.io-index"
3612 checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
3613
3614 +[[package]]
3615 +name = "uniffi"
3616 +version = "0.29.5"
3617 +source = "registry+https://github.com/rust-lang/crates.io-index"
3618 +checksum = "3291800a6b06569f7d3e15bdb6dc235e0f0c8bd3eb07177f430057feb076415f"
3619 +dependencies = [
3620 + "anyhow",
3621 + "camino",
3622 + "cargo_metadata",
3623 + "clap 4.6.1",
3624 + "uniffi_bindgen",
3625 + "uniffi_core",
3626 + "uniffi_macros",
3627 + "uniffi_pipeline",
3628 +]
3629 +
3630 +[[package]]
3631 +name = "uniffi_bindgen"
3632 +version = "0.29.5"
3633 +source = "registry+https://github.com/rust-lang/crates.io-index"
3634 +checksum = "a04b99fa7796eaaa7b87976a0dbdd1178dc1ee702ea00aca2642003aef9b669e"
3635 +dependencies = [
3636 + "anyhow",
3637 + "askama",
3638 + "camino",
3639 + "cargo_metadata",
3640 + "fs-err",
3641 + "glob",
3642 + "goblin",
3643 + "heck 0.5.0",
3644 + "indexmap",
3645 + "once_cell",
3646 + "serde",
3647 + "tempfile",
3648 + "textwrap 0.16.2",
3649 + "toml 0.5.11",
3650 + "uniffi_internal_macros",
3651 + "uniffi_meta",
3652 + "uniffi_pipeline",
3653 + "uniffi_udl",
3654 +]
3655 +
3656 +[[package]]
3657 +name = "uniffi_core"
3658 +version = "0.29.5"
3659 +source = "registry+https://github.com/rust-lang/crates.io-index"
3660 +checksum = "f38a9a27529ccff732f8efddb831b65b1e07f7dea3fd4cacd4a35a8c4b253b98"
3661 +dependencies = [
3662 + "anyhow",
3663 + "async-compat",
3664 + "bytes",
3665 + "once_cell",
3666 + "static_assertions",
3667 +]
3668 +
3669 +[[package]]
3670 +name = "uniffi_internal_macros"
3671 +version = "0.29.5"
3672 +source = "registry+https://github.com/rust-lang/crates.io-index"
3673 +checksum = "09acd2ce09c777dd65ee97c251d33c8a972afc04873f1e3b21eb3492ade16933"
3674 +dependencies = [
3675 + "anyhow",
3676 + "indexmap",
3677 + "proc-macro2",
3678 + "quote",
3679 + "syn 2.0.117",
3680 +]
3681 +
3682 +[[package]]
3683 +name = "uniffi_macros"
3684 +version = "0.29.5"
3685 +source = "registry+https://github.com/rust-lang/crates.io-index"
3686 +checksum = "5596f178c4f7aafa1a501c4e0b96236a96bc2ef92bdb453d83e609dad0040152"
3687 +dependencies = [
3688 + "camino",
3689 + "fs-err",
3690 + "once_cell",
3691 + "proc-macro2",
3692 + "quote",
3693 + "serde",
3694 + "syn 2.0.117",
3695 + "toml 0.5.11",
3696 + "uniffi_meta",
3697 +]
3698 +
3699 +[[package]]
3700 +name = "uniffi_meta"
3701 +version = "0.29.5"
3702 +source = "registry+https://github.com/rust-lang/crates.io-index"
3703 +checksum = "beadc1f460eb2e209263c49c4f5b19e9a02e00a3b2b393f78ad10d766346ecff"
3704 +dependencies = [
3705 + "anyhow",
3706 + "siphasher 0.3.11",
3707 + "uniffi_internal_macros",
3708 + "uniffi_pipeline",
3709 +]
3710 +
3711 +[[package]]
3712 +name = "uniffi_pipeline"
3713 +version = "0.29.5"
3714 +source = "registry+https://github.com/rust-lang/crates.io-index"
3715 +checksum = "dd76b3ac8a2d964ca9fce7df21c755afb4c77b054a85ad7a029ad179cc5abb8a"
3716 +dependencies = [
3717 + "anyhow",
3718 + "heck 0.5.0",
3719 + "indexmap",
3720 + "tempfile",
3721 + "uniffi_internal_macros",
3722 +]
3723 +
3724 +[[package]]
3725 +name = "uniffi_udl"
3726 +version = "0.29.5"
3727 +source = "registry+https://github.com/rust-lang/crates.io-index"
3728 +checksum = "4319cf905911d70d5b97ce0f46f101619a22e9a189c8c46d797a9955e9233716"
3729 +dependencies = [
3730 + "anyhow",
3731 + "textwrap 0.16.2",
3732 + "uniffi_meta",
3733 + "weedle2",
3734 +]
3735 +
3736 [[package]]
3737 name = "unindent"
3738 version = "0.2.4"
@@ -3652,6 +3989,15 @@ dependencies = [
3989 "rustls-pki-types",
3990 ]
3991
3992 +[[package]]
3993 +name = "weedle2"
3994 +version = "5.0.0"
3995 +source = "registry+https://github.com/rust-lang/crates.io-index"
3996 +checksum = "998d2c24ec099a87daf9467808859f9d82b61f1d9c9701251aea037f514eae0e"
3997 +dependencies = [
3998 + "nom",
3999 +]
4000 +
4001 [[package]]
4002 name = "whoami"
4003 version = "2.1.2"
Cargo.toml
+1
@@ -44,6 +44,7 @@ thiserror = "2.0"
44 tokio = "1.44"
45 tokio-postgres = "0.7"
46 toml = "0.8"
47 +uniffi = "0.29"
48 tracing = "0.1.35"
49 tracing-bunyan-formatter = "0.3.3"
50 tracing-subscriber = "0.3.14"
crates/smbcloud-auth-sdk-apple/Cargo.toml new
+29
@@ -0,0 +1,29 @@
1 +[package]
2 +name = "smbcloud-auth-sdk-apple"
3 +version = "0.4.1"
4 +edition = "2024"
5 +authors = ["Seto Elkahfi <hej@setoelkahfi.se>"]
6 +description = "Apple platform bindings for the smbCloud Auth SDK."
7 +license = "Apache-2.0"
8 +repository = "https://github.com/smbcloudXYZ/smbcloud-cli"
9 +publish = false
10 +
11 +[lib]
12 +name = "smbcloud_auth"
13 +crate-type = ["lib", "cdylib", "staticlib"]
14 +path = "src/smbcloud_auth_sdk_apple.rs"
15 +
16 +[features]
17 +bindgen = ["uniffi/cli"]
18 +
19 +[[bin]]
20 +name = "uniffi-bindgen"
21 +path = "uniffi-bindgen.rs"
22 +required-features = ["bindgen"]
23 +
24 +[dependencies]
25 +smbcloud-auth-sdk = { workspace = true }
26 +smbcloud-model = { workspace = true }
27 +smbcloud-network = { workspace = true }
28 +thiserror = { workspace = true }
29 +uniffi = { workspace = true, features = ["tokio"] }
crates/smbcloud-auth-sdk-apple/src/smbcloud_auth_sdk_apple.rs new
+388
@@ -0,0 +1,388 @@
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 +}
crates/smbcloud-auth-sdk-apple/uniffi-bindgen.rs new
+3
@@ -0,0 +1,3 @@
1 +fn main() {
2 + uniffi::uniffi_bindgen_main()
3 +}
crates/smbcloud-model/src/account.rs
+3 -3
@@ -32,9 +32,9 @@ pub struct Status {
32 #[derive(Debug, Serialize, Deserialize)]
33 #[tsync]
34 pub struct Data {
35 - id: i32,
36 - email: String,
37 - created_at: String,
35 + pub id: i32,
36 + pub email: String,
37 + pub created_at: String,
38 }
39
40 /// This is smbCloud authorization model