Skip to content

Commit 728931a

Browse files
committed
support restoring from html backup and uri lists directly
1 parent 3c88a00 commit 728931a

File tree

12 files changed

+256
-34
lines changed

12 files changed

+256
-34
lines changed

Stratum.Core/Stratum.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<LangVersion>13</LangVersion>
77
</PropertyGroup>
88
<ItemGroup>
9+
<PackageReference Include="HtmlAgilityPack" Version="1.11.72" />
910
<PackageReference Include="Konscious.Security.Cryptography.Argon2" Version="1.3.1" />
1011
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
1112
<PrivateAssets>all</PrivateAssets>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (C) 2025 jmh
2+
// SPDX-License-Identifier: GPL-3.0-only
3+
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using HtmlAgilityPack;
7+
using Stratum.Core.Backup;
8+
9+
namespace Stratum.Core.Converter
10+
{
11+
public class HtmlBackupConverter : UriListBackupConverter
12+
{
13+
public HtmlBackupConverter(IIconResolver iconResolver) : base(iconResolver)
14+
{
15+
}
16+
17+
public override Task<ConversionResult> ConvertAsync(byte[] data, string password = null)
18+
{
19+
var html = Encoding.UTF8.GetString(data);
20+
var document = new HtmlDocument();
21+
document.LoadHtml(html);
22+
23+
var builder = new StringBuilder();
24+
var nodes = document.DocumentNode.SelectNodes("//code");
25+
26+
foreach (var node in nodes)
27+
{
28+
builder.AppendLine(node.InnerText);
29+
}
30+
31+
return Task.FromResult(ConvertText(builder.ToString()));
32+
}
33+
}
34+
}

Stratum.Core/src/Converter/UriListBackupConverter.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ public UriListBackupConverter(IIconResolver iconResolver) : base(iconResolver)
2323
public override Task<ConversionResult> ConvertAsync(byte[] data, string password = null)
2424
{
2525
var text = Encoding.UTF8.GetString(data);
26+
return Task.FromResult(ConvertText(text));
27+
}
28+
29+
protected ConversionResult ConvertText(string text)
30+
{
2631
var lines = text.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
2732

2833
if (!lines.Any(l => l.StartsWith("otpauth")))
@@ -55,9 +60,7 @@ public override Task<ConversionResult> ConvertAsync(byte[] data, string password
5560
}
5661

5762
var backup = new Backup.Backup { Authenticators = authenticators };
58-
var result = new ConversionResult { Failures = failures, Backup = backup };
59-
60-
return Task.FromResult(result);
63+
return new ConversionResult { Failures = failures, Backup = backup };
6164
}
6265
}
6366
}

Stratum.Droid/Assets/about.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ <h3>ZXing-C++</h3>
9797
<p>Apache-2.0 License</p>
9898
<a href="https://github.com/zxing-cpp/zxing-cpp">https://github.com/zxing-cpp/zxing-cpp</a>
9999

100+
<h3>HtmlAgilityPack</h3>
101+
<p>Copyright &copy; ZZZ Projects</p>
102+
<p>MIT License</p>
103+
<a href="https://github.com/zzzprojects/html-agility-pack">https://github.com/zzzprojects/html-agility-pack</a>
104+
100105
%LICENSE
101106
</body>
102107
</html>

Stratum.Droid/Resources/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
<string name="backupToFileMessage">Save all your authenticators, categories and icons to an encrypted backup file. This file can be restored within the app.</string>
131131

132132
<string name="backupHtml">Back up to HTML file</string>
133-
<string name="backupHtmlMessage">Save your authenticators to an unencrypted HTML document with QR codes. To restore, these codes must be scanned individually. Categories and icons are not saved.</string>
133+
<string name="backupHtmlMessage">Save your authenticators to an unencrypted HTML document with QR codes. Categories and icons are not saved.</string>
134134
<string name="backupHtmlWarning">Backups to HTML files are unencrypted and may leave your secret keys vulnerable.\n\nThis file cannot be restored within the app and does not contain categories and icons. If unsure, opt for encrypted file backup instead.\n\nContinue?</string>
135135

