mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-13 01:25:11 +00:00
fix(actions): allow adding raw metadata values via appendMetadataRaw (#12567)
# Which Problems Are Solved Since #10666 (v4.1+, backported to v4.x), metadata values set through actions v1 (`api.metadata.push` and `api.v1.user.appendMetadata`) are always JSON-encoded via `json.Marshal`. This made the write path consistent with the JSON-based read path, but removed the ability to store raw (unencoded) metadata values: - A scalar string is now always stored quoted (`"de"` instead of `de`). - The previous byte-array convention (mapping a string to an integer array in the script, handled by `mapBytesToByteArray` introduced in #5526) now stores the literal integer-array text (e.g. `[100,101]`) instead of the raw bytes. Customers migrating from v3.x whose downstream systems base64-decode metadata values from tokens and expect raw bytes have no way to produce them anymore — changing the consuming system is not always possible. Reverting the default is not an option either, as clients that adopted actions v1 on v4.x now rely on the JSON encoding. # How the Problems Are Solved - Adds a new, opt-in function `api.v1.user.appendMetadataRaw(key, value)` to the actions v1 login flows (external / internal authentication post authentication and pre creation), next to the existing `appendMetadata`. - The value is stored as raw bytes without JSON encoding: - a string is stored as its plain UTF-8 bytes (`de`, not `"de"`) - byte arrays (`Uint8Array` or a plain array of integers 0-255, the old convention) are stored as-is, so existing v3 scripts using a string-to-byte-array helper only need to switch the function name - other types (and empty values) throw an error - The existing `appendMetadata` and `api.metadata.push` behavior remains byte-for-byte unchanged. # Additional Changes - Documented `appendMetadataRaw` (and the JSON encoding behavior of `appendMetadata`) in the external and internal authentication actions docs. - Added unit tests for the new function (through a real goja runtime) and a test locking in the existing `appendMetadata` JSON-encoding behavior. # Additional Context - Regression introduced as a side effect of #10666 (which fixed #10470); the raw byte handling was originally introduced in #5526. - Reported by a customer upgrading from v3.4.x to v4.16.x, whose PostAuthentication action maps token payload claims into user metadata. - Requires backport to v4.x. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ba3a45bee4
commit
2bd42e8fc4
@@ -176,6 +176,53 @@ func (md *MetadataList) AppendMetadataFunc(call goja.FunctionCall) goja.Value {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppendMetadataRawFunc appends a metadata entry storing the value as raw bytes
|
||||
// without JSON encoding. In contrast to [MetadataList.AppendMetadataFunc], a string
|
||||
// is stored as its plain UTF-8 bytes (e.g. `de` instead of `"de"`).
|
||||
// Allowed values are strings and byte arrays (Uint8Array or an array of integers 0-255).
|
||||
func (md *MetadataList) AppendMetadataRawFunc(call goja.FunctionCall) goja.Value {
|
||||
if len(call.Arguments) != 2 {
|
||||
panic("exactly 2 (key, value) arguments expected")
|
||||
}
|
||||
|
||||
value := rawMetadataValue(call.Arguments[1].Export())
|
||||
if len(value) == 0 {
|
||||
panic("value must not be empty")
|
||||
}
|
||||
|
||||
md.metadata = append(md.metadata,
|
||||
&Metadata{
|
||||
Key: call.Arguments[0].Export().(string),
|
||||
Value: call.Arguments[1],
|
||||
value: value,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// rawMetadataValue converts an exported goja value to raw bytes.
|
||||
// Strings are converted to their UTF-8 bytes, Uint8Array is exported as []byte directly
|
||||
// and plain arrays must only contain integers in the range 0-255.
|
||||
func rawMetadataValue(v interface{}) []byte {
|
||||
switch value := v.(type) {
|
||||
case string:
|
||||
return []byte(value)
|
||||
case []byte:
|
||||
return value
|
||||
case []interface{}:
|
||||
bytes := make([]byte, len(value))
|
||||
for i, item := range value {
|
||||
b, ok := item.(int64)
|
||||
if !ok || b < 0 || b > 255 {
|
||||
panic("array value must only contain integers between 0 and 255")
|
||||
}
|
||||
bytes[i] = byte(b)
|
||||
}
|
||||
return bytes
|
||||
default:
|
||||
panic("value must be a string or byte array")
|
||||
}
|
||||
}
|
||||
|
||||
func (md *MetadataList) MetadataListFromDomain(runtime *goja.Runtime) interface{} {
|
||||
for i, metadata := range md.metadata {
|
||||
md.metadata[i].Value = metadataByteArrayToValue(metadata.value, runtime)
|
||||
|
||||
Reference in New Issue
Block a user