diff --git a/back/src/Kyoo.Abstractions/Extensions.cs b/back/src/Kyoo.Abstractions/Extensions.cs
index 1d4a26c2..05756ba6 100644
--- a/back/src/Kyoo.Abstractions/Extensions.cs
+++ b/back/src/Kyoo.Abstractions/Extensions.cs
@@ -20,6 +20,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
+using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Authentication.Models;
namespace Kyoo.Authentication
@@ -52,5 +53,13 @@ namespace Kyoo.Authentication
return id;
return null;
}
+
+ public static Guid GetIdOrThrow(this ClaimsPrincipal user)
+ {
+ Guid? ret = user.GetId();
+ if (ret == null)
+ throw new UnauthorizedException();
+ return ret.Value;
+ }
}
}
diff --git a/back/src/Kyoo.Abstractions/Models/Exceptions/UnauthorizedException.cs b/back/src/Kyoo.Abstractions/Models/Exceptions/UnauthorizedException.cs
new file mode 100644
index 00000000..fc4f5f08
--- /dev/null
+++ b/back/src/Kyoo.Abstractions/Models/Exceptions/UnauthorizedException.cs
@@ -0,0 +1,37 @@
+// Kyoo - A portable and vast media library solution.
+// Copyright (c) Kyoo.
+//
+// See AUTHORS.md and LICENSE file in the project root for full license information.
+//
+// Kyoo is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// Kyoo is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Kyoo. If not, see .
+
+using System;
+using System.Runtime.Serialization;
+
+namespace Kyoo.Abstractions.Models.Exceptions
+{
+ [Serializable]
+ public class UnauthorizedException : Exception
+ {
+ public UnauthorizedException() { }
+
+ public UnauthorizedException(string message)
+ : base(message)
+ { }
+
+ protected UnauthorizedException(SerializationInfo info, StreamingContext context)
+ : base(info, context)
+ { }
+ }
+}
diff --git a/back/src/Kyoo.Authentication/Views/AuthApi.cs b/back/src/Kyoo.Authentication/Views/AuthApi.cs
index 8840f3cd..3652306c 100644
--- a/back/src/Kyoo.Authentication/Views/AuthApi.cs
+++ b/back/src/Kyoo.Authentication/Views/AuthApi.cs
@@ -197,11 +197,9 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task> GetMe()
{
- if (!Guid.TryParse(User.FindFirstValue(Claims.Id), out Guid userID))
- return Unauthorized(new RequestError("User not authenticated or token invalid."));
try
{
- return await _users.Get(userID);
+ return await _users.Get(User.GetIdOrThrow());
}
catch (ItemNotFoundException)
{
@@ -226,11 +224,9 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task> EditMe(User user)
{
- if (!Guid.TryParse(User.FindFirstValue(Claims.Id), out Guid userID))
- return Unauthorized(new RequestError("User not authenticated or token invalid."));
try
{
- user.Id = userID;
+ user.Id = User.GetIdOrThrow();
return await _users.Edit(user);
}
catch (ItemNotFoundException)
@@ -256,13 +252,12 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task> PatchMe(PartialResource user)
{
- if (!Guid.TryParse(User.FindFirstValue(Claims.Id), out Guid userID))
- return Unauthorized(new RequestError("User not authenticated or token invalid."));
+ Guid userId = User.GetIdOrThrow();
try
{
- if (user.Id.HasValue && user.Id != userID)
+ if (user.Id.HasValue && user.Id != userId)
throw new ArgumentException("Can't edit your user id.");
- return await _users.Patch(userID, TryUpdateModelAsync);
+ return await _users.Patch(userId, TryUpdateModelAsync);
}
catch (ItemNotFoundException)
{
@@ -286,11 +281,9 @@ namespace Kyoo.Authentication.Views
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task> DeleteMe()
{
- if (!Guid.TryParse(User.FindFirstValue(Claims.Id), out Guid userID))
- return Unauthorized(new RequestError("User not authenticated or token invalid."));
try
{
- await _users.Delete(userID);
+ await _users.Delete(User.GetIdOrThrow());
return NoContent();
}
catch (ItemNotFoundException)
diff --git a/back/src/Kyoo.Core/ExceptionFilter.cs b/back/src/Kyoo.Core/ExceptionFilter.cs
index 9a5bdb28..3e5ad9be 100644
--- a/back/src/Kyoo.Core/ExceptionFilter.cs
+++ b/back/src/Kyoo.Core/ExceptionFilter.cs
@@ -61,6 +61,9 @@ namespace Kyoo.Core
// Should not happen but if it does, it is better than returning a 409 with no body since clients expect json content
context.Result = new ConflictObjectResult(new RequestError("Duplicated item"));
break;
+ case UnauthorizedException ex:
+ context.Result = new UnauthorizedObjectResult(new RequestError(ex.Message ?? "User not authenticated or token invalid."));
+ break;
case Exception ex:
_logger.LogError(ex, "Unhandled error");
context.Result = new ServerErrorObjectResult(new RequestError("Internal Server Error"));
diff --git a/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs b/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs
index ed92e82d..ceb39f6d 100644
--- a/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs
+++ b/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs
@@ -122,7 +122,6 @@ namespace Kyoo.Core.Api
/// This episode does not have a specific status.
/// No episode with the given ID or slug could be found.
[HttpGet("{identifier:id}/watchStatus")]
- [HttpGet("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -133,7 +132,7 @@ namespace Kyoo.Core.Api
id => Task.FromResult(id),
async slug => (await _libraryManager.Episodes.Get(slug)).Id
);
- return await _libraryManager.WatchStatus.GetEpisodeStatus(id, User.GetId()!.Value);
+ return await _libraryManager.WatchStatus.GetEpisodeStatus(id, User.GetIdOrThrow());
}
///
@@ -150,7 +149,6 @@ namespace Kyoo.Core.Api
/// The status was not considered impactfull enough to be saved (less then 5% of watched for example).
/// No episode with the given ID or slug could be found.
[HttpPost("{identifier:id}/watchStatus")]
- [HttpPost("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -164,7 +162,7 @@ namespace Kyoo.Core.Api
);
return await _libraryManager.WatchStatus.SetEpisodeStatus(
id,
- User.GetId()!.Value,
+ User.GetIdOrThrow(),
status,
watchedTime
);
@@ -181,7 +179,6 @@ namespace Kyoo.Core.Api
/// The status has been deleted.
/// No episode with the given ID or slug could be found.
[HttpDelete("{identifier:id}/watchStatus")]
- [HttpDelete("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -191,7 +188,7 @@ namespace Kyoo.Core.Api
id => Task.FromResult(id),
async slug => (await _libraryManager.Episodes.Get(slug)).Id
);
- await _libraryManager.WatchStatus.DeleteEpisodeStatus(id, User.GetId()!.Value);
+ await _libraryManager.WatchStatus.DeleteEpisodeStatus(id, User.GetIdOrThrow());
}
}
}
diff --git a/back/src/Kyoo.Core/Views/Resources/MovieApi.cs b/back/src/Kyoo.Core/Views/Resources/MovieApi.cs
index d42226f3..52a117c2 100644
--- a/back/src/Kyoo.Core/Views/Resources/MovieApi.cs
+++ b/back/src/Kyoo.Core/Views/Resources/MovieApi.cs
@@ -163,7 +163,6 @@ namespace Kyoo.Core.Api
/// This movie does not have a specific status.
/// No movie with the given ID or slug could be found.
[HttpGet("{identifier:id}/watchStatus")]
- [HttpGet("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -174,7 +173,7 @@ namespace Kyoo.Core.Api
id => Task.FromResult(id),
async slug => (await _libraryManager.Movies.Get(slug)).Id
);
- return await _libraryManager.WatchStatus.GetMovieStatus(id, User.GetId()!.Value);
+ return await _libraryManager.WatchStatus.GetMovieStatus(id, User.GetIdOrThrow());
}
///
@@ -192,7 +191,6 @@ namespace Kyoo.Core.Api
/// WatchedTime can't be specified if status is not watching.
/// No movie with the given ID or slug could be found.
[HttpPost("{identifier:id}/watchStatus")]
- [HttpPost("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -206,7 +204,7 @@ namespace Kyoo.Core.Api
);
return await _libraryManager.WatchStatus.SetMovieStatus(
id,
- User.GetId()!.Value,
+ User.GetIdOrThrow(),
status,
watchedTime
);
@@ -223,7 +221,6 @@ namespace Kyoo.Core.Api
/// The status has been deleted.
/// No movie with the given ID or slug could be found.
[HttpDelete("{identifier:id}/watchStatus")]
- [HttpDelete("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -233,7 +230,7 @@ namespace Kyoo.Core.Api
id => Task.FromResult(id),
async slug => (await _libraryManager.Movies.Get(slug)).Id
);
- await _libraryManager.WatchStatus.DeleteMovieStatus(id, User.GetId()!.Value);
+ await _libraryManager.WatchStatus.DeleteMovieStatus(id, User.GetIdOrThrow());
}
}
}
diff --git a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs
index 46a03639..8859c8f2 100644
--- a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs
+++ b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs
@@ -240,7 +240,6 @@ namespace Kyoo.Core.Api
/// This show does not have a specific status.
/// No show with the given ID or slug could be found.
[HttpGet("{identifier:id}/watchStatus")]
- [HttpGet("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -251,7 +250,7 @@ namespace Kyoo.Core.Api
id => Task.FromResult(id),
async slug => (await _libraryManager.Shows.Get(slug)).Id
);
- return await _libraryManager.WatchStatus.GetShowStatus(id, User.GetId()!.Value);
+ return await _libraryManager.WatchStatus.GetShowStatus(id, User.GetIdOrThrow());
}
///
@@ -267,7 +266,6 @@ namespace Kyoo.Core.Api
/// The status was not considered impactfull enough to be saved (less then 5% of watched for example).
/// No movie with the given ID or slug could be found.
[HttpPost("{identifier:id}/watchStatus")]
- [HttpPost("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -281,7 +279,7 @@ namespace Kyoo.Core.Api
);
return await _libraryManager.WatchStatus.SetShowStatus(
id,
- User.GetId()!.Value,
+ User.GetIdOrThrow(),
status
);
}
@@ -297,7 +295,6 @@ namespace Kyoo.Core.Api
/// The status has been deleted.
/// No show with the given ID or slug could be found.
[HttpDelete("{identifier:id}/watchStatus")]
- [HttpDelete("{identifier:id}/watchStatus", Order = AlternativeRoute)]
[UserOnly]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -307,7 +304,7 @@ namespace Kyoo.Core.Api
id => Task.FromResult(id),
async slug => (await _libraryManager.Shows.Get(slug)).Id
);
- await _libraryManager.WatchStatus.DeleteShowStatus(id, User.GetId()!.Value);
+ await _libraryManager.WatchStatus.DeleteShowStatus(id, User.GetIdOrThrow());
}
}
}