From ec02b3d8bb19988cc6e490178b75db6efb96aba9 Mon Sep 17 00:00:00 2001 From: Roger Far Date: Sat, 9 Jan 2021 12:34:05 -0700 Subject: [PATCH] Improved inital setup by adding a short wizard. --- client/src/app/app-routing.module.ts | 16 +- client/src/app/app.module.ts | 2 + client/src/app/auth-resolver.service.ts | 15 ++ client/src/app/auth.interceptor.ts | 6 +- client/src/app/auth.service.ts | 13 +- client/src/app/login/login.component.html | 3 +- .../navbar/settings/settings.component.html | 2 +- client/src/app/setup/setup.component.html | 143 ++++++++++++++++++ client/src/app/setup/setup.component.scss | 0 client/src/app/setup/setup.component.ts | 66 ++++++++ server/RdtClient.Data/Data/DataContext.cs | 41 ++++- server/RdtClient.Data/Data/DataMigration.cs | 63 -------- .../Controllers/AuthController.cs | 51 ++++++- server/RdtClient.Web/Program.cs | 1 + server/RdtClient.Web/RdtClient.Web.csproj | 18 --- server/RdtClient.Web/Startup.cs | 3 +- server/RdtClient.Web/serviceinstall.bat | 30 ---- server/RdtClient.Web/serviceremove.bat | 30 ---- server/RdtClient.Web/startup.bat | 1 - 19 files changed, 345 insertions(+), 159 deletions(-) create mode 100644 client/src/app/auth-resolver.service.ts create mode 100644 client/src/app/setup/setup.component.html create mode 100644 client/src/app/setup/setup.component.scss create mode 100644 client/src/app/setup/setup.component.ts delete mode 100644 server/RdtClient.Data/Data/DataMigration.cs delete mode 100644 server/RdtClient.Web/serviceinstall.bat delete mode 100644 server/RdtClient.Web/serviceremove.bat delete mode 100644 server/RdtClient.Web/startup.bat diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts index 4899741..f16c983 100644 --- a/client/src/app/app-routing.module.ts +++ b/client/src/app/app-routing.module.ts @@ -1,22 +1,30 @@ import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { MainLayoutComponent } from './main-layout/main-layout.component'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthResolverService } from './auth-resolver.service'; import { LoginComponent } from './login/login.component'; +import { MainLayoutComponent } from './main-layout/main-layout.component'; +import { SetupComponent } from './setup/setup.component'; const routes: Routes = [ { path: 'login', component: LoginComponent, }, + { + path: 'setup', + component: SetupComponent, + }, { path: '', component: MainLayoutComponent, - canActivate: [], + resolve: { + isLoggedIn: AuthResolverService, + }, }, ]; @NgModule({ - imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })], + imports: [RouterModule.forRoot(routes)], exports: [RouterModule], }) export class AppRoutingModule {} diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 6390cb1..6d84dd0 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -18,6 +18,7 @@ import { FileStatusPipe } from './file-status.pipe'; import { LoginComponent } from './login/login.component'; import { AuthInterceptor } from './auth.interceptor'; import { curray } from 'curray'; +import { SetupComponent } from './setup/setup.component'; curray(); @@ -34,6 +35,7 @@ curray(); TorrentStatusPipe, FileStatusPipe, LoginComponent, + SetupComponent, ], imports: [ BrowserModule, diff --git a/client/src/app/auth-resolver.service.ts b/client/src/app/auth-resolver.service.ts new file mode 100644 index 0000000..4420377 --- /dev/null +++ b/client/src/app/auth-resolver.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; +import { Observable } from 'rxjs'; +import { AuthService } from './auth.service'; + +@Injectable({ + providedIn: 'root', +}) +export class AuthResolverService implements Resolve> { + constructor(private authService: AuthService) {} + + resolve() { + return this.authService.isLoggedIn(); + } +} diff --git a/client/src/app/auth.interceptor.ts b/client/src/app/auth.interceptor.ts index 2aa8775..5545958 100644 --- a/client/src/app/auth.interceptor.ts +++ b/client/src/app/auth.interceptor.ts @@ -3,7 +3,7 @@ import { HttpEvent, HttpHandler, HttpInterceptor, - HttpRequest, + HttpRequest } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; @@ -20,7 +20,9 @@ export class AuthInterceptor implements HttpInterceptor { ): Observable> { return next.handle(req).pipe( catchError((error: HttpErrorResponse) => { - if (error && (error.status === 401 || error.status === 403)) { + if (error && error.status === 402) { + this.router.navigate(['/setup']); + } else if (error && (error.status === 401 || error.status === 403)) { this.router.navigate(['/login']); } return throwError(error); diff --git a/client/src/app/auth.service.ts b/client/src/app/auth.service.ts index 50a3a20..7378193 100644 --- a/client/src/app/auth.service.ts +++ b/client/src/app/auth.service.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; @Injectable({ @@ -8,6 +8,17 @@ import { Observable } from 'rxjs/internal/Observable'; export class AuthService { constructor(private http: HttpClient) {} + public isLoggedIn(): Observable { + return this.http.get(`/Api/Authentication/IsLoggedIn`); + } + + public create(userName: string, password: string): Observable { + return this.http.post(`/Api/Authentication/Create`, { + userName, + password, + }); + } + public login(userName: string, password: string): Observable { return this.http.post(`/Api/Authentication/Login`, { userName, diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 9463640..d31bfdf 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html @@ -2,7 +2,7 @@
-
+
@@ -41,6 +41,7 @@ class="button is-success" (click)="login()" [ngClass]="{ 'is-loading': loggingIn }" + [disabled]="loggingIn" > Login diff --git a/client/src/app/navbar/settings/settings.component.html b/client/src/app/navbar/settings/settings.component.html index d21528c..6b013da 100644 --- a/client/src/app/navbar/settings/settings.component.html +++ b/client/src/app/navbar/settings/settings.component.html @@ -36,7 +36,7 @@ [(ngModel)]="settingDownloadFolder" />
-

Path on the host where Real Debrid Client is hosted.

+

Path on the host where RealDebrid Client is hosted.

diff --git a/client/src/app/setup/setup.component.html b/client/src/app/setup/setup.component.html new file mode 100644 index 0000000..c9ab5ea --- /dev/null +++ b/client/src/app/setup/setup.component.html @@ -0,0 +1,143 @@ +
+
+
+
+
+ +
+
+ Welcome to RealDebrid Client. Please create your account by + entering a username and password. +
+
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+
+ +
+
+ {{ error }} +
+
+ +
+
+ To be able to use the RealDebrid Client you need a premium + subscription to download torrents. +

+ Not premium yet? + Use this link to sign up to RealDebrid. +

+ To connect to RealDebrid please enter your Personal Prive API + Token found here: + https://real-debrid.com/apitoken. +
+
+ +
+ + + + +
+
+
+ +
+
+ {{ error }} +
+
+ +
+
+ You are now connected to RealDebrid! To setup Radarr or Sonarr, + please following the instructions here: + https://github.com/rogerfar/rdt-client/. +
+
+ +
+
+
+
+
+
+
diff --git a/client/src/app/setup/setup.component.scss b/client/src/app/setup/setup.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/client/src/app/setup/setup.component.ts b/client/src/app/setup/setup.component.ts new file mode 100644 index 0000000..77f9be9 --- /dev/null +++ b/client/src/app/setup/setup.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { AuthService } from '../auth.service'; +import { Setting } from '../models/setting.model'; +import { SettingsService } from '../settings.service'; + +@Component({ + selector: 'app-setup', + templateUrl: './setup.component.html', + styleUrls: ['./setup.component.scss'], +}) +export class SetupComponent implements OnInit { + public userName: string; + public password: string; + public token: string; + + public error: string; + public working: boolean; + + public step: number = 1; + + constructor( + private authService: AuthService, + private settingsService: SettingsService, + private router: Router + ) {} + + ngOnInit(): void {} + + public setup(): void { + this.error = null; + this.working = true; + + this.authService.create(this.userName, this.password).subscribe( + () => { + this.step = 2; + this.working = false; + }, + (err) => { + this.working = false; + this.error = err.error; + } + ); + } + + public setToken(): void { + const setting = new Setting(); + setting.settingId = 'RealDebridApiKey'; + setting.value = this.token; + + this.settingsService.update([setting]).subscribe( + () => { + this.step = 3; + this.working = false; + }, + (err) => { + this.working = false; + this.error = err.error; + } + ); + } + + public close(): void { + this.router.navigate(['/']); + } +} diff --git a/server/RdtClient.Data/Data/DataContext.cs b/server/RdtClient.Data/Data/DataContext.cs index bb1491e..0c77857 100644 --- a/server/RdtClient.Data/Data/DataContext.cs +++ b/server/RdtClient.Data/Data/DataContext.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using RdtClient.Data.Models.Data; @@ -33,5 +35,42 @@ namespace RdtClient.Data.Data fk.DeleteBehavior = DeleteBehavior.Restrict; } } + + public async Task Seed() + { + var seedSettings = new List + { + new Setting + { + SettingId = "RealDebridApiKey", + Type = "String", + Value = "" + }, + new Setting + { + SettingId = "DownloadFolder", + Type = "String", + Value = "/data/downloads" + }, + new Setting + { + SettingId = "DownloadLimit", + Type = "Int32", + Value = "10" + } + }; + + var dbSettings = await Settings.ToListAsync(); + foreach (var seedSetting in seedSettings) + { + var dbSetting = dbSettings.FirstOrDefault(m => m.SettingId == seedSetting.SettingId); + + if (dbSetting == null) + { + await Settings.AddAsync(seedSetting); + await SaveChangesAsync(); + } + } + } } } \ No newline at end of file diff --git a/server/RdtClient.Data/Data/DataMigration.cs b/server/RdtClient.Data/Data/DataMigration.cs deleted file mode 100644 index faacfd4..0000000 --- a/server/RdtClient.Data/Data/DataMigration.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Linq; -using System.Runtime.InteropServices; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using RdtClient.Data.Models.Data; - -namespace RdtClient.Data.Data -{ - public static class DataMigration - { - private static Boolean InDocker => Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") == "true"; - - public static void Setup(IServiceScope serviceScope) - { - using var scope = serviceScope.ServiceProvider.GetRequiredService().CreateScope(); - - var dataContext = scope.ServiceProvider.GetRequiredService(); - - dataContext.Database.Migrate(); - - Seed(dataContext); - } - - private static void Seed(DataContext dataContext) - { - var defaultDownloadPath = "/data/downloads"; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !InDocker) - { - defaultDownloadPath = @"C:\Downloads"; - } - - var configuration = dataContext.Settings.FirstOrDefault(); - - if (configuration == null) - { - dataContext.Settings.Add(new Setting - { - SettingId = "RealDebridApiKey", - Type = "String", - Value = "" - }); - - dataContext.Settings.Add(new Setting - { - SettingId = "DownloadFolder", - Type = "String", - Value = defaultDownloadPath - }); - - dataContext.Settings.Add(new Setting - { - SettingId = "DownloadLimit", - Type = "Int32", - Value = "10" - }); - - dataContext.SaveChanges(); - } - } - } -} diff --git a/server/RdtClient.Web/Controllers/AuthController.cs b/server/RdtClient.Web/Controllers/AuthController.cs index 7d9ceac..233fc07 100644 --- a/server/RdtClient.Web/Controllers/AuthController.cs +++ b/server/RdtClient.Web/Controllers/AuthController.cs @@ -16,6 +16,50 @@ namespace RdtClient.Web.Controllers { _authentication = authentication; } + + [AllowAnonymous] + [Route("IsLoggedIn")] + [HttpGet] + public async Task IsLoggedIn() + { + if (User.Identity?.IsAuthenticated == false) + { + var user = await _authentication.GetUser(); + + if (user == null) + { + return StatusCode(402, "Setup required"); + } + + return Unauthorized(); + } + + return Ok(); + } + + [AllowAnonymous] + [Route("Create")] + [HttpPost] + public async Task Create([FromBody] AuthControllerLoginRequest request) + { + var user = await _authentication.GetUser(); + + if (user != null) + { + return StatusCode(401); + } + + var registerResult = await _authentication.Register(request.UserName, request.Password); + + if (!registerResult.Succeeded) + { + return BadRequest(registerResult.Errors.First().Description); + } + + await _authentication.Login(request.UserName, request.Password); + + return Ok(); + } [AllowAnonymous] [Route("Login")] @@ -26,12 +70,7 @@ namespace RdtClient.Web.Controllers if (user == null) { - var registerResult = await _authentication.Register(request.UserName, request.Password); - - if (!registerResult.Succeeded) - { - return BadRequest(registerResult.Errors.First().Description); - } + return StatusCode(402); } var result = await _authentication.Login(request.UserName, request.Password); diff --git a/server/RdtClient.Web/Program.cs b/server/RdtClient.Web/Program.cs index 8b441e8..4fcfdc3 100644 --- a/server/RdtClient.Web/Program.cs +++ b/server/RdtClient.Web/Program.cs @@ -28,6 +28,7 @@ namespace RdtClient.Web { var dbContext = scope.ServiceProvider.GetRequiredService(); await dbContext.Database.MigrateAsync(); + await dbContext.Seed(); } await host.RunAsync(); diff --git a/server/RdtClient.Web/RdtClient.Web.csproj b/server/RdtClient.Web/RdtClient.Web.csproj index d27d42d..5334df7 100644 --- a/server/RdtClient.Web/RdtClient.Web.csproj +++ b/server/RdtClient.Web/RdtClient.Web.csproj @@ -6,24 +6,6 @@ 94c24cba-f03f-4453-a671-3640b517c573 - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - diff --git a/server/RdtClient.Web/Startup.cs b/server/RdtClient.Web/Startup.cs index c3fd05a..3aaf03b 100644 --- a/server/RdtClient.Web/Startup.cs +++ b/server/RdtClient.Web/Startup.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; @@ -85,7 +86,7 @@ namespace RdtClient.Web Service.DiConfig.Config(services); } - public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger, DataContext dataContext) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger logger) { if (env.IsDevelopment()) { diff --git a/server/RdtClient.Web/serviceinstall.bat b/server/RdtClient.Web/serviceinstall.bat deleted file mode 100644 index 284f6e7..0000000 --- a/server/RdtClient.Web/serviceinstall.bat +++ /dev/null @@ -1,30 +0,0 @@ -@echo off -set installpath=%~dp0 -NET SESSION >nul 2>&1 -IF %ERRORLEVEL% EQU 0 ( - echo adding firewall rules... - netsh.exe advfirewall firewall add rule name="RealDebridClient" dir=in action=allow program="%installpath%RdtClient.Web.exe" enable=yes > nul - echo installing service... - sc create RealDebridClient binPath="%installpath%RdtClient.Web.exe" start=auto - timeout /t 5 /nobreak > NUL - net start RealDebridClient -) ELSE ( - echo ######## ######## ######## ####### ######## - echo ## ## ## ## ## ## ## ## ## - echo ## ## ## ## ## ## ## ## ## - echo ###### ######## ######## ## ## ######## - echo ## ## ## ## ## ## ## ## ## - echo ## ## ## ## ## ## ## ## ## - echo ######## ## ## ## ## ####### ## ## - echo. - echo. - echo ####### ERROR: ADMINISTRATOR PRIVILEGES REQUIRED ######### - echo This script must be run as administrator to work properly! - echo If you're seeing this after clicking on a start menu icon, - echo then right click on the shortcut and select "Run As Administrator". - echo ########################################################## - echo. - PAUSE - EXIT /B 1 -) -@echo ON \ No newline at end of file diff --git a/server/RdtClient.Web/serviceremove.bat b/server/RdtClient.Web/serviceremove.bat deleted file mode 100644 index b6fa4f1..0000000 --- a/server/RdtClient.Web/serviceremove.bat +++ /dev/null @@ -1,30 +0,0 @@ -@echo off -set installpath=%~dp0 -NET SESSION >nul 2>&1 -IF %ERRORLEVEL% EQU 0 ( - echo removing firewall rules... - netsh.exe advfirewall firewall remove rule name="RealDebridClient" > nul - echo removing service... - net stop RealDebridClient - timeout /t 5 /nobreak > NUL - sc delete RealDebridClient -) ELSE ( - echo ######## ######## ######## ####### ######## - echo ## ## ## ## ## ## ## ## ## - echo ## ## ## ## ## ## ## ## ## - echo ###### ######## ######## ## ## ######## - echo ## ## ## ## ## ## ## ## ## - echo ## ## ## ## ## ## ## ## ## - echo ######## ## ## ## ## ####### ## ## - echo. - echo. - echo ####### ERROR: ADMINISTRATOR PRIVILEGES REQUIRED ######### - echo This script must be run as administrator to work properly! - echo If you're seeing this after clicking on a start menu icon, - echo then right click on the shortcut and select "Run As Administrator". - echo ########################################################## - echo. - PAUSE - EXIT /B 1 -) -@echo ON \ No newline at end of file diff --git a/server/RdtClient.Web/startup.bat b/server/RdtClient.Web/startup.bat deleted file mode 100644 index 4420b52..0000000 --- a/server/RdtClient.Web/startup.bat +++ /dev/null @@ -1 +0,0 @@ -dotnet RdtClient.Web.dll \ No newline at end of file