mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 04:49:33 -07:00
Merge pull request #4510 from fservida/sso
SSO with Header Authentication
This commit is contained in:
commit
71aa74ddcc
7 changed files with 146 additions and 58 deletions
|
@ -13,5 +13,8 @@ namespace Ombi.Settings.Settings.Models
|
||||||
public bool RequireNonAlphanumeric { get; set; }
|
public bool RequireNonAlphanumeric { get; set; }
|
||||||
public bool RequireUppercase { get; set; }
|
public bool RequireUppercase { get; set; }
|
||||||
public bool EnableOAuth { get; set; } // Plex OAuth
|
public bool EnableOAuth { get; set; } // Plex OAuth
|
||||||
|
public bool EnableHeaderAuth { get; set; } // Header SSO
|
||||||
|
public string HeaderAuthVariable { get; set; } // Header SSO
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -28,6 +28,10 @@ export class AuthService extends ServiceHelpers {
|
||||||
return this.http.post<boolean>(`${this.url}/requirePassword`, JSON.stringify(login), { headers: this.headers });
|
return this.http.post<boolean>(`${this.url}/requirePassword`, JSON.stringify(login), { headers: this.headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public headerAuth(): Observable<any> {
|
||||||
|
return this.http.post<boolean>(`${this.url}/header_auth`, {}, { headers: this.headers });
|
||||||
|
}
|
||||||
|
|
||||||
public getToken() {
|
public getToken() {
|
||||||
return this.jwtHelperService.tokenGetter();
|
return this.jwtHelperService.tokenGetter();
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,6 +238,8 @@ export interface IAuthenticationSettings extends ISettings {
|
||||||
requireNonAlphanumeric: boolean;
|
requireNonAlphanumeric: boolean;
|
||||||
requireUppercase: boolean;
|
requireUppercase: boolean;
|
||||||
enableOAuth: boolean;
|
enableOAuth: boolean;
|
||||||
|
enableHeaderAuth: boolean;
|
||||||
|
headerAuthVariable: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICustomPage extends ISettings {
|
export interface ICustomPage extends ISettings {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, OnDestroy, OnInit, Inject } from "@angular/core";
|
import { Component, OnDestroy, OnInit, Inject } from "@angular/core";
|
||||||
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
|
||||||
import { ActivatedRoute, Router } from "@angular/router";
|
import { ActivatedRoute, Router } from "@angular/router";
|
||||||
import { TranslateService } from "@ngx-translate/core";
|
import { TranslateService } from "@ngx-translate/core";
|
||||||
|
@ -106,7 +106,7 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
this.settingsService
|
this.settingsService
|
||||||
.getAuthentication()
|
.getAuthentication()
|
||||||
.subscribe((x) => (this.authenticationSettings = x));
|
.subscribe((x) => { this.authenticationSettings = x; this.headerAuth(); });
|
||||||
this.settingsService.getClientId().subscribe((x) => (this.clientId = x));
|
this.settingsService.getClientId().subscribe((x) => (this.clientId = x));
|
||||||
this.images.getRandomBackground().subscribe((x) => {
|
this.images.getRandomBackground().subscribe((x) => {
|
||||||
this.background = this.sanitizer.bypassSecurityTrustStyle(
|
this.background = this.sanitizer.bypassSecurityTrustStyle(
|
||||||
|
@ -121,7 +121,6 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
if (base.length > 1) {
|
if (base.length > 1) {
|
||||||
this.baseUrl = base;
|
this.baseUrl = base;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.translate
|
this.translate
|
||||||
.get("Login.Errors.IncorrectCredentials")
|
.get("Login.Errors.IncorrectCredentials")
|
||||||
.subscribe((x) => (this.errorBody = x));
|
.subscribe((x) => (this.errorBody = x));
|
||||||
|
@ -255,6 +254,31 @@ export class LoginComponent implements OnDestroy, OnInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public headerAuth() {
|
||||||
|
|
||||||
|
if (this.authenticationSettings.enableHeaderAuth) {
|
||||||
|
this.authService.headerAuth().subscribe(
|
||||||
|
(x) => {
|
||||||
|
this.store.save("id_token", x.access_token);
|
||||||
|
|
||||||
|
if (this.authService.loggedIn()) {
|
||||||
|
this.ngOnDestroy();
|
||||||
|
this.router.navigate(["/"]);
|
||||||
|
} else {
|
||||||
|
this.notify.open(this.errorBody, "OK", {
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
this.notify.open(this.errorBody, "OK", {
|
||||||
|
duration: 3000000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
clearInterval(this.timer);
|
clearInterval(this.timer);
|
||||||
clearInterval(this.pinTimer);
|
clearInterval(this.pinTimer);
|
||||||
|
|
|
@ -1,66 +1,81 @@
|
||||||
<settings-menu></settings-menu>
|
<settings-menu></settings-menu>
|
||||||
<div class="small-middle-container">
|
<div class="small-middle-container">
|
||||||
<wiki></wiki>
|
<wiki></wiki>
|
||||||
<fieldset *ngIf="form">
|
<fieldset *ngIf="form">
|
||||||
<legend>Authentication</legend>
|
<legend>Authentication</legend>
|
||||||
<div class="md-form-field" style="margin-top:1em;"></div>
|
<div class="md-form-field" style="margin-top:1em;"></div>
|
||||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<mat-slide-toggle id="allowNoPassword" name="allowNoPassword" formControlName="allowNoPassword">
|
<mat-slide-toggle id="allowNoPassword" name="allowNoPassword" formControlName="allowNoPassword">
|
||||||
Allow users to login without a password</mat-slide-toggle>
|
Allow users to login without a password
|
||||||
</div>
|
</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<mat-slide-toggle id="enableOAuth" name="enableOAuth" formControlName="enableOAuth">Enable Plex OAuth</mat-slide-toggle>
|
<mat-slide-toggle id="enableOAuth" name="enableOAuth" formControlName="enableOAuth">Enable Plex OAuth</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <hr/>
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<div class="checkbox">
|
||||||
<div class="checkbox">
|
<mat-slide-toggle id="enableHeaderAuth" name="enableHeaderAuth" formControlName="enableHeaderAuth">Enable Authentication with Header Variable</mat-slide-toggle>
|
||||||
<input type="checkbox" id="requiredDigit" name="requiredDigit" formControlName="requiredDigit">
|
</div>
|
||||||
<label for="requiredDigit">Require a digit in the password</label>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="requiredLength" class="control-label">Required password length</label>
|
<label for="headerAuthVariable" class="control-label">Header Authentication Variable</label>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" class="form-control form-control-custom " id="requiredLength" name="requiredLength" formControlName="requiredLength">
|
<input type="text" class="form-control form-control-custom " id="headerAuthVariable" name="headerAuthVariable" formControlName="headerAuthVariable">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<!-- <hr/>
|
||||||
<div class="checkbox">
|
<div class="form-group">
|
||||||
<input type="checkbox" id="requiredLowercase" name="requiredLowercase" formControlName="requiredLowercase">
|
<div class="checkbox">
|
||||||
<label for="requiredLowercase">Require a lowercase character in the password</label>
|
<input type="checkbox" id="requiredDigit" name="requiredDigit" formControlName="requiredDigit">
|
||||||
</div>
|
<label for="requiredDigit">Require a digit in the password</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<label for="requiredLength" class="control-label">Required password length</label>
|
||||||
<input type="checkbox" id="requireNonAlphanumeric" name="requireNonAlphanumeric" formControlName="requireNonAlphanumeric">
|
<div>
|
||||||
<label for="requireNonAlphanumeric">Require a NonAlphanumeric character in the password</label>
|
<input type="text" class="form-control form-control-custom " id="requiredLength" name="requiredLength" formControlName="requiredLength">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="checkbox">
|
|
||||||
<input type="checkbox" id="requireUppercase" name="requireUppercase" formControlName="requireUppercase">
|
|
||||||
<label for="requireUppercase">Require a uppercase character in the password</label>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div>
|
<div class="checkbox">
|
||||||
<button mat-raised-button type="submit" color="primary" [disabled]="form.invalid" class="mat-focus-indicator mat-stroked-button accent mat-accent mat-raised-button mat-button-base" ng-reflect-disabled="false">
|
<input type="checkbox" id="requiredLowercase" name="requiredLowercase" formControlName="requiredLowercase">
|
||||||
<span class="mat-button-wrapper">Submit</span><div matripple="" class="mat-ripple mat-button-ripple" ng-reflect-disabled="false" ng-reflect-centered="false" ng-reflect-trigger="[object HTMLButtonElement]"></div>
|
<label for="requiredLowercase">Require a lowercase character in the password</label>
|
||||||
<div class="mat-button-focus-overlay"></div></button><div class="md-form-field" style="margin-top:1em;"></div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" id="requireNonAlphanumeric" name="requireNonAlphanumeric" formControlName="requireNonAlphanumeric">
|
||||||
|
<label for="requireNonAlphanumeric">Require a NonAlphanumeric character in the password</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="checkbox">
|
||||||
|
<input type="checkbox" id="requireUppercase" name="requireUppercase" formControlName="requireUppercase">
|
||||||
|
<label for="requireUppercase">Require a uppercase character in the password</label>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div>
|
||||||
|
<button mat-raised-button type="submit" color="primary" [disabled]="form.invalid" class="mat-focus-indicator mat-stroked-button accent mat-accent mat-raised-button mat-button-base" ng-reflect-disabled="false">
|
||||||
|
<span class="mat-button-wrapper">Submit</span><div matripple="" class="mat-ripple mat-button-ripple" ng-reflect-disabled="false" ng-reflect-centered="false" ng-reflect-trigger="[object HTMLButtonElement]"></div>
|
||||||
|
<div class="mat-button-focus-overlay"></div>
|
||||||
|
</button><div class="md-form-field" style="margin-top:1em;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { FormBuilder, FormGroup } from "@angular/forms";
|
import { FormBuilder, FormGroup } from "@angular/forms";
|
||||||
|
|
||||||
import { NotificationService } from "../../services";
|
import { NotificationService } from "../../services";
|
||||||
|
@ -26,6 +26,8 @@ export class AuthenticationComponent implements OnInit {
|
||||||
requireNonAlphanumeric: [x.requireNonAlphanumeric],
|
requireNonAlphanumeric: [x.requireNonAlphanumeric],
|
||||||
requireUppercase: [x.requireUppercase],
|
requireUppercase: [x.requireUppercase],
|
||||||
enableOAuth: [x.enableOAuth],
|
enableOAuth: [x.enableOAuth],
|
||||||
|
enableHeaderAuth: [x.enableHeaderAuth],
|
||||||
|
headerAuthVariable: [x.headerAuthVariable],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ using Ombi.Models.External;
|
||||||
using Ombi.Models.Identity;
|
using Ombi.Models.Identity;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
using Ombi.Store.Repository;
|
using Ombi.Store.Repository;
|
||||||
|
using Ombi.Core.Settings;
|
||||||
|
using Ombi.Settings.Settings.Models;
|
||||||
|
|
||||||
namespace Ombi.Controllers.V1
|
namespace Ombi.Controllers.V1
|
||||||
{
|
{
|
||||||
|
@ -25,13 +27,14 @@ namespace Ombi.Controllers.V1
|
||||||
public class TokenController : ControllerBase
|
public class TokenController : ControllerBase
|
||||||
{
|
{
|
||||||
public TokenController(OmbiUserManager um, IOptions<TokenAuthentication> ta, ITokenRepository token,
|
public TokenController(OmbiUserManager um, IOptions<TokenAuthentication> ta, ITokenRepository token,
|
||||||
IPlexOAuthManager oAuthManager, ILogger<TokenController> logger)
|
IPlexOAuthManager oAuthManager, ILogger<TokenController> logger, ISettingsService<AuthenticationSettings> auth)
|
||||||
{
|
{
|
||||||
_userManager = um;
|
_userManager = um;
|
||||||
_tokenAuthenticationOptions = ta.Value;
|
_tokenAuthenticationOptions = ta.Value;
|
||||||
_token = token;
|
_token = token;
|
||||||
_plexOAuthManager = oAuthManager;
|
_plexOAuthManager = oAuthManager;
|
||||||
_log = logger;
|
_log = logger;
|
||||||
|
_authSettings = auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly TokenAuthentication _tokenAuthenticationOptions;
|
private readonly TokenAuthentication _tokenAuthenticationOptions;
|
||||||
|
@ -39,6 +42,7 @@ namespace Ombi.Controllers.V1
|
||||||
private readonly OmbiUserManager _userManager;
|
private readonly OmbiUserManager _userManager;
|
||||||
private readonly IPlexOAuthManager _plexOAuthManager;
|
private readonly IPlexOAuthManager _plexOAuthManager;
|
||||||
private readonly ILogger<TokenController> _log;
|
private readonly ILogger<TokenController> _log;
|
||||||
|
private readonly ISettingsService<AuthenticationSettings> _authSettings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the token.
|
/// Gets the token.
|
||||||
|
@ -272,5 +276,39 @@ namespace Ombi.Controllers.V1
|
||||||
|
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("header_auth")]
|
||||||
|
[ProducesResponseType(401)]
|
||||||
|
public async Task<IActionResult> HeaderAuth()
|
||||||
|
{
|
||||||
|
string username = null;
|
||||||
|
|
||||||
|
var authSettings = await _authSettings.GetSettingsAsync();
|
||||||
|
_log.LogInformation("Logging with header: " + authSettings.HeaderAuthVariable);
|
||||||
|
if (authSettings.HeaderAuthVariable != null && authSettings.EnableHeaderAuth)
|
||||||
|
{
|
||||||
|
if (Request.HttpContext?.Request?.Headers != null && Request.HttpContext.Request.Headers.ContainsKey(authSettings.HeaderAuthVariable))
|
||||||
|
{
|
||||||
|
username = Request.HttpContext.Request.Headers[authSettings.HeaderAuthVariable].ToString();
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
var user = await _userManager.FindByNameAsync(username);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return new UnauthorizedResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await CreateToken(true, user);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new UnauthorizedResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new UnauthorizedResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue