From 14ecbb65a3147e62076c4babe2a3b4cbfd4bbfca Mon Sep 17 00:00:00 2001 From: Florian Rappl Date: Tue, 3 Mar 2026 23:54:14 +0100 Subject: [PATCH] Compute auth token in Node --- src/ElectronNET.API/ElectronNetRuntime.cs | 2 ++ .../RuntimeControllerDotNetFirst.cs | 3 +- .../RuntimeControllerElectronFirst.cs | 11 ++----- .../ElectronProcess/ElectronProcessActive.cs | 31 +++++++++---------- .../RuntimeControllerAspNetBase.cs | 8 ++++- .../RuntimeControllerAspNetDotnetFirst.cs | 14 +++------ .../RuntimeControllerAspNetElectronFirst.cs | 14 +++------ src/ElectronNET.Host/main.js | 30 ++++++------------ 8 files changed, 47 insertions(+), 66 deletions(-) diff --git a/src/ElectronNET.API/ElectronNetRuntime.cs b/src/ElectronNET.API/ElectronNetRuntime.cs index 290587f..406e8d1 100644 --- a/src/ElectronNET.API/ElectronNetRuntime.cs +++ b/src/ElectronNET.API/ElectronNetRuntime.cs @@ -27,6 +27,8 @@ public static string ElectronExtraArguments { get; set; } + public static string ElectronAuthToken { get; internal set; } + public static int? ElectronSocketPort { get; internal set; } public static int? AspNetWebPort { get; internal set; } diff --git a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs index 6483bf9..1ecd341 100644 --- a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs +++ b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerDotNetFirst.cs @@ -58,8 +58,9 @@ private void ElectronProcess_Ready(object sender, EventArgs e) { var port = ElectronNetRuntime.ElectronSocketPort.Value; + var token = ElectronNetRuntime.ElectronAuthToken; this.TransitionState(LifetimeState.Started); - this.socketBridge = new SocketBridgeService(port, ""); + this.socketBridge = new SocketBridgeService(port, token); this.socketBridge.Ready += this.SocketBridge_Ready; this.socketBridge.Stopped += this.SocketBridge_Stopped; this.socketBridge.Start(); diff --git a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs index a76d8bf..9ca868c 100644 --- a/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs +++ b/src/ElectronNET.API/Runtime/Controllers/RuntimeControllerElectronFirst.cs @@ -11,7 +11,6 @@ { private ElectronProcessBase electronProcess; private SocketBridgeService socketBridge; - private int? port; public RuntimeControllerElectronFirst() { @@ -36,12 +35,8 @@ protected override Task StartCore() { - this.port = ElectronNetRuntime.ElectronSocketPort; - - if (!this.port.HasValue) - { - throw new Exception("No port has been specified by Electron!"); - } + var port = ElectronNetRuntime.ElectronSocketPort.Value; + var token = ElectronNetRuntime.ElectronAuthToken; if (!ElectronNetRuntime.ElectronProcessId.HasValue) { @@ -49,7 +44,7 @@ } this.TransitionState(LifetimeState.Starting); - this.socketBridge = new SocketBridgeService(this.port!.Value, ""); + this.socketBridge = new SocketBridgeService(port, token); this.socketBridge.Ready += this.SocketBridge_Ready; this.socketBridge.Stopped += this.SocketBridge_Stopped; this.socketBridge.Start(); diff --git a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs index 4504b5e..9af3543 100644 --- a/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs +++ b/src/ElectronNET.API/Runtime/Services/ElectronProcess/ElectronProcessActive.cs @@ -2,9 +2,11 @@ { using System; using System.ComponentModel; + using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; + using System.Text.RegularExpressions; using System.Threading.Tasks; using ElectronNET.Common; using ElectronNET.Runtime.Data; @@ -15,6 +17,7 @@ [Localizable(false)] internal class ElectronProcessActive : ElectronProcessBase { + private readonly Regex extractor = new Regex("^Electron Socket: listening on port (\\d+) at .* using ([a-f0-9]+)$"); private readonly bool isUnpackaged; private readonly string electronBinaryName; private readonly string extraArguments; @@ -159,37 +162,31 @@ { var tcs = new TaskCompletionSource(); - void Read_SocketIO_Port(object sender, string line) + void Read_SocketIO_Parameters(object sender, string line) { - // Look for "Electron Socket: listening on port %s at" - var prefix = "Electron Socket: listening on port "; + // Look for "Electron Socket: listening on port %s at ..." + var match = extractor.Match(line); - if (line.StartsWith(prefix)) + if (match?.Success ?? false) { - var start = prefix.Length; - var end = line.IndexOf(' ', start + 1); - var port = line[start..end]; + var port = int.Parse(match.Groups[1].Value); + var token = match.Groups[2].Value; - if (int.TryParse(port, out var p)) - { - // We got the port, so no more need for reading this - this.process.LineReceived -= Read_SocketIO_Port; - ElectronNetRuntime.ElectronSocketPort = p; - tcs.SetResult(); - } + this.process.LineReceived -= Read_SocketIO_Parameters; + ElectronNetRuntime.ElectronAuthToken = token; + ElectronNetRuntime.ElectronSocketPort = port; + tcs.SetResult(); } } try { - await Task.Delay(10.ms()).ConfigureAwait(false); - Console.Error.WriteLine("[StartInternal]: startCmd: {0}", startCmd); Console.Error.WriteLine("[StartInternal]: args: {0}", args); this.process = new ProcessRunner("ElectronRunner"); this.process.ProcessExited += this.Process_Exited; - this.process.LineReceived += Read_SocketIO_Port; + this.process.LineReceived += Read_SocketIO_Parameters; this.process.Run(startCmd, args, directoriy); await tcs.Task.ConfigureAwait(false); diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs index ac09ea2..5cd297d 100644 --- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs +++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetBase.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection; using ElectronNET.API; using ElectronNET.API.Bridge; + using ElectronNET.AspNet.Services; using ElectronNET.Common; using ElectronNET.Runtime.Controllers; using ElectronNET.Runtime.Data; @@ -17,12 +18,14 @@ { private readonly IServer server; private readonly AspNetLifetimeAdapter aspNetLifetimeAdapter; + private readonly IElectronAuthenticationService authenticationService; private SocketBridgeService socketBridge; - protected RuntimeControllerAspNetBase(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter) + protected RuntimeControllerAspNetBase(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) { this.server = server; this.aspNetLifetimeAdapter = aspNetLifetimeAdapter; + this.authenticationService = authenticationService; this.aspNetLifetimeAdapter.Ready += this.AspNetLifetimeAdapter_Ready; this.aspNetLifetimeAdapter.Stopping += this.AspNetLifetimeAdapter_Stopping; this.aspNetLifetimeAdapter.Stopped += this.AspNetLifetimeAdapter_Stopped; @@ -59,10 +62,13 @@ this.ElectronProcess.IsReady() && this.aspNetLifetimeAdapter.IsReady()) { + var token = ElectronNetRuntime.ElectronAuthToken; var serverAddressesFeature = this.server.Features.Get(); var address = serverAddressesFeature.Addresses.First(); var uri = new Uri(address); + // Only if somebody registered an IElectronAuthenticationService service - otherwise we do not care + this.authenticationService?.SetExpectedToken(token); ElectronNetRuntime.AspNetWebPort = uri.Port; this.TransitionState(LifetimeState.Ready); diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs index 204f09c..0d2390a 100644 --- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs +++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetDotnetFirst.cs @@ -8,18 +8,14 @@ using ElectronNET.Runtime.Data; using ElectronNET.Runtime.Helpers; using ElectronNET.Runtime.Services.ElectronProcess; + using System.Security.Principal; internal class RuntimeControllerAspNetDotnetFirst : RuntimeControllerAspNetBase { private ElectronProcessBase electronProcess; - private readonly string authorization; - public RuntimeControllerAspNetDotnetFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter) + public RuntimeControllerAspNetDotnetFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, authenticationService) { - this.authorization = Guid.NewGuid().ToString("N"); // 32 hex chars, no hyphens - - // Only if somebody registered an IElectronAuthenticationService service - otherwise we do not care - authenticationService?.SetExpectedToken(this.authorization); } internal override ElectronProcessBase ElectronProcess => this.electronProcess; @@ -28,9 +24,8 @@ { var isUnPacked = ElectronNetRuntime.StartupMethod.IsUnpackaged(); var electronBinaryName = ElectronNetRuntime.ElectronExecutable; - var authToken = this.authorization; var port = ElectronNetRuntime.ElectronSocketPort ?? 0; - var args = $"{Environment.CommandLine} --authtoken={authToken}"; + var args = Environment.CommandLine; this.electronProcess = new ElectronProcessActive(isUnPacked, electronBinaryName, args, port); this.electronProcess.Ready += this.ElectronProcess_Ready; @@ -48,8 +43,9 @@ private void ElectronProcess_Ready(object sender, EventArgs e) { var port = ElectronNetRuntime.ElectronSocketPort.Value; + var token = ElectronNetRuntime.ElectronAuthToken; this.TransitionState(LifetimeState.Started); - this.CreateSocketBridge(port, this.authorization); + this.CreateSocketBridge(port, token); } private void ElectronProcess_Stopped(object sender, EventArgs e) diff --git a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs index 30c9551..757507d 100644 --- a/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs +++ b/src/ElectronNET.AspNet/Runtime/Controllers/RuntimeControllerAspNetElectronFirst.cs @@ -3,15 +3,15 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; + using ElectronNET.AspNet.Services; using ElectronNET.Runtime.Data; using ElectronNET.Runtime.Services.ElectronProcess; internal class RuntimeControllerAspNetElectronFirst : RuntimeControllerAspNetBase { private ElectronProcessBase electronProcess; - private int? port; - public RuntimeControllerAspNetElectronFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter) : base(server, aspNetLifetimeAdapter) + public RuntimeControllerAspNetElectronFirst(IServer server, AspNetLifetimeAdapter aspNetLifetimeAdapter, IElectronAuthenticationService authenticationService = null) : base(server, aspNetLifetimeAdapter, authenticationService) { } @@ -19,19 +19,15 @@ protected override Task StartCore() { - this.port = ElectronNetRuntime.ElectronSocketPort; - - if (!this.port.HasValue) - { - throw new Exception("No port has been specified by Electron!"); - } + var port = ElectronNetRuntime.ElectronSocketPort.Value; + var token = ElectronNetRuntime.ElectronAuthToken; if (!ElectronNetRuntime.ElectronProcessId.HasValue) { throw new Exception("No electronPID has been specified by Electron!"); } - this.CreateSocketBridge(this.port!.Value, ""); + this.CreateSocketBridge(port, token); this.electronProcess = new ElectronProcessPassive(ElectronNetRuntime.ElectronProcessId.Value); this.electronProcess.Stopped += this.ElectronProcess_Stopped; diff --git a/src/ElectronNET.Host/main.js b/src/ElectronNET.Host/main.js index 6f22906..5b4fb61 100644 --- a/src/ElectronNET.Host/main.js +++ b/src/ElectronNET.Host/main.js @@ -1,6 +1,7 @@ const { app } = require('electron'); const { BrowserWindow } = require('electron'); const { createServer } = require('http'); +const { randomUUID } = require('crypto'); const { Server } = require('socket.io'); const path = require('path'); const fs = require('fs'); @@ -25,6 +26,7 @@ let unpackeddotnet = false; let dotnetpacked = false; let electronforcedport; let electronUrl; +let authToken = randomUUID().split('-').join(''); if (app.commandLine.hasSwitch('manifest')) { manifestJsonFileName = app.commandLine.getSwitchValue('manifest'); @@ -44,15 +46,8 @@ if (app.commandLine.hasSwitch('electronforcedport')) { electronforcedport = +app.commandLine.getSwitchValue('electronforcedport'); } -let authToken; - -if (app.commandLine.hasSwitch('authtoken')) { - authToken = app.commandLine.getSwitchValue('authtoken'); - // Store in global for access by browser windows - global.authToken = authToken; -} - -console.log('Started with token', authToken); +// Store in global for access by browser windows +global.authToken = authToken; if (app.commandLine.hasSwitch('electronurl')) { electronUrl = app.commandLine.getSwitchValue('electronurl'); @@ -194,7 +189,7 @@ app.on('quit', async (event, exitCode) => { } } - // Clean up Socket.IO connection (legacy mode only) + // Clean up Socket.IO connection if (typeof io !== 'undefined' && io && typeof io.close === 'function') { try { io.close(); @@ -202,15 +197,6 @@ app.on('quit', async (event, exitCode) => { console.error('Error closing Socket.IO connection:', e); } } - - // Clean up SignalR connection (SignalR mode only) - if (global['electronsignalr'] && typeof global['electronsignalr'].connection !== 'undefined') { - try { - await global['electronsignalr'].connection.stop(); - } catch (e) { - console.error('Error closing SignalR connection:', e); - } - } }); function isSplashScreenEnabled() { @@ -278,6 +264,7 @@ function startSocketApiBridge(port) { // otherwise the Windows Firewall will be triggered console.debug('Electron Socket: starting...'); server = createServer(); + const host = !port ? '127.0.0.1' : 'localhost'; let hostHook; io = new Server({ pingTimeout: 60000, // in ms, default is 5000 @@ -285,10 +272,11 @@ function startSocketApiBridge(port) { }); io.attach(server); - server.listen(port, 'localhost'); + server.listen(port, host); server.on('listening', function () { const addr = server.address(); - console.info('Electron Socket: listening on port %s at %s', addr.port, addr.address); + console.info(`Electron Socket: listening on port ${addr.port} at ${addr.address} using ${authToken}`); + // Now that socket connection is established, we can guarantee port will not be open for portscanner if (unpackedelectron) { startAspCoreBackendUnpackaged(port);