136136
<string name="backupUriList">Back up to URI list file</string>

Stratum.Droid/src/Activity/MainActivity.cs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,7 @@ private async Task RestoreFromUri(Uri uri)
13681368
SetLoading(true);
13691369

13701370
byte[] data;
1371+
string displayName;
13711372

13721373
try
13731374
{
@@ -1377,24 +1378,48 @@ private async Task RestoreFromUri(Uri uri)
13771378
{
13781379
throw new IOException("The file is empty");
13791380
}
1381+
1382+
displayName = FileUtil.GetDisplayName(ContentResolver, uri);
13801383
}
13811384
catch (Exception e)
13821385
{
13831386
_log.Error(e, "Error picking file to restore");
1384-
ShowSnackbar(Resource.String.filePickError, Snackbar.LengthShort);
13851387
SetLoading(false);
1388+
ShowSnackbar(Resource.String.filePickError, Snackbar.LengthShort);
13861389
return;
13871390
}
13881391

1392+
try
1393+
{
1394+
if (displayName.EndsWith("." + UriListBackup.FileExtension))
1395+
{
1396+
await ImportFromData(new UriListBackupConverter(_iconResolver), data);
1397+
}
1398+
else if (displayName.EndsWith("." + HtmlBackup.FileExtension))
1399+
{
1400+
await ImportFromData(new HtmlBackupConverter(_iconResolver), data);
1401+
}
1402+
else
1403+
{
1404+
await RestoreFromData(data);
1405+
}
1406+
}
1407+
finally
1408+
{
1409+
SetLoading(false);
1410+
}
1411+
}
1412+
1413+
private async Task RestoreFromData(byte[] data)
1414+
{
13891415
var supportedEncryptions = _backupEncryptions.Where(e => e.CanBeDecrypted(data));
13901416

13911417
if (!supportedEncryptions.Any())
13921418
{
13931419
ShowSnackbar(Resource.String.invalidFileError, Snackbar.LengthShort);
1394-
SetLoading(false);
13951420
return;
13961421
}
1397-
1422+
13981423
try
13991424
{
14001425
var result = await DecryptAndRestore(data, null);
@@ -1405,10 +1430,6 @@ private async Task RestoreFromUri(Uri uri)
14051430
_log.Error(e, "Error decrypting file");
14061431
PromptForRestorePassword(data);
14071432
}
1408-
finally
1409-
{
1410-
SetLoading(false);
1411-
}
14121433
}
14131434

14141435
private async Task ImportFromData(BackupConverter converter, byte[] data)

Stratum.Droid/src/Interface/Fragment/AutoBackupSetupBottomSheet.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ private void UpdateLocationStatusText()
253253

254254
try
255255
{
256-
dirName = FileUtil.GetDocumentName(Context.ContentResolver, uri);
256+
dirName = FileUtil.GetDocumentTreeDisplayName(Context.ContentResolver, uri);
257257
}
258258
catch (Exception e)
259259
{

Stratum.Droid/src/Util/FileUtil.cs

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -97,36 +97,50 @@ public static Task WriteFileAsync(Context context, Uri uri, string data)
9797
});
9898
}
9999

100-
public static string GetDocumentName(ContentResolver resolver, Uri uri)
100+
private static string GetContentUriDisplayName(ContentResolver resolver, Uri uri)
101101
{
102-
string name = null;
103-
104-
if (uri.Scheme == "content")
102+
ICursor cursor = null;
103+
104+
try
105105
{
106-
ICursor cursor = null;
106+
cursor = resolver.Query(uri, null, null, null, null);
107107

108-
try
108+
if (cursor != null && cursor.MoveToFirst())
109109
{
110-
var documentUri =
111-
DocumentsContract.BuildDocumentUriUsingTree(uri, DocumentsContract.GetTreeDocumentId(uri));
110+
var index = cursor.GetColumnIndex(DocumentsContract.Document.ColumnDisplayName);
111+
return cursor.GetString(index);
112+
}
113+
}
114+
finally
115+
{
116+
cursor?.Close();
117+
}
118+
119+
return null;
120+
}
112121

