feat: Security
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
/* eslint-disable @typescript-eslint/member-ordering */
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import {
|
||||
Actions,
|
||||
createEffect,
|
||||
@@ -20,7 +21,8 @@ import {
|
||||
catchError,
|
||||
withLatestFrom,
|
||||
tap,
|
||||
switchMap
|
||||
switchMap,
|
||||
filter
|
||||
} from 'rxjs/operators';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { MessagesActions } from '../messages/messages.actions';
|
||||
@@ -47,9 +49,11 @@ import {
|
||||
Room,
|
||||
User
|
||||
} from '../../shared-kernel';
|
||||
import { setStoredCurrentUserId } from '../../core/storage/current-user-storage';
|
||||
import { clearStoredCurrentUserId, setStoredCurrentUserId } from '../../core/storage/current-user-storage';
|
||||
import { findRoomMember, removeRoomMember } from '../rooms/room-members.helpers';
|
||||
import { AppI18nService } from '../../core/i18n';
|
||||
import { AuthTokenStoreService } from '../../domains/authentication/application/services/auth-token-store.service';
|
||||
import { hasValidPersistedSession, SESSION_EXPIRED_ERROR_CODE } from '../../domains/authentication/domain/logic/auth-session.rules';
|
||||
|
||||
type IncomingModerationExtraAction =
|
||||
| ReturnType<typeof RoomsActions.forgetRoom>
|
||||
@@ -68,6 +72,8 @@ export class UsersEffects {
|
||||
private serverDirectory = inject(ServerDirectoryFacade);
|
||||
private webrtc = inject(RealtimeSessionFacade);
|
||||
private readonly i18n = inject(AppI18nService);
|
||||
private readonly authTokenStore = inject(AuthTokenStoreService);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
/** Prepares persisted state for a successful login before exposing the user in-memory. */
|
||||
authenticateUser$ = createEffect(() =>
|
||||
@@ -106,6 +112,14 @@ export class UsersEffects {
|
||||
|
||||
const sanitizedUser = this.clearStartupVoiceConnection(user);
|
||||
|
||||
if (!this.hasPersistedSessionToken(sanitizedUser)) {
|
||||
clearStoredCurrentUserId();
|
||||
|
||||
return of(UsersActions.loadCurrentUserFailure({
|
||||
error: SESSION_EXPIRED_ERROR_CODE
|
||||
}));
|
||||
}
|
||||
|
||||
if (sanitizedUser === user) {
|
||||
return of(UsersActions.loadCurrentUserSuccess({ user }));
|
||||
}
|
||||
@@ -205,8 +219,6 @@ export class UsersEffects {
|
||||
return this.serverDirectory.kickServerMember(
|
||||
room.id,
|
||||
{
|
||||
actorUserId: currentUser.id,
|
||||
actorRole: currentUser.role,
|
||||
targetUserId: userId
|
||||
},
|
||||
this.toSourceSelector(room)
|
||||
@@ -287,8 +299,6 @@ export class UsersEffects {
|
||||
return this.serverDirectory.banServerMember(
|
||||
room.id,
|
||||
{
|
||||
actorUserId: currentUser.id,
|
||||
actorRole: currentUser.role,
|
||||
targetUserId: userId,
|
||||
banId: ban.oderId,
|
||||
displayName: ban.displayName,
|
||||
@@ -358,8 +368,6 @@ export class UsersEffects {
|
||||
return this.serverDirectory.unbanServerMember(
|
||||
room.id,
|
||||
{
|
||||
actorUserId: currentUser.id,
|
||||
actorRole: currentUser.role,
|
||||
banId: oderId
|
||||
},
|
||||
this.toSourceSelector(room)
|
||||
@@ -477,6 +485,24 @@ export class UsersEffects {
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
/** Send users back to login when their persisted session token is missing or rejected. */
|
||||
redirectOnSessionExpired$ = createEffect(
|
||||
() =>
|
||||
this.actions$.pipe(
|
||||
ofType(UsersActions.loadCurrentUserFailure),
|
||||
filter(({ error }) => error === SESSION_EXPIRED_ERROR_CODE),
|
||||
tap(() => {
|
||||
clearStoredCurrentUserId();
|
||||
void this.router.navigate(['/login'], {
|
||||
queryParams: {
|
||||
returnUrl: this.router.url
|
||||
}
|
||||
});
|
||||
})
|
||||
),
|
||||
{ dispatch: false }
|
||||
);
|
||||
|
||||
/** Keep signaling identity aligned with the current profile to avoid stale fallback names. */
|
||||
syncSignalingIdentity$ = createEffect(
|
||||
() =>
|
||||
@@ -511,6 +537,15 @@ export class UsersEffects {
|
||||
return savedRooms.find((room) => room.id === roomId) ?? null;
|
||||
}
|
||||
|
||||
private hasPersistedSessionToken(user: User): boolean {
|
||||
return hasValidPersistedSession(
|
||||
user,
|
||||
this.serverDirectory.activeServer()?.url,
|
||||
(serverUrl) => this.authTokenStore.getToken(serverUrl),
|
||||
() => this.authTokenStore.hasAnyValidToken()
|
||||
);
|
||||
}
|
||||
|
||||
private resolveDisplayName(user: Pick<User, 'displayName' | 'username'>): string {
|
||||
const displayName = user.displayName?.trim();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user