mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(console,core): remove DataHook devFeature guard (#5898)
* feat(console,core): remove DataHook devFeature guard remove DataHook devFeature guard * chore: add changeset add changeset * chore: update changesets update changesets
This commit is contained in:
parent
7f5625d1f8
commit
b5104d8c19
9 changed files with 103 additions and 66 deletions
79
.changeset/hip-fireants-talk.md
Normal file
79
.changeset/hip-fireants-talk.md
Normal file
|
@ -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 |
|
|
@ -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,
|
||||
|
@ -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) => ({
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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';
|
||||
|
@ -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),
|
||||
}));
|
||||
|
|
|
@ -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';
|
||||
|
@ -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,
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
hookEventGuard,
|
||||
hookEventsGuard,
|
||||
hookResponseGuard,
|
||||
interactionHookEventGuard,
|
||||
type Hook,
|
||||
type HookResponse,
|
||||
} from '@logto/schemas';
|
||||
|
@ -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';
|
||||
|
@ -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));
|
||||
|
||||
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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: {
|
||||
|
@ -82,9 +80,6 @@ export default function koaInteractionHooks<
|
|||
});
|
||||
};
|
||||
|
||||
// TODO: remove dev features check
|
||||
ctx.assignDataHookContext = isDevFeaturesEnabled ? assignDataHookContext : noop;
|
||||
|
||||
await next();
|
||||
|
||||
if (interactionHookContext.interactionHookResult) {
|
||||
|
@ -92,8 +87,7 @@ export default function koaInteractionHooks<
|
|||
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));
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
@ -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),
|
||||
|
|
|
@ -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';
|
||||
|
@ -176,23 +175,18 @@ export default function roleRoutes<T extends ManagementApiRouter>(
|
|||
scopeIds.map((scopeId) => ({ id: generateStandardId(), roleId: role.id, scopeId }))
|
||||
);
|
||||
|
||||
const { isDevFeaturesEnabled } = EnvSet.values;
|
||||
// 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);
|
||||
|
||||
// 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,
|
||||
});
|
||||
ctx.appendDataHookContext({
|
||||
event: 'Role.Scopes.Updated',
|
||||
...buildManagementApiContext(ctx),
|
||||
roleId: role.id,
|
||||
data: newRolesScopes,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ctx.body = role;
|
||||
|
|
Loading…
Reference in a new issue