Add default values for when Sync is off

This commit is contained in:
Amelia 2025-07-01 20:02:37 +02:00
parent 9fb29dec20
commit a122ae07a9
No known key found for this signature in database
GPG key ID: D6D0ECE365407EAA
12 changed files with 203 additions and 42 deletions

View file

@ -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>

View file

@ -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;

View file

@ -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');

View file

@ -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 {

View file

@ -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 {

View file

@ -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>

View file

@ -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

View file

@ -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();
} }

View file

@ -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>

View file

@ -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 => {

View file

@ -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 => {

View file

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