Skip to content

Commit

Permalink
Lock app after inactivity option
Browse files Browse the repository at this point in the history
Closes #5 + Some formatting
  • Loading branch information
timokoessler committed Mar 26, 2024
1 parent 017419b commit cc13bee
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 61 deletions.
57 changes: 33 additions & 24 deletions Guard/Core/Export/Exporter/BackupExporter.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using Guard.Core.Models;
using Guard.Core.Security;
using System.IO;
using System.IO;
using System.Text;
using System.Text.Json;
using Guard.Core.Models;
using Guard.Core.Security;

namespace Guard.Core.Export.Exporter
{
Expand All @@ -11,8 +11,11 @@ internal class BackupExporter : IExporter
public string Name => "Backup";
public IExporter.ExportType Type => IExporter.ExportType.File;
public string ExportFileExtensions => "2FAGuard Backup (*.2fabackup) | *.2fabackup";

public bool RequiresPassword() => true;

private readonly byte[] prefix = Encoding.UTF8.GetBytes("2FAGuardBackupV1");

public string GetDefaultFileName()
{
return $"2FAGuardBackup-{DateTime.Now:yyyy-MM-dd-HH-mm-ss}.2fabackup";
Expand All @@ -26,36 +29,36 @@ public async Task Export(string? path, byte[]? password)
byte[] salt = EncryptionHelper.GenerateSaltBytes();
EncryptionHelper encryption = new(password, salt);

var tokenHelpers = await TokenManager.GetAllTokens() ?? throw new Exception(I18n.GetString("export.notokens"));
var tokenHelpers =
await TokenManager.GetAllTokens()
?? throw new Exception(I18n.GetString("export.notokens"));
List<Backup.Token> tokens = [];

foreach (var tokenHelper in tokenHelpers)
{
Backup.Token token = new()
{
Issuer = tokenHelper.dBToken.Issuer,
Username = tokenHelper.Username,
Secret = tokenHelper.DecryptedSecret,
Algorithm = tokenHelper.dBToken.Algorithm,
Digits = tokenHelper.dBToken.Digits,
Period = tokenHelper.dBToken.Period,
Icon = tokenHelper.dBToken.Icon,
IconType = tokenHelper.dBToken.IconType,
UpdatedTime = tokenHelper.dBToken.UpdatedTime,
CreationTime = tokenHelper.dBToken.CreationTime,
};
Backup.Token token =
new()
{
Issuer = tokenHelper.dBToken.Issuer,
Username = tokenHelper.Username,
Secret = tokenHelper.DecryptedSecret,
Algorithm = tokenHelper.dBToken.Algorithm,
Digits = tokenHelper.dBToken.Digits,
Period = tokenHelper.dBToken.Period,
Icon = tokenHelper.dBToken.Icon,
IconType = tokenHelper.dBToken.IconType,
UpdatedTime = tokenHelper.dBToken.UpdatedTime,
CreationTime = tokenHelper.dBToken.CreationTime,
};
if (tokenHelper.dBToken.EncryptedNotes != null)
{
token.Notes = Auth.GetMainEncryptionHelper().DecryptBytesToString(tokenHelper.dBToken.EncryptedNotes);
token.Notes = Auth.GetMainEncryptionHelper()
.DecryptBytesToString(tokenHelper.dBToken.EncryptedNotes);
}
tokens.Add(token);
}

Backup backup = new()
{
Version = 1,
Tokens = tokens.ToArray(),
};
Backup backup = new() { Version = 1, Tokens = [.. tokens], };

byte[] data = JsonSerializer.SerializeToUtf8Bytes(backup);

Expand All @@ -64,7 +67,13 @@ public async Task Export(string? path, byte[]? password)
byte[] result = new byte[prefix.Length + salt.Length + encryptedData.Length];
Buffer.BlockCopy(prefix, 0, result, 0, prefix.Length);
Buffer.BlockCopy(salt, 0, result, prefix.Length, salt.Length);
Buffer.BlockCopy(encryptedData, 0, result, prefix.Length + salt.Length, encryptedData.Length);
Buffer.BlockCopy(
encryptedData,
0,
result,
prefix.Length + salt.Length,
encryptedData.Length
);

await File.WriteAllBytesAsync(path, result);
}
Expand Down
13 changes: 7 additions & 6 deletions Guard/Core/Icons/IconManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ public static string[] GetIconNames()
private static readonly string defaultIconSVG =
"<svg id=\"Layer_2\" enable-background=\"new 0 0 100 100\" viewBox=\"0 0 100 100\" xmlns=\"http://www.w3.org/2000/svg\"><linearGradient id=\"SVGID_1_\" gradientUnits=\"userSpaceOnUse\" x1=\"-9.237\" x2=\"99.736\" y1=\"-.469\" y2=\"107.441\"><stop offset=\".1507538\" stop-color=\"#1da3ff\"/><stop offset=\".8542714\" stop-color=\"#0048c6\"/></linearGradient><path d=\"m89.1815948 10.7360106c-10.9813919-10.9813921-28.7712554-10.9813251-39.7526474.0000667-7.5442123 7.5442114-9.9051704 18.3169556-7.0720215 27.8926964l-39.7745678 39.7745695v19.0856628l18.7671616.010994-.6588573-11.9917297 10.9155064.5931091-.5930386-10.9155731 10.904583.6040268-.5930405-10.9155731 11.9916611.6589203 7.9725037-7.972496c9.5758095 2.8332176 20.3485527.4722557 27.8927574-7.0719528 10.9813919-10.981392 10.9813919-28.7713281-.0000001-39.7527209zm-5.6883468 5.6883535c3.4591599 3.4591656 3.4591599 9.0816326 0 12.5407982-3.4591675 3.4591656-9.0816345 3.4591656-12.540802 0-3.4590988-3.4590988-3.4590988-9.0815659.0000687-12.5407314s9.0816345-3.4591656 12.5407333-.0000668z\" fill=\"url(#SVGID_1_)\"/></svg>";

private static readonly TotpIcon defaultIcon = new TotpIcon
{
Type = IconType.Default,
Svg = defaultIconSVG,
Name = "default",
};
private static readonly TotpIcon defaultIcon =
new()
{
Type = IconType.Default,
Svg = defaultIconSVG,
Name = "default",
};

public static TotpIcon GetIcon(string name, IconColor color, IconType type)
{
Expand Down
12 changes: 12 additions & 0 deletions Guard/Core/Models/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ internal enum SortOrderSetting
CREATED_DESC
}

internal enum LockTimeSetting
{
Never,
ThirtySeconds,
OneMinute,
FiveMinutes,
TenMinutes,
ThirtyMinutes,
OneHour
}

internal class AppSettings
{
public ThemeSetting Theme { get; set; } = ThemeSetting.System;
Expand All @@ -31,5 +42,6 @@ internal class AppSettings
public SortOrderSetting SortOrder { get; set; } = SortOrderSetting.ISSUER_ASC;
public bool ShowTokenCardIntro { get; set; } = true;
public bool MinimizeToTray { get; set; } = false;
public LockTimeSetting LockTime { get; set; } = LockTimeSetting.TenMinutes;
}
}
2 changes: 1 addition & 1 deletion Guard/Core/NavigationContextManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal class NavigationContextManager

