From 185a43bac8c3245e7d0b1f506f90fe06efb62f02 Mon Sep 17 00:00:00 2001
From: Steven Weathers <steven@weathers.me>
Date: Sun, 22 Sep 2024 21:55:16 -0400
Subject: [PATCH] Add unit tests for middleware

---
 internal/http/checkin/checkin.go              |   8 +-
 internal/http/middleware_test.go              | 582 ++++++++++++++++--
 internal/http/poker/poker.go                  |   8 +-
 internal/http/retro/retro.go                  |   8 +-
 internal/http/storyboard/storyboard.go        |   8 +-
 internal/http/types.go                        |  24 +-
 internal/http/user.go                         |  16 +-
 internal/webhook/subscription/subscription.go |   8 +-
 thunderdome/user.go                           |  30 +-
 9 files changed, 603 insertions(+), 89 deletions(-)

diff --git a/internal/http/checkin/checkin.go b/internal/http/checkin/checkin.go
index b5ab1312..983ca4dc 100644
--- a/internal/http/checkin/checkin.go
+++ b/internal/http/checkin/checkin.go
@@ -58,6 +58,10 @@ type TeamDataSvc interface {
 	TeamGet(ctx context.Context, TeamID string) (*thunderdome.Team, error)
 }
 
+type UserDataSvc interface {
+	GetGuestUser(ctx context.Context, UserID string) (*thunderdome.User, error)
+}
+
 // Service provides retro service
 type Service struct {
 	config                Config
@@ -65,7 +69,7 @@ type Service struct {
 	validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error)
 	validateUserCookie    func(w http.ResponseWriter, r *http.Request) (string, error)
 	eventHandlers         map[string]func(context.Context, string, string, string) ([]byte, error, bool)
-	UserService           thunderdome.UserDataSvc
+	UserService           UserDataSvc
 	AuthService           AuthDataSvc
 	CheckinService        CheckinDataSvc
 	TeamService           TeamDataSvc
@@ -77,7 +81,7 @@ func New(
 	logger *otelzap.Logger,
 	validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error),
 	validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error),
