fix login redirect
This commit is contained in:
@@ -53,9 +53,9 @@ export class InfoComponent implements OnInit {
|
|||||||
await this.userService.update$(uid, {chordMode: value});
|
await this.userService.update$(uid, {chordMode: value});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getUserRoles = (roles: string): roles[] => (roles?.split(';') ?? []) as roles[];
|
public getUserRoles = (role: string): roles[] => (role?.split(';') ?? []) as roles[];
|
||||||
public transdormUserRoles = (roles: roles): string =>
|
public transdormUserRoles = (role: string): string =>
|
||||||
this.getUserRoles(roles)
|
this.getUserRoles(role)
|
||||||
.map(_ => new RolePipe().transform(_))
|
.map(_ => new RolePipe().transform(_))
|
||||||
.join(', ');
|
.join(', ');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {roles} from '../../../services/user/roles';
|
|||||||
|
|
||||||
@Pipe({name: 'role'})
|
@Pipe({name: 'role'})
|
||||||
export class RolePipe implements PipeTransform {
|
export class RolePipe implements PipeTransform {
|
||||||
public transform(role: roles): string {
|
public transform(role: roles | string): string {
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case 'contributor':
|
case 'contributor':
|
||||||
return 'Mitarbeiter';
|
return 'Mitarbeiter';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {Component, Input, inject} from '@angular/core';
|
import {Component, Input, inject} from '@angular/core';
|
||||||
import {User} from '../../../../../services/user/user';
|
import {User} from '../../../../../services/user/user';
|
||||||
import {UserService} from '../../../../../services/user/user.service';
|
import {UserService} from '../../../../../services/user/user.service';
|
||||||
import {ROLE_TYPES} from '../../../../../services/user/roles';
|
import {ROLE_TYPES, roles} from '../../../../../services/user/roles';
|
||||||
import {faTimes} from '@fortawesome/free-solid-svg-icons';
|
import {faTimes} from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import {MatFormField, MatLabel} from '@angular/material/form-field';
|
import {MatFormField, MatLabel} from '@angular/material/form-field';
|
||||||
@@ -24,7 +24,7 @@ export class UserComponent {
|
|||||||
|
|
||||||
public id = '';
|
public id = '';
|
||||||
public name = '';
|
public name = '';
|
||||||
public roles: string[] = [];
|
public roles: roles[] = [];
|
||||||
public ROLE_TYPES = ROLE_TYPES;
|
public ROLE_TYPES = ROLE_TYPES;
|
||||||
public edit = false;
|
public edit = false;
|
||||||
public faClose = faTimes;
|
public faClose = faTimes;
|
||||||
@@ -36,7 +36,7 @@ export class UserComponent {
|
|||||||
this.roles = this.getRoleArray(value.role);
|
this.roles = this.getRoleArray(value.role);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onRoleChanged(id: string, roles: string[]): Promise<void> {
|
public async onRoleChanged(id: string, roles: roles[]): Promise<void> {
|
||||||
const role = roles.join(';');
|
const role = roles.join(';');
|
||||||
await this.userService.update$(id, {role});
|
await this.userService.update$(id, {role});
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ export class UserComponent {
|
|||||||
await this.userService.update$(id, {name: target.value});
|
await this.userService.update$(id, {name: target.value});
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRoleArray(role: string): string[] {
|
public getRoleArray(role: string): roles[] {
|
||||||
return role ? role.split(';') : [];
|
return (role ? role.split(';') : []) as roles[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ describe('UserSessionService', () => {
|
|||||||
const updateSpy = jasmine.createSpy('update').and.resolveTo();
|
const updateSpy = jasmine.createSpy('update').and.resolveTo();
|
||||||
dbServiceSpy.doc.and.returnValue({update: updateSpy} as never);
|
dbServiceSpy.doc.and.returnValue({update: updateSpy} as never);
|
||||||
runInFirebaseContextSpy.and.resolveTo({user: {uid: 'user-1'}});
|
runInFirebaseContextSpy.and.resolveTo({user: {uid: 'user-1'}});
|
||||||
|
authStateSubject.next({uid: 'user-1'});
|
||||||
|
|
||||||
await expectAsync(service.login('mail', 'secret')).toBeResolvedTo('user-1');
|
await expectAsync(service.login('mail', 'secret')).toBeResolvedTo('user-1');
|
||||||
|
|
||||||
@@ -92,6 +93,23 @@ describe('UserSessionService', () => {
|
|||||||
expect(updateSpy).toHaveBeenCalledWith({songUsage: {}});
|
expect(updateSpy).toHaveBeenCalledWith({songUsage: {}});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should wait for auth state propagation before resolving login', async () => {
|
||||||
|
runInFirebaseContextSpy.and.resolveTo({user: {uid: 'user-1'}});
|
||||||
|
|
||||||
|
let resolved = false;
|
||||||
|
const loginPromise = service.login('mail', 'secret').then(result => {
|
||||||
|
resolved = true;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.resolve();
|
||||||
|
expect(resolved).toBeFalse();
|
||||||
|
|
||||||
|
authStateSubject.next({uid: 'user-1'});
|
||||||
|
|
||||||
|
await expectAsync(loginPromise).toBeResolvedTo('user-1');
|
||||||
|
});
|
||||||
|
|
||||||
it('should delegate logout and password reset to AngularFire auth APIs', async () => {
|
it('should delegate logout and password reset to AngularFire auth APIs', async () => {
|
||||||
runInFirebaseContextSpy.and.resolveTo();
|
runInFirebaseContextSpy.and.resolveTo();
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {EnvironmentInjector, Injectable, inject, runInInjectionContext} from '@a
|
|||||||
import {Auth, authState, createUserWithEmailAndPassword, sendPasswordResetEmail, signInWithEmailAndPassword, signOut} from '@angular/fire/auth';
|
import {Auth, authState, createUserWithEmailAndPassword, sendPasswordResetEmail, signInWithEmailAndPassword, signOut} from '@angular/fire/auth';
|
||||||
import {User as AuthUser} from 'firebase/auth';
|
import {User as AuthUser} from 'firebase/auth';
|
||||||
import {firstValueFrom, Observable, of} from 'rxjs';
|
import {firstValueFrom, Observable, of} from 'rxjs';
|
||||||
import {map, shareReplay, switchMap} from 'rxjs/operators';
|
import {filter, map, shareReplay, switchMap, take} from 'rxjs/operators';
|
||||||
import {DbService} from '../db.service';
|
import {DbService} from '../db.service';
|
||||||
import {environment} from '../../../environments/environment';
|
import {environment} from '../../../environments/environment';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
@@ -59,6 +59,7 @@ export class UserSessionService {
|
|||||||
const dUser = await this.readUser(aUser.user.uid);
|
const dUser = await this.readUser(aUser.user.uid);
|
||||||
if (!dUser) return null;
|
if (!dUser) return null;
|
||||||
await this.initSongUsage(dUser);
|
await this.initSongUsage(dUser);
|
||||||
|
await this.awaitAuthenticatedUser(aUser.user.uid);
|
||||||
|
|
||||||
return aUser.user.uid;
|
return aUser.user.uid;
|
||||||
}
|
}
|
||||||
@@ -90,6 +91,15 @@ export class UserSessionService {
|
|||||||
await this.update$(user.id, {songUsage: {}});
|
await this.update$(user.id, {songUsage: {}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async awaitAuthenticatedUser(uid: string): Promise<void> {
|
||||||
|
await firstValueFrom(
|
||||||
|
this.user$.pipe(
|
||||||
|
filter((user): user is User => !!user && user.id === uid),
|
||||||
|
take(1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private runInFirebaseContext = <T>(factory: () => T): T => runInInjectionContext(this.environmentInjector, factory);
|
private runInFirebaseContext = <T>(factory: () => T): T => runInInjectionContext(this.environmentInjector, factory);
|
||||||
private readUser$ = (uid: string) => this.db.doc$<User>('users/' + uid);
|
private readUser$ = (uid: string) => this.db.doc$<User>('users/' + uid);
|
||||||
private readUser = (uid: string): Promise<User | null> => firstValueFrom(this.readUser$(uid));
|
private readUser = (uid: string): Promise<User | null> => firstValueFrom(this.readUser$(uid));
|
||||||
|
|||||||
@@ -89,4 +89,42 @@ describe('RoleGuard', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should redirect members to shows instead of new-user', done => {
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||||
|
routerSpy.createUrlTree.and.returnValue({redirect: ['shows']} as never);
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{provide: Router, useValue: routerSpy},
|
||||||
|
{provide: UserService, useValue: {user$: of({role: 'member'})}},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
guard = TestBed.inject(RoleGuard);
|
||||||
|
|
||||||
|
guard.canActivate({data: {requiredRoles: ['user']}} as never).subscribe(result => {
|
||||||
|
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['shows']);
|
||||||
|
expect(result).toEqual({redirect: ['shows']} as never);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should choose a matching default route from all assigned roles', done => {
|
||||||
|
TestBed.resetTestingModule();
|
||||||
|
routerSpy = jasmine.createSpyObj<Router>('Router', ['createUrlTree']);
|
||||||
|
routerSpy.createUrlTree.and.callFake(commands => ({redirect: commands}) as never);
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{provide: Router, useValue: routerSpy},
|
||||||
|
{provide: UserService, useValue: {user$: of({role: ' none ; leader '})}},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
guard = TestBed.inject(RoleGuard);
|
||||||
|
|
||||||
|
guard.canActivate({data: {requiredRoles: ['presenter']}} as never).subscribe(result => {
|
||||||
|
expect(routerSpy.createUrlTree).toHaveBeenCalledWith(['shows']);
|
||||||
|
expect(result).toEqual({redirect: ['shows']} as never);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {ActivatedRouteSnapshot, Router, UrlTree} from '@angular/router';
|
|||||||
import {Observable} from 'rxjs';
|
import {Observable} from 'rxjs';
|
||||||
import {UserService} from '../../services/user/user.service';
|
import {UserService} from '../../services/user/user.service';
|
||||||
import {map, take} from 'rxjs/operators';
|
import {map, take} from 'rxjs/operators';
|
||||||
|
import {roles} from '../../services/user/roles';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -23,30 +24,45 @@ export class RoleGuard {
|
|||||||
if (!user) {
|
if (!user) {
|
||||||
return this.router.createUrlTree(['brand', 'new-user']);
|
return this.router.createUrlTree(['brand', 'new-user']);
|
||||||
}
|
}
|
||||||
const roles = user.role?.split(';') ?? [];
|
const userRoles = this.parseRoles(user.role);
|
||||||
if (roles.indexOf('admin') !== -1) {
|
if (userRoles.includes('admin')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const allowed = roles.some(s => requiredRoles.indexOf(s) !== -1);
|
const allowed = userRoles.some(role => requiredRoles.includes(role));
|
||||||
|
|
||||||
return allowed || this.router.createUrlTree(this.defaultRoute(roles));
|
return allowed || this.router.createUrlTree(this.defaultRoute(userRoles));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private defaultRoute(roles: string[]): string[] {
|
private defaultRoute(userRoles: roles[]): string[] {
|
||||||
if (!roles || roles.length === 0) {
|
if (userRoles.length === 0) {
|
||||||
return ['brand', 'new-user'];
|
return ['brand', 'new-user'];
|
||||||
}
|
}
|
||||||
switch (roles[0]) {
|
|
||||||
case 'user':
|
if (userRoles.includes('user') || userRoles.includes('contributor')) {
|
||||||
return ['songs'];
|
return ['songs'];
|
||||||
case 'presenter':
|
|
||||||
return ['presentation'];
|
|
||||||
case 'leader':
|
|
||||||
return ['shows'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['brand', 'new-user'];
|
if (userRoles.includes('leader') || userRoles.includes('member')) {
|
||||||
|
return ['shows'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userRoles.includes('presenter')) {
|
||||||
|
return ['presentation'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['user', 'info'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseRoles(role: string | null | undefined): roles[] {
|
||||||
|
if (!role) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return role
|
||||||
|
.split(';')
|
||||||
|
.map(value => value.trim())
|
||||||
|
.filter((value): value is roles => value.length > 0 && value !== 'none');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import {Pipe, PipeTransform} from '@angular/core';
|
|||||||
name: 'sortBy',
|
name: 'sortBy',
|
||||||
})
|
})
|
||||||
export class SortByPipe implements PipeTransform {
|
export class SortByPipe implements PipeTransform {
|
||||||
public transform(value: unknown[] | null, order: 'asc' | 'desc' = 'asc', column = ''): unknown[] | null {
|
public transform<T>(value: T[] | null, order: 'asc' | 'desc' = 'asc', column = ''): T[] | null {
|
||||||
if (!value || !order) {
|
if (!value || !order) {
|
||||||
return value;
|
return value;
|
||||||
} // no array
|
} // no array
|
||||||
if (!column || column === '') {
|
if (!column || column === '') {
|
||||||
if (order === 'asc') {
|
if (order === 'asc') {
|
||||||
return value.sort();
|
return [...value].sort();
|
||||||
} else {
|
} else {
|
||||||
return value.sort().reverse();
|
return [...value].sort().reverse();
|
||||||
}
|
}
|
||||||
} // sort 1d array
|
} // sort 1d array
|
||||||
if (value.length <= 1) {
|
if (value.length <= 1) {
|
||||||
@@ -26,7 +26,7 @@ export class SortByPipe implements PipeTransform {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getComparableValue(item: unknown, column: string): string | number {
|
private getComparableValue<T>(item: T, column: string): string | number {
|
||||||
const value = (item as Record<string, unknown>)[column];
|
const value = (item as Record<string, unknown>)[column];
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return value.getTime();
|
return value.getTime();
|
||||||
|
|||||||
Reference in New Issue
Block a user