mirror of https://github.com/Ombi-app/Ombi
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
5.2 KiB
152 lines
5.2 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Claims;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Extensions.Options;
|
|
using Newtonsoft.Json;
|
|
using Ombi.Core.IdentityResolver;
|
|
using Ombi.Models;
|
|
using Ombi.Store.Context;
|
|
using Ombi.Store.Repository;
|
|
|
|
namespace Ombi.Auth
|
|
{
|
|
public class TokenProviderMiddleware
|
|
{
|
|
private readonly RequestDelegate _next;
|
|
private readonly TokenProviderOptions _options;
|
|
private readonly JsonSerializerSettings _serializerSettings;
|
|
private readonly IUserIdentityManager _identityManager;
|
|
|
|
public TokenProviderMiddleware(
|
|
RequestDelegate next,
|
|
IOptions<TokenProviderOptions> options, IUserIdentityManager manager)
|
|
{
|
|
_next = next;
|
|
_options = options.Value;
|
|
ThrowIfInvalidOptions(_options);
|
|
|
|
_serializerSettings = new JsonSerializerSettings
|
|
{
|
|
Formatting = Formatting.Indented
|
|
};
|
|
_identityManager = manager;
|
|
}
|
|
|
|
public Task Invoke(HttpContext context)
|
|
{
|
|
// If the request path doesn't match, skip
|
|
if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
|
|
{
|
|
return _next(context);
|
|
}
|
|
|
|
// Request must be POST with Content-Type: application/json
|
|
if (!context.Request.Method.Equals("POST")
|
|
)
|
|
{
|
|
context.Response.StatusCode = 400;
|
|
return context.Response.WriteAsync("Bad request.");
|
|
}
|
|
|
|
|
|
return GenerateToken(context);
|
|
}
|
|
|
|
private async Task GenerateToken(HttpContext context)
|
|
{
|
|
var request = context.Request;
|
|
UserAuthModel userInfo;
|
|
|
|
using (var bodyReader = new StreamReader(request.Body))
|
|
{
|
|
string body = await bodyReader.ReadToEndAsync();
|
|
userInfo = JsonConvert.DeserializeObject<UserAuthModel>(body);
|
|
}
|
|
|
|
var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password, _identityManager);
|
|
if (identity == null)
|
|
{
|
|
context.Response.StatusCode = 400;
|
|
await context.Response.WriteAsync("Invalid username or password.");
|
|
return;
|
|
}
|
|
|
|
var now = DateTime.UtcNow;
|
|
|
|
// Specifically add the jti (nonce), iat (issued timestamp), and sub (subject/user) claims.
|
|
// You can add other claims here, if you want:
|
|
var jwtClaims = new List<Claim>
|
|
{
|
|
new Claim(JwtRegisteredClaimNames.Sub, userInfo.Username),
|
|
new Claim(JwtRegisteredClaimNames.Jti, await _options.NonceGenerator()),
|
|
new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(now).ToUniversalTime().ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
|
|
};
|
|
|
|
identity.Claims.ToList().AddRange(jwtClaims);
|
|
|
|
// Create the JWT and write it to a string
|
|
var jwt = new JwtSecurityToken(
|
|
issuer: _options.Issuer,
|
|
audience: _options.Audience,
|
|
claims: identity.Claims,
|
|
notBefore: now,
|
|
expires: now.Add(_options.Expiration),
|
|
signingCredentials: _options.SigningCredentials);
|
|
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
|
|
|
|
var response = new
|
|
{
|
|
access_token = encodedJwt,
|
|
expires_in = (int)_options.Expiration.TotalSeconds
|
|
};
|
|
|
|
// Serialize and return the response
|
|
context.Response.ContentType = "application/json";
|
|
await context.Response.WriteAsync(JsonConvert.SerializeObject(response, _serializerSettings));
|
|
}
|
|
|
|
private static void ThrowIfInvalidOptions(TokenProviderOptions options)
|
|
{
|
|
if (string.IsNullOrEmpty(options.Path))
|
|
{
|
|
throw new ArgumentNullException(nameof(TokenProviderOptions.Path));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(options.Issuer))
|
|
{
|
|
throw new ArgumentNullException(nameof(TokenProviderOptions.Issuer));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(options.Audience))
|
|
{
|
|
throw new ArgumentNullException(nameof(TokenProviderOptions.Audience));
|
|
}
|
|
|
|
if (options.Expiration == TimeSpan.Zero)
|
|
{
|
|
throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(TokenProviderOptions.Expiration));
|
|
}
|
|
|
|
if (options.IdentityResolver == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(TokenProviderOptions.IdentityResolver));
|
|
}
|
|
|
|
if (options.SigningCredentials == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(TokenProviderOptions.SigningCredentials));
|
|
}
|
|
|
|
if (options.NonceGenerator == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(TokenProviderOptions.NonceGenerator));
|
|
}
|
|
}
|
|
|
|
}
|
|
} |