Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Popup, theme, optimized search in popup, MD updates, fast dialog animation enhancement, added interprocess WinApi communication #130

Merged
merged 37 commits into from
Oct 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1c7fdfb
Made focusing more consistent, fixed XAML issue
Erapchu Sep 9, 2022
1ebb2e5
Switch for args key, don't check twice
Erapchu Sep 13, 2022
780a848
UI popup changes
Erapchu Sep 13, 2022
bb8aa04
Handle Ctrl + V and Ctrl + Shift + V
Erapchu Sep 13, 2022
ff27a76
Added textblock for info
Erapchu Sep 13, 2022
db5f8af
Handle Enter and Enter + Shift
Erapchu Sep 14, 2022
16f716f
Switch to Ctrl + Enter due some window handle Shift and Ctrl + V simu…
Erapchu Sep 14, 2022
fd34bd4
Small refactoring
Erapchu Sep 14, 2022
11a132b
Handle scroll into view from search textbox
Erapchu Sep 14, 2022
5a6a975
System theme settings enhancement
Erapchu Sep 19, 2022
41acd6f
UI changes
Erapchu Sep 19, 2022
1c4962e
Refactoring (Code cleanup)
Erapchu Sep 19, 2022
57e2eaf
Optimized search in popup
Erapchu Sep 27, 2022
f51218e
Optimized search in main window - scroll only if required
Erapchu Sep 27, 2022
401b2be
Duplicate analyzer issue fixes
Erapchu Sep 28, 2022
73bb916
Updated MaterialDesignThemes NuGet to 4.6.1 and fixes
Erapchu Sep 28, 2022
cd4b16c
Back to fast dialog host
Erapchu Sep 29, 2022
e31a611
Optimized dialog host fast template
Erapchu Sep 30, 2022
4ed14f5
Added translation to processing control
Erapchu Oct 1, 2022
ac8b4bd
Code moving and refactoring
Erapchu Oct 1, 2022
a6d1347
Refactoring (code cleanup)
Erapchu Oct 1, 2022
ff0f889
Moved controls to subfolder
Erapchu Oct 1, 2022
c630f0c
Renamed credentialsdialog
Erapchu Oct 1, 2022
7616584
Moved cred details control to subfolder
Erapchu Oct 1, 2022
4f23aed
Removed hotkey service because it unused here
Erapchu Oct 1, 2022
26a155c
Renamed enum and view model for credential details
Erapchu Oct 1, 2022
c3945dd
Using multilanguage for tooltip
Erapchu Oct 1, 2022
f3ee90b
Moved windows to subfolder
Erapchu Oct 1, 2022
2e9d160
Moved processing control to subfolder
Erapchu Oct 1, 2022
5c51aee
Renamed processing dialog content
Erapchu Oct 1, 2022
b8b1ba4
Moved message box to subfolder
Erapchu Oct 1, 2022
d70cc6f
Moved input box to subfolder
Erapchu Oct 1, 2022
43e619a
Made helper for processing dialog
Erapchu Oct 1, 2022
290bcd3
Using async duplicate lock and async lock + await
Erapchu Oct 1, 2022
9736650
Split login and logout functions
Erapchu Oct 1, 2022
42198fa
Added interprocess simple WinApi communication
Erapchu Oct 1, 2022
01293d0
Moved logic to hidden interprocess window
Erapchu Oct 1, 2022
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
27 changes: 27 additions & 0 deletions SinglePass.Language/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions SinglePass.Language/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -325,4 +325,13 @@
<data name="CanTMergeCredentials" xml:space="preserve">
<value>Can't merge credentials</value>
</data>
<data name="ToPasteLogin" xml:space="preserve">
<value>to paste login</value>
</data>
<data name="ToPastePassword" xml:space="preserve">
<value>to paste password</value>
</data>
<data name="Theme" xml:space="preserve">
<value>Theme</value>
</data>
</root>
9 changes: 9 additions & 0 deletions SinglePass.Language/Properties/Resources.ru.resx
Original file line number Diff line number Diff line change
Expand Up @@ -345,4 +345,13 @@
<data name="NoAnyChanges" xml:space="preserve">
<value>Нет изменений</value>
</data>
<data name="ToPasteLogin" xml:space="preserve">
<value>для вставки логина</value>
</data>
<data name="ToPastePassword" xml:space="preserve">
<value>для вставки пароля</value>
</data>
<data name="Theme" xml:space="preserve">
<value>Тема</value>
</data>
</root>
9 changes: 8 additions & 1 deletion SinglePass.WPF/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@
using SinglePass.WPF.Settings;
using SinglePass.WPF.ViewModels;
using SinglePass.WPF.Views;
using SinglePass.WPF.Views.Windows;
using System;
using System.Threading;
using System.Windows;
using System.Windows.Interop;

