Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0384b43
Merge pull request #53 from YassinLokhat/52-use-sha512-for-hash
YassinLokhat May 31, 2026
afa5225
Initial commit
Jun 12, 2026
ece3a7f
refactor(mvvm): add ObservableObject and RelayCommand primitives
Jun 12, 2026
6a62af6
feat(logging): add TraceSource-based logger and global exception hand…
Jun 12, 2026
325ac77
fix: address misc bugs in MainWindow, hotkeys and view model lifetime
Jun 12, 2026
71f4c54
perf(theme): freeze brushes and expose them as XAML resources
Jun 12, 2026
998217a
feat(theme): complete dark theme coverage and add semantic colors
Jun 12, 2026
9ac10ec
refactor(services): introduce dialog, navigation and session services
Jun 12, 2026
3063e47
refactor(mvvm): remove static Views and database from MainViewModel
Jun 12, 2026
5773d55
refactor(mvvm): move MainWindow menu actions to ViewModel commands
Jun 12, 2026
824f566
perf(viewmodel): debounce filter changes and virtualize ListBoxes
Jun 12, 2026
9f4789d
refactor(mvvm): bind clear/regenerate/copy/insert buttons via commands
Jun 12, 2026
9b97547
feat(security): mark clipboard content sensitive and auto-clear
Jun 12, 2026
77c2ce9
feat(security): read login password through SecurePassword with ZeroF…
Jun 12, 2026
857f351
chore: enable latest-all analyzers and tune severities via .editorconfig
Jun 12, 2026
3bcacd1
Merge branch '_workshop1' into _workshop0
YassinLokhat Jun 12, 2026
e14f164
Merge remote-tracking branch 'origin/master' into _workshop
YassinLokhat Jun 12, 2026
8ccfafb
Merge pull request #53 from YassinLokhat/52-use-sha512-for-hash
YassinLokhat May 31, 2026
a1847c2
Restoring UnitTests.csproj
YassinLokhat Jun 12, 2026
4938b81
Revert GroupBox regression
YassinLokhat Jun 18, 2026
64b75b6
Code cleanup
YassinLokhat Jun 18, 2026
1e6e0de
Merge pull request #55 from YassinLokhat/_workshop
YassinLokhat Jun 18, 2026
3b8c58c
Updating version from 1.0.2 to 1.1.0
YassinLokhat Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
root = true

[*.cs]
indent_style = space
indent_size = 3
end_of_line = crlf
insert_final_newline = true
charset = utf-8-bom
trim_trailing_whitespace = true

# Security/correctness rules are kept as errors.
# The rules below are downgraded because they would require sweeping
# rewrites unrelated to the current focus (architecture, security and
# performance). They can be tightened back to errors progressively.

