mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 17:57:33 +00:00
fix(console): allow management of metadata of users of other organizations again (#9490)
# Which Problems Are Solved
With the recent change in Console to use the User V2 API
(https://github.com/zitadel/zitadel/pull/9312), some functionality still
needs to call the management API, which requires the organization
context. The context was not passed anymore, leading to error in cases
where the calling user (e.g. an IAM_OWNER) was not part of the same
organization.
# How the Problems Are Solved
Added an interceptor to provide the `x-zitadel-orgid` header for the new
management client.
# Additional Changes
None
# Additional Context
- closes #9488
(cherry picked from commit 61c4b1c3fd
)
This commit is contained in:
@@ -14,7 +14,7 @@ import { ExhaustedService } from './exhausted.service';
|
||||
import { AuthInterceptor, AuthInterceptorProvider, NewConnectWebAuthInterceptor } from './interceptors/auth.interceptor';
|
||||
import { ExhaustedGrpcInterceptor } from './interceptors/exhausted.grpc.interceptor';
|
||||
import { I18nInterceptor } from './interceptors/i18n.interceptor';
|
||||
import { OrgInterceptor } from './interceptors/org.interceptor';
|
||||
import { NewConnectWebOrgInterceptor, OrgInterceptor, OrgInterceptorProvider } from './interceptors/org.interceptor';
|
||||
import { StorageService } from './storage.service';
|
||||
import { UserServiceClient } from '../proto/generated/zitadel/user/v2/User_serviceServiceClientPb';
|
||||
//@ts-ignore
|
||||
@@ -46,6 +46,7 @@ export class GrpcService {
|
||||
private readonly exhaustedService: ExhaustedService,
|
||||
private readonly authInterceptor: AuthInterceptor,
|
||||
private readonly authInterceptorProvider: AuthInterceptorProvider,
|
||||
private readonly orgInterceptorProvider: OrgInterceptorProvider,
|
||||
) {}
|
||||
|
||||
public loadAppEnvironment(): Promise<any> {
|
||||
@@ -62,7 +63,7 @@ export class GrpcService {
|
||||
const interceptors = {
|
||||
unaryInterceptors: [
|
||||
new ExhaustedGrpcInterceptor(this.exhaustedService, this.envService),
|
||||
new OrgInterceptor(this.storageService),
|
||||
new OrgInterceptor(this.orgInterceptorProvider),
|
||||
this.authInterceptor,
|
||||
new I18nInterceptor(this.translate),
|
||||
],
|
||||
@@ -103,8 +104,15 @@ export class GrpcService {
|
||||
baseUrl: env.api,
|
||||
interceptors: [NewConnectWebAuthInterceptor(this.authInterceptorProvider)],
|
||||
});
|
||||
const transportOldAPIs = createGrpcWebTransport({
|
||||
baseUrl: env.api,
|
||||
interceptors: [
|
||||
NewConnectWebAuthInterceptor(this.authInterceptorProvider),
|
||||
NewConnectWebOrgInterceptor(this.orgInterceptorProvider),
|
||||
],
|
||||
});
|
||||
this.userNew = createUserServiceClient(transport);
|
||||
this.mgmtNew = createManagementServiceClient(transport);
|
||||
this.mgmtNew = createManagementServiceClient(transportOldAPIs);
|
||||
this.authNew = createAuthServiceClient(transport);
|
||||
|
||||
const authConfig: AuthConfig = {
|
||||
|
@@ -3,24 +3,55 @@ import { Request, RpcError, StatusCode, UnaryInterceptor, UnaryResponse } from '
|
||||
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
|
||||
import { StorageKey, StorageLocation, StorageService } from '../storage.service';
|
||||
import { ConnectError, Interceptor } from '@connectrpc/connect';
|
||||
import { firstValueFrom, identity, Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, filter, map } from 'rxjs/operators';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
const ORG_HEADER_KEY = 'x-zitadel-orgid';
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OrgInterceptor<TReq = unknown, TResp = unknown> implements UnaryInterceptor<TReq, TResp> {
|
||||
constructor(private readonly storageService: StorageService) {}
|
||||
constructor(private readonly orgInterceptorProvider: OrgInterceptorProvider) {}
|
||||
|
||||
public async intercept(request: Request<TReq, TResp>, invoker: any): Promise<UnaryResponse<TReq, TResp>> {
|
||||
const metadata = request.getMetadata();
|
||||
|
||||
const org: Org.AsObject | null = this.storageService.getItem(StorageKey.organization, StorageLocation.session);
|
||||
|
||||
if (org) {
|
||||
metadata[ORG_HEADER_KEY] = `${org.id}`;
|
||||
const orgId = this.orgInterceptorProvider.getOrgId();
|
||||
if (orgId) {
|
||||
metadata[ORG_HEADER_KEY] = orgId;
|
||||
}
|
||||
|
||||
return invoker(request).catch(this.orgInterceptorProvider.handleError);
|
||||
}
|
||||
}
|
||||
|
||||
export function NewConnectWebOrgInterceptor(orgInterceptorProvider: OrgInterceptorProvider): Interceptor {
|
||||
return (next) => async (req) => {
|
||||
if (!req.header.get(ORG_HEADER_KEY)) {
|
||||
const orgId = orgInterceptorProvider.getOrgId();
|
||||
if (orgId) {
|
||||
req.header.set(ORG_HEADER_KEY, orgId);
|
||||
}
|
||||
}
|
||||
|
||||
return next(req).catch(orgInterceptorProvider.handleError);
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OrgInterceptorProvider {
|
||||
constructor(private storageService: StorageService) {}
|
||||
|
||||
getOrgId() {
|
||||
const org: Org.AsObject | null = this.storageService.getItem(StorageKey.organization, StorageLocation.session);
|
||||
return org?.id;
|
||||
}
|
||||
|
||||
handleError = (error: any): never => {
|
||||
if (!(error instanceof RpcError) && !(error instanceof ConnectError)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
return await invoker(request);
|
||||
} catch (error: any) {
|
||||
if (
|
||||
error instanceof RpcError &&
|
||||
error.code === StatusCode.PERMISSION_DENIED &&
|
||||
@@ -28,7 +59,7 @@ export class OrgInterceptor<TReq = unknown, TResp = unknown> implements UnaryInt
|
||||
) {
|
||||
this.storageService.removeItem(StorageKey.organization, StorageLocation.session);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user