diff --git a/.env.example b/.env.example index a1089c44..cd67a291 100644 --- a/.env.example +++ b/.env.example @@ -41,7 +41,7 @@ THEMOVIEDB_APIKEY= # The url you can use to reach your kyoo instance. This is used during oidc to redirect users to your instance. PUBLIC_URL=http://localhost:5000 -# Use a builtin oidc service (google, discord, or simkl): +# Use a builtin oidc service (google, discord, trakt, or simkl): # When you create a client_id, secret combo you may be asked for a redirect url. You need to specify https://YOUR-PUBLIC-URL/api/auth/logged/YOUR-SERVICE-NAME OIDC_DISCORD_CLIENTID= OIDC_DISCORD_SECRET= diff --git a/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs b/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs index ab8a5a6c..4bd4d447 100644 --- a/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs +++ b/back/src/Kyoo.Abstractions/Models/Utils/Sort.cs @@ -102,7 +102,11 @@ public record Sort : Sort return new Conglomerate(sortBy.Split(',').Select(x => From(x, seed)).ToArray()); if (sortBy.StartsWith("random:")) - return new Random(uint.Parse(sortBy["random:".Length..])); + { + if (uint.TryParse(sortBy["random:".Length..], out uint sseed)) + return new Random(sseed); + throw new ValidationException("Invalid random seed specified. Expected a number."); + } string key = sortBy.Contains(':') ? sortBy[..sortBy.IndexOf(':')] : sortBy; string? order = sortBy.Contains(':') ? sortBy[(sortBy.IndexOf(':') + 1)..] : null; diff --git a/back/src/Kyoo.Authentication/Models/DTO/JwtProfile.cs b/back/src/Kyoo.Authentication/Models/DTO/JwtProfile.cs index 85272f8b..c1de4fbb 100644 --- a/back/src/Kyoo.Authentication/Models/DTO/JwtProfile.cs +++ b/back/src/Kyoo.Authentication/Models/DTO/JwtProfile.cs @@ -63,8 +63,12 @@ public class JwtProfile { if (value is null) return; + // trakt store their name there (they also store name but that's not the same). + Username ??= value["username"]?.ToString(); // simkl store their name there. Username ??= value["name"]?.ToString(); + + Sub ??= value["ids"]?["uuid"]?.ToString(); } } diff --git a/back/src/Kyoo.Authentication/Models/Options/PermissionOption.cs b/back/src/Kyoo.Authentication/Models/Options/PermissionOption.cs index cd41c0bb..90b8cb67 100644 --- a/back/src/Kyoo.Authentication/Models/Options/PermissionOption.cs +++ b/back/src/Kyoo.Authentication/Models/Options/PermissionOption.cs @@ -148,5 +148,19 @@ public class OidcProvider GetExtraHeaders = (OidcProvider self) => new() { ["simkl-api-key"] = self.ClientId }, }, + ["trakt"] = new("trakt") + { + DisplayName = "Trakt", + LogoUrl = "https://logo.clearbit.com/trakt.tv", + AuthorizationUrl = "https://api.trakt.tv/oauth/authorize", + TokenUrl = "https://api.trakt.tv/oauth/token", + ProfileUrl = "https://api.trakt.tv/users/settings", + // does not seems to have scopes + Scope = null, + TokenUseJsonBody = true, + GetProfileUrl = (profile) => $"https://trakt.tv/users/{profile.Username}", + GetExtraHeaders = (OidcProvider self) => + new() { ["trakt-api-key"] = self.ClientId, ["trakt-api-version"] = "2", }, + }, }; } diff --git a/back/src/Kyoo.Core/Views/Helper/SortBinder.cs b/back/src/Kyoo.Core/Views/Helper/SortBinder.cs index 8bafab6d..fb39e0b1 100644 --- a/back/src/Kyoo.Core/Views/Helper/SortBinder.cs +++ b/back/src/Kyoo.Core/Views/Helper/SortBinder.cs @@ -43,7 +43,7 @@ public class SortBinder : IModelBinder { object sort = bindingContext .ModelType.GetMethod(nameof(Sort.From))! - .Invoke(null, new object?[] { sortBy.FirstValue, seed })!; + .Invoke(null, [sortBy.FirstValue, seed])!; bindingContext.Result = ModelBindingResult.Success(sort); bindingContext.HttpContext.Items["seed"] = seed; return Task.CompletedTask; diff --git a/front/packages/models/src/account-internal.ts b/front/packages/models/src/account-internal.ts index b8d4be89..c8a859c1 100644 --- a/front/packages/models/src/account-internal.ts +++ b/front/packages/models/src/account-internal.ts @@ -21,6 +21,7 @@ import { ZodTypeAny, z } from "zod"; import { Account, AccountP } from "./accounts"; import { MMKV } from "react-native-mmkv"; +import { Platform } from "react-native"; export const storage = new MMKV(); @@ -32,6 +33,13 @@ const readAccounts = () => { const writeAccounts = (accounts: Account[]) => { storage.set("accounts", JSON.stringify(accounts)); + if (Platform.OS === "web") { + const selected = accounts.find((x) => x.selected); + if (!selected) return; + setCookie("account", selected); + // cookie used for images and videos since we can't add Authorization headers in img or video tags. + setCookie("X-Bearer", selected?.token.access_token); + } }; export const setCookie = (key: string, val?: unknown) => {