mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Add authentication documentation to the swagger
This commit is contained in:
parent
d257901c64
commit
b5821b0d02
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Permissions
|
||||
{
|
||||
/// <summary>
|
||||
/// The annotated route can only be accessed by a logged in user.
|
||||
/// </summary>
|
||||
[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.
|
||||
}
|
||||
}
|
@ -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
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IStartupAction> ConfigureSteps => new IStartupAction[]
|
||||
{
|
||||
SA.New<IApplicationBuilder>(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<IApplicationBuilder>(app => app.UseAuthentication(), SA.Authentication),
|
||||
// SA.New<IApplicationBuilder>(app => app.UseAuthorization(), SA.Authorization)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
/// <param name="user">The user to create a token for.</param>
|
||||
/// <param name="expireIn">When this token will expire.</param>
|
||||
/// <returns>A new, valid access token.</returns>
|
||||
string CreateAccessToken([NotNull] User user, out TimeSpan expireIn);
|
||||
string CreateAccessToken(User user, out TimeSpan expireIn);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new refresh token for the given user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to create a token for.</param>
|
||||
/// <returns>A new, valid refresh token.</returns>
|
||||
Task<string> CreateRefreshToken([NotNull] User user);
|
||||
Task<string> CreateRefreshToken(User user);
|
||||
|
||||
/// <summary>
|
||||
/// Check if the given refresh token is valid and if it is, retrieve the id of the user this token belongs to.
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -13,11 +13,4 @@
|
||||
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="$(LoginRoot)**;" />
|
||||
<Content Include="$(LoginRoot)**" Visible="false">
|
||||
<Link>login/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -49,7 +49,7 @@ namespace Kyoo.Authentication
|
||||
public string RefreshToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
[JsonProperty("expire_in")]
|
||||
|
@ -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
|
||||
/// <response code="403">The user and password does not match.</response>
|
||||
[HttpPost("login")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<JwtToken>> Login([FromBody] LoginRequest request)
|
||||
{
|
||||
User user = await _users.GetOrDefault(x => x.Username == request.Username);
|
||||
@ -139,10 +139,11 @@ namespace Kyoo.Authentication.Views
|
||||
/// </remarks>
|
||||
/// <param name="token">A valid refresh token.</param>
|
||||
/// <returns>A new access and refresh token.</returns>
|
||||
/// <response code="400">The given refresh token is invalid.</response>
|
||||
/// <response code="403">The given refresh token is invalid.</response>
|
||||
[HttpGet("refresh")]
|
||||
[UserOnly]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(RequestError))]
|
||||
public async Task<ActionResult<JwtToken>> 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
|
||||
/// <returns>The currently authenticated user.</returns>
|
||||
/// <response code="403">The given access token is invalid.</response>
|
||||
[HttpGet("me")]
|
||||
[UserOnly]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<User>> GetMe()
|
||||
@ -204,6 +206,7 @@ namespace Kyoo.Authentication.Views
|
||||
/// <returns>The currently authenticated user after modifications.</returns>
|
||||
/// <response code="403">The given access token is invalid.</response>
|
||||
[HttpPut("me")]
|
||||
[UserOnly]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<User>> EditMe(User user, [FromQuery] bool resetOld = true)
|
||||
@ -230,6 +233,7 @@ namespace Kyoo.Authentication.Views
|
||||
/// <returns>The currently authenticated user after modifications.</returns>
|
||||
/// <response code="403">The given access token is invalid.</response>
|
||||
[HttpDelete("me")]
|
||||
[UserOnly]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult<User>> DeleteMe()
|
||||
|
@ -37,7 +37,7 @@ namespace Kyoo.Core.Api
|
||||
/// </summary>
|
||||
[Route("subtitles")]
|
||||
[Route("subtitle", Order = AlternativeRoute)]
|
||||
[PartialPermission(nameof(SubtitleApi))]
|
||||
[PartialPermission("subtitle")]
|
||||
[ApiController]
|
||||
[ApiDefinition("Subtitles", Group = WatchGroup)]
|
||||
public class SubtitleApi : ControllerBase
|
||||
|
@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
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>();
|
||||
OpenApiSecurityRequirement perms = context.MethodInfo.GetCustomAttributes<PermissionAttribute>()
|
||||
OpenApiSecurityRequirement perms = context.MethodInfo.GetCustomAttributes<UserOnlyAttribute>()
|
||||
.Aggregate(new OpenApiSecurityRequirement(), (agg, cur) =>
|
||||
{
|
||||
agg[nameof(Kyoo)] = Array.Empty<string>();
|
||||
return agg;
|
||||
});
|
||||
|
||||
perms = context.MethodInfo.GetCustomAttributes<PermissionAttribute>()
|
||||
.Aggregate(perms, (agg, cur) =>
|
||||
{
|
||||
ICollection<string> 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<string> 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<string> _GetPermissionsList(OpenApiSecurityRequirement security, Group group)
|
||||
private static ICollection<string> _GetPermissionsList(OpenApiSecurityRequirement security, Group group)
|
||||
{
|
||||
return security.TryGetValue(group.ToString(), out IEnumerable<string> perms)
|
||||
? perms.ToList()
|
||||
|
@ -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<string, object>
|
||||
{
|
||||
["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<string, object>
|
||||
{
|
||||
["type"] = "OpenID Connect or Api Key"
|
||||
},
|
||||
Description = "The permission group used for administrative items like tasks, account management " +
|
||||
"and library creation."
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user