# Style/design suggestions: keep visible (suggestion) but not breaking.
dotnet_diagnostic.CA1003.severity = suggestion # EventHandler<T> with EventArgs
dotnet_diagnostic.CA1008.severity = suggestion # Enums should have zero value
dotnet_diagnostic.CA1024.severity = suggestion # Use properties where appropriate
dotnet_diagnostic.CA1032.severity = suggestion # Implement standard exception ctors
dotnet_diagnostic.CA1056.severity = suggestion # Uri properties should not be strings
dotnet_diagnostic.CA1030.severity = suggestion # Make method an event
dotnet_diagnostic.CA1031.severity = suggestion # Do not catch general Exception
dotnet_diagnostic.CA1034.severity = suggestion # Nested types should not be visible
dotnet_diagnostic.CA1051.severity = suggestion # Do not declare visible instance fields
dotnet_diagnostic.CA1062.severity = suggestion # Validate arguments of public methods
dotnet_diagnostic.CA1303.severity = none # Localised string literal
dotnet_diagnostic.CA1304.severity = suggestion # ToLower without culture
dotnet_diagnostic.CA1305.severity = suggestion # IFormatProvider missing
dotnet_diagnostic.CA1307.severity = suggestion # StringComparison missing
dotnet_diagnostic.CA1308.severity = suggestion # ToLowerInvariant vs ToUpperInvariant
dotnet_diagnostic.CA1310.severity = suggestion # StartsWith without StringComparison
dotnet_diagnostic.CA1311.severity = suggestion # Specify culture for ToLower/ToUpper
dotnet_diagnostic.CA1707.severity = suggestion # Underscore in identifiers
dotnet_diagnostic.CA1716.severity = suggestion # Reserved language identifier
dotnet_diagnostic.CA1805.severity = suggestion # Default value initialization
dotnet_diagnostic.CA1810.severity = suggestion # Init static fields inline
dotnet_diagnostic.CA1812.severity = suggestion # Avoid uninstantiated internal class
dotnet_diagnostic.CA1815.severity = suggestion # Override Equals on value types
dotnet_diagnostic.CA1819.severity = suggestion # Properties should not return arrays
dotnet_diagnostic.CA1822.severity = suggestion # Mark members static
dotnet_diagnostic.CA1859.severity = suggestion # Use concrete types for performance
dotnet_diagnostic.CA1862.severity = suggestion # StringComparison for Contains
dotnet_diagnostic.CA2227.severity = suggestion # Collection properties should be read-only

# In an executable, internal types are encouraged but the existing
# code base exposes most types publicly. Lower CA1515 to suggestion
# to surface it without breaking the build.
dotnet_diagnostic.CA1515.severity = suggestion

# CA1815 / CA1851 noisy across the codebase; keep as suggestion.
dotnet_diagnostic.CA1851.severity = suggestion

# Security: keep dispose ownership warning visible without breaking.
# Database lifecycle is managed by ISessionService.
dotnet_diagnostic.CA2000.severity = suggestion

# CA5392 (DefaultDllImportSearchPaths): keep as suggestion; the
# P/Invokes target well-known system DLLs (user32, dwmapi).
dotnet_diagnostic.CA5392.severity = suggestion

# The Core/QrCode logic uses multidimensional arrays for matrix algebra.
# Rewriting to jagged arrays would degrade readability without any gain.
dotnet_diagnostic.CA1814.severity = suggestion

# CA1852 (seal internal types): worthwhile but currently noisy in Core.
dotnet_diagnostic.CA1852.severity = suggestion

# CA2201 (reserved exception types): the codebase intentionally throws
# NullReferenceException in some places; flag as suggestion until those
# sites can be reviewed individually.
dotnet_diagnostic.CA2201.severity = suggestion

# CA1001 (type owns disposable field but is not IDisposable): User holds
# a DispatcherTimer; the existing lifetime is tied to Database.Dispose.
# Surfaced as suggestion for a follow-up.
dotnet_diagnostic.CA1001.severity = suggestion

# Cryptography rules: the existing on-disk format relies on MD5/SHA1/
# legacy IV derivation. Changing the algorithms would silently break
# every existing user database, so they are kept as suggestion and the
# follow-up will introduce versioned migrations.
dotnet_diagnostic.CA5350.severity = suggestion
dotnet_diagnostic.CA5351.severity = suggestion
dotnet_diagnostic.CA5394.severity = suggestion
dotnet_diagnostic.CA5401.severity = suggestion

# Visual Studio Editor-only style rules; keep silent when not relevant.
dotnet_diagnostic.IDE0001.severity = suggestion
dotnet_diagnostic.IDE0008.severity = suggestion
dotnet_diagnostic.IDE0028.severity = suggestion
dotnet_diagnostic.IDE0046.severity = suggestion
dotnet_diagnostic.IDE0047.severity = suggestion
dotnet_diagnostic.IDE0048.severity = suggestion
dotnet_diagnostic.IDE0058.severity = suggestion
dotnet_diagnostic.IDE0072.severity = suggestion
dotnet_diagnostic.IDE0290.severity = suggestion
2 changes: 2 additions & 0 deletions Core/Upsilon.Apps.Passkey.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
<Description>A local stored Password Manager Core.</Description>
<AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.1</FileVersion>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest-all</AnalysisLevel>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
Expand Down
49 changes: 48 additions & 1 deletion GUI/WPF/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Windows;
using System.Windows.Threading;
using Upsilon.Apps.Passkey.GUI.WPF.Helper;