internal static void ClearContext()
{
CurrentContext = new NavigationContext();
CurrentContext = [];
}
}
}
67 changes: 59 additions & 8 deletions Guard/Core/Security/InactivityDetector.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,81 @@
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows;
using Guard.Core.Models;
using Guard.Core.Storage;

namespace Guard.Core.Security
{
internal class InactivityDetector
{
private static DateTime? lostFocusTime;
private static bool isInactive = false;
private static System.Timers.Timer? timer;
private static MainWindow? mainWindow;

public static void OnFocusLost()
{
lostFocusTime = DateTime.Now;
isInactive = true;
}

public static void OnFocusGained()
{
lostFocusTime = null;
isInactive = false;
}

public static void Start()
{
timer = new System.Timers.Timer(1000);
if (SettingsManager.Settings.LockTime == LockTimeSetting.Never)
{
return;
}
mainWindow ??= (MainWindow)Application.Current.MainWindow;
timer = new System.Timers.Timer(3000);
timer.Elapsed += TimerTick;
timer.AutoReset = true;
timer.Enabled = true;
}

private static void TimerTick(object? sender, ElapsedEventArgs e)
public static void Stop()
{
// Check if the app is inactive
timer?.Stop();
timer?.Dispose();
}

public static bool IsRunning()
{
return timer?.Enabled ?? false;
}

private static void TimerTick(object? sender, ElapsedEventArgs e)
{
TimeSpan? timeUntilLock = GetTimeUntilLock();
if (timeUntilLock == null)
{
return;
}
TimeSpan lastUserInput = GetLastUserInput();
if (
lastUserInput > timeUntilLock.Value
|| (
lostFocusTime != null
&& lostFocusTime.Value.Add(timeUntilLock.Value) < DateTime.Now
)
)
{
Stop();
mainWindow?.Dispatcher.Invoke(() =>
{
mainWindow?.Logout();
});
}
}

// https://stackoverflow.com/a/5672897
private struct LASTINPUTINFO
{
public uint cbSize;
public uint dwTime;

}

private static TimeSpan GetLastUserInput()
Expand All @@ -55,6 +91,21 @@ private static TimeSpan GetLastUserInput()
[DllImport("user32.dll", SetLastError = true)]
static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

// ---

private static TimeSpan? GetTimeUntilLock()
{
return SettingsManager.Settings.LockTime switch
{
LockTimeSetting.Never => null,
LockTimeSetting.ThirtySeconds => TimeSpan.FromSeconds(30),
LockTimeSetting.OneMinute => TimeSpan.FromMinutes(1),
LockTimeSetting.FiveMinutes => TimeSpan.FromMinutes(5),
LockTimeSetting.TenMinutes => TimeSpan.FromMinutes(10),
LockTimeSetting.ThirtyMinutes => TimeSpan.FromMinutes(30),
LockTimeSetting.OneHour => TimeSpan.FromHours(1),
_ => null,
};
}
}
}
9 changes: 9 additions & 0 deletions Guard/Resources/Strings.de.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@
<system:String x:Key="i.settings.reset.confirm.title">Bist du sicher?</system:String>
<system:String x:Key="i.settings.reset.confirm.content">Möchtest du wirklich alle Daten und Einstellungen der App löschen? Diese Aktion kann nicht rückgängig gemacht werden.</system:String>
<system:String x:Key="i.settings.reset.confirm.btn">Zurücksetzen</system:String>
<system:String x:Key="i.settings.locktime">Sperr-Timer</system:String>
<system:String x:Key="i.settings.locktime.description">App nach Inaktivität automatisch sperren</system:String>
<system:String x:Key="i.settings.locktime.never">Nie</system:String>
<system:String x:Key="i.settings.locktime.thirtyseconds">30 Sekunden</system:String>
<system:String x:Key="i.settings.locktime.oneminute">1 Minute</system:String>
<system:String x:Key="i.settings.locktime.fiveminutes">5 Minuten</system:String>
<system:String x:Key="i.settings.locktime.tenminutes">10 Minuten</system:String>
<system:String x:Key="i.settings.locktime.thirtyminutes">30 Minuten</system:String>
<system:String x:Key="i.settings.locktime.onehour">1 Stunde</system:String>

