0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(core,connector): update koaQuotaGuard to fit new pricing model (#5123)

This commit is contained in:
Darcy Ye 2023-12-19 18:51:53 +08:00 committed by GitHub
parent e28822997f
commit 22e9580d68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 44 additions and 28 deletions

View file

@ -49,6 +49,6 @@
"access": "public"
},
"devDependencies": {
"@logto/cloud": "0.2.5-5a698db"
"@logto/cloud": "0.2.5-5deb133"
}
}

View file

@ -93,7 +93,7 @@
"zod": "^3.22.4"
},
"devDependencies": {
"@logto/cloud": "0.2.5-5a698db",
"@logto/cloud": "0.2.5-5deb133",
"@silverhand/eslint-config": "4.0.1",
"@silverhand/ts-config": "4.0.0",
"@types/debug": "^4.1.7",

View file

@ -1,4 +1,5 @@
import { ConnectorType, DemoConnector } from '@logto/connector-kit';
import { RoleType, ReservedPlanId } from '@logto/schemas';
import { EnvSet } from '#src/env-set/index.js';
import RequestError from '#src/errors/RequestError/index.js';
@ -44,7 +45,9 @@ export const createQuotaLibrary = (
// Ignore the default management API resource
return { count: count - 1 };
},
rolesLimit: async () => countRoles(),
rolesLimit: async () => countRoles(undefined, { type: RoleType.User }),
machineToMachineRolesLimit: async () =>
countRoles(undefined, { type: RoleType.MachineToMachine }),
scopesPerResourceLimit: async (queryKey) => {
assertThat(queryKey, new TypeError('queryKey for scopesPerResourceLimit is required'));
return countScopesByResourceId(queryKey);
@ -61,13 +64,6 @@ export const createQuotaLibrary = (
).length;
return { count };
},
standardConnectorsLimit: async () => {
const connectors = await getLogtoConnectors();
const count = connectors.filter(
({ metadata: { isStandard, id } }) => isStandard && id !== DemoConnector.Social
).length;
return { count };
},
customDomainEnabled: notNumber,
mfaEnabled: notNumber,
organizationsEnabled: notNumber,
@ -96,8 +92,9 @@ export const createQuotaLibrary = (
return;
}
const plan = await getTenantSubscriptionPlan(cloudConnection);
const limit = plan.quota[key];
const { id: planId, quota } = await getTenantSubscriptionPlan(cloudConnection);
// Only apply hard quota limit for free plan, o/w it's soft limit (use `null` to bypass quota check for soft limit cases).
const limit = planId === ReservedPlanId.Free ? quota[key] : null;
if (limit === null) {
return;

View file

@ -129,11 +129,11 @@ export default function applicationRoutes<T extends AuthedRouter>(
async (ctx, next) => {
const { oidcClientMetadata, ...rest } = ctx.guard.body;
await quota.guardKey(
rest.type === ApplicationType.MachineToMachine
? 'machineToMachineLimit'
: 'applicationsLimit'
);
// When creating a m2m app, should check both m2m limit and application limit.
if (rest.type === ApplicationType.MachineToMachine) {
await quota.guardKey('machineToMachineLimit');
}
await quota.guardKey('applicationsLimit');
// Third party applications must be traditional type
if (rest.isThirdParty) {

View file

@ -25,9 +25,6 @@ const guardConnectorsQuota = async (
factory: ConnectorFactory<typeof router>,
quota: QuotaLibrary
) => {
if (factory.metadata.isStandard) {
await quota.guardKey('standardConnectorsLimit');
}
if (factory.type === ConnectorType.Social) {
await quota.guardKey('socialConnectorsLimit');
}

View file

@ -1,5 +1,5 @@
import type { RoleResponse } from '@logto/schemas';
import { Roles, featuredApplicationGuard, featuredUserGuard } from '@logto/schemas';
import { RoleType, Roles, featuredApplicationGuard, featuredUserGuard } from '@logto/schemas';
import { generateStandardId } from '@logto/shared';
import { pickState, tryThat } from '@silverhand/essentials';
import { object, string, z, number } from 'zod';
@ -7,7 +7,6 @@ import { object, string, z, number } from 'zod';
import RequestError from '#src/errors/RequestError/index.js';
import koaGuard from '#src/middleware/koa-guard.js';
import koaPagination from '#src/middleware/koa-pagination.js';
import koaQuotaGuard from '#src/middleware/koa-quota-guard.js';
import koaRoleRlsErrorHandler from '#src/middleware/koa-role-rls-error-handler.js';
import assertThat from '#src/utils/assert-that.js';
import { parseSearchParamsForSearch } from '#src/utils/search.js';
@ -133,7 +132,6 @@ export default function roleRoutes<T extends AuthedRouter>(...[router, tenant]:
router.post(
'/roles',
koaQuotaGuard({ key: 'rolesLimit', quota }),
koaGuard({
body: Roles.createGuard
.omit({ id: true })
@ -145,6 +143,13 @@ export default function roleRoutes<T extends AuthedRouter>(...[router, tenant]:
const { body } = ctx.guard;
const { scopeIds, ...roleBody } = body;
// `rolesLimit` is actually the limit of user roles, keep this name for backward compatibility.
// We have optional `type` when creating a new role, if `type` is not provided, use `User` as default.
// `machineToMachineRolesLimit` is the limit of machine to machine roles, and is independent to `rolesLimit`.
await quota.guardKey(
roleBody.type === RoleType.MachineToMachine ? 'machineToMachineRolesLimit' : 'rolesLimit'
);
assertThat(
!(await findRoleByRoleName(roleBody.name)),
new RequestError({

View file

@ -9,7 +9,9 @@ type RouteResponseType<T extends { search?: unknown; body?: unknown; response?:
export type SubscriptionPlan = RouteResponseType<GetRoutes['/api/subscription-plans']>[number];
// Since `standardConnectorsLimit` will be removed in the upcoming pricing V2, no need to guard it.
// `tokenLimit` is not guarded in backend.
export type FeatureQuota = Omit<
SubscriptionPlan['quota'],
'tenantLimit' | 'mauLimit' | 'auditLogsRetentionDays'
'tenantLimit' | 'mauLimit' | 'auditLogsRetentionDays' | 'standardConnectorsLimit' | 'tokenLimit'
>;

View file

@ -1322,8 +1322,8 @@ importers:
specifier: ^29.5.0
version: 29.5.0
'@logto/cloud':
specifier: 0.2.5-5a698db
version: 0.2.5-5a698db(zod@3.22.4)
specifier: 0.2.5-5deb133
version: 0.2.5-5deb133(zod@3.22.4)
'@rollup/plugin-commonjs':
specifier: ^25.0.0
version: 25.0.7(rollup@4.1.4)
@ -3324,8 +3324,8 @@ importers:
version: 3.22.4
devDependencies:
'@logto/cloud':
specifier: 0.2.5-5a698db
version: 0.2.5-5a698db(zod@3.22.4)
specifier: 0.2.5-5deb133
version: 0.2.5-5deb133(zod@3.22.4)
'@silverhand/eslint-config':
specifier: 4.0.1
version: 4.0.1(eslint@8.44.0)(prettier@3.0.0)(typescript@5.0.2)
@ -7522,6 +7522,16 @@ packages:
- zod
dev: true
/@logto/cloud@0.2.5-5deb133(zod@3.22.4):
resolution: {integrity: sha512-3itFraudBBc3bcS6qiBFHXQKoDYPFihxRu+L4HqDVEwTCBJfVVgGgD4F0fMpSpC+ouJY+VVdZGh7Yn9772B5fg==}
engines: {node: ^18.12.0}
dependencies:
'@silverhand/essentials': 2.8.6
'@withtyped/server': 0.12.9(zod@3.22.4)
transitivePeerDependencies:
- zod
dev: true
/@logto/js@3.0.1:
resolution: {integrity: sha512-vsU6mH5oiiW3k00pMyVA4V31K2Bd0rOT9qWch2l5e5o1yCQLJ3zUIOjGjChu3m2TRu1d920iiUpZU3Lzf6Pwdw==}
dependencies:
@ -9221,6 +9231,11 @@ packages:
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
dev: true
/@silverhand/essentials@2.8.6:
resolution: {integrity: sha512-qNyc6CvZRngP66hTA8sbpXGsiu9j6Hu62jCy7LV3tJiytg/hmxJB5oM9k5tpaUa9kHwYJ2X9CManX7SYYNrzHg==}
engines: {node: ^16.13.0 || ^18.12.0 || ^19.2.0, pnpm: ^8.0.0}
dev: true
/@silverhand/ts-config-react@4.0.0(typescript@5.0.2):
resolution: {integrity: sha512-IkZka1iuIBgw0AUbsknghw1vOIs4zOgUxR8jL38Kuk63hmSj687N4BWBb8KhVMhqaG4U/DYkbSEZuwsyHBe68g==}
engines: {node: ^18.12.0}