Skip to content

feat(ffi): Allow passing in a SecretsBundle after logging in#6212

Draft
poljar wants to merge 8 commits intomainfrom
poljar/login/secret-bundle-import
Draft

feat(ffi): Allow passing in a SecretsBundle after logging in#6212
poljar wants to merge 8 commits intomainfrom
poljar/login/secret-bundle-import

Conversation

@poljar
Copy link
Contributor

@poljar poljar commented Feb 25, 2026

This PR allows people to get a secrets bundle out of band and import it after logging in a new client.

Mainly targeted to support the Element Classic -> Element X migration.

This closes #6087.

  • I've documented the public API Changes in the appropriate CHANGELOG.md files.
  • I've read the CONTRIBUTING.md file, notably the sections about Pull requests, Commit message format, and AI policy.
  • This PR was made with the help of AI.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 25, 2026

Merging this PR will improve performance by 67.96%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 49 untouched benchmarks

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation Restore session [memory store] 282 ms 167.9 ms +67.96%

Comparing poljar/login/secret-bundle-import (6849bad) with main (acda2e8)

Open in CodSpeed

@bmarty
Copy link
Contributor

bmarty commented Feb 25, 2026

Hello, thanks, I am testing the updated API using EXA.

@poljar
Copy link
Contributor Author

poljar commented Feb 25, 2026

Hello, thanks, I am testing the updated API using EXA.

Ah, thanks for testing.

}

