Add default values for when Sync is off
This commit is contained in:
parent
9fb29dec20
commit
a122ae07a9
12 changed files with 203 additions and 42 deletions
|
|
@ -1,5 +1,8 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using API.Entities.Enums;
|
||||||
|
|
||||||
namespace API.DTOs.Settings;
|
namespace API.DTOs.Settings;
|
||||||
|
|
||||||
public record OidcConfigDto: OidcPublicConfigDto
|
public record OidcConfigDto: OidcPublicConfigDto
|
||||||
|
|
@ -17,6 +20,17 @@ public record OidcConfigDto: OidcPublicConfigDto
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SyncUserSettings { get; set; }
|
public bool SyncUserSettings { get; set; }
|
||||||
|
|
||||||
|
// Default values used when SyncUserSettings is false
|
||||||
|
#region Default user settings
|
||||||
|
|
||||||
|
public List<string> DefaultRoles { get; set; } = [];
|
||||||
|
public List<int> DefaultLibraries { get; set; } = [];
|
||||||
|
public AgeRating DefaultAgeRating { get; set; } = AgeRating.Unknown;
|
||||||
|
public bool DefaultIncludeUnknowns { get; set; } = false;
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the <see cref="OidcPublicConfigDto.Authority"/> has been set
|
/// Returns true if the <see cref="OidcPublicConfigDto.Authority"/> has been set
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -56,10 +57,7 @@ public class OidcService(ILogger<OidcService> logger, UserManager<AppUser> userM
|
||||||
throw new KavitaException("errors.oidc.missing-external-id");
|
throw new KavitaException("errors.oidc.missing-external-id");
|
||||||
|
|
||||||
var user = await unitOfWork.UserRepository.GetByExternalId(externalId, AppUserIncludes.UserPreferences);
|
var user = await unitOfWork.UserRepository.GetByExternalId(externalId, AppUserIncludes.UserPreferences);
|
||||||
if (user != null)
|
if (user != null) return user;
|
||||||
{
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
|
|
||||||
var email = principal.FindFirstValue(ClaimTypes.Email);
|
var email = principal.FindFirstValue(ClaimTypes.Email);
|
||||||
if (string.IsNullOrEmpty(email))
|
if (string.IsNullOrEmpty(email))
|
||||||
|
|
@ -68,18 +66,23 @@ public class OidcService(ILogger<OidcService> logger, UserManager<AppUser> userM
|
||||||
if (settings.RequireVerifiedEmail && !principal.HasVerifiedEmail())
|
if (settings.RequireVerifiedEmail && !principal.HasVerifiedEmail())
|
||||||
throw new KavitaException("errors.oidc.email-not-verified");
|
throw new KavitaException("errors.oidc.email-not-verified");
|
||||||
|
|
||||||
|
|
||||||
|
user = await unitOfWork.UserRepository.GetUserByEmailAsync(email, AppUserIncludes.UserPreferences | AppUserIncludes.SideNavStreams);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
logger.LogInformation("User {Name} has matched on email to {ExternalId}", user.UserName, externalId);
|
||||||
|
user.ExternalId = externalId;
|
||||||
|
await unitOfWork.CommitAsync();
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot match on native account, try and create new one
|
||||||
if (settings.SyncUserSettings && principal.GetAccessRoles().Count == 0)
|
if (settings.SyncUserSettings && principal.GetAccessRoles().Count == 0)
|
||||||
throw new KavitaException("errors.oidc.role-not-assigned");
|
throw new KavitaException("errors.oidc.role-not-assigned");
|
||||||
|
|
||||||
|
user = await NewUserFromOpenIdConnect(settings, principal, externalId);
|
||||||
user = await unitOfWork.UserRepository.GetUserByEmailAsync(email, AppUserIncludes.UserPreferences | AppUserIncludes.SideNavStreams)
|
|
||||||
?? await NewUserFromOpenIdConnect(settings, principal);
|
|
||||||
if (user == null) return null;
|
if (user == null) return null;
|
||||||
|
|
||||||
user.ExternalId = externalId;
|
|
||||||
|
|
||||||
await SyncUserSettings(settings, principal, user);
|
|
||||||
|
|
||||||
var roles = await userManager.GetRolesAsync(user);
|
var roles = await userManager.GetRolesAsync(user);
|
||||||
if (roles.Count > 0 && !roles.Contains(PolicyConstants.LoginRole))
|
if (roles.Count > 0 && !roles.Contains(PolicyConstants.LoginRole))
|
||||||
throw new KavitaException("errors.oidc.disabled-account");
|
throw new KavitaException("errors.oidc.disabled-account");
|
||||||
|
|
@ -98,14 +101,15 @@ public class OidcService(ILogger<OidcService> logger, UserManager<AppUser> userM
|
||||||
await unitOfWork.CommitAsync();
|
await unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<AppUser?> NewUserFromOpenIdConnect(OidcConfigDto settings, ClaimsPrincipal claimsPrincipal)
|
private async Task<AppUser?> NewUserFromOpenIdConnect(OidcConfigDto settings, ClaimsPrincipal claimsPrincipal, string externalId)
|
||||||
{
|
{
|
||||||
if (!settings.ProvisionAccounts) return null;
|
if (!settings.ProvisionAccounts) return null;
|
||||||
|
|
||||||
var emailClaim = claimsPrincipal.FindFirst(ClaimTypes.Email);
|
var emailClaim = claimsPrincipal.FindFirst(ClaimTypes.Email);
|
||||||
if (emailClaim == null || string.IsNullOrWhiteSpace(emailClaim.Value)) return null;
|
if (emailClaim == null || string.IsNullOrWhiteSpace(emailClaim.Value)) return null;
|
||||||
|
|
||||||
var name = claimsPrincipal.FindFirstValue(ClaimTypes.Name);
|
var name = claimsPrincipal.FindFirstValue(JwtRegisteredClaimNames.PreferredUsername);
|
||||||
|
name ??= claimsPrincipal.FindFirstValue(ClaimTypes.Name);
|
||||||
name ??= claimsPrincipal.FindFirstValue(ClaimTypes.GivenName);
|
name ??= claimsPrincipal.FindFirstValue(ClaimTypes.GivenName);
|
||||||
name ??= claimsPrincipal.FindFirstValue(ClaimTypes.Surname);
|
name ??= claimsPrincipal.FindFirstValue(ClaimTypes.Surname);
|
||||||
name ??= emailClaim.Value;
|
name ??= emailClaim.Value;
|
||||||
|
|
@ -117,6 +121,8 @@ public class OidcService(ILogger<OidcService> logger, UserManager<AppUser> userM
|
||||||
name = emailClaim.Value;
|
name = emailClaim.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Creating new user from OIDC: {Name} - {ExternalId}", name, externalId);
|
||||||
|
|
||||||
// TODO: Move to account service, as we're sharing code with AccountController
|
// TODO: Move to account service, as we're sharing code with AccountController
|
||||||
var user = new AppUserBuilder(name, emailClaim.Value,
|
var user = new AppUserBuilder(name, emailClaim.Value,
|
||||||
await unitOfWork.SiteThemeRepository.GetDefaultTheme()).Build();
|
await unitOfWork.SiteThemeRepository.GetDefaultTheme()).Build();
|
||||||
|
|
@ -129,10 +135,6 @@ public class OidcService(ILogger<OidcService> logger, UserManager<AppUser> userM
|
||||||
throw new KavitaException("errors.oidc.creating-user");
|
throw new KavitaException("errors.oidc.creating-user");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Owner = AppUserOwner.OpenIdConnect;
|
|
||||||
AddDefaultStreamsToUser(user, mapper);
|
|
||||||
await AddDefaultReadingProfileToUser(user);
|
|
||||||
|
|
||||||
if (settings.RequireVerifiedEmail)
|
if (settings.RequireVerifiedEmail)
|
||||||
{
|
{
|
||||||
// Email has been verified by OpenID Connect provider
|
// Email has been verified by OpenID Connect provider
|
||||||
|
|
@ -140,13 +142,36 @@ public class OidcService(ILogger<OidcService> logger, UserManager<AppUser> userM
|
||||||
await userManager.ConfirmEmailAsync(user, token);
|
await userManager.ConfirmEmailAsync(user, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
await userManager.AddToRoleAsync(user, PolicyConstants.LoginRole);
|
user.ExternalId = externalId;
|
||||||
await userManager.AddToRoleAsync(user, PolicyConstants.PlebRole);
|
user.Owner = AppUserOwner.OpenIdConnect;
|
||||||
|
|
||||||
|
AddDefaultStreamsToUser(user, mapper);
|
||||||
|
await AddDefaultReadingProfileToUser(user);
|
||||||
|
await SyncUserSettings(settings, claimsPrincipal, user);
|
||||||
|
await SetDefaults(settings, user);
|
||||||
|
|
||||||
await unitOfWork.CommitAsync();
|
await unitOfWork.CommitAsync();
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SetDefaults(OidcConfigDto settings, AppUser user)
|
||||||
|
{
|
||||||
|
if (settings.SyncUserSettings) return;
|
||||||
|
|
||||||
|
// Assign roles
|
||||||
|
var errors = await accountService.UpdateRolesForUser(user, settings.DefaultRoles);
|
||||||
|
if (errors.Any()) throw new KavitaException("errors.oidc.syncing-user");
|
||||||
|
|
||||||
|
// Assign libraries
|
||||||
|
await accountService.UpdateLibrariesForUser(user, settings.DefaultLibraries, settings.DefaultRoles.Contains(PolicyConstants.AdminRole));
|
||||||
|
|
||||||
|
// Assign age rating
|
||||||
|
user.AgeRestriction = settings.DefaultAgeRating;
|
||||||
|
user.AgeRestrictionIncludeUnknowns = settings.DefaultIncludeUnknowns;
|
||||||
|
|
||||||
|
await unitOfWork.CommitAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SyncUserSettings(OidcConfigDto settings, ClaimsPrincipal claimsPrincipal, AppUser user)
|
public async Task SyncUserSettings(OidcConfigDto settings, ClaimsPrincipal claimsPrincipal, AppUser user)
|
||||||
{
|
{
|
||||||
if (!settings.SyncUserSettings) return;
|
if (!settings.SyncUserSettings) return;
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,17 @@ export class AgeRatingPipe implements PipeTransform {
|
||||||
|
|
||||||
private readonly translocoService = inject(TranslocoService);
|
private readonly translocoService = inject(TranslocoService);
|
||||||
|
|
||||||
transform(value: AgeRating | AgeRatingDto | undefined): string {
|
transform(value: AgeRating | AgeRatingDto | undefined | string): string {
|
||||||
if (value === undefined || value === null) return this.translocoService.translate('age-rating-pipe.unknown');
|
if (value === undefined || value === null) return this.translocoService.translate('age-rating-pipe.unknown');
|
||||||
|
|
||||||
if (value.hasOwnProperty('title')) {
|
if (value.hasOwnProperty('title')) {
|
||||||
return (value as AgeRatingDto).title;
|
return (value as AgeRatingDto).title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
value = parseInt(value, 10) as AgeRating;
|
||||||
|
}
|
||||||
|
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case AgeRating.Unknown:
|
case AgeRating.Unknown:
|
||||||
return this.translocoService.translate('age-rating-pipe.unknown');
|
return this.translocoService.translate('age-rating-pipe.unknown');
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {UserOwner} from "../_models/user";
|
||||||
import {translate} from "@jsverse/transloco";
|
import {translate} from "@jsverse/transloco";
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'creationSourcePipe'
|
name: 'userOwnerPipe'
|
||||||
})
|
})
|
||||||
export class UserOwnerPipe implements PipeTransform {
|
export class UserOwnerPipe implements PipeTransform {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {AgeRating} from "../../_models/metadata/age-rating";
|
||||||
|
|
||||||
export interface OidcConfig {
|
export interface OidcConfig {
|
||||||
authority: string;
|
authority: string;
|
||||||
|
|
@ -8,6 +9,10 @@ export interface OidcConfig {
|
||||||
autoLogin: boolean;
|
autoLogin: boolean;
|
||||||
disablePasswordAuthentication: boolean;
|
disablePasswordAuthentication: boolean;
|
||||||
providerName: string;
|
providerName: string;
|
||||||
|
defaultRoles: string[];
|
||||||
|
defaultLibraries: number[];
|
||||||
|
defaultAgeRating: AgeRating;
|
||||||
|
defaultIncludeUnknowns: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OidcPublicConfig {
|
export interface OidcPublicConfig {
|
||||||
|
|
|
||||||
|
|
@ -78,12 +78,12 @@
|
||||||
@if (userForm.get('owner'); as formControl) {
|
@if (userForm.get('owner'); as formControl) {
|
||||||
<app-setting-item [title]="t('owner')" [subtitle]="t('owner-tooltip')">
|
<app-setting-item [title]="t('owner')" [subtitle]="t('owner-tooltip')">
|
||||||
<ng-template #view>
|
<ng-template #view>
|
||||||
<div>{{member().owner | UserOwnerPipe}}</div>
|
<div>{{member().owner | userOwnerPipe}}</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template #edit>
|
<ng-template #edit>
|
||||||
<select class="form-select" id="creationSource" formControlName="creationSource">
|
<select class="form-select" id="creationSource" formControlName="creationSource">
|
||||||
@for (source of UserOwners; track source) {
|
@for (source of UserOwners; track source) {
|
||||||
<option [value]="source">{{source | UserOwnerPipe}}</option>
|
<option [value]="source">{{source | userOwnerPipe}}</option>
|
||||||
}
|
}
|
||||||
</select>
|
</select>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ export class EditUserComponent implements OnInit {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.userForm.addControl('email', new FormControl(this.member().email, [Validators.required]));
|
this.userForm.addControl('email', new FormControl(this.member().email, [Validators.required]));
|
||||||
this.userForm.addControl('username', new FormControl(this.member().username, [Validators.required, Validators.pattern(AllowedUsernameCharacters)]));
|
this.userForm.addControl('username', new FormControl(this.member().username, [Validators.required, Validators.pattern(AllowedUsernameCharacters)]));
|
||||||
this.userForm.addControl('creationSource', new FormControl(this.member().owner, [Validators.required]));
|
this.userForm.addControl('owner', new FormControl(this.member().owner, [Validators.required]));
|
||||||
|
|
||||||
// TODO: Rework, bad hack
|
// TODO: Rework, bad hack
|
||||||
// Work around isLocked so we're able to downgrade users
|
// Work around isLocked so we're able to downgrade users
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
inject,
|
inject, input,
|
||||||
Input,
|
Input,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output
|
Output
|
||||||
|
|
@ -29,6 +29,7 @@ export class LibrarySelectorComponent implements OnInit {
|
||||||
private readonly cdRef = inject(ChangeDetectorRef);
|
private readonly cdRef = inject(ChangeDetectorRef);
|
||||||
|
|
||||||
@Input() member: Member | undefined;
|
@Input() member: Member | undefined;
|
||||||
|
preselectedLibraries = input<number[]>([]);
|
||||||
@Output() selected: EventEmitter<Array<Library>> = new EventEmitter<Array<Library>>();
|
@Output() selected: EventEmitter<Array<Library>> = new EventEmitter<Array<Library>>();
|
||||||
|
|
||||||
allLibraries: Library[] = [];
|
allLibraries: Library[] = [];
|
||||||
|
|
@ -61,6 +62,14 @@ export class LibrarySelectorComponent implements OnInit {
|
||||||
});
|
});
|
||||||
this.selectAll = this.selections.selected().length === this.allLibraries.length;
|
this.selectAll = this.selections.selected().length === this.allLibraries.length;
|
||||||
this.selected.emit(this.selections.selected());
|
this.selected.emit(this.selections.selected());
|
||||||
|
} else if (this.preselectedLibraries().length > 0) {
|
||||||
|
this.preselectedLibraries().forEach((id) => {
|
||||||
|
const foundLib = this.allLibraries.find(lib => lib.id === id);
|
||||||
|
if (foundLib) {
|
||||||
|
this.selections.toggle(foundLib, true, (a, b) => a.name === b.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.selectAll = this.selections.selected().length === this.allLibraries.length;
|
||||||
}
|
}
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,55 @@
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
<div class="setting-section-break"></div>
|
||||||
|
<h4>{{t('defaults')}}</h4>
|
||||||
|
<div class="text-muted">{{t('defaults-requirement')}}</div>
|
||||||
|
<ng-container>
|
||||||
|
|
||||||
|
<div class="row g-0 mt-4 mb-4">
|
||||||
|
@if(settingsForm.get('defaultAgeRating'); as formControl) {
|
||||||
|
<app-setting-item [title]="t('defaultAgeRating')" [subtitle]="t('defaultAgeRating-tooltip')">
|
||||||
|
<ng-template #view>
|
||||||
|
<div>{{formControl.value | ageRating}}</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #edit>
|
||||||
|
<select class="form-select" formControlName="defaultAgeRating">
|
||||||
|
<option value="-1">{{t('no-restriction')}}</option>
|
||||||
|
@for (ageRating of ageRatings(); track ageRating.value) {
|
||||||
|
<option [value]="ageRating.value">{{ageRating.title}}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</ng-template>
|
||||||
|
</app-setting-item>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-0 mt-4 mb-4">
|
||||||
|
@if(settingsForm.get('defaultIncludeUnknowns'); as formControl) {
|
||||||
|
<app-setting-switch [title]="t('defaultIncludeUnknowns')" [subtitle]="t('defaultIncludeUnknowns-tooltip')">
|
||||||
|
<ng-template #switch>
|
||||||
|
<div class="form-check form-switch float-end">
|
||||||
|
<input id="defaultIncludeUnknowns" type="checkbox" class="form-check-input" formControlName="defaultIncludeUnknowns">
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</app-setting-switch>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (this.oidcSettings()) {
|
||||||
|
<div class="row g-0 mb-3">
|
||||||
|
<div class="col-md-6 pe-4">
|
||||||
|
<app-role-selector (selected)="updateRoles($event)" [allowAdmin]="true" [preSelectedRoles]="selectedRoles()"></app-role-selector>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<app-library-selector (selected)="updateLibraries($event)" [preselectedLibraries]="selectedLibraries()"></app-library-selector>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {ChangeDetectorRef, Component, DestroyRef, OnInit} from '@angular/core';
|
import {ChangeDetectorRef, Component, DestroyRef, effect, OnInit, signal} from '@angular/core';
|
||||||
import {TranslocoDirective} from "@jsverse/transloco";
|
import {TranslocoDirective} from "@jsverse/transloco";
|
||||||
import {ServerSettings} from "../_models/server-settings";
|
import {ServerSettings} from "../_models/server-settings";
|
||||||
import {
|
import {
|
||||||
|
|
@ -16,6 +16,16 @@ import {SettingItemComponent} from "../../settings/_components/setting-item/sett
|
||||||
import {SettingSwitchComponent} from "../../settings/_components/setting-switch/setting-switch.component";
|
import {SettingSwitchComponent} from "../../settings/_components/setting-switch/setting-switch.component";
|
||||||
import {debounceTime, distinctUntilChanged, filter, map, of, switchMap, tap} from "rxjs";
|
import {debounceTime, distinctUntilChanged, filter, map, of, switchMap, tap} from "rxjs";
|
||||||
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
|
import {RestrictionSelectorComponent} from "../../user-settings/restriction-selector/restriction-selector.component";
|
||||||
|
import {AgeRatingPipe} from "../../_pipes/age-rating.pipe";
|
||||||
|
import {MetadataService} from "../../_services/metadata.service";
|
||||||
|
import {AgeRating} from "../../_models/metadata/age-rating";
|
||||||
|
import {AgeRatingDto} from "../../_models/metadata/age-rating-dto";
|
||||||
|
import {allRoles, Role} from "../../_services/account.service";
|
||||||
|
import {Library} from "../../_models/library/library";
|
||||||
|
import {LibraryService} from "../../_services/library.service";
|
||||||
|
import {LibrarySelectorComponent} from "../library-selector/library-selector.component";
|
||||||
|
import {RoleSelectorComponent} from "../role-selector/role-selector.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-manage-open-idconnect',
|
selector: 'app-manage-open-idconnect',
|
||||||
|
|
@ -23,7 +33,10 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
TranslocoDirective,
|
TranslocoDirective,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
SettingItemComponent,
|
SettingItemComponent,
|
||||||
SettingSwitchComponent
|
SettingSwitchComponent,
|
||||||
|
AgeRatingPipe,
|
||||||
|
LibrarySelectorComponent,
|
||||||
|
RoleSelectorComponent
|
||||||
],
|
],
|
||||||
templateUrl: './manage-open-idconnect.component.html',
|
templateUrl: './manage-open-idconnect.component.html',
|
||||||
styleUrl: './manage-open-idconnect.component.scss'
|
styleUrl: './manage-open-idconnect.component.scss'
|
||||||
|
|
@ -31,30 +44,43 @@ import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
|
||||||
export class ManageOpenIDConnectComponent implements OnInit {
|
export class ManageOpenIDConnectComponent implements OnInit {
|
||||||
|
|
||||||
serverSettings!: ServerSettings;
|
serverSettings!: ServerSettings;
|
||||||
oidcSettings!: OidcConfig;
|
oidcSettings = signal<OidcConfig | undefined>(undefined);
|
||||||
settingsForm: FormGroup = new FormGroup({});
|
settingsForm: FormGroup = new FormGroup({});
|
||||||
|
|
||||||
|
ageRatings = signal<AgeRatingDto[]>([]);
|
||||||
|
selectedLibraries = signal<number[]>([]);
|
||||||
|
selectedRoles = signal<string[]>([]);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private settingsService: SettingsService,
|
private settingsService: SettingsService,
|
||||||
private cdRef: ChangeDetectorRef,
|
private cdRef: ChangeDetectorRef,
|
||||||
private destroyRef: DestroyRef,
|
private destroyRef: DestroyRef,
|
||||||
|
private metadataService: MetadataService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.metadataService.getAllAgeRatings().subscribe(ratings => {
|
||||||
|
this.ageRatings.set(ratings);
|
||||||
|
});
|
||||||
|
|
||||||
this.settingsService.getServerSettings().subscribe({
|
this.settingsService.getServerSettings().subscribe({
|
||||||
next: data => {
|
next: data => {
|
||||||
this.serverSettings = data;
|
this.serverSettings = data;
|
||||||
this.oidcSettings = this.serverSettings.oidcConfig;
|
this.oidcSettings.set(this.serverSettings.oidcConfig);
|
||||||
|
this.selectedRoles.set(this.serverSettings.oidcConfig.defaultRoles);
|
||||||
|
this.selectedLibraries.set(this.serverSettings.oidcConfig.defaultLibraries);
|
||||||
|
|
||||||
this.settingsForm.addControl('authority', new FormControl(this.oidcSettings.authority, [], [this.authorityValidator()]));
|
this.settingsForm.addControl('authority', new FormControl(this.serverSettings.oidcConfig.authority, [], [this.authorityValidator()]));
|
||||||
this.settingsForm.addControl('clientId', new FormControl(this.oidcSettings.clientId, [this.requiredIf('authority')]));
|
this.settingsForm.addControl('clientId', new FormControl(this.serverSettings.oidcConfig.clientId, [this.requiredIf('authority')]));
|
||||||
this.settingsForm.addControl('provisionAccounts', new FormControl(this.oidcSettings.provisionAccounts, []));
|
this.settingsForm.addControl('provisionAccounts', new FormControl(this.serverSettings.oidcConfig.provisionAccounts, []));
|
||||||
this.settingsForm.addControl('requireVerifiedEmail', new FormControl(this.oidcSettings.requireVerifiedEmail, []));
|
this.settingsForm.addControl('requireVerifiedEmail', new FormControl(this.serverSettings.oidcConfig.requireVerifiedEmail, []));
|
||||||
this.settingsForm.addControl('syncUserSettings', new FormControl(this.oidcSettings.syncUserSettings, []));
|
this.settingsForm.addControl('syncUserSettings', new FormControl(this.serverSettings.oidcConfig.syncUserSettings, []));
|
||||||
this.settingsForm.addControl('autoLogin', new FormControl(this.oidcSettings.autoLogin, []));
|
this.settingsForm.addControl('autoLogin', new FormControl(this.serverSettings.oidcConfig.autoLogin, []));
|
||||||
this.settingsForm.addControl('disablePasswordAuthentication', new FormControl(this.oidcSettings.disablePasswordAuthentication, []));
|
this.settingsForm.addControl('disablePasswordAuthentication', new FormControl(this.serverSettings.oidcConfig.disablePasswordAuthentication, []));
|
||||||
this.settingsForm.addControl('providerName', new FormControl(this.oidcSettings.providerName, []));
|
this.settingsForm.addControl('providerName', new FormControl(this.serverSettings.oidcConfig.providerName, []));
|
||||||
|
this.settingsForm.addControl("defaultAgeRating", new FormControl(this.serverSettings.oidcConfig.defaultAgeRating, []));
|
||||||
|
this.settingsForm.addControl('defaultIncludeUnknowns', new FormControl(this.serverSettings.oidcConfig.defaultIncludeUnknowns, []));
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
|
|
||||||
this.settingsForm.valueChanges.pipe(
|
this.settingsForm.valueChanges.pipe(
|
||||||
|
|
@ -64,7 +90,7 @@ export class ManageOpenIDConnectComponent implements OnInit {
|
||||||
filter(() => {
|
filter(() => {
|
||||||
// Do not auto save when provider settings have changed
|
// Do not auto save when provider settings have changed
|
||||||
const settings: OidcConfig = this.settingsForm.getRawValue();
|
const settings: OidcConfig = this.settingsForm.getRawValue();
|
||||||
return settings.authority == this.oidcSettings.authority && settings.clientId == this.oidcSettings.clientId;
|
return settings.authority == this.oidcSettings()?.authority && settings.clientId == this.oidcSettings()?.clientId;
|
||||||
}),
|
}),
|
||||||
tap(() => this.save())
|
tap(() => this.save())
|
||||||
).subscribe();
|
).subscribe();
|
||||||
|
|
@ -72,17 +98,30 @@ export class ManageOpenIDConnectComponent implements OnInit {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateRoles(roles: string[]) {
|
||||||
|
this.selectedRoles.set(roles);
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLibraries(libraries: Library[]) {
|
||||||
|
this.selectedLibraries.set(libraries.map(l => l.id));
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
if (!this.settingsForm.valid) return;
|
if (!this.settingsForm.valid || !this.serverSettings || !this.oidcSettings) return;
|
||||||
|
|
||||||
const data = this.settingsForm.getRawValue();
|
const data = this.settingsForm.getRawValue();
|
||||||
const newSettings = Object.assign({}, this.serverSettings);
|
const newSettings = Object.assign({}, this.serverSettings);
|
||||||
newSettings.oidcConfig = data as OidcConfig;
|
newSettings.oidcConfig = data as OidcConfig;
|
||||||
|
newSettings.oidcConfig.defaultAgeRating = parseInt(newSettings.oidcConfig.defaultAgeRating as unknown as string, 10) as AgeRating;
|
||||||
|
newSettings.oidcConfig.defaultRoles = this.selectedRoles();
|
||||||
|
newSettings.oidcConfig.defaultLibraries = this.selectedLibraries();
|
||||||
|
|
||||||
this.settingsService.updateServerSettings(newSettings).subscribe({
|
this.settingsService.updateServerSettings(newSettings).subscribe({
|
||||||
next: data => {
|
next: data => {
|
||||||
this.serverSettings = data;
|
this.serverSettings = data;
|
||||||
this.oidcSettings = data.oidcConfig;
|
this.oidcSettings.set(data.oidcConfig);
|
||||||
this.cdRef.markForCheck();
|
this.cdRef.markForCheck();
|
||||||
},
|
},
|
||||||
error: error => {
|
error: error => {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
inject,
|
inject, input,
|
||||||
Input,
|
Input,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output
|
Output
|
||||||
|
|
@ -33,6 +33,7 @@ export class RoleSelectorComponent implements OnInit {
|
||||||
* This must have roles
|
* This must have roles
|
||||||
*/
|
*/
|
||||||
@Input() member: Member | undefined | User;
|
@Input() member: Member | undefined | User;
|
||||||
|
preSelectedRoles = input<string[]>([]);
|
||||||
/**
|
/**
|
||||||
* Allows the selection of Admin role
|
* Allows the selection of Admin role
|
||||||
*/
|
*/
|
||||||
|
|
@ -77,6 +78,13 @@ export class RoleSelectorComponent implements OnInit {
|
||||||
foundRole[0].selected = true;
|
foundRole[0].selected = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (this.preSelectedRoles().length > 0) {
|
||||||
|
this.preSelectedRoles().forEach((role) => {
|
||||||
|
const foundRole = this.selectedRoles.filter(item => item.data === role);
|
||||||
|
if (foundRole.length > 0) {
|
||||||
|
foundRole[0].selected = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// For new users, preselect LoginRole
|
// For new users, preselect LoginRole
|
||||||
this.selectedRoles.forEach(role => {
|
this.selectedRoles.forEach(role => {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,15 @@
|
||||||
"disablePasswordAuthentication": "Disable password authentication",
|
"disablePasswordAuthentication": "Disable password authentication",
|
||||||
"disablePasswordAuthentication-tooltip": "Users with the admin role can bypass this restriction",
|
"disablePasswordAuthentication-tooltip": "Users with the admin role can bypass this restriction",
|
||||||
"providerName": "Provider name",
|
"providerName": "Provider name",
|
||||||
"providerName-tooltip": "Name show on the login screen"
|
"providerName-tooltip": "Name show on the login screen",
|
||||||
|
|
||||||
|
"defaults": "Defaults",
|
||||||
|
"defaults-requirement": "The following settings are used when a user is registered via OIDC while SyncUserSettings is turned off",
|
||||||
|
"defaultIncludeUnknowns": "Include unknowns",
|
||||||
|
"defaultIncludeUnknowns-tooltip": "Include unknown age ratings",
|
||||||
|
"defaultAgeRating": "Age rating",
|
||||||
|
"defaultAgeRating-tooltip": "Maximum age rating shown to new users",
|
||||||
|
"no-restriction": "{{restriction-selector.no-restriction}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue