mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-15 02:15:14 +00:00
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:
co-authored by
Florian Forster
parent
fa57cc48c1
commit
0721acf605
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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 ';
|
||||
+4
-3
@@ -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>;
|
||||
}
|
||||
+3
-3
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user