pull/1389/head
Jamie.Rees 8 years ago
parent a21db34d60
commit 98fb15c263

@ -1,5 +1,8 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Ombi.Core;
using Ombi.Core.Engine;
@ -53,5 +56,16 @@ namespace Ombi.DependencyInjection
services.AddTransient<IRequestService, JsonRequestService>();
return services;
}
public static IServiceCollection RegisterIdentity(this IServiceCollection services)
{
services.AddAuthorization(auth =>
{
auth.AddPolicy("Bearer", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser().Build());
});
return services;
}
}
}

@ -4,11 +4,20 @@
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="1.4.2" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Authorization">
<HintPath>..\..\..\..\..\.nuget\packages\microsoft.aspnetcore.authorization\1.1.1\lib\netstandard1.3\Microsoft.AspNetCore.Authorization.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
<HintPath>..\..\..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>

@ -6,6 +6,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />

@ -17,5 +17,5 @@
/libpeerconnection.log
npm-debug.log
testem.log
/typings
#/typings
/systemjs.config.js*

@ -0,0 +1,72 @@
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.IdentityModel.Tokens;
namespace Ombi.Auth
{
public class CustomJwtDataFormat : ISecureDataFormat<AuthenticationTicket>
{
private readonly string algorithm;
private readonly TokenValidationParameters validationParameters;
public CustomJwtDataFormat(string algorithm, TokenValidationParameters validationParameters)
{
this.algorithm = algorithm;
this.validationParameters = validationParameters;
}
public AuthenticationTicket Unprotect(string protectedText)
=> Unprotect(protectedText, null);
public AuthenticationTicket Unprotect(string protectedText, string purpose)
{
var handler = new JwtSecurityTokenHandler();
ClaimsPrincipal principal = null;
SecurityToken validToken = null;
try
{
principal = handler.ValidateToken(protectedText, this.validationParameters, out validToken);
var validJwt = validToken as JwtSecurityToken;
if (validJwt == null)
{
throw new ArgumentException("Invalid JWT");
}
if (!validJwt.Header.Alg.Equals(algorithm, StringComparison.Ordinal))
{
throw new ArgumentException($"Algorithm must be '{algorithm}'");
}
// Additional custom validation of JWT claims here (if any)
}
catch (SecurityTokenValidationException)
{
return null;
}
catch (ArgumentException)
{
return null;
}
// Validation passed. Return a valid AuthenticationTicket:
return new AuthenticationTicket(principal, new AuthenticationProperties(), "Cookie");
}
// This ISecureDataFormat implementation is decode-only
public string Protect(AuthenticationTicket data)
{
throw new NotImplementedException();
}
public string Protect(AuthenticationTicket data, string purpose)
{
throw new NotImplementedException();
}
}
}

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Ombi.Models;
namespace Ombi.Auth
{
public class TokenProviderMiddleware
{
private readonly RequestDelegate _next;
private readonly TokenProviderOptions _options;
private readonly JsonSerializerSettings _serializerSettings;
public TokenProviderMiddleware(
RequestDelegate next,
IOptions<TokenProviderOptions> options)
{
_next = next;
_options = options.Value;
ThrowIfInvalidOptions(_options);
_serializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented
};
}
public Task Invoke(HttpContext context)
{
// If the request path doesn't match, skip
if (!context.Request.Path.Equals(_options.Path, StringComparison.Ordinal))
{
return _next(context);
}
// Request must be POST with Content-Type: application/json
if (!context.Request.Method.Equals("POST")
)
{
context.Response.StatusCode = 400;
return context.Response.WriteAsync("Bad request.");
}
return GenerateToken(context);
}
private async Task GenerateToken(HttpContext context)
{
var request = context.Request;
UserAuthModel userInfo; // TODO use a stong type
using (var bodyReader = new StreamReader(request.Body))
{
string body = await bodyReader.ReadToEndAsync();
userInfo = JsonConvert.DeserializeObject<UserAuthModel>(body);
}
var identity = await _options.IdentityResolver(userInfo.Username, userInfo.Password);
if (identity == null)
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Invalid username or password.");
return;
}
var now = DateTime.UtcNow;
// Specifically add the jti (nonce), iat (issued timestamp), and sub (subject/user) claims.
// You can add other claims here, if you want:
var jwtClaims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, userInfo.Username),
new Claim(JwtRegisteredClaimNames.Jti, await _options.NonceGenerator()),
new Claim(JwtRegisteredClaimNames.Iat, new DateTimeOffset(now).ToUniversalTime().ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
identity.Claims.ToList().AddRange(jwtClaims);
// Create the JWT and write it to a string
var jwt = new JwtSecurityToken(
issuer: _options.Issuer,
audience: _options.Audience,
claims: identity.Claims,
notBefore: now,
expires: now.Add(_options.Expiration),
signingCredentials: _options.SigningCredentials);
var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
var response = new
{
access_token = encodedJwt,
expires_in = (int)_options.Expiration.TotalSeconds
};
// Serialize and return the response
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(response, _serializerSettings));
}
private static void ThrowIfInvalidOptions(TokenProviderOptions options)
{
if (string.IsNullOrEmpty(options.Path))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Path));
}
if (string.IsNullOrEmpty(options.Issuer))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Issuer));
}
if (string.IsNullOrEmpty(options.Audience))
{
throw new ArgumentNullException(nameof(TokenProviderOptions.Audience));
}
if (options.Expiration == TimeSpan.Zero)
{
throw new ArgumentException("Must be a non-zero TimeSpan.", nameof(TokenProviderOptions.Expiration));
}
if (options.IdentityResolver == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.IdentityResolver));
}
if (options.SigningCredentials == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.SigningCredentials));
}
if (options.NonceGenerator == null)
{
throw new ArgumentNullException(nameof(TokenProviderOptions.NonceGenerator));
}
}
}
}

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
namespace Ombi.Auth
{
public class TokenProviderOptions
{
/// <summary>
/// The relative request path to listen on.
/// </summary>
/// <remarks>The default path is <c>/token</c>.</remarks>
public string Path { get; set; } = "/token/";
/// <summary>
/// The Issuer (iss) claim for generated tokens.
/// </summary>
public string Issuer { get; set; }
/// <summary>
/// The Audience (aud) claim for the generated tokens.
/// </summary>
public string Audience { get; set; }
/// <summary>
/// The expiration time for the generated tokens.
/// </summary>
/// <remarks>The default is five minutes (300 seconds).</remarks>
public TimeSpan Expiration { get; set; } = TimeSpan.FromMinutes(5);
/// <summary>
/// The signing key to use when generating tokens.
/// </summary>
public SigningCredentials SigningCredentials { get; set; }
/// <summary>
/// Resolves a user identity given a username and password.
/// </summary>
public Func<string, string, Task<ClaimsIdentity>> IdentityResolver { get; set; }
/// <summary>
/// Generates a random value (nonce) for each generated token.
/// </summary>
/// <remarks>The default nonce is a random GUID.</remarks>
public Func<Task<string>> NonceGenerator { get; set; }
= () => Task.FromResult(Guid.NewGuid().ToString());
}
}

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc;
namespace Ombi.Controllers
{
@ -12,24 +8,5 @@ namespace Ombi.Controllers
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
public IActionResult Error()
{
return View();
}
}
}

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Ombi.Core.Engine;
using Ombi.Core.Models.Requests;
@ -7,6 +8,7 @@ using Ombi.Core.Models.Search;
namespace Ombi.Controllers
{
[Authorize]
public class RequestController : BaseV1ApiController
{
public RequestController(IRequestEngine engine)

@ -0,0 +1,18 @@
using System;
namespace Ombi.Models
{
public class RequestResult
{
public RequestState State { get; set; }
public string Msg { get; set; }
public Object Data { get; set; }
}
public enum RequestState
{
Failed = -1,
NotAuth = 0,
Success = 1
}
}

@ -0,0 +1,8 @@
namespace Ombi.Models
{
public class UserAuthModel
{
public string Username { get; set; }
public string Password { get; set; }
}
}

@ -3,24 +3,48 @@
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx.10.12-x64;ubuntu.16.10-x64;debian.8-x64;</RuntimeIdentifiers>
<PackageTargetFallback>portable-net45+win8</PackageTargetFallback>
</PropertyGroup>
<ItemGroup>
<Content Include="wwwroot\app\auth\auth - Copy.module.js.map">
<DependentUpon>auth - Copy.module.js</DependentUpon>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="wwwroot\app\auth\auth.guard.ts">
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
<Content Include="wwwroot\app\auth\auth.module.ts" />
<Content Include="wwwroot\app\auth\auth.service.ts" />
<Content Include="wwwroot\app\auth\AuthModule.ts" />
<Content Include="wwwroot\app\auth\IUserLogin.ts" />
<Content Include="wwwroot\app\login\login.component.html" />
<Content Include="wwwroot\app\login\login.component.ts" />
<Content Include="wwwroot\app\settings\settingsmenu.component.html" />
<Content Include="wwwroot\app\settings\settingsmenu.component.js" />
<Content Include="wwwroot\app\settings\settingsmenu.component.js.map" />
<Content Include="wwwroot\app\settings\settingsmenu.component.ts" />
</ItemGroup>
<ItemGroup>
<None Include="Startup.Auth.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="IdentityServer4" Version="1.4.2" />
<PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.1" />
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.0" />
</ItemGroup>
@ -30,4 +54,13 @@
<ProjectReference Include="..\Ombi.DependencyInjection\Ombi.DependencyInjection.csproj" />
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.TheMovieDbApi.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Configuration">
<HintPath>..\..\..\..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.Configuration.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Csp">
<HintPath>..\..\..\..\..\.nuget\packages\system.security.cryptography.csp\4.3.0\ref\netstandard1.3\System.Security.Cryptography.Csp.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

@ -11,6 +11,7 @@ namespace Ombi
{
public static void Main(string[] args)
{
Console.Title = "Ombi";
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())

@ -0,0 +1,97 @@
using System;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Ombi.Auth;
namespace Ombi
{
public partial class Startup
{
public SymmetricSecurityKey signingKey;
private void ConfigureAuth(IApplicationBuilder app)
{
var signingKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("TokenAuthentication:SecretKey").Value));
var tokenProviderOptions = new TokenProviderOptions
{
Path = Configuration.GetSection("TokenAuthentication:TokenPath").Value,
Audience = Configuration.GetSection("TokenAuthentication:Audience").Value,
Issuer = Configuration.GetSection("TokenAuthentication:Issuer").Value,
SigningCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256),
IdentityResolver = GetIdentity
};
var tokenValidationParameters = new TokenValidationParameters
{
// The signing key must match!
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
// Validate the JWT Issuer (iss) claim
ValidateIssuer = true,
ValidIssuer = Configuration.GetSection("TokenAuthentication:Issuer").Value,
// Validate the JWT Audience (aud) claim
ValidateAudience = true,
ValidAudience = Configuration.GetSection("TokenAuthentication:Audience").Value,
// Validate the token expiry
ValidateLifetime = true,
// If you want to allow a certain amount of clock drift, set that here:
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
AuthenticationScheme = "Cookie",
CookieName = Configuration.GetSection("TokenAuthentication:CookieName").Value,
TicketDataFormat = new CustomJwtDataFormat(
SecurityAlgorithms.HmacSha256,
tokenValidationParameters)
});
app.UseMiddleware<TokenProviderMiddleware>(Options.Create(tokenProviderOptions));
}
private Task<ClaimsIdentity> GetIdentity(string username, string password)
{
// DEMO CODE, DON NOT USE IN PRODUCTION!!!
if (username == "TEST" && password == "TEST123")
{
var claim = new ClaimsIdentity(new GenericIdentity(username, "Token"),
new[]
{
//new Claim(ClaimTypes.Role, "Admin"),
new Claim(ClaimTypes.Name, "Test"),
});
claim.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, "Admin", ClaimValueTypes.String));
return Task.FromResult(claim);
}
if (username == "TEST2" && password == "TEST123")
{
return Task.FromResult(new ClaimsIdentity(new GenericIdentity(username, "Token"), new Claim[] {
new Claim(ClaimTypes.Role, "User"),
new Claim(ClaimTypes.Name, "Test2"), }));
}
// Account doesn't exists
return Task.FromResult<ClaimsIdentity>(null);
}
}
}

