diff --git a/src/Ombi.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs index c81d9b1c8..419c2257d 100644 --- a/src/Ombi.Store/Entities/OmbiUser.cs +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -22,6 +22,8 @@ namespace Ombi.Store.Entities public int? MovieRequestLimit { get; set; } public int? EpisodeRequestLimit { get; set; } + public string UserAccessToken { get; set; } + [NotMapped] public bool IsEmbyConnect => UserType == UserType.EmbyUser && EmbyConnectUserId.HasValue(); diff --git a/src/Ombi.Store/Migrations/20180112132036_UserAccessToken.Designer.cs b/src/Ombi.Store/Migrations/20180112132036_UserAccessToken.Designer.cs new file mode 100644 index 000000000..be9ceeb50 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180112132036_UserAccessToken.Designer.cs @@ -0,0 +1,889 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180112132036_UserAccessToken")] + partial class UserAccessToken + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ProviderId"); + + b.Property("Title"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180112132036_UserAccessToken.cs b/src/Ombi.Store/Migrations/20180112132036_UserAccessToken.cs new file mode 100644 index 000000000..a8ed4a6af --- /dev/null +++ b/src/Ombi.Store/Migrations/20180112132036_UserAccessToken.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class UserAccessToken : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserAccessToken", + table: "AspNetUsers", + type: "TEXT", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UserAccessToken", + table: "AspNetUsers"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index bd3e5a62d..40b76ee37 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -303,6 +303,8 @@ namespace Ombi.Store.Migrations b.Property("TwoFactorEnabled"); + b.Property("UserAccessToken"); + b.Property("UserName") .HasMaxLength(256); diff --git a/src/Ombi/ClientApp/app/app.component.html b/src/Ombi/ClientApp/app/app.component.html index d16079a3b..5364fe9a6 100644 --- a/src/Ombi/ClientApp/app/app.component.html +++ b/src/Ombi/ClientApp/app/app.component.html @@ -76,6 +76,10 @@ {{ 'NavigationBar.UpdateDetails' | translate }} +
  • + + {{ 'NavigationBar.OpenMobileApp' | translate }} +
  • {{ 'NavigationBar.Logout' | translate }} diff --git a/src/Ombi/ClientApp/app/app.component.ts b/src/Ombi/ClientApp/app/app.component.ts index 6b3b61542..c42ae05a6 100644 --- a/src/Ombi/ClientApp/app/app.component.ts +++ b/src/Ombi/ClientApp/app/app.component.ts @@ -3,7 +3,7 @@ import { NavigationStart, Router } from "@angular/router"; import { TranslateService } from "@ngx-translate/core"; import { AuthService } from "./auth/auth.service"; import { ILocalUser } from "./auth/IUserLogin"; -import { NotificationService } from "./services"; +import { IdentityService, NotificationService } from "./services"; import { JobService, SettingsService } from "./services"; import { ICustomizationSettings } from "./interfaces"; @@ -21,6 +21,8 @@ export class AppComponent implements OnInit { public showNav: boolean; public updateAvailable: boolean; public currentUrl: string; + public userAccessToken: string; + public showMobileLink = false; private checkedForUpdate: boolean; @@ -29,7 +31,8 @@ export class AppComponent implements OnInit { private readonly router: Router, private readonly settingsService: SettingsService, private readonly jobService: JobService, - public readonly translate: TranslateService) { + public readonly translate: TranslateService, + private readonly identityService: IdentityService) { this.translate.addLangs(["en", "de", "fr","da","es","it","nl","sv","no"]); // this language will be used as a fallback when a translation isn't found in the current language this.translate.setDefaultLang("en"); @@ -67,6 +70,19 @@ export class AppComponent implements OnInit { return this.user.roles.some(r => r === role); } + public openMobileApp(event: any) { + event.preventDefault(); + if(!this.customizationSettings.applicationUrl) { + this.notificationService.warning("Mobile","Please ask your admin to setup the Application URL!"); + return; + } + + this.identityService.getAccessToken().subscribe(x => { + const url = `ombi://${this.customizationSettings.applicationUrl}/${x}`; + window.location.assign(url); + }); + } + public logOut() { this.authService.logout(); this.router.navigate(["login"]); diff --git a/src/Ombi/ClientApp/app/auth/auth.service.ts b/src/Ombi/ClientApp/app/auth/auth.service.ts index 976c9dcb9..6249edc5b 100644 --- a/src/Ombi/ClientApp/app/auth/auth.service.ts +++ b/src/Ombi/ClientApp/app/auth/auth.service.ts @@ -41,7 +41,6 @@ export class AuthService extends ServiceHelpers { const u = { name, roles: [] as string[] }; if (roles instanceof Array) { - u.roles = roles; } else { u.roles.push(roles); diff --git a/src/Ombi/ClientApp/app/interfaces/IUser.ts b/src/Ombi/ClientApp/app/interfaces/IUser.ts index 19fee24a8..8cad85fb0 100644 --- a/src/Ombi/ClientApp/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/app/interfaces/IUser.ts @@ -12,6 +12,7 @@ export interface IUser { hasLoggedIn: boolean; movieRequestLimit: number; episodeRequestLimit: number; + userAccessToken: string; // FOR UI checked: boolean; } diff --git a/src/Ombi/ClientApp/app/services/identity.service.ts b/src/Ombi/ClientApp/app/services/identity.service.ts index 6a58fb46f..812885caf 100644 --- a/src/Ombi/ClientApp/app/services/identity.service.ts +++ b/src/Ombi/ClientApp/app/services/identity.service.ts @@ -19,6 +19,10 @@ export class IdentityService extends ServiceHelpers { public getUser(): Observable { return this.http.get(this.url, {headers: this.headers}); } + + public getAccessToken(): Observable { + return this.http.get(`${this.url}accesstoken`, {headers: this.headers}); + } public getUserById(id: string): Observable { return this.http.get(`${this.url}User/${id}`, {headers: this.headers}); diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts index 7d0b4167a..36b187e79 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts @@ -31,6 +31,7 @@ export class UserManagementAddComponent implements OnInit { lastLoggedIn: new Date(), episodeRequestLimit: 0, movieRequestLimit: 0, + userAccessToken: "", }; } diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index d9b1f7a46..58ba1b7c1 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -316,7 +316,8 @@ namespace Ombi.Controllers UserName = user.UserName, UserType = UserType.LocalUser, MovieRequestLimit = user.MovieRequestLimit, - EpisodeRequestLimit = user.EpisodeRequestLimit + EpisodeRequestLimit = user.EpisodeRequestLimit, + UserAccessToken = Guid.NewGuid().ToString("N"), }; var userResult = await UserManager.CreateAsync(ombiUser, user.Password); @@ -684,9 +685,32 @@ namespace Ombi.Controllers BackgroundJob.Enqueue(() => WelcomeEmail.SendEmail(ombiUser)); } - private async Task> AddRoles(IEnumerable roles, OmbiUser ombiUser) + [HttpGet("accesstoken")] + [ApiExplorerSettings(IgnoreApi = true)] + public async Task GetUserAccessToken() + { + var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name); + if (user == null) + { + return Guid.Empty.ToString("N"); + } + if (user.UserAccessToken.IsNullOrEmpty()) + { + // Let's create an access token for this user + user.UserAccessToken = Guid.NewGuid().ToString("N"); + var result = await UserManager.UpdateAsync(user); + if (!result.Succeeded) + { + LogErrors(result); + return Guid.Empty.ToString("N"); + } + } + return user.UserAccessToken; + } + + private async Task> AddRoles(IEnumerable roles, OmbiUser ombiUser) { - var roleResult = new List(); + var roleResult = new List(); foreach (var role in roles) { if (role.Enabled) diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 8553cfb31..f66754a0e 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -194,6 +194,8 @@ namespace Ombi app.UseAuthentication(); + app.UseMiddleware(); + app.ApiKeyMiddlewear(serviceProvider); app.UseSwagger(); app.UseSwaggerUI(c => @@ -202,7 +204,6 @@ namespace Ombi c.ShowJsonEditor(); }); - app.UseMiddleware(); app.UseMvc(routes => { routes.MapRoute( diff --git a/src/Ombi/StartupExtensions.cs b/src/Ombi/StartupExtensions.cs index ee6301a9a..e4dae18e4 100644 --- a/src/Ombi/StartupExtensions.cs +++ b/src/Ombi/StartupExtensions.cs @@ -8,12 +8,14 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.PlatformAbstractions; using Microsoft.Extensions.Primitives; using Microsoft.IdentityModel.Tokens; using Ombi.Config; +using Ombi.Core.Authentication; using Ombi.Core.Settings; using Ombi.Helpers; using Ombi.Models.Identity; @@ -60,7 +62,7 @@ namespace Ombi In = "header", Type = "apiKey", }); - + c.OperationFilter(); c.DescribeAllParametersInCamelCase(); }); @@ -135,6 +137,12 @@ namespace Ombi await ValidateApiKey(serviceProvider, context, next, queryKey); } } + // User access token used by the mobile app + else if (context.Request.Headers["UserAccessToken"].Any()) + { + var headerKey = context.Request.Headers["UserAccessToken"].FirstOrDefault(); + await ValidateUserAccessToken(serviceProvider, context, next, headerKey); + } else { await next(); @@ -147,6 +155,32 @@ namespace Ombi }); } + private static async Task ValidateUserAccessToken(IServiceProvider serviceProvider, HttpContext context, Func next, string key) + { + if (key.IsNullOrEmpty()) + { + await context.Response.WriteAsync("Invalid User Access Token"); + return; + } + + var um = serviceProvider.GetService(); + var user = await um.Users.FirstOrDefaultAsync(x => x.UserAccessToken == key); + if (user == null) + { + context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + await context.Response.WriteAsync("Invalid User Access Token"); + } + else + { + + var identity = new GenericIdentity(user.UserName); + var roles = await um.GetRolesAsync(user); + var principal = new GenericPrincipal(identity, roles.ToArray()); + context.User = principal; + await next(); + } + } + private static async Task ValidateApiKey(IServiceProvider serviceProvider, HttpContext context, Func next, string key) { var settingsProvider = serviceProvider.GetService>(); diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 4e00625aa..96da0a13e 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -63,7 +63,8 @@ "Danish": "Danish", "Dutch": "Dutch", "Norwegian":"Norwegian" - } + }, + "OpenMobileApp":"Open Mobile App" }, "Search": { "Title": "Search",