mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-26 16:07:13 +00:00
[PUT-487] fix: set max-min on app-user-count queries 🐛 (#2384)
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test-backend (24.x) (push) Has been cancelled
test / API tests (node env, api-test) (24.x) (push) Has been cancelled
test / puterjs (node env, vitest) (24.x) (push) Has been cancelled
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test-backend (24.x) (push) Has been cancelled
test / API tests (node env, api-test) (24.x) (push) Has been cancelled
test / puterjs (node env, vitest) (24.x) (push) Has been cancelled
This commit is contained in:
Vendored
+10
-7
@@ -1,21 +1,23 @@
|
||||
import APIError from '@heyputer/backend/src/api/APIError.js';
|
||||
import type APIError from '@heyputer/backend/src/api/APIError.js';
|
||||
import type { WebServerService } from '@heyputer/backend/src/modules/web/WebServerService.js';
|
||||
import query from '@heyputer/backend/src/om/query/query';
|
||||
import type query from '@heyputer/backend/src/om/query/query';
|
||||
import type { Actor } from '@heyputer/backend/src/services/auth/Actor.js';
|
||||
import type { BaseDatabaseAccessService } from '@heyputer/backend/src/services/database/BaseDatabaseAccessService.d.ts';
|
||||
import type { EmailService } from '@heyputer/backend/src/services/EmailService.js';
|
||||
import type { EntityStoreService } from '@heyputer/backend/src/services/EntityStoreService.js';
|
||||
import type { MeteringService } from '@heyputer/backend/src/services/MeteringService/MeteringService.ts';
|
||||
import type { MeteringServiceWrapper } from '@heyputer/backend/src/services/MeteringService/MeteringServiceWrapper.mjs';
|
||||
import { DynamoKVStore } from '@heyputer/backend/src/services/repositories/DynamoKVStore/DynamoKVStore.ts';
|
||||
import type { DynamoKVStore } from '@heyputer/backend/src/services/repositories/DynamoKVStore/DynamoKVStore.ts';
|
||||
import type { SUService } from '@heyputer/backend/src/services/SUService.js';
|
||||
import type { IUser } from '@heyputer/backend/src/services/User.js';
|
||||
import type { UserService } from '@heyputer/backend/src/services/UserService.d.ts';
|
||||
import { Context } from '@heyputer/backend/src/util/context.js';
|
||||
import kvjs from '@heyputer/kv.js';
|
||||
import type { Context } from '@heyputer/backend/src/util/context.js';
|
||||
import type kvjs from '@heyputer/kv.js';
|
||||
import type { RequestHandler } from 'express';
|
||||
import type FSNodeContext from '../src/backend/src/filesystem/FSNodeContext.js';
|
||||
import type helpers from '../src/backend/src/helpers.js';
|
||||
import type * as ExtensionControllerExports from './ExtensionController/src/ExtensionController.ts';
|
||||
import { type EmailService } from '@heyputer/backend/src/services/EmailService.js';
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
@@ -98,7 +100,8 @@ interface ServiceNameMap {
|
||||
database: BaseDatabaseAccessService;
|
||||
user: UserService;
|
||||
'web-server': WebServerService;
|
||||
'email': EmailService
|
||||
'email': EmailService;
|
||||
'es:app': EntityStoreService;
|
||||
}
|
||||
|
||||
export interface ExtensionEventTypeMap {
|
||||
|
||||
@@ -3,6 +3,46 @@ const { db } = extension.import('data');
|
||||
const { APIError } = extension.import('core');
|
||||
const app_es = extension.import('service:es:app') as any;
|
||||
|
||||
const DEFAULT_LIMIT = 100;
|
||||
const MAX_LIMIT = 1000;
|
||||
const MAX_OFFSET = 100_000;
|
||||
|
||||
const parseIntegerParam = (
|
||||
value: unknown,
|
||||
{
|
||||
key,
|
||||
min,
|
||||
max,
|
||||
fallback,
|
||||
}: { key: string, min: number, max: number, fallback: number },
|
||||
) => {
|
||||
if ( value === undefined || value === null ) return fallback;
|
||||
|
||||
const parsed = typeof value === 'number'
|
||||
? value
|
||||
: (typeof value === 'string' && value.trim() !== ''
|
||||
? Number(value)
|
||||
: Number.NaN);
|
||||
|
||||
if ( !Number.isFinite(parsed) || !Number.isInteger(parsed) ) {
|
||||
throw APIError.create('field_invalid', undefined, {
|
||||
key,
|
||||
expected: `an integer between ${min} and ${max}`,
|
||||
got: value,
|
||||
});
|
||||
}
|
||||
|
||||
if ( parsed < min || parsed > max ) {
|
||||
throw APIError.create('field_invalid', undefined, {
|
||||
key,
|
||||
expected: `an integer between ${min} and ${max}`,
|
||||
got: parsed,
|
||||
});
|
||||
}
|
||||
|
||||
return parsed;
|
||||
};
|
||||
|
||||
extension.on('create.interfaces', (event) => {
|
||||
event.createInterface('app-telemetry', {
|
||||
description: 'Provides methods for getting app telemetry',
|
||||
@@ -39,7 +79,20 @@ extension.on('create.interfaces', (event) => {
|
||||
|
||||
extension.on('create.drivers', event => {
|
||||
event.createDriver('app-telemetry', 'app-telemetry', {
|
||||
async get_users ({ app_uuid, limit = 100, offset = 0 }: { app_uuid: string, limit: number, offset: number }) {
|
||||
async get_users ({ app_uuid, limit, offset }: { app_uuid: string, limit?: number, offset?: number }) {
|
||||
const safeLimit = parseIntegerParam(limit, {
|
||||
key: 'limit',
|
||||
min: 1,
|
||||
max: MAX_LIMIT,
|
||||
fallback: DEFAULT_LIMIT,
|
||||
});
|
||||
const safeOffset = parseIntegerParam(offset, {
|
||||
key: 'offset',
|
||||
min: 0,
|
||||
max: MAX_OFFSET,
|
||||
fallback: 0,
|
||||
});
|
||||
|
||||
// first lets make sure executor owns this app
|
||||
const [result] = (await app_es.select({ predicate: new Eq({ key: 'uid', value: app_uuid }) }));
|
||||
if ( ! result ) {
|
||||
@@ -50,7 +103,7 @@ extension.on('create.drivers', event => {
|
||||
const users: Array<{ username: string, uuid: string }> = await db.read(`SELECT user.username, user.uuid FROM user_to_app_permissions
|
||||
INNER JOIN user ON user_to_app_permissions.user_id = user.id
|
||||
WHERE permission = 'flag:app-is-authenticated' AND app_id=? ORDER BY (dt IS NOT NULL), dt, user_id LIMIT ? OFFSET ?`,
|
||||
[result.private_meta.mysql_id, limit, offset]);
|
||||
[result.private_meta.mysql_id, safeLimit, safeOffset]);
|
||||
return users.map(e => {
|
||||
return { user: e.username, user_uuid: e.uuid };
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user