diff --git a/trusted_device.go b/trusted_device.go new file mode 100644 index 00000000..fdbabe97 --- /dev/null +++ b/trusted_device.go @@ -0,0 +1,24 @@ +package clerk + +// TrustedDevice represents a device enrolled by a user for trusted-device authentication. +type TrustedDevice struct { + APIResource + ID string `json:"id"` + Object string `json:"object"` + Platform string `json:"platform"` + AppIdentifier string `json:"app_identifier"` + Name *string `json:"name"` + Algorithm string `json:"algorithm"` + Status string `json:"status"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` + LastUsedAt *int64 `json:"last_used_at"` + RevokedAt *int64 `json:"revoked_at"` +} + +// TrustedDeviceList represents a list of a user's active trusted devices. +type TrustedDeviceList struct { + APIResource + TrustedDevices []*TrustedDevice `json:"data"` + TotalCount int64 `json:"total_count"` +} diff --git a/user/api.go b/user/api.go index 09b9ff16..468b2eb5 100644 --- a/user/api.go +++ b/user/api.go @@ -115,6 +115,16 @@ func DeletePasskey(ctx context.Context, userID, identificationID string) (*clerk return getClient().DeletePasskey(ctx, userID, identificationID) } +// ListTrustedDevices lists a user's active trusted devices. +func ListTrustedDevices(ctx context.Context, userID string) (*clerk.TrustedDeviceList, error) { + return getClient().ListTrustedDevices(ctx, userID) +} + +// RevokeTrustedDevice revokes a user's trusted device. +func RevokeTrustedDevice(ctx context.Context, userID, trustedDeviceID string) (*clerk.TrustedDevice, error) { + return getClient().RevokeTrustedDevice(ctx, userID, trustedDeviceID) +} + // DeleteWeb3Wallet deletes a web3 wallet by its identification ID. func DeleteWeb3Wallet(ctx context.Context, userID, identificationID string) (*clerk.DeletedResource, error) { return getClient().DeleteWeb3Wallet(ctx, userID, identificationID) diff --git a/user/client.go b/user/client.go index 7a07e531..890ec097 100644 --- a/user/client.go +++ b/user/client.go @@ -571,6 +571,30 @@ func (c *Client) DeletePasskey(ctx context.Context, userID, identificationID str return resource, err } +// ListTrustedDevices lists a user's active trusted devices. +func (c *Client) ListTrustedDevices(ctx context.Context, userID string) (*clerk.TrustedDeviceList, error) { + path, err := clerk.JoinPath(path, userID, "/trusted_devices") + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodGet, path) + resource := &clerk.TrustedDeviceList{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} + +// RevokeTrustedDevice revokes a user's trusted device. +func (c *Client) RevokeTrustedDevice(ctx context.Context, userID, trustedDeviceID string) (*clerk.TrustedDevice, error) { + path, err := clerk.JoinPath(path, userID, "/trusted_devices", trustedDeviceID) + if err != nil { + return nil, err + } + req := clerk.NewAPIRequest(http.MethodDelete, path) + resource := &clerk.TrustedDevice{} + err = c.Backend.Call(ctx, req, resource) + return resource, err +} + // DeleteWeb3Wallet deletes a web3 wallet by its identification ID. func (c *Client) DeleteWeb3Wallet(ctx context.Context, userID, identificationID string) (*clerk.DeletedResource, error) { path, err := clerk.JoinPath(path, userID, "/web3_wallets", identificationID) diff --git a/user/client_test.go b/user/client_test.go index 5877ff2c..01640cdf 100644 --- a/user/client_test.go +++ b/user/client_test.go @@ -601,6 +601,57 @@ func TestUserClientDeletePasskey(t *testing.T) { require.NoError(t, err) require.Equal(t, passkeyIdentificationID, passkey.ID) } + +func TestUserClientListTrustedDevices(t *testing.T) { + t.Parallel() + userID := "user_123" + trustedDeviceID := "tdc_345" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(fmt.Sprintf(`{"data":[{"id":"%s","object":"trusted_device","platform":"ios","app_identifier":"com.example.app","algorithm":"ES256","status":"active","created_at":1,"updated_at":2}],"total_count":1}`, trustedDeviceID)), + Method: http.MethodGet, + Path: "/v1/users/" + userID + "/trusted_devices", + }, + } + client := NewClient(config) + trustedDevices, err := client.ListTrustedDevices(context.Background(), userID) + require.NoError(t, err) + require.Equal(t, int64(1), trustedDevices.TotalCount) + require.Len(t, trustedDevices.TrustedDevices, 1) + require.Equal(t, trustedDeviceID, trustedDevices.TrustedDevices[0].ID) + require.Equal(t, "trusted_device", trustedDevices.TrustedDevices[0].Object) + require.Equal(t, "ios", trustedDevices.TrustedDevices[0].Platform) + require.Equal(t, "com.example.app", trustedDevices.TrustedDevices[0].AppIdentifier) + require.Equal(t, "ES256", trustedDevices.TrustedDevices[0].Algorithm) + require.Equal(t, "active", trustedDevices.TrustedDevices[0].Status) + require.Equal(t, int64(1), trustedDevices.TrustedDevices[0].CreatedAt) + require.Equal(t, int64(2), trustedDevices.TrustedDevices[0].UpdatedAt) +} + +func TestUserClientRevokeTrustedDevice(t *testing.T) { + t.Parallel() + userID := "user_123" + trustedDeviceID := "tdc_345" + config := &clerk.ClientConfig{} + config.HTTPClient = &http.Client{ + Transport: &clerktest.RoundTripper{ + T: t, + Out: json.RawMessage(fmt.Sprintf(`{"id":"%s","object":"trusted_device","platform":"ios","app_identifier":"com.example.app","algorithm":"ES256","status":"revoked","created_at":1,"updated_at":2,"revoked_at":2}`, trustedDeviceID)), + Method: http.MethodDelete, + Path: "/v1/users/" + userID + "/trusted_devices/" + trustedDeviceID, + }, + } + client := NewClient(config) + trustedDevice, err := client.RevokeTrustedDevice(context.Background(), userID, trustedDeviceID) + require.NoError(t, err) + require.Equal(t, trustedDeviceID, trustedDevice.ID) + require.Equal(t, "revoked", trustedDevice.Status) + require.NotNil(t, trustedDevice.RevokedAt) + require.Equal(t, int64(2), *trustedDevice.RevokedAt) +} + func TestUserClientDeleteWeb3Wallet(t *testing.T) { t.Parallel() userID := "user_123"