mirror of
https://github.com/bitwarden/mobile
synced 2025-12-05 23:53:33 +00:00
Compare commits
17 Commits
dependabot
...
v2022.11.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b34f68794 | ||
|
|
c9eb3f7f2d | ||
|
|
eab32ef59e | ||
|
|
bb759c16ea | ||
|
|
b4c0fba5e9 | ||
|
|
5e4192b7db | ||
|
|
0187676b5e | ||
|
|
3b796c6599 | ||
|
|
eeb0f61986 | ||
|
|
13eff49372 | ||
|
|
47d3a8b345 | ||
|
|
76042a2ef7 | ||
|
|
0507fcd54a | ||
|
|
1a69cc0591 | ||
|
|
0866a78802 | ||
|
|
cfda5fd6ff | ||
|
|
526904d1d8 |
@@ -7,6 +7,12 @@
|
|||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-format"
|
"dotnet-format"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"cake.tool": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-cake"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
73
.github/workflows/build.yml
vendored
73
.github/workflows/build.yml
vendored
@@ -59,6 +59,10 @@ jobs:
|
|||||||
name: Android
|
name: Android
|
||||||
runs-on: windows-2022
|
runs-on: windows-2022
|
||||||
needs: setup
|
needs: setup
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
variant: ['prod', 'qa']
|
||||||
steps:
|
steps:
|
||||||
- name: Setup NuGet
|
- name: Setup NuGet
|
||||||
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
|
||||||
@@ -67,7 +71,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up MSBuild
|
- name: Set up MSBuild
|
||||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||||
|
|
||||||
- name: Work Around for broken Windows 2022 Runner Image
|
- name: Work Around for broken Windows 2022 Runner Image
|
||||||
run: |
|
run: |
|
||||||
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
|
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
|
||||||
@@ -87,7 +91,6 @@ jobs:
|
|||||||
Write-Host "components were not installed"
|
Write-Host "components were not installed"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
- name: Print environment
|
- name: Print environment
|
||||||
run: |
|
run: |
|
||||||
nuget help | grep Version
|
nuget help | grep Version
|
||||||
@@ -98,7 +101,8 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
- name: Decrypt secrets
|
- name: Decrypt secrets
|
||||||
env:
|
env:
|
||||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||||
@@ -109,12 +113,17 @@ jobs:
|
|||||||
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
|
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||||
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
|
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||||
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
|
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
|
||||||
shell: bash
|
shell: bash
|
||||||
|
- name: Decrypt secrets - Google Services
|
||||||
|
if: ${{ matrix.variant == 'prod' }}
|
||||||
|
env:
|
||||||
|
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||||
|
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
|
||||||
|
shell: bash
|
||||||
- name: Increment version
|
- name: Increment version
|
||||||
run: |
|
run: |
|
||||||
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
|
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
|
||||||
@@ -142,26 +151,35 @@ jobs:
|
|||||||
run: dotnet test test/Core.Test/Core.Test.csproj
|
run: dotnet test test/Core.Test/Core.Test.csproj
|
||||||
|
|
||||||
- name: Build Play Store publisher
|
- name: Build Play Store publisher
|
||||||
|
if: ${{ matrix.variant == 'prod' }}
|
||||||
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
|
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
|
||||||
|
|
||||||
- name: Build for Play Store
|
- name: Setup Android build (${{ matrix.variant }})
|
||||||
|
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
|
||||||
|
|
||||||
|
- name: Build Android
|
||||||
run: |
|
run: |
|
||||||
$configuration = "Release";
|
$configuration = "Release";
|
||||||
|
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
Write-Output "##### Build $configuration Configuration"
|
Write-Output "##### Build $configuration Configuration"
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
|
|
||||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
|
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
|
||||||
|
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Sign for Play Store
|
- name: Sign Android Build
|
||||||
env:
|
env:
|
||||||
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
|
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
|
||||||
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
|
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
|
||||||
run: |
|
run: |
|
||||||
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
||||||
|
$packageName = "com.x8bit.bitwarden";
|
||||||
|
|
||||||
|
if ("${{ matrix.variant }}" -ne "prod")
|
||||||
|
{
|
||||||
|
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
|
||||||
|
}
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
Write-Output "##### Sign Google Play Bundle Release Configuration"
|
Write-Output "##### Sign Google Play Bundle Release Configuration"
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
@@ -175,9 +193,8 @@ jobs:
|
|||||||
Write-Output "##### Copy Google Play Bundle to project root"
|
Write-Output "##### Copy Google Play Bundle to project root"
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
|
|
||||||
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
|
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.aab");
|
||||||
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab");
|
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab");
|
||||||
|
|
||||||
Copy-Item $signedAabPath $signedAabDestPath
|
Copy-Item $signedAabPath $signedAabDestPath
|
||||||
|
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
@@ -193,33 +210,41 @@ jobs:
|
|||||||
Write-Output "##### Copy Release APK to project root"
|
Write-Output "##### Copy Release APK to project root"
|
||||||
Write-Output "########################################"
|
Write-Output "########################################"
|
||||||
|
|
||||||
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
|
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.apk");
|
||||||
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk");
|
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk");
|
||||||
|
|
||||||
Copy-Item $signedApkPath $signedApkDestPath
|
Copy-Item $signedApkPath $signedApkDestPath
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
- name: Upload Prod .aab artifact
|
||||||
- name: Upload Play Store .aab artifact
|
if: ${{ matrix.variant == 'prod' }}
|
||||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: com.x8bit.bitwarden.aab
|
name: com.x8bit.bitwarden.aab
|
||||||
path: ./com.x8bit.bitwarden.aab
|
path: ./com.x8bit.bitwarden.aab
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload Play Store .apk artifact
|
- name: Upload Prod .apk artifact
|
||||||
|
if: ${{ matrix.variant == 'prod' }}
|
||||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
with:
|
with:
|
||||||
name: com.x8bit.bitwarden.apk
|
name: com.x8bit.bitwarden.apk
|
||||||
path: ./com.x8bit.bitwarden.apk
|
path: ./com.x8bit.bitwarden.apk
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Other .apk artifact
|
||||||
|
if: ${{ matrix.variant != 'prod' }}
|
||||||
|
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||||
|
with:
|
||||||
|
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
|
||||||
|
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Deploy to Play Store
|
- name: Deploy to Play Store
|
||||||
if: |
|
if: ${{ matrix.variant == 'prod' && (( github.ref == 'refs/heads/master'
|
||||||
(github.ref == 'refs/heads/master'
|
&& needs.setup.outputs.rc_branch_exists == 0
|
||||||
&& needs.setup.outputs.rc_branch_exists == 0
|
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
|| github.ref == 'refs/heads/hotfix-rc' ) }}
|
||||||
|| github.ref == 'refs/heads/hotfix-rc'
|
|
||||||
run: |
|
run: |
|
||||||
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
|
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
|
||||||
CREDS_PATH="$HOME/secrets/play_creds.json"
|
CREDS_PATH="$HOME/secrets/play_creds.json"
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -208,4 +208,5 @@ FakesAssemblies/
|
|||||||
# Other
|
# Other
|
||||||
project.lock.json
|
project.lock.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
src/App/Css
|
src/App/Css
|
||||||
|
tools
|
||||||
|
|||||||
345
build.cake
Normal file
345
build.cake
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
#addin nuget:?package=Cake.FileHelpers&version=5.0.0
|
||||||
|
#addin nuget:?package=Cake.AndroidAppManifest&version=1.1.2
|
||||||
|
#addin nuget:?package=Cake.Plist&version=0.7.0
|
||||||
|
#addin nuget:?package=Cake.Incubator&version=7.0.0
|
||||||
|
#tool dotnet:?package=GitVersion.Tool&version=5.10.3
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
|
var debugScript = Argument<bool>("debugScript", false);
|
||||||
|
var target = Argument("target", "Default");
|
||||||
|
var configuration = Argument("configuration", "Release");
|
||||||
|
var variant = Argument("variant", "dev");
|
||||||
|
|
||||||
|
abstract record VariantConfig(
|
||||||
|
string AppName,
|
||||||
|
string AndroidPackageName,
|
||||||
|
string iOSBundleId,
|
||||||
|
string ApsEnvironment
|
||||||
|
);
|
||||||
|
|
||||||
|
const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden";
|
||||||
|
const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden";
|
||||||
|
|
||||||
|
record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development");
|
||||||
|
record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development");
|
||||||
|
record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production");
|
||||||
|
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production");
|
||||||
|
|
||||||
|
VariantConfig GetVariant() => variant.ToLower() switch{
|
||||||
|
"qa" => new QA(),
|
||||||
|
"beta" => new Beta(),
|
||||||
|
"prod" => new Prod(),
|
||||||
|
_ => new Dev()
|
||||||
|
};
|
||||||
|
|
||||||
|
GitVersion _gitVersion; //will be set by GetGitInfo task
|
||||||
|
var _slnPath = Path.Combine(""); //base path used to access files. If build.cake file is moved, just update this
|
||||||
|
string _androidPackageName = string.Empty; //will be set by UpdateAndroidManifest task
|
||||||
|
string CreateFeatureBranch(string prevVersionName, GitVersion git) => $"{prevVersionName}-{git.BranchName.Replace("/","-")}";
|
||||||
|
string GetVersionName(string prevVersionName, VariantConfig buildVariant, GitVersion git) => buildVariant is Prod? prevVersionName : CreateFeatureBranch(prevVersionName, git);
|
||||||
|
int CreateBuildNumber(int previousNumber) => ++previousNumber;
|
||||||
|
|
||||||
|
Task("GetGitInfo")
|
||||||
|
.Does(()=> {
|
||||||
|
_gitVersion = GitVersion(new GitVersionSettings());
|
||||||
|
|
||||||
|
if(debugScript)
|
||||||
|
{
|
||||||
|
Information($"GitVersion Dump:\n{_gitVersion.Dump()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Information("Git data Load successfully.");
|
||||||
|
});
|
||||||
|
|
||||||
|
#region Android
|
||||||
|
Task("UpdateAndroidAppIcon")
|
||||||
|
.Does(()=>{
|
||||||
|
//TODO we'll implement variant icons later
|
||||||
|
//manifest.ApplicationIcon = "@mipmap/ic_launcher";
|
||||||
|
Information($"Updated Androix App Icon with success");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Task("UpdateAndroidManifest")
|
||||||
|
.IsDependentOn("GetGitInfo")
|
||||||
|
.Does(()=>
|
||||||
|
{
|
||||||
|
var buildVariant = GetVariant();
|
||||||
|
var manifestPath = Path.Combine(_slnPath, "src", "Android", "Properties", "AndroidManifest.xml");
|
||||||
|
|
||||||
|
// Cake.AndroidAppManifest doesn't currently enable us to access nested items so, quick (not ideal) fix:
|
||||||
|
var manifestText = FileReadText(manifestPath);
|
||||||
|
manifestText = manifestText.Replace("com.x8bit.bitwarden.", buildVariant.AndroidPackageName + ".");
|
||||||
|
manifestText = manifestText.Replace("android:label=\"Bitwarden\"", $"android:label=\"{buildVariant.AppName}\"");
|
||||||
|
FileWriteText(manifestPath, manifestText);
|
||||||
|
|
||||||
|
var manifest = DeserializeAppManifest(manifestPath);
|
||||||
|
|
||||||
|
var prevVersionCode = manifest.VersionCode;
|
||||||
|
var prevVersionName = manifest.VersionName;
|
||||||
|
_androidPackageName = manifest.PackageName;
|
||||||
|
|
||||||
|
//manifest.VersionCode = CreateBuildNumber(prevVersionCode);
|
||||||
|
manifest.VersionName = GetVersionName(prevVersionName, buildVariant, _gitVersion);
|
||||||
|
manifest.PackageName = buildVariant.AndroidPackageName;
|
||||||
|
manifest.ApplicationLabel = buildVariant.AppName;
|
||||||
|
|
||||||
|
//Information($"AndroidManigest.xml VersionCode from {prevVersionCode} to {manifest.VersionCode}");
|
||||||
|
Information($"AndroidManigest.xml VersionName from {prevVersionName} to {manifest.VersionName}");
|
||||||
|
Information($"AndroidManigest.xml PackageName from {_androidPackageName} to {buildVariant.AndroidPackageName}");
|
||||||
|
Information($"AndroidManigest.xml ApplicationLabel to {buildVariant.AppName}");
|
||||||
|
|
||||||
|
SerializeAppManifest(manifestPath, manifest);
|
||||||
|
|
||||||
|
Information("AndroidManifest updated with success!");
|
||||||
|
});
|
||||||
|
|
||||||
|
void ReplaceInFile(string filePath, string oldtext, string newtext)
|
||||||
|
{
|
||||||
|
var fileText = FileReadText(filePath);
|
||||||
|
|
||||||
|
if(string.IsNullOrEmpty(fileText) || !fileText.Contains(oldtext))
|
||||||
|
{
|
||||||
|
throw new Exception($"Couldn't find {filePath} or it didn't contain: {oldtext}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fileText = fileText.Replace(oldtext, newtext);
|
||||||
|
|
||||||
|
FileWriteText(filePath, fileText);
|
||||||
|
Information($"{filePath} modified successfully.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Task("UpdateAndroidCodeFiles")
|
||||||
|
.IsDependentOn("UpdateAndroidManifest")
|
||||||
|
.Does(()=> {
|
||||||
|
var buildVariant = GetVariant();
|
||||||
|
|
||||||
|
//We're not using _androidPackageName here because the codefile is currently slightly different string than the one in AndroidManifest.xml
|
||||||
|
var keyName = "com.8bit.bitwarden";
|
||||||
|
var fixedPackageName = buildVariant.AndroidPackageName.Replace("x8bit", "8bit");
|
||||||
|
var filePath = Path.Combine(_slnPath, "src", "Android", "Services", "BiometricService.cs");
|
||||||
|
ReplaceInFile(filePath, keyName, fixedPackageName);
|
||||||
|
|
||||||
|
var packageFileList = new string[] {
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "MainActivity.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "MainApplication.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Constants.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Accessibility", "AccessibilityService.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillHelpers.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Receivers", "ClearClipboardAlarmReceiver.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Receivers", "EventUploadReceiver.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Receivers", "PackageReplacedReceiver.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Receivers", "RestrictionsChangedReceiver.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Services", "DeviceActionService.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Tiles", "AutofillTileService.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Tiles", "GeneratorTileService.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Tiles", "MyVaultTileService.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "google-services.json"),
|
||||||
|
Path.Combine(_slnPath, "store", "google", "Publisher", "Program.cs"),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach(string path in packageFileList)
|
||||||
|
{
|
||||||
|
ReplaceInFile(path, "com.x8bit.bitwarden", buildVariant.AndroidPackageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelFileList = new string[] {
|
||||||
|
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach(string path in labelFileList)
|
||||||
|
{
|
||||||
|
ReplaceInFile(path, "Bitwarden\"", $"{buildVariant.AppName}\"");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#endregion Android
|
||||||
|
|
||||||
|
#region iOS
|
||||||
|
enum iOSProjectType
|
||||||
|
{
|
||||||
|
Null,
|
||||||
|
MainApp,
|
||||||
|
Autofill,
|
||||||
|
Extension,
|
||||||
|
ShareExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
|
||||||
|
{
|
||||||
|
iOSProjectType.Autofill => $"{buildVariant.iOSBundleId}.autofill",
|
||||||
|
iOSProjectType.Extension => $"{buildVariant.iOSBundleId}.find-login-action-extension",
|
||||||
|
iOSProjectType.ShareExtension => $"{buildVariant.iOSBundleId}.share-extension",
|
||||||
|
_ => buildVariant.iOSBundleId
|
||||||
|
};
|
||||||
|
|
||||||
|
string GetiOSBundleName(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
|
||||||
|
{
|
||||||
|
iOSProjectType.Autofill => $"{buildVariant.AppName} Autofill",
|
||||||
|
iOSProjectType.Extension => $"{buildVariant.AppName} Extension",
|
||||||
|
iOSProjectType.ShareExtension => $"{buildVariant.AppName} Share Extension",
|
||||||
|
_ => buildVariant.AppName
|
||||||
|
};
|
||||||
|
|
||||||
|
private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, GitVersion git, iOSProjectType projectType = iOSProjectType.MainApp)
|
||||||
|
{
|
||||||
|
var plistFile = File(plistPath);
|
||||||
|
dynamic plist = DeserializePlist(plistFile);
|
||||||
|
|
||||||
|
var prevVersionName = plist["CFBundleShortVersionString"];
|
||||||
|
var prevVersionString = plist["CFBundleVersion"];
|
||||||
|
var prevVersion = int.Parse(plist["CFBundleVersion"]);
|
||||||
|
var prevBundleId = plist["CFBundleIdentifier"];
|
||||||
|
var prevBundleName = plist["CFBundleName"];
|
||||||
|
//var newVersion = CreateBuildNumber(prevVersion).ToString();
|
||||||
|
var newVersionName = GetVersionName(prevVersionName, buildVariant, git);
|
||||||
|
var newBundleId = GetiOSBundleId(buildVariant, projectType);
|
||||||
|
var newBundleName = GetiOSBundleName(buildVariant, projectType);
|
||||||
|
|
||||||
|
plist["CFBundleName"] = newBundleName;
|
||||||
|
plist["CFBundleDisplayName"] = newBundleName;
|
||||||
|
//plist["CFBundleVersion"] = newVersion;
|
||||||
|
plist["CFBundleShortVersionString"] = newVersionName;
|
||||||
|
plist["CFBundleIdentifier"] = newBundleId;
|
||||||
|
|
||||||
|
if(projectType == iOSProjectType.MainApp)
|
||||||
|
{
|
||||||
|
plist["CFBundleURLTypes"][0]["CFBundleURLName"] = $"{buildVariant.iOSBundleId}.url";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(projectType == iOSProjectType.Extension)
|
||||||
|
{
|
||||||
|
var keyText = plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"];
|
||||||
|
plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializePlist(plistFile, plist);
|
||||||
|
|
||||||
|
Information($"Changed app name from {prevBundleName} to {newBundleName}");
|
||||||
|
//Information($"Changed Bundle Version from {prevVersion} to {newVersion}");
|
||||||
|
Information($"Changed Bundle Short Version name from {prevVersionName} to {newVersionName}");
|
||||||
|
Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}");
|
||||||
|
Information($"{plistPath} updated with success!");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant)
|
||||||
|
{
|
||||||
|
var EntitlementlistFile = File(entitlementsPath);
|
||||||
|
dynamic Entitlements = DeserializePlist(EntitlementlistFile);
|
||||||
|
|
||||||
|
Entitlements["aps-environment"] = buildVariant.ApsEnvironment;
|
||||||
|
Entitlements["keychain-access-groups"] = new List<string>() { "$(AppIdentifierPrefix)" + buildVariant.iOSBundleId };
|
||||||
|
Entitlements["com.apple.security.application-groups"] = new List<string>() { $"group.{buildVariant.iOSBundleId}" };;
|
||||||
|
|
||||||
|
Information($"Changed ApsEnvironment name to {buildVariant.ApsEnvironment}");
|
||||||
|
Information($"Changed keychain-access-groups bundleID to {buildVariant.iOSBundleId}");
|
||||||
|
|
||||||
|
SerializePlist(EntitlementlistFile, Entitlements);
|
||||||
|
|
||||||
|
Information($"{entitlementsPath} updated with success!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Task("UpdateiOSIcon")
|
||||||
|
.Does(()=>{
|
||||||
|
//TODO we'll implement variant icons later
|
||||||
|
Information($"Updating IOS App Icon");
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("UpdateiOSPlist")
|
||||||
|
.IsDependentOn("GetGitInfo")
|
||||||
|
.Does(()=> {
|
||||||
|
var buildVariant = GetVariant();
|
||||||
|
var infoPath = Path.Combine(_slnPath, "src", "iOS", "Info.plist");
|
||||||
|
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS", "Entitlements.plist");
|
||||||
|
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp);
|
||||||
|
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("UpdateiOSAutofillPlist")
|
||||||
|
.IsDependentOn("GetGitInfo")
|
||||||
|
.IsDependentOn("UpdateiOSPlist")
|
||||||
|
.Does(()=> {
|
||||||
|
var buildVariant = GetVariant();
|
||||||
|
var infoPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Info.plist");
|
||||||
|
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Entitlements.plist");
|
||||||
|
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Autofill);
|
||||||
|
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("UpdateiOSExtensionPlist")
|
||||||
|
.IsDependentOn("GetGitInfo")
|
||||||
|
.IsDependentOn("UpdateiOSPlist")
|
||||||
|
.Does(()=> {
|
||||||
|
var buildVariant = GetVariant();
|
||||||
|
var infoPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Info.plist");
|
||||||
|
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Entitlements.plist");
|
||||||
|
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Extension);
|
||||||
|
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("UpdateiOSShareExtensionPlist")
|
||||||
|
.IsDependentOn("GetGitInfo")
|
||||||
|
.IsDependentOn("UpdateiOSPlist")
|
||||||
|
.Does(()=> {
|
||||||
|
var buildVariant = GetVariant();
|
||||||
|
var infoPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Info.plist");
|
||||||
|
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Entitlements.plist");
|
||||||
|
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.ShareExtension);
|
||||||
|
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("UpdateiOSCodeFiles")
|
||||||
|
.IsDependentOn("UpdateiOSPlist")
|
||||||
|
.Does(()=> {
|
||||||
|
var buildVariant = GetVariant();
|
||||||
|
var fileList = new string[] {
|
||||||
|
Path.Combine(_slnPath, "src", "iOS.Core", "Utilities", "iOSCoreHelpers.cs"),
|
||||||
|
Path.Combine(_slnPath, "src", "iOS.Core", "Constants.cs"),
|
||||||
|
Path.Combine(".github", "resources", "export-options-ad-hoc.plist"),
|
||||||
|
Path.Combine(".github", "resources", "export-options-app-store.plist"),
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach(string path in fileList)
|
||||||
|
{
|
||||||
|
ReplaceInFile(path, "com.8bit.bitwarden", buildVariant.iOSBundleId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#endregion iOS
|
||||||
|
|
||||||
|
#region Main Tasks
|
||||||
|
Task("Android")
|
||||||
|
//.IsDependentOn("UpdateAndroidAppIcon")
|
||||||
|
.IsDependentOn("UpdateAndroidManifest")
|
||||||
|
.IsDependentOn("UpdateAndroidCodeFiles")
|
||||||
|
.Does(()=>
|
||||||
|
{
|
||||||
|
Information("Android app updated");
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("iOS")
|
||||||
|
//.IsDependentOn("UpdateiOSIcon")
|
||||||
|
.IsDependentOn("UpdateiOSPlist")
|
||||||
|
.IsDependentOn("UpdateiOSAutofillPlist")
|
||||||
|
.IsDependentOn("UpdateiOSExtensionPlist")
|
||||||
|
.IsDependentOn("UpdateiOSShareExtensionPlist")
|
||||||
|
.IsDependentOn("UpdateiOSCodeFiles")
|
||||||
|
.Does(()=>
|
||||||
|
{
|
||||||
|
Information("iOS app updated");
|
||||||
|
});
|
||||||
|
|
||||||
|
Task("Default")
|
||||||
|
.Does(() => {
|
||||||
|
var usage = @"Missing target.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
dotnet cake build.cake --target (Android | iOS) --variant (dev | qa | beta | prod)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--debugScript=<bool> Script debug mode.
|
||||||
|
";
|
||||||
|
Information(usage);
|
||||||
|
});
|
||||||
|
#endregion Main Tasks
|
||||||
|
|
||||||
|
RunTarget(target);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.10.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.11.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ namespace Bit.App.Abstractions
|
|||||||
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
|
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
|
||||||
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
|
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
|
||||||
Task LogOutAsync(string userId, bool userInitiated, bool expired);
|
Task LogOutAsync(string userId, bool userInitiated, bool expired);
|
||||||
|
Task PromptToSwitchToExistingAccountAsync(string userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
@@ -33,8 +34,9 @@ namespace Bit.App
|
|||||||
private readonly IAccountsManager _accountsManager;
|
private readonly IAccountsManager _accountsManager;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
private static bool _isResumed;
|
private static bool _isResumed;
|
||||||
// this variable is static because the app is launching new activities on notification click, creating new instances of App.
|
// these variables are static because the app is launching new activities on notification click, creating new instances of App.
|
||||||
private static bool _pendingCheckPasswordlessLoginRequests;
|
private static bool _pendingCheckPasswordlessLoginRequests;
|
||||||
|
private static object _processingLoginRequestLock = new object();
|
||||||
|
|
||||||
public App(AppOptions appOptions)
|
public App(AppOptions appOptions)
|
||||||
{
|
{
|
||||||
@@ -143,9 +145,15 @@ namespace Bit.App
|
|||||||
new NavigationPage(new RemoveMasterPasswordPage()));
|
new NavigationPage(new RemoveMasterPasswordPage()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (message.Command == "passwordlessLoginRequest" || message.Command == "unlocked" || message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
else if (message.Command == Constants.PasswordlessLoginRequestKey
|
||||||
|
|| message.Command == "unlocked"
|
||||||
|
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
|
||||||
{
|
{
|
||||||
CheckPasswordlessLoginRequestsAsync().FireAndForget();
|
lock (_processingLoginRequestLock)
|
||||||
|
{
|
||||||
|
// lock doesn't allow for async execution
|
||||||
|
CheckPasswordlessLoginRequestsAsync().Wait();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -162,7 +170,6 @@ namespace Bit.App
|
|||||||
_pendingCheckPasswordlessLoginRequests = true;
|
_pendingCheckPasswordlessLoginRequests = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pendingCheckPasswordlessLoginRequests = false;
|
_pendingCheckPasswordlessLoginRequests = false;
|
||||||
if (await _vaultTimeoutService.IsLockedAsync())
|
if (await _vaultTimeoutService.IsLockedAsync())
|
||||||
{
|
{
|
||||||
@@ -182,6 +189,11 @@ namespace Bit.App
|
|||||||
|
|
||||||
// Delay to wait for the vault page to appear
|
// Delay to wait for the vault page to appear
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
|
// if there is a request modal opened ignore all incoming requests
|
||||||
|
if (App.Current.MainPage.Navigation.ModalStack.Any(p => p is NavigationPage navPage && navPage.CurrentPage is LoginPasswordlessPage))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id);
|
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id);
|
||||||
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
|
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
|
||||||
{
|
{
|
||||||
@@ -196,7 +208,7 @@ namespace Bit.App
|
|||||||
});
|
});
|
||||||
await _stateService.SetPasswordlessLoginNotificationAsync(null);
|
await _stateService.SetPasswordlessLoginNotificationAsync(null);
|
||||||
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
|
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
|
||||||
if (loginRequestData.CreationDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) > DateTime.UtcNow)
|
if (!loginRequestData.IsExpired)
|
||||||
{
|
{
|
||||||
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
|
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
|
||||||
}
|
}
|
||||||
@@ -211,13 +223,20 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
|
|
||||||
var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
|
var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
|
||||||
await Device.InvokeOnMainThreadAsync(async () =>
|
Device.BeginInvokeOnMainThread(async () =>
|
||||||
{
|
{
|
||||||
var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok);
|
try
|
||||||
if (result == AppResources.Ok)
|
|
||||||
{
|
{
|
||||||
await _stateService.SetActiveUserAsync(notification.UserId);
|
var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok);
|
||||||
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
if (result == AppResources.Ok)
|
||||||
|
{
|
||||||
|
await _stateService.SetActiveUserAsync(notification.UserId);
|
||||||
|
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerHelper.LogEvenIfCantBeResolved(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
@@ -242,7 +261,7 @@ namespace Bit.App
|
|||||||
}
|
}
|
||||||
if (_pendingCheckPasswordlessLoginRequests)
|
if (_pendingCheckPasswordlessLoginRequests)
|
||||||
{
|
{
|
||||||
CheckPasswordlessLoginRequestsAsync().FireAndForget();
|
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||||
}
|
}
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
@@ -278,7 +297,7 @@ namespace Bit.App
|
|||||||
_isResumed = true;
|
_isResumed = true;
|
||||||
if (_pendingCheckPasswordlessLoginRequests)
|
if (_pendingCheckPasswordlessLoginRequests)
|
||||||
{
|
{
|
||||||
CheckPasswordlessLoginRequestsAsync().FireAndForget();
|
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||||
}
|
}
|
||||||
if (Device.RuntimePlatform == Device.Android)
|
if (Device.RuntimePlatform == Device.Android)
|
||||||
{
|
{
|
||||||
@@ -440,7 +459,14 @@ namespace Bit.App
|
|||||||
switch (navTarget)
|
switch (navTarget)
|
||||||
{
|
{
|
||||||
case NavigationTarget.HomeLogin:
|
case NavigationTarget.HomeLogin:
|
||||||
Current.MainPage = new NavigationPage(new HomePage(Options));
|
if (navParams is HomeNavigationParams homeParams)
|
||||||
|
{
|
||||||
|
Current.MainPage = new NavigationPage(new HomePage(Options, homeParams.ShouldCheckRememberEmail));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Current.MainPage = new NavigationPage(new HomePage(Options));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.Login:
|
case NavigationTarget.Login:
|
||||||
if (navParams is LoginNavigationParams loginParams)
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ namespace Bit.App.Pages
|
|||||||
private readonly AppOptions _appOptions;
|
private readonly AppOptions _appOptions;
|
||||||
private IBroadcasterService _broadcasterService;
|
private IBroadcasterService _broadcasterService;
|
||||||
|
|
||||||
public HomePage(AppOptions appOptions = null, bool checkRememberedEmail = true)
|
public HomePage(AppOptions appOptions = null, bool shouldCheckRememberEmail = true)
|
||||||
{
|
{
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||||
_appOptions = appOptions;
|
_appOptions = appOptions;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_vm = BindingContext as HomeViewModel;
|
_vm = BindingContext as HomeViewModel;
|
||||||
_vm.Page = this;
|
_vm.Page = this;
|
||||||
_vm.CheckHasRememberedEmail = checkRememberedEmail;
|
_vm.ShouldCheckRememberEmail = shouldCheckRememberEmail;
|
||||||
_vm.ShowCancelButton = _appOptions?.IosExtension ?? false;
|
_vm.ShowCancelButton = _appOptions?.IosExtension ?? false;
|
||||||
_vm.StartLoginAction = async () => await StartLoginAsync();
|
_vm.StartLoginAction = async () => await StartLoginAsync();
|
||||||
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
|
_vm.StartRegisterAction = () => Device.BeginInvokeOnMainThread(async () => await StartRegisterAsync());
|
||||||
@@ -59,7 +59,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
if (!_appOptions?.HideAccountSwitcher ?? false)
|
if (!_appOptions?.HideAccountSwitcher ?? false)
|
||||||
{
|
{
|
||||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(false);
|
||||||
}
|
}
|
||||||
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
|
_broadcasterService.Subscribe(nameof(HomePage), (message) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
@@ -24,13 +25,17 @@ namespace Bit.App.Pages
|
|||||||
private bool _canLogin;
|
private bool _canLogin;
|
||||||
private IPlatformUtilsService _platformUtilsService;
|
private IPlatformUtilsService _platformUtilsService;
|
||||||
private ILogger _logger;
|
private ILogger _logger;
|
||||||
|
private IEnvironmentService _environmentService;
|
||||||
|
private IAccountsManager _accountManager;
|
||||||
|
|
||||||
public HomeViewModel()
|
public HomeViewModel()
|
||||||
{
|
{
|
||||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
_stateService = ServiceContainer.Resolve<IStateService>();
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
_messagingService = ServiceContainer.Resolve<IMessagingService>();
|
||||||
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
|
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>();
|
||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>();
|
||||||
|
_environmentService = ServiceContainer.Resolve<IEnvironmentService>();
|
||||||
|
_accountManager = ServiceContainer.Resolve<IAccountsManager>();
|
||||||
|
|
||||||
PageTitle = AppResources.Bitwarden;
|
PageTitle = AppResources.Bitwarden;
|
||||||
|
|
||||||
@@ -68,7 +73,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public bool CanContinue => !string.IsNullOrEmpty(Email);
|
public bool CanContinue => !string.IsNullOrEmpty(Email);
|
||||||
|
|
||||||
public bool CheckHasRememberedEmail { get; set; }
|
public bool ShouldCheckRememberEmail { get; set; }
|
||||||
|
|
||||||
public FormattedString CreateAccountText
|
public FormattedString CreateAccountText
|
||||||
{
|
{
|
||||||
@@ -107,11 +112,11 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public void CheckNavigateLoginStep()
|
public void CheckNavigateLoginStep()
|
||||||
{
|
{
|
||||||
if (CheckHasRememberedEmail && RememberEmail)
|
if (ShouldCheckRememberEmail && RememberEmail)
|
||||||
{
|
{
|
||||||
StartLoginAction();
|
StartLoginAction();
|
||||||
}
|
}
|
||||||
CheckHasRememberedEmail = false;
|
ShouldCheckRememberEmail = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ContinueToLoginStepAsync()
|
public async Task ContinueToLoginStepAsync()
|
||||||
@@ -132,6 +137,16 @@ namespace Bit.App.Pages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await _stateService.SetRememberedEmailAsync(RememberEmail ? Email : null);
|
await _stateService.SetRememberedEmailAsync(RememberEmail ? Email : null);
|
||||||
|
var userId = await _stateService.GetUserIdAsync(Email);
|
||||||
|
if (!string.IsNullOrWhiteSpace(userId))
|
||||||
|
{
|
||||||
|
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
|
||||||
|
if (userEnvUrls?.Base == _environmentService.BaseUrl)
|
||||||
|
{
|
||||||
|
await _accountManager.PromptToSwitchToExistingAccountAsync(userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
StartLoginAction();
|
StartLoginAction();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
@@ -53,11 +54,6 @@ namespace Bit.App.Pages
|
|||||||
ToolbarItems.Add(_getPasswordHint);
|
ToolbarItems.Add(_getPasswordHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.Android && !_vm.IsEmailEnabled)
|
|
||||||
{
|
|
||||||
ToolbarItems.Add(_removeAccount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_appOptions?.IosExtension ?? false)
|
if (_appOptions?.IosExtension ?? false)
|
||||||
{
|
{
|
||||||
_vm.ShowCancelButton = true;
|
_vm.ShowCancelButton = true;
|
||||||
@@ -77,16 +73,20 @@ namespace Bit.App.Pages
|
|||||||
_mainContent.Content = _mainLayout;
|
_mainContent.Content = _mainLayout;
|
||||||
_accountAvatar?.OnAppearing();
|
_accountAvatar?.OnAppearing();
|
||||||
|
|
||||||
|
await _vm.InitAsync();
|
||||||
if (!_appOptions?.HideAccountSwitcher ?? false)
|
if (!_appOptions?.HideAccountSwitcher ?? false)
|
||||||
{
|
{
|
||||||
_vm.AvatarImageSource = await GetAvatarImageSourceAsync();
|
_vm.AvatarImageSource = await GetAvatarImageSourceAsync(_vm.EmailIsInSavedAccounts);
|
||||||
}
|
}
|
||||||
await _vm.InitAsync();
|
|
||||||
if (!_inputFocused)
|
if (!_inputFocused)
|
||||||
{
|
{
|
||||||
RequestFocus(_masterPassword);
|
RequestFocus(_masterPassword);
|
||||||
_inputFocused = true;
|
_inputFocused = true;
|
||||||
}
|
}
|
||||||
|
if (Device.RuntimePlatform == Device.Android && !_vm.CanRemoveAccount)
|
||||||
|
{
|
||||||
|
ToolbarItems.Add(_removeAccount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnBackButtonPressed()
|
protected override bool OnBackButtonPressed()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
@@ -6,9 +7,11 @@ using Bit.App.Controls;
|
|||||||
using Bit.App.Models;
|
using Bit.App.Models;
|
||||||
using Bit.App.Resources;
|
using Bit.App.Resources;
|
||||||
using Bit.App.Utilities;
|
using Bit.App.Utilities;
|
||||||
|
using Bit.App.Utilities.AccountManagement;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.Core.Abstractions;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models.View;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Xamarin.CommunityToolkit.ObjectModel;
|
using Xamarin.CommunityToolkit.ObjectModel;
|
||||||
@@ -29,6 +32,7 @@ namespace Bit.App.Pages
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IApiService _apiService;
|
private readonly IApiService _apiService;
|
||||||
private readonly IAppIdService _appIdService;
|
private readonly IAppIdService _appIdService;
|
||||||
|
private readonly IAccountsManager _accountManager;
|
||||||
private bool _showPassword;
|
private bool _showPassword;
|
||||||
private bool _showCancelButton;
|
private bool _showCancelButton;
|
||||||
private string _email;
|
private string _email;
|
||||||
@@ -49,6 +53,7 @@ namespace Bit.App.Pages
|
|||||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||||
_apiService = ServiceContainer.Resolve<IApiService>();
|
_apiService = ServiceContainer.Resolve<IApiService>();
|
||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>();
|
_appIdService = ServiceContainer.Resolve<IAppIdService>();
|
||||||
|
_accountManager = ServiceContainer.Resolve<IAccountsManager>();
|
||||||
|
|
||||||
PageTitle = AppResources.Bitwarden;
|
PageTitle = AppResources.Bitwarden;
|
||||||
TogglePasswordCommand = new Command(TogglePassword);
|
TogglePasswordCommand = new Command(TogglePassword);
|
||||||
@@ -107,22 +112,23 @@ namespace Bit.App.Pages
|
|||||||
set => SetProperty(ref _isKnownDevice, value);
|
set => SetProperty(ref _isKnownDevice, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsIosExtension { get; set; }
|
|
||||||
|
|
||||||
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
public AccountSwitchingOverlayViewModel AccountSwitchingOverlayViewModel { get; }
|
||||||
|
|
||||||
public Command LogInCommand { get; }
|
public Command LogInCommand { get; }
|
||||||
public Command TogglePasswordCommand { get; }
|
public Command TogglePasswordCommand { get; }
|
||||||
public ICommand MoreCommand { get; internal set; }
|
public ICommand MoreCommand { get; internal set; }
|
||||||
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
public string ShowPasswordIcon => ShowPassword ? BitwardenIcons.EyeSlash : BitwardenIcons.Eye;
|
||||||
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
public string PasswordVisibilityAccessibilityText => ShowPassword ? AppResources.PasswordIsVisibleTapToHide : AppResources.PasswordIsNotVisibleTapToShow;
|
||||||
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
|
public string LoggingInAsText => string.Format(AppResources.LoggingInAsX, Email);
|
||||||
|
public bool IsIosExtension { get; set; }
|
||||||
|
public bool CanRemoveAccount { get; set; }
|
||||||
public Action StartTwoFactorAction { get; set; }
|
public Action StartTwoFactorAction { get; set; }
|
||||||
public Action LogInSuccessAction { get; set; }
|
public Action LogInSuccessAction { get; set; }
|
||||||
public Action UpdateTempPasswordAction { get; set; }
|
public Action UpdateTempPasswordAction { get; set; }
|
||||||
public Action StartSsoLoginAction { get; set; }
|
public Action StartSsoLoginAction { get; set; }
|
||||||
public Action CloseAction { get; set; }
|
public Action CloseAction { get; set; }
|
||||||
|
|
||||||
|
public bool EmailIsInSavedAccounts => _stateService.AccountViews != null && _stateService.AccountViews.Any(e => e.Email == Email);
|
||||||
|
|
||||||
protected override II18nService i18nService => _i18nService;
|
protected override II18nService i18nService => _i18nService;
|
||||||
protected override IEnvironmentService environmentService => _environmentService;
|
protected override IEnvironmentService environmentService => _environmentService;
|
||||||
protected override IDeviceActionService deviceActionService => _deviceActionService;
|
protected override IDeviceActionService deviceActionService => _deviceActionService;
|
||||||
@@ -130,14 +136,23 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
public async Task InitAsync()
|
public async Task InitAsync()
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
try
|
||||||
if (string.IsNullOrWhiteSpace(Email))
|
|
||||||
{
|
{
|
||||||
Email = await _stateService.GetRememberedEmailAsync();
|
await _deviceActionService.ShowLoadingAsync(AppResources.Loading);
|
||||||
|
await AccountSwitchingOverlayViewModel.RefreshAccountViewsAsync();
|
||||||
|
if (string.IsNullOrWhiteSpace(Email))
|
||||||
|
{
|
||||||
|
Email = await _stateService.GetRememberedEmailAsync();
|
||||||
|
}
|
||||||
|
var deviceIdentifier = await _appIdService.GetAppIdAsync();
|
||||||
|
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, deviceIdentifier);
|
||||||
|
CanRemoveAccount = await _stateService.GetActiveUserEmailAsync() != Email;
|
||||||
|
await _deviceActionService.HideLoadingAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
HandleException(ex);
|
||||||
}
|
}
|
||||||
var deviceIdentifier = await _appIdService.GetAppIdAsync();
|
|
||||||
IsKnownDevice = await _apiService.GetKnownDeviceAsync(Email, deviceIdentifier);
|
|
||||||
await _deviceActionService.HideLoadingAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task LogInAsync(bool showLoading = true, bool checkForExistingAccount = false)
|
public async Task LogInAsync(bool showLoading = true, bool checkForExistingAccount = false)
|
||||||
@@ -180,7 +195,7 @@ namespace Bit.App.Pages
|
|||||||
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
|
var userEnvUrls = await _stateService.GetEnvironmentUrlsAsync(userId);
|
||||||
if (userEnvUrls?.Base == _environmentService.BaseUrl)
|
if (userEnvUrls?.Base == _environmentService.BaseUrl)
|
||||||
{
|
{
|
||||||
await PromptToSwitchToExistingAccountAsync(userId);
|
await _accountManager.PromptToSwitchToExistingAccountAsync(userId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,7 +252,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task MoreAsync()
|
private async Task MoreAsync()
|
||||||
{
|
{
|
||||||
var buttons = IsEmailEnabled
|
var buttons = IsEmailEnabled || CanRemoveAccount
|
||||||
? new[] { AppResources.GetPasswordHint }
|
? new[] { AppResources.GetPasswordHint }
|
||||||
: new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount };
|
: new[] { AppResources.GetPasswordHint, AppResources.RemoveAccount };
|
||||||
var selection = await _deviceActionService.DisplayActionSheetAsync(AppResources.Options, AppResources.Cancel, null, buttons);
|
var selection = await _deviceActionService.DisplayActionSheetAsync(AppResources.Options, AppResources.Cancel, null, buttons);
|
||||||
@@ -287,16 +302,14 @@ namespace Bit.App.Pages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PromptToSwitchToExistingAccountAsync(string userId)
|
private void HandleException(Exception ex)
|
||||||
{
|
{
|
||||||
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
|
Xamarin.Essentials.MainThread.InvokeOnMainThreadAsync(async () =>
|
||||||
AppResources.SwitchToAlreadyAddedAccountConfirmation,
|
|
||||||
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
|
|
||||||
if (switchToAccount)
|
|
||||||
{
|
{
|
||||||
await _stateService.SetActiveUserAsync(userId);
|
await _deviceActionService.HideLoadingAsync();
|
||||||
_messagingService.Send("switchedAccount");
|
await _platformUtilsService.ShowDialogAsync(AppResources.GenericErrorMessage);
|
||||||
}
|
}).FireAndForget();
|
||||||
|
_logger.Exception(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
_vm.LoginRequest = loginPasswordlessDetails;
|
_vm.LoginRequest = loginPasswordlessDetails;
|
||||||
|
|
||||||
if (Device.RuntimePlatform == Device.iOS)
|
ToolbarItems.Add(_closeItem);
|
||||||
{
|
|
||||||
ToolbarItems.Add(_closeItem);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Close_Clicked(object sender, System.EventArgs e)
|
private async void Close_Clicked(object sender, System.EventArgs e)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ namespace Bit.App.Pages
|
|||||||
private async Task UpdateRequestTime()
|
private async Task UpdateRequestTime()
|
||||||
{
|
{
|
||||||
TriggerPropertyChanged(nameof(TimeOfRequestText));
|
TriggerPropertyChanged(nameof(TimeOfRequestText));
|
||||||
if (DateTime.UtcNow > LoginRequest?.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes))
|
if (LoginRequest?.IsExpired ?? false)
|
||||||
{
|
{
|
||||||
StopRequestTimeUpdater();
|
StopRequestTimeUpdater();
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
||||||
@@ -110,7 +110,7 @@ namespace Bit.App.Pages
|
|||||||
|
|
||||||
private async Task PasswordlessLoginAsync(bool approveRequest)
|
private async Task PasswordlessLoginAsync(bool approveRequest)
|
||||||
{
|
{
|
||||||
if (LoginRequest.RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) <= DateTime.UtcNow)
|
if (LoginRequest.IsExpired)
|
||||||
{
|
{
|
||||||
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
await _platformUtilsService.ShowDialogAsync(AppResources.LoginRequestHasAlreadyExpired);
|
||||||
await Page.Navigation.PopModalAsync();
|
await Page.Navigation.PopModalAsync();
|
||||||
@@ -171,5 +171,7 @@ namespace Bit.App.Pages
|
|||||||
public string DeviceType { get; set; }
|
public string DeviceType { get; set; }
|
||||||
|
|
||||||
public string IpAddress { get; set; }
|
public string IpAddress { get; set; }
|
||||||
|
|
||||||
|
public bool IsExpired => RequestDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) < DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ namespace Bit.App.Pages
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _deviceActionService.ShowLoadingAsync(AppResources.Syncing);
|
await _deviceActionService.ShowLoadingAsync(AppResources.Syncing);
|
||||||
|
await _syncService.SyncPasswordlessLoginRequestsAsync();
|
||||||
var success = await _syncService.FullSyncAsync(true);
|
var success = await _syncService.FullSyncAsync(true);
|
||||||
await _deviceActionService.HideLoadingAsync();
|
await _deviceActionService.HideLoadingAsync();
|
||||||
if (success)
|
if (success)
|
||||||
|
|||||||
@@ -189,6 +189,7 @@ namespace Bit.App.Pages
|
|||||||
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
|
if (await _stateService.GetSyncOnRefreshAsync() && Refreshing && !SyncRefreshing)
|
||||||
{
|
{
|
||||||
SyncRefreshing = true;
|
SyncRefreshing = true;
|
||||||
|
await _syncService.SyncPasswordlessLoginRequestsAsync();
|
||||||
await _syncService.FullSyncAsync(false);
|
await _syncService.FullSyncAsync(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1762,7 +1762,7 @@ Scanning will happen automatically.</value>
|
|||||||
<value>Syncing vault with pull down gesture.</value>
|
<value>Syncing vault with pull down gesture.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogInSso" xml:space="preserve">
|
<data name="LogInSso" xml:space="preserve">
|
||||||
<value>Enterprise Single Sign-On</value>
|
<value>Enterprise single sign-on</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogInSsoSummary" xml:space="preserve">
|
<data name="LogInSsoSummary" xml:space="preserve">
|
||||||
<value>Quickly log in using your organization's single sign-on portal. Please enter your organization's identifier to begin.</value>
|
<value>Quickly log in using your organization's single sign-on portal. Please enter your organization's identifier to begin.</value>
|
||||||
@@ -2486,7 +2486,7 @@ Do you want to switch to this account?</value>
|
|||||||
<value>Not you?</value>
|
<value>Not you?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogInWithMasterPassword" xml:space="preserve">
|
<data name="LogInWithMasterPassword" xml:space="preserve">
|
||||||
<value>Log In with master password</value>
|
<value>Log in with master password</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="LogInWithAnotherDevice" xml:space="preserve">
|
<data name="LogInWithAnotherDevice" xml:space="preserve">
|
||||||
<value>Log In with another device</value>
|
<value>Log In with another device</value>
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ namespace Bit.App.Services
|
|||||||
};
|
};
|
||||||
|
|
||||||
_pushNotificationService.Value.SendLocalNotification(AppResources.LogInRequested, String.Format(AppResources.ConfimLogInAttempForX, userEmail), notificationData);
|
_pushNotificationService.Value.SendLocalNotification(AppResources.LogInRequested, String.Format(AppResources.ConfimLogInAttempForX, userEmail), notificationData);
|
||||||
_messagingService.Value.Send("passwordlessLoginRequest", passwordlessLoginMessage);
|
_messagingService.Value.Send(Constants.PasswordlessLoginRequestKey, passwordlessLoginMessage);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -228,7 +228,9 @@ namespace Bit.App.Services
|
|||||||
if (data is PasswordlessNotificationData passwordlessNotificationData)
|
if (data is PasswordlessNotificationData passwordlessNotificationData)
|
||||||
{
|
{
|
||||||
var notificationUserId = await _stateService.Value.GetUserIdAsync(passwordlessNotificationData.UserEmail);
|
var notificationUserId = await _stateService.Value.GetUserIdAsync(passwordlessNotificationData.UserEmail);
|
||||||
if (notificationUserId != null)
|
var activeUserEmail = await _stateService.Value.GetActiveUserEmailAsync();
|
||||||
|
var notificationSaved = await _stateService.Value.GetPasswordlessLoginNotificationAsync();
|
||||||
|
if (activeUserEmail != passwordlessNotificationData.UserEmail && notificationUserId != null && notificationSaved != null)
|
||||||
{
|
{
|
||||||
await _stateService.Value.SetActiveUserAsync(notificationUserId);
|
await _stateService.Value.SetActiveUserAsync(notificationUserId);
|
||||||
_messagingService.Value.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
_messagingService.Value.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Bit.Core.Abstractions;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Models.Domain;
|
using Bit.Core.Models.Domain;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using Xamarin.Essentials;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
|
|
||||||
namespace Bit.App.Utilities.AccountManagement
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
@@ -103,11 +104,12 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
// var orgIdentifier = await _stateService.GetOrgIdentifierAsync();
|
||||||
|
|
||||||
var email = await _stateService.GetEmailAsync();
|
var email = await _stateService.GetEmailAsync();
|
||||||
_accountsManagerHost.Navigate(NavigationTarget.Login, new LoginNavigationParams(email));
|
await _stateService.SetRememberedEmailAsync(email);
|
||||||
|
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin, new HomeNavigationParams(true));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin, new HomeNavigationParams(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -183,7 +185,7 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
await Device.InvokeOnMainThreadAsync(() =>
|
await Device.InvokeOnMainThreadAsync(() =>
|
||||||
{
|
{
|
||||||
Options.HideAccountSwitcher = false;
|
Options.HideAccountSwitcher = false;
|
||||||
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin);
|
_accountsManagerHost.Navigate(NavigationTarget.HomeLogin, new HomeNavigationParams(false));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,5 +220,17 @@ namespace Bit.App.Utilities.AccountManagement
|
|||||||
_messagingService.Send(AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED);
|
_messagingService.Send(AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task PromptToSwitchToExistingAccountAsync(string userId)
|
||||||
|
{
|
||||||
|
var switchToAccount = await _platformUtilsService.ShowDialogAsync(
|
||||||
|
AppResources.SwitchToAlreadyAddedAccountConfirmation,
|
||||||
|
AppResources.AccountAlreadyAdded, AppResources.Yes, AppResources.Cancel);
|
||||||
|
if (switchToAccount)
|
||||||
|
{
|
||||||
|
await _stateService.SetActiveUserAsync(userId);
|
||||||
|
_messagingService.Send("switchedAccount");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/App/Utilities/AccountManagement/HomeNavigationParams.cs
Normal file
14
src/App/Utilities/AccountManagement/HomeNavigationParams.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using Bit.App.Abstractions;
|
||||||
|
|
||||||
|
namespace Bit.App.Utilities.AccountManagement
|
||||||
|
{
|
||||||
|
public class HomeNavigationParams : INavigationParams
|
||||||
|
{
|
||||||
|
public HomeNavigationParams(bool shouldCheckRememberEmail)
|
||||||
|
{
|
||||||
|
ShouldCheckRememberEmail = shouldCheckRememberEmail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShouldCheckRememberEmail { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -83,6 +83,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<SendResponse> PutSendAsync(string id, SendRequest request);
|
Task<SendResponse> PutSendAsync(string id, SendRequest request);
|
||||||
Task<SendResponse> PutSendRemovePasswordAsync(string id);
|
Task<SendResponse> PutSendRemovePasswordAsync(string id);
|
||||||
Task DeleteSendAsync(string id);
|
Task DeleteSendAsync(string id);
|
||||||
|
Task<List<PasswordlessLoginResponse>> GetAuthRequestAsync();
|
||||||
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
|
Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id);
|
||||||
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
Task<PasswordlessLoginResponse> PutAuthRequestAsync(string id, string key, string masterPasswordHash, string deviceIdentifier, bool requestApproved);
|
||||||
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
|
Task<string> GetUsernameFromAsync(ForwardedEmailServiceType service, UsernameGeneratorConfig config);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
|
Task<AuthResult> LogInCompleteAsync(string email, string masterPassword, TwoFactorProviderType twoFactorProvider, string twoFactorToken, bool? remember = null);
|
||||||
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
|
Task<AuthResult> LogInTwoFactorAsync(TwoFactorProviderType twoFactorProvider, string twoFactorToken, string captchaToken, bool? remember = null);
|
||||||
|
|
||||||
|
Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync();
|
||||||
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id);
|
||||||
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
Task<PasswordlessLoginResponse> PasswordlessLoginAsync(string id, string pubKey, bool requestApproved);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace Bit.Core.Abstractions
|
|||||||
{
|
{
|
||||||
List<AccountView> AccountViews { get; }
|
List<AccountView> AccountViews { get; }
|
||||||
Task<string> GetActiveUserIdAsync();
|
Task<string> GetActiveUserIdAsync();
|
||||||
|
Task<string> GetActiveUserEmailAsync();
|
||||||
Task<bool> IsActiveAccountAsync(string userId = null);
|
Task<bool> IsActiveAccountAsync(string userId = null);
|
||||||
Task SetActiveUserAsync(string userId);
|
Task SetActiveUserAsync(string userId);
|
||||||
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
Task CheckExtensionActiveUserAndSwitchIfNeededAsync();
|
||||||
|
|||||||
@@ -14,5 +14,7 @@ namespace Bit.Core.Abstractions
|
|||||||
Task<bool> SyncDeleteFolderAsync(SyncFolderNotification notification);
|
Task<bool> SyncDeleteFolderAsync(SyncFolderNotification notification);
|
||||||
Task<bool> SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit);
|
Task<bool> SyncUpsertCipherAsync(SyncCipherNotification notification, bool isEdit);
|
||||||
Task<bool> SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit);
|
Task<bool> SyncUpsertFolderAsync(SyncFolderNotification notification, bool isEdit);
|
||||||
|
// Passwordless code will be moved to an independent service in future techdept
|
||||||
|
Task SyncPasswordlessLoginRequestsAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
public const string iOSNotificationClearActionId = "Clear";
|
public const string iOSNotificationClearActionId = "Clear";
|
||||||
public const string NotificationData = "notificationData";
|
public const string NotificationData = "notificationData";
|
||||||
public const string NotificationDataType = "Type";
|
public const string NotificationDataType = "Type";
|
||||||
|
public const string PasswordlessLoginRequestKey = "passwordlessLoginRequest";
|
||||||
public const int SelectFileRequestCode = 42;
|
public const int SelectFileRequestCode = 42;
|
||||||
public const int SelectFilePermissionRequestCode = 43;
|
public const int SelectFilePermissionRequestCode = 43;
|
||||||
public const int SaveFileRequestCode = 44;
|
public const int SaveFileRequestCode = 44;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Response
|
namespace Bit.Core.Models.Response
|
||||||
{
|
{
|
||||||
@@ -13,7 +15,19 @@ namespace Bit.Core.Models.Response
|
|||||||
public string Key { get; set; }
|
public string Key { get; set; }
|
||||||
public string MasterPasswordHash { get; set; }
|
public string MasterPasswordHash { get; set; }
|
||||||
public DateTime CreationDate { get; set; }
|
public DateTime CreationDate { get; set; }
|
||||||
public bool RequestApproved { get; set; }
|
public DateTime? ResponseDate { get; set; }
|
||||||
|
public bool? RequestApproved { get; set; }
|
||||||
public string Origin { get; set; }
|
public string Origin { get; set; }
|
||||||
|
public string RequestAccessCode { get; set; }
|
||||||
|
public Tuple<byte[], byte[]> RequestKeyPair { get; set; }
|
||||||
|
|
||||||
|
public bool IsAnswered => RequestApproved != null && ResponseDate != null;
|
||||||
|
|
||||||
|
public bool IsExpired => CreationDate.ToUniversalTime().AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) < DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PasswordlessLoginsResponse
|
||||||
|
{
|
||||||
|
public List<PasswordlessLoginResponse> Data { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -536,6 +536,12 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
#region PasswordlessLogin
|
#region PasswordlessLogin
|
||||||
|
|
||||||
|
public async Task<List<PasswordlessLoginResponse>> GetAuthRequestAsync()
|
||||||
|
{
|
||||||
|
var response = await SendAsync<object, PasswordlessLoginsResponse>(HttpMethod.Get, $"/auth-requests/", null, true, true);
|
||||||
|
return response.Data;
|
||||||
|
}
|
||||||
|
|
||||||
public Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id)
|
public Task<PasswordlessLoginResponse> GetAuthRequestAsync(string id)
|
||||||
{
|
{
|
||||||
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}", null, true, true);
|
return SendAsync<object, PasswordlessLoginResponse>(HttpMethod.Get, $"/auth-requests/{id}", null, true, true);
|
||||||
@@ -795,8 +801,6 @@ namespace Bit.Core.Services
|
|||||||
if (authed
|
if (authed
|
||||||
&&
|
&&
|
||||||
(
|
(
|
||||||
(tokenError && response.StatusCode == HttpStatusCode.BadRequest)
|
|
||||||
||
|
|
||||||
(logoutOnUnauthorized && response.StatusCode == HttpStatusCode.Unauthorized)
|
(logoutOnUnauthorized && response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
||
|
||
|
||||||
response.StatusCode == HttpStatusCode.Forbidden
|
response.StatusCode == HttpStatusCode.Forbidden
|
||||||
@@ -813,6 +817,17 @@ namespace Bit.Core.Services
|
|||||||
var responseJsonString = await response.Content.ReadAsStringAsync();
|
var responseJsonString = await response.Content.ReadAsStringAsync();
|
||||||
responseJObject = JObject.Parse(responseJsonString);
|
responseJObject = JObject.Parse(responseJsonString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authed && tokenError
|
||||||
|
&&
|
||||||
|
response.StatusCode == HttpStatusCode.BadRequest
|
||||||
|
&&
|
||||||
|
responseJObject?["error"]?.ToString() == "invalid_grant")
|
||||||
|
{
|
||||||
|
await _logoutCallbackAsync(new Tuple<string, bool, bool>(null, false, true));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return new ErrorResponse(responseJObject, response.StatusCode, tokenError);
|
return new ErrorResponse(responseJObject, response.StatusCode, tokenError);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|||||||
@@ -471,6 +471,11 @@ namespace Bit.Core.Services
|
|||||||
SelectedTwoFactorProviderType = null;
|
SelectedTwoFactorProviderType = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<PasswordlessLoginResponse>> GetPasswordlessLoginRequestsAsync()
|
||||||
|
{
|
||||||
|
return await _apiService.GetAuthRequestAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
public async Task<PasswordlessLoginResponse> GetPasswordlessLoginRequestByIdAsync(string id)
|
||||||
{
|
{
|
||||||
return await _apiService.GetAuthRequestAsync(id);
|
return await _apiService.GetAuthRequestAsync(id);
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ namespace Bit.Core.Services
|
|||||||
return activeUserId;
|
return activeUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetActiveUserEmailAsync()
|
||||||
|
{
|
||||||
|
var activeUserId = await GetActiveUserIdAsync();
|
||||||
|
return await GetEmailAsync(activeUserId);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<bool> IsActiveAccountAsync(string userId = null)
|
public async Task<bool> IsActiveAccountAsync(string userId = null)
|
||||||
{
|
{
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace Bit.Core.Services
|
|||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ISendService _sendService;
|
private readonly ISendService _sendService;
|
||||||
private readonly IKeyConnectorService _keyConnectorService;
|
private readonly IKeyConnectorService _keyConnectorService;
|
||||||
|
private readonly ILogger _logger;
|
||||||
private readonly Func<Tuple<string, bool, bool>, Task> _logoutCallbackAsync;
|
private readonly Func<Tuple<string, bool, bool>, Task> _logoutCallbackAsync;
|
||||||
|
|
||||||
public SyncService(
|
public SyncService(
|
||||||
@@ -39,6 +40,7 @@ namespace Bit.Core.Services
|
|||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
ISendService sendService,
|
ISendService sendService,
|
||||||
IKeyConnectorService keyConnectorService,
|
IKeyConnectorService keyConnectorService,
|
||||||
|
ILogger logger,
|
||||||
Func<Tuple<string, bool, bool>, Task> logoutCallbackAsync)
|
Func<Tuple<string, bool, bool>, Task> logoutCallbackAsync)
|
||||||
{
|
{
|
||||||
_stateService = stateService;
|
_stateService = stateService;
|
||||||
@@ -53,6 +55,7 @@ namespace Bit.Core.Services
|
|||||||
_policyService = policyService;
|
_policyService = policyService;
|
||||||
_sendService = sendService;
|
_sendService = sendService;
|
||||||
_keyConnectorService = keyConnectorService;
|
_keyConnectorService = keyConnectorService;
|
||||||
|
_logger = logger;
|
||||||
_logoutCallbackAsync = logoutCallbackAsync;
|
_logoutCallbackAsync = logoutCallbackAsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -382,5 +385,45 @@ namespace Bit.Core.Services
|
|||||||
new Dictionary<string, SendData>();
|
new Dictionary<string, SendData>();
|
||||||
await _sendService.ReplaceAsync(sends);
|
await _sendService.ReplaceAsync(sends);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SyncPasswordlessLoginRequestsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = await _stateService.GetActiveUserIdAsync();
|
||||||
|
// if the user has not enabled passwordless logins ignore requests
|
||||||
|
if (!await _stateService.GetApprovePasswordlessLoginsAsync(userId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var loginRequests = await _apiService.GetAuthRequestAsync();
|
||||||
|
if (loginRequests == null || !loginRequests.Any())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var validLoginRequest = loginRequests.Where(l => !l.IsAnswered && !l.IsExpired)
|
||||||
|
.OrderByDescending(x => x.CreationDate)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (validLoginRequest is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _stateService.SetPasswordlessLoginNotificationAsync(new PasswordlessRequestNotification()
|
||||||
|
{
|
||||||
|
Id = validLoginRequest.Id,
|
||||||
|
UserId = userId
|
||||||
|
});
|
||||||
|
|
||||||
|
_messagingService.Send(Constants.PasswordlessLoginRequestKey);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ namespace Bit.Core.Utilities
|
|||||||
var messagingService = Resolve<IMessagingService>("messagingService");
|
var messagingService = Resolve<IMessagingService>("messagingService");
|
||||||
var cryptoFunctionService = Resolve<ICryptoFunctionService>("cryptoFunctionService");
|
var cryptoFunctionService = Resolve<ICryptoFunctionService>("cryptoFunctionService");
|
||||||
var cryptoService = Resolve<ICryptoService>("cryptoService");
|
var cryptoService = Resolve<ICryptoService>("cryptoService");
|
||||||
|
var logger = Resolve<ILogger>();
|
||||||
|
|
||||||
SearchService searchService = null;
|
SearchService searchService = null;
|
||||||
|
|
||||||
var tokenService = new TokenService(stateService);
|
var tokenService = new TokenService(stateService);
|
||||||
@@ -67,7 +69,7 @@ namespace Bit.Core.Utilities
|
|||||||
});
|
});
|
||||||
var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService,
|
var syncService = new SyncService(stateService, apiService, settingsService, folderService, cipherService,
|
||||||
cryptoService, collectionService, organizationService, messagingService, policyService, sendService,
|
cryptoService, collectionService, organizationService, messagingService, policyService, sendService,
|
||||||
keyConnectorService, (extras) =>
|
keyConnectorService, logger, (extras) =>
|
||||||
{
|
{
|
||||||
messagingService.Send("logout", extras);
|
messagingService.Send("logout", extras);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|||||||
@@ -425,10 +425,10 @@ namespace Bit.iOS.Autofill
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchHomePage(bool checkRememberedEmail = true)
|
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
|
||||||
{
|
{
|
||||||
var appOptions = new AppOptions { IosExtension = true };
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
var homePage = new HomePage(appOptions, checkRememberedEmail: checkRememberedEmail);
|
var homePage = new HomePage(appOptions, shouldCheckRememberEmail);
|
||||||
var app = new App.App(appOptions);
|
var app = new App.App(appOptions);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
ThemeManager.SetTheme(app.Resources);
|
||||||
ThemeManager.ApplyResourcesTo(homePage);
|
ThemeManager.ApplyResourcesTo(homePage);
|
||||||
@@ -457,8 +457,8 @@ namespace Bit.iOS.Autofill
|
|||||||
ThemeManager.ApplyResourcesTo(environmentPage);
|
ThemeManager.ApplyResourcesTo(environmentPage);
|
||||||
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
|
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(environmentPage);
|
var navigationPage = new NavigationPage(environmentPage);
|
||||||
@@ -476,7 +476,7 @@ namespace Bit.iOS.Autofill
|
|||||||
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(registerPage);
|
var navigationPage = new NavigationPage(registerPage);
|
||||||
@@ -498,7 +498,7 @@ namespace Bit.iOS.Autofill
|
|||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(loginPage);
|
var navigationPage = new NavigationPage(loginPage);
|
||||||
@@ -604,7 +604,14 @@ namespace Bit.iOS.Autofill
|
|||||||
switch (navTarget)
|
switch (navTarget)
|
||||||
{
|
{
|
||||||
case NavigationTarget.HomeLogin:
|
case NavigationTarget.HomeLogin:
|
||||||
DismissViewController(false, () => LaunchHomePage());
|
if (navParams is HomeNavigationParams homeParams)
|
||||||
|
{
|
||||||
|
DismissViewController(false, () => LaunchHomePage(homeParams.ShouldCheckRememberEmail));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DismissViewController(false, () => LaunchHomePage());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.Login:
|
case NavigationTarget.Login:
|
||||||
if (navParams is LoginNavigationParams loginParams)
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.autofill</string>
|
<string>com.8bit.bitwarden.autofill</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.10.1</string>
|
<string>2022.11.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
<string>com.8bit.bitwarden.find-login-action-extension</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.10.1</string>
|
<string>2022.11.0</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<key>CFBundleLocalizations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
|
|||||||
@@ -446,10 +446,10 @@ namespace Bit.iOS.Extension
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchHomePage(bool checkRememberedEmail = true)
|
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
|
||||||
{
|
{
|
||||||
var appOptions = new AppOptions { IosExtension = true };
|
var appOptions = new AppOptions { IosExtension = true };
|
||||||
var homePage = new HomePage(appOptions, checkRememberedEmail: checkRememberedEmail);
|
var homePage = new HomePage(appOptions, shouldCheckRememberEmail);
|
||||||
var app = new App.App(appOptions);
|
var app = new App.App(appOptions);
|
||||||
ThemeManager.SetTheme(app.Resources);
|
ThemeManager.SetTheme(app.Resources);
|
||||||
ThemeManager.ApplyResourcesTo(homePage);
|
ThemeManager.ApplyResourcesTo(homePage);
|
||||||
@@ -478,8 +478,8 @@ namespace Bit.iOS.Extension
|
|||||||
ThemeManager.ApplyResourcesTo(environmentPage);
|
ThemeManager.ApplyResourcesTo(environmentPage);
|
||||||
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
|
vm.SubmitSuccessAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(environmentPage);
|
var navigationPage = new NavigationPage(environmentPage);
|
||||||
@@ -497,7 +497,7 @@ namespace Bit.iOS.Extension
|
|||||||
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
vm.RegistrationSuccess = () => DismissViewController(false, () => LaunchLoginFlow(vm.Email));
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(registerPage);
|
var navigationPage = new NavigationPage(registerPage);
|
||||||
@@ -519,7 +519,7 @@ namespace Bit.iOS.Extension
|
|||||||
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissViewController(false, () => LaunchUpdateTempPasswordFlow());
|
||||||
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
vm.StartSsoLoginAction = () => DismissViewController(false, () => LaunchLoginSsoFlow());
|
||||||
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
vm.LogInSuccessAction = () => DismissLockAndContinue();
|
||||||
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(checkRememberedEmail: false));
|
vm.CloseAction = () => DismissViewController(false, () => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
var navigationPage = new NavigationPage(loginPage);
|
var navigationPage = new NavigationPage(loginPage);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.10.1</string>
|
<string>2022.11.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
|
|||||||
@@ -287,9 +287,9 @@ namespace Bit.iOS.ShareExtension
|
|||||||
return _app;
|
return _app;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchHomePage(bool checkRememberedEmail = true)
|
private void LaunchHomePage(bool shouldCheckRememberEmail = true)
|
||||||
{
|
{
|
||||||
var homePage = new HomePage(_appOptions.Value, checkRememberedEmail: checkRememberedEmail);
|
var homePage = new HomePage(_appOptions.Value, shouldCheckRememberEmail);
|
||||||
SetupAppAndApplyResources(homePage);
|
SetupAppAndApplyResources(homePage);
|
||||||
if (homePage.BindingContext is HomeViewModel vm)
|
if (homePage.BindingContext is HomeViewModel vm)
|
||||||
{
|
{
|
||||||
@@ -311,8 +311,8 @@ namespace Bit.iOS.ShareExtension
|
|||||||
ThemeManager.ApplyResourcesTo(environmentPage);
|
ThemeManager.ApplyResourcesTo(environmentPage);
|
||||||
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
if (environmentPage.BindingContext is EnvironmentPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage(checkRememberedEmail: false));
|
vm.SubmitSuccessAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(checkRememberedEmail: false));
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigateToPage(environmentPage);
|
NavigateToPage(environmentPage);
|
||||||
@@ -325,7 +325,7 @@ namespace Bit.iOS.ShareExtension
|
|||||||
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
if (registerPage.BindingContext is RegisterPageViewModel vm)
|
||||||
{
|
{
|
||||||
vm.RegistrationSuccess = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
|
vm.RegistrationSuccess = () => DismissAndLaunch(() => LaunchLoginFlow(vm.Email));
|
||||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(checkRememberedEmail: false));
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
}
|
}
|
||||||
NavigateToPage(registerPage);
|
NavigateToPage(registerPage);
|
||||||
}
|
}
|
||||||
@@ -339,11 +339,8 @@ namespace Bit.iOS.ShareExtension
|
|||||||
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
|
vm.StartTwoFactorAction = () => DismissAndLaunch(() => LaunchTwoFactorFlow(false));
|
||||||
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
vm.UpdateTempPasswordAction = () => DismissAndLaunch(() => LaunchUpdateTempPasswordFlow());
|
||||||
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
vm.StartSsoLoginAction = () => DismissAndLaunch(() => LaunchLoginSsoFlow());
|
||||||
vm.LogInSuccessAction = () =>
|
vm.LogInSuccessAction = () => { DismissLockAndContinue(); };
|
||||||
{
|
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(shouldCheckRememberEmail: false));
|
||||||
DismissLockAndContinue();
|
|
||||||
};
|
|
||||||
vm.CloseAction = () => DismissAndLaunch(() => LaunchHomePage(checkRememberedEmail: false));
|
|
||||||
}
|
}
|
||||||
NavigateToPage(loginPage);
|
NavigateToPage(loginPage);
|
||||||
|
|
||||||
@@ -427,7 +424,14 @@ namespace Bit.iOS.ShareExtension
|
|||||||
switch (navTarget)
|
switch (navTarget)
|
||||||
{
|
{
|
||||||
case NavigationTarget.HomeLogin:
|
case NavigationTarget.HomeLogin:
|
||||||
ExecuteLaunch(() => LaunchHomePage());
|
if (navParams is HomeNavigationParams homeParams)
|
||||||
|
{
|
||||||
|
ExecuteLaunch(() => LaunchHomePage(homeParams.ShouldCheckRememberEmail));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExecuteLaunch(() => LaunchHomePage());
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case NavigationTarget.Login:
|
case NavigationTarget.Login:
|
||||||
if (navParams is LoginNavigationParams loginParams)
|
if (navParams is LoginNavigationParams loginParams)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.8bit.bitwarden</string>
|
<string>com.8bit.bitwarden</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2022.10.1</string>
|
<string>2022.11.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>CFBundleIconName</key>
|
<key>CFBundleIconName</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user