113-
if (documentUri == null)
114-
{
115-
throw new IOException("Cannot get document URI");
116-
}
122+
public static string GetDisplayName(ContentResolver resolver, Uri uri)
123+
{
124+
return uri.Scheme == "content"
125+
? GetContentUriDisplayName(resolver, uri) :
126+
uri.LastPathSegment;
127+
}
117128

118-
cursor = resolver.Query(documentUri, null, null, null, null);
129+
public static string GetDocumentTreeDisplayName(ContentResolver resolver, Uri uri)
130+
{
131+
string name;
119132

120-
if (cursor != null && cursor.MoveToFirst())
121-
{
122-
var index = cursor.GetColumnIndex(DocumentsContract.Document.ColumnDisplayName);
123-
name = cursor.GetString(index);
124-
}
125-
}
126-
finally
133+
if (uri.Scheme == "content")
134+
{
135+
var documentUri =
136+
DocumentsContract.BuildDocumentUriUsingTree(uri, DocumentsContract.GetTreeDocumentId(uri));
137+
138+
if (documentUri == null)
127139
{
128-
cursor?.Close();
140+
throw new IOException("Cannot get document URI");
129141
}
142+
143+
name = GetContentUriDisplayName(resolver, documentUri);
130144
}
131145
else
132146
{

Stratum.Test/Stratum.Test.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
<PrivateAssets>all</PrivateAssets>
1212
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1313
</PackageReference>
14-
<PackageReference Include="HtmlAgilityPack" Version="1.11.72" />
1514
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
1615
<PrivateAssets>all</PrivateAssets>
1716
</PackageReference>
@@ -106,5 +105,8 @@
106105
<None Update="data\ente.encrypted.json">
107106
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
108107
</None>
108+
<None Update="data\backup.html">
109+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
110+
</None>
109111
</ItemGroup>
110112
</Project>

Stratum.Test/data/backup.html

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<!doctype html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Stratum Backup</title>
6+
<style>
7+
body {
8+
font-family: sans-serif;
9+
}
10+
11+
table {
12+
width: 100%;
13+
border-collapse: collapse;
14+
}
15+
16+
table th {
17+
text-align: left;
18+
}
19+
20+
table tr:not(:last-of-type) {
21+
border-bottom: solid 1px #eee;
22+
}
23+
24+
table tr:first-of-type {
25+
border-color: #000;
26+
}
27+
28+
table th, table td {
29+
padding: 8px;
30+
}
31+
</style>
32+
</head>
33+
<body>
34+
35+
<table>
36+
<tr>
37+
<th>Issuer</th>
38+
<th>Username</th>
39+
<th>OTP Auth URI</th>
40+
<th>QR Code</th>
41+
</tr>
42+
43+
<tr>
44+
<td>Long</td>
45+
<td>Period</td>
46+
<td><code>otpauth://totp/Long%3APeriod?secret=JGRMXBLPPYQIFMRBFQDHNP3MPPYQYNUN&issuer=Long&period=60</code></td>
47+
<td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOQAAADkAQAAAACgLLUgAAACY0lEQVR4nO2Y0W3DQAxDuYH235IbqCJ1dhAU7R8NBIhbxPE9f1wkipKN/ucgvvTzKYAiiOquOQGN+TKLYarV+UYWmzWgGqXFNJ3vc2J7X/QWS4sP0GHaBWuDwH6KbgAcezg+D1D9SwHlM6lb3nKUodrKr+NNkxnqevKOqFDAKXmrwQylV7fIVGJcjDQtJWDoZH0yMKdZpK7C1BVNp6JV38q/hN9pCu3GRdYys9IfiTSV7KaclQIdtNzZp8pydHZgOHZGhUORYV+7ylGoSSgRcm86ErNDKS9LSbcprc2+2L2ucqo7R0frJbFNFFpSP+LrNHVFqWHBHlrOSSNOnXmVVp2ArLshTWVhrjBFQP1R1FdZKgeTo7RbtNokH6GKgiLfLmxJQWK4p74YPXOf5h9sJrjjQZhuc7R3KufWfHUdN8tRagrwbdKfpz+J/+4pKSpdi+80pCXbCtLU/cJ+Vm6V3HR0mqrC3DmUhbJxw0UfpvrcTSj2blWeTDpM2xn3CGQP5Srv8roYhf2sNg3tGneppele+/fD0tOttpkwPR1CzbFwRl3baphersl9WPQTqwbdMPWEKwTPXjsXbRlEqWK+ErDmtC/1q2sWjVFZl5y0sRLc/BNpqtkHPC1D04HP1WmqX87eiGyJK/H3U0yOYtWG7R4q8sZrJoxRzz21JobrWRG3JmPU80jtOKJ4lHNwvf3I0X3VwtM29qJeHhuja5yuMUlNcwhfU32QKhQ4dgJ7WbnUH6DnpQd3xN9ie4SWnxNrRdD+6DT1atlYtB/PJH11qxzFeRrWi57eQcRyqDD9+/jSj6c/tAB4pgnLluIAAAAASUVORK5CYII="></td>
48+
</tr><tr>
49+
<td>Many</td>
50+
<td>Digits</td>
51+
<td><code>otpauth://totp/Many%3ADigits?secret=WEV2GHDH4EAU7D5SZGCIQOFBNQ35EHBS&issuer=Many&digits=9</code></td>
52+
<td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOQAAADkAQAAAACgLLUgAAACVklEQVR4nO2YUW7DQAhE5wbc/5bcgDIzmzhR1f6NpUix0sbetx8EWBiM+edqfOnnUwDVewM09rG6eoqLYVr7qeLDfs3uW1RcTFPe72dtKwh2lxbzFAtI1h10RM9NdM3aJQak6Yi5hTLisqbpEi7UvMUoQ5lgv663nMxQ/XhmeMuaDQTskTBloinkMoVuad73DXS/94DTFOh020Fhyh/fPN1a2ZxXZcnTUqbxSJ+SQksZ/DDdNdWv9TtLqQ9718n2HN2fv+HeSBS/GH/umk7TfVyDmrv2P7SFO9JUfwy4oj40S8U0TpVqbJU+Y0x2p36WltKNCcBFt8qTFlHqqKs5MwmYdnquMBVWEdctN7iYpSmtGpUzVpU5Qeg4ZbIXc90dmV5x3whTixAWUuCcb7xEIUXxUF7Me24ZtctKU3eIOXLALbquLhmjFpu+bJVa14s3MtQ9Q/1KPUpRWfdMmGqMUHJb+3meeHbJGK2H6rMrLO7xrHUxyioCDTIUf/y0UyBM1ZppEvWmtrV1fphyXWFgh1IBLUn8SdNRtOE1j6jXFBOk8MTSLS2iazpPoUI2bZXpHXNV0RhlYyo44br1XxJswpRSjw7AQw14bOw0pS3sGFZeoy4t4Remffaonhb17jj50hQS9lLZo8zrM7JmKfTOxSOM3jdJ5yJOPZWreegthE/6Iwo5qhcOXKE8OEPM9ZYpR6VEFHYNx6ovvIlTGFrswXZeVSVKJat5vhT8sui8gTY0EUOeYE19sypEpTTHCgi+lyoJ0+NyzjKMhdQQnqogR/++vvTj6Q9Oxbb/hJTDjQAAAABJRU5ErkJggg=="></td>
53+
</tr><tr>
54+
<td>Sha</td>
55+
<td>256</td>
56+
<td><code>otpauth://totp/Sha%3A256?secret=RJNZJM7PAGJUBG32XHQWHFZVLYOMGIUQ&issuer=Sha&algorithm=SHA256</code></td>
57+
<td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOQAAADkAQAAAACgLLUgAAACU0lEQVR4nO2YS27DMAwFeQPe/5a8Acs3lN2mQLt7BgLEiWFJowXBvxT9z1Pxoe9PIyI7s+aXGfNmd7LopUyqet7Zw6Zgh5tqjFQjSXUl27TopzPTUEyj6oeotDGLoY04xBOUf4xIY/BZLM1fbOShgSy/nhef9FA90bJ7IZX84DUGPbRqQytKssjyieu5KQt6Z4gW9J+FNNNRwkQXwuALCy/rG6n0MFOZILBBSSnRZpqkTw3zpJOYYV5R5qP6jRyNMirjOKGbyu1iXTyQS3bQ2EwVXIUQpZw2j/xeTmimFMhAJyWzU6oy3JR0HRJOfqAlBVqVmxJWEiZQCEGmuEszlQaaLLrChbwvyk4HgAnoKHbXXa18NJRHVJ+LMiWloJE206JWCG+9nA9xlmZKpWIH8lAw5Pl2SsocYegzg2xKq2umRYU6G4nvRCo3lShZp5UPGmwNbiu4KLlaebRUrnrzZ98+aaNJl6leSHmbEl3f3YiRnsxVp6uXbIS3maJ+aSKJ9HNou7OZjeJ4Cmk2MUj1+G2mKc0r1ORnSX8bdNlmWnUqZfV2Jeec6qacyTmkbmNwzhR2Kj1sPy8BcfaitffTdXFa+T0wVl2nGB8lxrZarftxJr+j20YV0mr1UIE8oGpzuJuu9vcKYD0db28z3Zrc2xbkmUa0m558pobk7JNdItx0b9W2u1f7tTnlunXxUc4OqlW763jcXbt9lFsm3oi82sAftcxJz20eLe+ejyMfoHsTgSW2++y7pvgoRWOv8ppv83HTVTlTrluQh7bES/9+PvTt6RcN7ZvPHMGX9QAAAABJRU5ErkJggg=="></td>
58+
</tr><tr>
59+
<td>Sha</td>
60+
<td>512</td>
61+
<td><code>otpauth://totp/Sha%3A512?secret=UW33SR5O4EKTDXNCBBS5FSPUKAYXIWOH&issuer=Sha&algorithm=SHA512</code></td>
62+
<td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOQAAADkAQAAAACgLLUgAAACXElEQVR4nO2YUY7DMAhEuQH3vyU3YJmHHala7f5NpUpNqybxcyUHGMCJ/ueo+NLPpxGRNafMmNua3+rUoJnmfGdgBkVm5kxLDbppcDsL06yZ1neCnxbrSplhr99Eg2eX7ecY+BY6X/CcGjdU9ouPPFQB9ut4iUkP5VDEa+DYIl81aKG1xlC04wJNidIEL50nr1asI3FCUJF31W2kyVpaSkuUrnB/VuWi88Oa5PNZzqxxrIEnzFTDJE/ZQh7g3HYqe2tKE3QpAcgybhrK3FqEgmCGxhcyx/GCkeZKreQCculWjzRTRCZx12pa/pfS3TTkA3K3ZBbnH3Gt4aP4OhhXLiviQFr3UoQd5LTC+Ll/cFMqBNLSYtQToLKnhrpobQYtJK4gaBLLrRo2mpQqhXgGPtfEeKxho6Rs2UGFYuOAcmmndWQmy68LFAl2umGm/ElGozKraLkpNqe33gb/eMFOQx81e00nQDLtfLK3jWr/0kQ6RYO6tcbxUslYMbdNpiTWlMg2U2lZjc9p5qnP8US7jzYbGKWTNcm6JW93baNJu8nTnw57d29uSjuwd7VO6Ix+eiQbpUjql82M8jY7ivs2wEZrU/bZlsfuLfJ6wUn71EhiDjtkvIHKB3SaCrc6KntynY/2VuU9rT8azZlp8NqDDjt1scrLNNOjK3TNbkJVU5XSTOmo+9QKQr/4Q5vpvlXj4ZO6nPQJ6aa5jQE90CZtTc2w0/NWbd8x7ZYt4lmVlfZ2t2yhOG+l9lN2yLUXTWLpdtP7xoH1rMb7xUceGqcynvWoL9mPmf59fOnH0x9j6C7SOaYqhQAAAABJRU5ErkJggg=="></td>
63+
</tr><tr>
64+
<td>Standard</td>
65+
<td>Hotp</td>
66+
<td><code>otpauth://hotp/Standard%3AHotp?secret=JOR2VZXMNHACFNLRAOACFFRDRYAOFMLB&issuer=Standard&counter=10</code></td>
67+
<td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOQAAADkAQAAAACgLLUgAAACXUlEQVR4nO2YW27DQAhF2QH73yU7oJzL2E0rtX/XUqSMI8Xm+APznon+Z1V86PvTiMhZnR1VWTFXJ0IzzdZDVc1jjSR5HbmZrlLDsxDWmCERPkELJVCq8Uv1U3SEKIbxA/4E5Te+Hq0mBHCGQqHd9ETdz/U7Jg2Uxac3zh9jjKx+5qCHEmJkV8gNtTQvrXx0JPM4LgjFADFfRyMrRZmOdf/guSoyv6uKizbBhkQsawMdqZeOC8Ye84LiD84v2k9RQRZASkVTSNjprla30KXiXW5KxGEMnJCqKlx5ZZmN6svH/sU/9pgMR8V0U4yPPom01J4LqZmm0pqZYKcCpVhfWvkoCm2gl0J+1bq94KNYnTLWW7xTFfzKMh/dMoYOCjmeCLtwU+aApF0o30qljCB0U/I45AduGL9COd5u2ppxMX6qiuMBDSReyvdHbprpRh3jsoaRKrE26CimKm2o6KanP6trYH91yrprrI0GKqlVaBJR51B+e6nmEepJKQ5qh94nqO7JslZW7wwYbsqQx8enRi8FnVqzm+4Q0Ce5d78WdzXzUXk61uNnnN89o5n27llKc+69ibm9b6OhXky898b6GY3aTreIbuMgs+ULPy3NHrVpndIo76jzUR7i7IgRYw3p56YhrAkk9CIl5u4aNqrdKS+F2ocigJripqpntbM1mX5MU24aZwhKFe9U38q8uqSP6oBlJ2od6HVuDLSbqi/vVnGPIfKMYn5Kt+pViLPEMwn5aezApTm71EJuL/joNg7qSmljsb3jxUceumcPod0ilW2Pbl/27Cb69/rQt6dfELlFyva0Mq8AAAAASUVORK5CYII="></td>
68+
</tr><tr>
69+
<td>Standard</td>
70+
<td>Totp</td>
71+
<td><code>otpauth://totp/Standard%3ATotp?secret=54VEGZFJHM3BGDONFERMPUOKNGDJETGM&issuer=Standard</code></td>
72+
<td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAANQAAADUAQAAAADMpTC1AAACC0lEQVR4nO2YS67DMAhF2QH73yU74HEPTpQ36Cx3UKnpJ7GPpQLm50Z/vCp+7BtYRGTNd1VqnN2pKRvTYKZznpcXUz4WOVelfl+Lopiyshrta8wwgxp57CyZi30KM5v3CDMj9L6mbCxWhOf1z8/eZnvJzNm4Lp+2sXEd6a5BxA7GEj7WetZLwiTxcpa62IxTQikoWREMTUwPEyfSN9bWs6jSxrSxqB9ni2f21t3AdNsXiUfLxt4+pizXbOk404jRCp32MaSoIwQ2V2T6mPznKJ/ruLP2rlXvs9FXrjM+q2o1niWRLt81MO1pnQ3NXMdNI5Ou7Klyj/K6YvPK8wZGFU5ssJA1TiZDqxorGRT9Rtx+/T5Lau8W4JGDTH7b2sCUaCRNrEOdHsfHkmocyrESpHQPI8O8in7tqNJP0U/ZmKpi1amPW4rrik0HQ57th1syYevLdw0sGuc9WaeSNBc+Nt+NlZPWBp8qI1PmpubTNG51vHzJwXZfVUBoBJTpKo2Mxpez2h5mCM9H7/02o8FnqCMiEbPNlYttf0iOVZXiHBU+VhRhzE1fU49ew8FyhZDNZfDNeddZzcDQnoK/IVpkoLYx/kvIPR8Gi3X3sbzLPc3+qV1GtidsqR3E6EkKToYE2OB4l5npvyDOM9vV9LMPeZ0JF/6kjLDdTftYbDHcXIAYyanUxT5dP/YF7A8FS4eoIJXHmwAAAABJRU5ErkJggg=="></td>
73+
</tr><tr>
74+
<td>NoIssuer</td>
75+
<td>NoIssuer</td>
76+
<td><code>otpauth://totp/NoIssuer%3ANoIssuer?secret=KY2QJPMNQLSGSZE2C5F7AYBLBVEEDHFX&issuer=NoIssuer</code></td>
77+
<td><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOQAAADkAQAAAACgLLUgAAACZ0lEQVR4nO2Y0W3DMAxEuQH335IbsLxH2UlbtH8XIECMNlb8BJSlyNPJ0f9cFR/6/jQisrKrOquiMjTSQzPN+anMmjnF9+pIPXTTGUf0PJiQZtp83c8X0AllJoQi0bj6RTQmAxPU5H5+B76E9mZiLqJL7s9r5KEqsF/Xt5r0UC6imUBCVV8/e9BC52GewHSbR3Mv1buXEtSsP63V6m5VYT5lw0NVaKyFuHJBJdaJykhVYqRD8QUlp7DSTKe4GU48pQ6LpWWnwVJUnq7eBF3ZMFKV3dSe5GRLr7SPtJtGr5SlQtJQlUdGvFR/nc6mx5seo+DdlOxrRiClM2JKm6kWQXoSiSXos0O3m9LK+u9n1TVVWspmbaYIZzWPQh/aLCVmZlpNOFoDpWMqroKVcFNFgahpLVAWDexUuhXKh0JRalZHL7dpo70iqkpLGT7ovQo+qm+YIcIrFbpc0aU5NqpVQDu1Ux5fHYi4l6LUeaLYA4XSc7kCGz0+nvTHheLanX1UhU7iazVUaanb5Rrp8Xy1jaaTROG5zXTbuba8yX0dc+ClyRDdXDlh56hKMz3aTZ9Jw4tJd5fZqCLaI2pwOA9Ce7ggF6W/Ob4IVK+QP6rdRdVTga/mlLoH1DsbPrqG9tS8VFSmDz/ipaqx6GMweSFBKaSd0uHsGuzTxylkmmkgmio+jm54MZbDTNFRvA+ncgzJY5f00X2rJh1ZMdVR9ckV2GieFy/c95aPU6qRUuX46rV/yXJc6m2lW3W74pfFzVdQWpzMa+9YEXXT3pezkccF4XjvXdJGY70856fcFyF7MDfTv68PfXv6Bcw7P+6YeQWCAAAAAElFTkSuQmCC"></td>
78+
</tr>
79+
</table>
80+
81+
</body>
82+
</html>

0 commit comments

Comments
 (0)