diff --git a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m
index 1f6644d15..719ecb8c3 100644
--- a/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m
+++ b/Riot/Modules/Common/Recents/DataSources/RecentsDataSource.m
@@ -1894,6 +1894,21 @@ NSString *const kRecentsDataSourceTapOnDirectoryServerChange = @"kRecentsDataSou
MXSession* session = [self mxSession];
if(!session)
return;
+
+ // bwi #7565 as we have a session here and its a migration step -> use the migrationAssistant here
+
+ MigrationAssistant *migrationAssistant = [[MigrationAssistant alloc] init];
+
+ BOOL retVal = [migrationAssistant storeDisplaynameWithSession:session];
+ if ( retVal == false) {
+ MXLogError(@"[RecentsDataSource] shouldShowFeatureBanner could not store displaname for migration")
+ }
+
+ retVal = [migrationAssistant storeHomeserverWithSession:session];
+ if ( retVal == false) {
+ MXLogError(@"[RecentsDataSource] shouldShowFeatureBanner could not store homeserver for migration")
+ }
+
FeatureBannerVisibilityService *featureBannerService = [FeatureBannerVisibilityService alloc];
[featureBannerService isUnreadWithCompletion: ^(BOOL unread) {
if (unread) {
diff --git a/Riot/SupportingFiles/Info.plist b/Riot/SupportingFiles/Info.plist
index 2157da520..480c9018e 100644
--- a/Riot/SupportingFiles/Info.plist
+++ b/Riot/SupportingFiles/Info.plist
@@ -149,5 +149,7 @@
$(BASE_BUNDLE_IDENTIFIER)
keychainAccessGroup
$(KEYCHAIN_ACCESS_GROUP)
+ KeychainMigrationSharedGroup
+ $(AppIdentifierPrefix)de.bwi.messenger.shared.migration
diff --git a/Riot/SupportingFiles/Riot.entitlements b/Riot/SupportingFiles/Riot.entitlements
index b7f9c6920..f9ab80917 100644
--- a/Riot/SupportingFiles/Riot.entitlements
+++ b/Riot/SupportingFiles/Riot.entitlements
@@ -13,6 +13,7 @@
keychain-access-groups
$(KEYCHAIN_ACCESS_GROUP)
+ $(AppIdentifierPrefix)de.bwi.messenger.shared.migration
diff --git a/bwi/MigrationAssistent/MigrationAssistant.swift b/bwi/MigrationAssistent/MigrationAssistant.swift
new file mode 100644
index 000000000..2c8170cbb
--- /dev/null
+++ b/bwi/MigrationAssistent/MigrationAssistant.swift
@@ -0,0 +1,46 @@
+//
+/*
+ * Copyright (c) 2025 BWI GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import MatrixSDK
+
+// bwi #7565 has to be a class to be called by Objective C
+// reads user name and displayName from session and stores in shared keychain
+
+@objcMembers class MigrationAssistant : NSObject {
+ func storeDisplayname( session: MXSession ) -> Bool {
+
+ var returnValue = false
+
+ if let displayname = session.myUser.displayName {
+ let ossStatus = SharedKeychain.save(account: "migration_displayname", value: displayname)
+ returnValue = ossStatus == errSecSuccess
+ }
+
+ return returnValue
+ }
+
+ func storeHomeserver( session: MXSession ) -> Bool {
+ var returnValue = false
+
+ if let displayname = session.matrixRestClient.homeserver {
+ returnValue = SharedKeychain.save(account: "migration_homeserver", value: displayname) == errSecSuccess
+ }
+
+ return returnValue
+ }
+}
diff --git a/bwi/SharedKeychain/SharedKeychain.swift b/bwi/SharedKeychain/SharedKeychain.swift
new file mode 100644
index 000000000..9703c992f
--- /dev/null
+++ b/bwi/SharedKeychain/SharedKeychain.swift
@@ -0,0 +1,95 @@
+//
+/*
+ * Copyright (c) 2025 BWI GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Security
+import Foundation
+
+enum SharedKeychainConfig {
+ static var sharedGroup: String {
+ Bundle.main.object(forInfoDictionaryKey: "KeychainMigrationSharedGroup") as? String
+ ?? ""
+ }
+
+ static var service = "de.bwi.sharedkeychain"
+}
+
+// bwi #7565 do some keychain magic, needs to have entitlement for SharedKeychainConfig.sharedGroup and needs to store it in info.plist to hide team id.
+
+enum SharedKeychain {
+ @discardableResult
+ static func save(
+ account: String,
+ value: String
+ ) -> OSStatus {
+ let data = value.data(using: .utf8)!
+
+ // Delete existing item first
+ let deleteQuery: [String: Any] = [
+ kSecClass as String: kSecClassGenericPassword,
+ kSecAttrAccount as String: account,
+ kSecAttrService as String: SharedKeychainConfig.service,
+ kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup
+ ]
+ SecItemDelete(deleteQuery as CFDictionary)
+
+ // Add new value
+ let addQuery: [String: Any] = [
+ kSecClass as String: kSecClassGenericPassword,
+ kSecAttrAccount as String: account,
+ kSecAttrService as String: SharedKeychainConfig.service,
+ kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup,
+ kSecValueData as String: data
+ ]
+ return SecItemAdd(addQuery as CFDictionary, nil)
+ }
+
+ static func load(
+ account: String
+ ) -> String? {
+ let query: [String: Any] = [
+ kSecClass as String: kSecClassGenericPassword,
+ kSecAttrAccount as String: account,
+ kSecAttrService as String: SharedKeychainConfig.service,
+ kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup,
+ kSecMatchLimit as String: kSecMatchLimitOne,
+ kSecReturnData as String: true
+ ]
+
+ var result: AnyObject?
+ let status = SecItemCopyMatching(query as CFDictionary, &result)
+
+ guard status == errSecSuccess,
+ let data = result as? Data,
+ let value = String(data: data, encoding: .utf8) else {
+ return nil
+ }
+ return value
+ }
+
+ @discardableResult
+ static func delete(
+ account: String
+ ) -> OSStatus {
+ let query: [String: Any] = [
+ kSecClass as String: kSecClassGenericPassword,
+ kSecAttrAccount as String: account,
+ kSecAttrService as String: SharedKeychainConfig.service,
+ kSecAttrAccessGroup as String: SharedKeychainConfig.sharedGroup
+ ]
+ return SecItemDelete(query as CFDictionary)
+ }
+}