diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..153edc3 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,28 @@ +#!/usr/bin/env sh +set -eu + +if git diff --cached --quiet -- \ + "package.json" \ + "package-lock.json" \ + "Steam Desktop Authenticator/tailwind.config.js" \ + "Steam Desktop Authenticator/ui/tailwind-input.css" \ + ':(glob)Steam Desktop Authenticator/ui/**/*.html'; then + if ! git diff --cached --quiet -- "Steam Desktop Authenticator/ui/assets/css/app.css"; then + echo "Stage the UI source changes with app.css so the stylesheet matches the committed source." >&2 + exit 1 + fi + exit 0 +fi + +if ! git diff --quiet -- \ + "package.json" \ + "package-lock.json" \ + "Steam Desktop Authenticator/tailwind.config.js" \ + "Steam Desktop Authenticator/ui/tailwind-input.css" \ + ':(glob)Steam Desktop Authenticator/ui/**/*.html'; then + echo "Stage or stash all UI source changes before committing so app.css is generated from the staged source." >&2 + exit 1 +fi + +npm run build:ui +git add -- "Steam Desktop Authenticator/ui/assets/css/app.css" diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index fbbd6aa..637d8bb 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -67,6 +67,18 @@ jobs: with: fetch-depth: 0 + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Install UI build dependencies + run: npm ci + + - name: Verify generated UI stylesheet + run: npm run check:ui + - name: Install .NET Core uses: actions/setup-dotnet@v6 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1d1e907..132621a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,11 +36,33 @@ jobs: - name: Checkout uses: actions/checkout@v7 + - name: Install Node.js + uses: actions/setup-node@v6 + with: + node-version: 22 + cache: npm + + - name: Install UI build dependencies + run: npm ci + - name: Install .NET Core uses: actions/setup-dotnet@v6 with: dotnet-version: 8.0.x + - name: Generate and commit local UI assets + run: | + npm run build:ui + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add "Steam Desktop Authenticator\ui\assets\css\app.css" + if (git diff --cached --quiet) { + Write-Host "Local UI stylesheet is current." + } else { + git commit -m "Build local UI stylesheet" + git push + } + - name: Update Version Numbers run: | $version = $env:VERSION @@ -78,25 +100,28 @@ jobs: - name: Clean and publish the release package run: | dotnet clean $env:Solution_Name -c Release - dotnet publish "Steam Desktop Authenticator\Steam Desktop Authenticator.csproj" -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=false -p:PublishTrimmed=false -p:EnableCompressionInSingleFile=false -p:DebugSymbols=false -o publish\ASDA + dotnet publish "Launcher\Launcher.csproj" -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=false -p:PublishTrimmed=false -p:EnableCompressionInSingleFile=false -p:DebugSymbols=false -o publish\ASDA Remove-Item publish\ASDA\*.pdb, publish\ASDA\*.xml, publish\ASDA\*.config -Force -ErrorAction SilentlyContinue + Remove-Item publish\ASDA\bin\*.pdb, publish\ASDA\bin\*.xml, publish\ASDA\bin\*.config -Force -ErrorAction SilentlyContinue $requiredPaths = @( "publish\ASDA\Steam Desktop Authenticator.exe", - "publish\ASDA\WebView2Loader.dll", - "publish\ASDA\runtimes", - "publish\ASDA\app\wwwroot\index.html", - "publish\ASDA\app\wwwroot\login.html", - "publish\ASDA\app\wwwroot\input.html" + "publish\ASDA\bin\Steam Desktop Authenticator.exe", + "publish\ASDA\bin\WebView2Loader.dll", + "publish\ASDA\bin\runtimes", + "publish\ASDA\bin\app\ui\index.html", + "publish\ASDA\bin\app\ui\login.html", + "publish\ASDA\bin\app\ui\input.html", + "publish\ASDA\bin\app\ui\assets\css\app.css", + "publish\ASDA\bin\app\ui\assets\fonts\inter-latin.woff2", + "publish\ASDA\bin\app\ui\assets\fonts\jetbrains-mono-latin.woff2", + "publish\ASDA\bin\app\ui\assets\icons\check.svg" ) $missingPaths = $requiredPaths | Where-Object { -not (Test-Path $_) } if ($missingPaths) { throw "Release package is incomplete: $($missingPaths -join ', ')" } - if (Test-Path publish\ASDA\bin) { - throw "Release package unexpectedly contains the legacy launcher bin folder." - } - name: Zip Build Output run: | diff --git a/.gitignore b/.gitignore index 126044e..c9d8700 100644 --- a/.gitignore +++ b/.gitignore @@ -33,8 +33,8 @@ bld/ # Visual Studio 2015/2017 cache/options directory .vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ +# Uncomment if you have tasks that create the project's static files in ui +#ui/ # Visual Studio 2017 auto generated files Generated\ Files/ @@ -554,6 +554,8 @@ maFiles/ *.maFile # Steam Guard authenticator exports contain account secrets. Never commit them. *.[mM][aA][fF][iI][lL][eE] +# Ignore every casing and location of the account-data directory, including manifest.json within it. +**/[mM][aA][fF][iI][lL][eE][sS]/ # Debug and JSON files generated during our analysis raw_issues.json diff --git a/Launcher/Launcher.csproj b/Launcher/Launcher.csproj index d32eab5..b85db31 100644 --- a/Launcher/Launcher.csproj +++ b/Launcher/Launcher.csproj @@ -12,7 +12,7 @@ - + diff --git a/Launcher/Program.cs b/Launcher/Program.cs index 577ce4a..781e612 100644 --- a/Launcher/Program.cs +++ b/Launcher/Program.cs @@ -1,5 +1,6 @@ using System.Diagnostics; +const string DataDirectoryEnvironmentVariable = "ASDA_DATA_DIRECTORY"; string binPath = Path.Combine(AppContext.BaseDirectory, "bin", "Steam Desktop Authenticator.exe"); if (File.Exists(binPath)) @@ -10,6 +11,7 @@ WorkingDirectory = Path.Combine(AppContext.BaseDirectory, "bin"), UseShellExecute = false }; + processInfo.Environment[DataDirectoryEnvironmentVariable] = AppContext.BaseDirectory; // Pass along any arguments foreach (var arg in args) diff --git a/README.md b/README.md index ec7985e..451f14e 100644 --- a/README.md +++ b/README.md @@ -45,23 +45,104 @@ All credit for the original design and implementation goes to Jessecar96 and the - Added multi-account trade-confirmation monitoring and notification support. - Added optional diagnostic logging so users can view and report issues. -## How to Compile / Build +## Build from Source on Windows -If you prefer to compile the program from source rather than downloading the pre-built binaries, follow these steps: +The committed UI assets are included in a normal .NET publish, so Node.js is not required for an ordinary release build. -1. **Install Prerequisites**: You must have the **[.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)** installed on your machine. -2. **Clone the Repository**: - ```bash +### Prerequisites + +- Windows 10 or later, 64-bit +- **[.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)** + +To run the built application, install the .NET 8 Desktop Runtime if the SDK is not installed, and install the [Microsoft Edge WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/#download-section). + +### Build a release package from Command Prompt + +1. Clone the repository and open the cloned folder: + + ```powershell git clone https://github.com/AstroZer01/Astro-Steam-Desktop-Authenticator.git cd Astro-Steam-Desktop-Authenticator ``` -3. **Publish the Application**: - Create the same portable release layout used by the release workflow: - ```bash - dotnet publish "Steam Desktop Authenticator/Steam Desktop Authenticator.csproj" -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=false -p:PublishTrimmed=false -p:EnableCompressionInSingleFile=false -p:DebugSymbols=false -o publish/ASDA + +2. From the repository root, run this command in Command Prompt: + + ```bat + dotnet publish "Launcher\Launcher.csproj" -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=false -p:PublishTrimmed=false -p:EnableCompressionInSingleFile=false -p:DebugSymbols=false -o "publish\ASDA" + ``` + + Close any copy of the app running from that folder before rebuilding so Windows can replace its executable. `dotnet publish` restores the required Windows x64 dependencies automatically and preserves an existing root `publish\ASDA\maFiles` folder. + +3. When `dotnet publish` completes successfully, start: + + ```text + publish\ASDA\Steam Desktop Authenticator.exe ``` - *Alternatively, open `SteamDesktopAuthenticator.sln` in **Visual Studio 2022**, right-click the **Steam Desktop Authenticator** project, and choose **Publish** (not Build). Use a Folder target with the same Windows x64, framework-dependent, single-file settings and output folder shown above.* -4. **Run the Application**: Start `publish/ASDA/Steam Desktop Authenticator.exe`. Keep the complete `publish/ASDA` folder together; it contains the WebView files and runtime dependencies. The `maFiles` folder is created there on first launch and stores your local account data. + +Keep the complete `publish\ASDA` folder together in a folder your Windows account can write to; do not install this portable package under `Program Files`. The root executable is the Launcher; it starts the real app from `publish\ASDA\bin`. The `bin` folder contains the WebView files and runtime dependencies, while the application creates `publish\ASDA\maFiles` beside the Launcher on first launch. Back up that `maFiles` folder securely. + +#### Reusable personal build script + +If you keep a personal `.bat` file outside the repository, use this version. Change the two paths to your own locations. It intentionally never deletes `%OUTPUT%`, so an existing `%OUTPUT%\maFiles\manifest.json` and its `.maFile` account secrets survive every rebuild. `dotnet clean` only removes intermediate files from the source checkout; it does not affect the release folder. + +```bat +@echo off +setlocal EnableExtensions +title Build Astro Steam Desktop Assistant + +set "PROJECT_ROOT=E:\Desktop\cursor projects\SteamDesktopAuthenticator-master" +set "OUTPUT=E:\Desktop\Astro Steam Desktop Assistant" + +cd /d "%PROJECT_ROOT%" || goto :failed + +dotnet clean "SteamDesktopAuthenticator.sln" -c Release || goto :failed +dotnet publish "Launcher\Launcher.csproj" -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=false -p:PublishTrimmed=false -p:EnableCompressionInSingleFile=false -p:DebugSymbols=false -o "%OUTPUT%" || goto :failed + +del /q "%OUTPUT%\*.pdb" "%OUTPUT%\*.xml" "%OUTPUT%\*.config" 2>nul +del /q "%OUTPUT%\bin\*.pdb" "%OUTPUT%\bin\*.xml" "%OUTPUT%\bin\*.config" 2>nul + +echo. +echo Compilation finished: +echo %OUTPUT% +pause +exit /b 0 + +:failed +echo. +echo Compilation failed. Close any running copy of the app and try again. +pause +exit /b 1 +``` + +Never use `rmdir /s`, `del /s`, or a file-cleanup tool on the release root. If you need to discard a release, first copy `maFiles` somewhere secure, then remove only the files you explicitly intend to replace. Git ignores `maFiles` folders and `.maFile` exports so account secrets do not enter commits, pull requests, or releases from this repository. + +### Build with Visual Studio 2022 + +1. Open `SteamDesktopAuthenticator.sln`. +2. Select the `Release` configuration. +3. In Solution Explorer, right-click **Launcher** — not **Steam Desktop Authenticator** — then choose **Publish**. +4. Select a **Folder** target. Use `publish\ASDA` as the target location and select **Windows x64** with **Framework-dependent** deployment. +5. In publish settings, enable **Produce single file** and leave trimming disabled, then select **Publish**. + +Visual Studio's regular **Build** command places development output under `bin`; use **Publish** when you want the portable release folder described above. + +### UI stylesheet for contributors + +The WebView UI ships with committed, local CSS and fonts. Normal .NET builds use the committed `ui/assets/css/app.css` and do not require Node.js. + +After changing an HTML template, `tailwind.config.js`, or `ui/tailwind-input.css`, install **[Node.js 22 LTS](https://nodejs.org/)** and run: + +```powershell +npm ci # once after cloning or when package-lock.json changes +npm run build:ui +``` + +Commit the resulting `Steam Desktop Authenticator/ui/assets/css/app.css` with the UI change. + +- `npm run build:ui` regenerates `Steam Desktop Authenticator/ui/assets/css/app.css`. +- `npm run check:ui` regenerates the stylesheet and fails if the generated file was not committed. + +The hook runs only when staged UI templates, the Tailwind configuration, or the Tailwind input stylesheet change. CI verifies the same invariant, and the release workflow regenerates the stylesheet before publishing.