<!-- Application setup -->
<system:String x:Key="i.welcome.subtext">Vielen Dank fürs Herunterladen! Bitte wähle, wie du deine Token sichern möchtest.</system:String>
Expand Down
9 changes: 9 additions & 0 deletions Guard/Resources/Strings.en.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,15 @@
<system:String x:Key="i.settings.reset.confirm.title">Are you sure?</system:String>
<system:String x:Key="i.settings.reset.confirm.content">Do you really want to delete all tokens and settings? This action cannot be undone.</system:String>
<system:String x:Key="i.settings.reset.confirm.btn">Reset</system:String>
<system:String x:Key="i.settings.locktime">Lock timer</system:String>
<system:String x:Key="i.settings.locktime.description">Lock the app after a certain time of inactivity</system:String>
<system:String x:Key="i.settings.locktime.never">Never</system:String>
<system:String x:Key="i.settings.locktime.thirtyseconds">30 seconds</system:String>
<system:String x:Key="i.settings.locktime.oneminute">1 minute</system:String>
<system:String x:Key="i.settings.locktime.fiveminutes">5 minutes</system:String>
<system:String x:Key="i.settings.locktime.tenminutes">10 minutes</system:String>
<system:String x:Key="i.settings.locktime.thirtyminutes">30 minutes</system:String>
<system:String x:Key="i.settings.locktime.onehour">1 hour</system:String>

