mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-06-03 05:34:21 -04:00
Misc Updates (#665)
* Do not allow non-admins to change their passwords when authentication is disabled * Clean up the login page so that input field text is black * cleanup some resizing when typing a password and having a lot of users * Changed the LastActive for a user to not just be login, but also when they open an already authenticated session.
This commit is contained in:
parent
40ea4235fe
commit
70f324669b
@ -140,7 +140,7 @@ namespace API.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_unitOfWork.HasChanges()) return Ok("Nothing was updated");
|
if (!_unitOfWork.HasChanges()) return Ok(updateSettingsDto);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -35,15 +35,6 @@ namespace API.Data.Repositories
|
|||||||
return _mapper.Map<ServerSettingDto>(settings);
|
return _mapper.Map<ServerSettingDto>(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerSettingDto GetSettingsDto()
|
|
||||||
{
|
|
||||||
var settings = _context.ServerSetting
|
|
||||||
.Select(x => x)
|
|
||||||
.AsNoTracking()
|
|
||||||
.ToList();
|
|
||||||
return _mapper.Map<ServerSettingDto>(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<ServerSetting> GetSettingAsync(ServerSettingKey key)
|
public Task<ServerSetting> GetSettingAsync(ServerSettingKey key)
|
||||||
{
|
{
|
||||||
return _context.ServerSetting.SingleOrDefaultAsync(x => x.Key == key);
|
return _context.ServerSetting.SingleOrDefaultAsync(x => x.Key == key);
|
||||||
|
@ -10,7 +10,6 @@ namespace API.Interfaces.Repositories
|
|||||||
{
|
{
|
||||||
void Update(ServerSetting settings);
|
void Update(ServerSetting settings);
|
||||||
Task<ServerSettingDto> GetSettingsDtoAsync();
|
Task<ServerSettingDto> GetSettingsDtoAsync();
|
||||||
ServerSettingDto GetSettingsDto();
|
|
||||||
Task<ServerSetting> GetSettingAsync(ServerSettingKey key);
|
Task<ServerSetting> GetSettingAsync(ServerSettingKey key);
|
||||||
Task<IEnumerable<ServerSetting>> GetSettingsAsync();
|
Task<IEnumerable<ServerSetting>> GetSettingsAsync();
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
@ -27,7 +28,7 @@ namespace API.SignalR.Presence
|
|||||||
_unitOfWork = unitOfWork;
|
_unitOfWork = unitOfWork;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UserConnected(string username, string connectionId)
|
public async Task UserConnected(string username, string connectionId)
|
||||||
{
|
{
|
||||||
lock (OnlineUsers)
|
lock (OnlineUsers)
|
||||||
{
|
{
|
||||||
@ -41,7 +42,10 @@ namespace API.SignalR.Presence
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
// Update the last active for the user
|
||||||
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(username);
|
||||||
|
user.LastActive = DateTime.Now;
|
||||||
|
await _unitOfWork.CommitAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task UserDisconnected(string username, string connectionId)
|
public Task UserDisconnected(string username, string connectionId)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { ServerSettings } from './_models/server-settings';
|
import { ServerSettings } from './_models/server-settings';
|
||||||
|
|
||||||
@ -37,6 +38,8 @@ export class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAuthenticationEnabled() {
|
getAuthenticationEnabled() {
|
||||||
return this.http.get<boolean>(this.baseUrl + 'settings/authentication-enabled', {responseType: 'text' as 'json'});
|
return this.http.get<string>(this.baseUrl + 'settings/authentication-enabled', {responseType: 'text' as 'json'}).pipe(map((res: string) => {
|
||||||
|
return res === 'true';
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class="mx-auto login">
|
<div class="mx-auto login">
|
||||||
|
|
||||||
<ng-container *ngIf="isLoaded">
|
<ng-container *ngIf="isLoaded">
|
||||||
<div class="display: inline-block" *ngIf="firstTimeFlow">
|
<div class="display: inline-block" *ngIf="firstTimeFlow">
|
||||||
<h3 class="card-title text-center">Create an Admin Account</h3>
|
<h3 class="card-title text-center">Create an Admin Account</h3>
|
||||||
<div class="card p-3">
|
<div class="card p-3">
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<form [formGroup]="loginForm" (ngSubmit)="login()" novalidate class="needs-validation" *ngIf="!firstTimeFlow">
|
<form [formGroup]="loginForm" (ngSubmit)="login()" novalidate class="needs-validation" *ngIf="!firstTimeFlow">
|
||||||
<div class="row row-cols-4 row-cols-md-4 row-cols-sm-2 row-cols-xs-2">
|
<div class="row row-cols-4 row-cols-md-4 row-cols-sm-2 row-cols-xs-2">
|
||||||
<ng-container *ngFor="let member of memberNames">
|
<ng-container *ngFor="let member of memberNames">
|
||||||
<div class="col align-self-center card p-3 m-3" style="width: 12rem;">
|
<div class="col align-self-center card p-3 m-3" style="max-width: 12rem;">
|
||||||
<span tabindex="0" (click)="select(member)" a11y-click="13,32">
|
<span tabindex="0" (click)="select(member)" a11y-click="13,32">
|
||||||
<div class="logo-container">
|
<div class="logo-container">
|
||||||
<h3 class="card-title text-center">{{member | sentenceCase}}</h3>
|
<h3 class="card-title text-center">{{member | sentenceCase}}</h3>
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
height: calc(100vh);
|
height: calc(100vh);
|
||||||
min-height: 289px;
|
min-height: 289px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 100vw;
|
||||||
|
max-width: 100vw;
|
||||||
|
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
@ -80,4 +82,5 @@
|
|||||||
|
|
||||||
input {
|
input {
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
|
color: black;
|
||||||
}
|
}
|
@ -47,7 +47,7 @@ export class UserLoginComponent implements OnInit {
|
|||||||
|
|
||||||
this.settingsService.getAuthenticationEnabled().pipe(take(1)).subscribe((enabled: boolean) => {
|
this.settingsService.getAuthenticationEnabled().pipe(take(1)).subscribe((enabled: boolean) => {
|
||||||
// There is a bug where this is coming back as a string not a boolean.
|
// There is a bug where this is coming back as a string not a boolean.
|
||||||
this.authDisabled = enabled + '' === 'false';
|
this.authDisabled = !enabled;
|
||||||
if (this.authDisabled) {
|
if (this.authDisabled) {
|
||||||
this.loginForm.get('password')?.setValidators([]);
|
this.loginForm.get('password')?.setValidators([]);
|
||||||
|
|
||||||
|
@ -175,37 +175,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template ngbPanelContent>
|
<ng-template ngbPanelContent>
|
||||||
<p>Change your Password</p>
|
<ng-container *ngIf="isAuthenticationEnabled || isAdmin; else authDisabled">
|
||||||
<div class="alert alert-danger" role="alert" *ngIf="resetPasswordErrors.length > 0">
|
<p>Change your Password</p>
|
||||||
<div *ngFor="let error of resetPasswordErrors">{{error}}</div>
|
<div class="alert alert-danger" role="alert" *ngIf="resetPasswordErrors.length > 0">
|
||||||
</div>
|
<div *ngFor="let error of resetPasswordErrors">{{error}}</div>
|
||||||
<form [formGroup]="passwordChangeForm">
|
</div>
|
||||||
<div class="form-group">
|
<form [formGroup]="passwordChangeForm">
|
||||||
<label for="new-password">New Password</label>
|
<div class="form-group">
|
||||||
<input class="form-control" type="password" id="new-password" formControlName="password" required>
|
<label for="new-password">New Password</label>
|
||||||
<div id="password-validations" class="invalid-feedback" *ngIf="passwordChangeForm.dirty || passwordChangeForm.touched">
|
<input class="form-control" type="password" id="new-password" formControlName="password" required>
|
||||||
<div *ngIf="password?.errors?.required">
|
<div id="password-validations" class="invalid-feedback" *ngIf="passwordChangeForm.dirty || passwordChangeForm.touched">
|
||||||
This field is required
|
<div *ngIf="password?.errors?.required">
|
||||||
|
This field is required
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label for="confirm-password">Confirm Password</label>
|
||||||
<label for="confirm-password">Confirm Password</label>
|
<input class="form-control" type="password" id="confirm-password" formControlName="confirmPassword" aria-describedby="password-validations" required>
|
||||||
<input class="form-control" type="password" id="confirm-password" formControlName="confirmPassword" aria-describedby="password-validations" required>
|
<div id="password-validations" class="invalid-feedback" *ngIf="passwordChangeForm.dirty || passwordChangeForm.touched">
|
||||||
<div id="password-validations" class="invalid-feedback" *ngIf="passwordChangeForm.dirty || passwordChangeForm.touched">
|
<div *ngIf="!passwordsMatch">
|
||||||
<div *ngIf="!passwordsMatch">
|
Passwords must match
|
||||||
Passwords must match
|
</div>
|
||||||
</div>
|
<div *ngIf="confirmPassword?.errors?.required">
|
||||||
<div *ngIf="confirmPassword?.errors?.required">
|
This field is required
|
||||||
This field is required
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="float-right mb-3">
|
||||||
<div class="float-right mb-3">
|
<button type="button" class="btn btn-secondary mr-2" aria-describedby="password-panel" (click)="resetPasswordForm()">Reset</button>
|
||||||
<button type="button" class="btn btn-secondary mr-2" aria-describedby="password-panel" (click)="resetPasswordForm()">Reset</button>
|
<button type="submit" class="btn btn-primary" aria-describedby="password-panel" (click)="savePasswordForm()" [disabled]="!passwordChangeForm.valid || !(passwordChangeForm.dirty || passwordChangeForm.touched)">Save</button>
|
||||||
<button type="submit" class="btn btn-primary" aria-describedby="password-panel" (click)="savePasswordForm()" [disabled]="!passwordChangeForm.valid || !(passwordChangeForm.dirty || passwordChangeForm.touched)">Save</button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</ng-container>
|
||||||
|
<ng-template #authDisabled>
|
||||||
|
<p class="text-warning">Authentication is disabled on this server. A password is not required to authenticate.</p>
|
||||||
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-panel>
|
</ngb-panel>
|
||||||
<ngb-panel id="api-panel" title="OPDS">
|
<ngb-panel id="api-panel" title="OPDS">
|
||||||
|
@ -11,8 +11,6 @@ import { AccountService } from 'src/app/_services/account.service';
|
|||||||
import { NavService } from 'src/app/_services/nav.service';
|
import { NavService } from 'src/app/_services/nav.service';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { SettingsService } from 'src/app/admin/settings.service';
|
import { SettingsService } from 'src/app/admin/settings.service';
|
||||||
import { keyframes } from '@angular/animations';
|
|
||||||
import { environment } from 'src/environments/environment';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-user-preferences',
|
selector: 'app-user-preferences',
|
||||||
@ -29,6 +27,8 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||||||
settingsForm: FormGroup = new FormGroup({});
|
settingsForm: FormGroup = new FormGroup({});
|
||||||
passwordChangeForm: FormGroup = new FormGroup({});
|
passwordChangeForm: FormGroup = new FormGroup({});
|
||||||
user: User | undefined = undefined;
|
user: User | undefined = undefined;
|
||||||
|
isAdmin: boolean = false;
|
||||||
|
isAuthenticationEnabled: boolean = true;
|
||||||
|
|
||||||
passwordsMatch = false;
|
passwordsMatch = false;
|
||||||
resetPasswordErrors: string[] = [];
|
resetPasswordErrors: string[] = [];
|
||||||
@ -75,7 +75,10 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.settingsService.getOpdsEnabled().subscribe(res => {
|
this.settingsService.getOpdsEnabled().subscribe(res => {
|
||||||
this.opdsEnabled = res;
|
this.opdsEnabled = res;
|
||||||
})
|
});
|
||||||
|
this.settingsService.getAuthenticationEnabled().subscribe(res => {
|
||||||
|
this.isAuthenticationEnabled = res;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -83,6 +86,7 @@ export class UserPreferencesComponent implements OnInit, OnDestroy {
|
|||||||
this.accountService.currentUser$.pipe(take(1)).subscribe((user: User) => {
|
this.accountService.currentUser$.pipe(take(1)).subscribe((user: User) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
this.isAdmin = this.accountService.hasAdminRole(user);
|
||||||
|
|
||||||
if (this.fontFamilies.indexOf(this.user.preferences.bookReaderFontFamily) < 0) {
|
if (this.fontFamilies.indexOf(this.user.preferences.bookReaderFontFamily) < 0) {
|
||||||
this.user.preferences.bookReaderFontFamily = 'default';
|
this.user.preferences.bookReaderFontFamily = 'default';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user