You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
11 KiB
343 lines
11 KiB
import type { IUser, LicenseModule } from '@rocket.chat/core-typings';
|
|
import type { Logger } from '@rocket.chat/logger';
|
|
import type { Method, MethodOf, OperationParams, OperationResult, PathPattern, UrlParams } from '@rocket.chat/rest-typings';
|
|
import type { ValidateFunction } from 'ajv';
|
|
|
|
import type { ITwoFactorOptions } from '../../2fa/server/code';
|
|
import type { DeprecationLoggerNextPlannedVersion } from '../../lib/server/lib/deprecationWarningLogger';
|
|
|
|
export type SuccessStatusCodes = Exclude<Range<208>, Range<200>>;
|
|
|
|
export type RedirectStatusCodes = Exclude<Range<308>, Range<300>>;
|
|
|
|
export type AuthorizationStatusCodes = Exclude<Range<451>, Range<400>>;
|
|
|
|
export type ErrorStatusCodes = Exclude<Exclude<Range<511>, Range<500>>, 509>;
|
|
|
|
export type SuccessResult<T, TStatusCode extends SuccessStatusCodes = 200> = {
|
|
statusCode: TStatusCode;
|
|
body: T extends object ? { success: true } & T : T;
|
|
};
|
|
|
|
export type FailureResult<T, TStack = undefined, TErrorType = undefined, TErrorDetails = undefined> = {
|
|
statusCode: 400;
|
|
body: T extends object
|
|
? { success: false } & T
|
|
: {
|
|
success: false;
|
|
error?: T;
|
|
stack?: TStack;
|
|
errorType?: TErrorType;
|
|
details?: TErrorDetails;
|
|
message?: string;
|
|
} & (undefined extends TErrorType ? object : { errorType: TErrorType }) &
|
|
(undefined extends TErrorDetails ? object : { details: TErrorDetails extends string ? unknown : TErrorDetails });
|
|
};
|
|
|
|
export type RedirectResult<T, TStatusCode extends RedirectStatusCodes = 300> = {
|
|
statusCode: TStatusCode;
|
|
body: T;
|
|
};
|
|
|
|
export type UnauthorizedResult<T> = {
|
|
statusCode: 401;
|
|
body: {
|
|
success: false;
|
|
error: T | 'unauthorized';
|
|
};
|
|
};
|
|
|
|
export type ForbiddenResult<T> = {
|
|
statusCode: 403;
|
|
body: {
|
|
success: false;
|
|
// TODO: MAJOR remove 'unauthorized'
|
|
error: T | 'forbidden' | 'unauthorized';
|
|
};
|
|
};
|
|
|
|
export type InternalError<T, StatusCode extends ErrorStatusCodes = 500, D = 'Internal server error'> = {
|
|
statusCode: StatusCode;
|
|
body: {
|
|
error: T | D;
|
|
success: false;
|
|
};
|
|
};
|
|
|
|
export type UnavailableResult<T, StatusCode = 503> = {
|
|
statusCode: StatusCode;
|
|
body: {
|
|
error: T | 'Service Unavailable';
|
|
success: false;
|
|
};
|
|
};
|
|
|
|
export type NotFoundResult<T = string> = {
|
|
statusCode: 404;
|
|
body: {
|
|
success: false;
|
|
error: T;
|
|
};
|
|
};
|
|
|
|
export type TOperation = 'hasAll' | 'hasAny';
|
|
export type NonEnterpriseTwoFactorOptions = {
|
|
authRequired: true;
|
|
forceTwoFactorAuthenticationForNonEnterprise: true;
|
|
twoFactorRequired: true;
|
|
permissionsRequired?: string[] | { [key in Method]: string[] } | { [key in Method]: { operation: TOperation; permissions: string[] } };
|
|
twoFactorOptions: ITwoFactorOptions;
|
|
};
|
|
|
|
export type Options = SharedOptions<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'>;
|
|
|
|
export type SharedOptions<TMethod extends string> = (
|
|
| {
|
|
permissionsRequired?:
|
|
| string[]
|
|
| ({ [key in TMethod]?: string[] } & { '*'?: string[] })
|
|
| ({ [key in TMethod]?: { operation: TOperation; permissions: string[] } } & {
|
|
'*'?: { operation: TOperation; permissions: string[] };
|
|
});
|
|
authRequired?: boolean;
|
|
forceTwoFactorAuthenticationForNonEnterprise?: boolean;
|
|
rateLimiterOptions?:
|
|
| {
|
|
numRequestsAllowed?: number;
|
|
intervalTimeInMS?: number;
|
|
}
|
|
| boolean;
|
|
queryOperations?: string[];
|
|
queryFields?: string[];
|
|
}
|
|
| {
|
|
permissionsRequired?:
|
|
| string[]
|
|
| ({ [key in TMethod]?: string[] } & { '*'?: string[] })
|
|
| ({ [key in TMethod]?: { operation: TOperation; permissions: string[] } } & {
|
|
'*'?: { operation: TOperation; permissions: string[] };
|
|
});
|
|
authRequired: true;
|
|
twoFactorRequired: true;
|
|
twoFactorOptions?: ITwoFactorOptions;
|
|
rateLimiterOptions?:
|
|
| {
|
|
numRequestsAllowed?: number;
|
|
intervalTimeInMS?: number;
|
|
}
|
|
| boolean;
|
|
|
|
queryOperations?: string[];
|
|
queryFields?: string[];
|
|
}
|
|
) & {
|
|
/**
|
|
* @deprecated The `validateParams` option is deprecated. Use `query` and/OR `body` instead.
|
|
*/
|
|
validateParams?: ValidateFunction | { [key in TMethod]?: ValidateFunction };
|
|
authOrAnonRequired?: true;
|
|
deprecation?: {
|
|
version: DeprecationLoggerNextPlannedVersion;
|
|
alternatives?: PathPattern[];
|
|
};
|
|
};
|
|
|
|
export type GenericRouteExecutionContext = ActionThis<any, any, any>;
|
|
|
|
export type RouteExecutionContext<TMethod extends Method, TPathPattern extends PathPattern, TOptions> = ActionThis<
|
|
TMethod,
|
|
TPathPattern,
|
|
TOptions
|
|
>;
|
|
|
|
export type ActionThis<TMethod extends Method, TPathPattern extends PathPattern, TOptions> = {
|
|
readonly logger: Logger;
|
|
route: string;
|
|
readonly requestIp: string;
|
|
urlParams: UrlParams<TPathPattern>;
|
|
readonly response: Response;
|
|
// TODO make it unsafe
|
|
readonly queryParams: TMethod extends 'GET'
|
|
? TOptions extends { validateParams: ValidateFunction<infer T> }
|
|
? T
|
|
: TOptions extends { validateParams: { GET: ValidateFunction<infer T> } }
|
|
? T
|
|
: Partial<OperationParams<TMethod, TPathPattern>> & { offset?: number; count?: number }
|
|
: Record<string, string>;
|
|
// TODO make it unsafe
|
|
readonly bodyParams: TMethod extends 'GET'
|
|
? Record<string, unknown>
|
|
: TOptions extends { validateParams: ValidateFunction<infer T> }
|
|
? T
|
|
: TOptions extends { validateParams: infer V }
|
|
? V extends { [key in TMethod]: ValidateFunction<infer T> }
|
|
? T
|
|
: Partial<OperationParams<TMethod, TPathPattern>>
|
|
: // TODO remove the extra (optionals) params when all the endpoints that use these are typed correctly
|
|
Partial<OperationParams<TMethod, TPathPattern>>;
|
|
readonly request: Request;
|
|
|
|
readonly queryOperations: TOptions extends { queryOperations: infer T } ? T : never;
|
|
readonly queryFields: TOptions extends { queryFields: infer T } ? T : never;
|
|
|
|
parseJsonQuery(): Promise<{
|
|
sort: Record<string, 1 | -1>;
|
|
/**
|
|
* @deprecated To access "fields" parameter, use ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS environment variable.
|
|
*/
|
|
fields: Record<string, 0 | 1>;
|
|
/**
|
|
* @deprecated To access "query" parameter, use ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS environment variable.
|
|
*/
|
|
query: Record<string, unknown>;
|
|
}>;
|
|
|
|
readonly connection: {
|
|
token: string;
|
|
id: string;
|
|
close: () => void;
|
|
clientAddress: string;
|
|
httpHeaders: Record<string, any>;
|
|
};
|
|
} & (TOptions extends { authRequired: true }
|
|
? {
|
|
user: IUser;
|
|
userId: string;
|
|
readonly token: string;
|
|
}
|
|
: TOptions extends { authOrAnonRequired: true }
|
|
? {
|
|
user?: IUser;
|
|
userId?: string;
|
|
readonly token?: string;
|
|
}
|
|
: {
|
|
user?: IUser | null;
|
|
userId?: string | undefined;
|
|
readonly token?: string;
|
|
});
|
|
|
|
export type ResultFor<TMethod extends Method, TPathPattern extends PathPattern> = (
|
|
| SuccessResult<OperationResult<TMethod, TPathPattern>>
|
|
| FailureResult<unknown, unknown, unknown, unknown>
|
|
| UnauthorizedResult<unknown>
|
|
| NotFoundResult
|
|
| {
|
|
statusCode: number;
|
|
body: unknown;
|
|
}
|
|
) & {
|
|
headers?: Record<string, string>;
|
|
};
|
|
|
|
export type Action<TMethod extends Method, TPathPattern extends PathPattern, TOptions> =
|
|
| ((this: ActionThis<TMethod, TPathPattern, TOptions>) => Promise<ResultFor<TMethod, TPathPattern>>)
|
|
| ((this: ActionThis<TMethod, TPathPattern, TOptions>) => ResultFor<TMethod, TPathPattern>);
|
|
|
|
export type InnerAction<TMethod extends Method, TPathPattern extends PathPattern, TOptions> =
|
|
Action<TMethod, TPathPattern, TOptions> extends (this: infer This) => infer Result
|
|
? This extends ActionThis<TMethod, TPathPattern, TOptions>
|
|
? (this: Mutable<This>) => Result
|
|
: never
|
|
: never;
|
|
|
|
type Mutable<Immutable> = {
|
|
-readonly [key in keyof Immutable]: Immutable[key];
|
|
};
|
|
|
|
type Operation<TMethod extends Method, TPathPattern extends PathPattern, TEndpointOptions> =
|
|
| Action<TMethod, TPathPattern, TEndpointOptions>
|
|
| ({
|
|
action: Action<TMethod, TPathPattern, TEndpointOptions>;
|
|
} & { twoFactorRequired: boolean });
|
|
|
|
type ActionOperation<TMethod extends Method, TPathPattern extends PathPattern, TEndpointOptions extends Options = object> = {
|
|
action: Action<TMethod, TPathPattern, TEndpointOptions>;
|
|
} & TEndpointOptions;
|
|
|
|
export type Operations<TPathPattern extends PathPattern, TOptions extends Options = object> = {
|
|
[M in MethodOf<TPathPattern> as Lowercase<M>]: Operation<Uppercase<M>, TPathPattern, TOptions>;
|
|
};
|
|
|
|
export type ActionOperations<TPathPattern extends PathPattern, TOptions extends Options = object> = {
|
|
[M in MethodOf<TPathPattern> as Lowercase<M>]: ActionOperation<Uppercase<M>, TPathPattern, TOptions>;
|
|
};
|
|
|
|
type Range<N extends number, Result extends number[] = []> = Result['length'] extends N
|
|
? Result[number]
|
|
: Range<N, [...Result, Result['length']]>;
|
|
|
|
type HTTPStatusCodes = SuccessStatusCodes | RedirectStatusCodes | AuthorizationStatusCodes | ErrorStatusCodes;
|
|
|
|
export type TypedOptions = {
|
|
response: {
|
|
[K in HTTPStatusCodes]?: ValidateFunction;
|
|
};
|
|
query?: ValidateFunction;
|
|
body?: ValidateFunction;
|
|
tags?: string[];
|
|
typed?: boolean;
|
|
license?: LicenseModule[];
|
|
} & SharedOptions<'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'>;
|
|
|
|
export type TypedThis<TOptions extends TypedOptions, TPath extends string = ''> = {
|
|
userId: TOptions['authRequired'] extends true ? string : string | undefined;
|
|
user: TOptions['authRequired'] extends true ? IUser : IUser | null;
|
|
token: TOptions['authRequired'] extends true ? string : string | undefined;
|
|
queryParams: TOptions['query'] extends ValidateFunction<infer Query> ? Query : never;
|
|
urlParams: UrlParams<TPath> extends Record<any, any> ? UrlParams<TPath> : never;
|
|
parseJsonQuery(): Promise<{
|
|
sort: Record<string, 1 | -1>;
|
|
/**
|
|
* @deprecated To access "fields" parameter, use ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS environment variable.
|
|
*/
|
|
fields: Record<string, 0 | 1>;
|
|
/**
|
|
* @deprecated To access "query" parameter, use ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS environment variable.
|
|
*/
|
|
query: Record<string, unknown>;
|
|
}>;
|
|
bodyParams: TOptions['body'] extends ValidateFunction<infer Body> ? Body : never;
|
|
|
|
requestIp?: string;
|
|
route: string;
|
|
response: Response;
|
|
};
|
|
|
|
type PromiseOrValue<T> = T | Promise<T>;
|
|
|
|
type InferResult<TResult> = TResult extends ValidateFunction<infer T> ? T : TResult;
|
|
|
|
type InferNon200Result<T> =
|
|
InferResult<T> extends {
|
|
success: false;
|
|
error?: infer TError;
|
|
}
|
|
? TError
|
|
: never;
|
|
|
|
type Results<TResponse extends TypedOptions['response']> = {
|
|
[K in keyof TResponse]: K extends SuccessStatusCodes
|
|
? SuccessResult<InferResult<TResponse[200]>, K>
|
|
: K extends RedirectStatusCodes
|
|
? RedirectResult<InferNon200Result<TResponse[300]>, K>
|
|
: K extends 400
|
|
? FailureResult<InferResult<TResponse[400]>>
|
|
: K extends 401
|
|
? UnauthorizedResult<InferNon200Result<TResponse[401]>>
|
|
: K extends 403
|
|
? ForbiddenResult<InferNon200Result<TResponse[403]>>
|
|
: K extends 404
|
|
? NotFoundResult<InferNon200Result<TResponse[404]>>
|
|
: K extends ErrorStatusCodes
|
|
? InternalError<InferResult<TResponse[500]>, K>
|
|
: never;
|
|
}[keyof TResponse] & {
|
|
headers?: Record<string, string>;
|
|
};
|
|
|
|
export type TypedAction<
|
|
TOptions extends TypedOptions,
|
|
TPath extends string = '',
|
|
TThis extends TypedThis<TOptions, TPath> = TypedThis<TOptions, TPath>,
|
|
> = (this: TThis, request: Request) => PromiseOrValue<Results<TOptions['response']>>;
|
|
|