<!-- Application setup -->
<system:String x:Key="i.welcome.subtext">Thanks for downloading! Please choose how you would like to secure your tokens.</system:String>
Expand Down
12 changes: 6 additions & 6 deletions Guard/Views/Pages/Add/AddOverview.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using Guard.Core;
using System.Windows;
using System.Windows.Controls;
using Guard.Core;
using Guard.Core.Import.Importer;
using Guard.Views.Controls;
using System.Windows;
using System.Windows.Controls;
using Wpf.Ui.Controls;

namespace Guard.Views.Pages.Add
Expand Down Expand Up @@ -38,11 +38,11 @@ private void Clipboard_Click(object sender, RoutedEventArgs e)

private async void Import(IImporter importer)
{
int total = 0,
duplicate = 0,
tokenID = 0;
try
{
int total;
int duplicate;
int tokenID;
if (importer.Type == IImporter.ImportType.File)
{
Microsoft.Win32.OpenFileDialog openFileDialog =
Expand Down
8 changes: 4 additions & 4 deletions Guard/Views/Pages/ChangePasswordPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Guard.Core;
using Guard.Core.Security;
using System.Text;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Guard.Core;
using Guard.Core.Security;

namespace Guard.Views.Pages
{
Expand All @@ -12,7 +12,7 @@ namespace Guard.Views.Pages
/// </summary>
public partial class ChangePasswordPage : Page
{
private MainWindow mainWindow;
private readonly MainWindow mainWindow;
private readonly bool insecure = false;

public ChangePasswordPage()
Expand Down
25 changes: 25 additions & 0 deletions Guard/Views/Pages/Settings.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,31 @@
<ui:CardControl
Grid.Column="0"
Margin="0,0,0,15"
Icon="{ui:SymbolIcon ClockLock24}">
<ui:CardControl.Header>
<Grid Margin="0,0,35,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ui:TextBlock
Grid.Row="0"
FontTypography="Body"
Text="{DynamicResource i.settings.locktime}" />
<ui:TextBlock
Grid.Row="1"
Foreground="{DynamicResource TextFillColorSecondaryBrush}"
Text="{DynamicResource i.settings.locktime.description}" />
</Grid>
</ui:CardControl.Header>
<ComboBox
x:Name="LockTimeComboBox"
Grid.Column="1"
MinWidth="125" />
</ui:CardControl>
<ui:CardControl
Grid.Column="1"
Margin="15,0,0,15"
Icon="{ui:SymbolIcon Delete24}">
<ui:CardControl.Header>
<Grid Margin="0,0,35,0">
Expand Down
Loading

0 comments on commit cc13bee

Please sign in to comment.