Skip to content

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

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

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

Conversation

@poljar
Copy link
Copy Markdown
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
Copy Markdown

codspeed-hq bot commented Feb 25, 2026

Merging this PR will not alter performance

✅ 50 untouched benchmarks


Comparing poljar/login/secret-bundle-import (0ec9338) with main (a65fec3)

Open in CodSpeed

@bmarty
Copy link
Copy Markdown
Contributor

bmarty commented Feb 25, 2026

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

@poljar
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown

codecov bot commented Feb 25, 2026

Codecov Report

❌ Patch coverage is 35.48387% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.85%. Comparing base (eb51c86) to head (0ec9338).
⚠️ Report is 12 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 34.48% 16 Missing and 3 partials ⚠️
...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.84%   89.85%           
=======================================
  Files         378      378           
  Lines      103339   103366   +27     
  Branches   103339   103366   +27     
=======================================
+ Hits        92847    92879   +32     
- Misses       6915     6918    +3     
+ Partials     3577     3569    -8     

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

@bmarty
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor

bmarty commented Feb 26, 2026

Did you perhaps test this with an OIDC login?

Yes

@poljar
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor Author

poljar commented Mar 11, 2026

Rebased now.

@pixlwave
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
Contributor

bmarty commented Mar 12, 2026

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

@poljar
Copy link
Copy Markdown
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.

@poljar
Copy link
Copy Markdown
Contributor Author

poljar commented Mar 20, 2026

I added a method which allows you to check if the bundle contains the backup key as well, as this is the only optional secret in the bundle.

As for the complement-crypto problem, I opened matrix-org/complement-crypto#233 to get complement-crypto on the same uniffi version as the SDK uses.

poljar and others added 10 commits April 1, 2026 14:35
Co-authored-by: Benoit Marty <benoitm@matrix.org>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
This allows us to double check if the right user got logged in.
Since we already export a type with the same name and technically this
type contains more than just the bundle.
Co-authored-by: Benoit Marty <benoitm@matrix.org>
Signed-off-by: Damir Jelić <poljar@termina.org.uk>
@poljar poljar force-pushed the poljar/login/secret-bundle-import branch from b00b3e4 to d039dad Compare April 1, 2026 13:01
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