@ -1,5 +1,10 @@
using System.IO;
using System;
using System.IO;
using System.Linq;
using System.Text;
using IdentityServer4.EntityFramework.DbContexts;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;
@ -8,12 +13,16 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using Ombi.Auth;
using Ombi.DependencyInjection;
using Ombi.Models;
using Ombi.Store.Context;
namespace Ombi
{
public class Startup
public partial class Startup
{
public Startup(IHostingEnvironment env)
{
@ -51,6 +60,10 @@ namespace Ombi
app.UseExceptionHandler("/Home/Error");
}
ConfigureAuth(app);
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".map"] = "application/octet-stream";
@ -70,5 +83,8 @@ namespace Ombi
defaults: new { controller = "Home", action = "Index" });
});
}
}
}

@ -4,5 +4,12 @@
"LogLevel": {
"Default": "Warning"
}
},
"TokenAuthentication": {
"SecretKey": "secretkey_secretkey123!",
"Issuer": "DemoIssuer",
"Audience": "DemoAudience",
"TokenPath": "/api/token/",
"CookieName": "access_token"
}
}

@ -57,6 +57,7 @@ var paths = {
'./bower_components/PACE/pace.js',
'./node_modules/bootstrap/dist/js/bootstrap.js',
'./node_modules/tether/dist/js/tether.js',
'./node_modules/angular2-jwt/angular2-jwt.js',
'./systemjs.config.js',
],
dest: './lib/'