-	userService thunderdome.UserDataSvc, authService AuthDataSvc,
+	userService UserDataSvc, authService AuthDataSvc,
 	checkinService CheckinDataSvc, teamService TeamDataSvc,
 ) *Service {
 	c := &Service{
diff --git a/internal/http/middleware_test.go b/internal/http/middleware_test.go
index bf2df9df..c9de7986 100644
--- a/internal/http/middleware_test.go
+++ b/internal/http/middleware_test.go
@@ -2,6 +2,7 @@ package http
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
@@ -189,7 +190,7 @@ func TestTeamUserOnly(t *testing.T) {
 		{
 			name:     "Valid team user",
 			userID:   "2d6176c8-50d6-4963-8172-2c20ca5022a3",
-			userType: "REGISTERED",
+			userType: thunderdome.RegisteredUserType,
 			teamID:   "128ee064-62ca-43b2-9fca-9c1089c89bd2",
 			setupMocks: func(mtds *MockTeamDataSvc, ml *MockLogger) {
 				mtds.On(
@@ -221,7 +222,7 @@ func TestTeamUserOnly(t *testing.T) {
 		{
 			name:     "Department admin",
 			userID:   "6a12ef8c-1140-4faa-a505-22910a7593f9",
-			userType: "REGISTERED",
+			userType: thunderdome.RegisteredUserType,
 			teamID:   "7d4d0a17-cb20-4499-bc7f-ecaf2e77c15a",
 			setupMocks: func(mtds *MockTeamDataSvc, ml *MockLogger) {
 				deptRole := thunderdome.AdminUserType
@@ -238,7 +239,7 @@ func TestTeamUserOnly(t *testing.T) {
 		{
 			name:     "Organization admin",
 			userID:   "31c8521e-2e68-4898-b3cf-e919cf80dbe2",
-			userType: "REGISTERED",
+			userType: thunderdome.RegisteredUserType,
 			teamID:   "0ea230df-b5fe-47ae-a473-5153004eebdd",
 			setupMocks: func(mtds *MockTeamDataSvc, ml *MockLogger) {
 				orgRole := thunderdome.AdminUserType
@@ -255,7 +256,7 @@ func TestTeamUserOnly(t *testing.T) {
 		{
 			name:     "Invalid team ID",
 			userID:   "1353a056-d239-41e1-ad1a-b3f0777e6c3a",
-			userType: "REGISTERED",
+			userType: thunderdome.RegisteredUserType,
 			teamID:   "invalid-team-id",
 			setupMocks: func(mtds *MockTeamDataSvc, ml *MockLogger) {
 				ml.On("Ctx", mock.Anything).Return(zap.NewNop())
@@ -265,7 +266,7 @@ func TestTeamUserOnly(t *testing.T) {
 		{
 			name:     "Team not found",
 			userID:   "1b853ef6-2c28-4c8e-ac29-a9d4827774fe",
-			userType: "REGISTERED",
+			userType: thunderdome.RegisteredUserType,
 			teamID:   "e52b9251-4722-4b4d-8f97-204ce7e51eec",
 			setupMocks: func(mtds *MockTeamDataSvc, ml *MockLogger) {
 				mtds.On(
@@ -281,7 +282,7 @@ func TestTeamUserOnly(t *testing.T) {
 		{
 			name:     "Unauthorized user",
 			userID:   "a805def1-e1fa-42a9-b5f6-ee338799fa77",
-			userType: "REGISTERED",
+			userType: thunderdome.RegisteredUserType,
 			teamID:   "a805def1-e1fa-42a9-b5f6-ee338799fa77",
 			setupMocks: func(mtds *MockTeamDataSvc, ml *MockLogger) {
 				mtds.On("TeamUserRoles",
@@ -372,7 +373,7 @@ func TestTeamAdminOnly(t *testing.T) {
 		{
 			name:             "Non-Admin User",
 			userType:         thunderdome.RegisteredUserType,
-			teamRole:         ptr("MEMBER"),
+			teamRole:         ptr(thunderdome.EntityMemberUserType),
 			departmentRole:   nil,
 			organizationRole: nil,
 			expectedStatus:   http.StatusForbidden,
@@ -447,7 +448,7 @@ func TestSubscribedTeamOnly(t *testing.T) {
 		},
 		{
 			name:                 "Subscribed team allowed",
-			userType:             "MEMBER",
+			userType:             thunderdome.EntityMemberUserType,
 			teamID:               "2d6176c8-50d6-4963-8172-2c20ca5022a3",
 			subscriptionsEnabled: true,
 			expectedStatusCode:   http.StatusOK,
@@ -461,7 +462,7 @@ func TestSubscribedTeamOnly(t *testing.T) {
 		},
 		{
 			name:                 "Unsubscribed team forbidden",
-			userType:             "MEMBER",
+			userType:             thunderdome.EntityMemberUserType,
 			teamID:               "128ee064-62ca-43b2-9fca-9c1089c89bd2",
 			subscriptionsEnabled: true,
 			expectedStatusCode:   http.StatusForbidden,
@@ -475,13 +476,13 @@ func TestSubscribedTeamOnly(t *testing.T) {
 		},
 		{
 			name:               "Invalid teamID",
-			userType:           "MEMBER",
+			userType:           thunderdome.EntityMemberUserType,
 			teamID:             "invalid-uuid",
 			expectedStatusCode: http.StatusBadRequest,
 		},
 		{
 			name:                 "Subscriptions disabled",
-			userType:             "MEMBER",
+			userType:             thunderdome.EntityMemberUserType,
 			teamID:               "31c8521e-2e68-4898-b3cf-e919cf80dbe2",
 			subscriptionsEnabled: false,
 			expectedStatusCode:   http.StatusOK,
@@ -744,7 +745,7 @@ func TestSubscribedOrgOnly(t *testing.T) {
 		},
 		{
 			name:                 "Subscribed organization allowed",
-			userType:             "MEMBER",
+			userType:             thunderdome.EntityMemberUserType,
 			orgID:                "2d6176c8-50d6-4963-8172-2c20ca5022a3",
 			subscriptionsEnabled: true,
 			isSubscribed:         true,
@@ -759,7 +760,7 @@ func TestSubscribedOrgOnly(t *testing.T) {
 		},
 		{
 			name:                 "Unsubscribed organization forbidden",
-			userType:             "MEMBER",
+			userType:             thunderdome.EntityMemberUserType,
 			orgID:                "128ee064-62ca-43b2-9fca-9c1089c89bd2",
 			subscriptionsEnabled: true,
 			isSubscribed:         false,
@@ -774,13 +775,13 @@ func TestSubscribedOrgOnly(t *testing.T) {
 		},
 		{
 			name:               "Invalid orgID",
-			userType:           "MEMBER",
+			userType:           thunderdome.EntityMemberUserType,
 			orgID:              "invalid-uuid",
 			expectedStatusCode: http.StatusBadRequest,
 		},
 		{
 			name:                 "Subscriptions disabled",
-			userType:             "MEMBER",
+			userType:             thunderdome.EntityMemberUserType,
 			orgID:                "31c8521e-2e68-4898-b3cf-e919cf80dbe2",
 			subscriptionsEnabled: false,
 			isSubscribed:         false,
@@ -850,7 +851,7 @@ func TestDepartmentAdminOnly(t *testing.T) {
 			orgID:              "00241406-94cb-4fe1-9b4a-e979f5761f10",
 			departmentID:       "0024d5f8-42b1-46c7-b5a7-da97d59d6b36",
 			expectedStatusCode: http.StatusOK,
-			expectedOrgRole:    "MEMBER",
+			expectedOrgRole:    thunderdome.EntityMemberUserType,
 			expectedDeptRole:   thunderdome.AdminUserType,
 			setupMocks: func(mockOrgDataSvc *MockOrganizationDataService) {
 				mockOrgDataSvc.On(
@@ -859,7 +860,7 @@ func TestDepartmentAdminOnly(t *testing.T) {
 					"0023f0d5-19d0-403f-a30d-d5b7616c72bd",
 					"00241406-94cb-4fe1-9b4a-e979f5761f10",
 					"0024d5f8-42b1-46c7-b5a7-da97d59d6b36",
-				).Return("MEMBER", thunderdome.AdminUserType, nil).Once()
+				).Return(thunderdome.EntityMemberUserType, thunderdome.AdminUserType, nil).Once()
 			},
 		},
 		{
@@ -895,7 +896,7 @@ func TestDepartmentAdminOnly(t *testing.T) {
 					"3f3d4ca5-6eae-4372-81ba-de8bbaa2dac2",
 					"2d6176c8-50d6-4963-8172-2c20ca5022a3",
 					"002738c2-fcf2-438e-a755-2bf9c4233b74",
-				).Return("MEMBER", "MEMBER", nil).Once()
+				).Return(thunderdome.EntityMemberUserType, thunderdome.EntityMemberUserType, nil).Once()
 			},
 		},
 		{
@@ -1000,8 +1001,8 @@ func TestDepartmentUserOnly(t *testing.T) {
 			orgID:              "128ee064-62ca-43b2-9fca-9c1089c89bd2",
 			departmentID:       "31c8521e-2e68-4898-b3cf-e919cf80dbe2",
 			expectedStatusCode: http.StatusOK,
-			expectedOrgRole:    "MEMBER",
-			expectedDeptRole:   "MEMBER",
+			expectedOrgRole:    thunderdome.EntityMemberUserType,
+			expectedDeptRole:   thunderdome.EntityMemberUserType,
 			setupMocks: func(mockOrgDataSvc *MockOrganizationDataService, mockLogger *MockLogger) {
 				mockOrgDataSvc.On(
 					"DepartmentUserRole",
@@ -1009,7 +1010,7 @@ func TestDepartmentUserOnly(t *testing.T) {
 					"ea840339-2e16-4c10-8744-33ee1b636596",
 					"128ee064-62ca-43b2-9fca-9c1089c89bd2",
 					"31c8521e-2e68-4898-b3cf-e919cf80dbe2",
-				).Return("MEMBER", "MEMBER", nil).Once()
+				).Return(thunderdome.EntityMemberUserType, thunderdome.EntityMemberUserType, nil).Once()
 			},
 		},
 		{
@@ -1019,7 +1020,7 @@ func TestDepartmentUserOnly(t *testing.T) {
 			orgID:              "2d6176c8-50d6-4963-8172-2c20ca5022a3",
 			departmentID:       "002738c2-fcf2-438e-a755-2bf9c4233b74",
 			expectedStatusCode: http.StatusOK,
-			expectedOrgRole:    "MEMBER",
+			expectedOrgRole:    thunderdome.EntityMemberUserType,
 			expectedDeptRole:   thunderdome.AdminUserType,
 			setupMocks: func(mockOrgDataSvc *MockOrganizationDataService, mockLogger *MockLogger) {
 				mockOrgDataSvc.On(
@@ -1028,7 +1029,7 @@ func TestDepartmentUserOnly(t *testing.T) {
 					"3f3d4ca5-6eae-4372-81ba-de8bbaa2dac2",
 					"2d6176c8-50d6-4963-8172-2c20ca5022a3",
 					"002738c2-fcf2-438e-a755-2bf9c4233b74",
-				).Return("MEMBER", thunderdome.AdminUserType, nil).Once()
+				).Return(thunderdome.EntityMemberUserType, thunderdome.AdminUserType, nil).Once()
 			},
 		},
 		{
@@ -1045,7 +1046,7 @@ func TestDepartmentUserOnly(t *testing.T) {
 					"0014c2dc-3e89-4369-857d-b420f5786eff",
 					"0019f42b-de8f-41ee-904e-3cb6c9ddc8f4",
 					"001a6acb-c174-43f0-9930-0b1242931123",
-				).Return("MEMBER", "", nil).Once()
+				).Return(thunderdome.EntityMemberUserType, "", nil).Once()
 			},
 		},
 		{
@@ -1164,18 +1165,18 @@ func TestOrgAdminOnly(t *testing.T) {
 		{
 			name:            "Non-Admin User",
 			userID:          "323e4567-e89b-12d3-a456-426614174000",
-			userType:        "REGULAR",
+			userType:        thunderdome.RegisteredUserType,
 			orgID:           "323e4567-e89b-12d3-a456-426614174001",
-			expectedOrgRole: "MEMBER",
+			expectedOrgRole: thunderdome.EntityMemberUserType,
 			expectedStatus:  http.StatusForbidden,
 			mockSetup: func(mockOrgDataSvc *MockOrganizationDataService) {
-				mockOrgDataSvc.On("OrganizationUserRole", mock.Anything, "323e4567-e89b-12d3-a456-426614174000", "323e4567-e89b-12d3-a456-426614174001").Return("MEMBER", nil)
+				mockOrgDataSvc.On("OrganizationUserRole", mock.Anything, "323e4567-e89b-12d3-a456-426614174000", "323e4567-e89b-12d3-a456-426614174001").Return(thunderdome.EntityMemberUserType, nil)
 			},
 		},
 		{
 			name:           "User Not in Organization",
 			userID:         "423e4567-e89b-12d3-a456-426614174000",
-			userType:       "REGULAR",
+			userType:       thunderdome.RegisteredUserType,
 			orgID:          "423e4567-e89b-12d3-a456-426614174001",
 			expectedStatus: http.StatusForbidden,
 			mockSetup: func(mockOrgDataSvc *MockOrganizationDataService) {
@@ -1190,7 +1191,7 @@ func TestOrgAdminOnly(t *testing.T) {
 		{
 			name:           "Invalid OrgID",
 			userID:         "523e4567-e89b-12d3-a456-426614174000",
-			userType:       "REGULAR",
+			userType:       thunderdome.RegisteredUserType,
 			orgID:          "invalid-org-id",
 			expectedStatus: http.StatusBadRequest,
 			mockSetup:      func(mockOrgDataSvc *MockOrganizationDataService) {},
@@ -1278,9 +1279,9 @@ func TestOrgUserOnly(t *testing.T) {
 		{
 			name:            "Valid Org User",
 			userID:          "223e4567-e89b-12d3-a456-426614174000",
-			userType:        "REGULAR",
+			userType:        thunderdome.RegisteredUserType,
 			orgID:           "223e4567-e89b-12d3-a456-426614174001",
-			expectedOrgRole: "MEMBER",
+			expectedOrgRole: thunderdome.EntityMemberUserType,
 			expectedStatus:  http.StatusOK,
 			mockSetup: func(mockOrgDataSvc *MockOrganizationDataService) {
 				mockOrgDataSvc.On(
@@ -1292,13 +1293,13 @@ func TestOrgUserOnly(t *testing.T) {
 					mock.Anything,
 					"223e4567-e89b-12d3-a456-426614174000",
 					"223e4567-e89b-12d3-a456-426614174001",
-				).Return("MEMBER", nil)
+				).Return(thunderdome.EntityMemberUserType, nil)
 			},
 		},
 		{
 			name:           "User Not in Organization",
 			userID:         "323e4567-e89b-12d3-a456-426614174000",
-			userType:       "REGULAR",
+			userType:       thunderdome.RegisteredUserType,
 			orgID:          "323e4567-e89b-12d3-a456-426614174001",
 			expectedStatus: http.StatusForbidden,
 			mockSetup: func(mockOrgDataSvc *MockOrganizationDataService) {
@@ -1318,7 +1319,7 @@ func TestOrgUserOnly(t *testing.T) {
 		{
 			name:           "Invalid OrgID",
 			userID:         "423e4567-e89b-12d3-a456-426614174000",
-			userType:       "REGULAR",
+			userType:       thunderdome.RegisteredUserType,
 			orgID:          "invalid-org-id",
 			expectedStatus: http.StatusBadRequest,
 			mockSetup:      func(mockOrgDataSvc *MockOrganizationDataService) {},
@@ -1326,7 +1327,7 @@ func TestOrgUserOnly(t *testing.T) {
 		{
 			name:           "Organization Not Found",
 			userID:         "523e4567-e89b-12d3-a456-426614174000",
-			userType:       "REGULAR",
+			userType:       thunderdome.RegisteredUserType,
 			orgID:          "523e4567-e89b-12d3-a456-426614174001",
 			expectedStatus: http.StatusNotFound,
 			mockSetup: func(mockOrgDataSvc *MockOrganizationDataService) {
@@ -1340,7 +1341,7 @@ func TestOrgUserOnly(t *testing.T) {
 		{
 			name:           "Internal Server Error",
 			userID:         "623e4567-e89b-12d3-a456-426614174000",
-			userType:       "REGULAR",
+			userType:       thunderdome.RegisteredUserType,
 			orgID:          "623e4567-e89b-12d3-a456-426614174001",
 			expectedStatus: http.StatusInternalServerError,
 			mockSetup: func(mockOrgDataSvc *MockOrganizationDataService) {
@@ -1418,7 +1419,7 @@ func TestSubscribedUserOnly(t *testing.T) {
 		{
 			name:                 "Subscriptions Disabled",
 			userID:               "123e4567-e89b-12d3-a456-426614174000",
-			userType:             "REGULAR",
+			userType:             thunderdome.RegisteredUserType,
 			subscriptionsEnabled: false,
 			expectedStatus:       http.StatusOK,
 			mockSetup:            func(mockSubDataSvc *MockSubscriptionDataService) {},
@@ -1434,7 +1435,7 @@ func TestSubscribedUserOnly(t *testing.T) {
 		{
 			name:                 "Subscribed Regular User",
 			userID:               "323e4567-e89b-12d3-a456-426614174000",
-			userType:             "REGULAR",
+			userType:             thunderdome.RegisteredUserType,
 			subscriptionsEnabled: true,
 			expectedStatus:       http.StatusOK,
 			mockSetup: func(mockSubDataSvc *MockSubscriptionDataService) {
@@ -1444,7 +1445,7 @@ func TestSubscribedUserOnly(t *testing.T) {
 		{
 			name:                 "Unsubscribed Regular User",
 			userID:               "423e4567-e89b-12d3-a456-426614174000",
-			userType:             "REGULAR",
+			userType:             thunderdome.RegisteredUserType,
 			subscriptionsEnabled: true,
 			expectedStatus:       http.StatusForbidden,
 			mockSetup: func(mockSubDataSvc *MockSubscriptionDataService) {
@@ -1521,7 +1522,7 @@ func TestSubscribedEntityUserOnly(t *testing.T) {
 		{
 			name:                 "Matching User ID",
 			userID:               "323e4567-e89b-12d3-a456-426614174000",
-			userType:             "REGULAR",
+			userType:             thunderdome.RegisteredUserType,
 			entityUserID:         "323e4567-e89b-12d3-a456-426614174000",
 			subscriptionsEnabled: true,
 			expectedStatus:       http.StatusOK,
@@ -1532,7 +1533,7 @@ func TestSubscribedEntityUserOnly(t *testing.T) {
 		{
 			name:                 "Non-Matching User ID",
 			userID:               "423e4567-e89b-12d3-a456-426614174000",
-			userType:             "REGULAR",
+			userType:             thunderdome.RegisteredUserType,
 			entityUserID:         "523e4567-e89b-12d3-a456-426614174000",
 			subscriptionsEnabled: true,
 			expectedStatus:       http.StatusForbidden,
@@ -1541,7 +1542,7 @@ func TestSubscribedEntityUserOnly(t *testing.T) {
 		{
 			name:                 "Subscriptions Disabled",
 			userID:               "623e4567-e89b-12d3-a456-426614174000",
-			userType:             "REGULAR",
+			userType:             thunderdome.RegisteredUserType,
 			entityUserID:         "623e4567-e89b-12d3-a456-426614174000",
 			subscriptionsEnabled: false,
 			expectedStatus:       http.StatusOK,
@@ -1550,7 +1551,7 @@ func TestSubscribedEntityUserOnly(t *testing.T) {
 		{
 			name:                 "Unsubscribed User",
 			userID:               "723e4567-e89b-12d3-a456-426614174000",
-			userType:             "REGULAR",
+			userType:             thunderdome.RegisteredUserType,
 			entityUserID:         "723e4567-e89b-12d3-a456-426614174000",
 			subscriptionsEnabled: true,
 			expectedStatus:       http.StatusForbidden,
@@ -1561,7 +1562,7 @@ func TestSubscribedEntityUserOnly(t *testing.T) {
 		{
 			name:                 "Invalid Entity User ID",
 			userID:               "823e4567-e89b-12d3-a456-426614174000",
-			userType:             "REGULAR",
+			userType:             thunderdome.RegisteredUserType,
 			entityUserID:         "invalid-user-id",
 			subscriptionsEnabled: true,
 			expectedStatus:       http.StatusBadRequest,
@@ -1662,3 +1663,496 @@ func (m *MockSubscriptionDataService) CheckActiveSubscriber(ctx context.Context,
 	args := m.Called(ctx, userID)
 	return args.Error(0)
 }
+
+func TestVerifiedUserOnly(t *testing.T) {
+	tests := []struct {
+		name                      string
+		sessionUserID             string
+		userType                  string
+		entityUserID              string
+		externalAPIVerifyRequired bool
+		userVerified              bool
+		expectedStatus            int
+		mockSetup                 func(mockUserDataSvc *MockUserDataService)
+	}{
+		{
+			name:                      "Admin User (Entity User is Verified)",
+			sessionUserID:             "123e4567-e89b-12d3-a456-426614174000",
+			userType:                  thunderdome.AdminUserType,
+			entityUserID:              "223e4567-e89b-12d3-a456-426614174000",
+			externalAPIVerifyRequired: true,
+			userVerified:              true,
+			expectedStatus:            http.StatusOK,
+			mockSetup: func(mockUserDataSvc *MockUserDataService) {
+				mockUserDataSvc.On(
+					"GetUser",
+					mock.Anything,
+					"223e4567-e89b-12d3-a456-426614174000",
+				).Return(&thunderdome.User{Verified: true}, nil)
+			},
+		},
+		{
+			name:                      "Admin User (Entity User is Not Verified)",
+			sessionUserID:             "123e4567-e89b-12d3-a456-426614174001",
+			userType:                  thunderdome.AdminUserType,
+			entityUserID:              "223e4567-e89b-12d3-a456-426614174001",
+			externalAPIVerifyRequired: true,
+			userVerified:              false,
+			expectedStatus:            http.StatusForbidden,
+			mockSetup: func(mockUserDataSvc *MockUserDataService) {
+				mockUserDataSvc.On(
+					"GetUser",
+					mock.Anything,
+					"223e4567-e89b-12d3-a456-426614174001",
+				).Return(&thunderdome.User{Verified: false}, nil)
+			},
+		},
+		{
+			name:                      "Matching Verified User",
+			sessionUserID:             "323e4567-e89b-12d3-a456-426614174000",
+			userType:                  thunderdome.RegisteredUserType,
+			entityUserID:              "323e4567-e89b-12d3-a456-426614174000",
+			externalAPIVerifyRequired: true,
+			userVerified:              true,
+			expectedStatus:            http.StatusOK,
+			mockSetup: func(mockUserDataSvc *MockUserDataService) {
+				mockUserDataSvc.On("GetUser", mock.Anything, "323e4567-e89b-12d3-a456-426614174000").Return(&thunderdome.User{Verified: true}, nil)
+			},
+		},
+		{
+			name:                      "Matching Unverified User",
+			sessionUserID:             "423e4567-e89b-12d3-a456-426614174000",
+			userType:                  thunderdome.RegisteredUserType,
+			entityUserID:              "423e4567-e89b-12d3-a456-426614174000",
+			externalAPIVerifyRequired: true,
+			userVerified:              false,
+			expectedStatus:            http.StatusForbidden,
+			mockSetup: func(mockUserDataSvc *MockUserDataService) {
+				mockUserDataSvc.On("GetUser", mock.Anything, "423e4567-e89b-12d3-a456-426614174000").Return(&thunderdome.User{Verified: false}, nil)
+			},
+		},
+		{
+			name:                      "Non-Matching User ID",
+			sessionUserID:             "523e4567-e89b-12d3-a456-426614174000",
+			userType:                  thunderdome.RegisteredUserType,
+			entityUserID:              "623e4567-e89b-12d3-a456-426614174000",
+			externalAPIVerifyRequired: true,
+			userVerified:              true,
+			expectedStatus:            http.StatusForbidden,
+			mockSetup:                 func(mockUserDataSvc *MockUserDataService) {},
+		},
+		{
+			name:                      "Verification Not Required",
+			sessionUserID:             "723e4567-e89b-12d3-a456-426614174000",
+			userType:                  thunderdome.RegisteredUserType,
+			entityUserID:              "723e4567-e89b-12d3-a456-426614174000",
+			externalAPIVerifyRequired: false,
+			userVerified:              false,
+			expectedStatus:            http.StatusOK,
+			mockSetup: func(mockUserDataSvc *MockUserDataService) {
+				mockUserDataSvc.On(
+					"GetUser",
+					mock.Anything, "723e4567-e89b-12d3-a456-426614174000",
+				).Return(&thunderdome.User{Verified: false}, nil)
+			},
+		},
+		{
+			name:                      "Invalid Entity User ID",
+			sessionUserID:             "823e4567-e89b-12d3-a456-426614174000",
+			userType:                  thunderdome.RegisteredUserType,
+			entityUserID:              "invalid-user-id",
+			externalAPIVerifyRequired: true,
+			userVerified:              true,
+			expectedStatus:            http.StatusBadRequest,
+			mockSetup:                 func(mockUserDataSvc *MockUserDataService) {},
+		},
+		{
+			name:                      "User Not Found",
+			sessionUserID:             "923e4567-e89b-12d3-a456-426614174000",
+			userType:                  thunderdome.RegisteredUserType,
+			entityUserID:              "923e4567-e89b-12d3-a456-426614174000",
+			externalAPIVerifyRequired: true,
+			userVerified:              true,
+			expectedStatus:            http.StatusInternalServerError,
+			mockSetup: func(mockUserDataSvc *MockUserDataService) {
+				mockUserDataSvc.On("GetUser", mock.Anything, "923e4567-e89b-12d3-a456-426614174000").Return(nil, errors.New("user not found"))
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Mock UserDataService
+			mockUserDataSvc := new(MockUserDataService)
+
+			// Create a new service with the mock and config
+			s := &Service{
+				UserDataSvc: mockUserDataSvc,
+				Config: &Config{
+					ExternalAPIVerifyRequired: tt.externalAPIVerifyRequired,
+				},
+				Logger: otelzap.New(zap.NewNop()),
+			}
+
+			// Define a dummy handler for testing
+			dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(http.StatusOK)
+			})
+
+			// Setup mock expectations
+			tt.mockSetup(mockUserDataSvc)
+
+			// Create a new request
+			req, err := http.NewRequest("GET", "/users/"+tt.entityUserID, nil)
+			assert.NoError(t, err)
+
+			// Create a new response recorder
+			rr := httptest.NewRecorder()
+
+			// Set up the context with user information
+			ctx := context.WithValue(req.Context(), contextKeyUserID, tt.sessionUserID)
+			ctx = context.WithValue(ctx, contextKeyUserType, tt.userType)
+			req = req.WithContext(ctx)
+
+			// Set up router with vars
+			router := mux.NewRouter()
+			router.HandleFunc("/users/{userId}", s.verifiedUserOnly(dummyHandler))
+
+			// Serve the request
+			router.ServeHTTP(rr, req)
+
+			// Check the status code
+			assert.Equal(t, tt.expectedStatus, rr.Code)
+
+			// Clear mock expectations for the next test
+			mockUserDataSvc.AssertExpectations(t)
+		})
+	}
+}
+
+// MockUserDataService is a mock of UserDataService
+type MockUserDataService struct {
+	mock.Mock
+}
+
+func (m *MockUserDataService) GetGuestUser(ctx context.Context, UserID string) (*thunderdome.User, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) GetUserByEmail(ctx context.Context, UserEmail string) (*thunderdome.User, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) GetRegisteredUsers(ctx context.Context, Limit int, Offset int) ([]*thunderdome.User, int, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) SearchRegisteredUsersByEmail(ctx context.Context, Email string, Limit int, Offset int) ([]*thunderdome.User, int, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) CreateUser(ctx context.Context, UserName string, UserEmail string, UserPassword string) (NewUser *thunderdome.User, VerifyID string, RegisterErr error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) CreateUserGuest(ctx context.Context, UserName string) (*thunderdome.User, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) CreateUserRegistered(ctx context.Context, UserName string, UserEmail string, UserPassword string, ActiveUserID string) (NewUser *thunderdome.User, VerifyID string, RegisterErr error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) UpdateUserAccount(ctx context.Context, UserID string, UserName string, UserEmail string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) UpdateUserProfile(ctx context.Context, UserID string, UserName string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) UpdateUserProfileLdap(ctx context.Context, UserID string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) PromoteUser(ctx context.Context, UserID string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) DemoteUser(ctx context.Context, UserID string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) DisableUser(ctx context.Context, UserID string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) EnableUser(ctx context.Context, UserID string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) DeleteUser(ctx context.Context, UserID string) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) CleanGuests(ctx context.Context, DaysOld int) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) GetActiveCountries(ctx context.Context) ([]string, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) GetUserCredential(ctx context.Context, UserID string) (*thunderdome.Credential, error) {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (m *MockUserDataService) GetUser(ctx context.Context, userID string) (*thunderdome.User, error) {
+	args := m.Called(ctx, userID)
+	if args.Get(0) == nil {
+		return nil, args.Error(1)
+	}
+	return args.Get(0).(*thunderdome.User), args.Error(1)
+}
+
+func TestAdminOnly(t *testing.T) {
+	tests := []struct {
+		name           string
+		userType       string
+		expectedStatus int
+	}{
+		{
+			name:           "Admin User",
+			userType:       thunderdome.AdminUserType,
+			expectedStatus: http.StatusOK,
+		},
+		{
+			name:           "Regular User",
+			userType:       thunderdome.RegisteredUserType,
+			expectedStatus: http.StatusForbidden,
+		},
+		{
+			name:           "Guest User",
+			userType:       "GUEST",
+			expectedStatus: http.StatusForbidden,
+		},
+		{
+			name:           "Empty User Type",
+			userType:       "",
+			expectedStatus: http.StatusForbidden,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Create a new service
+			s := &Service{
+				// Add any necessary service configuration here
+			}
+
+			// Define a dummy handler for testing
+			dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(http.StatusOK)
+			})
+
+			// Create a new request
+			req, err := http.NewRequest("GET", "/admin-only", nil)
+			assert.NoError(t, err)
+
+			// Create a new response recorder
+			rr := httptest.NewRecorder()
+
+			// Set up the context with user type
+			ctx := context.WithValue(req.Context(), contextKeyUserType, tt.userType)
+			req = req.WithContext(ctx)
+
+			// Call the middleware
+			handler := s.adminOnly(dummyHandler)
+			handler.ServeHTTP(rr, req)
+
+			// Check the status code
+			assert.Equal(t, tt.expectedStatus, rr.Code)
+
+			// If the status is Forbidden, check for the correct error message
+			if tt.expectedStatus == http.StatusForbidden {
+				var responseBody map[string]interface{}
+				err = json.NewDecoder(rr.Body).Decode(&responseBody)
+				assert.NoError(t, err)
+				assert.Equal(t, "REQUIRES_ADMIN", responseBody["error"])
+			}
+		})
+	}
+}
+
+func TestRegisteredUserOnly(t *testing.T) {
+	tests := []struct {
+		name           string
+		userType       string
+		expectedStatus int
+	}{
+		{
+			name:           "Admin User",
+			userType:       thunderdome.AdminUserType,
+			expectedStatus: http.StatusOK,
+		},
+		{
+			name:           "Regular User",
+			userType:       thunderdome.RegisteredUserType,
+			expectedStatus: http.StatusOK,
+		},
+		{
+			name:           "Guest User",
+			userType:       thunderdome.GuestUserType,
+			expectedStatus: http.StatusForbidden,
+		},
+		{
+			name:           "Empty User Type",
+			userType:       "",
+			expectedStatus: http.StatusOK,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Create a new service
+			s := &Service{
+				// Add any necessary service configuration here
+			}
+
+			// Define a dummy handler for testing
+			dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(http.StatusOK)
+			})
+
+			// Create a new request
+			req, err := http.NewRequest("GET", "/registered-only", nil)
+			assert.NoError(t, err)
+
+			// Create a new response recorder
+			rr := httptest.NewRecorder()
+
+			// Set up the context with user type
+			ctx := context.WithValue(req.Context(), contextKeyUserType, tt.userType)
+			req = req.WithContext(ctx)
+
+			// Call the middleware
+			handler := s.registeredUserOnly(dummyHandler)
+			handler.ServeHTTP(rr, req)
+
+			// Check the status code
+			assert.Equal(t, tt.expectedStatus, rr.Code)
+
+			// If the status is Forbidden, check for the correct error message
+			if tt.expectedStatus == http.StatusForbidden {
+				var responseBody map[string]interface{}
+				err = json.NewDecoder(rr.Body).Decode(&responseBody)
+				assert.NoError(t, err)
+				assert.Equal(t, "REGISTERED_USER_ONLY", responseBody["error"])
+			}
+		})
+	}
+}
+
+func TestEntityUserOnly(t *testing.T) {
+	tests := []struct {
+		name           string
+		userID         string
+		userType       string
+		entityUserID   string
+		expectedStatus int
+	}{
+		{
+			name:           "Matching User ID",
+			userID:         "123e4567-e89b-12d3-a456-426614174000",
+			userType:       "REGULAR",
+			entityUserID:   "123e4567-e89b-12d3-a456-426614174000",
+			expectedStatus: http.StatusOK,
+		},
+		{
+			name:           "Admin User with Different Entity ID",
+			userID:         "223e4567-e89b-12d3-a456-426614174000",
+			userType:       thunderdome.AdminUserType,
+			entityUserID:   "323e4567-e89b-12d3-a456-426614174000",
+			expectedStatus: http.StatusOK,
+		},
+		{
+			name:           "Non-Matching User ID",
+			userID:         "423e4567-e89b-12d3-a456-426614174000",
+			userType:       "REGULAR",
+			entityUserID:   "523e4567-e89b-12d3-a456-426614174000",
+			expectedStatus: http.StatusForbidden,
+		},
+		{
+			name:           "Invalid Entity User ID",
+			userID:         "623e4567-e89b-12d3-a456-426614174000",
+			userType:       "REGULAR",
+			entityUserID:   "invalid-user-id",
+			expectedStatus: http.StatusBadRequest,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Create a new service
+			s := &Service{
+				// Add any necessary service configuration here
+			}
+
+			// Define a dummy handler for testing
+			dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				w.WriteHeader(http.StatusOK)
+			})
+
+			// Create a new request
+			req, err := http.NewRequest("GET", "/users/"+tt.entityUserID, nil)
+			assert.NoError(t, err)
+
+			// Create a new response recorder
+			rr := httptest.NewRecorder()
+
+			// Set up the context with user information
+			ctx := context.WithValue(req.Context(), contextKeyUserID, tt.userID)
+			ctx = context.WithValue(ctx, contextKeyUserType, tt.userType)
+			req = req.WithContext(ctx)
+
+			// Set up router with vars
+			router := mux.NewRouter()
+			router.HandleFunc("/users/{userId}", s.entityUserOnly(dummyHandler))
+
+			// Serve the request
+			router.ServeHTTP(rr, req)
+
+			// Check the status code
+			assert.Equal(t, tt.expectedStatus, rr.Code)
+
+			// If the status is not OK, check for the correct error message
+			if tt.expectedStatus != http.StatusOK {
+				var responseBody map[string]interface{}
+				err = json.NewDecoder(rr.Body).Decode(&responseBody)
+				assert.NoError(t, err)
+
+				if tt.expectedStatus == http.StatusForbidden {
+					assert.Equal(t, "INVALID_USER", responseBody["error"])
+				}
+			}
+		})
+	}
+}
diff --git a/internal/http/poker/poker.go b/internal/http/poker/poker.go
index 0ab4834c..3486d8f5 100644
--- a/internal/http/poker/poker.go
+++ b/internal/http/poker/poker.go
@@ -43,6 +43,10 @@ type AuthDataSvc interface {
 	GetSessionUser(ctx context.Context, SessionId string) (*thunderdome.User, error)
 }
 
+type UserDataSvc interface {
+	GetGuestUser(ctx context.Context, UserID string) (*thunderdome.User, error)
+}
+
 // Service provides battle service
 type Service struct {
 	config                Config
@@ -50,7 +54,7 @@ type Service struct {
 	validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error)
 	validateUserCookie    func(w http.ResponseWriter, r *http.Request) (string, error)
 	eventHandlers         map[string]func(context.Context, string, string, string) ([]byte, error, bool)
-	UserService           thunderdome.UserDataSvc
+	UserService           UserDataSvc
 	AuthService           AuthDataSvc
 	BattleService         thunderdome.PokerDataSvc
 }
@@ -60,7 +64,7 @@ func New(
 	config Config, logger *otelzap.Logger,
 	validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error),
 	validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error),
-	userService thunderdome.UserDataSvc, authService AuthDataSvc,
+	userService UserDataSvc, authService AuthDataSvc,
 	battleService thunderdome.PokerDataSvc,
 ) *Service {
 	b := &Service{
diff --git a/internal/http/retro/retro.go b/internal/http/retro/retro.go
index d18c4d38..0dd28f58 100644
--- a/internal/http/retro/retro.go
+++ b/internal/http/retro/retro.go
@@ -42,6 +42,10 @@ type AuthDataSvc interface {
 	GetSessionUser(ctx context.Context, SessionId string) (*thunderdome.User, error)
 }
 
+type UserDataSvc interface {
+	GetGuestUser(ctx context.Context, UserID string) (*thunderdome.User, error)
+}
+
 // Service provides retro service
 type Service struct {
 	config                Config
@@ -49,7 +53,7 @@ type Service struct {
 	validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error)
 	validateUserCookie    func(w http.ResponseWriter, r *http.Request) (string, error)
 	eventHandlers         map[string]func(context.Context, string, string, string) ([]byte, error, bool)
-	UserService           thunderdome.UserDataSvc
+	UserService           UserDataSvc
 	AuthService           AuthDataSvc
 	RetroService          thunderdome.RetroDataSvc
 	TemplateService       thunderdome.RetroTemplateDataSvc
@@ -62,7 +66,7 @@ func New(
 	logger *otelzap.Logger,
 	validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error),
 	validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error),
-	userService thunderdome.UserDataSvc, authService AuthDataSvc,
+	userService UserDataSvc, authService AuthDataSvc,
 	retroService thunderdome.RetroDataSvc, templateService thunderdome.RetroTemplateDataSvc,
 	emailService thunderdome.EmailService,
 ) *Service {
diff --git a/internal/http/storyboard/storyboard.go b/internal/http/storyboard/storyboard.go
index 9345da97..708fc19c 100644
--- a/internal/http/storyboard/storyboard.go
+++ b/internal/http/storyboard/storyboard.go
@@ -42,6 +42,10 @@ type AuthDataSvc interface {
 	GetSessionUser(ctx context.Context, SessionId string) (*thunderdome.User, error)
 }
 
+type UserDataSvc interface {
+	GetGuestUser(ctx context.Context, UserID string) (*thunderdome.User, error)
+}
+
 // Service provides storyboard service
 type Service struct {
 	config                Config
@@ -49,7 +53,7 @@ type Service struct {
 	ValidateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error)
 	ValidateUserCookie    func(w http.ResponseWriter, r *http.Request) (string, error)
 	EventHandlers         map[string]func(context.Context, string, string, string) ([]byte, error, bool)
-	UserService           thunderdome.UserDataSvc
+	UserService           UserDataSvc
 	AuthService           AuthDataSvc
 	StoryboardService     thunderdome.StoryboardDataSvc
 }
@@ -60,7 +64,7 @@ func New(
 	logger *otelzap.Logger,
 	validateSessionCookie func(w http.ResponseWriter, r *http.Request) (string, error),
 	validateUserCookie func(w http.ResponseWriter, r *http.Request) (string, error),
-	userService thunderdome.UserDataSvc, authService AuthDataSvc,
+	userService UserDataSvc, authService AuthDataSvc,
 	storyboardService thunderdome.StoryboardDataSvc,
 ) *Service {
 	sb := &Service{
diff --git a/internal/http/types.go b/internal/http/types.go
index 9282ff49..4d71267d 100644
--- a/internal/http/types.go
+++ b/internal/http/types.go
@@ -109,7 +109,7 @@ type Service struct {
 	Router               *mux.Router
 	Email                thunderdome.EmailService
 	Logger               *otelzap.Logger
-	UserDataSvc          thunderdome.UserDataSvc
+	UserDataSvc          UserDataSvc
 	ApiKeyDataSvc        APIKeyDataSvc
 	AlertDataSvc         AlertDataSvc
 	AuthDataSvc          AuthDataSvc
@@ -302,3 +302,25 @@ type SubscriptionDataSvc interface {
 	GetSubscriptions(ctx context.Context, Limit int, Offset int) ([]thunderdome.Subscription, int, error)
 	DeleteSubscription(ctx context.Context, id string) error
 }
+
+type UserDataSvc interface {
+	GetUser(ctx context.Context, UserID string) (*thunderdome.User, error)
+	GetGuestUser(ctx context.Context, UserID string) (*thunderdome.User, error)
+	GetUserByEmail(ctx context.Context, UserEmail string) (*thunderdome.User, error)
+	GetRegisteredUsers(ctx context.Context, Limit int, Offset int) ([]*thunderdome.User, int, error)
+	SearchRegisteredUsersByEmail(ctx context.Context, Email string, Limit int, Offset int) ([]*thunderdome.User, int, error)
+	CreateUser(ctx context.Context, UserName string, UserEmail string, UserPassword string) (NewUser *thunderdome.User, VerifyID string, RegisterErr error)
+	CreateUserGuest(ctx context.Context, UserName string) (*thunderdome.User, error)
+	CreateUserRegistered(ctx context.Context, UserName string, UserEmail string, UserPassword string, ActiveUserID string) (NewUser *thunderdome.User, VerifyID string, RegisterErr error)
+	UpdateUserAccount(ctx context.Context, UserID string, UserName string, UserEmail string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error
+	UpdateUserProfile(ctx context.Context, UserID string, UserName string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error
+	UpdateUserProfileLdap(ctx context.Context, UserID string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error
+	PromoteUser(ctx context.Context, UserID string) error
+	DemoteUser(ctx context.Context, UserID string) error
+	DisableUser(ctx context.Context, UserID string) error
+	EnableUser(ctx context.Context, UserID string) error
+	DeleteUser(ctx context.Context, UserID string) error
+	CleanGuests(ctx context.Context, DaysOld int) error
+	GetActiveCountries(ctx context.Context) ([]string, error)
+	GetUserCredential(ctx context.Context, UserID string) (*thunderdome.Credential, error)
+}
diff --git a/internal/http/user.go b/internal/http/user.go
index 32314c57..e6e9cccb 100644
--- a/internal/http/user.go
+++ b/internal/http/user.go
@@ -498,14 +498,14 @@ func (s *Service) handleUserDepartmentInvite() http.HandlerFunc {
 			return
 		}
 
-		_, orgAddErr := s.OrganizationDataSvc.OrganizationUpsertUser(ctx, org.Id, UserID, "MEMBER")
+		_, orgAddErr := s.OrganizationDataSvc.OrganizationUpsertUser(ctx, org.Id, UserID, thunderdome.EntityMemberUserType)
 		if orgAddErr != nil {
 			s.Logger.Ctx(ctx).Error(
 				"handleUserDepartmentInvite upsert organization user error", zap.Error(orgAddErr),
 				zap.String("session_user_id", *SessionUserID),
 				zap.String("organization_id", org.Id),
 				zap.String("department_id", dept.Id),
-				zap.String("user_id", UserID), zap.String("user_role", "MEMBER"))
+				zap.String("user_id", UserID), zap.String("user_role", thunderdome.EntityMemberUserType))
 			s.Failure(w, r, http.StatusInternalServerError, orgAddErr)
 			return
 		}
@@ -593,13 +593,13 @@ func (s *Service) handleUserTeamInvite() http.HandlerFunc {
 
 		// team is associated to organization, upsert user to organization
 		if team.OrganizationId != "" {
-			_, orgAddErr := s.OrganizationDataSvc.OrganizationUpsertUser(ctx, team.OrganizationId, UserID, "MEMBER")
+			_, orgAddErr := s.OrganizationDataSvc.OrganizationUpsertUser(ctx, team.OrganizationId, UserID, thunderdome.EntityMemberUserType)
 			if orgAddErr != nil {
 				s.Logger.Ctx(ctx).Error(
 					"handleTeamInviteUser upsert organization user error", zap.Error(orgAddErr),
 					zap.String("session_user_id", *SessionUserID),
 					zap.String("organization_id", team.OrganizationId),
-					zap.String("user_id", UserID), zap.String("user_role", "MEMBER"))
+					zap.String("user_id", UserID), zap.String("user_role", thunderdome.EntityMemberUserType))
 				s.Failure(w, r, http.StatusInternalServerError, orgAddErr)
 				return
 			}
@@ -628,25 +628,25 @@ func (s *Service) handleUserTeamInvite() http.HandlerFunc {
 
 			team.OrganizationId = org.Id
 
-			_, orgAddErr := s.OrganizationDataSvc.OrganizationUpsertUser(ctx, org.Id, UserID, "MEMBER")
+			_, orgAddErr := s.OrganizationDataSvc.OrganizationUpsertUser(ctx, org.Id, UserID, thunderdome.EntityMemberUserType)
 			if orgAddErr != nil {
 				s.Logger.Ctx(ctx).Error(
 					"handleTeamInviteUser upsert organization user error", zap.Error(orgAddErr),
 					zap.String("session_user_id", *SessionUserID),
 					zap.String("organization_id", org.Id),
 					zap.String("department_id", dept.Id),
-					zap.String("user_id", UserID), zap.String("user_role", "MEMBER"))
+					zap.String("user_id", UserID), zap.String("user_role", thunderdome.EntityMemberUserType))
 				s.Failure(w, r, http.StatusInternalServerError, orgAddErr)
 				return
 			}
 
-			_, deptAddErr := s.OrganizationDataSvc.DepartmentUpsertUser(ctx, team.DepartmentId, UserID, "MEMBER")
+			_, deptAddErr := s.OrganizationDataSvc.DepartmentUpsertUser(ctx, team.DepartmentId, UserID, thunderdome.EntityMemberUserType)
 			if deptAddErr != nil {
 				s.Logger.Ctx(ctx).Error(
 					"handleTeamInviteUser upsert department user error", zap.Error(deptAddErr),
 					zap.String("session_user_id", *SessionUserID),
 					zap.String("department_id", team.DepartmentId),
-					zap.String("user_id", UserID), zap.String("user_role", "MEMBER"))
+					zap.String("user_id", UserID), zap.String("user_role", thunderdome.EntityMemberUserType))
 				s.Failure(w, r, http.StatusInternalServerError, deptAddErr)
 				return
 			}
diff --git a/internal/webhook/subscription/subscription.go b/internal/webhook/subscription/subscription.go
index 7d1631f5..6933a25d 100644
--- a/internal/webhook/subscription/subscription.go
+++ b/internal/webhook/subscription/subscription.go
@@ -32,12 +32,16 @@ type DataSvc interface {
 	UpdateSubscription(ctx context.Context, id string, sub thunderdome.Subscription) (thunderdome.Subscription, error)
 }
 
+type UserDataSvc interface {
+	GetUser(ctx context.Context, UserID string) (*thunderdome.User, error)
+}
+
 type Service struct {
 	config      Config
 	logger      *otelzap.Logger
 	dataSvc     DataSvc
 	emailSvc    thunderdome.EmailService
-	userDataSvc thunderdome.UserDataSvc
+	userDataSvc UserDataSvc
 }
 
 func New(
@@ -45,7 +49,7 @@ func New(
 	logger *otelzap.Logger,
 	dataSvc DataSvc,
 	emailSvc thunderdome.EmailService,
-	userDataSvc thunderdome.UserDataSvc,
+	userDataSvc UserDataSvc,
 ) *Service {
 	// The library needs to be configured with your account's secret key.
 	// Ensure the key is kept out of any version control system you might be using.
diff --git a/thunderdome/user.go b/thunderdome/user.go
index 1a7360a1..a1490bff 100644
--- a/thunderdome/user.go
+++ b/thunderdome/user.go
@@ -1,14 +1,14 @@
 package thunderdome
 
 import (
-	"context"
 	"time"
 )
 
 const (
-	GuestUserType      = "GUEST"
-	RegisteredUserType = "REGISTERED"
-	AdminUserType      = "ADMIN"
+	GuestUserType        = "GUEST"
+	RegisteredUserType   = "REGISTERED"
+	AdminUserType        = "ADMIN"
+	EntityMemberUserType = "MEMBER" // used for organizations, teams, etc
 )
 
 type UserUICookie struct {
@@ -42,25 +42,3 @@ type User struct {
 	Theme                string    `json:"theme"`
 	Picture              string    `json:"picture"`
 }
-
-type UserDataSvc interface {
-	GetUser(ctx context.Context, UserID string) (*User, error)
-	GetGuestUser(ctx context.Context, UserID string) (*User, error)
-	GetUserByEmail(ctx context.Context, UserEmail string) (*User, error)
-	GetRegisteredUsers(ctx context.Context, Limit int, Offset int) ([]*User, int, error)
-	SearchRegisteredUsersByEmail(ctx context.Context, Email string, Limit int, Offset int) ([]*User, int, error)
-	CreateUser(ctx context.Context, UserName string, UserEmail string, UserPassword string) (NewUser *User, VerifyID string, RegisterErr error)
-	CreateUserGuest(ctx context.Context, UserName string) (*User, error)
-	CreateUserRegistered(ctx context.Context, UserName string, UserEmail string, UserPassword string, ActiveUserID string) (NewUser *User, VerifyID string, RegisterErr error)
-	UpdateUserAccount(ctx context.Context, UserID string, UserName string, UserEmail string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error
-	UpdateUserProfile(ctx context.Context, UserID string, UserName string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error
-	UpdateUserProfileLdap(ctx context.Context, UserID string, UserAvatar string, NotificationsEnabled bool, Country string, Locale string, Company string, JobTitle string, Theme string) error
-	PromoteUser(ctx context.Context, UserID string) error
-	DemoteUser(ctx context.Context, UserID string) error
-	DisableUser(ctx context.Context, UserID string) error
-	EnableUser(ctx context.Context, UserID string) error
-	DeleteUser(ctx context.Context, UserID string) error
-	CleanGuests(ctx context.Context, DaysOld int) error
-	GetActiveCountries(ctx context.Context) ([]string, error)
-	GetUserCredential(ctx context.Context, UserID string) (*Credential, error)
-}