Skip to content

Commit ba1daa4

Browse files
authored
Merge pull request #14 from osodevops/feat/user-api-gap-with-docs
feat: implement missing fields on user objects
2 parents d9b2c45 + 05c4f32 commit ba1daa4

11 files changed

Lines changed: 530 additions & 45 deletions

File tree

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ resource "workos_user" "admin" {
7474
email = "admin@example.com"
7575
first_name = "Admin"
7676
last_name = "User"
77+
external_id = "admin-001"
7778
email_verified = true
79+
80+
metadata = {
81+
department = "Engineering"
82+
title = "Platform Lead"
83+
}
7884
}
7985
8086
resource "workos_organization_membership" "admin" {
@@ -156,6 +162,11 @@ data "workos_user" "john" {
156162
email = "john@example.com"
157163
}
158164
165+
# Look up user by external ID
166+
data "workos_user" "by_ext" {
167+
external_id = "admin-001"
168+
}
169+
159170
# Look up organization role by slug
160171
data "workos_organization_role" "billing" {
161172
organization_id = workos_organization.example.id
@@ -188,7 +199,7 @@ data "workos_permission" "billing_read" {
188199
| `workos_directory` | Retrieves directory by ID or organization (read-only) |
189200
| `workos_directory_user` | Retrieves directory-synced user |
190201
| `workos_directory_group` | Retrieves directory-synced group |
191-
| `workos_user` | Retrieves AuthKit user by ID or email |
202+
| `workos_user` | Retrieves AuthKit user by ID, email, or external ID |
192203
| `workos_organization_role` | Retrieves organization role by slug or ID |
193204
| `workos_permission` | Retrieves permission by slug |
194205

docs/data-sources/user.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ description: |-
2424
output "user_name" {
2525
value = "${data.workos_user.by_email.first_name} ${data.workos_user.by_email.last_name}"
2626
}
27+
28+
Lookup by External ID
29+
30+
data "workos_user" "by_external_id" {
31+
external_id = "ext-12345"
32+
}
2733
---
2834

2935
# workos_user (Data Source)
@@ -56,6 +62,14 @@ output "user_name" {
5662
}
5763
```
5864

65+
### Lookup by External ID
66+
67+
```hcl
68+
data "workos_user" "by_external_id" {
69+
external_id = "ext-12345"
70+
}
71+
```
72+
5973
## Example Usage
6074

6175
```terraform
@@ -80,14 +94,32 @@ output "user_full_name" {
8094
output "email_verified" {
8195
value = data.workos_user.by_email.email_verified
8296
}
97+
98+
# Look up a user by external ID
99+
data "workos_user" "by_external_id" {
100+
external_id = "ext-jane-001"
101+
}
102+
103+
output "external_user_email" {
104+
value = data.workos_user.by_external_id.email
105+
}
106+
107+
output "external_user_metadata" {
108+
value = data.workos_user.by_external_id.metadata
109+
}
110+
111+
output "external_user_locale" {
112+
value = data.workos_user.by_external_id.locale
113+
}
83114
```
84115

85116
<!-- schema generated by tfplugindocs -->
86117
## Schema
87118

88119
### Optional
89120

90-
- `email` (String) The user's email address. Either `id` or `email` must be specified.
121+
- `email` (String) The user's email address. Either `id`, `email`, or `external_id` must be specified.
122+
- `external_id` (String) An external identifier for the user. Can be used as a lookup key — either `id`, `email`, or `external_id` must be specified.
91123
- `id` (String) The unique identifier of the user (e.g., `user_01HXYZ...`). Either `id` or `email` must be specified.
92124

93125
### Read-Only
@@ -96,5 +128,7 @@ output "email_verified" {
96128
- `email_verified` (Boolean) Whether the user's email address has been verified.
97129
- `first_name` (String) The user's first name.
98130
- `last_name` (String) The user's last name.
131+
- `locale` (String) The user's locale (e.g., `en-US`).
132+
- `metadata` (Map of String) Custom metadata for the user as key-value string pairs.
99133
- `profile_picture_url` (String) URL of the user's profile picture.
100134
- `updated_at` (String) The timestamp when the user was last updated (RFC3339 format).

docs/resources/user.md

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ description: |-
1717
email_verified = true
1818
}
1919
20+
User with External ID and Metadata
21+
22+
resource "workos_user" "with_metadata" {
23+
email = "user@example.com"
24+
first_name = "Jane"
25+
last_name = "Smith"
26+
external_id = "ext-12345"
27+
email_verified = true
28+
29+
metadata = {
30+
department = "Engineering"
31+
timezone = "America/New_York"
32+
}
33+
}
34+
2035
User with Password
2136
2237
resource "workos_user" "with_password" {
@@ -57,6 +72,23 @@ resource "workos_user" "example" {
5772
}
5873
```
5974

75+
### User with External ID and Metadata
76+
77+
```hcl
78+
resource "workos_user" "with_metadata" {
79+
email = "user@example.com"
80+
first_name = "Jane"
81+
last_name = "Smith"
82+
external_id = "ext-12345"
83+
email_verified = true
84+
85+
metadata = {
86+
department = "Engineering"
87+
timezone = "America/New_York"
88+
}
89+
}
90+
```
91+
6092
### User with Password
6193

6294
```hcl
@@ -91,11 +123,25 @@ resource "workos_user" "basic" {
91123
email_verified = true
92124
}
93125
94-
# User with password authentication
95-
resource "workos_user" "with_password" {
126+
# User with external ID and metadata
127+
resource "workos_user" "with_metadata" {
96128
email = "jane@example.com"
97129
first_name = "Jane"
98130
last_name = "Smith"
131+
external_id = "ext-jane-001"
132+
email_verified = true
133+
134+
metadata = {
135+
department = "Engineering"
136+
timezone = "America/New_York"
137+
}
138+
}
139+
140+
# User with password authentication
141+
resource "workos_user" "with_password" {
142+
email = "password-user@example.com"
143+
first_name = "Password"
144+
last_name = "User"
99145
password = var.user_password
100146
email_verified = true
101147
}
@@ -115,11 +161,12 @@ resource "workos_user" "sso_user" {
115161
116162
# User with pre-hashed password (for migration)
117163
resource "workos_user" "migrated" {
118-
email = "migrated@example.com"
119-
first_name = "Migrated"
120-
last_name = "User"
121-
password_hash = var.bcrypt_password_hash
122-
email_verified = true
164+
email = "migrated@example.com"
165+
first_name = "Migrated"
166+
last_name = "User"
167+
password_hash = var.bcrypt_password_hash
168+
password_hash_type = "bcrypt"
169+
email_verified = true
123170
}
124171
125172
# Variables
@@ -145,6 +192,11 @@ output "user_email" {
145192
value = workos_user.basic.email
146193
description = "The email of the basic user"
147194
}
195+
196+
output "user_external_id" {
197+
value = workos_user.with_metadata.external_id
198+
description = "The external ID of the user with metadata"
199+
}
148200
```
149201

150202
<!-- schema generated by tfplugindocs -->
@@ -157,14 +209,18 @@ output "user_email" {
157209
### Optional
158210

159211
- `email_verified` (Boolean) Whether the user's email address has been verified. Defaults to `false`.
212+
- `external_id` (String) An external identifier for the user. Useful for mapping to identifiers in external systems.
160213
- `first_name` (String) The user's first name.
161214
- `last_name` (String) The user's last name.
215+
- `metadata` (Map of String) Custom metadata for the user as key-value string pairs.
162216
- `password` (String, Sensitive) The user's password. This is a write-only field and is not returned by the API. Only used during user creation.
163-
- `password_hash` (String, Sensitive) A pre-hashed password (bcrypt). This is a write-only field and is not returned by the API. Use this if you're migrating users with existing password hashes.
217+
- `password_hash` (String, Sensitive) A pre-hashed password (bcrypt or argon2). This is a write-only field and is not returned by the API. Use this if you're migrating users with existing password hashes.
218+
- `password_hash_type` (String) The type of password hash (e.g., `bcrypt`, `argon2`). This is a write-only field used only during creation alongside `password_hash`.
164219

165220
### Read-Only
166221

167222
- `created_at` (String) The timestamp when the user was created (RFC3339 format).
168223
- `id` (String) The unique identifier of the user (e.g., `user_01HXYZ...`).
224+
- `locale` (String) The user's locale (e.g., `en-US`). Set by the system based on user activity.
169225
- `profile_picture_url` (String) URL of the user's profile picture.
170226
- `updated_at` (String) The timestamp when the user was last updated (RFC3339 format).

examples/data-sources/workos_user/data-source.tf

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,20 @@ output "user_full_name" {
1919
output "email_verified" {
2020
value = data.workos_user.by_email.email_verified
2121
}
22+
23+
# Look up a user by external ID
24+
data "workos_user" "by_external_id" {
25+
external_id = "ext-jane-001"
26+
}
27+
28+
output "external_user_email" {
29+
value = data.workos_user.by_external_id.email
30+
}
31+
32+
output "external_user_metadata" {
33+
value = data.workos_user.by_external_id.metadata
34+
}
35+
36+
output "external_user_locale" {
37+
value = data.workos_user.by_external_id.locale
38+
}

examples/resources/workos_user/resource.tf

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,25 @@ resource "workos_user" "basic" {
66
email_verified = true
77
}
88

9-
# User with password authentication
10-
resource "workos_user" "with_password" {
9+
# User with external ID and metadata
10+
resource "workos_user" "with_metadata" {
1111
email = "jane@example.com"
1212
first_name = "Jane"
1313
last_name = "Smith"
14+
external_id = "ext-jane-001"
15+
email_verified = true
16+
17+
metadata = {
18+
department = "Engineering"
19+
timezone = "America/New_York"
20+
}
21+
}
22+
23+
# User with password authentication
24+
resource "workos_user" "with_password" {
25+
email = "password-user@example.com"
26+
first_name = "Password"
27+
last_name = "User"
1428
password = var.user_password
1529
email_verified = true
1630
}
@@ -30,11 +44,12 @@ resource "workos_user" "sso_user" {
3044

3145
# User with pre-hashed password (for migration)
3246
resource "workos_user" "migrated" {
33-
email = "migrated@example.com"
34-
first_name = "Migrated"
35-
last_name = "User"
36-
password_hash = var.bcrypt_password_hash
37-
email_verified = true
47+
email = "migrated@example.com"
48+
first_name = "Migrated"
49+
last_name = "User"
50+
password_hash = var.bcrypt_password_hash
51+
password_hash_type = "bcrypt"
52+
email_verified = true
3853
}
3954

4055
# Variables
@@ -60,3 +75,8 @@ output "user_email" {
6075
value = workos_user.basic.email
6176
description = "The email of the basic user"
6277
}
78+
79+
output "user_external_id" {
80+
value = workos_user.with_metadata.external_id
81+
description = "The external ID of the user with metadata"
82+
}

internal/client/models.go

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -139,33 +139,41 @@ type DirectoryGroup struct {
139139

140140
// User represents a WorkOS AuthKit User
141141
type User struct {
142-
ID string `json:"id"`
143-
Object string `json:"object"`
144-
Email string `json:"email"`
145-
EmailVerified bool `json:"email_verified"`
146-
FirstName string `json:"first_name,omitempty"`
147-
LastName string `json:"last_name,omitempty"`
148-
ProfilePictureURL string `json:"profile_picture_url,omitempty"`
149-
CreatedAt time.Time `json:"created_at"`
150-
UpdatedAt time.Time `json:"updated_at"`
142+
ID string `json:"id"`
143+
Object string `json:"object"`
144+
Email string `json:"email"`
145+
EmailVerified bool `json:"email_verified"`
146+
FirstName string `json:"first_name,omitempty"`
147+
LastName string `json:"last_name,omitempty"`
148+
ProfilePictureURL string `json:"profile_picture_url,omitempty"`
149+
ExternalID string `json:"external_id,omitempty"`
150+
Metadata map[string]string `json:"metadata,omitempty"`
151+
Locale string `json:"locale,omitempty"`
152+
CreatedAt time.Time `json:"created_at"`
153+
UpdatedAt time.Time `json:"updated_at"`
151154
}
152155

153156
// UserCreateRequest represents the request to create a user
154157
type UserCreateRequest struct {
155-
Email string `json:"email"`
156-
Password string `json:"password,omitempty"`
157-
PasswordHash string `json:"password_hash,omitempty"`
158-
FirstName string `json:"first_name,omitempty"`
159-
LastName string `json:"last_name,omitempty"`
160-
EmailVerified bool `json:"email_verified,omitempty"`
158+
Email string `json:"email"`
159+
Password string `json:"password,omitempty"`
160+
PasswordHash string `json:"password_hash,omitempty"`
161+
PasswordHashType string `json:"password_hash_type,omitempty"`
162+
FirstName string `json:"first_name,omitempty"`
163+
LastName string `json:"last_name,omitempty"`
164+
EmailVerified bool `json:"email_verified,omitempty"`
165+
ExternalID string `json:"external_id,omitempty"`
166+
Metadata map[string]string `json:"metadata,omitempty"`
161167
}
162168

163169
// UserUpdateRequest represents the request to update a user
164170
type UserUpdateRequest struct {
165-
Email string `json:"email,omitempty"`
166-
FirstName string `json:"first_name,omitempty"`
167-
LastName string `json:"last_name,omitempty"`
168-
EmailVerified *bool `json:"email_verified,omitempty"`
171+
Email string `json:"email,omitempty"`
172+
FirstName string `json:"first_name,omitempty"`
173+
LastName string `json:"last_name,omitempty"`
174+
EmailVerified *bool `json:"email_verified,omitempty"`
175+
ExternalID string `json:"external_id,omitempty"`
176+
Metadata map[string]string `json:"metadata,omitempty"`
169177
}
170178

171179
// OrganizationMembership represents a user's membership in an organization

internal/client/users.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ func (c *Client) ListUsers(ctx context.Context, email string, organizationID str
8282
return &resp, nil
8383
}
8484

85+
// GetUserByExternalID retrieves a user by external ID
86+
func (c *Client) GetUserByExternalID(ctx context.Context, externalID string) (*User, error) {
87+
var user User
88+
err := c.Get(ctx, "/user_management/users/external_id/"+externalID, &user)
89+
if err != nil {
90+
return nil, fmt.Errorf("failed to get user by external ID: %w", err)
91+
}
92+
return &user, nil
93+
}
94+
8595
// GetUserByEmail retrieves a user by email
8696
func (c *Client) GetUserByEmail(ctx context.Context, email string) (*User, error) {
8797
resp, err := c.ListUsers(ctx, email, "")

0 commit comments

Comments
 (0)