@ -18,6 +18,7 @@
"@types/systemjs": "^0.20.2",
"angular2-infinite-scroll": "^0.3.4",
"angular2-moment": "^1.3.3",
"angular2-jwt": "0.2.0",
"bootstrap": "3.3.6",
"core-js": "^2.4.1",
"del": "^2.2.2",

@ -0,0 +1,5 @@
// Globals
declare var module: any;
declare var require: any;
declare var localStorage: any;

@ -0,0 +1 @@
/// <reference path="globals/globals.d.ts" />

@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { NotificationService } from './services/notification.service';
import { AuthService } from './auth/auth.service';
@Component({
selector: 'ombi',
@ -8,5 +9,5 @@ import { NotificationService } from './services/notification.service';
})
export class AppComponent {
constructor(public notificationService: NotificationService) { };
constructor(public notificationService: NotificationService, public authService : AuthService) { };
}

@ -12,12 +12,16 @@ import { InfiniteScrollModule } from 'angular2-infinite-scroll/angular2-infinite
import { SearchComponent } from './search/search.component';
import { RequestComponent } from './requests/request.component';
import { LoginComponent } from './login/login.component';
import { PageNotFoundComponent } from './errors/not-found.component';
// Services
import { SearchService } from './services/search.service';
import { RequestService } from './services/request.service';
import { NotificationService } from './services/notification.service';
import { AuthService } from './auth/auth.service';
import { AuthGuard } from './auth/auth.guard';
import { AuthModule } from './auth/auth.module';
// Modules
import { SettingsModule } from './settings/settings.module';
@ -28,8 +32,10 @@ import { DataTableModule, SharedModule } from 'primeng/primeng';
const routes: Routes = [
{ path: '*', component: PageNotFoundComponent },
{ path: 'search', component: SearchComponent },
{ path: 'requests', component: RequestComponent },
{ path: '', redirectTo: '/search', pathMatch: 'full' },
{ path: 'search', component: SearchComponent, canActivate: [AuthGuard] },
{ path: 'requests', component: RequestComponent, canActivate: [AuthGuard] },
{ path: 'login', component: LoginComponent },
];
@NgModule({
@ -44,18 +50,22 @@ const routes: Routes = [
SettingsModule,
DataTableModule,
SharedModule,
InfiniteScrollModule
InfiniteScrollModule,
AuthModule
],
declarations: [
AppComponent,
PageNotFoundComponent,
SearchComponent,
RequestComponent
RequestComponent,
LoginComponent
],
providers: [
SearchService,
RequestService,
NotificationService
NotificationService,
AuthService,
AuthGuard,
],
bootstrap: [AppComponent]
})

