Compute auth token in Node

This commit is contained in:
Florian Rappl
2026-03-03 23:54:14 +01:00
parent cf5938450f
commit 14ecbb65a3
8 changed files with 47 additions and 66 deletions

View File

@@ -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; }

View File

@@ -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();

View File

@@ -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();

View File

@@ -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);

View File

@@ -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<IServerAddressesFeature>();
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);

View File

@@ -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)

View File

@@ -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;

View File

@@ -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);