Compare commits

...

7 Commits

Author SHA1 Message Date
f79fa4ca27 fix(auth): Verbesserung von isAuthenticated() durch Überprüfung des HTTP-Antwortstatus
Die isAuthenticated()-Methode wurde aktualisiert, um den Anmeldestatus anhand des HTTP-Antwortstatus zu bestimmen, anstatt sich nur auf den Antwortkörper zu verlassen. Außerdem wird sichergestellt, dass `_isLogedIn` im Fehlerfall explizit auf false gesetzt wird. Dies verbessert die Zuverlässigkeit der Sitzungsvalidierung.
2025-07-22 15:45:38 +02:00
55822047bc refactor(api.models): Verschiebung ins api-Verzeichnis und Vereinfachung der Namenskonvention 2025-07-22 14:21:31 +02:00
de360968dc refactor(User): Aktualisierung des User-Modells. 2025-07-22 14:15:35 +02:00
06303ec2b5 refactor(startup): remove cookie-based authentication configuration 2025-07-22 13:13:52 +02:00
437f33a323 fix(auth): Fehlermeldung bei 401 unauthorized während der Login-Prüfung verhindern
Überspringe die Anzeige der Fehlermeldung in isAuthenticated, wenn der Antwortstatus 401,
ist, was typischerweise anzeigt, dass der Benutzer einfach nicht eingeloggt ist, nicht ein Serverproblem.
2025-07-22 10:33:42 +02:00
963ab12488 chore: add scripts to be able to start without ssl
- changed target of proxy
2025-07-22 10:19:36 +02:00
df24905a0e feat(logging): NLog nur in Nicht-Entwicklungsumgebungen anwenden
Die Einrichtung des Logging-Anbieters wurde mit einer Prüfung versehen, um sicherzustellen, dass NLog nur verwendet wird, wenn es sich nicht in der Entwicklung befindet. Dadurch wird verhindert, dass das Standard-Protokollierungsverhalten während der Entwicklung überschrieben wird.
2025-07-22 08:51:06 +02:00
37 changed files with 108 additions and 111 deletions

View File

@ -3,7 +3,8 @@
"version": "4.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --ssl -o",
"start:ssl": "ng serve --ssl -o",
"start": "ng serve -o",
"build": "ng build --configuration production",
"watch": "ng build --watch --configuration development",
"test": "ng test",

View File

@ -1,6 +1,6 @@
{
"/api": {
"target": "https://localhost:7103",
"target": "https://localhost:7052",
"secure": false,
"changeOrigin": true
}

View File

@ -1,5 +1,5 @@
import { Component, inject, signal } from '@angular/core';
import { Group } from '../../../models/user-management.api.models';
import { Group } from '../../../services/api/api-models';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';

View File

@ -1,5 +1,5 @@
import { Component, inject, ChangeDetectionStrategy, signal, OnInit, computed } from '@angular/core';
import { UserRep } from '../../../models/user-management.api.models';
import { UserRep } from '../../../services/api/api-models';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UserRepService } from '../../../services/api/user-representation.service';
import { GroupUpdateFormComponent } from '../group-update-form/group-update-form.component';

View File

@ -1,5 +1,5 @@
import { Component, inject, signal } from '@angular/core';
import { User } from '../../../models/user-management.api.models';
import { User } from '../../../services/api/api-models';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

View File