@ -0,0 +1,4 @@
export interface IUserLogin {
username: string,
password:string
}

@ -0,0 +1,20 @@

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { CanActivate } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) { }
canActivate() {
if (this.auth.loggedIn()) {
return true;
} else {
this.router.navigate(['login']);
return false;
}
}
}

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { Http, RequestOptions } from '@angular/http';
import { AuthHttp, AuthConfig } from 'angular2-jwt';
export function authHttpServiceFactory(http: Http, options: RequestOptions) {
return new AuthHttp(new AuthConfig(), http, options);
}
@NgModule({
providers: [
{
provide: AuthHttp,
useFactory: authHttpServiceFactory,
deps: [Http, RequestOptions]
}
]
})
export class AuthModule {}

@ -0,0 +1,33 @@

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import { ServiceHelpers } from '../services/service.helpers';
import { IUserLogin } from './IUserLogin';
import { tokenNotExpired } from 'angular2-jwt';
import { Http } from '@angular/http';
@Injectable()
export class AuthService extends ServiceHelpers {
constructor(http: Http) {
super(http, '/api/token');
}
login(login:IUserLogin) : Observable<any> {
return this.http.post(`${this.url}/`, JSON.stringify(login), { headers: this.headers })
.map(this.extractData);
}
loggedIn() {
return tokenNotExpired();
}
logout() {
localStorage.removeItem('id_token');
}
}

