fix: metadata decoding and encoding #9816 (#10024)

# Which Problems Are Solved
Metadata encoding and decoding on the organization detail page was
broken due to use of the old, generated gRPC client.

# How the Problems Are Solved
The metadata values are now correctly base64 decoded and encoded on the
organization detail page.

# Additional Changes
Refactored parts of the code to remove the dependency on the buffer npm
package, replacing it with the browser-native TextEncoder and
TextDecoder APIs.

# Additional Context
- Closes [#9816](https://github.com/zitadel/zitadel/issues/9816)
This commit is contained in:
Ramon
2025-06-10 09:48:46 +02:00
committed by GitHub
parent 4df138286b
commit c1cda9bfac
4 changed files with 13 additions and 14 deletions

View File

@@ -4,7 +4,6 @@ import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { Metadata as MetadataV2 } from '@zitadel/proto/zitadel/metadata_pb'; import { Metadata as MetadataV2 } from '@zitadel/proto/zitadel/metadata_pb';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb'; import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
import { Buffer } from 'buffer';
export type MetadataDialogData = { export type MetadataDialogData = {
metadata: (Metadata.AsObject | MetadataV2)[]; metadata: (Metadata.AsObject | MetadataV2)[];
@@ -26,9 +25,10 @@ export class MetadataDialogComponent {
public dialogRef: MatDialogRef<MetadataDialogComponent>, public dialogRef: MatDialogRef<MetadataDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: MetadataDialogData, @Inject(MAT_DIALOG_DATA) public data: MetadataDialogData,
) { ) {
const decoder = new TextDecoder();
this.metadata = data.metadata.map(({ key, value }) => ({ this.metadata = data.metadata.map(({ key, value }) => ({
key, key,
value: typeof value === 'string' ? value : Buffer.from(value as unknown as string, 'base64').toString('utf8'), value: typeof value === 'string' ? value : decoder.decode(value),
})); }));
} }

View File

@@ -5,7 +5,6 @@ import { Observable, ReplaySubject } from 'rxjs';
import { Metadata as MetadataV2 } from '@zitadel/proto/zitadel/metadata_pb'; import { Metadata as MetadataV2 } from '@zitadel/proto/zitadel/metadata_pb';
import { map, startWith } from 'rxjs/operators'; import { map, startWith } from 'rxjs/operators';
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb'; import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
import { Buffer } from 'buffer';
type StringMetadata = { type StringMetadata = {
key: string; key: string;
@@ -37,12 +36,13 @@ export class MetadataComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.dataSource$ = this.metadata$.pipe( this.dataSource$ = this.metadata$.pipe(
map((metadata) => map((metadata) => {
metadata.map(({ key, value }) => ({ const decoder = new TextDecoder();
return metadata.map(({ key, value }) => ({
key, key,
value: Buffer.from(value as any as string, 'base64').toString('utf-8'), value: typeof value === 'string' ? value : decoder.decode(value),
})), }));
), }),
startWith([] as StringMetadata[]), startWith([] as StringMetadata[]),
map((metadata) => new MatTableDataSource(metadata)), map((metadata) => new MatTableDataSource(metadata)),
); );

View File

@@ -1,7 +1,6 @@
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Buffer } from 'buffer';
import { BehaviorSubject, from, Observable, of, Subject, takeUntil } from 'rxjs'; import { BehaviorSubject, from, Observable, of, Subject, takeUntil } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators'; import { catchError, finalize, map } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
@@ -266,10 +265,11 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
.listOrgMetadata() .listOrgMetadata()
.then((resp) => { .then((resp) => {
this.loadingMetadata = false; this.loadingMetadata = false;
this.metadata = resp.resultList.map((md) => { const decoder = new TextDecoder();
this.metadata = resp.resultList.map(({ key, value }) => {
return { return {
key: md.key, key,
value: Buffer.from(md.value as string, 'base64').toString('utf-8'), value: atob(typeof value === 'string' ? value : decoder.decode(value)),
}; };
}); });
}) })

View File

@@ -4,7 +4,6 @@ import { Validators } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Buffer } from 'buffer';
import { catchError, filter, map, startWith, take } from 'rxjs/operators'; import { catchError, filter, map, startWith, take } from 'rxjs/operators';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { phoneValidator, requiredValidator } from 'src/app/modules/form-field/validators/validators'; import { phoneValidator, requiredValidator } from 'src/app/modules/form-field/validators/validators';
@@ -582,7 +581,7 @@ export class UserDetailComponent implements OnInit {
const setFcn = (key: string, value: string) => const setFcn = (key: string, value: string) =>
this.newMgmtService.setUserMetadata({ this.newMgmtService.setUserMetadata({
key, key,
value: Buffer.from(value), value: new TextEncoder().encode(value),
id: user.userId, id: user.userId,
}); });
const removeFcn = (key: string): Promise<any> => this.newMgmtService.removeUserMetadata({ key, id: user.userId }); const removeFcn = (key: string): Promise<any> => this.newMgmtService.removeUserMetadata({ key, id: user.userId });