Created
March 31, 2023 09:20
-
-
Save v0l/74346ae530896115bfe2504c8cd018d3 to your computer and use it in GitHub Desktop.
Nostr auth handler for ASP.NET
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Security.Claims; | |
using System.Text.Encodings.Web; | |
using System.Text.Json; | |
using Microsoft.AspNetCore.Authentication; | |
using Microsoft.Extensions.Options; | |
using NNostr.Client; | |
public static class NostrAuth | |
{ | |
public const string Scheme = "Nostr"; | |
} | |
public class NostrAuthOptions : AuthenticationSchemeOptions | |
{ | |
} | |
public class NostrAuthHandler : AuthenticationHandler<NostrAuthOptions> | |
{ | |
public NostrAuthHandler(IOptionsMonitor<NostrAuthOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : | |
base(options, logger, encoder, clock) | |
{ | |
} | |
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() | |
{ | |
var auth = Request.Headers.Authorization.FirstOrDefault()?.Trim(); | |
if (string.IsNullOrEmpty(auth)) | |
{ | |
return AuthenticateResult.Fail("Missing Authorization header"); | |
} | |
if (!auth.StartsWith(NostrAuth.Scheme)) | |
{ | |
return AuthenticateResult.Fail("Invalid auth scheme"); | |
} | |
var token = auth[6..]; | |
var bToken = Convert.FromBase64String(token); | |
if (string.IsNullOrEmpty(token) || bToken.Length == 0 || bToken[0] != '{') | |
{ | |
return AuthenticateResult.Fail("Invalid token"); | |
} | |
var ev = JsonSerializer.Deserialize<NostrEvent>(bToken); | |
if (ev == default) | |
{ | |
return AuthenticateResult.Fail("Invalid nostr event"); | |
} | |
if (!ev.Verify()) | |
{ | |
return AuthenticateResult.Fail("Invalid nostr event, invalid sig"); | |
} | |
if (ev.Kind != 27_235) | |
{ | |
return AuthenticateResult.Fail("Invalid nostr event, wrong kind"); | |
} | |
var diffTime = Math.Abs((ev.CreatedAt!.Value - DateTime.UtcNow).TotalSeconds); | |
if (diffTime > 10d) | |
{ | |
return AuthenticateResult.Fail("Invalid nostr event, timestamp out of range"); | |
} | |
var urlTag = ev.Tags.FirstOrDefault(a => a.TagIdentifier == "url"); | |
var methodTag = ev.Tags.FirstOrDefault(a => a.TagIdentifier == "method"); | |
if (string.IsNullOrEmpty(urlTag?.Data[0]) || | |
!new Uri(urlTag.Data[0]).AbsolutePath.Equals(Request.Path, StringComparison.InvariantCultureIgnoreCase)) | |
{ | |
return AuthenticateResult.Fail("Invalid nostr event, url tag invalid"); | |
} | |
if (string.IsNullOrEmpty(methodTag?.Data[0]) || | |
!methodTag.Data[0].Equals(Request.Method, StringComparison.InvariantCultureIgnoreCase)) | |
{ | |
return AuthenticateResult.Fail("Invalid nostr event, method tag invalid"); | |
} | |
var principal = new ClaimsIdentity(new[] | |
{ | |
new Claim(ClaimTypes.Name, ev.PublicKey) | |
}); | |
return AuthenticateResult.Success(new(new ClaimsPrincipal(new[] {principal}), Scheme.Name)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment