Skip to content

Commit

Permalink
feat(console,core): remove DataHook devFeature guard (#5898)
Browse files Browse the repository at this point in the history
* feat(console,core): remove DataHook devFeature guard

remove DataHook devFeature guard

* chore: add changeset

add changeset

* chore: update changesets

update changesets
  • Loading branch information
simeng-li committed May 22, 2024
1 parent 7f5625d commit b5104d8
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 67 deletions.
79 changes: 79 additions & 0 deletions .changeset/hip-fireants-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
"@logto/console": minor
"@logto/core": minor
"@logto/schemas": minor
---

add new webhook events

We introduce a new event type `DataHook` to unlock a series of events that can be triggered by data updates (mostly Management API):

- User.Created
- User.Deleted
- User.Data.Updated
- User.SuspensionStatus.Updated
- Role.Created
- Role.Deleted
- Role.Data.Updated
- Role.Scopes.Updated
- Scope.Created
- Scope.Deleted
- Scope.Data.Updated
- Organization.Created
- Organization.Deleted
- Organization.Data.Updated
- Organization.Membership.Updated
- OrganizationRole.Created
- OrganizationRole.Deleted
- OrganizationRole.Data.Updated
- OrganizationRole.Scopes.Updated
- OrganizationScope.Created
- OrganizationScope.Deleted
- OrganizationScope.Data.Updated

DataHook events are triggered when the data associated with the event is updated via management API request or user interaction actions.

### Management API triggered events

| API endpoint | Event |
| ---------------------------------------------------------- | ----------------------------------------------------------- |
| POST /users | User.Created |
| DELETE /users/:userId | User.Deleted |
| PATCH /users/:userId | User.Data.Updated |
| PATCH /users/:userId/custom-data | User.Data.Updated |
| PATCH /users/:userId/profile | User.Data.Updated |
| PATCH /users/:userId/password | User.Data.Updated |
| PATCH /users/:userId/is-suspended | User.SuspensionStatus.Updated |
| POST /roles | Role.Created, (Role.Scopes.Update) |
| DELETE /roles/:id | Role.Deleted |
| PATCH /roles/:id | Role.Data.Updated |
| POST /roles/:id/scopes | Role.Scopes.Updated |
| DELETE /roles/:id/scopes/:scopeId | Role.Scopes.Updated |
| POST /resources/:resourceId/scopes | Scope.Created |
| DELETE /resources/:resourceId/scopes/:scopeId | Scope.Deleted |
| PATCH /resources/:resourceId/scopes/:scopeId | Scope.Data.Updated |
| POST /organizations | Organization.Created |
| DELETE /organizations/:id | Organization.Deleted |
| PATCH /organizations/:id | Organization.Data.Updated |
| PUT /organizations/:id/users | Organization.Membership.Updated |
| POST /organizations/:id/users | Organization.Membership.Updated |
| DELETE /organizations/:id/users/:userId | Organization.Membership.Updated |
| POST /organization-roles | OrganizationRole.Created, (OrganizationRole.Scopes.Updated) |
| DELETE /organization-roles/:id | OrganizationRole.Deleted |
| PATCH /organization-roles/:id | OrganizationRole.Data.Updated |
| POST /organization-scopes | OrganizationScope.Created |
| DELETE /organization-scopes/:id | OrganizationScope.Deleted |
| PATCH /organization-scopes/:id | OrganizationScope.Data.Updated |
| PUT /organization-roles/:id/scopes | OrganizationRole.Scopes.Updated |
| POST /organization-roles/:id/scopes | OrganizationRole.Scopes.Updated |
| DELETE /organization-roles/:id/scopes/:organizationScopeId | OrganizationRole.Scopes.Updated |

### User interaction triggered events

| User interaction action | Event |
| ------------------------ | ----------------- |
| User email/phone linking | User.Data.Updated |
| User MFAs linking | User.Data.Updated |
| User social/SSO linking | User.Data.Updated |
| User password reset | User.Data.Updated |
| User registration | User.Created |
16 changes: 6 additions & 10 deletions packages/console/src/components/BasicWebhookForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { type Hook, type HookConfig, type HookEvent } from '@logto/schemas';
import { Controller, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

import { isDevFeaturesEnabled } from '@/consts/env';
import {
dataHookEventsLabel,
interactionHookEvents,
Expand All @@ -18,15 +17,12 @@ import { uriValidator } from '@/utils/validator';
import * as styles from './index.module.scss';

const hookEventGroups: Array<CheckboxOptionGroup<HookEvent>> = [
// TODO: Remove dev feature guard
...(isDevFeaturesEnabled
? schemaGroupedDataHookEvents.map(([schema, events]) => ({
title: dataHookEventsLabel[schema],
options: events.map((event) => ({
value: event,
})),
}))
: []),
...schemaGroupedDataHookEvents.map(([schema, events]) => ({
title: dataHookEventsLabel[schema],
options: events.map((event) => ({
value: event,
})),
})),
{
title: 'webhooks.schemas.interaction',
options: interactionHookEvents.map((event) => ({
Expand Down
1 change: 1 addition & 0 deletions packages/console/src/consts/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ const isProduction = process.env.NODE_ENV === 'production';
export const isCloud = yes(process.env.IS_CLOUD);
export const adminEndpoint = process.env.ADMIN_ENDPOINT;

// eslint-disable-next-line import/no-unused-modules
export const isDevFeaturesEnabled =
!isProduction || yes(process.env.DEV_FEATURES_ENABLED) || yes(process.env.INTEGRATION_TEST);
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import { z } from 'zod';
import EventSelector from '@/components/AuditLogTable/components/EventSelector';
import EmptyDataPlaceholder from '@/components/EmptyDataPlaceholder';
import { defaultPageSize } from '@/consts';
import { isDevFeaturesEnabled } from '@/consts/env';
import { interactionHookEvents } from '@/consts/webhooks';
import Table from '@/ds-components/Table';
import Tag from '@/ds-components/Tag';
import { type RequestError } from '@/hooks/use-api';
Expand All @@ -22,10 +20,7 @@ import { buildHookEventLogKey, getHookEventKey } from '../utils';

import * as styles from './index.module.scss';

// TODO: Remove dev feature guard
const webhookEvents = isDevFeaturesEnabled ? hookEvents : interactionHookEvents;

const hookLogEventOptions = webhookEvents.map((event) => ({
const hookLogEventOptions = hookEvents.map((event) => ({
title: event,
value: buildHookEventLogKey(event),
}));
Expand Down
7 changes: 0 additions & 7 deletions packages/core/src/middleware/koa-management-api-hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { trySafe } from '@silverhand/essentials';
import { type MiddlewareType } from 'koa';
import { type IRouterParamContext } from 'koa-router';

import { EnvSet } from '#src/env-set/index.js';
import { DataHookContextManager } from '#src/libraries/hook/context-manager.js';
import type Libraries from '#src/tenants/Libraries.js';
import { getConsoleLogFromContext } from '#src/utils/console.js';
Expand All @@ -22,12 +21,6 @@ export const koaManagementApiHooks = <StateT, ContextT extends IRouterParamConte
hooks: Libraries['hooks']
): MiddlewareType<StateT, WithHookContext<ContextT>, ResponseT> => {
return async (ctx, next) => {
// TODO: Remove dev feature guard
const { isDevFeaturesEnabled } = EnvSet.values;
if (!isDevFeaturesEnabled) {
return next();
}

const {
header: { 'user-agent': userAgent },
ip,
Expand Down
12 changes: 2 additions & 10 deletions packages/core/src/routes/hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
hookEventGuard,
hookEventsGuard,
hookResponseGuard,
interactionHookEventGuard,
type Hook,
type HookResponse,
} from '@logto/schemas';
Expand All @@ -15,7 +14,6 @@ import { conditional, deduplicate, yes } from '@silverhand/essentials';
import { subDays } from 'date-fns';
import { z } from 'zod';

import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
Expand All @@ -25,12 +23,7 @@ import assertThat from '#src/utils/assert-that.js';

import type { ManagementApiRouter, RouterInitArgs } from './types.js';

const { isDevFeaturesEnabled } = EnvSet.values;
// TODO: remove dev features guard
const webhookEventsGuard = isDevFeaturesEnabled
? hookEventsGuard
: interactionHookEventGuard.array();
const nonemptyUniqueHookEventsGuard = webhookEventsGuard
const nonemptyUniqueHookEventsGuard = hookEventsGuard
.nonempty()
.transform((events) => deduplicate(events));

Expand Down Expand Up @@ -167,8 +160,7 @@ export default function hookRoutes<T extends ManagementApiRouter>(
koaQuotaGuard({ key: 'hooksLimit', quota }),
koaGuard({
body: Hooks.createGuard.omit({ id: true, signingKey: true }).extend({
// TODO: remove dev features guard
event: (isDevFeaturesEnabled ? hookEventGuard : interactionHookEventGuard).optional(),
event: hookEventGuard.optional(),
events: nonemptyUniqueHookEventsGuard.optional(),
}),
response: Hooks.guard,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { userInfoSelectFields, type DataHookEvent, type User } from '@logto/schemas';
import { conditional, conditionalString, noop, pick, trySafe } from '@silverhand/essentials';
import { conditional, conditionalString, pick, trySafe } from '@silverhand/essentials';
import type { MiddlewareType } from 'koa';
import type { IRouterParamContext } from 'koa-router';

import { EnvSet } from '#src/env-set/index.js';
import {
DataHookContextManager,
InteractionHookContextManager,
Expand Down Expand Up @@ -41,7 +40,6 @@ export default function koaInteractionHooks<
hooks: { triggerInteractionHooks, triggerDataHooks },
}: Libraries): MiddlewareType<StateT, WithInteractionHooksContext<ContextT>, ResponseT> {
return async (ctx, next) => {
const { isDevFeaturesEnabled } = EnvSet.values;
const { event: interactionEvent } = getInteractionStorage(ctx.interactionDetails.result);

const {
Expand Down Expand Up @@ -71,7 +69,7 @@ export default function koaInteractionHooks<
});

// Assign user and event data to the data hook context
const assignDataHookContext: AssignDataHookContext = ({ event, user, data: extraData }) => {
ctx.assignDataHookContext = ({ event, user, data: extraData }) => {
dataHookContext.appendContext({
event,
data: {
Expand All @@ -82,18 +80,14 @@ export default function koaInteractionHooks<
});
};

// TODO: remove dev features check
ctx.assignDataHookContext = isDevFeaturesEnabled ? assignDataHookContext : noop;

await next();

if (interactionHookContext.interactionHookResult) {
// Hooks should not crash the app
void trySafe(triggerInteractionHooks(getConsoleLogFromContext(ctx), interactionHookContext));
}

// TODO: remove dev features check
if (isDevFeaturesEnabled && dataHookContext.contextArray.length > 0) {
if (dataHookContext.contextArray.length > 0) {
// Hooks should not crash the app
void trySafe(triggerDataHooks(getConsoleLogFromContext(ctx), dataHookContext));
}
Expand Down
9 changes: 1 addition & 8 deletions packages/core/src/routes/organization/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
import { generateStandardId } from '@logto/shared';
import { z } from 'zod';

import { EnvSet } from '#src/env-set/index.js';
import { buildManagementApiContext } from '#src/libraries/hook/utils.js';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
Expand Down Expand Up @@ -112,17 +111,11 @@ export default function organizationRoleRoutes<T extends ManagementApiRouter>(
);
}

const { isDevFeaturesEnabled } = EnvSet.values;

ctx.body = role;
ctx.status = 201;

// Trigger `OrganizationRole.Scope.Updated` event if organizationScopeIds or resourceScopeIds are provided.
// TODO: remove dev feature guard
if (
isDevFeaturesEnabled &&
(organizationScopeIds.length > 0 || resourceScopeIds.length > 0)
) {
if (organizationScopeIds.length > 0 || resourceScopeIds.length > 0) {
ctx.appendDataHookContext({
event: 'OrganizationRole.Scopes.Updated',
...buildManagementApiContext(ctx),
Expand Down
28 changes: 11 additions & 17 deletions packages/core/src/routes/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { generateStandardId } from '@logto/shared';
import { pickState, trySafe, tryThat } from '@silverhand/essentials';
import { number, object, string, z } from 'zod';

import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
import { buildManagementApiContext } from '#src/libraries/hook/utils.js';
import koaGuard from '#src/middleware/koa-guard.js';
Expand Down Expand Up @@ -176,23 +175,18 @@ export default function roleRoutes<T extends ManagementApiRouter>(
scopeIds.map((scopeId) => ({ id: generateStandardId(), roleId: role.id, scopeId }))
);

const { isDevFeaturesEnabled } = EnvSet.values;

// TODO: Remove dev feature guard
if (isDevFeaturesEnabled) {
// Trigger the `Role.Scopes.Updated` event if scopeIds are provided. Should not break the request
await trySafe(async () => {
// Align the response type with POST /roles/:id/scopes
const newRolesScopes = await findScopesByIds(scopeIds);

ctx.appendDataHookContext({
event: 'Role.Scopes.Updated',
...buildManagementApiContext(ctx),
roleId: role.id,
data: newRolesScopes,
});
// Trigger the `Role.Scopes.Updated` event if scopeIds are provided. Should not break the request
await trySafe(async () => {
// Align the response type with POST /roles/:id/scopes
const newRolesScopes = await findScopesByIds(scopeIds);

ctx.appendDataHookContext({
event: 'Role.Scopes.Updated',
...buildManagementApiContext(ctx),
roleId: role.id,
data: newRolesScopes,
});
}
});
}

ctx.body = role;
Expand Down

0 comments on commit b5104d8

Please sign in to comment.