Skip to content

Commit c8258e4

Browse files
committed
Merge branch 'dev_v5.x'
2 parents 334c109 + 1ecc159 commit c8258e4

41 files changed

Lines changed: 1358 additions & 510 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AdvancedLicensing/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Before implementing, [submit a support request](https://support.scichart.com/sup
1212

1313
### [Simple Server Validation](SimpleServerSideLicensing/)
1414

15-
Server computes an HMAC-SHA256 token using a hex key from MyAccount. No native binaries, no FFI, no challenge/response. Requires a license with the `"SV"` feature flag.
15+
Server computes an HMAC-SHA256 token using a hex key from MyAccount. No native binaries, no FFI, no asymmetric challenge/response. Two modes (inline or round-trip with client nonce) selected per-licence. Requires a license with the `SV:H:V:N` feature flag.
1616

1717
**Use this if:** you have a web server you control, or are building an Electron or Tauri desktop app, and want the easiest possible integration.
1818

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[Dd]ebug/
2+
[Rr]elease/
3+
[Bb]in/
4+
[Oo]bj/
5+
.vs/
6+
*.user
7+
pack/
8+
*.nupkg
9+
*.snupkg
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace SciChart.AspNetCore.SimpleLicensing;
2+
3+
/// <summary>
4+
/// Generates SciChart Simple Server Validation v2 tokens.
5+
/// </summary>
6+
public interface ISciChartLicenseTokenService
7+
{
8+
/// <summary>
9+
/// Returns an inline token of the form <c>v2:serverNonce:serverNow:hmac</c>.
10+
/// Servable to many clients and embeddable in HTML via a meta tag.
11+
/// </summary>
12+
string CreateInlineToken();
13+
14+
/// <summary>
15+
/// Returns a round-trip token of the form <c>v2:clientNonce:serverNonce:serverNow:hmac</c>.
16+
/// The client nonce is echoed verbatim — caller must validate it first.
17+
/// </summary>
18+
string CreateRoundTripToken(string clientNonce);
19+
20+
/// <summary>
21+
/// Checks whether the given string is shaped as a valid client nonce
22+
/// (8–64 hex characters). Use before passing untrusted input to
23+
/// <see cref="CreateRoundTripToken"/>.
24+
/// </summary>
25+
bool IsValidClientNonce(string clientNonce);
26+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
SciChart.AspNetCore.SimpleLicensing
2+
Copyright (c) SciChart Ltd. All rights reserved.
3+
4+
This package is licensed under the SciChart End User License Agreement.
5+
6+
You may only use this package together with a valid SciChart.js licence that
7+
includes the Simple Server Validation (SV) feature. Use without such a licence
8+
is not permitted.
9+
10+
The current SciChart EULA is available at:
11+
https://www.scichart.com/scichart-eula/
12+
13+
For licensing enquiries, contact:
14+
sales@scichart.com — commercial licences
15+
support@scichart.com — technical support
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# SciChart.AspNetCore.SimpleLicensing
2+
3+
ASP.NET Core integration for [SciChart.js](https://www.scichart.com) **Simple Server Validation v2** — server-signed license tokens using HMAC-SHA256, no native dependency.
4+
5+
This package contains:
6+
7+
- `ISciChartLicenseTokenService` — generates v2 tokens (inline + round-trip).
8+
- `<scichart-license />` tag helper — embeds the token in `<head>` as `<meta name="x-scichart-license" content="v2:..." />`.
9+
- `MapSciChartLicenseEndpoint()` — minimal-API endpoint that serves the token over `GET /api/license`.
10+
11+
Use both together. The tag helper covers initial validation on every full page load (no XHR needed). The endpoint covers re-validation when the cached `scLicense` cookie expires — long-running SPAs eventually outlive `valid_time` and need a fresh token without a reload — and is also the only path the client uses when the licence has `validate_nonce=1`.
12+
13+
## Prerequisites
14+
15+
- ASP.NET Core 8.0 or newer
16+
- A SciChart license with the `SV:H:V:N` feature flag, where:
17+
- `max_skew` — accepted client/server clock skew as `H` or `H.MM`
18+
- `valid_time` — token validity in the client's wall clock (`H.MM`)
19+
- `validate_nonce``0` permissive (inline or round-trip); `1` restricted (round-trip only)
20+
- A 64-char hex Server Secret from [SciChart MyAccount](https://www.scichart.com/profile)
21+
22+
Contact [support@scichart.com](mailto:support@scichart.com) to have an SV v2 feature added to your license.
23+
24+
## Usage
25+
26+
### 1. Install
27+
28+
```bash
29+
dotnet add package SciChart.AspNetCore.SimpleLicensing
30+
```
31+
32+
### 2. Configure
33+
34+
`appsettings.json`:
35+
36+
```json
37+
{
38+
"SciChart": {
39+
"ServerSecret": "0123456789abcdef..."
40+
}
41+
}
42+
```
43+
44+
### 3. Register
45+
46+
`Program.cs`:
47+
48+
```csharp
49+
builder.Services.AddSciChartSimpleLicensing(builder.Configuration.GetSection("SciChart"));
50+
51+
// ... after `var app = builder.Build();`
52+
app.MapSciChartLicenseEndpoint();
53+
```
54+
55+
Alternative inline configuration:
56+
57+
```csharp
58+
builder.Services.AddSciChartSimpleLicensing(opts =>
59+
opts.ServerSecret = "0123456789abcdef...");
60+
```
61+
62+
### 4. Register the tag helper
63+
64+
`Views/_ViewImports.cshtml`:
65+
66+
```cshtml
67+
@addTagHelper *, SciChart.AspNetCore.SimpleLicensing
68+
```
69+
70+
### 5. Emit the meta tag
71+
72+
`Views/Shared/_Layout.cshtml`:
73+
74+
```cshtml
75+
<!DOCTYPE html>
76+
<html>
77+
<head>
78+
<scichart-license />
79+
<!-- other head content -->
80+
</head>
81+
<body>
82+
@RenderBody()
83+
</body>
84+
</html>
85+
```
86+
87+
### 6. Client side
88+
89+
The client still sets its runtime license key as usual:
90+
91+
```ts
92+
SciChartSurface.setRuntimeLicenseKey("YOUR_CLIENT_LICENSE_KEY");
93+
```
94+
95+
That's it. Any page rendered through the layout carries a fresh server-signed v2 token in its head; the SciChart WASM runtime picks it up on `SciChartSurface.create(...)` and validates it offline.
96+
97+
## How the token gets to the client
98+
99+
Initial validation on every full page load — no XHR:
100+
101+
```
102+
Server-rendered page:
103+
<head>
104+
<meta name="x-scichart-license"
105+
content="v2:<serverNonce>:<serverNow>:<hmac>" />
106+
</head>
107+
108+
SciChart WASM verifies HMAC against the runtime licence key's
109+
embedded server secret, checks clock skew against max_skew,
110+
enforces monotonicity, and caches the result in `scLicense`
111+
until valid_time has elapsed.
112+
```
113+
114+
Re-validation when the cached cookie expires — uses the mapped endpoint:
115+
116+
```
117+
Browser (SciChart.js)
118+
→ GET /api/license?orderid=<X>[&nonce=<hex>]
119+
← 200 OK body: v2:[clientNonce:]serverNonce:serverNow:hmac
120+
```
121+
122+
A long-running SPA that stays open past `valid_time` will hit this path; the meta tag from the original page render is no longer fresh enough. Licences with `validate_nonce=1` use the same endpoint and additionally echo a client-generated nonce.
123+
124+
## Why a singleton
125+
126+
`ISciChartLicenseTokenService` is registered as a singleton — the secret is hex-decoded to bytes once at first resolution. To rotate the secret, restart the host (the standard ASP.NET Core options pattern would otherwise hot-reload `IOptionsMonitor<T>`, but a token-signing key change is a security-sensitive event that warrants an explicit restart).
127+
128+
## Differences from Advanced Server Licensing
129+
130+
| | Simple (this package) | Advanced (`SciChart.Server.Licensing`) |
131+
| --------------------------- | ------------------------------------ | -------------------------------------- |
132+
| Server dependency | None (stdlib HMAC) | Native DLL + FFI |
133+
| Crypto | Symmetric HMAC-SHA256 | Asymmetric NaCl box |
134+
| Token validity | Per-licence (`valid_time`) | 7 days, daily re-validation |
135+
| Cross-origin replay defence | Round-trip shape + client nonce | Encrypted challenge enforces domain |
136+
| Clock-skew tolerance | Per-licence (`max_skew`, 0 disables) | Anchored on client time |
137+
| Required feature flag | `SV:H:V:N` | none |
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
8+
<NoWarn>$(NoWarn);CS1591</NoWarn>
9+
<Deterministic>true</Deterministic>
10+
</PropertyGroup>
11+
12+
<PropertyGroup>
13+
<PackageId>SciChart.AspNetCore.SimpleLicensing</PackageId>
14+
<Version>1.0.0</Version>
15+
<Authors>SciChart Ltd</Authors>
16+
<Company>SciChart Ltd</Company>
17+
<Product>SciChart.js Advanced Licensing</Product>
18+
<Description>Server-side support for SciChart.js Simple Server Validation v2 in ASP.NET Core: token service, tag helper for inline meta-tag delivery, and an endpoint mapper for the round-trip shape.</Description>
19+
<PackageProjectUrl>https://www.scichart.com</PackageProjectUrl>
20+
<RepositoryUrl>https://github.com/ABTSoftware/SciChart.JS.Examples</RepositoryUrl>
21+
<RepositoryType>git</RepositoryType>
22+
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
23+
<PackageTags>scichart;licensing;aspnetcore;hmac</PackageTags>
24+
<PackageReadmeFile>README.md</PackageReadmeFile>
25+
<IsPackable>true</IsPackable>
26+
<IncludeSymbols>true</IncludeSymbols>
27+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
28+
</PropertyGroup>
29+
30+
<ItemGroup>
31+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
32+
</ItemGroup>
33+
34+
<ItemGroup>
35+
<None Include="README.md" Pack="true" PackagePath="\" />
36+
<None Include="LICENSE.txt" Pack="true" PackagePath="\" />
37+
</ItemGroup>
38+
39+
</Project>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
using System.Security.Cryptography;
2+
using System.Text;
3+
using System.Text.RegularExpressions;
4+
using Microsoft.Extensions.Options;
5+
6+
namespace SciChart.AspNetCore.SimpleLicensing;
7+
8+
internal sealed class SciChartLicenseTokenService : ISciChartLicenseTokenService
9+
{
10+
private static readonly Regex ClientNoncePattern =
11+
new("^[0-9a-fA-F]{8,64}$", RegexOptions.Compiled);
12+
13+
private readonly byte[] _secretBytes;
14+
15+
public SciChartLicenseTokenService(IOptions<SciChartSimpleLicensingOptions> options)
16+
{
17+
var secret = options.Value.ServerSecret;
18+
if (string.IsNullOrWhiteSpace(secret))
19+
{
20+
throw new InvalidOperationException(
21+
"SciChartSimpleLicensingOptions.ServerSecret is not set. " +
22+
"Configure it from SciChart MyAccount — a 64-character hex string.");
23+
}
24+
try
25+
{
26+
_secretBytes = Convert.FromHexString(secret);
27+
}
28+
catch (FormatException ex)
29+
{
30+
throw new InvalidOperationException(
31+
"SciChartSimpleLicensingOptions.ServerSecret must be a hex string.", ex);
32+
}
33+
}
34+
35+
public string CreateInlineToken()
36+
{
37+
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
38+
var serverNonce = NewServerNonce();
39+
return Sign($"v2:{serverNonce}:{now}");
40+
}
41+
42+
public string CreateRoundTripToken(string clientNonce)
43+
{
44+
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
45+
var serverNonce = NewServerNonce();
46+
return Sign($"v2:{clientNonce}:{serverNonce}:{now}");
47+
}
48+
49+
public bool IsValidClientNonce(string clientNonce) =>
50+
!string.IsNullOrEmpty(clientNonce) && ClientNoncePattern.IsMatch(clientNonce);
51+
52+
private static string NewServerNonce() =>
53+
Convert.ToHexString(RandomNumberGenerator.GetBytes(8)).ToLowerInvariant();
54+
55+
private string Sign(string payload)
56+
{
57+
var mac = HMACSHA256.HashData(_secretBytes, Encoding.UTF8.GetBytes(payload));
58+
return $"{payload}:{Convert.ToHexString(mac).ToLowerInvariant()}";
59+
}
60+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Microsoft.AspNetCore.Builder;
2+
using Microsoft.AspNetCore.Http;
3+
using Microsoft.AspNetCore.Routing;
4+
5+
namespace SciChart.AspNetCore.SimpleLicensing;
6+
7+
public static class SciChartSimpleLicensingEndpointRouteBuilderExtensions
8+
{
9+
/// <summary>
10+
/// Maps <c>GET {pattern}</c> (default <c>/api/license</c>) to serve SciChart v2 license tokens.
11+
/// Returns the inline shape by default; if the request carries <c>?nonce=&lt;hex&gt;</c>, returns
12+
/// the round-trip shape with the client nonce echoed and signed.
13+
/// </summary>
14+
/// <remarks>
15+
/// Call this alongside the <c>&lt;scichart-license /&gt;</c> tag helper. The tag helper covers
16+
/// initial validation on each full page load; this endpoint covers re-validation when the
17+
/// cached <c>scLicense</c> cookie expires (after <c>valid_time</c>) without a page reload —
18+
/// e.g. long-running SPA sessions — and is also the only path used when the licence has
19+
/// <c>validate_nonce=1</c>.
20+
/// </remarks>
21+
public static RouteHandlerBuilder MapSciChartLicenseEndpoint(
22+
this IEndpointRouteBuilder endpoints,
23+
string pattern = "/api/license")
24+
{
25+
ArgumentNullException.ThrowIfNull(endpoints);
26+
27+
return endpoints.MapGet(pattern, (ISciChartLicenseTokenService svc, HttpContext ctx) =>
28+
{
29+
var rawNonce = ctx.Request.Query["nonce"].ToString();
30+
if (!string.IsNullOrEmpty(rawNonce))
31+
{
32+
if (!svc.IsValidClientNonce(rawNonce))
33+
{
34+
return Results.Text("Error: malformed client nonce", "text/plain", statusCode: 400);
35+
}
36+
return Results.Text(svc.CreateRoundTripToken(rawNonce), "text/plain");
37+
}
38+
return Results.Text(svc.CreateInlineToken(), "text/plain");
39+
});
40+
}
41+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace SciChart.AspNetCore.SimpleLicensing;
2+
3+
/// <summary>
4+
/// Configuration for SciChart Simple Server Validation v2.
5+
/// </summary>
6+
public sealed class SciChartSimpleLicensingOptions
7+
{
8+
/// <summary>
9+
/// Server Secret from SciChart MyAccount (64 hex characters). Required.
10+
/// Hex is decoded to raw bytes for the HMAC key — do not hex-decode it yourself.
11+
/// </summary>
12+
public string ServerSecret { get; set; } = string.Empty;
13+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
3+
4+
namespace SciChart.AspNetCore.SimpleLicensing;
5+
6+
public static class SciChartSimpleLicensingServiceCollectionExtensions
7+
{
8+
/// <summary>
9+
/// Registers <see cref="ISciChartLicenseTokenService"/> and binds options from a delegate.
10+
/// </summary>
11+
public static IServiceCollection AddSciChartSimpleLicensing(
12+
this IServiceCollection services,
13+
Action<SciChartSimpleLicensingOptions> configure)
14+
{
15+
ArgumentNullException.ThrowIfNull(configure);
16+
services.Configure(configure);
17+
services.AddSingleton<ISciChartLicenseTokenService, SciChartLicenseTokenService>();
18+
return services;
19+
}
20+
21+
/// <summary>
22+
/// Registers <see cref="ISciChartLicenseTokenService"/> and binds options from a configuration section
23+
/// (e.g. <c>builder.Configuration.GetSection("SciChart")</c>).
24+
/// </summary>
25+
public static IServiceCollection AddSciChartSimpleLicensing(
26+
this IServiceCollection services,
27+
IConfiguration configuration)
28+
{
29+
ArgumentNullException.ThrowIfNull(configuration);
30+
services.Configure<SciChartSimpleLicensingOptions>(configuration);
31+
services.AddSingleton<ISciChartLicenseTokenService, SciChartLicenseTokenService>();
32+
return services;
33+
}
34+
}

0 commit comments

Comments
 (0)