namespace SinglePass.WPF
{
Expand Down Expand Up @@ -85,8 +87,9 @@ private static IServiceProvider ConfigureServices(IConfiguration configuration)
services.AddScoped<PasswordsViewModel>();
services.AddScoped<CloudSyncViewModel>();
services.AddScoped<SettingsViewModel>();
services.AddScoped<CredentialsDialogViewModel>();
services.AddScoped<CredentialsDetailsViewModel>();

// Popup
services.AddTransient<PopupWindow>();
services.AddTransient<PopupViewModel>();

Expand Down Expand Up @@ -132,6 +135,9 @@ private void Application_Startup(object sender, StartupEventArgs e)
var welcomeWindow = new WelcomeWindow();
welcomeWindow.Show();

var hiw = new HiddenInterprocessWindow();
hiw.InitWithoutShowing();

_logger = Services.GetService<ILogger<App>>();
_logger.LogInformation("Log session started!");

Expand Down Expand Up @@ -165,6 +171,7 @@ private void Application_Startup(object sender, StartupEventArgs e)
}
else
{
InterprocessHelper.ShowMainWindow();
Shutdown();
}
}
Expand Down
78 changes: 17 additions & 61 deletions SinglePass.WPF/Authorization/Brokers/BaseAuthorizationBroker.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
using SinglePass.WPF.Authorization.Helpers;
using SinglePass.WPF.Authorization.Responses;
using SinglePass.WPF.Authorization.TokenHolders;
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
Expand All @@ -16,6 +12,7 @@ public abstract class BaseAuthorizationBroker : IAuthorizationBroker
{
private readonly IHttpClientFactory _httpClientFactory;

protected string RedirectUri { get; set; }
public ITokenHolder TokenHolder { get; }

public BaseAuthorizationBroker(IHttpClientFactory httpClientFactory, ITokenHolder tokenHolder)
Expand All @@ -26,24 +23,24 @@ public BaseAuthorizationBroker(IHttpClientFactory httpClientFactory, ITokenHolde

public async Task AuthorizeAsync(CancellationToken cancellationToken)
{
var redirectUri = BuildRedirectUri();
var authorizationUri = BuildAuthorizationUri(redirectUri);
using var listener = OAuthHelper.StartListener(redirectUri);
BuildRedirectUri();
var authorizationUri = BuildAuthorizationUri();
using var listener = OAuthHelper.StartListener(RedirectUri);
OAuthHelper.OpenBrowser(authorizationUri);
var response = await GetResponseFromListener(listener, cancellationToken);
var response = await OAuthHelper.GetResponseFromListener(listener, BuildClosePageResponse(), cancellationToken);
if (string.IsNullOrWhiteSpace(response?.Code))
{
throw new Exception("Code was empty!");
}
var tokenResponse = await RetrieveToken(response.Code, redirectUri, cancellationToken);
var tokenResponse = await RetrieveToken(response.Code, cancellationToken);
await TokenHolder.SetAndSaveToken(tokenResponse, cancellationToken);
}

public async Task RefreshAccessToken(CancellationToken cancellationToken)
{
var client = _httpClientFactory.CreateClient();
var refreshTokenEndpointUri = BuildRefreshAccessTokenEndpointUri();
var postData = BuildRequestForRefreshToken();
var refreshTokenEndpointUri = BuildRefreshTokenEndpointUri();
var postData = BuildRefreshTokenRequest();
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(refreshTokenEndpointUri))
{
Content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded")
Expand All @@ -58,61 +55,20 @@ public async Task RevokeToken(CancellationToken cancellationToken)
{
await TokenHolder.RemoveToken();
var client = _httpClientFactory.CreateClient();
var revokeTokenEndpointUri = BuildRevokeTokenEndpointUri();
var revokeTokenEndpointUri = BuildTokenRevokeEndpointUri();
var stringContent = new StringContent(string.Empty, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = await client.PostAsync(revokeTokenEndpointUri, stringContent, cancellationToken);
using var content = response.Content;
var json = await content.ReadAsStringAsync(cancellationToken);
}

private async Task<AuthorizationCodeResponseUrl> GetResponseFromListener(HttpListener listener, CancellationToken cancellationToken)
{
HttpListenerContext context;
// Set up cancellation. HttpListener.GetContextAsync() doesn't accept a cancellation token,
// the HttpListener needs to be stopped which immediately aborts the GetContextAsync() call.
using (cancellationToken.Register(listener.Stop))
{
// Wait to get the authorization code response.
try
{
context = await listener.GetContextAsync().ConfigureAwait(false);
}
catch (Exception) when (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
// Next line will never be reached because cancellation will always have been requested in this catch block.
// But it's required to satisfy compiler.
throw new InvalidOperationException();
}
catch
{
throw;
}
}
NameValueCollection coll = context.Request.QueryString;

// Write a "close" response.
var bytes = Encoding.UTF8.GetBytes(BuildClosePageResponse());
context.Response.ContentLength64 = bytes.Length;
context.Response.SendChunked = false;
context.Response.KeepAlive = false;
var output = context.Response.OutputStream;
await output.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
await output.FlushAsync(cancellationToken).ConfigureAwait(false);
output.Close();
context.Response.Close();

// Create a new response URL with a dictionary that contains all the response query parameters.
return new AuthorizationCodeResponseUrl(coll.AllKeys.ToDictionary(k => k, k => coll[k]));
}

private async Task<string> RetrieveToken(string code, string redirectUri, CancellationToken cancellationToken)
private async Task<string> RetrieveToken(string code, CancellationToken cancellationToken)
{
string result;

var client = _httpClientFactory.CreateClient();
var tokenEndpointUri = BuildTokenEndpointUri();
var postData = BuildRequestForToken(code, redirectUri);
var postData = BuildTokenRequest(code);
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(tokenEndpointUri))
{
Content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded")
Expand All @@ -130,12 +86,12 @@ protected virtual string BuildClosePageResponse()
return "Authorization success, you can return to application";
}

protected abstract string BuildRedirectUri();
protected abstract string BuildAuthorizationUri(string redirectUri);
protected abstract void BuildRedirectUri();
protected abstract string BuildAuthorizationUri();
protected abstract string BuildTokenEndpointUri();
protected abstract string BuildRequestForToken(string code, string redirectUri);
protected abstract string BuildRefreshAccessTokenEndpointUri();
protected abstract string BuildRequestForRefreshToken();
protected abstract string BuildRevokeTokenEndpointUri();
protected abstract string BuildTokenRequest(string code);
protected abstract string BuildRefreshTokenEndpointUri();
protected abstract string BuildRefreshTokenRequest();
protected abstract string BuildTokenRevokeEndpointUri();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,43 +25,43 @@ public GoogleAuthorizationBroker(
_config = options.Value;
}

protected override string BuildAuthorizationUri(string redirectUri)
protected override string BuildAuthorizationUri()
{
return "https://accounts.google.com/o/oauth2/v2/auth?" +
$"scope={HttpUtility.UrlEncode(_config.Scopes)}&" +
"access_type=offline&" +
"include_granted_scopes=true&" +
"response_type=code&" +
"state=state_parameter_passthrough_value&" +
$"redirect_uri={HttpUtility.UrlEncode(redirectUri)}&" +
$"redirect_uri={HttpUtility.UrlEncode(RedirectUri)}&" +
$"client_id={_config.ClientId}";
}

protected override string BuildRedirectUri()
protected override void BuildRedirectUri()
{
var unusedPort = OAuthHelper.GetRandomUnusedPort();
return $"http://localhost:{unusedPort}/";
RedirectUri = $"http://localhost:{unusedPort}/";
}

protected override string BuildRefreshAccessTokenEndpointUri()
protected override string BuildRefreshTokenEndpointUri()
{
return "https://oauth2.googleapis.com/token";
}

protected override string BuildRequestForRefreshToken()
protected override string BuildRefreshTokenRequest()
{
return $"client_id={_config.ClientId}&" +
$"client_secret={_config.ClientSecret}&" +
$"refresh_token={TokenHolder.Token.RefreshToken}&" +
$"grant_type=refresh_token";
}

protected override string BuildRequestForToken(string code, string redirectUri)
protected override string BuildTokenRequest(string code)
{
return $"code={code}&" +
$"client_id={_config.ClientId}&" +
$"client_secret={_config.ClientSecret}&" +
$"redirect_uri={HttpUtility.UrlEncode(redirectUri)}&" +
$"redirect_uri={HttpUtility.UrlEncode(RedirectUri)}&" +
$"grant_type=authorization_code";
}

Expand All @@ -70,7 +70,7 @@ protected override string BuildTokenEndpointUri()
return "https://oauth2.googleapis.com/token";
}

protected override string BuildRevokeTokenEndpointUri()
protected override string BuildTokenRevokeEndpointUri()
{
return $"https://oauth2.googleapis.com/revoke?token={TokenHolder.Token.RefreshToken}";
}
Expand Down
4 changes: 2 additions & 2 deletions SinglePass.WPF/Authorization/Brokers/IAuthorizationBroker.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Threading;
using SinglePass.WPF.Authorization.TokenHolders;
using System.Threading;
using System.Threading.Tasks;
using SinglePass.WPF.Authorization.TokenHolders;

namespace SinglePass.WPF.Authorization.Brokers
{
Expand Down
53 changes: 52 additions & 1 deletion SinglePass.WPF/Authorization/Helpers/OAuthHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
using System.Diagnostics;
using SinglePass.WPF.Authorization.Responses;
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SinglePass.WPF.Authorization.Helpers
{
Expand Down Expand Up @@ -34,5 +41,49 @@ public static void OpenBrowser(string uri)
uri = System.Text.RegularExpressions.Regex.Replace(uri, @"(\\+)$", @"$1$1");
Process.Start(new ProcessStartInfo("cmd", $"/c start \"\" \"{uri}\"") { CreateNoWindow = true });
}

public static async Task<AuthorizationCodeResponseUrl> GetResponseFromListener(
HttpListener listener,
string responseHtmlText,
CancellationToken cancellationToken = default)
{
HttpListenerContext context;
// Set up cancellation. HttpListener.GetContextAsync() doesn't accept a cancellation token,
// the HttpListener needs to be stopped which immediately aborts the GetContextAsync() call.
using (cancellationToken.Register(listener.Stop))
{
// Wait to get the authorization code response.
try
{
context = await listener.GetContextAsync().ConfigureAwait(false);
}
catch (Exception) when (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
// Next line will never be reached because cancellation will always have been requested in this catch block.
// But it's required to satisfy compiler.
throw new InvalidOperationException();
}
catch
{
throw;
}
}
NameValueCollection coll = context.Request.QueryString;

// Write a "close" response.
var bytes = Encoding.UTF8.GetBytes(responseHtmlText);
context.Response.ContentLength64 = bytes.Length;
context.Response.SendChunked = false;
context.Response.KeepAlive = false;
var output = context.Response.OutputStream;
await output.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
await output.FlushAsync(cancellationToken).ConfigureAwait(false);
output.Close();
context.Response.Close();

// Create a new response URL with a dictionary that contains all the response query parameters.
return new AuthorizationCodeResponseUrl(coll.AllKeys.ToDictionary(k => k, k => coll[k]));
}
}
}
4 changes: 2 additions & 2 deletions SinglePass.WPF/Authorization/TokenHolders/ITokenHolder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Threading;
using SinglePass.WPF.Authorization.Responses;
using System.Threading;
using System.Threading.Tasks;
using SinglePass.WPF.Authorization.Responses;

namespace SinglePass.WPF.Authorization.TokenHolders
{
Expand Down
Loading