namespace Upsilon.Apps.Passkey.GUI.WPF
{
Expand All @@ -7,6 +9,51 @@ namespace Upsilon.Apps.Passkey.GUI.WPF
/// </summary>
public partial class App : Application
{
}
protected override void OnStartup(StartupEventArgs e)
{
DispatcherUnhandledException += _onDispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += _onAppDomainUnhandledException;
TaskScheduler.UnobservedTaskException += _onUnobservedTaskException;

Log.Info($"Application starting (PID {Environment.ProcessId}).");

base.OnStartup(e);
}

protected override void OnExit(ExitEventArgs e)
{
Log.Info($"Application exiting with code {e.ApplicationExitCode}.");
Log.Flush();

base.OnExit(e);
}

private static void _onDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
Log.Error(e.Exception, "Unhandled UI exception");
// The application keeps running; the caller has already shown any
// feedback it needed. Marking as handled prevents the default crash.
e.Handled = true;
}

private static void _onAppDomainUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (e.ExceptionObject is Exception exception)
{
Log.Error(exception, $"Unhandled AppDomain exception (terminating: {e.IsTerminating})");
}
else
{
Log.Error($"Unhandled non-CLR AppDomain exception (terminating: {e.IsTerminating}).");
}

Log.Flush();
}

private static void _onUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
{
Log.Error(e.Exception, "Unobserved task exception");
e.SetObserved();
}
}
}
2 changes: 1 addition & 1 deletion GUI/WPF/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Windows;
using System.Windows;

[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
Expand Down
22 changes: 22 additions & 0 deletions GUI/WPF/Helper/AppInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Reflection;

namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
{
/// <summary>
/// Read-only metadata about the running assembly. Centralises the title shown
/// in window headers so it is no longer scattered across view-models.
/// </summary>
public static class AppInfo
{
private static readonly Lazy<string> _title = new(_buildTitle);

public static string Title => _title.Value;

private static string _buildTitle()
{
AssemblyName name = Assembly.GetExecutingAssembly().GetName();
string version = name.Version?.ToString(3) ?? "0.0.0";
return $"{name.Name} v{version}";
}
}
}
58 changes: 58 additions & 0 deletions GUI/WPF/Helper/AsyncRelayCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Windows.Input;

namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
{
/// <summary>
/// Asynchronous variant of <see cref="RelayCommand"/>. The command reports
/// <see cref="CanExecute"/> as <c>false</c> while the previous execution is
/// still pending, preventing re-entrancy.
/// </summary>
public sealed class AsyncRelayCommand : ICommand
{
private readonly Func<object?, Task> _execute;
private readonly Predicate<object?>? _canExecute;

public AsyncRelayCommand(Func<Task> execute, Func<bool>? canExecute = null)
: this(_ => execute(), canExecute is null ? null : _ => canExecute())
{
ArgumentNullException.ThrowIfNull(execute);
}

public AsyncRelayCommand(Func<object?, Task> execute, Predicate<object?>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}

public event EventHandler? CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}

public bool IsRunning { get; private set; }

public bool CanExecute(object? parameter) => !IsRunning && (_canExecute?.Invoke(parameter) ?? true);

public async void Execute(object? parameter)
{
if (!CanExecute(parameter))
{
return;
}

IsRunning = true;
CommandManager.InvalidateRequerySuggested();

try
{
await _execute(parameter).ConfigureAwait(true);
}
finally
{
IsRunning = false;
CommandManager.InvalidateRequerySuggested();
}
}
}
}
Loading
Loading