diff --git a/src/Kyoo.Abstractions/Models/Resources/User.cs b/src/Kyoo.Abstractions/Models/Resources/User.cs
index 17ad2591..cf9f5368 100644
--- a/src/Kyoo.Abstractions/Models/Resources/User.cs
+++ b/src/Kyoo.Abstractions/Models/Resources/User.cs
@@ -45,6 +45,7 @@ namespace Kyoo.Abstractions.Models
///
/// The user password (hashed, it can't be read like that). The hashing format is implementation defined.
///
+ [SerializeIgnore]
public string Password { get; set; }
///
@@ -55,6 +56,7 @@ namespace Kyoo.Abstractions.Models
///
/// Arbitrary extra data that can be used by specific authentication implementations.
///
+ [SerializeIgnore]
public Dictionary ExtraData { get; set; }
///
diff --git a/src/Kyoo.Authentication/Controllers/PermissionValidator.cs b/src/Kyoo.Authentication/Controllers/PermissionValidator.cs
index d3200d1c..0b4f8768 100644
--- a/src/Kyoo.Authentication/Controllers/PermissionValidator.cs
+++ b/src/Kyoo.Authentication/Controllers/PermissionValidator.cs
@@ -96,7 +96,10 @@ namespace Kyoo.Authentication
/// The kind of permission needed.
/// The group of the permission.
/// The option containing default values.
- public PermissionValidatorFilter(string permission, Kind kind, Group group,
+ public PermissionValidatorFilter(
+ string permission,
+ Kind kind,
+ Group group,
IOptionsMonitor options)
{
_permission = permission;
diff --git a/src/Kyoo.Authentication/Controllers/TokenController.cs b/src/Kyoo.Authentication/Controllers/TokenController.cs
index db841f89..c7962cd4 100644
--- a/src/Kyoo.Authentication/Controllers/TokenController.cs
+++ b/src/Kyoo.Authentication/Controllers/TokenController.cs
@@ -62,9 +62,6 @@ namespace Kyoo.Authentication
///
public string CreateAccessToken(User user, out TimeSpan expireIn)
{
- if (user == null)
- throw new ArgumentNullException(nameof(user));
-
expireIn = new TimeSpan(1, 0, 0);
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
@@ -94,9 +91,6 @@ namespace Kyoo.Authentication
///
public Task CreateRefreshToken(User user)
{
- if (user == null)
- throw new ArgumentNullException(nameof(user));
-
SymmetricSecurityKey key = new(Encoding.UTF8.GetBytes(_options.Value.Secret));
SigningCredentials credential = new(key, SecurityAlgorithms.HmacSha256Signature);
JwtSecurityToken token = new(
diff --git a/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs b/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs
index 2c2e6711..b57ed640 100644
--- a/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs
+++ b/src/Kyoo.Authentication/Models/Options/AuthenticationOption.cs
@@ -31,7 +31,7 @@ namespace Kyoo.Authentication.Models
///
/// The default jwt secret.
///
- public const string DefaultSecret = "jwt-secret";
+ public const string DefaultSecret = "4c@mraGB!KRfF@kpS8739y9FcHemKxBsqqxLbdR?";
///
/// The secret used to encrypt the jwt.
diff --git a/src/Kyoo.Authentication/Views/AuthApi.cs b/src/Kyoo.Authentication/Views/AuthApi.cs
index 9906fe8a..b237975b 100644
--- a/src/Kyoo.Authentication/Views/AuthApi.cs
+++ b/src/Kyoo.Authentication/Views/AuthApi.cs
@@ -175,15 +175,76 @@ namespace Kyoo.Authentication.Views
/// The currently authenticated user.
/// The given access token is invalid.
[HttpGet("me")]
- [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task> GetMe()
{
if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
return Forbid();
- return await _users.Get(userID);
+ try
+ {
+ return await _users.Get(userID);
+ }
+ catch (ItemNotFoundException)
+ {
+ return Forbid();
+ }
}
- // TODO: Add a put to edit the current user.
+ ///
+ /// Edit self
+ ///
+ ///
+ /// Edit information about the currently authenticated user.
+ ///
+ /// The new data for the current user.
+ ///
+ /// Should old properties of the resource be discarded or should null values considered as not changed?
+ ///
+ /// The currently authenticated user after modifications.
+ /// The given access token is invalid.
+ [HttpPut("me")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public async Task> EditMe(User user, [FromQuery] bool resetOld = true)
+ {
+ if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
+ return Forbid();
+ try
+ {
+ user.ID = userID;
+ return await _users.Edit(user, resetOld);
+ }
+ catch (ItemNotFoundException)
+ {
+ return Forbid();
+ }
+ }
+
+ ///
+ /// Delete account
+ ///
+ ///
+ /// Delete the current account.
+ ///
+ /// The currently authenticated user after modifications.
+ /// The given access token is invalid.
+ [HttpDelete("me")]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public async Task> DeleteMe()
+ {
+ if (!int.TryParse(User.FindFirstValue(ClaimTypes.NameIdentifier), out int userID))
+ return Forbid();
+ try
+ {
+ await _users.Delete(userID);
+ return NoContent();
+ }
+ catch (ItemNotFoundException)
+ {
+ return Forbid();
+ }
+ }
}
}
diff --git a/src/Kyoo.Core/Views/Helper/CrudApi.cs b/src/Kyoo.Core/Views/Helper/CrudApi.cs
index 16e8beb1..3aa3cc01 100644
--- a/src/Kyoo.Core/Views/Helper/CrudApi.cs
+++ b/src/Kyoo.Core/Views/Helper/CrudApi.cs
@@ -220,7 +220,7 @@ namespace Kyoo.Core.Api
/// No item could be found with the given id or slug.
[HttpDelete("{identifier:id}")]
[PartialPermission(Kind.Delete)]
- [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task Delete(Identifier identifier)
{
@@ -236,7 +236,7 @@ namespace Kyoo.Core.Api
return NotFound();
}
- return Ok();
+ return NoContent();
}
///
@@ -250,7 +250,7 @@ namespace Kyoo.Core.Api
/// One or multiple filters are invalid.
[HttpDelete]
[PartialPermission(Kind.Delete)]
- [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
public async Task Delete([FromQuery] Dictionary where)
{
@@ -263,7 +263,7 @@ namespace Kyoo.Core.Api
return BadRequest(new RequestError(ex.Message));
}
- return Ok();
+ return NoContent();
}
}
}
diff --git a/src/Kyoo.Host.Generic/settings.json b/src/Kyoo.Host.Generic/settings.json
index 778fb20a..bd3f637c 100644
--- a/src/Kyoo.Host.Generic/settings.json
+++ b/src/Kyoo.Host.Generic/settings.json
@@ -65,7 +65,7 @@
"newUser": ["overall.read", "overall.write"]
},
"profilePicturePath": "users/",
- "secret": "jwt-secret"
+ "secret": "4c@mraGB!KRfF@kpS8740y9FcHemKxBsqqxLbdR?"
},
"tvdb": {
diff --git a/tests/robot/Authentication/auth.robot b/tests/robot/Authentication/auth.robot
deleted file mode 100644
index 623666bf..00000000
--- a/tests/robot/Authentication/auth.robot
+++ /dev/null
@@ -1,18 +0,0 @@
-*** Settings ***
-Documentation Tests of the /auth route.
-... Ensures that the user can authenticate on kyoo.
-Resource ../rest.resource
-
-
-*** Test Cases ***
-BadAccount
- [Documentation] Login fails if user does not exist
- POST /auth/login {"username": "toto", "password": "tata"}
- Output
- Integer response status 403
-
-Register
- [Documentation] Create a new user and login in it
- POST /auth/register {"username": "toto", "password": "tata", "email": "mail@kyoo.moe"}
- Output
- Integer response status 403
diff --git a/tests/robot/auth/auth.robot b/tests/robot/auth/auth.robot
new file mode 100644
index 00000000..5070f51b
--- /dev/null
+++ b/tests/robot/auth/auth.robot
@@ -0,0 +1,83 @@
+*** Settings ***
+Documentation Tests of the /auth route.
+... Ensures that the user can authenticate on kyoo.
+
+Resource ../rest.resource
+
+
+*** Keywords ***
+Login
+ [Documentation] Shortcut to login with the given username for future requests
+ [Arguments] ${username}
+ &{res}= POST /auth/login {"username": "${username}", "password": "password-${username}"}
+ Output
+ Integer response status 200
+ String response body access_token
+ Set Headers {"Authorization": "Bearer ${res.body.access_token}"}
+
+Register
+ [Documentation] Shortcut to register with the given username for future requests
+ [Arguments] ${username}
+ &{res}= POST
+ ... /auth/register
+ ... {"username": "${username}", "password": "password-${username}", "email": "${username}@kyoo.moe"}
+ Output
+ Integer response status 200
+ String response body access_token
+ Set Headers {"Authorization": "Bearer ${res.body.access_token}"}
+
+Logout
+ [Documentation] Logout the current user, only the local client is affected.
+ Set Headers {"Authorization": ""}
+
+
+*** Test Cases ***
+Me cant be accessed without an account
+ Get /auth/me
+ Output
+ Integer response status 403
+
+Bad Account
+ [Documentation] Login fails if user does not exist
+ POST /auth/login {"username": "i-don-t-exist", "password": "pass"}
+ Output
+ Integer response status 403
+
+Register
+ [Documentation] Create a new user and login in it
+ Register user-1
+ [Teardown] DELETE /auth/me
+
+Register Duplicates
+ [Documentation] If two users tries to register with the same username, it fails
+ Register user-duplicate
+ # We can't use the `Register` keyword because it assert for success
+ POST /auth/register {"username": "user-duplicate", "password": "pass", "email": "mail@kyoo.moe"}
+ Output
+ Integer response status 409
+ [Teardown] DELETE /auth/me
+
+Delete Account
+ [Documentation] Check if a user can delete it's account
+ Register I-should-be-deleted
+ DELETE /auth/me
+ Output
+ Integer response status 204
+
+Login
+ [Documentation] Create a new user and login in it
+ Register login-user
+ ${res}= GET /auth/me
+ Output
+ Integer response status 200
+ String response body username login-user
+
+ Logout
+ Login login-user
+ ${me}= Get /auth/me
+ Output
+ Output ${me}
+ Should Be Equal As Strings ${res["body"]} ${me["body"]}
+
+ [Teardown] DELETE /auth/me
+
diff --git a/tests/robot/pyproject.toml b/tests/robot/pyproject.toml
new file mode 100644
index 00000000..e5e1011e
--- /dev/null
+++ b/tests/robot/pyproject.toml
@@ -0,0 +1,4 @@
+[tool.robotidy]
+configure = [
+ "MergeAndOrderSections:order=comments,settings,keywords,variables,testcases"
+]