fix(console): toast login handler, show user session loginname, policy value incrementation, accessibility (#413)

* get auth policy, fix increment from 0

* seo, accessibility

* ngsw rem check for update

* organize interceptors

* toast i18n part1

* show loginname

* use primary color

* toast login handler, fix user session type

* Update console/src/assets/i18n/de.json

Co-authored-by: Florian Forster <[email protected]>

* Update console/src/assets/i18n/de.json

Co-authored-by: Florian Forster <[email protected]>

* Update console/src/assets/i18n/de.json

Co-authored-by: Florian Forster <[email protected]>

* Update console/src/assets/i18n/en.json

Co-authored-by: Florian Forster <[email protected]>

* Update console/src/index.html

Co-authored-by: Florian Forster <[email protected]>

* Update console/src/assets/i18n/en.json

Co-authored-by: Florian Forster <[email protected]>

* Update console/src/assets/i18n/en.json

Co-authored-by: Florian Forster <[email protected]>

Co-authored-by: Florian Forster <[email protected]>
This commit is contained in:
Max Peintner
2020-07-09 10:54:55 +02:00
committed by GitHub
co-authored by Florian Forster
parent fa57cc48c1
commit 0721acf605
69 changed files with 796 additions and 675 deletions
@@ -1,15 +0,0 @@
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class AuthInterceptorService implements HttpInterceptor {
constructor() { }
public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req);
}
}
+16 -2
View File
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { Empty } from 'google-protobuf/google/protobuf/empty_pb';
import { Metadata } from 'grpc-web';
import { from, Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { catchError, switchMap } from 'rxjs/operators';
import { AuthServicePromiseClient } from '../proto/generated/auth_grpc_web_pb';
import {
@@ -15,6 +15,7 @@ import {
MyProjectOrgSearchRequest,
MyProjectOrgSearchResponse,
PasswordChange,
PasswordComplexityPolicy,
UpdateUserAddressRequest,
UpdateUserEmailRequest,
UpdateUserPhoneRequest,
@@ -23,6 +24,7 @@ import {
UserEmail,
UserPhone,
UserProfile,
UserProfileView,
UserSessionViews,
UserView,
VerifyMfaOtp,
@@ -57,7 +59,7 @@ export class AuthUserService {
return responseMapper(response);
}
public async GetMyUserProfile(): Promise<UserProfile> {
public async GetMyUserProfile(): Promise<UserProfileView> {
return await this.request(
c => c.getMyUserProfile,
new Empty(),
@@ -65,6 +67,15 @@ export class AuthUserService {
);
}
public async GetMyPasswordComplexityPolicy(): Promise<PasswordComplexityPolicy> {
return await this.request(
c => c.getMyPasswordComplexityPolicy,
new Empty(),
f => f,
);
}
public async GetMyUser(): Promise<UserView> {
return await this.request(
c => c.getMyUser,
@@ -319,6 +330,9 @@ export class AuthUserService {
this._roleCache = userRoles;
return of(this.hasRoles(userRoles, roles, each));
}),
catchError((err) => {
return of(false);
}),
);
} else {
return of(false);
+9 -5
View File
@@ -4,7 +4,7 @@ import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
import { catchError, filter, map, mergeMap, take, timeout } from 'rxjs/operators';
import { Org, UserProfile } from '../proto/generated/auth_pb';
import { Org, UserProfileView } from '../proto/generated/auth_pb';
import { AuthUserService } from './auth-user.service';
import { GrpcService } from './grpc.service';
import { StatehandlerService } from './statehandler.service';
@@ -16,7 +16,7 @@ import { StorageKey, StorageService } from './storage.service';
export class AuthService {
private cachedOrgs: Org.AsObject[] = [];
private _activeOrgChanged: Subject<Org.AsObject> = new Subject();
public user!: Observable<UserProfile.AsObject>;
public user!: Observable<UserProfileView.AsObject>;
private _authenticated: boolean = false;
private readonly _authenticationChanged: BehaviorSubject<
boolean
@@ -61,7 +61,11 @@ export class AuthService {
return from(this.oauthService.loadUserProfile());
}
public async authenticate(config?: Partial<AuthConfig>, setState: boolean = true): Promise<boolean> {
public async authenticate(
config?: Partial<AuthConfig>,
setState: boolean = true,
force: boolean = false,
): Promise<boolean> {
this.config.issuer = config?.issuer || this.grpcService.issuer;
this.config.clientId = config?.clientId || this.grpcService.clientid;
this.config.redirectUri = config?.redirectUri || this.grpcService.redirectUri;
@@ -73,7 +77,8 @@ export class AuthService {
await this.oauthService.loadDiscoveryDocumentAndTryLogin();
this._authenticated = this.oauthService.hasValidAccessToken();
if (!this.oauthService.hasValidIdToken() || !this.authenticated || config) {
if (!this.oauthService.hasValidIdToken() || !this.authenticated || config || force) {
const newState = setState ? await this.statehandler.createState().toPromise() : undefined;
this.oauthService.initCodeFlow(newState);
}
@@ -89,7 +94,6 @@ export class AuthService {
this.router.navigate(['/']);
}
public get activeOrgChanged(): Observable<Org.AsObject> {
return this._activeOrgChanged;
}
@@ -1,31 +1,27 @@
import { Inject, Injectable } from '@angular/core';
import { Metadata } from 'grpc-web';
import {
GrpcDefaultHandler,
GrpcHandler,
GrpcInterceptorHandler,
GrpcRequestFn,
} from './grpc-handler';
import { GRPC_INTERCEPTORS, GrpcInterceptor } from './grpc-interceptor';
import { GrpcDefaultHandler, GrpcHandler, GrpcInterceptorHandler, GrpcRequestFn } from './grpc-handler';
import { GRPC_INTERCEPTORS, GrpcInterceptor } from './interceptors/grpc-interceptor';
@Injectable({
providedIn: 'root',
providedIn: 'root',
})
export class GrpcBackendService {
constructor(
@Inject(GRPC_INTERCEPTORS) private readonly interceptors: GrpcInterceptor[],
) {}
constructor(
@Inject(GRPC_INTERCEPTORS) private readonly interceptors: GrpcInterceptor[],
) { }
public async runRequest<TReq, TResp>(
requestFn: GrpcRequestFn<TReq, TResp>,
request: TReq,
metadata: Metadata = {},
): Promise<TResp> {
const defaultHandler: GrpcHandler = new GrpcDefaultHandler(requestFn);
const chain = this.interceptors.reduceRight(
(next, interceptor) => new GrpcInterceptorHandler(next, interceptor),
defaultHandler,
);
return chain.handle(request, metadata);
}
public async runRequest<TReq, TResp>(
requestFn: GrpcRequestFn<TReq, TResp>,
request: TReq,
metadata: Metadata = {},
): Promise<TResp> {
const defaultHandler: GrpcHandler = new GrpcDefaultHandler(requestFn);
const chain = this.interceptors.reduceRight(
(next, interceptor) => new GrpcInterceptorHandler(next, interceptor),
defaultHandler,
);
return chain.handle(request, metadata);
}
}
+15 -15
View File
@@ -1,31 +1,31 @@
import { Metadata } from 'grpc-web';
import { GrpcInterceptor } from './grpc-interceptor';
import { GrpcInterceptor } from './interceptors/grpc-interceptor';
export type GrpcRequestFn<TReq, TResp> = (
request: TReq,
metadata?: Metadata,
request: TReq,
metadata?: Metadata,
) => Promise<TResp>;
export interface GrpcHandler {
handle(req: unknown, metadata: Metadata): Promise<any>;
handle(req: unknown, metadata: Metadata): Promise<any>;
}
export class GrpcInterceptorHandler implements GrpcHandler {
constructor(
private readonly next: GrpcHandler,
private interceptor: GrpcInterceptor,
) {}
constructor(
private readonly next: GrpcHandler,
private interceptor: GrpcInterceptor,
) { }
public handle(req: unknown, metadata: Metadata): Promise<unknown> {
return this.interceptor.intercept(req, metadata, this.next);
}
public handle(req: unknown, metadata: Metadata): Promise<unknown> {
return this.interceptor.intercept(req, metadata, this.next);
}
}
export class GrpcDefaultHandler<TReq, TResp> implements GrpcHandler {
constructor(private readonly requestFn: GrpcRequestFn<TReq, TResp>) {}
constructor(private readonly requestFn: GrpcRequestFn<TReq, TResp>) { }
public handle(req: TReq, metadata: Metadata): Promise<TResp> {
return this.requestFn(req, metadata);
}
public handle(req: TReq, metadata: Metadata): Promise<TResp> {
return this.requestFn(req, metadata);
}
}
@@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { Metadata } from 'grpc-web';
import { GrpcHandler } from './grpc-handler';
import { GrpcHandler } from '../grpc-handler';
import { StorageService } from '../storage.service';
import { GrpcInterceptor } from './grpc-interceptor';
import { StorageService } from './storage.service';
const authorizationKey = 'Authorization';
const bearerPrefix = 'Bearer ';
@@ -1,11 +1,12 @@
import { InjectionToken } from '@angular/core';
import { Metadata } from 'grpc-web';
import { GrpcHandler } from './grpc-handler';
import { GrpcHandler } from '../grpc-handler';
export const GRPC_INTERCEPTORS = new InjectionToken<GrpcInterceptor[]>(
'GRPC_INTERCEPTORS',
'GRPC_INTERCEPTORS',
);
export interface GrpcInterceptor {
intercept(req: unknown, metadata: Metadata, next: GrpcHandler): Promise<any>;
intercept(req: unknown, metadata: Metadata, next: GrpcHandler): Promise<any>;
}
@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { Metadata } from 'grpc-web';
import { Org } from '../proto/generated/auth_pb';
import { GrpcHandler } from './grpc-handler';
import { Org } from '../../proto/generated/auth_pb';
import { GrpcHandler } from '../grpc-handler';
import { StorageService } from '../storage.service';
import { GrpcInterceptor } from './grpc-interceptor';
import { StorageService } from './storage.service';
const orgKey = 'x-zitadel-orgid';
+33 -10
View File
@@ -1,23 +1,46 @@
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { take } from 'rxjs/operators';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root',
})
export class ToastService {
constructor(private snackBar: MatSnackBar) { }
constructor(private snackBar: MatSnackBar, private translate: TranslateService, private authService: AuthService) { }
public showInfo(message: string): void {
this.showMessage(message, 'close');
public showInfo(message: string, i18nkey: boolean = false): void {
if (i18nkey) {
this.translate
.get(message)
.subscribe(data => {
this.showMessage(data, 'close');
});
} else {
this.showMessage(message, 'close');
}
}
public showError(message: string): void {
this.showMessage(decodeURI(message), 'close');
public showError(grpcError: any): void {
const { message, code, metadata } = grpcError;
// TODO: remove check for code === 7
if (code === 16 || (code === 7 && message === 'invalid token')) {
const actionObserver$ = this.showMessage(decodeURI(message), 'Login');
actionObserver$.pipe(take(1)).subscribe(() => {
this.authService.authenticate(undefined, true, true);
});
} else {
const actionObserver$ = this.showMessage(decodeURI(message), 'close', { duration: 5000 });
}
}
private showMessage(message: string, action: string): void {
this.snackBar.open(message, action, {
duration: 5000,
});
private showMessage(message: string, action: string, config?: MatSnackBarConfig): Observable<void> {
const ref = this.snackBar.open(message, action, config);
return ref.onAction();
}
}
@@ -7,8 +7,6 @@ import { SwUpdate } from '@angular/service-worker';
})
export class UpdateService {
constructor(private swUpdate: SwUpdate, snackbar: MatSnackBar) {
this.swUpdate.checkForUpdate();
this.swUpdate.available.subscribe((evt) => {
const snack = snackbar.open('Update Available', 'Reload');