fix: return correct error in GET /users/@me
This commit is contained in:
		
							parent
							
								
									6c9d1c328b
								
							
						
					
					
						commit
						22d09ad7a6
					
				
					 5 changed files with 37 additions and 17 deletions
				
			
		| 
						 | 
					@ -20,15 +20,16 @@ public class UsersController(
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    [HttpGet("{userRef}")]
 | 
					    [HttpGet("{userRef}")]
 | 
				
			||||||
    [ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType<UserRendererService.UserResponse>(statusCode: StatusCodes.Status200OK)]
 | 
				
			||||||
    public async Task<IActionResult> GetUserAsync(string userRef)
 | 
					    public async Task<IActionResult> GetUserAsync(string userRef, CancellationToken ct = default)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var user = await db.ResolveUserAsync(userRef, CurrentToken);
 | 
					        var user = await db.ResolveUserAsync(userRef, CurrentToken, ct);
 | 
				
			||||||
        return Ok(await userRendererService.RenderUserAsync(
 | 
					        return Ok(await userRendererService.RenderUserAsync(
 | 
				
			||||||
            user,
 | 
					            user,
 | 
				
			||||||
            selfUser: CurrentUser,
 | 
					            selfUser: CurrentUser,
 | 
				
			||||||
            token: CurrentToken,
 | 
					            token: CurrentToken,
 | 
				
			||||||
            renderMembers: true,
 | 
					            renderMembers: true,
 | 
				
			||||||
            renderAuthMethods: true
 | 
					            renderAuthMethods: true,
 | 
				
			||||||
 | 
					            ct: ct
 | 
				
			||||||
        ));
 | 
					        ));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -59,6 +60,11 @@ public class UsersController(
 | 
				
			||||||
            user.Bio = req.Bio;
 | 
					            user.Bio = req.Bio;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (req.HasProperty(nameof(req.Links)))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            user.Links = req.Links ?? [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (req.HasProperty(nameof(req.Avatar)))
 | 
					        if (req.HasProperty(nameof(req.Avatar)))
 | 
				
			||||||
            errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar)));
 | 
					            errors.Add(("avatar", ValidationUtils.ValidateAvatar(req.Avatar)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -150,5 +156,6 @@ public class UsersController(
 | 
				
			||||||
        public string? DisplayName { get; init; }
 | 
					        public string? DisplayName { get; init; }
 | 
				
			||||||
        public string? Bio { get; init; }
 | 
					        public string? Bio { get; init; }
 | 
				
			||||||
        public string? Avatar { get; init; }
 | 
					        public string? Avatar { get; init; }
 | 
				
			||||||
 | 
					        public string[]? Links { get; init; }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -9,23 +9,29 @@ namespace Foxnouns.Backend.Database;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public static class DatabaseQueryExtensions
 | 
					public static class DatabaseQueryExtensions
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static async Task<User> ResolveUserAsync(this DatabaseContext context, string userRef, Token? token)
 | 
					    public static async Task<User> ResolveUserAsync(this DatabaseContext context, string userRef, Token? token,
 | 
				
			||||||
 | 
					        CancellationToken ct = default)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (userRef == "@me" && token != null)
 | 
					        if (userRef == "@me")
 | 
				
			||||||
            return await context.Users.FirstAsync(u => u.Id == token.UserId);
 | 
					        {
 | 
				
			||||||
 | 
					            return token != null
 | 
				
			||||||
 | 
					                ? await context.Users.FirstAsync(u => u.Id == token.UserId, ct)
 | 
				
			||||||
 | 
					                : throw new ApiError.Unauthorized("This endpoint requires an authenticated user.",
 | 
				
			||||||
 | 
					                    ErrorCode.AuthenticationRequired);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        User? user;
 | 
					        User? user;
 | 
				
			||||||
        if (Snowflake.TryParse(userRef, out var snowflake))
 | 
					        if (Snowflake.TryParse(userRef, out var snowflake))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            user = await context.Users
 | 
					            user = await context.Users
 | 
				
			||||||
                .Where(u => !u.Deleted)
 | 
					                .Where(u => !u.Deleted)
 | 
				
			||||||
                .FirstOrDefaultAsync(u => u.Id == snowflake);
 | 
					                .FirstOrDefaultAsync(u => u.Id == snowflake, ct);
 | 
				
			||||||
            if (user != null) return user;
 | 
					            if (user != null) return user;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        user = await context.Users
 | 
					        user = await context.Users
 | 
				
			||||||
            .Where(u => !u.Deleted)
 | 
					            .Where(u => !u.Deleted)
 | 
				
			||||||
            .FirstOrDefaultAsync(u => u.Username == userRef);
 | 
					            .FirstOrDefaultAsync(u => u.Username == userRef, ct);
 | 
				
			||||||
        if (user != null) return user;
 | 
					        if (user != null) return user;
 | 
				
			||||||
        throw new ApiError.NotFound("No user with that ID or username found.", code: ErrorCode.UserNotFound);
 | 
					        throw new ApiError.NotFound("No user with that ID or username found.", code: ErrorCode.UserNotFound);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,11 +23,15 @@ public class ApiError(string message, HttpStatusCode? statusCode = null, ErrorCo
 | 
				
			||||||
    public readonly HttpStatusCode StatusCode = statusCode ?? HttpStatusCode.InternalServerError;
 | 
					    public readonly HttpStatusCode StatusCode = statusCode ?? HttpStatusCode.InternalServerError;
 | 
				
			||||||
    public readonly ErrorCode ErrorCode = errorCode ?? ErrorCode.InternalServerError;
 | 
					    public readonly ErrorCode ErrorCode = errorCode ?? ErrorCode.InternalServerError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public class Unauthorized(string message) : ApiError(message, statusCode: HttpStatusCode.Unauthorized,
 | 
					    public class Unauthorized(string message, ErrorCode errorCode = ErrorCode.AuthenticationError) : ApiError(message,
 | 
				
			||||||
        errorCode: ErrorCode.AuthenticationError);
 | 
					        statusCode: HttpStatusCode.Unauthorized,
 | 
				
			||||||
 | 
					        errorCode: errorCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public class Forbidden(string message, IEnumerable<string>? scopes = null)
 | 
					    public class Forbidden(
 | 
				
			||||||
        : ApiError(message, statusCode: HttpStatusCode.Forbidden)
 | 
					        string message,
 | 
				
			||||||
 | 
					        IEnumerable<string>? scopes = null,
 | 
				
			||||||
 | 
					        ErrorCode errorCode = ErrorCode.Forbidden)
 | 
				
			||||||
 | 
					        : ApiError(message, statusCode: HttpStatusCode.Forbidden, errorCode: errorCode)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public readonly string[] Scopes = scopes?.ToArray() ?? [];
 | 
					        public readonly string[] Scopes = scopes?.ToArray() ?? [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -115,6 +119,8 @@ public enum ErrorCode
 | 
				
			||||||
    Forbidden,
 | 
					    Forbidden,
 | 
				
			||||||
    BadRequest,
 | 
					    BadRequest,
 | 
				
			||||||
    AuthenticationError,
 | 
					    AuthenticationError,
 | 
				
			||||||
 | 
					    AuthenticationRequired,
 | 
				
			||||||
 | 
					    MissingScopes,
 | 
				
			||||||
    GenericApiError,
 | 
					    GenericApiError,
 | 
				
			||||||
    UserNotFound,
 | 
					    UserNotFound,
 | 
				
			||||||
    MemberNotFound,
 | 
					    MemberNotFound,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,10 +18,10 @@ public class AuthorizationMiddleware : IMiddleware
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var token = ctx.GetToken();
 | 
					        var token = ctx.GetToken();
 | 
				
			||||||
        if (token == null)
 | 
					        if (token == null)
 | 
				
			||||||
            throw new ApiError.Unauthorized("This endpoint requires an authenticated user.");
 | 
					            throw new ApiError.Unauthorized("This endpoint requires an authenticated user.", ErrorCode.AuthenticationRequired);
 | 
				
			||||||
        if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.Scopes.ExpandScopes()).Any())
 | 
					        if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.Scopes.ExpandScopes()).Any())
 | 
				
			||||||
            throw new ApiError.Forbidden("This endpoint requires ungranted scopes.",
 | 
					            throw new ApiError.Forbidden("This endpoint requires ungranted scopes.",
 | 
				
			||||||
                attribute.Scopes.Except(token.Scopes.ExpandScopes()));
 | 
					                attribute.Scopes.Except(token.Scopes.ExpandScopes()), ErrorCode.MissingScopes);
 | 
				
			||||||
        if (attribute.RequireAdmin && token.User.Role != UserRole.Admin)
 | 
					        if (attribute.RequireAdmin && token.User.Role != UserRole.Admin)
 | 
				
			||||||
            throw new ApiError.Forbidden("This endpoint can only be used by admins.");
 | 
					            throw new ApiError.Forbidden("This endpoint can only be used by admins.");
 | 
				
			||||||
        if (attribute.RequireModerator && token.User.Role != UserRole.Admin && token.User.Role != UserRole.Moderator)
 | 
					        if (attribute.RequireModerator && token.User.Role != UserRole.Admin && token.User.Role != UserRole.Moderator)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,7 +12,8 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe
 | 
				
			||||||
    public async Task<UserResponse> RenderUserAsync(User user, User? selfUser = null,
 | 
					    public async Task<UserResponse> RenderUserAsync(User user, User? selfUser = null,
 | 
				
			||||||
        Token? token = null,
 | 
					        Token? token = null,
 | 
				
			||||||
        bool renderMembers = true,
 | 
					        bool renderMembers = true,
 | 
				
			||||||
        bool renderAuthMethods = false)
 | 
					        bool renderAuthMethods = false,
 | 
				
			||||||
 | 
					        CancellationToken ct = default)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var isSelfUser = selfUser?.Id == user.Id;
 | 
					        var isSelfUser = selfUser?.Id == user.Id;
 | 
				
			||||||
        var tokenCanReadHiddenMembers = token.HasScope("member.read") && isSelfUser;
 | 
					        var tokenCanReadHiddenMembers = token.HasScope("member.read") && isSelfUser;
 | 
				
			||||||
| 
						 | 
					@ -24,7 +25,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe
 | 
				
			||||||
        renderAuthMethods = renderAuthMethods && tokenPrivileged;
 | 
					        renderAuthMethods = renderAuthMethods && tokenPrivileged;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        IEnumerable<Member> members =
 | 
					        IEnumerable<Member> members =
 | 
				
			||||||
            renderMembers ? await db.Members.Where(m => m.UserId == user.Id).ToListAsync() : [];
 | 
					            renderMembers ? await db.Members.Where(m => m.UserId == user.Id).ToListAsync(ct) : [];
 | 
				
			||||||
        // Unless the user is requesting their own members AND the token can read hidden members, we filter out unlisted members.
 | 
					        // Unless the user is requesting their own members AND the token can read hidden members, we filter out unlisted members.
 | 
				
			||||||
        if (!(isSelfUser && tokenCanReadHiddenMembers)) members = members.Where(m => m.Unlisted);
 | 
					        if (!(isSelfUser && tokenCanReadHiddenMembers)) members = members.Where(m => m.Unlisted);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,7 +33,7 @@ public class UserRendererService(DatabaseContext db, MemberRendererService membe
 | 
				
			||||||
            ? await db.AuthMethods
 | 
					            ? await db.AuthMethods
 | 
				
			||||||
                .Where(a => a.UserId == user.Id)
 | 
					                .Where(a => a.UserId == user.Id)
 | 
				
			||||||
                .Include(a => a.FediverseApplication)
 | 
					                .Include(a => a.FediverseApplication)
 | 
				
			||||||
                .ToListAsync()
 | 
					                .ToListAsync(ct)
 | 
				
			||||||
            : [];
 | 
					            : [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new UserResponse(
 | 
					        return new UserResponse(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue