Files
zitadel/console/src/app/services/grpc.service.ts
Livio Spring 61c4b1c3fd 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
2025-03-14 06:05:45 +00:00

140 lines
5.5 KiB
TypeScript

import { PlatformLocation } from '@angular/common';
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { AuthConfig } from 'angular-oauth2-oidc';
import { catchError, firstValueFrom, switchMap, tap } from 'rxjs';
import { AdminServiceClient } from '../proto/generated/zitadel/AdminServiceClientPb';
import { AuthServiceClient } from '../proto/generated/zitadel/AuthServiceClientPb';
import { ManagementServiceClient } from '../proto/generated/zitadel/ManagementServiceClientPb';
import { fallbackLanguage, supportedLanguagesRegexp } from '../utils/language';
import { AuthenticationService } from './authentication.service';
import { EnvironmentService } from './environment.service';
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 { NewConnectWebOrgInterceptor, OrgInterceptor, OrgInterceptorProvider } from './interceptors/org.interceptor';
import { StorageService } from './storage.service';
import { UserServiceClient } from '../proto/generated/zitadel/user/v2/User_serviceServiceClientPb';
//@ts-ignore
import { createUserServiceClient } from '@zitadel/client/v2';
//@ts-ignore
import { createAuthServiceClient, createManagementServiceClient } from '@zitadel/client/v1';
import { createGrpcWebTransport } from '@connectrpc/connect-web';
import { FeatureServiceClient } from '../proto/generated/zitadel/feature/v2/Feature_serviceServiceClientPb';
@Injectable({
providedIn: 'root',
})
export class GrpcService {
public auth!: AuthServiceClient;
public mgmt!: ManagementServiceClient;
public admin!: AdminServiceClient;
public feature!: FeatureServiceClient;
public user!: UserServiceClient;
public userNew!: ReturnType<typeof createUserServiceClient>;
public mgmtNew!: ReturnType<typeof createManagementServiceClient>;
public authNew!: ReturnType<typeof createAuthServiceClient>;
constructor(
private readonly envService: EnvironmentService,
private readonly platformLocation: PlatformLocation,
private readonly authenticationService: AuthenticationService,
private readonly storageService: StorageService,
private readonly translate: TranslateService,
private readonly exhaustedService: ExhaustedService,
private readonly authInterceptor: AuthInterceptor,
private readonly authInterceptorProvider: AuthInterceptorProvider,
private readonly orgInterceptorProvider: OrgInterceptorProvider,
) {}
public loadAppEnvironment(): Promise<any> {
// We use the browser language until we can make API requests to get the users configured language.
const browserLanguage = this.translate.getBrowserLang();
const language = browserLanguage?.match(supportedLanguagesRegexp) ? browserLanguage : fallbackLanguage;
const init = this.translate.use(language || this.translate.defaultLang).pipe(
switchMap(() => this.envService.env),
tap((env) => {
if (!env?.api || !env?.issuer) {
return;
}
const interceptors = {
unaryInterceptors: [
new ExhaustedGrpcInterceptor(this.exhaustedService, this.envService),
new OrgInterceptor(this.orgInterceptorProvider),
this.authInterceptor,
new I18nInterceptor(this.translate),
],
};
this.auth = new AuthServiceClient(
env.api,
null,
// @ts-ignore
interceptors,
);
this.mgmt = new ManagementServiceClient(
env.api,
null,
// @ts-ignore
interceptors,
);
this.admin = new AdminServiceClient(
env.api,
null,
// @ts-ignore
interceptors,
);
this.feature = new FeatureServiceClient(
env.api,
null,
// @ts-ignore
interceptors,
);
this.user = new UserServiceClient(
env.api,
null,
// @ts-ignore
interceptors,
);
const transport = createGrpcWebTransport({
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(transportOldAPIs);
this.authNew = createAuthServiceClient(transport);
const authConfig: AuthConfig = {
scope: 'openid profile email',
responseType: 'code',
oidc: true,
clientId: env.clientid,
issuer: env.issuer,
redirectUri: window.location.origin + this.platformLocation.getBaseHrefFromDOM() + 'auth/callback',
postLogoutRedirectUri: window.location.origin + this.platformLocation.getBaseHrefFromDOM() + 'signedout',
requireHttps: false,
};
this.authenticationService.initConfig(authConfig);
}),
catchError((err) => {
console.error('Failed to load environment from assets', err);
throw err;
}),
);
return firstValueFrom(init);
}
}