impl SecretsBundle {
pub fn bundle_from_str(bundle: &str) -> Result<Arc<Self>, ClientError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like the method bundle_from_str is not exposed in the FFI layer, can you double check @poljar ? Thanks! An alternative would be that the secrets parameter added to login_with_oidc_callback and login would be of type optional String.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, that's true.

An alternative would be that the secrets parameter added to login_with_oidc_callback and login would be of type optional String.

That would complicate things for the codepath where we want to get the secrets bundle from a database.

I exported the methods now.

@poljar poljar force-pushed the poljar/login/secret-bundle-import branch from 0f34947 to f75470e Compare February 25, 2026 15:08
@codecov
Copy link

codecov bot commented Feb 25, 2026

Codecov Report

❌ Patch coverage is 9.67742% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.89%. Comparing base (791f4bc) to head (6849bad).
⚠️ Report is 30 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
crates/matrix-sdk/src/encryption/mod.rs 6.89% 27 Missing ⚠️
...atrix-sdk/src/authentication/oauth/qrcode/login.rs 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6212      +/-   ##
==========================================
- Coverage   89.91%   89.89%   -0.03%     
==========================================
  Files         372      372              
  Lines      102538   102565      +27     
  Branches   102538   102565      +27     
==========================================
+ Hits        92199    92200       +1     
- Misses       6785     6809      +24     
- Partials     3554     3556       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@bmarty
Copy link
Contributor

bmarty commented Feb 25, 2026

I am providing the secrets to the function with oidc, and at the end the session is not verified. I believe that this is supposed to be the case? Happy to provide some logs if it helps.

@poljar
Copy link
Contributor Author

poljar commented Feb 26, 2026

I am providing the secrets to the function with oidc, and at the end the session is not verified. I believe that this is supposed to be the case? Happy to provide some logs if it helps.

Hmm, I read through the code again, it should give you a verified device in the end.

If it's not too much trouble logs could help, though I think I'll need to make an integration test for this and move the logic into the SDK.

@poljar
Copy link
Contributor Author

poljar commented Feb 26, 2026

Oh wait, I did another mistake. Did you perhaps test this with an OIDC login?

If so I forgot to connect things for the OIDC login.

@bmarty
Copy link
Contributor

bmarty commented Feb 26, 2026

Did you perhaps test this with an OIDC login?

Yes

@poljar
Copy link
Contributor Author

poljar commented Feb 26, 2026

Did you perhaps test this with an OIDC login?

Yes

Ok, my bad: 44a92c3 fixes this for the OIDC login.

@bmarty
Copy link
Contributor

bmarty commented Feb 26, 2026

Thanks @poljar, I confirm that the session is now verified when using MAS!

Err(ClientError::Generic {
msg: "Secrets bundle does not belong to the user which was logged in".to_owned(),
details: None,
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the SDK should not throw anything but just ignore the provided secrets if the userId does not match, so the application can just continue with the "manual" verification step. Do you agree?

CC @pixlwave

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think I'd rather split this API into 2 calls if it doesn't throw.

client.login()
client.importSecrets()

That way a client can tell whether or not that operation was successful or not and decide to act accordingly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or specifically make it throw some kind of ClientError::Secrets { msg } so that this specific error can be handled differently. It would be nice to start moving away from ClientError::Generic everywhere anyway 😇

Copy link
Contributor Author

@poljar poljar Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That way a client can tell whether or not that operation was successful or not and decide to act accordingly.

We can do this, but you have to promise me to call this before you start a sync.

Or specifically make it throw some kind of ClientError::Secrets { msg } so that this specific error can be handled differently. It would be nice to start moving away from ClientError::Generic everywhere anyway 😇

Yeah, I skipped adding more specific errors for now, but we can obviously add them.

Let me know which way you prefer.

Copy link
Member

@pixlwave pixlwave Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this, but you have to promise me to call this before you start a sync.

I don't think it's even possible for us to start a sync at this stage in the flow anyway but, just in case…

tenor-3603527095

Let me know which way you prefer.

Personally I think 2 calls makes more sense but I'm happy either way. Any preference @bmarty?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, let's go with two separate calls then.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the login function (and the other of the same vein) can return an object which has an importSecrets method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the login function (and the other of the same vein) can return an object which has an importSecrets method?

Hmm, that would again result in a breaking change and complicate the interface compared to the added error variant and argument.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(maybe just ignore my last comment)
With the double API approach, I guess the application can also check the userId, after the login call, and before providing the secret, using client.session().userId, so it's good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave the user ID check on the Rust side, so people don't forget about it.

@poljar poljar force-pushed the poljar/login/secret-bundle-import branch from 5150bda to bef186d Compare February 27, 2026 13:53
@poljar poljar changed the title feat(ffi): Allow passing in a SecretsBundle while logging in feat(ffi): Allow passing in a SecretsBundle after logging in Mar 11, 2026
@poljar
Copy link
Contributor Author

poljar commented Mar 11, 2026

The complement-crypto failure is due to mozilla/uniffi-rs#2775.

Other than that, this should now work for iOS as well. It should also have the two step interface people were requesting.

Still needs some tests before this is ready for review.

@bmarty
Copy link
Contributor

bmarty commented Mar 11, 2026

Thanks, I can test the new API. Can I ask you to update the branch regarding develop, so that I will not have too many API breaks to manage?

@poljar
Copy link
Contributor Author

poljar commented Mar 11, 2026

Thanks, I can test the new API. Can I ask you to update the branch regarding develop, so that I will not have too many API breaks to manage?

Ah, you mean rebase on top of main? Sure, can do that.

@poljar poljar force-pushed the poljar/login/secret-bundle-import branch from 36750c9 to b7e7412 Compare March 11, 2026 16:59
@poljar
Copy link
Contributor Author

poljar commented Mar 11, 2026

Rebased now.

@pixlwave
Copy link
Member

pixlwave commented Mar 11, 2026

Thank you! Although unfortunately I bring bad news: It doesn't compile yet on iOS as both the generated matrix_sdk_crypto.swift and matrix_sdk_ffi.swift have a SecretsBundle (and SecretsBundleProtocol) definition so they conflict.

Screenshot 2026-03-11 at 6 37 36 pm

@bmarty
Copy link
Contributor

bmarty commented Mar 12, 2026

I have no conflict on building the Android sdk, probably because SecretsBundle declaration are in 2 different package, but it seems that the method import_secrets_bundle is not exposed through the FFI layer, so the application cannot access it. I'll double check on my side.

EDIT: OK, my bad, I need to call client.encryption().importSecretsBundle now.

@bmarty
Copy link
Contributor

bmarty commented Mar 12, 2026

Tested on Android using b7e7412 and it's working as expected 🎉

Since we already export a type with the same name and technically this
type contains more than just the bundle.
@poljar
Copy link
Contributor Author

poljar commented Mar 12, 2026

Thank you! Although unfortunately I bring bad news: It doesn't compile yet on iOS as both the generated matrix_sdk_crypto.swift and matrix_sdk_ffi.swift have a SecretsBundle (and SecretsBundleProtocol) definition so they conflict.

Annoying, but ok, I renamed one of the types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Let the application be able to login using a matrix id and secrets

3 participants