@ -5,7 +5,7 @@ import { GroupService } from '../../services/api/group.service';
import { Observable, forkJoin, of } from 'rxjs';
import { catchError, finalize } from 'rxjs/operators';
import { DirGroupTableComponent } from '../tables/dir-group-table/dir-group-table.component';
import { DirGroup } from '../../models/user-management.api.models';
import { DirGroup } from '../../services/api/api-models';
@Component({
standalone: true,

View File

@ -36,7 +36,7 @@ export class NavMenuComponent {
isChecked = true;
constructor(private dialog: MatDialog, private authService: AuthenticationService, public refreshService: RefreshService, public creationService: CreationService, public updateService: UpdateService, public transferService: TransferService, public buttonVisibilityService: ButtonVisibilityService, public deletionService: DeletionService) {
this.authService.isAuthenticated().then().catch()
this.authService.isAuthenticated()
this.updateActCount = this.updateService.totalCount;
this.updateService.addChangeListener(UpdateEvent.CountChange, () => {
this.updateActCount = updateService.totalCount;

View File

@ -1,6 +1,6 @@
import { Component, Inject } from '@angular/core';
import { MatListModule } from '@angular/material/list';
import { User } from '../../../models/user-management.api.models'
import { User } from '../../../services/api/api-models'
import { MAT_BOTTOM_SHEET_DATA } from '@angular/material/bottom-sheet';
@Component({

View File

@ -1,6 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BaseTableComponent } from './base-table.component';
import { ApiService } from '../../../services/api/user-management.api.service';
import { ApiService } from '../../../services/api/api-service';
import { NO_ERRORS_SCHEMA } from '@angular/core';
describe('BaseTableComponent', () => {

View File

@ -1,5 +1,5 @@
import { Component, Inject, Input, OnDestroy, OnInit, ViewChild, input } from '@angular/core';
import { ApiService } from '../../../services/api/user-management.api.service';
import { ApiService } from '../../../services/api/api-service';
import { GuiGridModule, GuiColumn, GuiColumnMenu, GuiSorting, GuiRowDetail, GuiPaging, GuiPagingDisplay, GuiSearching, GuiCellEdit, GuiInfoPanel, GuiTitlePanel, GuiRowSelection, GuiSelectedRow, GuiGridComponent, GuiGridApi, GuiTheme, GuiRowStyle, GuiRowClass } from '@generic-ui/ngx-grid';
import { Subscription } from 'rxjs/internal/Subscription';
import { ColorModeService, Theme } from '../../../services/button/color-mode.service';

View File

@ -1,7 +1,7 @@
import { Component, Inject } from '@angular/core';
import { BaseTableComponent } from '../base-table/base-table.component';
import { DirGroupService } from '../../../services/api/dir-group.service';
import { DirGroup } from '../../../models/user-management.api.models';
import { DirGroup } from '../../../services/api/api-models';
import { GuiGridModule } from '@generic-ui/ngx-grid';
import { ColorModeService } from '../../../services/button/color-mode.service';
import { CommonModule } from '@angular/common';

View File

@ -1,5 +1,5 @@
import { Component, Inject } from '@angular/core';
import { DirUser } from '../../../models/user-management.api.models';
import { DirUser } from '../../../services/api/api-models';
import { DirUserService } from '../../../services/api/dir-user.service';
import { BaseTableComponent } from '../base-table/base-table.component';
import { GuiGridModule, GuiColumn } from '@generic-ui/ngx-grid';

View File

@ -1,6 +1,6 @@
import { Component, Inject } from '@angular/core';
import { GroupService } from '../../../services/api/group.service';
import { Group } from '../../../models/user-management.api.models';
import { Group } from '../../../services/api/api-models';
import { GuiGridModule, GuiColumn } from '@generic-ui/ngx-grid';
import { BaseTableComponent } from '../base-table/base-table.component';
import { ColorModeService } from '../../../services/button/color-mode.service';

View File

@ -1,6 +1,6 @@
import { Component, Inject } from '@angular/core';
import { GroupOfUserService } from '../../../services/group-of-user.service';
import { GroupOfUser } from '../../../models/user-management.api.models';
import { GroupOfUser } from '../../../services/api/api-models';
import { GuiGridModule, GuiColumn } from '@generic-ui/ngx-grid';
import { BaseTableComponent } from '../base-table/base-table.component';
import { ColorModeService } from '../../../services/color-mode.service';

View File

@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { Module } from '../../../models/user-management.api.models';
import { Module } from '../../../services/api/api-models';
import { GuiGridModule } from '@generic-ui/ngx-grid';
import { BaseTableComponent } from '../base-table/base-table.component';
import { ModuleService } from '../../../services/api/module.service'

View File

@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { UserRep } from '../../../models/user-management.api.models';
import { UserRep } from '../../../services/api/api-models';
import { UserRepService } from '../../../services/api/user-representation.service';
import { BaseTableComponent } from '../base-table/base-table.component';
import { GuiGridModule } from '@generic-ui/ngx-grid';

View File

@ -2,7 +2,7 @@ import { Component } from '@angular/core';
import { UserService } from '../../../services/api/user.service';
import { ModuleOfUserService } from '../../../services/api/module-of-user.service';
import { GroupOfUserService } from '../../../services/api/group-of-user.service';
import { User } from '../../../models/user-management.api.models';
import { User } from '../../../services/api/api-models';
import { GuiGridModule } from '@generic-ui/ngx-grid';
import { BaseTableComponent } from '../base-table/base-table.component';
import { ColorModeService } from '../../../services/button/color-mode.service'

View File

@ -7,7 +7,7 @@ import { catchError, finalize } from 'rxjs/operators';
import { DirGroupTableComponent } from '../tables/dir-group-table/dir-group-table.component';
import { DirUserTableComponent } from '../tables/dir-user-table/dir-user-table.component';
import { UserService } from '../../services/api/user.service';
import { User } from '../../models/user-management.api.models'
import { User } from '../../services/api/api-models'
import { RefreshService } from '../../services/button/refresh.service';
@Component({

View File

@ -5,7 +5,7 @@ import { MatTabsModule } from '@angular/material/tabs';
import { GuiCellEdit, GuiSelectedRow } from '@generic-ui/ngx-grid';
import { GroupFormComponent } from '../../components/forms/group-form/group-form.component';
import { BasePageComponent } from '../base-page/base-page.component';
import { Group } from '../../models/user-management.api.models';
import { Group } from '../../services/api/api-models';
import { firstValueFrom, forkJoin } from 'rxjs';
import Swal from 'sweetalert2';
import { env } from '../../../environments/environment';

View File

@ -4,7 +4,7 @@ import { GuiRowSelection, GuiRowSelectionMode, GuiRowSelectionType, GuiSelectedR
import { UserTableComponent } from '../../components/tables/user-table/user-table.component';
import { ModuleTableComponent } from '../../components/tables/module-table/module-table.component';
import { GroupTableComponent } from '../../components/tables/group-table/group-table.component';
import { User } from '../../models/user-management.api.models';
import { User } from '../../services/api/api-models';
import { MatTabsModule, MatTabGroup } from '@angular/material/tabs';
import { BasePageComponent } from '../base-page/base-page.component';

View File

@ -8,7 +8,7 @@ import Swal from 'sweetalert2';
import { MatTabsModule, MatTabGroup } from '@angular/material/tabs';
import { env } from '../../../environments/environment';
import { BasePageComponent } from '../base-page/base-page.component';
import { Group, User, UserRep } from '../../models/user-management.api.models';
import { Group, User, UserRep } from '../../services/api/api-models';
import { RepCreateFormComponent } from '../../components/forms/rep-create-form/rep-create-form.component';
import { MatDialog } from '@angular/material/dialog';
import { BaseTableComponent } from '../../components/tables/base-table/base-table.component';

View File

@ -6,7 +6,7 @@ import { GroupTableComponent } from '../../components/tables/group-table/group-t
import { ModuleTableComponent } from '../../components/tables/module-table/module-table.component';
import { UserCreateFormComponent } from '../../components/forms/user-create-form/user-create-form.component';
import { BasePageComponent } from '../base-page/base-page.component';
import { User } from '../../models/user-management.api.models';
import { User } from '../../services/api/api-models';
import { firstValueFrom, forkJoin } from 'rxjs';
import Swal from 'sweetalert2';
import { env } from '../../../environments/environment'

View File

@ -8,6 +8,8 @@ export interface User {
language?: string;
comment?: string;
deleted?: boolean;
deletedWhen?: Date;
deletedWho?: string;
dateFormat?: string;
addedWho?: string;
addedWhen?: Date;
@ -70,7 +72,7 @@ export interface UserRep {
repUser?: User
user?: User,
repGroup?: Group,
group?: Group,
group?: Group,
}
export interface DirGroup {

View File

@ -17,21 +17,22 @@ export class AuthenticationService {
constructor(
private router: Router,
private http: HttpClient,
urlService : UrlService)
{
this.loginUrl = urlService.apiRoute.login;
this.logoutUrl = urlService.apiRoute.logout;
this.checkUrl = urlService.apiRoute.loginCheck;
}
urlService: UrlService) {
this.loginUrl = urlService.apiRoute.login;
this.logoutUrl = urlService.apiRoute.logout;
this.checkUrl = urlService.apiRoute.loginCheck;
}
async isAuthenticated(): Promise<boolean> {
try {
const response = await firstValueFrom(this.http.get<boolean>(this.checkUrl, { withCredentials: true }));
_isLogedIn = response;
return response;
} catch (error) {
this.showErrorAlert();
return false;
const response = await firstValueFrom(this.http.get(this.checkUrl, { withCredentials: true, observe: 'response' }));
_isLogedIn = response?.status === 200;
return _isLogedIn;
} catch (error: any) {
if (error?.status !== 401)
this.showErrorAlert();
_isLogedIn = false
return _isLogedIn;
}
}
@ -81,4 +82,4 @@ export class AuthenticationService {
}
let _isLogedIn: boolean = false;
export const IsLogedIn = () => _isLogedIn
export const IsLogedIn = () => _isLogedIn

View File

@ -1,7 +1,7 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DirGroup, } from '../../models/user-management.api.models';
import { ApiService } from './user-management.api.service';
import { DirGroup, } from './api-models';
import { ApiService } from './api-service';
import { Observable } from 'rxjs/internal/Observable';
import Swal from 'sweetalert2';
import { UrlService } from './url.service';

View File

@ -1,7 +1,7 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { DirUser } from '../../models/user-management.api.models';
import { ApiService } from './user-management.api.service';
import { DirUser } from './api-models';
import { ApiService } from './api-service';
import { Observable } from 'rxjs/internal/Observable';
import { UrlService } from './url.service';

View File

@ -1,7 +1,7 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Inject, Injectable } from '@angular/core';
import { DirUser } from '../../models/user-management.api.models';
import { DirUser } from './api-models';
import { UrlService } from './url.service';
@Injectable({

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { GroupOfUser } from '../../models/user-management.api.models';
import { ApiService } from './user-management.api.service';
import { GroupOfUser } from './api-models';
import { ApiService } from './api-service';
import { Observable, firstValueFrom } from 'rxjs';
import { UrlService } from './url.service';

View File

@ -1,7 +1,7 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DirGroup, Group, } from '../../models/user-management.api.models';
import { ApiService } from './user-management.api.service';
import { DirGroup, Group, } from './api-models';
import { ApiService } from './api-service';
import { Observable } from 'rxjs/internal/Observable';
import { UrlService } from './url.service';

View File

@ -1,7 +1,7 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ModuleOfUser } from '../../models/user-management.api.models';
import { ApiService } from './user-management.api.service';
import { ModuleOfUser } from './api-models';
import { ApiService } from './api-service';
import { Observable, firstValueFrom } from 'rxjs';
import { UrlService } from './url.service';

View File

@ -1,7 +1,7 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ApiService } from './user-management.api.service';
import { Module } from '../../models/user-management.api.models';
import { ApiService } from './api-service';
import { Module } from './api-models';
import { UrlService } from './url.service';
@Injectable({

View File

@ -1,6 +1,6 @@
import { Inject, Injectable } from "@angular/core";
import { UserRep } from "../../models/user-management.api.models";
import { ApiService } from "./user-management.api.service";
import { UserRep } from "./api-models";
import { ApiService } from "./api-service";
import { HttpClient, HttpParams } from "@angular/common/http";
import { Observable } from "rxjs";
import { UrlService } from "./url.service";

View File

@ -1,7 +1,7 @@
import { Injectable, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DirUser, User } from '../../models/user-management.api.models';
import { ApiService } from './user-management.api.service';
import { DirUser, User } from './api-models';
import { ApiService } from './api-service';
import { Observable } from 'rxjs';
import { UrlService } from './url.service';

View File

@ -9,6 +9,8 @@ namespace DigitalData.UserManager.API.Controllers;
[Authorize]
[Obsolete("Use MediatR")]
[Route("api/[controller]")]
[ApiController]
public class UserController : BaseAuthController<IUserService, UserCreateDto, UserReadDto, UserUpdateDto, User>
{
public UserController(ILogger<UserController> logger, IUserService service) : base(logger, service, service)

View File

@ -1,8 +1,6 @@
using Microsoft.EntityFrameworkCore;
using DigitalData.UserManager.Infrastructure.Repositories;
using DigitalData.UserManager.Application;
using DigitalData.Core.Application;
using Microsoft.AspNetCore.Authentication.Cookies;
using NLog.Web;
using NLog;
using DigitalData.Core.API;
@ -31,8 +29,11 @@ try {
builder.Services.AddEncryptor(builder.Configuration.GetSection("EncryptionParameters"));
builder.Logging.ClearProviders();
builder.Host.UseNLog();
if (!builder.Environment.IsDevelopment())
{
builder.Logging.ClearProviders();
builder.Host.UseNLog();
}
builder.Services.AddControllers(opt =>
{
@ -50,23 +51,6 @@ try {
builder.Services.AddSwaggerGen();
}
builder.Services.AddControllers(opt =>
{
opt.Conventions.Add(new RemoveIfControllerConvention()
.AndIf(c => c.ControllerName == nameof(EncryptionController).Replace("Controller", ""))
.AndIf(c => !config.GetValue<bool>("UseEncryptor")));
});
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only
options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites
options.LoginPath = "/api/auth/login";
options.LogoutPath = "/api/auth/logout";
});
// Once the app is built, the password will be decrypted with Encryptor. lazy loading also acts as a call back method.
Lazy<string>? cnn_str = null;
@ -101,45 +85,44 @@ try {
var authTokenKeys = config.GetSection(nameof(AuthTokenKeys)).Get<AuthTokenKeys>() ?? new();
builder.Services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
opt.TokenValidationParameters = new TokenValidationParameters
{
var clientParams = lazyProvider.GetRequiredService<IOptions<ClientParams>>()?.Value;
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null)
context.Token = cookieToken;
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
context.Token = queryStrToken;
var clientParams = lazyProvider.GetRequiredService<IOptions<ClientParams>>()?.Value;
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
{
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null)
context.Token = cookieToken;
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
context.Token = queryStrToken;
}
return Task.CompletedTask;
}
return Task.CompletedTask;
}
};
});
};
});
builder.Services.AddSwaggerGen(setupAct =>
{

View File

@ -106,7 +106,11 @@ namespace DigitalData.UserManager.Domain.Entities
public int UserIdFkIntEcm { get; set; }
[Column("DELETED_WHEN")]
public DateTime DeletedWhen { get; set; }
public DateTime
#if NET7_0_OR_GREATER
?
#endif
DeletedWhen { get; set; }
[Column("DELETED_WHO")]
[StringLength(50)]
@ -114,6 +118,10 @@ namespace DigitalData.UserManager.Domain.Entities
#if NET7_0_OR_GREATER
required
#endif
string DeletedWho { get; set; }
string
#if NET7_0_OR_GREATER
?
# endif
DeletedWho { get; set; }
}
}