diff --git a/Steam Desktop Authenticator/ApplicationPaths.cs b/Steam Desktop Authenticator/ApplicationPaths.cs index 17ec6cc..bbf5fe2 100644 --- a/Steam Desktop Authenticator/ApplicationPaths.cs +++ b/Steam Desktop Authenticator/ApplicationPaths.cs @@ -5,19 +5,53 @@ namespace Steam_Desktop_Authenticator { internal static class ApplicationPaths { - private static readonly string[] RequiredWebResourceNames = { "index.html", "login.html", "input.html" }; + public const string DataDirectoryEnvironmentVariable = "ASDA_DATA_DIRECTORY"; + + private static readonly string[] RequiredWebResourceNames = + { + "index.html", + "login.html", + "input.html", + Path.Combine("assets", "css", "app.css"), + Path.Combine("assets", "fonts", "inter-latin.woff2"), + Path.Combine("assets", "fonts", "jetbrains-mono-latin.woff2"), + Path.Combine("assets", "icons", "check.svg") + }; public static string InstallDirectory => Path.TrimEndingDirectorySeparator(AppContext.BaseDirectory); - public static string WebRootDirectory => Path.Combine(InstallDirectory, "app", "wwwroot"); + public static string DataDirectory => ResolveDataDirectory(); + + public static string UiDirectory => Path.Combine(InstallDirectory, "app", "ui"); + + public static string WebViewUserDataDirectory => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "Astro Steam Desktop Authenticator", + "WebView2"); - public static string WebViewUserDataDirectory => Path.Combine(InstallDirectory, "app", "webview2"); + private static string ResolveDataDirectory() + { + string configuredDataDirectory = Environment.GetEnvironmentVariable(DataDirectoryEnvironmentVariable); + if (String.IsNullOrWhiteSpace(configuredDataDirectory)) + { + return InstallDirectory; + } + + try + { + return Path.TrimEndingDirectorySeparator(Path.GetFullPath(configuredDataDirectory)); + } + catch (Exception) + { + return InstallDirectory; + } + } public static bool TryGetMissingWebResource(out string missingResource) { foreach (string resourceName in RequiredWebResourceNames) { - string resourcePath = Path.Combine(WebRootDirectory, resourceName); + string resourcePath = Path.Combine(UiDirectory, resourceName); if (!File.Exists(resourcePath)) { missingResource = resourcePath; diff --git a/Steam Desktop Authenticator/InputForm.cs b/Steam Desktop Authenticator/InputForm.cs index e48ec1e..1e2c97c 100644 --- a/Steam Desktop Authenticator/InputForm.cs +++ b/Steam Desktop Authenticator/InputForm.cs @@ -117,7 +117,7 @@ private async void SetupModernUI(string label, bool isPassword) webView.CoreWebView2.ExecuteScriptAsync($"setupInput('{jsLabel}', {isPassStr})"); }; - string htmlPath = System.IO.Path.Combine(ApplicationPaths.WebRootDirectory, "input.html"); + string htmlPath = System.IO.Path.Combine(ApplicationPaths.UiDirectory, "input.html"); webView.Source = new Uri(htmlPath); } diff --git a/Steam Desktop Authenticator/LoginForm.cs b/Steam Desktop Authenticator/LoginForm.cs index b1b0729..69c2453 100644 --- a/Steam Desktop Authenticator/LoginForm.cs +++ b/Steam Desktop Authenticator/LoginForm.cs @@ -117,7 +117,7 @@ private async void SetupModernUI() } }; - string htmlPath = System.IO.Path.Combine(ApplicationPaths.WebRootDirectory, "login.html"); + string htmlPath = System.IO.Path.Combine(ApplicationPaths.UiDirectory, "login.html"); webView.Source = new Uri(htmlPath); } diff --git a/Steam Desktop Authenticator/MainForm.cs b/Steam Desktop Authenticator/MainForm.cs index 1d52687..36b4721 100644 --- a/Steam Desktop Authenticator/MainForm.cs +++ b/Steam Desktop Authenticator/MainForm.cs @@ -72,6 +72,7 @@ public partial class MainForm : Form private bool backgroundServicesStarted; const int VK_RCONTROL = 0xA3; + const int VK_ESCAPE = 0x1B; [DllImport("user32.dll")] private static extern bool GetCursorPos(out Point lpPoint); @@ -82,6 +83,8 @@ public partial class MainForm : Form private TradePopupForm popupFrm = new TradePopupForm(); private ToolStripMenuItem trayLoginActions; private ToolStripMenuItem trayAccountHeading; + private QrScanOverlayForm qrScanOverlay; + private bool qrScanInProgress; private sealed class RecentLoginAttempt { @@ -485,94 +488,137 @@ static async Task WaitForLeftAltKeyPress() } } - private async void btnLoginViaQr_Click(object sender, EventArgs e) + private void SetQrScanWaitingState(bool isWaiting, int remainingSeconds = 0) { - if (currentAccount == null) return; - - this.btnLoginViaQr.Enabled = false; - string originalText = this.btnLoginViaQr.Text; - - AstroMessageBox.Show("Move your cursor over the Steam QR code and press RIGHT CTRL to sign in.", "Scan QR Code", MessageBoxButtons.OK, MessageBoxIcon.Information); - - bool keyPressed = false; - for (int i = 300; i > 0; i--) + if (isWaiting) { - if ((GetAsyncKeyState(VK_RCONTROL) & 0x8000) != 0) - { - keyPressed = true; - break; - } + if (qrScanOverlay == null || qrScanOverlay.IsDisposed) + qrScanOverlay = new QrScanOverlayForm(); - if (i % 10 == 0) - { - this.btnLoginViaQr.Text = $"Press Right CTRL ({i / 10}s)"; - if (webView != null && webView.CoreWebView2 != null) - _ = webView.CoreWebView2.ExecuteScriptAsync($"updateQrButtonText('Press Right CTRL ({i / 10}s)')"); - } + qrScanOverlay.FollowCursor(); + if (!qrScanOverlay.Visible) + qrScanOverlay.Show(this); + } + else if (qrScanOverlay?.Visible == true) + { + qrScanOverlay.Hide(); + } - await Task.Delay(100); + if (webView?.CoreWebView2 != null) + { + string waitingValue = isWaiting ? "true" : "false"; + _ = webView.CoreWebView2.ExecuteScriptAsync($"setQrScanWaiting({waitingValue}, {remainingSeconds})"); } + } - this.btnLoginViaQr.Text = originalText; - this.btnLoginViaQr.Enabled = true; - if (webView != null && webView.CoreWebView2 != null) - _ = webView.CoreWebView2.ExecuteScriptAsync($"updateQrButtonText('{originalText}')"); + private async void btnLoginViaQr_Click(object sender, EventArgs e) + { + if (qrScanInProgress) + return; - if (!keyPressed) + if (currentAccount == null) { - AstroMessageBox.Show("QR Code scan cancelled (timeout).", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Information); + SetQrScanWaitingState(false); return; } - - GetCursorPos(out Point cursorPos); - int scanWidth = 500; - int scanHeight = 500; - using (Bitmap bitmap = new Bitmap(scanWidth, scanHeight)) + qrScanInProgress = true; + try { - using (Graphics g = Graphics.FromImage(bitmap)) + this.btnLoginViaQr.Enabled = false; + string originalText = this.btnLoginViaQr.Text; + SetQrScanWaitingState(true, 30); + + bool keyPressed = false; + bool cancelled = false; + for (int i = 300; i > 0; i--) { - g.CopyFromScreen(cursorPos.X - scanWidth / 2, cursorPos.Y - scanHeight / 2, 0, 0, bitmap.Size); + if ((GetAsyncKeyState(VK_RCONTROL) & 0x8000) != 0) + { + keyPressed = true; + break; + } + + if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) + { + cancelled = true; + break; + } + + if (i % 10 == 0) + { + this.btnLoginViaQr.Text = $"Press Right CTRL ({i / 10}s)"; + SetQrScanWaitingState(true, i / 10); + } + + await Task.Delay(100); } - var reader = new BarcodeReader(); - var result = reader.Decode(bitmap); + this.btnLoginViaQr.Text = originalText; + this.btnLoginViaQr.Enabled = true; + SetQrScanWaitingState(false); - if (result == null) + if (!keyPressed) { - AstroMessageBox.Show("No QR code detected. Make sure your cursor is exactly over the QR code.", "Scan Failed", MessageBoxButtons.OK, MessageBoxIcon.Warning); + string cancellationReason = cancelled ? "QR Code scan cancelled." : "QR Code scan cancelled (timeout)."; + AstroMessageBox.Show(cancellationReason, "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } - - string idOfQR = null; - if (!string.IsNullOrEmpty(result.Text)) + + GetCursorPos(out Point cursorPos); + int scanWidth = 500; + int scanHeight = 500; + + using (Bitmap bitmap = new Bitmap(scanWidth, scanHeight)) { - string[] parts = result.Text.Split('/'); - if (parts.Length > 5) + using (Graphics g = Graphics.FromImage(bitmap)) { - idOfQR = parts[5]; + g.CopyFromScreen(cursorPos.X - scanWidth / 2, cursorPos.Y - scanHeight / 2, 0, 0, bitmap.Size); + } + + var reader = new BarcodeReader(); + var result = reader.Decode(bitmap); + + if (result == null) + { + AstroMessageBox.Show("No QR code detected. Make sure your cursor is exactly over the QR code.", "Scan Failed", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; } - } - if (string.IsNullOrEmpty(idOfQR)) - { - AstroMessageBox.Show("Can't get ID of QR code. Steam might have changed their QR format.", "Wrong QR code.", MessageBoxButtons.OK, MessageBoxIcon.Error); - return; - } + string idOfQR = null; + if (!string.IsNullOrEmpty(result.Text)) + { + string[] parts = result.Text.Split('/'); + if (parts.Length > 5) + { + idOfQR = parts[5]; + } + } - try - { - string response = await currentAccount.SignInViaQR(idOfQR); - if (response != "1") - AstroMessageBox.Show($"Can't log in to account.\n\nDebug Info:\nID: {idOfQR}\nEResult: {response}\n\nTry refreshing the Steam login page and scan again.", "Something went wrong!", MessageBoxButtons.OK, MessageBoxIcon.Error); - else - AstroMessageBox.Show("Successfully logged in via QR code!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); - } - catch (Exception ex) - { - AstroMessageBox.Show($"Exception during QR Login:\n\nID: {idOfQR}\nError: {ex.Message}", "API Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + if (string.IsNullOrEmpty(idOfQR)) + { + AstroMessageBox.Show("Can't get ID of QR code. Steam might have changed their QR format.", "Wrong QR code.", MessageBoxButtons.OK, MessageBoxIcon.Error); + return; + } + + try + { + string response = await currentAccount.SignInViaQR(idOfQR); + if (response != "1") + AstroMessageBox.Show($"Can't log in to account.\n\nDebug Info:\nID: {idOfQR}\nEResult: {response}\n\nTry refreshing the Steam login page and scan again.", "Something went wrong!", MessageBoxButtons.OK, MessageBoxIcon.Error); + else + AstroMessageBox.Show("Successfully logged in via QR code!", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + catch (Exception ex) + { + AstroMessageBox.Show($"Exception during QR Login:\n\nID: {idOfQR}\nError: {ex.Message}", "API Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } } + finally + { + qrScanInProgress = false; + } } private void btnTradeConfirmations_Click(object sender, EventArgs e) @@ -918,7 +964,7 @@ private async void timerTradesPopup_Tick(object sender, EventArgs e) lblStatus.Text = ""; AstroMessageBox.Show("Your session for account " + account.AccountName + " has expired. You will be prompted to login again.", "Trade Confirmations", MessageBoxButtons.OK, MessageBoxIcon.Error); PromptRefreshLogin(account); - pendingTradeConfirmationCounts.Remove(account.Session.SteamID); + InvalidateTradeConfirmationCache(account); UpdateTradePendingCount(pendingTradeConfirmationCounts.Values.Sum()); return; } @@ -1339,6 +1385,23 @@ private void CacheTradeConfirmations(SteamGuardAccount account, IEnumerable entry.Value.Account?.Session?.SteamID == steamId) + .Select(entry => entry.Key) + .ToArray()) + { + loadedTradeConfirmations.Remove(confirmationKey); + } + } + private bool IsTradeCacheCompleteForSelection() { if (allAccounts == null || allAccounts.Length == 0) @@ -1413,6 +1476,16 @@ private void ReconcilePendingLoginRequestsForAccount(SteamGuardAccount account, pendingLoginRequests.Remove(staleKey); PruneLoginRequestBookkeeping(staleKey); } + + foreach (string staleRequestKey in completedAutomatedLoginActions + .Where(actionKey => actionKey.StartsWith(accountRequestPrefix, StringComparison.Ordinal) && + !fetchedRequestKeys.Contains(actionKey.Split('|')[0])) + .Select(actionKey => actionKey.Split('|')[0]) + .Distinct() + .ToList()) + { + PruneLoginRequestBookkeeping(staleRequestKey); + } } private static bool IsValidIpv4Address(string value) @@ -2114,7 +2187,7 @@ private async void SetupModernUI() }; // Load local html file - string htmlPath = System.IO.Path.Combine(ApplicationPaths.WebRootDirectory, "index.html"); + string htmlPath = System.IO.Path.Combine(ApplicationPaths.UiDirectory, "index.html"); webView.Source = new Uri(htmlPath); } @@ -2412,6 +2485,7 @@ private async Task LoadTradesAsync(string selectedAccountName = null) if (account.Session.IsRefreshTokenExpired()) { unavailableAccounts.Add(account.AccountName + " needs you to sign in again."); + InvalidateTradeConfirmationCache(account); continue; } Confirmation[] accountConfirmations = await FetchTradeConfirmationsForPageAsync(account); @@ -2436,6 +2510,7 @@ private async Task LoadTradesAsync(string selectedAccountName = null) : unavailableAccounts.Count == 0 ? null : "Some account confirmations could not be loaded: " + String.Join(" ", unavailableAccounts); + UpdateTradePendingCount(pendingTradeConfirmationCounts.Values.Sum()); await PublishCachedTradesAsync(errorMessage, selectionToLoad); } catch (Exception ex) diff --git a/Steam Desktop Authenticator/Manifest.cs b/Steam Desktop Authenticator/Manifest.cs index b477313..80c46e5 100644 --- a/Steam Desktop Authenticator/Manifest.cs +++ b/Steam Desktop Authenticator/Manifest.cs @@ -74,7 +74,8 @@ public class Manifest public static string GetExecutableDir() { - return ApplicationPaths.InstallDirectory; + // Account data lives beside the Launcher when the app is started through it. + return ApplicationPaths.DataDirectory; } public static Manifest GetManifest(bool forceLoad = false) diff --git a/Steam Desktop Authenticator/QrScanOverlayForm.cs b/Steam Desktop Authenticator/QrScanOverlayForm.cs new file mode 100644 index 0000000..a97d50c --- /dev/null +++ b/Steam Desktop Authenticator/QrScanOverlayForm.cs @@ -0,0 +1,136 @@ +using System; +using System.Drawing; +using System.Drawing.Text; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace Steam_Desktop_Authenticator +{ + internal sealed class QrScanOverlayForm : Form + { + private const int VisualSize = 350; + private const int InstructionsHeight = 48; + private readonly Timer followCursorTimer = new Timer { Interval = 16 }; + + [DllImport("user32.dll")] + private static extern bool GetCursorPos(out Point point); + + public QrScanOverlayForm() + { + FormBorderStyle = FormBorderStyle.None; + ShowInTaskbar = false; + StartPosition = FormStartPosition.Manual; + TopMost = true; + Size = new Size(VisualSize, VisualSize + InstructionsHeight); + BackColor = Color.Magenta; + TransparencyKey = Color.Magenta; + DoubleBuffered = true; + followCursorTimer.Tick += (sender, args) => FollowCursor(); + } + + protected override CreateParams CreateParams + { + get + { + const int WsExTransparent = 0x00000020; + const int WsExNoActivate = 0x08000000; + + CreateParams createParams = base.CreateParams; + createParams.ExStyle |= WsExTransparent | WsExNoActivate; + return createParams; + } + } + + protected override bool ShowWithoutActivation => true; + + public void FollowCursor() + { + if (GetCursorPos(out Point cursorPosition)) + { + Location = new Point( + cursorPosition.X - VisualSize / 2, + cursorPosition.Y - VisualSize / 2); + } + } + + protected override void OnVisibleChanged(EventArgs e) + { + base.OnVisibleChanged(e); + + if (Visible) + { + FollowCursor(); + followCursorTimer.Start(); + } + else + { + followCursorTimer.Stop(); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + followCursorTimer.Dispose(); + + base.Dispose(disposing); + } + + protected override void WndProc(ref Message message) + { + const int WmNcHitTest = 0x0084; + const int HtTransparent = -1; + + if (message.Msg == WmNcHitTest) + { + message.Result = new IntPtr(HtTransparent); + return; + } + + base.WndProc(ref message); + } + + protected override void OnPaint(PaintEventArgs e) + { + base.OnPaint(e); + + using (Pen borderPen = new Pen(Color.FromArgb(153, 153, 153), 3)) + { + e.Graphics.DrawRectangle(borderPen, 2, 2, VisualSize - 5, VisualSize - 5); + } + + DrawOutlinedInstruction(e.Graphics, "Press Right Ctrl to scan the QR code", + new Rectangle(0, VisualSize + 2, VisualSize, 20)); + DrawOutlinedInstruction(e.Graphics, "Press Esc to cancel", + new Rectangle(0, VisualSize + 22, VisualSize, 20)); + } + + private void DrawOutlinedInstruction(Graphics graphics, string text, Rectangle bounds) + { + using (Font boldFont = new Font(Font, FontStyle.Bold)) + using (StringFormat textFormat = new StringFormat + { + Alignment = StringAlignment.Center, + LineAlignment = StringAlignment.Center + }) + using (Brush outlineBrush = new SolidBrush(Color.Black)) + using (Brush textBrush = new SolidBrush(Color.FromArgb(204, 204, 204))) + { + TextRenderingHint originalRenderingHint = graphics.TextRenderingHint; + graphics.TextRenderingHint = TextRenderingHint.SingleBitPerPixelGridFit; + foreach (Point offset in new[] + { + new Point(-1, 0), new Point(1, 0), new Point(0, -1), new Point(0, 1) + }) + { + graphics.DrawString(text, boldFont, outlineBrush, + new Rectangle(bounds.X + offset.X, bounds.Y + offset.Y, bounds.Width, bounds.Height), + textFormat); + } + + graphics.DrawString(text, boldFont, textBrush, bounds, textFormat); + graphics.TextRenderingHint = originalRenderingHint; + } + } + } +} diff --git a/Steam Desktop Authenticator/Steam Desktop Authenticator.csproj b/Steam Desktop Authenticator/Steam Desktop Authenticator.csproj index 509064c..260da67 100644 --- a/Steam Desktop Authenticator/Steam Desktop Authenticator.csproj +++ b/Steam Desktop Authenticator/Steam Desktop Authenticator.csproj @@ -29,10 +29,10 @@ - + PreserveNewest PreserveNewest - app\wwwroot\%(RecursiveDir)%(Filename)%(Extension) + app\ui\%(RecursiveDir)%(Filename)%(Extension) diff --git a/Steam Desktop Authenticator/tailwind.config.js b/Steam Desktop Authenticator/tailwind.config.js new file mode 100644 index 0000000..3be9a0b --- /dev/null +++ b/Steam Desktop Authenticator/tailwind.config.js @@ -0,0 +1,45 @@ +/** + * Regenerate ui/assets/css/app.css with: + * npx --yes --package tailwindcss@3.4.17 --package @tailwindcss/forms@0.5.10 tailwindcss -c tailwind.config.js -i ui/tailwind-input.css -o ui/assets/css/app.css --minify + * + * The generated CSS is committed so normal Visual Studio/MSBuild builds do not require Node.js. + */ +const defaultTheme = require("tailwindcss/defaultTheme"); + +module.exports = { + content: ["./ui/**/*.html"], + darkMode: "class", + theme: { + extend: { + fontFamily: { + mono: ["JetBrains Mono", ...defaultTheme.fontFamily.mono] + }, + colors: { + "surface-variant": "#2d3449", + "on-surface": "#dae2fd", + primary: "#c3f5ff", + "primary-fixed": "#9cf0ff", + background: "#0b1326", + "surface-container-low": "#131b2e", + "surface-container": "#171f33", + "surface-container-high": "#222a3d", + "primary-container": "#00e5ff", + "on-surface-variant": "#bac9cc", + "surface-container-lowest": "#060e20", + "surface-container-highest": "#2d3449", + surface: "#0b1326", + "on-primary": "#00363d", + "on-primary-container": "#00363d", + secondary: "#4edea3", + "secondary-container": "#00a572", + "on-secondary": "#003824", + "surface-bright": "#31394d", + error: "#ff5449", + "on-error": "#690005", + "error-container": "#93000a", + "on-error-container": "#ffb4ab" + } + } + }, + plugins: [require("@tailwindcss/forms")] +}; diff --git a/Steam Desktop Authenticator/ui/assets/css/app.css b/Steam Desktop Authenticator/ui/assets/css/app.css new file mode 100644 index 0000000..a975c63 --- /dev/null +++ b/Steam Desktop Authenticator/ui/assets/css/app.css @@ -0,0 +1 @@ +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:JetBrains Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}[multiple],[type=date],[type=datetime-local],[type=email],[type=month],[type=number],[type=password],[type=search],[type=tel],[type=text],[type=time],[type=url],[type=week],input:where(:not([type])),select,textarea{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#6b7280;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow:0 0 #0000}[multiple]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=email]:focus,[type=month]:focus,[type=number]:focus,[type=password]:focus,[type=search]:focus,[type=tel]:focus,[type=text]:focus,[type=time]:focus,[type=url]:focus,[type=week]:focus,input:where(:not([type])):focus,select:focus,textarea:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#6b7280;opacity:1}input::placeholder,textarea::placeholder{color:#6b7280;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-meridiem-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-year-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple],[size]:where(select:not([size="1"])){background-image:none;background-position:0 0;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#6b7280;border-width:1px;--tw-shadow:0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset:var(--tw-empty,/*!*/ /*!*/);--tw-ring-offset-width:2px;--tw-ring-offset-color:#fff;--tw-ring-color:#2563eb;--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=checkbox]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=radio]:checked{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 16 16'%3E%3Ccircle cx='8' cy='8' r='3'/%3E%3C/svg%3E")}@media (forced-colors:active) {[type=radio]:checked{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:checked:focus,[type=checkbox]:checked:hover,[type=radio]:checked:focus,[type=radio]:checked:hover{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3E%3C/svg%3E");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:50%;background-repeat:no-repeat}@media (forced-colors:active) {[type=checkbox]:indeterminate{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}}[type=checkbox]:indeterminate:focus,[type=checkbox]:indeterminate:hover{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}body[data-page=dashboard]{background-color:#0b1326;color:#dae2fd;font-family:Inter,sans-serif;min-height:max(884px,100dvh);-moz-user-select:none;user-select:none;-webkit-user-select:none}body[data-page=dashboard] input,body[data-page=dashboard] textarea{-moz-user-select:text;user-select:text;-webkit-user-select:text}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.glass-panel{background:rgba(30,41,59,.7);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid hsla(0,0%,100%,.08)}.btn-primary{background:#00e5ff;border:none;color:#00363d;cursor:pointer;font-weight:700;transition:background .2s}.btn-primary:hover{background:#9cf0ff}.loader{animation:spin 1s linear infinite;border:4px solid hsla(0,0%,100%,.1);border-left-color:#00e5ff;border-radius:50%;height:40px;width:40px}.ui-icon{display:inline-block;fill:none;height:1.25rem;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.8;vertical-align:middle;width:1.25rem}.ui-icon-fill{fill:currentColor;stroke:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.bottom-0{bottom:0}.left-0{left:0}.left-\[-10\%\]{left:-10%}.right-2{right:.5rem}.right-\[-10\%\]{right:-10%}.top-0{top:0}.top-\[-10\%\]{top:-10%}.z-10{z-index:10}.z-50{z-index:50}.z-\[70\]{z-index:70}.-mx-2{margin-left:-.5rem;margin-right:-.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-4{margin-top:1rem;margin-bottom:1rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-11{height:2.75rem}.h-4{height:1rem}.h-8{height:2rem}.h-\[40\%\]{height:40%}.h-full{height:100%}.max-h-\[80vh\]{max-height:80vh}.min-h-\[300px\]{min-height:300px}.min-h-screen{min-height:100vh}.w-11{width:2.75rem}.w-20{width:5rem}.w-24{width:6rem}.w-8{width:2rem}.w-\[40\%\]{width:40%}.w-full{width:100%}.min-w-0{min-width:0}.min-w-4{min-width:1rem}.min-w-\[72px\]{min-width:72px}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.cursor-crosshair{cursor:crosshair}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.scroll-mt-4{scroll-margin-top:1rem}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-3{row-gap:.75rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;white-space:nowrap}.text-ellipsis,.truncate{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-xl{border-radius:.75rem}.rounded-t-xl{border-top-left-radius:.75rem;border-top-right-radius:.75rem}.border{border-width:1px}.border-l-2{border-left-width:2px}.border-l-4{border-left-width:4px}.border-t{border-top-width:1px}.border-\[\#4edea3\]\/30{border-color:rgba(78,222,163,.3)}.border-error\/30{border-color:rgba(255,84,73,.3)}.border-error\/40{border-color:rgba(255,84,73,.4)}.border-primary{--tw-border-opacity:1;border-color:rgb(195 245 255/var(--tw-border-opacity,1))}.border-primary\/20{border-color:rgba(195,245,255,.2)}.border-primary\/30{border-color:rgba(195,245,255,.3)}.border-transparent{border-color:transparent}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.border-white\/5{border-color:hsla(0,0%,100%,.05)}.bg-\[\#00E5FF\]{--tw-bg-opacity:1;background-color:rgb(0 229 255/var(--tw-bg-opacity,1))}.bg-\[\#00E5FF\]\/5{background-color:rgba(0,229,255,.05)}.bg-\[\#00a572\]\/20{background-color:rgba(0,165,114,.2)}.bg-\[\#0F172A\]{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity,1))}.bg-black\/60{background-color:rgba(0,0,0,.6)}.bg-error{--tw-bg-opacity:1;background-color:rgb(255 84 73/var(--tw-bg-opacity,1))}.bg-error-container\/20{background-color:rgba(147,0,10,.2)}.bg-error-container\/30{background-color:rgba(147,0,10,.3)}.bg-primary-container\/20{background-color:rgba(0,229,255,.2)}.bg-primary\/10{background-color:rgba(195,245,255,.1)}.bg-primary\/5{background-color:rgba(195,245,255,.05)}.bg-secondary{--tw-bg-opacity:1;background-color:rgb(78 222 163/var(--tw-bg-opacity,1))}.bg-secondary-container\/20{background-color:rgba(0,165,114,.2)}.bg-surface{--tw-bg-opacity:1;background-color:rgb(11 19 38/var(--tw-bg-opacity,1))}.bg-surface-container{--tw-bg-opacity:1;background-color:rgb(23 31 51/var(--tw-bg-opacity,1))}.bg-surface-container-low{--tw-bg-opacity:1;background-color:rgb(19 27 46/var(--tw-bg-opacity,1))}.bg-surface-container-lowest{--tw-bg-opacity:1;background-color:rgb(6 14 32/var(--tw-bg-opacity,1))}.bg-surface-variant{--tw-bg-opacity:1;background-color:rgb(45 52 73/var(--tw-bg-opacity,1))}.bg-surface\/50{background-color:rgba(11,19,38,.5)}.bg-surface\/80{background-color:rgba(11,19,38,.8)}.bg-transparent{background-color:transparent}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-\[\#00a572\]{--tw-gradient-from:#00a572 var(--tw-gradient-from-position);--tw-gradient-to:rgba(0,165,114,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-\[\#4edea3\]{--tw-gradient-to:#4edea3 var(--tw-gradient-to-position)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pb-20{padding-bottom:5rem}.pl-8{padding-left:2rem}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:JetBrains Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-\[10px\]{font-size:10px}.text-\[40px\]{font-size:40px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-4{line-height:1rem}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-\[\#00363D\]{--tw-text-opacity:1;color:rgb(0 54 61/var(--tw-text-opacity,1))}.text-\[\#4edea3\]{--tw-text-opacity:1;color:rgb(78 222 163/var(--tw-text-opacity,1))}.text-\[\#849396\]{--tw-text-opacity:1;color:rgb(132 147 150/var(--tw-text-opacity,1))}.text-\[\#bac9cc\]{--tw-text-opacity:1;color:rgb(186 201 204/var(--tw-text-opacity,1))}.text-\[\#c3f5ff\]{--tw-text-opacity:1;color:rgb(195 245 255/var(--tw-text-opacity,1))}.text-error{--tw-text-opacity:1;color:rgb(255 84 73/var(--tw-text-opacity,1))}.text-on-error{--tw-text-opacity:1;color:rgb(105 0 5/var(--tw-text-opacity,1))}.text-on-secondary{--tw-text-opacity:1;color:rgb(0 56 36/var(--tw-text-opacity,1))}.text-on-surface{--tw-text-opacity:1;color:rgb(218 226 253/var(--tw-text-opacity,1))}.text-on-surface-variant{--tw-text-opacity:1;color:rgb(186 201 204/var(--tw-text-opacity,1))}.text-on-surface-variant\/50{color:rgba(186,201,204,.5)}.text-on-surface\/50{color:rgba(218,226,253,.5)}.text-primary{--tw-text-opacity:1;color:rgb(195 245 255/var(--tw-text-opacity,1))}.text-secondary{--tw-text-opacity:1;color:rgb(78 222 163/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-2xl,.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-sm,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-\[\#00E5FF\]\/20,.shadow-\[\#00e5ff\]\/20{--tw-shadow-color:rgba(0,229,255,.2);--tw-shadow:var(--tw-shadow-colored)}.outline{outline-style:solid}.blur{--tw-blur:blur(8px)}.blur,.blur-\[120px\]{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.blur-\[120px\]{--tw-blur:blur(120px)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-lg{--tw-backdrop-blur:blur(16px)}.backdrop-blur-lg,.backdrop-blur-sm{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-1000{transition-duration:1s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}@font-face{font-family:Inter;font-style:normal;font-weight:400 700;font-display:swap;src:url(../fonts/inter-latin.woff2) format("woff2")}@font-face{font-family:JetBrains Mono;font-style:normal;font-weight:400 500;font-display:swap;src:url(../fonts/jetbrains-mono-latin.woff2) format("woff2")}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}body[data-page=dashboard] input[type=checkbox]:checked{background-color:#4edea3!important;background-image:url(../icons/check.svg)!important;border-color:#4edea3!important;color:#4edea3!important}.selection\:bg-primary-container ::-moz-selection{--tw-bg-opacity:1;background-color:rgb(0 229 255/var(--tw-bg-opacity,1))}.selection\:bg-primary-container ::selection{--tw-bg-opacity:1;background-color:rgb(0 229 255/var(--tw-bg-opacity,1))}.selection\:text-on-primary-container ::-moz-selection{--tw-text-opacity:1;color:rgb(0 54 61/var(--tw-text-opacity,1))}.selection\:text-on-primary-container ::selection{--tw-text-opacity:1;color:rgb(0 54 61/var(--tw-text-opacity,1))}.selection\:bg-primary-container::-moz-selection{--tw-bg-opacity:1;background-color:rgb(0 229 255/var(--tw-bg-opacity,1))}.selection\:bg-primary-container::selection{--tw-bg-opacity:1;background-color:rgb(0 229 255/var(--tw-bg-opacity,1))}.selection\:text-on-primary-container::-moz-selection{--tw-text-opacity:1;color:rgb(0 54 61/var(--tw-text-opacity,1))}.selection\:text-on-primary-container::selection{--tw-text-opacity:1;color:rgb(0 54 61/var(--tw-text-opacity,1))}.hover\:bg-\[\#00a572\]\/40:hover{background-color:rgba(0,165,114,.4)}.hover\:bg-\[\#9cf0ff\]:hover{--tw-bg-opacity:1;background-color:rgb(156 240 255/var(--tw-bg-opacity,1))}.hover\:bg-error-container\/30:hover{background-color:rgba(147,0,10,.3)}.hover\:bg-error-container\/60:hover{background-color:rgba(147,0,10,.6)}.hover\:bg-surface-bright:hover{--tw-bg-opacity:1;background-color:rgb(49 57 77/var(--tw-bg-opacity,1))}.hover\:bg-surface-variant:hover{--tw-bg-opacity:1;background-color:rgb(45 52 73/var(--tw-bg-opacity,1))}.hover\:bg-white\/5:hover{background-color:hsla(0,0%,100%,.05)}.hover\:text-on-surface:hover{--tw-text-opacity:1;color:rgb(218 226 253/var(--tw-text-opacity,1))}.hover\:text-primary:hover{--tw-text-opacity:1;color:rgb(195 245 255/var(--tw-text-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-\[\#00daf3\]:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(0 218 243/var(--tw-ring-opacity,1))}.focus\:ring-primary:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(195 245 255/var(--tw-ring-opacity,1))}.focus\:ring-secondary:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(78 222 163/var(--tw-ring-opacity,1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:text-on-surface{--tw-text-opacity:1;color:rgb(218 226 253/var(--tw-text-opacity,1))}@media (min-width:640px){.sm\:w-44{width:11rem}.sm\:w-auto{width:auto}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-end{justify-content:flex-end}.sm\:justify-between{justify-content:space-between}}@media (min-width:768px){.md\:p-6{padding:1.5rem}}@media (min-width:1024px){.lg\:col-span-8{grid-column:span 8/span 8}.lg\:col-start-3{grid-column-start:3}.lg\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:p-8{padding:2rem}} \ No newline at end of file diff --git a/Steam Desktop Authenticator/ui/assets/fonts/inter-latin.woff2 b/Steam Desktop Authenticator/ui/assets/fonts/inter-latin.woff2 new file mode 100644 index 0000000..d15208d Binary files /dev/null and b/Steam Desktop Authenticator/ui/assets/fonts/inter-latin.woff2 differ diff --git a/Steam Desktop Authenticator/ui/assets/fonts/jetbrains-mono-latin.woff2 b/Steam Desktop Authenticator/ui/assets/fonts/jetbrains-mono-latin.woff2 new file mode 100644 index 0000000..4d09cda Binary files /dev/null and b/Steam Desktop Authenticator/ui/assets/fonts/jetbrains-mono-latin.woff2 differ diff --git a/Steam Desktop Authenticator/ui/assets/icons/check.svg b/Steam Desktop Authenticator/ui/assets/icons/check.svg new file mode 100644 index 0000000..5a56217 --- /dev/null +++ b/Steam Desktop Authenticator/ui/assets/icons/check.svg @@ -0,0 +1 @@ + diff --git a/Steam Desktop Authenticator/wwwroot/index.html b/Steam Desktop Authenticator/ui/index.html similarity index 89% rename from Steam Desktop Authenticator/wwwroot/index.html rename to Steam Desktop Authenticator/ui/index.html index 98c1bed..e30d25e 100644 --- a/Steam Desktop Authenticator/wwwroot/index.html +++ b/Steam Desktop Authenticator/ui/index.html @@ -2,82 +2,33 @@ Astro Steam Authenticator - Dashboard - - - - - + - +

-
+
@@ -110,7 +61,16 @@
Account: ...
- + +
@@ -133,8 +93,8 @@

Confirmations

-
@@ -142,7 +102,7 @@

Confirmations

@@ -161,14 +121,14 @@

Login Actions