diff --git a/src/Kyoo.Abstractions/Models/Attributes/Permission/UserOnlyAttribute.cs b/src/Kyoo.Abstractions/Models/Attributes/Permission/UserOnlyAttribute.cs
new file mode 100644
index 00000000..884187a0
--- /dev/null
+++ b/src/Kyoo.Abstractions/Models/Attributes/Permission/UserOnlyAttribute.cs
@@ -0,0 +1,31 @@
+// 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;
+
+namespace Kyoo.Abstractions.Models.Permissions
+{
+ ///
+ /// The annotated route can only be accessed by a logged in user.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
+ public class UserOnlyAttribute : Attribute
+ {
+ // TODO: Implement a Filter Attribute to make this work. For now, this attribute is only useful as documentation.
+ }
+}
diff --git a/src/Kyoo.Authentication/AuthenticationModule.cs b/src/Kyoo.Authentication/AuthenticationModule.cs
index b2dcfcdb..726e22fe 100644
--- a/src/Kyoo.Authentication/AuthenticationModule.cs
+++ b/src/Kyoo.Authentication/AuthenticationModule.cs
@@ -18,8 +18,6 @@
using System;
using System.Collections.Generic;
-using System.IO;
-using System.Reflection;
using System.Text;
using Autofac;
using Kyoo.Abstractions;
@@ -27,10 +25,8 @@ using Kyoo.Abstractions.Controllers;
using Kyoo.Authentication.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.FileProviders;
using Microsoft.IdentityModel.Tokens;
namespace Kyoo.Authentication
@@ -105,25 +101,7 @@ namespace Kyoo.Authentication
///
public IEnumerable ConfigureSteps => new IStartupAction[]
{
- SA.New(app =>
- {
- PhysicalFileProvider provider = new(Path.Combine(
- Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
- "login"));
- app.UseDefaultFiles(new DefaultFilesOptions
- {
- RequestPath = new PathString("/login"),
- FileProvider = provider,
- RedirectToAppendTrailingSlash = true
- });
- app.UseStaticFiles(new StaticFileOptions
- {
- RequestPath = new PathString("/login"),
- FileProvider = provider
- });
- }, SA.StaticFiles),
SA.New(app => app.UseAuthentication(), SA.Authentication),
- // SA.New(app => app.UseAuthorization(), SA.Authorization)
};
}
}
diff --git a/src/Kyoo.Authentication/Controllers/ITokenController.cs b/src/Kyoo.Authentication/Controllers/ITokenController.cs
index f0ae7670..a6d488d9 100644
--- a/src/Kyoo.Authentication/Controllers/ITokenController.cs
+++ b/src/Kyoo.Authentication/Controllers/ITokenController.cs
@@ -18,7 +18,6 @@
using System;
using System.Threading.Tasks;
-using JetBrains.Annotations;
using Kyoo.Abstractions.Models;
using Microsoft.IdentityModel.Tokens;
@@ -35,14 +34,14 @@ namespace Kyoo.Authentication
/// The user to create a token for.
/// When this token will expire.
/// A new, valid access token.
- string CreateAccessToken([NotNull] User user, out TimeSpan expireIn);
+ string CreateAccessToken(User user, out TimeSpan expireIn);
///
/// Create a new refresh token for the given user.
///
/// The user to create a token for.
/// A new, valid refresh token.
- Task CreateRefreshToken([NotNull] User user);
+ Task CreateRefreshToken(User user);
///
/// Check if the given refresh token is valid and if it is, retrieve the id of the user this token belongs to.
diff --git a/src/Kyoo.Authentication/Controllers/TokenController.cs b/src/Kyoo.Authentication/Controllers/TokenController.cs
index c7962cd4..d006cf4e 100644
--- a/src/Kyoo.Authentication/Controllers/TokenController.cs
+++ b/src/Kyoo.Authentication/Controllers/TokenController.cs
@@ -105,7 +105,7 @@ namespace Kyoo.Authentication
},
expires: DateTime.UtcNow.AddYears(1)
);
- // TODO refresh keys are unique (thanks to the guid) but we could store them in DB to invalidate them if requested by the user.
+ // TODO: refresh keys are unique (thanks to the guid) but we could store them in DB to invalidate them if requested by the user.
return Task.FromResult(new JwtSecurityTokenHandler().WriteToken(token));
}
diff --git a/src/Kyoo.Authentication/Kyoo.Authentication.csproj b/src/Kyoo.Authentication/Kyoo.Authentication.csproj
index b34a7b42..e17c9615 100644
--- a/src/Kyoo.Authentication/Kyoo.Authentication.csproj
+++ b/src/Kyoo.Authentication/Kyoo.Authentication.csproj
@@ -13,11 +13,4 @@
-
-
-
- login/%(RecursiveDir)%(Filename)%(Extension)
- Always
-
-
diff --git a/src/Kyoo.Authentication/Models/JwtToken.cs b/src/Kyoo.Authentication/Models/JwtToken.cs
index c0dedc70..bb4c325e 100644
--- a/src/Kyoo.Authentication/Models/JwtToken.cs
+++ b/src/Kyoo.Authentication/Models/JwtToken.cs
@@ -49,7 +49,7 @@ namespace Kyoo.Authentication
public string RefreshToken { get; set; }
///
- /// When the access token will expire. After this tume, the refresh token should be used to retrieve.
+ /// When the access token will expire. After this time, the refresh token should be used to retrieve.
/// a new token.cs
///
[JsonProperty("expire_in")]
diff --git a/src/Kyoo.Authentication/Views/AuthApi.cs b/src/Kyoo.Authentication/Views/AuthApi.cs
index b237975b..e3899eba 100644
--- a/src/Kyoo.Authentication/Views/AuthApi.cs
+++ b/src/Kyoo.Authentication/Views/AuthApi.cs
@@ -23,9 +23,9 @@ using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Exceptions;
+using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils;
using Kyoo.Authentication.Models.DTO;
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
@@ -84,7 +84,7 @@ namespace Kyoo.Authentication.Views
/// The user and password does not match.
[HttpPost("login")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
+ [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task> Login([FromBody] LoginRequest request)
{
User user = await _users.GetOrDefault(x => x.Username == request.Username);
@@ -139,10 +139,11 @@ namespace Kyoo.Authentication.Views
///
/// A valid refresh token.
/// A new access and refresh token.
- /// The given refresh token is invalid.
+ /// The given refresh token is invalid.
[HttpGet("refresh")]
+ [UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
+ [ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
public async Task> Refresh([FromQuery] string token)
{
try
@@ -157,11 +158,11 @@ namespace Kyoo.Authentication.Views
}
catch (ItemNotFoundException)
{
- return BadRequest(new RequestError("Invalid refresh token."));
+ return Forbid(new RequestError("Invalid refresh token."));
}
catch (SecurityTokenException ex)
{
- return BadRequest(new RequestError(ex.Message));
+ return Forbid(new RequestError(ex.Message));
}
}
@@ -175,6 +176,7 @@ namespace Kyoo.Authentication.Views
/// The currently authenticated user.
/// The given access token is invalid.
[HttpGet("me")]
+ [UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task> GetMe()
@@ -204,6 +206,7 @@ namespace Kyoo.Authentication.Views
/// The currently authenticated user after modifications.
/// The given access token is invalid.
[HttpPut("me")]
+ [UserOnly]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task> EditMe(User user, [FromQuery] bool resetOld = true)
@@ -230,6 +233,7 @@ namespace Kyoo.Authentication.Views
/// The currently authenticated user after modifications.
/// The given access token is invalid.
[HttpDelete("me")]
+ [UserOnly]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task> DeleteMe()
diff --git a/src/Kyoo.Core/Views/Watch/SubtitleApi.cs b/src/Kyoo.Core/Views/Watch/SubtitleApi.cs
index 3266af8d..7e89bef6 100644
--- a/src/Kyoo.Core/Views/Watch/SubtitleApi.cs
+++ b/src/Kyoo.Core/Views/Watch/SubtitleApi.cs
@@ -37,7 +37,7 @@ namespace Kyoo.Core.Api
///
[Route("subtitles")]
[Route("subtitle", Order = AlternativeRoute)]
- [PartialPermission(nameof(SubtitleApi))]
+ [PartialPermission("subtitle")]
[ApiController]
[ApiDefinition("Subtitles", Group = WatchGroup)]
public class SubtitleApi : ControllerBase
diff --git a/src/Kyoo.Swagger/OperationPermissionProcessor.cs b/src/Kyoo.Swagger/OperationPermissionProcessor.cs
index b0511b24..e2d14bf9 100644
--- a/src/Kyoo.Swagger/OperationPermissionProcessor.cs
+++ b/src/Kyoo.Swagger/OperationPermissionProcessor.cs
@@ -16,6 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see .
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -36,12 +37,19 @@ namespace Kyoo.Swagger
public bool Process(OperationProcessorContext context)
{
context.OperationDescription.Operation.Security ??= new List();
- OpenApiSecurityRequirement perms = context.MethodInfo.GetCustomAttributes()
+ OpenApiSecurityRequirement perms = context.MethodInfo.GetCustomAttributes()
.Aggregate(new OpenApiSecurityRequirement(), (agg, cur) =>
+ {
+ agg[nameof(Kyoo)] = Array.Empty();
+ return agg;
+ });
+
+ perms = context.MethodInfo.GetCustomAttributes()
+ .Aggregate(perms, (agg, cur) =>
{
ICollection permissions = _GetPermissionsList(agg, cur.Group);
permissions.Add($"{cur.Type}.{cur.Kind.ToString().ToLower()}");
- agg[cur.Group.ToString()] = permissions;
+ agg[nameof(Kyoo)] = permissions;
return agg;
});
@@ -61,7 +69,7 @@ namespace Kyoo.Swagger
: cur.Kind;
ICollection permissions = _GetPermissionsList(agg, group);
permissions.Add($"{type}.{kind.ToString().ToLower()}");
- agg[group.ToString()] = permissions;
+ agg[nameof(Kyoo)] = permissions;
return agg;
});
}
@@ -70,7 +78,7 @@ namespace Kyoo.Swagger
return true;
}
- private ICollection _GetPermissionsList(OpenApiSecurityRequirement security, Group group)
+ private static ICollection _GetPermissionsList(OpenApiSecurityRequirement security, Group group)
{
return security.TryGetValue(group.ToString(), out IEnumerable perms)
? perms.ToList()
diff --git a/src/Kyoo.Swagger/SwaggerModule.cs b/src/Kyoo.Swagger/SwaggerModule.cs
index fafb1281..5025fa7c 100644
--- a/src/Kyoo.Swagger/SwaggerModule.cs
+++ b/src/Kyoo.Swagger/SwaggerModule.cs
@@ -120,35 +120,14 @@ namespace Kyoo.Swagger
}));
document.SchemaProcessors.Add(new ThumbnailProcessor());
- document.AddSecurity("Kyoo", new OpenApiSecurityScheme
+ document.AddSecurity(nameof(Kyoo), new OpenApiSecurityScheme
{
- Type = OpenApiSecuritySchemeType.OpenIdConnect,
- OpenIdConnectUrl = "/.well-known/openid-configuration",
- Description = "You can login via an OIDC client, clients must be first registered in kyoo. " +
- "Documentation coming soon."
+ Type = OpenApiSecuritySchemeType.Http,
+ Scheme = "Bearer",
+ BearerFormat = "JWT",
+ Description = "The user's bearer"
});
document.OperationProcessors.Add(new OperationPermissionProcessor());
- // This does not respect the swagger's specification but it works for swaggerUi and ReDoc so honestly this will do.
- document.AddSecurity(Group.Overall.ToString(), new OpenApiSecurityScheme
- {
- ExtensionData = new Dictionary
- {
- ["type"] = "OpenID Connect or Api Key"
- },
- Description = "Kyoo's permissions work by groups. Permissions are attributed to " +
- "a specific group and if a user has a group permission, it will be the same as having every " +
- "permission in the group. For example, having overall.read gives you collections.read, " +
- "shows.read and so on."
- });
- document.AddSecurity(Group.Admin.ToString(), new OpenApiSecurityScheme
- {
- ExtensionData = new Dictionary
- {
- ["type"] = "OpenID Connect or Api Key"
- },
- Description = "The permission group used for administrative items like tasks, account management " +
- "and library creation."
- });
});
}