feat: Implement login request handler with token generation and error handling

This commit is contained in:
Neil Dorin 2026-04-16 21:31:42 -06:00
parent d1501d2dab
commit dc656bddf1
5 changed files with 241 additions and 6 deletions

View file

@ -75,6 +75,8 @@ public class WebApiServer : IKeyName
if (_server == null) _server = new HttpCwsServer(BasePath); if (_server == null) _server = new HttpCwsServer(BasePath);
_server.AuthenticateAllRoutes = false;
_server.setProcessName(Key); _server.setProcessName(Key);
_server.HttpRequestHandler = new DefaultRequestHandler(); _server.HttpRequestHandler = new DefaultRequestHandler();
_server.ReceivedRequestEvent += ReceivedRequestEventHandler; _server.ReceivedRequestEvent += ReceivedRequestEventHandler;

View file

@ -69,6 +69,7 @@ public class EssentialsWebApi : EssentialsDevice
_server = new WebApiServer(Key, Name, BasePath); _server = new WebApiServer(Key, Name, BasePath);
_debugServer = new WebApiServer($"{key}-debug-app", $"{name} Debug App", "/debug"); _debugServer = new WebApiServer($"{key}-debug-app", $"{name} Debug App", "/debug");
_debugServer.SetFallbackHandler(new ServeDebugAppRequestHandler()); _debugServer.SetFallbackHandler(new ServeDebugAppRequestHandler());
@ -79,6 +80,11 @@ public class EssentialsWebApi : EssentialsDevice
{ {
var routes = new List<HttpCwsRoute> var routes = new List<HttpCwsRoute>
{ {
new HttpCwsRoute("login")
{
Name = "Root",
RouteHandler = new LoginRequestHandler()
},
new HttpCwsRoute("versions") new HttpCwsRoute("versions")
{ {
Name = "ReportVersions", Name = "ReportVersions",

View file

@ -40,12 +40,18 @@ public static class EssentialsWebApiHelpers
/// </summary> /// </summary>
public static object MapToDeviceListObject(IKeyed device) public static object MapToDeviceListObject(IKeyed device)
{ {
var interfaces = device.GetType()
.GetInterfaces()
.Select(i => i.Name)
.ToList();
return new return new
{ {
device.Key, device.Key,
Name = (device is IKeyName) Name = (device is IKeyName)
? (device as IKeyName).Name ? (device as IKeyName).Name
: "---" : "---",
Interfaces = interfaces
}; };
} }

View file

@ -40,26 +40,26 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
if (device is IRoutingInputs inputDevice) if (device is IRoutingInputs inputDevice)
{ {
deviceInfo.HasInputs = true; deviceInfo.HasInputs = true;
deviceInfo.InputPorts = inputDevice.InputPorts.Select(p => new PortInfo deviceInfo.InputPorts = [.. inputDevice.InputPorts.Select(p => new PortInfo
{ {
Key = p.Key, Key = p.Key,
SignalType = p.Type.ToString(), SignalType = p.Type.ToString(),
ConnectionType = p.ConnectionType.ToString(), ConnectionType = p.ConnectionType.ToString(),
IsInternal = p.IsInternal IsInternal = p.IsInternal
}).ToList(); })];
} }
// Check if device implements IRoutingOutputs // Check if device implements IRoutingOutputs
if (device is IRoutingOutputs outputDevice) if (device is IRoutingOutputs outputDevice)
{ {
deviceInfo.HasOutputs = true; deviceInfo.HasOutputs = true;
deviceInfo.OutputPorts = outputDevice.OutputPorts.Select(p => new PortInfo deviceInfo.OutputPorts = [.. outputDevice.OutputPorts.Select(p => new PortInfo
{ {
Key = p.Key, Key = p.Key,
SignalType = p.Type.ToString(), SignalType = p.Type.ToString(),
ConnectionType = p.ConnectionType.ToString(), ConnectionType = p.ConnectionType.ToString(),
IsInternal = p.IsInternal IsInternal = p.IsInternal
}).ToList(); })];
} }
// Check if device implements IRoutingInputsOutputs // Check if device implements IRoutingInputsOutputs
@ -86,10 +86,31 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
IsInternal = tl.IsInternal IsInternal = tl.IsInternal
}).ToList(); }).ToList();
// Get current active routes from DefaultCollection, grouped by signal type
var currentRoutes = RouteDescriptorCollection.DefaultCollection.Descriptors
.GroupBy(d => d.SignalType.ToString())
.Select(g => new CurrentRouteGroupInfo
{
SignalType = g.Key,
Routes = [.. g.Select(d => new ActiveRouteInfo
{
SourceDeviceKey = d.Source.Key,
DestinationDeviceKey = d.Destination.Key,
DestinationInputPortKey = d.InputPort?.Key,
Steps = [.. d.Routes.Select(r => new RouteSwitchStepInfo
{
SwitchingDeviceKey = r.SwitchingDevice?.Key,
InputPortKey = r.InputPort?.Key,
OutputPortKey = r.OutputPort?.Key
})]
})]
}).ToList();
var response = new RoutingSystemInfo var response = new RoutingSystemInfo
{ {
Devices = devices, Devices = devices,
TieLines = tielines TieLines = tielines,
CurrentRoutes = currentRoutes
}; };
var jsonResponse = JsonConvert.SerializeObject(response, Formatting.Indented); var jsonResponse = JsonConvert.SerializeObject(response, Formatting.Indented);
@ -121,6 +142,12 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
/// </summary> /// </summary>
[JsonProperty("tieLines")] [JsonProperty("tieLines")]
public List<TieLineInfo> TieLines { get; set; } public List<TieLineInfo> TieLines { get; set; }
/// <summary>
/// Gets or sets the current active routes in the system, grouped by signal type
/// </summary>
[JsonProperty("currentRoutes")]
public List<CurrentRouteGroupInfo> CurrentRoutes { get; set; }
} }
/// <summary> /// <summary>
@ -238,4 +265,77 @@ namespace PepperDash.Essentials.Core.Web.RequestHandlers
[JsonProperty("isInternal")] [JsonProperty("isInternal")]
public bool IsInternal { get; set; } public bool IsInternal { get; set; }
} }
/// <summary>
/// Represents a group of active routes for a given signal type
/// </summary>
public class CurrentRouteGroupInfo
{
/// <summary>
/// Gets or sets the signal type for the group of active routes (e.g., AudioVideo, Audio, Video, etc.)
/// </summary>
[JsonProperty("signalType")]
public string SignalType { get; set; }
/// <summary>
/// Gets or sets the list of active routes for the given signal type
/// </summary>
[JsonProperty("routes")]
public List<ActiveRouteInfo> Routes { get; set; }
}
/// <summary>
/// Represents a single active route from a source to a destination
/// </summary>
public class ActiveRouteInfo
{
/// <summary>
/// Gets or sets the key of the source device for the active route
/// </summary>
[JsonProperty("sourceDeviceKey")]
public string SourceDeviceKey { get; set; }
/// <summary>
/// Gets or sets the key of the destination device for the active route
/// </summary>
[JsonProperty("destinationDeviceKey")]
public string DestinationDeviceKey { get; set; }
/// <summary>
/// Gets or sets the key of the destination input port for the active route, if applicable
/// </summary>
[JsonProperty("destinationInputPortKey", NullValueHandling = NullValueHandling.Ignore)]
public string DestinationInputPortKey { get; set; }
/// <summary>
/// Gets or sets the list of switching steps for the active route
/// </summary>
[JsonProperty("steps")]
public List<RouteSwitchStepInfo> Steps { get; set; }
}
/// <summary>
/// Represents a single switching step within a route
/// </summary>
public class RouteSwitchStepInfo
{
/// <summary>
/// Gets or sets the key of the switching device for the route step
/// </summary>
[JsonProperty("switchingDeviceKey", NullValueHandling = NullValueHandling.Ignore)]
public string SwitchingDeviceKey { get; set; }
/// <summary>
/// Gets or sets the key of the input port for the route step, if applicable
/// </summary>
[JsonProperty("inputPortKey", NullValueHandling = NullValueHandling.Ignore)]
public string InputPortKey { get; set; }
/// <summary>
/// Gets or sets the key of the output port for the route step, if applicable
/// </summary>
[JsonProperty("outputPortKey", NullValueHandling = NullValueHandling.Ignore)]
public string OutputPortKey { get; set; }
}
} }

