Disable password auth setting

This commit is contained in:
Amelia 2025-06-30 15:54:36 +02:00
parent 1180d518a2
commit 188020597c
No known key found for this signature in database
GPG key ID: D6D0ECE365407EAA
20 changed files with 164 additions and 75 deletions

View file

@ -13,7 +13,7 @@ import {UserUpdateEvent} from '../_models/events/user-update-event';
import {AgeRating} from '../_models/metadata/age-rating';
import {AgeRestriction} from '../_models/metadata/age-restriction';
import {TextResonse} from '../_types/text-response';
import {takeUntilDestroyed} from "@angular/core/rxjs-interop";
import {takeUntilDestroyed, toSignal} from "@angular/core/rxjs-interop";
import {Action} from "./action-factory.service";
import {LicenseService} from "./license.service";
import {LocalizationService} from "./localization.service";
@ -63,6 +63,8 @@ export class AccountService {
return this.hasAdminRole(u);
}), shareReplay({bufferSize: 1, refCount: true}));
public currentUserSignal = toSignal(this.currentUserSource);
/**

View file

@ -91,7 +91,9 @@ export class OidcService {
}
logout() {
this.oauth2.logOut();
if (this.token) {
this.oauth2.logOut();
}
}
config() {

View file

@ -6,10 +6,12 @@ export interface OidcConfig {
requireVerifiedEmail: boolean;
provisionUserSettings: boolean;
autoLogin: boolean;
disablePasswordAuthentication: boolean;
}
export interface OidcPublicConfig {
authority: string;
clientId: string;
autoLogin: boolean;
disablePasswordAuthentication: boolean;
}

View file

@ -112,6 +112,18 @@
}
</div>
<div class="row g-0 mt-4 mb-4">
@if(settingsForm.get('disablePasswordAuthentication'); as formControl) {
<app-setting-switch [title]="t('disablePasswordAuthentication')" [subtitle]="t('disablePasswordAuthentication-tooltip')">
<ng-template #switch>
<div class="form-check form-switch float-end">
<input id="disablePasswordAuthentication" type="checkbox" class="form-check-input" formControlName="disablePasswordAuthentication">
</div>
</ng-template>
</app-setting-switch>
}
</div>
</ng-container>
</form>

View file

@ -51,6 +51,7 @@ export class ManageOpenIDConnectComponent implements OnInit {
this.settingsForm.addControl('requireVerifiedEmail', new FormControl(this.oidcSettings.requireVerifiedEmail, []));
this.settingsForm.addControl('provisionUserSettings', new FormControl(this.oidcSettings.provisionUserSettings, []));
this.settingsForm.addControl('autoLogin', new FormControl(this.oidcSettings.autoLogin, []));
this.settingsForm.addControl('disablePasswordAuthentication', new FormControl(this.oidcSettings.disablePasswordAuthentication, []));
this.cdRef.markForCheck();
}
})

View file

@ -103,7 +103,8 @@ export class AppComponent implements OnInit {
// Login automatically when a token is available
effect(() => {
const ready = this.oidcService.ready();
if (!ready || !this.oidcService.token) return;
const user = this.accountService.currentUserSignal();
if (!ready || !this.oidcService.token || user) return;
this.accountService.loginByToken(this.oidcService.token).subscribe({
next: () => {

View file

@ -1,37 +1,58 @@
<ng-container *transloco="let t; read: 'login'">
<ng-container *transloco="let t; prefix: 'login'">
<app-splash-container>
<ng-container title><h2>{{t('title')}}</h2></ng-container>
<ng-container body>
<ng-container *ngIf="isLoaded()">
<form [formGroup]="loginForm" (ngSubmit)="login()" novalidate class="needs-validation" *ngIf="!firstTimeFlow()">
<div class="card-text">
<div class="mb-3">
<label for="username" class="form-label visually-hidden">{{t('username')}}</label>
<input class="form-control custom-input" formControlName="username" id="username" autocomplete="username"
type="text" autofocus [placeholder]="t('username')">
</div>
<div class="mb-2">
<label for="password" class="form-label visually-hidden">{{t('password')}}</label>
<input class="form-control custom-input" formControlName="password" name="password" autocomplete="current-password"
id="password" type="password" [placeholder]="t('password')">
</div>
@if (isLoaded()) {
@if (showPasswordLogin() && !firstTimeFlow()) {
<form [formGroup]="loginForm" (ngSubmit)="login()" novalidate class="needs-validation">
<div class="card-text">
<div class="mb-3">
<label for="username" class="form-label visually-hidden">{{t('username')}}</label>
<input class="form-control custom-input" formControlName="username" id="username" autocomplete="username"
type="text" autofocus [placeholder]="t('username')">
</div>
<div class="mb-3 forgot-password">
<a routerLink="/registration/reset-password">{{t('forgot-password')}}</a>
</div>
<div class="mb-2">
<label for="password" class="form-label visually-hidden">{{t('password')}}</label>
<input class="form-control custom-input" formControlName="password" name="password" autocomplete="current-password"
id="password" type="password" [placeholder]="t('password')">
</div>
<div class="sign-in">
<button class="btn btn-outline-primary" type="submit" [disabled]="isSubmitting()">{{t('submit')}}</button>
</div>
</div>
</form>
<div class="mb-3 forgot-password">
<a routerLink="/registration/reset-password">{{t('forgot-password')}}</a>
</div>
@if (oidcService.ready()) {
<button [ngbTooltip]="t('oidc-tooltip')" class="btn btn-outline-primary mt-2" (click)="oidcService.login()">{{t('oidc')}}</button>
<div class="sign-in">
<button class="btn btn-outline-primary" type="submit" [disabled]="isSubmitting()">{{t('submit')}}</button>
</div>
</div>
</form>
}
</ng-container>
@if (oidcService.ready()) {
<button
class="btn btn-outline-primary mt-2 d-flex align-items-center gap-2"
(click)="oidcService.login()">
<img
ngSrc="assets/icons/open-id-connect-logo.svg"
alt="OIDC"
width="36"
height="36"
class="d-inline-block"
style="object-fit: contain;"
/>
{{t('oidc')}}
</button>
}
@if (!showPasswordLogin()) {
<div class="text-muted mt-2">
{{t('bypass')}}
<span class="text-muted clickable" (click)="forceShowPasswordLogin.set(true)">{{t('here')}}</span>
</div>
}
}
</ng-container>
</app-splash-container>
</ng-container>

View file

@ -45,4 +45,8 @@ a {
.btn {
font-family: var(--login-input-font-family);
}
}
}
.text-muted {
font-size: 0.8rem;
}

View file

@ -1,27 +1,24 @@
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component, computed,
DestroyRef, effect, inject,
effect, inject,
OnInit,
signal
} from '@angular/core';
import { FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
import {NgbModal, NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
import {NgbTooltip} from '@ng-bootstrap/ng-bootstrap';
import { ToastrService } from 'ngx-toastr';
import { take } from 'rxjs/operators';
import { AccountService } from '../../_services/account.service';
import { MemberService } from '../../_services/member.service';
import { NavService } from '../../_services/nav.service';
import { NgIf } from '@angular/common';
import {NgOptimizedImage} from '@angular/common';
import { SplashContainerComponent } from '../_components/splash-container/splash-container.component';
import {TRANSLOCO_SCOPE, TranslocoDirective} from "@jsverse/transloco";
import {TranslocoDirective} from "@jsverse/transloco";
import {environment} from "../../../environments/environment";
import {OidcService} from "../../_services/oidc.service";
import {forkJoin} from "rxjs";
import {takeUntilDestroyed, toSignal} from "@angular/core/rxjs-interop";
@Component({
@ -29,7 +26,7 @@ import {takeUntilDestroyed, toSignal} from "@angular/core/rxjs-interop";
templateUrl: './user-login.component.html',
styleUrls: ['./user-login.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SplashContainerComponent, NgIf, ReactiveFormsModule, RouterLink, TranslocoDirective, NgbTooltip]
imports: [SplashContainerComponent, ReactiveFormsModule, RouterLink, TranslocoDirective, NgbTooltip, NgOptimizedImage]
})
export class UserLoginComponent implements OnInit {
@ -61,7 +58,23 @@ export class UserLoginComponent implements OnInit {
/**
* undefined until query params are read
*/
skipAutoLogin = signal<boolean | undefined>(undefined)
skipAutoLogin = signal<boolean | undefined>(undefined);
/**
* Display the login form, regardless if the password authentication is disabled (admins can still log in)
*/
forceShowPasswordLogin = signal(false);
/**
* Display the login form
*/
showPasswordLogin = computed(() => {
const loaded = this.isLoaded();
const config = this.oidcService.settings();
const force = this.forceShowPasswordLogin();
if (force) return true;
return loaded && config && !config.disablePasswordAuthentication;
});
constructor() {
this.navService.hideNavBar();

View file

@ -0,0 +1 @@
<?xml version="1.0"?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="_x32_39-openid"><g><path d="M234.849,419v6.623c-79.268-9.958-139.334-53.393-139.334-105.757 c0-39.313,33.873-73.595,84.485-92.511L178.023,180C88.892,202.497,26.001,256.607,26.001,319.866 c0,76.288,90.871,139.128,208.95,149.705l0.018-0.009V419H234.849z" style="fill:#B2B2B2;"/><polygon points="304.772,436.713 304.67,436.713 304.67,221.667 304.67,213.667 304.67,42.429 234.849,78.25 234.849,221.667 234.969,221.667 234.969,469.563 " style="fill:#F7931E;"/><path d="M485.999,291.938l-9.446-100.114l-35.938,20.331C415.087,196.649,382.5,177.5,340,177.261 l0.002,36.406v7.498c3.502,0.968,6.923,2.024,10.301,3.125c14.145,4.611,27.176,10.352,38.666,17.128l-37.786,21.254 L485.999,291.938z" style="fill:#B2B2B2;"/></g></g><g id="Layer_1"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -7,7 +7,8 @@
"forgot-password": "Forgot Password?",
"submit": "Sign in",
"oidc": "Log in with OpenID Connect",
"oidc-tooltip": "This will connect you to an external site"
"bypass": "Password login has been disabled. Bypass ",
"here": "here"
},
"oidc": {
@ -34,7 +35,9 @@
"provisionUserSettings": "Provision user settings",
"provisionUserSettings-tooltip": "Synchronise Kavita user settings (roles, libraries, age rating) with those provided by the OIDC. See documentation for more information",
"autoLogin": "Auto login",
"autoLogin-tooltip": "Auto redirect to OpenID Connect provider when opening the login screen"
"autoLogin-tooltip": "Auto redirect to OpenID Connect provider when opening the login screen",
"disablePasswordAuthentication": "Disable password authentication",
"disablePasswordAuthentication-tooltip": "Users with the admin role can bypass this restriction"
}
},