diff --git a/go/apps/api/openapi/gen.go b/go/apps/api/openapi/gen.go index f0c55f4c7e..f29414e91f 100644 --- a/go/apps/api/openapi/gen.go +++ b/go/apps/api/openapi/gen.go @@ -634,10 +634,8 @@ type V2IdentitiesCreateIdentityResponseData = map[string]interface{} // V2IdentitiesDeleteIdentityRequestBody defines model for V2IdentitiesDeleteIdentityRequestBody. type V2IdentitiesDeleteIdentityRequestBody struct { - // ExternalId The id of this identity in your system. - // This should match the externalId value you used when creating the identity. - // This identifier typically comes from your authentication system and could be a userId, organizationId, or any other stable unique identifier in your application. - ExternalId string `json:"externalId"` + // Identity The ID of the identity to delete. This can be either the externalId (from your own system that was used during identity creation) or the identityId (the internal ID returned by the identity service). + Identity string `json:"identity"` } // V2IdentitiesDeleteIdentityResponseBody Empty response object. A successful response indicates the identity was deleted successfully. @@ -648,8 +646,8 @@ type V2IdentitiesDeleteIdentityResponseBody struct { // V2IdentitiesGetIdentityRequestBody defines model for V2IdentitiesGetIdentityRequestBody. type V2IdentitiesGetIdentityRequestBody struct { - // ExternalId The external ID of the identity to retrieve. This is the ID from your own system that was used during identity creation. - ExternalId string `json:"externalId"` + // Identity The ID of the identity to retrieve. This can be either the externalId (from your own system that was used during identity creation) or the identityId (the internal ID returned by the identity service). + Identity string `json:"identity"` } // V2IdentitiesGetIdentityResponseBody defines model for V2IdentitiesGetIdentityResponseBody. diff --git a/go/apps/api/openapi/openapi-generated.yaml b/go/apps/api/openapi/openapi-generated.yaml index 976ec55d50..a9ca263690 100644 --- a/go/apps/api/openapi/openapi-generated.yaml +++ b/go/apps/api/openapi/openapi-generated.yaml @@ -385,19 +385,16 @@ components: To resolve this error, check the current state of the resource and adjust your request accordingly. V2IdentitiesDeleteIdentityRequestBody: - additionalProperties: false type: object properties: - externalId: + identity: type: string minLength: 3 - description: | - The id of this identity in your system. - This should match the externalId value you used when creating the identity. - This identifier typically comes from your authentication system and could be a userId, organizationId, or any other stable unique identifier in your application. + description: The ID of the identity to delete. This can be either the externalId (from your own system that was used during identity creation) or the identityId (the internal ID returned by the identity service). example: user_123 + additionalProperties: false required: - - externalId + - identity V2IdentitiesDeleteIdentityResponseBody: type: object description: Empty response object. A successful response indicates the identity was deleted successfully. @@ -409,14 +406,14 @@ components: V2IdentitiesGetIdentityRequestBody: type: object properties: - externalId: + identity: type: string - minLength: 1 - description: The external ID of the identity to retrieve. This is the ID from your own system that was used during identity creation. + minLength: 3 + description: The ID of the identity to retrieve. This can be either the externalId (from your own system that was used during identity creation) or the identityId (the internal ID returned by the identity service). example: user_abc123 additionalProperties: false required: - - externalId + - identity V2IdentitiesGetIdentityResponseBody: type: object required: diff --git a/go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml b/go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml index 61cb939703..bc4d87a584 100644 --- a/go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml +++ b/go/apps/api/openapi/spec/paths/v2/identities/deleteIdentity/V2IdentitiesDeleteIdentityRequestBody.yaml @@ -1,13 +1,10 @@ -additionalProperties: false type: object properties: - externalId: + identity: type: string minLength: 3 - description: | - The id of this identity in your system. - This should match the externalId value you used when creating the identity. - This identifier typically comes from your authentication system and could be a userId, organizationId, or any other stable unique identifier in your application. + description: The ID of the identity to delete. This can be either the externalId (from your own system that was used during identity creation) or the identityId (the internal ID returned by the identity service). example: user_123 +additionalProperties: false required: - - externalId + - identity diff --git a/go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml b/go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml index eda1112c2c..0d0d3a3f3d 100644 --- a/go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml +++ b/go/apps/api/openapi/spec/paths/v2/identities/getIdentity/V2IdentitiesGetIdentityRequestBody.yaml @@ -1,11 +1,10 @@ type: object properties: - externalId: + identity: type: string - minLength: 1 - description: The external ID of the identity to retrieve. This is the ID - from your own system that was used during identity creation. + minLength: 3 + description: The ID of the identity to retrieve. This can be either the externalId (from your own system that was used during identity creation) or the identityId (the internal ID returned by the identity service). example: user_abc123 additionalProperties: false required: - - externalId + - identity diff --git a/go/apps/api/routes/v2_apis_list_keys/handler.go b/go/apps/api/routes/v2_apis_list_keys/handler.go index 2497683c7a..3f491e521b 100644 --- a/go/apps/api/routes/v2_apis_list_keys/handler.go +++ b/go/apps/api/routes/v2_apis_list_keys/handler.go @@ -20,8 +20,10 @@ import ( "github.com/unkeyed/unkey/go/pkg/zen" ) -type Request = openapi.V2ApisListKeysRequestBody -type Response = openapi.V2ApisListKeysResponseBody +type ( + Request = openapi.V2ApisListKeysRequestBody + Response = openapi.V2ApisListKeysResponseBody +) // Handler implements zen.Route interface for the v2 APIs list keys endpoint type Handler struct { @@ -175,9 +177,9 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { // 5. Query the keys var identityId string if req.ExternalId != nil && *req.ExternalId != "" { - identity, findErr := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, findErr := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: auth.AuthorizedWorkspaceID, - ExternalID: *req.ExternalId, + Identity: *req.ExternalId, Deleted: false, }) if findErr != nil { diff --git a/go/apps/api/routes/v2_identities_create_identity/200_test.go b/go/apps/api/routes/v2_identities_create_identity/200_test.go index 11c01b61a8..cc79fb3063 100644 --- a/go/apps/api/routes/v2_identities_create_identity/200_test.go +++ b/go/apps/api/routes/v2_identities_create_identity/200_test.go @@ -49,9 +49,10 @@ func TestCreateIdentitySuccessfully(t *testing.T) { }) require.NoError(t, err) - identity, err := db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: identityID, - Deleted: false, + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + Identity: identityID, + Deleted: false, + WorkspaceID: h.Resources().UserWorkspace.ID, }) require.NoError(t, err) require.Equal(t, identity.ExternalID, externalTestID) @@ -71,9 +72,10 @@ func TestCreateIdentitySuccessfully(t *testing.T) { }) require.NoError(t, err) - identity, err := db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: identityID, - Deleted: false, + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + Identity: identityID, + Deleted: false, + WorkspaceID: h.Resources().UserWorkspace.ID, }) require.NoError(t, err) require.Equal(t, identity.ExternalID, externalTestID) @@ -106,9 +108,9 @@ func TestCreateIdentitySuccessfully(t *testing.T) { require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) require.NotNil(t, res.Body) - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: externalTestID, + Identity: externalTestID, Deleted: false, }) require.NoError(t, err) @@ -128,9 +130,9 @@ func TestCreateIdentitySuccessfully(t *testing.T) { require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) require.NotNil(t, res.Body) - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: externalTestID, + Identity: externalTestID, Deleted: false, }) require.NoError(t, err) @@ -169,9 +171,9 @@ func TestCreateIdentitySuccessfully(t *testing.T) { require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) require.NotNil(t, res.Body) - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: externalTestID, + Identity: externalTestID, Deleted: false, }) require.NoError(t, err) @@ -228,9 +230,9 @@ func TestCreateIdentitySuccessfully(t *testing.T) { require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) require.NotNil(t, res.Body) - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: externalTestID, + Identity: externalTestID, Deleted: false, }) require.NoError(t, err) @@ -304,9 +306,9 @@ func TestCreateIdentitySuccessfully(t *testing.T) { require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) require.NotNil(t, res.Body) - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: externalTestID, + Identity: externalTestID, Deleted: false, }) require.NoError(t, err) @@ -345,9 +347,9 @@ func TestCreateIdentitySuccessfully(t *testing.T) { // Verify each identity was created with the correct externalId for i, externalID := range externalIDs { - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: externalID, + Identity: externalID, Deleted: false, }) identityIDs = append(identityIDs, identity.ID) @@ -375,9 +377,9 @@ func TestCreateIdentitySuccessfully(t *testing.T) { require.NotNil(t, res.Body) // Verify in database - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: externalTestID, + Identity: externalTestID, Deleted: false, }) require.NoError(t, err) @@ -415,9 +417,9 @@ func TestCreateIdentitySuccessfully(t *testing.T) { require.NotNil(t, res.Body) // Verify in database - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: externalTestID, + Identity: externalTestID, Deleted: false, }) require.NoError(t, err) diff --git a/go/apps/api/routes/v2_identities_delete_identity/200_test.go b/go/apps/api/routes/v2_identities_delete_identity/200_test.go index 2dcc01ca09..1644d566e3 100644 --- a/go/apps/api/routes/v2_identities_delete_identity/200_test.go +++ b/go/apps/api/routes/v2_identities_delete_identity/200_test.go @@ -37,7 +37,7 @@ func createTestIdentity(t *testing.T, h *testutil.Harness, numberOfRatelimits in require.NoError(t, err) ratelimitIds := make([]string, 0, numberOfRatelimits) - for i := 0; i < numberOfRatelimits; i++ { + for i := range numberOfRatelimits { rateLimitID := uid.New(uid.RatelimitPrefix) err = db.Query.InsertIdentityRatelimit(t.Context(), h.DB.RW(), db.InsertIdentityRatelimitParams{ ID: rateLimitID, @@ -82,33 +82,33 @@ func TestDeleteIdentitySuccess(t *testing.T) { testIdentity := createTestIdentity(t, h, 0) // Verify identity exists before deletion - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: testIdentity.ExternalID, + Identity: testIdentity.ExternalID, Deleted: false, }) require.NoError(t, err) require.Equal(t, testIdentity.ExternalID, identity.ExternalID) // Delete the identity via API - req := handler.Request{ExternalId: testIdentity.ExternalID} + req := handler.Request{Identity: testIdentity.ExternalID} res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) require.NotNil(t, res.Body) // Verify identity is soft deleted - _, err = db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + _, err = db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: testIdentity.ExternalID, + Identity: testIdentity.ExternalID, Deleted: false, }) require.Equal(t, sql.ErrNoRows, err, "identity should not be found with deleted=false") // Verify identity still exists but marked as deleted - deletedIdentity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + deletedIdentity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: h.Resources().UserWorkspace.ID, - ExternalID: testIdentity.ExternalID, + Identity: testIdentity.ExternalID, Deleted: true, }) require.NoError(t, err, "identity should still exist with deleted=true") @@ -126,16 +126,17 @@ func TestDeleteIdentitySuccess(t *testing.T) { require.Len(t, rateLimits, numberOfRatelimits) // Delete the identity via API - req := handler.Request{ExternalId: testIdentity.ExternalID} + req := handler.Request{Identity: testIdentity.ExternalID} res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) require.NotNil(t, res.Body) // Verify identity is soft deleted - _, err = db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: testIdentity.ID, - Deleted: false, + _, err = db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + WorkspaceID: h.Resources().UserWorkspace.ID, + Identity: testIdentity.ID, + Deleted: false, }) require.Equal(t, sql.ErrNoRows, err) @@ -155,16 +156,17 @@ func TestDeleteIdentitySuccess(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", wildcardKey)}, } - req := handler.Request{ExternalId: testIdentity.ExternalID} + req := handler.Request{Identity: testIdentity.ExternalID} res := testutil.CallRoute[handler.Request, handler.Response](h, route, wildcardHeaders, req) require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) require.NotNil(t, res.Body) // Verify identity is soft deleted - _, err := db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: testIdentity.ID, - Deleted: false, + _, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + WorkspaceID: h.Resources().UserWorkspace.ID, + Identity: testIdentity.ID, + Deleted: false, }) require.Equal(t, sql.ErrNoRows, err) }) @@ -173,7 +175,7 @@ func TestDeleteIdentitySuccess(t *testing.T) { testIdentity := createTestIdentity(t, h, 2) // Delete the identity - req := handler.Request{ExternalId: testIdentity.ExternalID} + req := handler.Request{Identity: testIdentity.ExternalID} res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res.Status, "expected 200, received: %#v", res) @@ -199,7 +201,7 @@ func TestDeleteIdentitySuccess(t *testing.T) { testIdentity := createTestIdentity(t, h, 0) // Delete the identity once - req := handler.Request{ExternalId: testIdentity.ExternalID} + req := handler.Request{Identity: testIdentity.ExternalID} res1 := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res1.Status, "first deletion should succeed") @@ -216,21 +218,23 @@ func TestDeleteIdentitySuccess(t *testing.T) { require.NoError(t, err) // Delete the new identity (this should trigger duplicate key error handling) - req2 := handler.Request{ExternalId: testIdentity.ExternalID} + req2 := handler.Request{Identity: testIdentity.ExternalID} res2 := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req2) require.Equal(t, 200, res2.Status, "second deletion should succeed despite duplicate key scenario") // Verify the new identity is soft deleted - _, err = db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: newIdentityID, - Deleted: false, + _, err = db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + WorkspaceID: h.Resources().UserWorkspace.ID, + Identity: newIdentityID, + Deleted: false, }) require.Equal(t, sql.ErrNoRows, err) // Verify the old identity was hard deleted (should not be found even with deleted=true) - _, err = db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: testIdentity.ID, - Deleted: true, + _, err = db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + WorkspaceID: h.Resources().UserWorkspace.ID, + Identity: testIdentity.ID, + Deleted: true, }) require.Equal(t, sql.ErrNoRows, err, "old identity should be hard deleted") }) diff --git a/go/apps/api/routes/v2_identities_delete_identity/400_test.go b/go/apps/api/routes/v2_identities_delete_identity/400_test.go index 42a19d9833..2967c71d97 100644 --- a/go/apps/api/routes/v2_identities_delete_identity/400_test.go +++ b/go/apps/api/routes/v2_identities_delete_identity/400_test.go @@ -44,8 +44,8 @@ func TestBadRequests(t *testing.T) { require.Greater(t, len(res.Body.Error.Errors), 0) }) - t.Run("empty external ID", func(t *testing.T) { - req := handler.Request{ExternalId: ""} + t.Run("empty identity", func(t *testing.T) { + req := handler.Request{Identity: ""} res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -58,8 +58,8 @@ func TestBadRequests(t *testing.T) { require.Greater(t, len(res.Body.Error.Errors), 0) }) - t.Run("external ID too short", func(t *testing.T) { - req := handler.Request{ExternalId: "id"} + t.Run("identity too short", func(t *testing.T) { + req := handler.Request{Identity: "id"} res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -72,9 +72,9 @@ func TestBadRequests(t *testing.T) { require.Greater(t, len(res.Body.Error.Errors), 0) }) - t.Run("external ID with special characters", func(t *testing.T) { - // Test with external ID containing only special characters (handler treats as not found) - req := handler.Request{ExternalId: "@#$%"} + t.Run("identity with special characters", func(t *testing.T) { + // Test with identity containing only special characters (handler treats as not found) + req := handler.Request{Identity: "@#$%"} res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, 404, res.Status, "expected 404, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -86,10 +86,10 @@ func TestBadRequests(t *testing.T) { require.NotEmpty(t, res.Body.Meta.RequestId) }) - t.Run("external ID too long", func(t *testing.T) { - // Test with extremely long external ID (handler treats as not found) + t.Run("identity too long", func(t *testing.T) { + // Test with extremely long identity (handler treats as not found) longExternalID := "test_" + string(make([]byte, 1000)) - req := handler.Request{ExternalId: longExternalID} + req := handler.Request{Identity: longExternalID} res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, 404, res.Status, "expected 404, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) diff --git a/go/apps/api/routes/v2_identities_delete_identity/401_test.go b/go/apps/api/routes/v2_identities_delete_identity/401_test.go index 2fa598eaa4..4a8c34ff32 100644 --- a/go/apps/api/routes/v2_identities_delete_identity/401_test.go +++ b/go/apps/api/routes/v2_identities_delete_identity/401_test.go @@ -30,7 +30,7 @@ func TestDeleteIdentityUnauthorized(t *testing.T) { // No Authorization header } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) require.Equal(t, http.StatusBadRequest, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -48,7 +48,9 @@ func TestDeleteIdentityUnauthorized(t *testing.T) { "Authorization": {"malformed_header"}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{ + Identity: uid.New("test"), + } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) require.Equal(t, http.StatusBadRequest, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -66,7 +68,7 @@ func TestDeleteIdentityUnauthorized(t *testing.T) { "Authorization": {"Bearer invalid_token"}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.UnauthorizedErrorResponse](h, route, headers, req) require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -84,7 +86,9 @@ func TestDeleteIdentityUnauthorized(t *testing.T) { "Authorization": {"Bearer "}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{ + Identity: uid.New("test"), + } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) require.Equal(t, http.StatusBadRequest, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -102,7 +106,7 @@ func TestDeleteIdentityUnauthorized(t *testing.T) { "Authorization": {"Bearer not-a-valid-key-format"}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.UnauthorizedErrorResponse](h, route, headers, req) require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -120,7 +124,7 @@ func TestDeleteIdentityUnauthorized(t *testing.T) { "Authorization": {"Bearer random_string_not_a_key"}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.UnauthorizedErrorResponse](h, route, headers, req) require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -143,7 +147,7 @@ func TestDeleteIdentityUnauthorized(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", differentWorkspaceKey)}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status, "expected 404, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -161,7 +165,7 @@ func TestDeleteIdentityUnauthorized(t *testing.T) { "Authorization": {"Bearer 123"}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.UnauthorizedErrorResponse](h, route, headers, req) require.Equal(t, http.StatusUnauthorized, res.Status, "expected 401, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) diff --git a/go/apps/api/routes/v2_identities_delete_identity/403_test.go b/go/apps/api/routes/v2_identities_delete_identity/403_test.go index 134cf2f953..524b5edad0 100644 --- a/go/apps/api/routes/v2_identities_delete_identity/403_test.go +++ b/go/apps/api/routes/v2_identities_delete_identity/403_test.go @@ -31,7 +31,7 @@ func TestDeleteIdentityForbidden(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", rootKey)}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) require.Equal(t, http.StatusForbidden, res.Status, "expected 403, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -50,7 +50,7 @@ func TestDeleteIdentityForbidden(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", rootKey)}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) require.Equal(t, http.StatusForbidden, res.Status, "expected 403, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -69,7 +69,7 @@ func TestDeleteIdentityForbidden(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", rootKey)}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) require.Equal(t, http.StatusForbidden, res.Status, "expected 403, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -88,7 +88,7 @@ func TestDeleteIdentityForbidden(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", rootKey)}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) require.Equal(t, http.StatusForbidden, res.Status, "expected 403, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -107,7 +107,7 @@ func TestDeleteIdentityForbidden(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", rootKey)}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) require.Equal(t, http.StatusForbidden, res.Status, "expected 403, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -129,7 +129,9 @@ func TestDeleteIdentityForbidden(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", rootKey)}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{ + Identity: uid.New("test"), + } res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) require.Equal(t, http.StatusForbidden, res.Status, "expected 403, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -148,7 +150,7 @@ func TestDeleteIdentityForbidden(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", rootKey)}, } - req := handler.Request{ExternalId: uid.New("test")} + req := handler.Request{Identity: uid.New("test")} res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) require.Equal(t, http.StatusForbidden, res.Status, "expected 403, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) diff --git a/go/apps/api/routes/v2_identities_delete_identity/404_test.go b/go/apps/api/routes/v2_identities_delete_identity/404_test.go index 890a625c25..662f9b90b1 100644 --- a/go/apps/api/routes/v2_identities_delete_identity/404_test.go +++ b/go/apps/api/routes/v2_identities_delete_identity/404_test.go @@ -34,7 +34,7 @@ func TestDeleteIdentityNotFound(t *testing.T) { t.Run("delete identity with non-existent external ID", func(t *testing.T) { nonExistentExternalID := "non_existent_" + uid.New("test") - req := handler.Request{ExternalId: nonExistentExternalID} + req := handler.Request{Identity: nonExistentExternalID} res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status, "expected 404, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -70,7 +70,7 @@ func TestDeleteIdentityNotFound(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", differentWorkspaceKey)}, } - req := handler.Request{ExternalId: externalId} + req := handler.Request{Identity: externalId} res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, differentHeaders, req) require.Equal(t, http.StatusNotFound, res.Status, "expected 404, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -97,11 +97,14 @@ func TestDeleteIdentityNotFound(t *testing.T) { require.NoError(t, err) // Soft delete the identity directly in DB - err = db.Query.SoftDeleteIdentity(t.Context(), h.DB.RW(), identityId) + err = db.Query.SoftDeleteIdentity(t.Context(), h.DB.RW(), db.SoftDeleteIdentityParams{ + Identity: identityId, + WorkspaceID: h.Resources().UserWorkspace.ID, + }) require.NoError(t, err) // Try to delete it again via API - req := handler.Request{ExternalId: externalId} + req := handler.Request{Identity: externalId} res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status, "expected 404, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) @@ -115,7 +118,7 @@ func TestDeleteIdentityNotFound(t *testing.T) { t.Run("delete identity using very long non-existent external ID", func(t *testing.T) { longExternalID := "very_long_external_id_that_does_not_exist_" + uid.New("test") + "_" + uid.New("test2") - req := handler.Request{ExternalId: longExternalID} + req := handler.Request{Identity: longExternalID} res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status, "expected 404, sent: %+v, received: %s", req, res.RawBody) require.NotNil(t, res.Body) diff --git a/go/apps/api/routes/v2_identities_delete_identity/handler.go b/go/apps/api/routes/v2_identities_delete_identity/handler.go index e897ecd487..676a832358 100644 --- a/go/apps/api/routes/v2_identities_delete_identity/handler.go +++ b/go/apps/api/routes/v2_identities_delete_identity/handler.go @@ -18,8 +18,10 @@ import ( "github.com/unkeyed/unkey/go/pkg/zen" ) -type Request = openapi.V2IdentitiesDeleteIdentityRequestBody -type Response = openapi.V2IdentitiesDeleteIdentityResponseBody +type ( + Request = openapi.V2IdentitiesDeleteIdentityRequestBody + Response = openapi.V2IdentitiesDeleteIdentityResponseBody +) // Handler implements zen.Route interface for the v2 identities delete identity endpoint type Handler struct { @@ -69,9 +71,9 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { return err } - identity, err := db.Query.FindIdentityByExternalID(ctx, h.DB.RO(), db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ WorkspaceID: auth.AuthorizedWorkspaceID, - ExternalID: req.ExternalId, + Identity: req.Identity, Deleted: false, }) if err != nil { @@ -96,22 +98,35 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } err = db.Tx(ctx, h.DB.RW(), func(ctx context.Context, tx db.DBTX) error { - err = db.Query.SoftDeleteIdentity(ctx, tx, identity.ID) + err = db.Query.SoftDeleteIdentity(ctx, tx, db.SoftDeleteIdentityParams{ + WorkspaceID: auth.AuthorizedWorkspaceID, + Identity: identity.ID, + }) // If we hit a duplicate key error, we know that we have an identity that was already soft deleted // so we can hard delete the "old" deleted version if db.IsDuplicateKeyError(err) { - err = deleteOldIdentity(ctx, tx, auth.AuthorizedWorkspaceID, identity.ExternalID) + // Delete the old soft-deleted identity and its ratelimits + err = db.Query.DeleteOldIdentityWithRatelimits(ctx, tx, db.DeleteOldIdentityWithRatelimitsParams{ + WorkspaceID: auth.AuthorizedWorkspaceID, + Identity: req.Identity, + }) if err != nil { - return err + return fault.Wrap(err, + fault.Code(codes.App.Internal.ServiceUnavailable.URN()), + fault.Internal("database failed to delete old soft-deleted identity"), + fault.Public("Failed to delete old deleted identity."), + ) } // Re-apply the soft delete operation - err = db.Query.SoftDeleteIdentity(ctx, tx, identity.ID) - + err = db.Query.SoftDeleteIdentity(ctx, tx, db.SoftDeleteIdentityParams{ + WorkspaceID: auth.AuthorizedWorkspaceID, + Identity: identity.ID, + }) } - if err != nil { + if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.ServiceUnavailable.URN()), fault.Internal("database failed to soft delete identity"), fault.Public("Failed to delete Identity."), @@ -196,35 +211,3 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { }, }) } - -func deleteOldIdentity(ctx context.Context, tx db.DBTX, workspaceID, externalID string) error { - oldIdentity, err := db.Query.FindIdentityByExternalID(ctx, tx, db.FindIdentityByExternalIDParams{ - WorkspaceID: workspaceID, - ExternalID: externalID, - Deleted: true, - }) - if err != nil { - return fault.Wrap(err, - fault.Code(codes.App.Internal.ServiceUnavailable.URN()), - fault.Internal("database failed to load old identity"), fault.Public("Failed to load Identity."), - ) - } - - err = db.Query.DeleteManyRatelimitsByIdentityID(ctx, tx, sql.NullString{String: oldIdentity.ID, Valid: true}) - if err != nil { - return fault.Wrap(err, - fault.Code(codes.App.Internal.ServiceUnavailable.URN()), - fault.Internal("database failed to delete identity ratelimits"), fault.Public("Failed to delete Identity ratelimits."), - ) - } - - err = db.Query.DeleteIdentity(ctx, tx, oldIdentity.ID) - if err != nil { - return fault.Wrap(err, - fault.Code(codes.App.Internal.ServiceUnavailable.URN()), - fault.Internal("database failed to delete identity"), fault.Public("Failed to delete Identity."), - ) - } - - return nil -} diff --git a/go/apps/api/routes/v2_identities_get_identity/200_test.go b/go/apps/api/routes/v2_identities_get_identity/200_test.go index 43f60f0bcc..8bf89b6549 100644 --- a/go/apps/api/routes/v2_identities_get_identity/200_test.go +++ b/go/apps/api/routes/v2_identities_get_identity/200_test.go @@ -40,7 +40,7 @@ func TestSuccess(t *testing.T) { externalID := "test_user_123" // Create metadata - metaMap := map[string]interface{}{ + metaMap := map[string]any{ "name": "Test User", "email": "test@example.com", "plan": "pro", @@ -73,7 +73,7 @@ func TestSuccess(t *testing.T) { // No need to set up permissions since we already gave the key the required permission t.Run("get by externalId", func(t *testing.T) { req := handler.Request{ - ExternalId: externalID, + Identity: externalID, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, http.StatusOK, res.Status, "expected 200, sent: %+v, received: %s", req, res.RawBody) @@ -116,7 +116,7 @@ func TestSuccess(t *testing.T) { require.NoError(t, err) req := handler.Request{ - ExternalId: externalIDWithoutMeta, + Identity: externalIDWithoutMeta, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res.Status) @@ -151,7 +151,7 @@ func TestSuccess(t *testing.T) { require.NoError(t, err) req := handler.Request{ - ExternalId: externalIDWithoutRatelimits, + Identity: externalIDWithoutRatelimits, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res.Status) @@ -238,7 +238,7 @@ func TestSuccess(t *testing.T) { // Retrieve the identity req := handler.Request{ - ExternalId: largeMetaExternalID, + Identity: largeMetaExternalID, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res.Status) @@ -318,7 +318,7 @@ func TestSuccess(t *testing.T) { // Retrieve the identity req := handler.Request{ - ExternalId: manyRateLimitsExternalID, + Identity: manyRateLimitsExternalID, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res.Status) @@ -366,7 +366,7 @@ func TestSuccess(t *testing.T) { // Immediately retrieve the identity req := handler.Request{ - ExternalId: recentExternalID, + Identity: recentExternalID, } res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) require.Equal(t, 200, res.Status) @@ -374,4 +374,20 @@ func TestSuccess(t *testing.T) { // Verify it's returned correctly require.Equal(t, recentExternalID, res.Body.Data.ExternalId) }) + + t.Run("retrieve identity with identityId", func(t *testing.T) { + // Create identity and capture the internal ID + identity := h.CreateIdentity(seed.CreateIdentityRequest{ + WorkspaceID: h.Resources().UserWorkspace.ID, + ExternalID: "unkey_works", + Meta: []byte("{}"), + }) + + req := handler.Request{ + Identity: identity, + } + res := testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) + require.Equal(t, http.StatusOK, res.Status) + require.Equal(t, "unkey_works", res.Body.Data.ExternalId) + }) } diff --git a/go/apps/api/routes/v2_identities_get_identity/400_test.go b/go/apps/api/routes/v2_identities_get_identity/400_test.go index b666686e09..19cd77bd72 100644 --- a/go/apps/api/routes/v2_identities_get_identity/400_test.go +++ b/go/apps/api/routes/v2_identities_get_identity/400_test.go @@ -27,7 +27,7 @@ func TestBadRequests(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", rootKey)}, } - t.Run("missing externalId", func(t *testing.T) { + t.Run("missing identity", func(t *testing.T) { req := handler.Request{} res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) @@ -36,15 +36,15 @@ func TestBadRequests(t *testing.T) { require.Equal(t, "https://unkey.com/docs/errors/unkey/application/invalid_input", res.Body.Error.Type) require.Equal(t, "POST request body for '/v2/identities.getIdentity' failed to validate schema", res.Body.Error.Detail) require.GreaterOrEqual(t, len(res.Body.Error.Errors), 1) - require.Equal(t, "/properties/externalId/minLength", res.Body.Error.Errors[0].Location) + require.Equal(t, "/properties/identity/minLength", res.Body.Error.Errors[0].Location) require.Equal(t, 400, res.Body.Error.Status) require.Equal(t, "Bad Request", res.Body.Error.Title) require.NotEmpty(t, res.Body.Meta.RequestId) }) - t.Run("empty externalId", func(t *testing.T) { + t.Run("empty identity", func(t *testing.T) { req := handler.Request{ - ExternalId: "", + Identity: "", } res := testutil.CallRoute[handler.Request, openapi.BadRequestErrorResponse](h, route, headers, req) require.Equal(t, 400, res.Status, "expected 400, sent: %+v, received: %s", req, res.RawBody) @@ -59,7 +59,7 @@ func TestBadRequests(t *testing.T) { t.Run("missing Authorization header", func(t *testing.T) { req := handler.Request{ - ExternalId: uid.New(uid.TestPrefix), + Identity: uid.New(uid.TestPrefix), } // Call without auth header @@ -73,7 +73,7 @@ func TestBadRequests(t *testing.T) { t.Run("malformed Authorization header", func(t *testing.T) { req := handler.Request{ - ExternalId: uid.New(uid.TestPrefix), + Identity: uid.New(uid.TestPrefix), } headers := http.Header{ diff --git a/go/apps/api/routes/v2_identities_get_identity/401_test.go b/go/apps/api/routes/v2_identities_get_identity/401_test.go index 3956891c8f..9e6daac74c 100644 --- a/go/apps/api/routes/v2_identities_get_identity/401_test.go +++ b/go/apps/api/routes/v2_identities_get_identity/401_test.go @@ -22,7 +22,7 @@ func TestUnauthorized(t *testing.T) { t.Run("invalid root key", func(t *testing.T) { req := handler.Request{ - ExternalId: "identity_123", + Identity: "identity_123", } // Non-existent key diff --git a/go/apps/api/routes/v2_identities_get_identity/403_test.go b/go/apps/api/routes/v2_identities_get_identity/403_test.go index 758d701d02..cd21a6f5ee 100644 --- a/go/apps/api/routes/v2_identities_get_identity/403_test.go +++ b/go/apps/api/routes/v2_identities_get_identity/403_test.go @@ -72,7 +72,7 @@ func TestForbidden(t *testing.T) { t.Run("no permission to read any identity", func(t *testing.T) { // The rootKey has no permissions, so it should fail req := handler.Request{ - ExternalId: externalID, + Identity: externalID, } res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, headers, req) require.Equal(t, http.StatusForbidden, res.Status) @@ -88,9 +88,9 @@ func TestForbidden(t *testing.T) { "Authorization": {fmt.Sprintf("Bearer %s", specificPermKey)}, } - // Try to use externalId when only having permission for specific identity IDs + // Try to use identity when only having permission for specific identity IDs req := handler.Request{ - ExternalId: externalID, + Identity: externalID, } res := testutil.CallRoute[handler.Request, openapi.ForbiddenErrorResponse](h, route, specificHeaders, req) require.Equal(t, http.StatusForbidden, res.Status) diff --git a/go/apps/api/routes/v2_identities_get_identity/404_test.go b/go/apps/api/routes/v2_identities_get_identity/404_test.go index a8717191b1..58424c476d 100644 --- a/go/apps/api/routes/v2_identities_get_identity/404_test.go +++ b/go/apps/api/routes/v2_identities_get_identity/404_test.go @@ -34,7 +34,7 @@ func TestNotFound(t *testing.T) { t.Run("external ID does not exist", func(t *testing.T) { nonExistentExternalID := "non_existent_external_id" req := handler.Request{ - ExternalId: nonExistentExternalID, + Identity: nonExistentExternalID, } res := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, req) require.Equal(t, http.StatusNotFound, res.Status, "expected 404, got: %d", res.Status) @@ -67,7 +67,10 @@ func TestNotFound(t *testing.T) { require.NoError(t, err) // Mark it as deleted - err = db.Query.SoftDeleteIdentity(ctx, tx, deletedIdentityID) + err = db.Query.SoftDeleteIdentity(ctx, tx, db.SoftDeleteIdentityParams{ + Identity: deletedIdentityID, + WorkspaceID: h.Resources().UserWorkspace.ID, + }) require.NoError(t, err) err = tx.Commit() @@ -75,7 +78,7 @@ func TestNotFound(t *testing.T) { // Try to retrieve the deleted identity by externalId reqByExternalId := handler.Request{ - ExternalId: deletedExternalID, + Identity: deletedExternalID, } resByExternalId := testutil.CallRoute[handler.Request, openapi.NotFoundErrorResponse](h, route, headers, reqByExternalId) require.Equal(t, http.StatusNotFound, resByExternalId.Status, "expected 404 for deleted identity (by externalId)") diff --git a/go/apps/api/routes/v2_identities_get_identity/handler.go b/go/apps/api/routes/v2_identities_get_identity/handler.go index 98f9f9aba2..c3c7291d28 100644 --- a/go/apps/api/routes/v2_identities_get_identity/handler.go +++ b/go/apps/api/routes/v2_identities_get_identity/handler.go @@ -61,12 +61,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { result, err := db.TxWithResult(ctx, h.DB.RO(), func(ctx context.Context, tx db.DBTX) (IdentityResult, error) { var identity db.Identity - identity, err = db.Query.FindIdentityByExternalID(ctx, tx, db.FindIdentityByExternalIDParams{ - ExternalID: req.ExternalId, + identity, err = db.Query.FindIdentity(ctx, tx, db.FindIdentityParams{ + Identity: req.Identity, WorkspaceID: auth.AuthorizedWorkspaceID, Deleted: false, }) - if err != nil { if db.IsNotFound(err) { return IdentityResult{}, fault.New("identity not found", @@ -118,7 +117,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } // Parse metadata - var metaMap map[string]interface{} + var metaMap map[string]any if len(identity.Meta) > 0 { err = json.Unmarshal(identity.Meta, &metaMap) if err != nil { @@ -127,7 +126,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { ) } } else { - metaMap = make(map[string]interface{}) + metaMap = make(map[string]any) } // Format ratelimits for the response diff --git a/go/apps/api/routes/v2_identities_list_identities/200_test.go b/go/apps/api/routes/v2_identities_list_identities/200_test.go index 707202aa01..b1f4731ed1 100644 --- a/go/apps/api/routes/v2_identities_list_identities/200_test.go +++ b/go/apps/api/routes/v2_identities_list_identities/200_test.go @@ -197,7 +197,10 @@ func TestSuccess(t *testing.T) { require.NoError(t, err) // Soft delete the identity - err = db.Query.SoftDeleteIdentity(ctx, tx, deletedIdentityID) + err = db.Query.SoftDeleteIdentity(ctx, tx, db.SoftDeleteIdentityParams{ + Identity: deletedIdentityID, + WorkspaceID: workspaceID, + }) require.NoError(t, err) err = tx.Commit() diff --git a/go/apps/api/routes/v2_identities_update_identity/handler.go b/go/apps/api/routes/v2_identities_update_identity/handler.go index 457d397486..8ffe3fa2f8 100644 --- a/go/apps/api/routes/v2_identities_update_identity/handler.go +++ b/go/apps/api/routes/v2_identities_update_identity/handler.go @@ -21,8 +21,10 @@ import ( "github.com/unkeyed/unkey/go/pkg/zen" ) -type Request = openapi.V2IdentitiesUpdateIdentityRequestBody -type Response = openapi.V2IdentitiesUpdateIdentityResponseBody +type ( + Request = openapi.V2IdentitiesUpdateIdentityRequestBody + Response = openapi.V2IdentitiesUpdateIdentityResponseBody +) // Handler implements zen.Route interface for the v2 identities update identity endpoint type Handler struct { @@ -111,8 +113,8 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { identity, err := db.TxWithResult(ctx, h.DB.RW(), func(ctx context.Context, tx db.DBTX) (db.Identity, error) { // Find by external ID - identity, err := db.Query.FindIdentityByExternalID(ctx, tx, db.FindIdentityByExternalIDParams{ - ExternalID: req.ExternalId, + identity, err := db.Query.FindIdentity(ctx, tx, db.FindIdentityParams{ + Identity: req.ExternalId, WorkspaceID: auth.AuthorizedWorkspaceID, Deleted: false, }) @@ -177,7 +179,6 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { ID: identity.ID, Meta: metaBytes, }) - if err != nil { // nolint:exhaustruct return db.Identity{}, fault.Wrap(err, @@ -348,7 +349,6 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { if len(rateLimitsToInsert) > 0 { err = db.BulkQuery.InsertIdentityRatelimits(ctx, tx, rateLimitsToInsert) - if err != nil { // nolint:exhaustruct return db.Identity{}, fault.Wrap(err, diff --git a/go/apps/api/routes/v2_keys_create_key/handler.go b/go/apps/api/routes/v2_keys_create_key/handler.go index ce16b0b7cf..2aa2889525 100644 --- a/go/apps/api/routes/v2_keys_create_key/handler.go +++ b/go/apps/api/routes/v2_keys_create_key/handler.go @@ -27,8 +27,10 @@ import ( "github.com/unkeyed/unkey/go/pkg/zen" ) -type Request = openapi.V2KeysCreateKeyRequestBody -type Response = openapi.V2KeysCreateKeyResponseBody +type ( + Request = openapi.V2KeysCreateKeyRequestBody + Response = openapi.V2KeysCreateKeyResponseBody +) type Handler struct { Logger logging.Logger @@ -166,7 +168,6 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { Keyring: s.AuthorizedWorkspaceID(), Data: keyResult.Key, }) - if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.ServiceUnavailable.URN()), @@ -206,9 +207,9 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { externalID := *req.ExternalId // Try to find existing identity - identity, err := db.Query.FindIdentityByExternalID(ctx, tx, db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, tx, db.FindIdentityParams{ WorkspaceID: auth.AuthorizedWorkspaceID, - ExternalID: externalID, + Identity: externalID, Deleted: false, }) @@ -231,7 +232,6 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { CreatedAt: now, Meta: []byte("{}"), }) - if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.ServiceUnavailable.URN()), @@ -315,7 +315,6 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { Encrypted: encryption.GetEncrypted(), EncryptionKeyID: encryption.GetKeyId(), }) - if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.ServiceUnavailable.URN()), @@ -356,7 +355,6 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { WorkspaceID: auth.AuthorizedWorkspaceID, Slugs: *req.Permissions, }) - if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.ServiceUnavailable.URN()), diff --git a/go/apps/api/routes/v2_keys_get_key/handler.go b/go/apps/api/routes/v2_keys_get_key/handler.go index cd55e669f4..703527c212 100644 --- a/go/apps/api/routes/v2_keys_get_key/handler.go +++ b/go/apps/api/routes/v2_keys_get_key/handler.go @@ -21,8 +21,10 @@ import ( "github.com/unkeyed/unkey/go/pkg/zen" ) -type Request = openapi.V2KeysGetKeyRequestBody -type Response = openapi.V2KeysGetKeyResponseBody +type ( + Request = openapi.V2KeysGetKeyRequestBody + Response = openapi.V2KeysGetKeyResponseBody +) // Handler implements zen.Route interface for the v2 keys.getKey endpoint type Handler struct { @@ -229,7 +231,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } if key.IdentityID.Valid { - identity, idErr := db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ID: key.IdentityID.String, Deleted: false}) + identity, idErr := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + Identity: key.IdentityID.String, + Deleted: false, + WorkspaceID: auth.AuthorizedWorkspaceID, + }) if idErr != nil { if db.IsNotFound(idErr) { return fault.New("identity not found for key", diff --git a/go/apps/api/routes/v2_keys_update_key/200_test.go b/go/apps/api/routes/v2_keys_update_key/200_test.go index 4d66721706..9b81d42fdd 100644 --- a/go/apps/api/routes/v2_keys_update_key/200_test.go +++ b/go/apps/api/routes/v2_keys_update_key/200_test.go @@ -106,7 +106,7 @@ func TestUpdateKeyUpdateAllFields(t *testing.T) { KeyId: keyResponse.KeyID, Name: nullable.NewNullableWithValue("newName"), ExternalId: nullable.NewNullableWithValue("newExternalId"), - Meta: nullable.NewNullableWithValue(map[string]interface{}{"new": "meta"}), + Meta: nullable.NewNullableWithValue(map[string]any{"new": "meta"}), Expires: nullable.NewNullNullable[int64](), Enabled: ptr.P(true), Credits: &openapi.KeyCreditsData{ @@ -134,8 +134,9 @@ func TestUpdateKeyUpdateAllFields(t *testing.T) { require.Equal(t, int32(50), key.RefillAmount.Int32) // Verify identity was created with correct external ID - identity, err := db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: key.IdentityID.String, + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + Identity: key.IdentityID.String, + WorkspaceID: h.Resources().UserWorkspace.ID, }) require.NoError(t, err) require.Equal(t, "newExternalId", identity.ExternalID) diff --git a/go/apps/api/routes/v2_keys_update_key/handler.go b/go/apps/api/routes/v2_keys_update_key/handler.go index 3ad7852e1b..a9a6e76fa4 100644 --- a/go/apps/api/routes/v2_keys_update_key/handler.go +++ b/go/apps/api/routes/v2_keys_update_key/handler.go @@ -23,8 +23,10 @@ import ( "github.com/unkeyed/unkey/go/pkg/zen" ) -type Request = openapi.V2KeysUpdateKeyRequestBody -type Response = openapi.V2KeysUpdateKeyResponseBody +type ( + Request = openapi.V2KeysUpdateKeyRequestBody + Response = openapi.V2KeysUpdateKeyResponseBody +) type Handler struct { Logger logging.Logger @@ -148,9 +150,9 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { externalID := req.ExternalId.MustGet() // Try to find existing identity - identity, err := db.Query.FindIdentityByExternalID(ctx, tx, db.FindIdentityByExternalIDParams{ + identity, err := db.Query.FindIdentity(ctx, tx, db.FindIdentityParams{ WorkspaceID: auth.AuthorizedWorkspaceID, - ExternalID: externalID, + Identity: externalID, Deleted: false, }) @@ -173,7 +175,6 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { CreatedAt: time.Now().UnixMilli(), Meta: []byte("{}"), }) - if err != nil { // Incase of duplicate key error just find existing identity if !db.IsDuplicateKeyError(err) { @@ -184,12 +185,11 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { ) } - identity, err = db.Query.FindIdentityByExternalID(ctx, tx, db.FindIdentityByExternalIDParams{ + identity, err = db.Query.FindIdentity(ctx, tx, db.FindIdentityParams{ WorkspaceID: auth.AuthorizedWorkspaceID, - ExternalID: externalID, + Identity: externalID, Deleted: false, }) - if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.ServiceUnavailable.URN()), @@ -373,7 +373,6 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { WorkspaceID: auth.AuthorizedWorkspaceID, Slugs: *req.Permissions, }) - if err != nil { return fault.Wrap(err, fault.Code(codes.App.Internal.ServiceUnavailable.URN()), diff --git a/go/apps/api/routes/v2_keys_update_key/three_state_test.go b/go/apps/api/routes/v2_keys_update_key/three_state_test.go index 95a6866dbf..fff2eac2c9 100644 --- a/go/apps/api/routes/v2_keys_update_key/three_state_test.go +++ b/go/apps/api/routes/v2_keys_update_key/three_state_test.go @@ -268,8 +268,9 @@ func TestThreeStateUpdateLogic(t *testing.T) { require.True(t, key.IdentityID.Valid) // Check that the identity exists - identity, err := db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: key.IdentityID.String, + identity, err := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + Identity: key.IdentityID.String, + WorkspaceID: h.Resources().UserWorkspace.ID, }) require.NoError(t, err) require.Equal(t, "updated-user", identity.ExternalID) @@ -314,8 +315,9 @@ func TestThreeStateUpdateLogic(t *testing.T) { require.True(t, key.IdentityID.Valid) // Check that the identity exists with correct external ID - identity, err = db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ - ID: key.IdentityID.String, + identity, err = db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{ + Identity: key.IdentityID.String, + WorkspaceID: h.Resources().UserWorkspace.ID, }) require.NoError(t, err) require.Equal(t, "preserved-user", identity.ExternalID) @@ -342,11 +344,11 @@ func TestThreeStateUpdateLogic(t *testing.T) { // Update all fields with different three-state behaviors req = handler.Request{ KeyId: keyResponse.KeyID, - Name: nullable.NewNullableWithValue("updated-name"), // Set to value - ExternalId: nullable.NewNullNullable[string](), // Set to NULL - Meta: nullable.NewNullableWithValue(map[string]interface{}{}), // Set to empty object - Expires: nullable.NewNullNullable[int64](), // Set to NULL - Enabled: ptr.P(false), // Set to specific value + Name: nullable.NewNullableWithValue("updated-name"), // Set to value + ExternalId: nullable.NewNullNullable[string](), // Set to NULL + Meta: nullable.NewNullableWithValue(map[string]any{}), // Set to empty object + Expires: nullable.NewNullNullable[int64](), // Set to NULL + Enabled: ptr.P(false), // Set to specific value } res = testutil.CallRoute[handler.Request, handler.Response](h, route, headers, req) diff --git a/go/apps/api/routes/v2_keys_whoami/handler.go b/go/apps/api/routes/v2_keys_whoami/handler.go index bf881e3dcb..cc6b7eee8b 100644 --- a/go/apps/api/routes/v2_keys_whoami/handler.go +++ b/go/apps/api/routes/v2_keys_whoami/handler.go @@ -21,8 +21,10 @@ import ( "github.com/unkeyed/unkey/go/pkg/zen" ) -type Request = openapi.V2KeysWhoamiRequestBody -type Response = openapi.V2KeysWhoamiResponseBody +type ( + Request = openapi.V2KeysWhoamiRequestBody + Response = openapi.V2KeysWhoamiResponseBody +) type Handler struct { // Services as public fields @@ -44,7 +46,6 @@ func (h *Handler) Path() string { } func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { - auth, emit, err := h.Keys.GetRootKey(ctx, s) defer emit() if err != nil { @@ -162,7 +163,7 @@ func (h *Handler) Handle(ctx context.Context, s *zen.Session) error { } if key.IdentityID.Valid { - identity, idErr := db.Query.FindIdentityByID(ctx, h.DB.RO(), db.FindIdentityByIDParams{ID: key.IdentityID.String, Deleted: false}) + identity, idErr := db.Query.FindIdentity(ctx, h.DB.RO(), db.FindIdentityParams{Identity: key.IdentityID.String, WorkspaceID: auth.AuthorizedWorkspaceID, Deleted: false}) if idErr != nil { if db.IsNotFound(idErr) { return fault.New("identity not found for key", diff --git a/go/pkg/codes/constants_gen.go b/go/pkg/codes/constants_gen.go index 70deeca842..4fce01b97d 100644 --- a/go/pkg/codes/constants_gen.go +++ b/go/pkg/codes/constants_gen.go @@ -6,20 +6,20 @@ type URN string // Error code constants for use in switch statements for exhaustive checking const ( - // ---------------- - // UserErrors - // ---------------- +// ---------------- +// UserErrors +// ---------------- - // BadRequest +// BadRequest // PermissionsQuerySyntaxError indicates a syntax or lexical error in verifyKey permissions query parsing. UserErrorsBadRequestPermissionsQuerySyntaxError URN = "err:user:bad_request:permissions_query_syntax_error" - // ---------------- - // UnkeyAuthErrors - // ---------------- +// ---------------- +// UnkeyAuthErrors +// ---------------- - // Authentication +// Authentication // Missing indicates authentication credentials were not provided. UnkeyAuthErrorsAuthenticationMissing URN = "err:unkey:authentication:missing" @@ -28,7 +28,7 @@ const ( // KeyNotFound indicates the authentication key was not found. UnkeyAuthErrorsAuthenticationKeyNotFound URN = "err:unkey:authentication:key_not_found" - // Authorization +// Authorization // InsufficientPermissions indicates the authenticated entity lacks // sufficient permissions for the requested operation. @@ -40,91 +40,92 @@ const ( // WorkspaceDisabled indicates the associated workspace is disabled. UnkeyAuthErrorsAuthorizationWorkspaceDisabled URN = "err:unkey:authorization:workspace_disabled" - // ---------------- - // UnkeyDataErrors - // ---------------- +// ---------------- +// UnkeyDataErrors +// ---------------- - // Key +// Key // NotFound indicates the requested key was not found. UnkeyDataErrorsKeyNotFound URN = "err:unkey:data:key_not_found" - // Workspace +// Workspace // NotFound indicates the requested workspace was not found. UnkeyDataErrorsWorkspaceNotFound URN = "err:unkey:data:workspace_not_found" - // Api +// Api // NotFound indicates the requested API was not found. UnkeyDataErrorsApiNotFound URN = "err:unkey:data:api_not_found" - // Permission +// Permission // Duplicate indicates the requested permission already exists. UnkeyDataErrorsPermissionDuplicate URN = "err:unkey:data:permission_already_exists" // NotFound indicates the requested permission was not found. UnkeyDataErrorsPermissionNotFound URN = "err:unkey:data:permission_not_found" - // Role +// Role // Duplicate indicates the requested role already exists. UnkeyDataErrorsRoleDuplicate URN = "err:unkey:data:role_already_exists" // NotFound indicates the requested role was not found. UnkeyDataErrorsRoleNotFound URN = "err:unkey:data:role_not_found" - // KeyAuth +// KeyAuth // NotFound indicates the requested key authentication was not found. UnkeyDataErrorsKeyAuthNotFound URN = "err:unkey:data:key_auth_not_found" - // RatelimitNamespace +// RatelimitNamespace // NotFound indicates the requested rate limit namespace was not found. UnkeyDataErrorsRatelimitNamespaceNotFound URN = "err:unkey:data:ratelimit_namespace_not_found" - // RatelimitOverride +// RatelimitOverride // NotFound indicates the requested rate limit override was not found. UnkeyDataErrorsRatelimitOverrideNotFound URN = "err:unkey:data:ratelimit_override_not_found" - // Identity +// Identity // NotFound indicates the requested identity was not found. UnkeyDataErrorsIdentityNotFound URN = "err:unkey:data:identity_not_found" // Duplicate indicates the requested identity already exists. UnkeyDataErrorsIdentityDuplicate URN = "err:unkey:data:identity_already_exists" - // AuditLog +// AuditLog // NotFound indicates the requested audit log was not found. UnkeyDataErrorsAuditLogNotFound URN = "err:unkey:data:audit_log_not_found" - // ---------------- - // UnkeyAppErrors - // ---------------- +// ---------------- +// UnkeyAppErrors +// ---------------- - // Internal +// Internal // UnexpectedError represents an unhandled or unexpected error condition. UnkeyAppErrorsInternalUnexpectedError URN = "err:unkey:application:unexpected_error" // ServiceUnavailable indicates a service is temporarily unavailable. UnkeyAppErrorsInternalServiceUnavailable URN = "err:unkey:application:service_unavailable" - // Validation +// Validation // InvalidInput indicates a client provided input that failed validation. UnkeyAppErrorsValidationInvalidInput URN = "err:unkey:application:invalid_input" // AssertionFailed indicates a runtime assertion or invariant check failed. UnkeyAppErrorsValidationAssertionFailed URN = "err:unkey:application:assertion_failed" - // Protection +// Protection // ProtectedResource indicates an attempt to modify a protected resource. UnkeyAppErrorsProtectionProtectedResource URN = "err:unkey:application:protected_resource" - // Precondition +// Precondition // PreconditionFailed indicates a precondition check failed. UnkeyAppErrorsPreconditionPreconditionFailed URN = "err:unkey:application:precondition_failed" + ) diff --git a/go/pkg/db/identity_delete.sql_generated.go b/go/pkg/db/identity_delete.sql_generated.go index 412703effa..cba318f3e1 100644 --- a/go/pkg/db/identity_delete.sql_generated.go +++ b/go/pkg/db/identity_delete.sql_generated.go @@ -10,13 +10,22 @@ import ( ) const deleteIdentity = `-- name: DeleteIdentity :exec -DELETE FROM identities WHERE id = ? +DELETE FROM identities +WHERE workspace_id = ? + AND (id = ? OR external_id = ?) ` +type DeleteIdentityParams struct { + WorkspaceID string `db:"workspace_id"` + Identity string `db:"identity"` +} + // DeleteIdentity // -// DELETE FROM identities WHERE id = ? -func (q *Queries) DeleteIdentity(ctx context.Context, db DBTX, id string) error { - _, err := db.ExecContext(ctx, deleteIdentity, id) +// DELETE FROM identities +// WHERE workspace_id = ? +// AND (id = ? OR external_id = ?) +func (q *Queries) DeleteIdentity(ctx context.Context, db DBTX, arg DeleteIdentityParams) error { + _, err := db.ExecContext(ctx, deleteIdentity, arg.WorkspaceID, arg.Identity, arg.Identity) return err } diff --git a/go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go b/go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go new file mode 100644 index 0000000000..35b79f1f5f --- /dev/null +++ b/go/pkg/db/identity_delete_old_with_ratelimits.sql_generated.go @@ -0,0 +1,37 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: identity_delete_old_with_ratelimits.sql + +package db + +import ( + "context" +) + +const deleteOldIdentityWithRatelimits = `-- name: DeleteOldIdentityWithRatelimits :exec +DELETE i, rl +FROM identities i +LEFT JOIN ratelimits rl ON rl.identity_id = i.id +WHERE i.workspace_id = ? + AND (i.id = ? OR i.external_id = ?) + AND i.deleted = true +` + +type DeleteOldIdentityWithRatelimitsParams struct { + WorkspaceID string `db:"workspace_id"` + Identity string `db:"identity"` +} + +// DeleteOldIdentityWithRatelimits +// +// DELETE i, rl +// FROM identities i +// LEFT JOIN ratelimits rl ON rl.identity_id = i.id +// WHERE i.workspace_id = ? +// AND (i.id = ? OR i.external_id = ?) +// AND i.deleted = true +func (q *Queries) DeleteOldIdentityWithRatelimits(ctx context.Context, db DBTX, arg DeleteOldIdentityWithRatelimitsParams) error { + _, err := db.ExecContext(ctx, deleteOldIdentityWithRatelimits, arg.WorkspaceID, arg.Identity, arg.Identity) + return err +} diff --git a/go/pkg/db/identity_find.sql_generated.go b/go/pkg/db/identity_find.sql_generated.go new file mode 100644 index 0000000000..6308ac746a --- /dev/null +++ b/go/pkg/db/identity_find.sql_generated.go @@ -0,0 +1,52 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: identity_find.sql + +package db + +import ( + "context" +) + +const findIdentity = `-- name: FindIdentity :one +SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at +FROM identities +WHERE workspace_id = ? + AND (external_id = ? OR id = ?) + AND deleted = ? +` + +type FindIdentityParams struct { + WorkspaceID string `db:"workspace_id"` + Identity string `db:"identity"` + Deleted bool `db:"deleted"` +} + +// FindIdentity +// +// SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at +// FROM identities +// WHERE workspace_id = ? +// AND (external_id = ? OR id = ?) +// AND deleted = ? +func (q *Queries) FindIdentity(ctx context.Context, db DBTX, arg FindIdentityParams) (Identity, error) { + row := db.QueryRowContext(ctx, findIdentity, + arg.WorkspaceID, + arg.Identity, + arg.Identity, + arg.Deleted, + ) + var i Identity + err := row.Scan( + &i.ID, + &i.ExternalID, + &i.WorkspaceID, + &i.Environment, + &i.Meta, + &i.Deleted, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/go/pkg/db/identity_find_by_external_id.sql_generated.go b/go/pkg/db/identity_find_by_external_id.sql_generated.go deleted file mode 100644 index 465f6a6ff3..0000000000 --- a/go/pkg/db/identity_find_by_external_id.sql_generated.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 -// source: identity_find_by_external_id.sql - -package db - -import ( - "context" -) - -const findIdentityByExternalID = `-- name: FindIdentityByExternalID :one -SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at FROM identities WHERE workspace_id = ? AND external_id = ? AND deleted = ? -` - -type FindIdentityByExternalIDParams struct { - WorkspaceID string `db:"workspace_id"` - ExternalID string `db:"external_id"` - Deleted bool `db:"deleted"` -} - -// FindIdentityByExternalID -// -// SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at FROM identities WHERE workspace_id = ? AND external_id = ? AND deleted = ? -func (q *Queries) FindIdentityByExternalID(ctx context.Context, db DBTX, arg FindIdentityByExternalIDParams) (Identity, error) { - row := db.QueryRowContext(ctx, findIdentityByExternalID, arg.WorkspaceID, arg.ExternalID, arg.Deleted) - var i Identity - err := row.Scan( - &i.ID, - &i.ExternalID, - &i.WorkspaceID, - &i.Environment, - &i.Meta, - &i.Deleted, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} diff --git a/go/pkg/db/identity_find_by_id.sql_generated.go b/go/pkg/db/identity_find_by_id.sql_generated.go deleted file mode 100644 index f54ddbb60e..0000000000 --- a/go/pkg/db/identity_find_by_id.sql_generated.go +++ /dev/null @@ -1,38 +0,0 @@ -// Code generated by sqlc. DO NOT EDIT. -// versions: -// sqlc v1.27.0 -// source: identity_find_by_id.sql - -package db - -import ( - "context" -) - -const findIdentityByID = `-- name: FindIdentityByID :one -SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at FROM identities WHERE id = ? AND deleted = ? -` - -type FindIdentityByIDParams struct { - ID string `db:"id"` - Deleted bool `db:"deleted"` -} - -// FindIdentityByID -// -// SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at FROM identities WHERE id = ? AND deleted = ? -func (q *Queries) FindIdentityByID(ctx context.Context, db DBTX, arg FindIdentityByIDParams) (Identity, error) { - row := db.QueryRowContext(ctx, findIdentityByID, arg.ID, arg.Deleted) - var i Identity - err := row.Scan( - &i.ID, - &i.ExternalID, - &i.WorkspaceID, - &i.Environment, - &i.Meta, - &i.Deleted, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} diff --git a/go/pkg/db/identity_soft_delete.sql_generated.go b/go/pkg/db/identity_soft_delete.sql_generated.go index 2a4013c152..dd26d7492d 100644 --- a/go/pkg/db/identity_soft_delete.sql_generated.go +++ b/go/pkg/db/identity_soft_delete.sql_generated.go @@ -10,13 +10,24 @@ import ( ) const softDeleteIdentity = `-- name: SoftDeleteIdentity :exec -UPDATE identities set deleted = 1 WHERE id = ? +UPDATE identities +SET deleted = 1 +WHERE workspace_id = ? + AND (id = ? OR external_id = ?) ` +type SoftDeleteIdentityParams struct { + WorkspaceID string `db:"workspace_id"` + Identity string `db:"identity"` +} + // SoftDeleteIdentity // -// UPDATE identities set deleted = 1 WHERE id = ? -func (q *Queries) SoftDeleteIdentity(ctx context.Context, db DBTX, id string) error { - _, err := db.ExecContext(ctx, softDeleteIdentity, id) +// UPDATE identities +// SET deleted = 1 +// WHERE workspace_id = ? +// AND (id = ? OR external_id = ?) +func (q *Queries) SoftDeleteIdentity(ctx context.Context, db DBTX, arg SoftDeleteIdentityParams) error { + _, err := db.ExecContext(ctx, softDeleteIdentity, arg.WorkspaceID, arg.Identity, arg.Identity) return err } diff --git a/go/pkg/db/querier_generated.go b/go/pkg/db/querier_generated.go index 33bdb503f9..d63aa37fb1 100644 --- a/go/pkg/db/querier_generated.go +++ b/go/pkg/db/querier_generated.go @@ -22,8 +22,10 @@ type Querier interface { DeleteAllKeyRolesByKeyID(ctx context.Context, db DBTX, keyID string) error //DeleteIdentity // - // DELETE FROM identities WHERE id = ? - DeleteIdentity(ctx context.Context, db DBTX, id string) error + // DELETE FROM identities + // WHERE workspace_id = ? + // AND (id = ? OR external_id = ?) + DeleteIdentity(ctx context.Context, db DBTX, arg DeleteIdentityParams) error //DeleteKeyByID // // DELETE k, kp, kr, rl, ek @@ -82,6 +84,15 @@ type Querier interface { // DELETE FROM roles_permissions // WHERE role_id = ? DeleteManyRolePermissionsByRoleID(ctx context.Context, db DBTX, roleID string) error + //DeleteOldIdentityWithRatelimits + // + // DELETE i, rl + // FROM identities i + // LEFT JOIN ratelimits rl ON rl.identity_id = i.id + // WHERE i.workspace_id = ? + // AND (i.id = ? OR i.external_id = ?) + // AND i.deleted = true + DeleteOldIdentityWithRatelimits(ctx context.Context, db DBTX, arg DeleteOldIdentityWithRatelimitsParams) error //DeletePermission // // DELETE FROM permissions @@ -160,14 +171,14 @@ type Querier interface { // WHERE version_id = ? AND is_enabled = true // ORDER BY created_at ASC FindHostnameRoutesByVersionId(ctx context.Context, db DBTX, versionID string) ([]HostnameRoute, error) - //FindIdentityByExternalID - // - // SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at FROM identities WHERE workspace_id = ? AND external_id = ? AND deleted = ? - FindIdentityByExternalID(ctx context.Context, db DBTX, arg FindIdentityByExternalIDParams) (Identity, error) - //FindIdentityByID + //FindIdentity // - // SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at FROM identities WHERE id = ? AND deleted = ? - FindIdentityByID(ctx context.Context, db DBTX, arg FindIdentityByIDParams) (Identity, error) + // SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at + // FROM identities + // WHERE workspace_id = ? + // AND (external_id = ? OR id = ?) + // AND deleted = ? + FindIdentity(ctx context.Context, db DBTX, arg FindIdentityParams) (Identity, error) //FindKeyByHash // // SELECT id, key_auth_id, hash, start, workspace_id, for_workspace_id, name, owner_id, identity_id, meta, expires, created_at_m, updated_at_m, deleted_at_m, refill_day, refill_amount, last_refill_at, enabled, remaining_requests, ratelimit_async, ratelimit_limit, ratelimit_duration, environment FROM `keys` WHERE hash = ? @@ -1245,8 +1256,11 @@ type Querier interface { SoftDeleteApi(ctx context.Context, db DBTX, arg SoftDeleteApiParams) error //SoftDeleteIdentity // - // UPDATE identities set deleted = 1 WHERE id = ? - SoftDeleteIdentity(ctx context.Context, db DBTX, id string) error + // UPDATE identities + // SET deleted = 1 + // WHERE workspace_id = ? + // AND (id = ? OR external_id = ?) + SoftDeleteIdentity(ctx context.Context, db DBTX, arg SoftDeleteIdentityParams) error //SoftDeleteKeyByID // // UPDATE `keys` SET deleted_at_m = ? WHERE id = ? diff --git a/go/pkg/db/queries/identity_delete.sql b/go/pkg/db/queries/identity_delete.sql index 9d758f12cf..77e4a38d98 100644 --- a/go/pkg/db/queries/identity_delete.sql +++ b/go/pkg/db/queries/identity_delete.sql @@ -1,2 +1,4 @@ -- name: DeleteIdentity :exec -DELETE FROM identities WHERE id = sqlc.arg('id') +DELETE FROM identities +WHERE workspace_id = sqlc.arg(workspace_id) + AND (id = sqlc.arg(identity) OR external_id = sqlc.arg(identity)); diff --git a/go/pkg/db/queries/identity_delete_old_with_ratelimits.sql b/go/pkg/db/queries/identity_delete_old_with_ratelimits.sql new file mode 100644 index 0000000000..0aafb69366 --- /dev/null +++ b/go/pkg/db/queries/identity_delete_old_with_ratelimits.sql @@ -0,0 +1,7 @@ +-- name: DeleteOldIdentityWithRatelimits :exec +DELETE i, rl +FROM identities i +LEFT JOIN ratelimits rl ON rl.identity_id = i.id +WHERE i.workspace_id = sqlc.arg(workspace_id) + AND (i.id = sqlc.arg(identity) OR i.external_id = sqlc.arg(identity)) + AND i.deleted = true; diff --git a/go/pkg/db/queries/identity_find.sql b/go/pkg/db/queries/identity_find.sql new file mode 100644 index 0000000000..c9ad98a161 --- /dev/null +++ b/go/pkg/db/queries/identity_find.sql @@ -0,0 +1,6 @@ +-- name: FindIdentity :one +SELECT * +FROM identities +WHERE workspace_id = sqlc.arg(workspace_id) + AND (external_id = sqlc.arg(identity) OR id = sqlc.arg(identity)) + AND deleted = sqlc.arg(deleted); diff --git a/go/pkg/db/queries/identity_find_by_external_id.sql b/go/pkg/db/queries/identity_find_by_external_id.sql deleted file mode 100644 index 61c02db6be..0000000000 --- a/go/pkg/db/queries/identity_find_by_external_id.sql +++ /dev/null @@ -1,2 +0,0 @@ --- name: FindIdentityByExternalID :one -SELECT * FROM identities WHERE workspace_id = sqlc.arg(workspace_id) AND external_id = sqlc.arg(external_id) AND deleted = sqlc.arg(deleted); diff --git a/go/pkg/db/queries/identity_find_by_id.sql b/go/pkg/db/queries/identity_find_by_id.sql deleted file mode 100644 index af5810122b..0000000000 --- a/go/pkg/db/queries/identity_find_by_id.sql +++ /dev/null @@ -1,2 +0,0 @@ --- name: FindIdentityByID :one -SELECT * FROM identities WHERE id = sqlc.arg(id) AND deleted = sqlc.arg(deleted); diff --git a/go/pkg/db/queries/identity_soft_delete.sql b/go/pkg/db/queries/identity_soft_delete.sql index 4ef0e838cb..88cbc95748 100644 --- a/go/pkg/db/queries/identity_soft_delete.sql +++ b/go/pkg/db/queries/identity_soft_delete.sql @@ -1,2 +1,5 @@ -- name: SoftDeleteIdentity :exec -UPDATE identities set deleted = 1 WHERE id = sqlc.arg('id') +UPDATE identities +SET deleted = 1 +WHERE workspace_id = sqlc.arg('workspace_id') + AND (id = sqlc.arg('identity') OR external_id = sqlc.arg('identity')); diff --git a/go/pkg/db/schema.sql b/go/pkg/db/schema.sql index 7a5094271b..275a93152e 100644 --- a/go/pkg/db/schema.sql +++ b/go/pkg/db/schema.sql @@ -430,6 +430,7 @@ CREATE INDEX `owner_id_idx` ON `keys` (`owner_id`); CREATE INDEX `identity_id_idx` ON `keys` (`identity_id`); CREATE INDEX `idx_keys_on_workspace_id` ON `keys` (`workspace_id`); CREATE INDEX `deleted_at_idx` ON `keys` (`deleted_at_m`); +CREATE INDEX `workspace_id_id_deleted_idx` ON `identities` (`workspace_id`, `id`, `deleted`); CREATE INDEX `name_idx` ON `ratelimits` (`name`); CREATE INDEX `identity_id_idx` ON `ratelimits` (`identity_id`); CREATE INDEX `key_id_idx` ON `ratelimits` (`key_id`);