View file

@ -0,0 +1,121 @@
using System;
using Crestron.SimplSharp.CrestronAuthentication;
using Crestron.SimplSharp.WebScripting;
using Newtonsoft.Json;
using PepperDash.Core.Web.RequestHandlers;
namespace PepperDash.Essentials.Core.Web.RequestHandlers;
/// <summary>
/// Represents a LoginRequestHandler
/// </summary>
public class LoginRequestHandler : WebApiBaseRequestHandler
{
/// <summary>
/// Constructor
/// </summary>
/// <remarks>
/// base(true) enables CORS support by default
/// </remarks>
public LoginRequestHandler()
: base(true)
{
}
/// <summary>
/// Handles POST method requests for user login and token generation
/// </summary>
/// <param name="context">The HTTP context for the request.</param>
protected override void HandlePost(HttpCwsContext context)
{
try
{
if (context.Request.ContentLength < 0)
{
context.Response.StatusCode = 400;
context.Response.StatusDescription = "Bad Request";
context.Response.End();
return;
}
var data = context.Request.GetRequestBody();
if (string.IsNullOrEmpty(data))
{
context.Response.StatusCode = 400;
context.Response.StatusDescription = "Bad Request";
context.Response.End();
return;
}
var loginRequest = JsonConvert.DeserializeObject<LoginRequest>(data);
if (loginRequest == null || string.IsNullOrEmpty(loginRequest.Username) || string.IsNullOrEmpty(loginRequest.Password))
{
context.Response.StatusCode = 400;
context.Response.StatusDescription = "Bad Request";
context.Response.End();
return;
}
Authentication.UserToken token;
try
{
token = Authentication.GetAuthenticationToken(loginRequest.Username, loginRequest.Password);
}
catch (ArgumentException)
{
context.Response.StatusCode = 401;
context.Response.StatusDescription = "Bad Request";
context.Response.End();
return;
}
if (!token.Valid)
{
context.Response.StatusCode = 401;
context.Response.StatusDescription = "Unauthorized";
context.Response.End();
return;
}
context.Response.StatusCode = 200;
context.Response.StatusDescription = "OK";
context.Response.ContentType = "application/json";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Write(JsonConvert.SerializeObject(new { Token = token }, Formatting.Indented), false);
context.Response.End();
}
catch (System.Exception ex)
{
context.Response.StatusCode = 500;
context.Response.StatusDescription = "Internal Server Error";
context.Response.ContentType = "application/json";
context.Response.ContentEncoding = System.Text.Encoding.UTF8;
context.Response.Write(JsonConvert.SerializeObject(new { Error = ex.Message }, Formatting.Indented), false);
context.Response.End();
}
}
}
/// <summary>
/// Represents a LoginRequest
/// </summary>
public class LoginRequest
{
/// <summary>
/// Gets or sets the username.
/// </summary>
public string Username { get; set; }
/// <summary>
/// Gets or sets the password.
/// </summary>
public string Password { get; set; }
}