Skip to content

Commit

Permalink
add default value for audience enum. LexAuthUser audience property is…
Browse files Browse the repository at this point in the history
… now an enum instead of string. set audience requirement to include default audience unless it's exclusive. Fixes password reset api only being available for reset tokens and not normally logged in users.
  • Loading branch information
hahn-kev committed Oct 20, 2023
1 parent cd73387 commit 810ad47
Show file tree
Hide file tree
Showing 8 changed files with 53 additions and 16 deletions.
10 changes: 9 additions & 1 deletion backend/LexBoxApi/Auth/AuthKernel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@ public static void AddLexBoxAuth(IServiceCollection services,
.Build();
foreach (var audience in Enum.GetValues<LexboxAudience>())
{
options.AddPolicy(audience.PolicyName(), builder =>
if (audience == LexboxAudience.Unknown) continue;
//for exclusive the endpoint is only accessible for the specified audience
options.AddPolicy(audience.PolicyName(true), builder =>
{
builder.RequireAuthenticatedUser();
builder.AddRequirements(new AudienceRequirement(audience));
});
//for non exclusive the endpoint is also accessible for the default audience
options.AddPolicy(audience.PolicyName(false), builder =>
{
builder.RequireAuthenticatedUser();
builder.AddRequirements(new AudienceRequirement(audience, LexboxAudience.LexboxApi));
});
}
//don't use RequireDefaultLexboxAuth here because that only allows the default audience
options.AddPolicy(AllowAnyAudienceAttribute.PolicyName, builder => builder.RequireAuthenticatedUser());
Expand Down
11 changes: 9 additions & 2 deletions backend/LexBoxApi/Auth/RequireAudienceAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
namespace LexBoxApi.Auth;
using LexCore.Auth;

namespace LexBoxApi.Auth;

//todo for now this attribute only supports a single audience, this may be fine for now.
//once we are at dotnet 8 we can support multiple audiences using the new IAuthorizationRequirementData api
//https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-8.0?view=aspnetcore-7.0#iauthorizationrequirementdata
public class RequireAudienceAttribute : LexboxAuthAttribute
{
public RequireAudienceAttribute(LexboxAudience audience) : base(audience.PolicyName())
/// <param name="audience">audience allowed to access this endpoint</param>
/// <param name="exclusive">when false the default audience is also allowed, when true the default audience is not allowed</param>
public RequireAudienceAttribute(LexboxAudience audience, bool exclusive = false) : base(audience.PolicyName(exclusive))
{
}
}
Expand Down
17 changes: 10 additions & 7 deletions backend/LexBoxApi/Auth/Requirements/AudienceRequirementHandler.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
using Microsoft.AspNetCore.Authorization;
using LexCore.Auth;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.JsonWebTokens;

namespace LexBoxApi.Auth.Requirements;

public class AudienceRequirement : IAuthorizationRequirement
{
public LexboxAudience Audience { get; }
public LexboxAudience[] ValidAudiences { get; }

public AudienceRequirement(LexboxAudience audience)
public AudienceRequirement(params LexboxAudience[] validAudiences)
{
Audience = audience;
ValidAudiences = validAudiences;
}
}

public class AudienceRequirementHandler : AuthorizationHandler<AudienceRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AudienceRequirement requirement)
{
var claim = context.User.FindFirst(JwtRegisteredClaimNames.Aud);
if (claim?.Value == requirement.Audience.ToString())
var claim = context.User.FindFirst(LexAuthConstants.AudienceClaimType);
if (Enum.TryParse<LexboxAudience>(claim?.Value, out var audience) &&
requirement.ValidAudiences.Contains(audience))
{
context.Succeed(requirement);
}
else
{
context.Fail(new AuthorizationFailureReason(this,
$"Token does not have the required audience: {requirement.Audience}"));
$"Token does not have the required audience: {requirement.ValidAudiences}"));
}

return Task.CompletedTask;
}
}
3 changes: 2 additions & 1 deletion backend/LexBoxApi/Controllers/AuthTestingController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LexBoxApi.Auth;
using LexCore.Auth;
using Microsoft.AspNetCore.Mvc;

namespace LexBoxApi.Controllers;
Expand All @@ -21,7 +22,7 @@ public OkResult RequiresAdmin()
}

[HttpGet("requires-forgot-password")]
[RequireAudience(LexboxAudience.ForgotPassword)]
[RequireAudience(LexboxAudience.ForgotPassword, true)]
public OkResult RequiresForgotPasswordAudience()
{
return Ok();
Expand Down
9 changes: 8 additions & 1 deletion backend/LexBoxApi/Controllers/LoginController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http;

namespace LexBoxApi.Controllers;

Expand Down Expand Up @@ -40,8 +41,13 @@ public LoginController(LexAuthService lexAuthService,
_turnstileService = turnstileService;
}

/// <summary>
/// this endpoint is called when we can only pass a jwt in the query string. It redirects to the requested path
/// and logs in using that jwt with a cookie
/// </summary>
[HttpGet("loginRedirect")]
[AllowAnyAudience]

public async Task<ActionResult> LoginRedirect(
string jwt, // This is required because auth looks for a jwt in the query string
string returnTo)
Expand Down Expand Up @@ -139,7 +145,8 @@ public async Task<ActionResult> ResetPassword(ResetPasswordRequest request)
await _lexBoxDbContext.SaveChangesAsync();
await _emailService.SendPasswordChangedEmail(user);
//the old jwt is only valid for calling forgot password endpoints, we need to generate a new one
await RefreshJwt();
if (lexAuthUser.Audience == LexboxAudience.ForgotPassword)
await RefreshJwt();
return Ok();
}
}
2 changes: 1 addition & 1 deletion backend/LexCore/Auth/LexAuthUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public LexAuthUser(User user)
[JsonPropertyName(LexAuthConstants.IdClaimType)]
public required Guid Id { get; set; }
[JsonPropertyName(LexAuthConstants.AudienceClaimType)]
public string Audience { get; set; }
public LexboxAudience Audience { get; set; }

[JsonPropertyName(LexAuthConstants.EmailClaimType)]
public required string Email { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
using System.Text.Json.Serialization;

namespace LexBoxApi.Auth;
namespace LexCore.Auth;

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum LexboxAudience
{
//default value of the enum, not a valid audience
Unknown,
//these names are converted to strings and are used in jwt tokens, if the name is changed that will invalidate all existing tokens
LexboxApi,
ForgotPassword,
SendAndReceive,
}

public static class LexboxAudienceHelper
{
public static string PolicyName(this LexboxAudience audience) => $"RequireAudience{audience}Policy";
public static string PolicyName(this LexboxAudience audience, bool exclusive)
{
return $"RequireAudience{audience}{(exclusive ? "Exclusive" : "")}Policy";
}
}
7 changes: 6 additions & 1 deletion frontend/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ type LexAuthUser {
canAccessProject(projectCode: String!): Boolean!
hasProjectCreatePermission: Boolean!
id: UUID!
audience: String!
audience: LexboxAudience!
email: String!
name: String!
role: UserRole!
Expand Down Expand Up @@ -484,6 +484,11 @@ enum DbErrorCode {
DUPLICATE
}

enum LexboxAudience {
LEXBOX_API
FORGOT_PASSWORD
}

enum ProjectMigrationStatus {
UNKNOWN
MIGRATED
Expand Down

0 comments on commit 810ad47

Please sign in to comment.