Merge pull request #4971 from FinnStutzenstein/anonymousImprovements
Improves the sidebar for anonymous users and routing
This commit is contained in:
commit
975f8dc169
@ -11,11 +11,11 @@ import { CollectionStringMapperService } from './collection-string-mapper.servic
|
||||
import { CommonAppConfig } from '../../site/common/common.config';
|
||||
import { ConfigAppConfig } from '../../site/config/config.config';
|
||||
import { ServicesToLoadOnAppsLoaded } from '../core.module';
|
||||
import { FallbackRoutesService } from './fallback-routes.service';
|
||||
import { MainMenuService } from './main-menu.service';
|
||||
import { MediafileAppConfig } from '../../site/mediafiles/mediafile.config';
|
||||
import { MotionsAppConfig } from '../../site/motions/motions.config';
|
||||
import { OnAfterAppsLoaded } from '../definitions/on-after-apps-loaded';
|
||||
import { plugins } from '../../../plugins';
|
||||
import { SearchService } from '../ui-services/search.service';
|
||||
import { isSearchable } from '../../site/base/searchable';
|
||||
import { TagAppConfig } from '../../site/tags/tag.config';
|
||||
@ -56,17 +56,11 @@ export class AppLoadService {
|
||||
private modelMapper: CollectionStringMapperService,
|
||||
private mainMenuService: MainMenuService,
|
||||
private searchService: SearchService,
|
||||
private injector: Injector
|
||||
private injector: Injector,
|
||||
private fallbackRoutesService: FallbackRoutesService
|
||||
) {}
|
||||
|
||||
public async loadApps(): Promise<void> {
|
||||
if (plugins.length) {
|
||||
console.log('plugins: ', plugins);
|
||||
}
|
||||
/*for (const pluginName of plugins) {
|
||||
const plugin = await import('../../../../../plugins/' + pluginName + '/' + pluginName);
|
||||
plugin.main();
|
||||
}*/
|
||||
const repositories: OnAfterAppsLoaded[] = [];
|
||||
appConfigs.forEach((config: AppConfig) => {
|
||||
if (config.models) {
|
||||
@ -92,6 +86,7 @@ export class AppLoadService {
|
||||
}
|
||||
if (config.mainMenuEntries) {
|
||||
this.mainMenuService.registerEntries(config.mainMenuEntries);
|
||||
this.fallbackRoutesService.registerFallbackEntries(config.mainMenuEntries);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router } from '@angular/router';
|
||||
|
||||
import { FallbackRoutesService } from './fallback-routes.service';
|
||||
import { OpenSlidesService } from './openslides.service';
|
||||
import { OperatorService } from './operator.service';
|
||||
|
||||
@ -21,7 +22,8 @@ export class AuthGuard implements CanActivate, CanActivateChild {
|
||||
public constructor(
|
||||
private router: Router,
|
||||
private operator: OperatorService,
|
||||
private openSlidesService: OpenSlidesService
|
||||
private openSlidesService: OpenSlidesService,
|
||||
private fallbackRoutesService: FallbackRoutesService
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -56,6 +58,28 @@ export class AuthGuard implements CanActivate, CanActivateChild {
|
||||
if (this.canActivate(route)) {
|
||||
return true;
|
||||
} else {
|
||||
this.handleForbiddenRoute(route);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a forbidden route. If the route is "/" (start page), It is tried to
|
||||
* use a fallback route provided by AuthGuardFallbackRoutes. If this won't work
|
||||
* or it wasn't the start page in the first place, the operator will be redirected
|
||||
* to an error page.
|
||||
*/
|
||||
private handleForbiddenRoute(route: ActivatedRouteSnapshot): void {
|
||||
if (route.url.length === 0) {
|
||||
// start page
|
||||
const fallbackRoute = this.fallbackRoutesService.getFallbackRoute();
|
||||
if (fallbackRoute) {
|
||||
this.router.navigate([fallbackRoute]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fall-through: If the url is the start page, but no other fallback was found,
|
||||
// navigate to the error page.
|
||||
|
||||
this.openSlidesService.redirectUrl = location.pathname;
|
||||
this.router.navigate(['/error'], {
|
||||
queryParams: {
|
||||
@ -65,4 +89,3 @@ export class AuthGuard implements CanActivate, CanActivateChild {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
43
client/src/app/core/core-services/fallback-routes.service.ts
Normal file
43
client/src/app/core/core-services/fallback-routes.service.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { OperatorService } from './operator.service';
|
||||
|
||||
export interface AuthGuardFallbackEntry {
|
||||
route: string;
|
||||
weight: number;
|
||||
permission: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classical Auth-Guard. Checks if the user has to correct permissions to enter a page, and forwards to login if not.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FallbackRoutesService {
|
||||
private fallbackEntries: AuthGuardFallbackEntry[] = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param operator Asking for the required permission
|
||||
*/
|
||||
public constructor(private operator: OperatorService) {}
|
||||
|
||||
/**
|
||||
* Adds fallback navigation entries for the start page.
|
||||
* @param entries The entries to add
|
||||
*/
|
||||
public registerFallbackEntries(entries: AuthGuardFallbackEntry[]): void {
|
||||
this.fallbackEntries.push(...entries);
|
||||
this.fallbackEntries = this.fallbackEntries.sort((a, b) => a.weight - b.weight);
|
||||
}
|
||||
|
||||
public getFallbackRoute(): string | null {
|
||||
for (const entry of this.fallbackEntries) {
|
||||
if (this.operator.hasPerms(entry.permission)) {
|
||||
return entry.route;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -30,17 +30,21 @@ export class OfflineService {
|
||||
* Sets the offline flag. Restores the DataStoreService to the last known configuration.
|
||||
*/
|
||||
public goOfflineBecauseFailedWhoAmI(): void {
|
||||
this.offline.next(true);
|
||||
if (!this.offline.getValue()) {
|
||||
console.log('offline because whoami failed.');
|
||||
}
|
||||
this.offline.next(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offline flag, because there is no connection to the server.
|
||||
*/
|
||||
public goOfflineBecauseConnectionLost(): void {
|
||||
this.offline.next(true);
|
||||
if (!this.offline.getValue()) {
|
||||
console.log('offline because connection lost.');
|
||||
}
|
||||
this.offline.next(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to return to online-status.
|
||||
|
@ -63,16 +63,23 @@ export class OpenSlidesService {
|
||||
*/
|
||||
public async bootup(): Promise<void> {
|
||||
// start autoupdate if the user is logged in:
|
||||
const response = await this.operator.whoAmIFromStorage();
|
||||
let response = await this.operator.whoAmIFromStorage();
|
||||
const needToCheckOperator = !!response;
|
||||
|
||||
if (!response) {
|
||||
response = await this.operator.whoAmI();
|
||||
}
|
||||
|
||||
if (!response.user && !response.guest_enabled) {
|
||||
if (!location.pathname.includes('error')) {
|
||||
this.redirectUrl = location.pathname;
|
||||
}
|
||||
this.redirectToLoginIfNotSubpage();
|
||||
this.checkOperator(false);
|
||||
} else {
|
||||
await this.afterLoginBootup(response.user_id);
|
||||
}
|
||||
|
||||
if (needToCheckOperator) {
|
||||
// Check for the operator via a async whoami (so no await here)
|
||||
// to validate, that the cache was correct.
|
||||
this.checkOperator(false);
|
||||
|
@ -116,10 +116,30 @@ export class OperatorService implements OnAfterAppsLoaded {
|
||||
*/
|
||||
private userRepository: UserRepositoryService | null;
|
||||
|
||||
private _currentWhoAmI: WhoAmI | null = null;
|
||||
private _defaultWhoAMI: WhoAmI = {
|
||||
user_id: null,
|
||||
guest_enabled: false,
|
||||
user: null,
|
||||
permissions: []
|
||||
};
|
||||
|
||||
/**
|
||||
* The current WhoAmI response to extract the user (the operator) from.
|
||||
*/
|
||||
private currentWhoAmI: WhoAmI | null;
|
||||
private get currentWhoAmI(): WhoAmI {
|
||||
return this._currentWhoAmI || this._defaultWhoAMI;
|
||||
}
|
||||
|
||||
private set currentWhoAmI(value: WhoAmI | null) {
|
||||
this._currentWhoAmI = value;
|
||||
|
||||
// Resetting the default whoami, when the current whoami isn't there. This
|
||||
// is for a fresh restart and do not have (old) changed values in this.defaultWhoAmI
|
||||
if (!value) {
|
||||
this._defaultWhoAMI = this.getDefaultWhoAmIResponse();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _loaded: Deferred<void> = new Deferred();
|
||||
|
||||
@ -169,7 +189,7 @@ export class OperatorService implements OnAfterAppsLoaded {
|
||||
),
|
||||
auditTime(10)
|
||||
)
|
||||
.subscribe(_ => this.updatePermissions());
|
||||
.subscribe(() => this.updatePermissions());
|
||||
|
||||
// Watches the user observable to update the viewUser for the operator.
|
||||
this.getUserObservable().subscribe(user => {
|
||||
@ -198,36 +218,6 @@ export class OperatorService implements OnAfterAppsLoaded {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a default WhoAmI response
|
||||
*/
|
||||
private getDefaultWhoAmIResponse(): WhoAmI {
|
||||
return {
|
||||
user_id: null,
|
||||
guest_enabled: false,
|
||||
user: null,
|
||||
permissions: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current WhoAmI response from the storage.
|
||||
*/
|
||||
public async whoAmIFromStorage(): Promise<WhoAmI> {
|
||||
let response: WhoAmI;
|
||||
try {
|
||||
response = await this.storageService.get<WhoAmI>(WHOAMI_STORAGE_KEY);
|
||||
if (!response) {
|
||||
response = this.getDefaultWhoAmIResponse();
|
||||
}
|
||||
} catch (e) {
|
||||
response = this.getDefaultWhoAmIResponse();
|
||||
}
|
||||
await this.updateCurrentWhoAmI(response);
|
||||
this._loaded.resolve();
|
||||
return this.currentWhoAmI;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the repo to get a view user.
|
||||
*/
|
||||
@ -239,6 +229,24 @@ export class OperatorService implements OnAfterAppsLoaded {
|
||||
this.viewOperatorSubject.next(this._viewUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current WhoAmI response from the storage.
|
||||
*/
|
||||
public async whoAmIFromStorage(): Promise<WhoAmI | null> {
|
||||
let response: WhoAmI | null = null;
|
||||
try {
|
||||
response = await this.storageService.get<WhoAmI>(WHOAMI_STORAGE_KEY);
|
||||
if (!isWhoAmI(response)) {
|
||||
response = null;
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
if (response) {
|
||||
await this.updateCurrentWhoAmI(response);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the operator user. Will be saved to storage
|
||||
* @param user The new operator.
|
||||
@ -274,9 +282,6 @@ export class OperatorService implements OnAfterAppsLoaded {
|
||||
* WhoAMI response.
|
||||
*/
|
||||
private async updateUserInCurrentWhoAmI(): Promise<void> {
|
||||
if (!this.currentWhoAmI) {
|
||||
this.currentWhoAmI = this.getDefaultWhoAmIResponse();
|
||||
}
|
||||
if (this.isAnonymous) {
|
||||
this.currentWhoAmI.user_id = null;
|
||||
this.currentWhoAmI.user = null;
|
||||
@ -300,6 +305,7 @@ export class OperatorService implements OnAfterAppsLoaded {
|
||||
|
||||
this._user = whoami ? whoami.user : null;
|
||||
await this.updatePermissions();
|
||||
this._loaded.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,9 +400,6 @@ export class OperatorService implements OnAfterAppsLoaded {
|
||||
}
|
||||
|
||||
// Save perms to current WhoAmI
|
||||
if (!this.currentWhoAmI) {
|
||||
this.currentWhoAmI = this.getDefaultWhoAmIResponse();
|
||||
}
|
||||
this.currentWhoAmI.permissions = this.permissions;
|
||||
|
||||
if (!this.OSStatus.isInHistoryMode) {
|
||||
@ -406,4 +409,16 @@ export class OperatorService implements OnAfterAppsLoaded {
|
||||
// publish changes in the operator.
|
||||
this.operatorSubject.next(this.user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a default WhoAmI response
|
||||
*/
|
||||
private getDefaultWhoAmIResponse(): WhoAmI {
|
||||
return {
|
||||
user_id: null,
|
||||
guest_enabled: false,
|
||||
user: null,
|
||||
permissions: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,14 @@
|
||||
|
||||
<!-- navigation -->
|
||||
<mat-nav-list class="main-nav">
|
||||
<ng-container *ngIf="!isLoggedIn">
|
||||
<a routerLink="/login" mat-list-item>
|
||||
<mat-icon>exit_to_app</mat-icon>
|
||||
<span translate>Login</span>
|
||||
</a>
|
||||
<mat-divider></mat-divider>
|
||||
</ng-container>
|
||||
|
||||
<span *ngFor="let entry of mainMenuService.entries">
|
||||
<a
|
||||
[@navItemAnim]
|
||||
|
@ -11,7 +11,6 @@ import { filter } from 'rxjs/operators';
|
||||
|
||||
import { navItemAnim, pageTransition } from '../shared/animations';
|
||||
import { OfflineService } from 'app/core/core-services/offline.service';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { OverlayService } from 'app/core/ui-services/overlay.service';
|
||||
import { UpdateService } from 'app/core/ui-services/update.service';
|
||||
import { langToLocale } from 'app/shared/utils/lang-to-locale';
|
||||
@ -90,7 +89,6 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
||||
public constructor(
|
||||
title: Title,
|
||||
protected translate: TranslateService,
|
||||
configService: ConfigService,
|
||||
offlineService: OfflineService,
|
||||
private updateService: UpdateService,
|
||||
private authService: AuthService,
|
||||
@ -108,11 +106,7 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
||||
overlayService.setSpinner(true, translate.instant('Loading data. Please wait...'));
|
||||
|
||||
this.operator.getViewUserObservable().subscribe(user => {
|
||||
if (user) {
|
||||
this.username = user.short_name;
|
||||
} else if (!user && configService.instant<boolean>('general_system_enable_anonymous')) {
|
||||
this.username = translate.instant('Guest');
|
||||
}
|
||||
this.username = user ? user.short_name : translate.instant('Guest');
|
||||
this.isLoggedIn = !!user;
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user