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",