@ -0,0 +1,32 @@
<div class="home">
<h1>Login</h1>
<div>
<p>
@UI.UserLogin_Paragraph <span title="@UI.UserLogin_Paragraph_SpanHover"><i class="fa fa-question-circle"></i></span>
</p>
</div>
<form method="POST" id="loginForm">
<div>
<div>
<label>Username</label>
</div>
<div>
<input [(ngModel)]="username" class="form-control form-control-custom" type="text" name="Username" />
</div>
</div>
<br />
<div>
<div>
<label> Password </label>
</div>
<div>
<input [(ngModel)]="password" class="form-control form-control-custom" name="Password" type="password" />
</div>
</div>
<br />
<button (click)="login()" class="btn btn-success-outline" type="submit"><i class="fa fa-user fa-fw"></i> Sign In</button>
</form>
</div>

@ -0,0 +1,34 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { AuthService } from '../auth/auth.service';
import { NotificationService } from '../services/notification.service';
@Component({
selector: 'ombi',
moduleId: module.id,
templateUrl: './login.component.html',
})
export class LoginComponent {
constructor(private authService: AuthService, private router: Router, private notify: NotificationService) { }
username: string;
password: string;
login(): void {
this.authService.login({ password: this.password, username: this.username })
.subscribe(x => {
localStorage.setItem("id_token", x.access_token);
if (this.authService.loggedIn()) {
this.router.navigate(['search']);
} else {
this.notify.error("Could not log in", "Incorrect username or password");
}
}, err => this.notify.error("Could not log in", "Incorrect username or password"));
}
}

@ -1,15 +1,15 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { AuthHttp } from 'angular2-jwt';
import { Observable } from 'rxjs/Rx';
import { ServiceHelpers } from './service.helpers';
import { ServiceAuthHelpers } from './service.helpers';
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
import { IRequestModel } from '../interfaces/IRequestModel';
@Injectable()
export class RequestService extends ServiceHelpers {
constructor(http: Http) {
export class RequestService extends ServiceAuthHelpers {
constructor(http: AuthHttp) {
super(http, '/api/v1/Request/');
}

@ -1,13 +1,13 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { AuthHttp } from 'angular2-jwt';
import { Observable } from 'rxjs/Rx';
import { ServiceHelpers } from './service.helpers';
import { ServiceAuthHelpers } from './service.helpers';
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
@Injectable()
export class SearchService extends ServiceHelpers {
constructor(http: Http) {
export class SearchService extends ServiceAuthHelpers {
constructor(http: AuthHttp) {
super(http, "/api/v1/search");
}

@ -1,6 +1,9 @@
import { Headers, Response, Http } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { AuthHttp } from 'angular2-jwt';
export class ServiceHelpers {
constructor(protected http: Http, protected url: string) {
@ -27,3 +30,30 @@ export class ServiceHelpers {
}
export class ServiceAuthHelpers {
constructor(protected http: AuthHttp, protected url: string) {
this.headers = new Headers();
this.headers.append('Content-Type', 'application/json; charset=utf-8');
}
protected headers: Headers;
protected extractData(res: Response) {
let body = res.json();
//console.log('extractData', body || {});
return body || {};
}
protected handleError(error: any) {
// In a real world app, we might use a remote logging infrastructure
// We'd also dig deeper into the error to get a better message
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
}

@ -3,6 +3,10 @@ import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { AuthService } from '../auth/auth.service';
import { AuthGuard } from '../auth/auth.guard';
import { AuthModule } from '../auth/auth.module';
import { OmbiComponent } from './ombi/ombi.component'
import { SettingsMenuComponent } from './settingsmenu.component';
@ -10,7 +14,7 @@ import { SettingsMenuComponent } from './settingsmenu.component';
import { MenuModule, InputSwitchModule, InputTextModule } from 'primeng/primeng';
const routes: Routes = [
{ path: 'Settings/Ombi', component: OmbiComponent }
{ path: 'Settings/Ombi', component: OmbiComponent, canActivate: [AuthGuard] }
];
@NgModule({
@ -21,7 +25,7 @@ const routes: Routes = [
MenuModule,
InputSwitchModule,
InputTextModule,
AuthModule
],
declarations: [
SettingsMenuComponent,
@ -31,6 +35,9 @@ const routes: Routes = [
RouterModule
],
providers: [
AuthService,
AuthGuard,
],
})

Loading…
Cancel
Save