Repository: zitadel/oidc (go)

Issue:
`invalid memory address or nil pointer dereference` when accessing UserInfoAddress
**Describe the bug**
When user info is missing address field it's decoded into `userinfo` struct where `Address` field has `UserInfoAddress` type but `nil` value.

`UserInfoAddress` type doesn't expect `nil` as receiver, so code like `info.GetAddress().GetCountry()` panics.

**To Reproduce**
Steps to reproduce the behavior:
1. Unmarshal userinfo without address
2. Try `info.GetAddress().GetCountry()`
3. See panic

**Expected behavior**
Probably, returning default values for all fields (e.g. `""`).

Another (IMO better but breaking) way is to return a pointer. So it will be possible to check value `GetAddress() == nil`

**Additional context**
Temp workaround:
```go
func isNil(val any) bool {
	return val == nil || (reflect.ValueOf(val).Kind() == reflect.Ptr && reflect.ValueOf(val).IsNil())
}

addr := info.GetAddress()
if !isNil(addr) {
  // ...
}
```

Pull request:
fix: nil pointer dereference on UserInfoAddress
When the Address field in userinfo is nil, an empty instance of userInfoAddress is now returned. This allows for safe chaining of Get methods. All of an empty userInfoAddress' Get methods will return empty strings.

Fixes #203

Patch:
diff --git a/pkg/oidc/userinfo.go b/pkg/oidc/userinfo.go
index 4d524e3..c8e34d6 100644
--- a/pkg/oidc/userinfo.go
+++ b/pkg/oidc/userinfo.go
@@ -167,6 +167,9 @@ func (u *userinfo) IsPhoneNumberVerified() bool {
 }
 
 func (u *userinfo) GetAddress() UserInfoAddress {
+	if u.Address == nil {
+		return &userInfoAddress{}
+	}
 	return u.Address
 }
 
@@ -389,7 +392,11 @@ func (u *userinfo) UnmarshalJSON(data []byte) error {
 	if err := json.Unmarshal(data, &a); err != nil {
 		return err
 	}
-	u.Address = a.Address
+
+	if a.Address != nil {
+		u.Address = a.Address
+	}
+
 	u.UpdatedAt = Time(time.Unix(a.UpdatedAt, 0).UTC())
 
 	if err := json.Unmarshal(data, &u.claims); err != nil {


Test patch:
diff --git a/pkg/oidc/userinfo_test.go b/pkg/oidc/userinfo_test.go
index f42ff3d..319a2fd 100644
--- a/pkg/oidc/userinfo_test.go
+++ b/pkg/oidc/userinfo_test.go
@@ -81,3 +81,39 @@ func TestUserInfoEmailVerifiedUnmarshal(t *testing.T) {
 		}, uie)
 	})
 }
+
+// issue 203 test case.
+func Test_userinfo_GetAddress_issue_203(t *testing.T) {
+	tests := []struct {
+		name string
+		data string
+	}{
+		{
+			name: "with address",
+			data: `{"address":{"street_address":"Test 789\nPostfach 2"},"email":"test","email_verified":true,"name":"Test","phone_number":"0791234567","phone_number_verified":true,"private_claim":"test","sub":"test"}`,
+		},
+		{
+			name: "without address",
+			data: `{"email":"test","email_verified":true,"name":"Test","phone_number":"0791234567","phone_number_verified":true,"private_claim":"test","sub":"test"}`,
+		},
+		{
+			name: "null address",
+			data: `{"address":null,"email":"test","email_verified":true,"name":"Test","phone_number":"0791234567","phone_number_verified":true,"private_claim":"test","sub":"test"}`,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			info := &userinfo{}
+			err := json.Unmarshal([]byte(tt.data), info)
+			assert.NoError(t, err)
+
+			info.GetAddress().GetCountry() //<- used to panic
+
+			// now shortly assure that a marshalling still produces the same as was parsed into the struct
+			marshal, err := json.Marshal(info)
+			assert.NoError(t, err)
+			assert.Equal(t, tt.data, string(marshal))
+		})
+	}
+}