diff --git a/lib/Auth/AuthHandler.ts b/lib/Auth/AuthHandler.ts index 16537821..d8171512 100644 --- a/lib/Auth/AuthHandler.ts +++ b/lib/Auth/AuthHandler.ts @@ -1,17 +1,20 @@ import { AnyObject, + AuthFailure, AuthGuardLocation, AuthGuardLocationResponse, AuthSocketSchema, CHANNELS, } from "prostgles-types"; -import { LocalParams, PRGLIOSocket } from "../DboBuilder/DboBuilder"; +import { PRGLIOSocket } from "../DboBuilder/DboBuilder"; import { DBOFullyTyped } from "../DBSchemaBuilder"; import { removeExpressRoute } from "../FileManager/FileManager"; import { DB, DBHandlerServer, Prostgles } from "../Prostgles"; import { Auth, + AuthClientRequest, AuthResult, + AuthResultOrError, BasicSession, ExpressReq, ExpressRes, @@ -23,6 +26,7 @@ import { getProviders } from "./setAuthProviders"; import { setupAuthRoutes } from "./setupAuthRoutes"; import { getClientRequestIPsInfo } from "./utils/getClientRequestIPsInfo"; import { getReturnUrl } from "./utils/getReturnUrl"; +import { getUserFromRequest } from "./utils/getUserFromRequest"; export { getClientRequestIPsInfo }; export const HTTP_FAIL_CODES = { @@ -141,23 +145,35 @@ export class AuthHandler { } }; - getUser = async (clientReq: { httpReq: ExpressReq }): Promise => { - const sid = clientReq.httpReq.cookies?.[this.sidKeyName]; + getUserAndHandleError = async (localParams: AuthClientRequest): Promise => { + const sid = this.getSID(localParams); if (!sid) return undefined; - + const handlerError = (code: AuthFailure["code"]) => { + if (localParams.httpReq) { + localParams.res + .status(HTTP_FAIL_CODES.BAD_REQUEST) + .json({ success: false, code, error: code }); + } + throw code; + }; try { - return this.throttledFunc(async () => { - return this.opts!.getUser( + const userOrErrorCode = await this.throttledFunc(async () => { + return this.opts.getUser( this.validateSid(sid), this.dbo as any, this.db, - getClientRequestIPsInfo(clientReq) + getClientRequestIPsInfo(localParams) ); }, 50); - } catch (err) { - console.error(err); + + if (typeof userOrErrorCode === "string") { + return handlerError(userOrErrorCode); + } + + return userOrErrorCode; + } catch (_err) { + return handlerError("server-error"); } - return undefined; }; init = setupAuthRoutes.bind(this); @@ -265,7 +281,7 @@ export class AuthHandler { const start = Date.now(); const errCodeOrSession = await this.loginThrottledAndValidate( loginParams, - getClientRequestIPsInfo({ httpReq: req }) + getClientRequestIPsInfo({ httpReq: req, res }) ); const loginResponse = typeof errCodeOrSession === "string" ? @@ -292,21 +308,22 @@ export class AuthHandler { * query params * Based on sid names in auth */ - getSID(localParams: LocalParams | undefined): string | undefined { - if (!localParams) return undefined; + getSID(maybeClientReq: AuthClientRequest | undefined): string | undefined { + if (!maybeClientReq) return undefined; const { sidKeyName } = this; - if (localParams.socket) { - const { handshake } = localParams.socket; + if (maybeClientReq.socket) { + const { handshake } = maybeClientReq.socket; const querySid = handshake.auth?.[sidKeyName] || handshake.query?.[sidKeyName]; let rawSid = querySid; if (!rawSid) { - const cookie_str = localParams.socket.handshake.headers?.cookie; + const cookie_str = maybeClientReq.socket.handshake.headers?.cookie; const cookie = parseCookieStr(cookie_str); rawSid = cookie[sidKeyName]; } return this.validateSid(rawSid); - } else if (localParams.httpReq) { - const [tokenType, base64Token] = localParams.httpReq.headers.authorization?.split(" ") ?? []; + } else { + const [tokenType, base64Token] = + maybeClientReq.httpReq.headers.authorization?.split(" ") ?? []; let bearerSid: string | undefined; if (tokenType && base64Token) { if (tokenType.trim() !== "Bearer") { @@ -314,8 +331,8 @@ export class AuthHandler { } bearerSid = Buffer.from(base64Token, "base64").toString(); } - return this.validateSid(bearerSid ?? localParams.httpReq.cookies?.[sidKeyName]); - } else throw "socket OR httpReq missing from localParams"; + return this.validateSid(bearerSid ?? maybeClientReq.httpReq.cookies?.[sidKeyName]); + } function parseCookieStr(cookie_str: string | undefined): any { if (!cookie_str || typeof cookie_str !== "string") { @@ -336,91 +353,16 @@ export class AuthHandler { /** * Used for logging */ - getSIDNoError = (localParams: LocalParams | undefined): string | undefined => { - if (!localParams) return undefined; + getSIDNoError = (clientReq: AuthClientRequest | undefined): string | undefined => { + if (!clientReq) return undefined; try { - return this.getSID(localParams); + return this.getSID(clientReq); } catch { return undefined; } }; - /** - * For a given sid return the user data if available - */ - async getClientInfo( - localParams: Pick - ): Promise> { - /** - * Get cached session if available - */ - const getSession = this.opts.cacheSession?.getSession; - const isSocket = "socket" in localParams; - if (isSocket && getSession && localParams.socket?.__prglCache) { - const { session, user, clientUser } = localParams.socket.__prglCache; - const isValid = this.isNonExpiredSocketSession(localParams.socket, session); - if (isValid) { - return { - sid: session.sid, - user, - clientUser, - }; - } else - return { - sid: session.sid, - }; - } - - /** - * Get sid from request and fetch user data - */ - const authStart = Date.now(); - const clientInfo = await this.throttledFunc(async () => { - const { getUser } = this.opts; - - if (localParams.httpReq || localParams.socket) { - const sid = this.getSID(localParams); - const clientReq = - localParams.httpReq ? { httpReq: localParams.httpReq } : { socket: localParams.socket! }; - let user, clientUser; - if (sid) { - const clientInfo = await getUser( - sid, - this.dbo as any, - this.db, - getClientRequestIPsInfo(clientReq) - ); - if (typeof clientInfo === "string") throw clientInfo; - user = clientInfo?.user; - clientUser = clientInfo?.clientUser; - } - if (getSession && isSocket) { - const session = await getSession(sid, this.dbo as any, this.db); - if (session && session.expires && user && clientUser && localParams.socket) { - localParams.socket.__prglCache = { - session, - user, - clientUser, - }; - } - } - if (sid) { - return { sid, user, clientUser }; - } - } - - return {}; - }, 5); - - await this.prostgles.opts.onLog?.({ - type: "auth", - command: "getClientInfo", - duration: Date.now() - authStart, - sid: clientInfo.sid, - socketId: localParams.socket?.id, - }); - return clientInfo; - } + getUserFromRequest = getUserFromRequest.bind(this); isNonExpiredSocketSession = ( socket: PRGLIOSocket, @@ -442,8 +384,8 @@ export class AuthHandler { }; getClientAuth = async ( - clientReq: Pick - ): Promise<{ auth: AuthSocketSchema; userData: AuthResult }> => { + clientReq: AuthClientRequest + ): Promise<{ auth: AuthSocketSchema; userData: AuthResultOrError }> => { let pathGuard = false; if (this.opts.expressConfig?.publicRoutes && !this.opts.expressConfig.disableSocketAuthGuard) { pathGuard = true; @@ -473,7 +415,7 @@ export class AuthHandler { pathname && typeof pathname === "string" && this.isUserRoute(pathname) && - !(await this.getClientInfo({ socket }))?.user + !(await this.getUserFromRequest({ socket }))?.user ) { cb(null, { shouldReload: true }); } else { @@ -488,7 +430,7 @@ export class AuthHandler { } } - const userData = await this.getClientInfo(clientReq); + const userData = await this.getUserFromRequest(clientReq); const { email } = this.opts.expressConfig?.registrations ?? {}; const auth: AuthSocketSchema = { providers: getProviders.bind(this)(), diff --git a/lib/Auth/AuthTypes.ts b/lib/Auth/AuthTypes.ts index 21b29885..49fb26d9 100644 --- a/lib/Auth/AuthTypes.ts +++ b/lib/Auth/AuthTypes.ts @@ -46,9 +46,10 @@ export type BasicSession = { /** On expired */ onExpiration: "redirect" | "show_error"; }; -export type AuthClientRequest = - | { socket: PRGLIOSocket; httpReq?: undefined } - | { httpReq: ExpressReq; socket?: undefined }; + +type SocketClientRequest = { socket: PRGLIOSocket; httpReq?: undefined }; +type HttpClientRequest = { httpReq: ExpressReq; res: ExpressRes; socket?: undefined }; +export type AuthClientRequest = SocketClientRequest | HttpClientRequest; type ThirdPartyProviders = { facebook?: Pick & { @@ -239,20 +240,30 @@ export type SessionUser< clientUser: ClientUser; }; -export type AuthResult = - | AuthFailure["code"] +export type AuthResultWithSID = | (SU & { sid: string }) | { + sid?: string | undefined; user?: undefined; + sessionFields?: undefined; + clientUser?: undefined; + } + | undefined; + +export type AuthResult = + | SU + | { + user?: undefined; + sessionFields?: undefined; clientUser?: undefined; - sid?: string | undefined; } | undefined; +export type AuthResultOrError = AuthFailure["code"] | AuthResult; export type AuthRequestParams = { db: DB; dbo: DBOFullyTyped; - getUser: () => Promise>; + getUser: () => Promise>; }; export type Auth = { @@ -270,7 +281,7 @@ export type Auth = { dbo: DBOFullyTyped, db: DB, client: AuthClientRequest & LoginClientInfo - ) => Awaitable>; + ) => Awaitable>; /** * Will setup auth routes diff --git a/lib/Auth/authProviders/setOAuthProviders.ts b/lib/Auth/authProviders/setOAuthProviders.ts index 6153a7a7..9d256be8 100644 --- a/lib/Auth/authProviders/setOAuthProviders.ts +++ b/lib/Auth/authProviders/setOAuthProviders.ts @@ -56,7 +56,7 @@ export function setOAuthProviders( app.get(callbackPath, async (req, res) => { try { - const clientInfo = getClientRequestIPsInfo({ httpReq: req }); + const clientInfo = getClientRequestIPsInfo({ httpReq: req, res }); const db = this.db; const dbo = this.dbo as any; const args = { provider: providerName, req, res, clientInfo, db, dbo }; diff --git a/lib/Auth/endpoints/getConfirmEmailRequestHandler.ts b/lib/Auth/endpoints/getConfirmEmailRequestHandler.ts index d93b8255..e0e9d336 100644 --- a/lib/Auth/endpoints/getConfirmEmailRequestHandler.ts +++ b/lib/Auth/endpoints/getConfirmEmailRequestHandler.ts @@ -23,7 +23,7 @@ export const getConfirmEmailRequestHandler = ( if (!id || typeof id !== "string") { return res.send({ success: false, code: "something-went-wrong", message: "Invalid code" }); } - const { httpReq, ...clientInfo } = getClientRequestIPsInfo({ httpReq: req }); + const { httpReq, ...clientInfo } = getClientRequestIPsInfo({ httpReq: req, res }); await emailAuthConfig.onEmailConfirmation({ confirmationCode: id, clientInfo, diff --git a/lib/Auth/endpoints/getRegisterRequestHandler.ts b/lib/Auth/endpoints/getRegisterRequestHandler.ts index 2a341ad5..8b026b68 100644 --- a/lib/Auth/endpoints/getRegisterRequestHandler.ts +++ b/lib/Auth/endpoints/getRegisterRequestHandler.ts @@ -40,7 +40,7 @@ export const getRegisterRequestHandler = ({ } } try { - const { httpReq, ...clientInfo } = getClientRequestIPsInfo({ httpReq: req }); + const { httpReq, ...clientInfo } = getClientRequestIPsInfo({ httpReq: req, res }); const { smtp } = emailAuthConfig; const errCodeOrResult = emailAuthConfig.signupType === "withPassword" ? diff --git a/lib/Auth/setupAuthRoutes.ts b/lib/Auth/setupAuthRoutes.ts index 9c31e91a..0c2850c9 100644 --- a/lib/Auth/setupAuthRoutes.ts +++ b/lib/Auth/setupAuthRoutes.ts @@ -2,7 +2,7 @@ import { RequestHandler, Response } from "express"; import { AuthResponse } from "prostgles-types"; import { DBOFullyTyped } from "../DBSchemaBuilder"; import { AUTH_ROUTES_AND_PARAMS, AuthHandler, HTTP_FAIL_CODES } from "./AuthHandler"; -import { AuthClientRequest, ExpressReq, ExpressRes, LoginParams } from "./AuthTypes"; +import { Auth, AuthClientRequest, ExpressReq, ExpressRes, LoginParams } from "./AuthTypes"; import { setAuthProviders, upsertNamedExpressMiddleware } from "./setAuthProviders"; import { getClientRequestIPsInfo } from "./utils/getClientRequestIPsInfo"; import { getReturnUrl } from "./utils/getReturnUrl"; @@ -33,7 +33,7 @@ export async function setupAuthRoutes(this: AuthHandler) { req, res, next, - getUser: () => this.getUser({ httpReq: req }) as any, + getUser: () => this.getUserAndHandleError({ httpReq: req, res }), dbo: this.dbo as DBOFullyTyped, db: this.db, }); @@ -61,7 +61,7 @@ export async function setupAuthRoutes(this: AuthHandler) { id, this.dbo as any, this.db, - getClientRequestIPsInfo({ httpReq: req }) + getClientRequestIPsInfo({ httpReq: req, res }) ); }); if (!response.session) { @@ -108,8 +108,15 @@ export async function setupAuthRoutes(this: AuthHandler) { /* Redirect if not logged in and requesting non public content */ app.get(AUTH_ROUTES_AND_PARAMS.catchAll, async (req: ExpressReq, res: ExpressRes, next) => { - const clientReq: AuthClientRequest = { httpReq: req }; - const getUser = this.getUser; + const clientReq: AuthClientRequest = { httpReq: req, res }; + const getUser = async () => { + const userOrCode = await this.getUserAndHandleError(clientReq); + if (typeof userOrCode === "string") { + res.status(HTTP_FAIL_CODES.BAD_REQUEST).json({ success: false, code: userOrCode }); + throw userOrCode; + } + return userOrCode; + }; if (this.prostgles.restApi) { if ( Object.values(this.prostgles.restApi.routes).some((restRoute) => @@ -137,7 +144,7 @@ export async function setupAuthRoutes(this: AuthHandler) { */ if (this.isUserRoute(req.path)) { /* Check auth. Redirect to login if unauthorized */ - const u = await getUser(clientReq); + const u = await getUser(); if (!u) { res.redirect( `${AUTH_ROUTES_AND_PARAMS.login}?returnURL=${encodeURIComponent(req.originalUrl)}` @@ -146,21 +153,18 @@ export async function setupAuthRoutes(this: AuthHandler) { } /* If authorized and going to returnUrl then redirect. Otherwise serve file */ - } else if (returnURL && (await getUser(clientReq))) { + } else if (returnURL && (await getUser())) { res.redirect(returnURL); return; /** If Logged in and requesting login then redirect to main page */ - } else if ( - this.matchesRoute(AUTH_ROUTES_AND_PARAMS.login, req.path) && - (await getUser(clientReq)) - ) { + } else if (this.matchesRoute(AUTH_ROUTES_AND_PARAMS.login, req.path) && (await getUser())) { res.redirect("/"); return; } onGetRequestOK?.(req, res, { - getUser: () => getUser(clientReq), + getUser, dbo: this.dbo as DBOFullyTyped, db: this.db, }); @@ -170,7 +174,7 @@ export async function setupAuthRoutes(this: AuthHandler) { typeof error === "string" ? error : error instanceof Error ? error.message : ""; - res.status(HTTP_FAIL_CODES.UNAUTHORIZED).json({ + res.status(HTTP_FAIL_CODES.BAD_REQUEST).json({ error: "Something went wrong when processing your request" + (errorMessage ? ": " + errorMessage : ""), diff --git a/lib/Auth/utils/getUserFromRequest.ts b/lib/Auth/utils/getUserFromRequest.ts new file mode 100644 index 00000000..7cdde8a3 --- /dev/null +++ b/lib/Auth/utils/getUserFromRequest.ts @@ -0,0 +1,71 @@ +import { AuthHandler, getClientRequestIPsInfo } from "../AuthHandler"; +import { AuthClientRequest, AuthResultWithSID } from "../AuthTypes"; + +/** + * For a given sid return the user data if available + */ +export async function getUserFromRequest( + this: AuthHandler, + maybeClientReq: AuthClientRequest | undefined +): Promise { + if (!maybeClientReq) return undefined; + /** + * Get cached session if available + */ + const getSession = this.opts.cacheSession?.getSession; + if (maybeClientReq.socket && getSession && maybeClientReq.socket.__prglCache) { + const { session, user, clientUser } = maybeClientReq.socket.__prglCache; + const isValid = this.isNonExpiredSocketSession(maybeClientReq.socket, session); + if (isValid) { + return { + sid: session.sid, + user, + clientUser, + }; + } else + return { + sid: session.sid, + }; + } + + /** + * Get sid from request and fetch user data + */ + const authStart = Date.now(); + const result = await this.throttledFunc(async () => { + const { getUser } = this.opts; + + const sid = this.getSID(maybeClientReq); + const clientInfoOrErr = + !sid ? undefined : ( + await getUser(sid, this.dbo as any, this.db, getClientRequestIPsInfo(maybeClientReq)) + ); + if (typeof clientInfoOrErr === "string") throw clientInfoOrErr; + const clientInfo = clientInfoOrErr; + if (getSession && maybeClientReq.socket) { + const session = await getSession(sid, this.dbo as any, this.db); + if (session && session.expires && clientInfo?.user) { + maybeClientReq.socket.__prglCache = { + session, + user: clientInfo.user, + clientUser: clientInfo.clientUser, + }; + } + } + + if (clientInfo?.user && sid) { + return { sid, ...clientInfo }; + } + + return { sid }; + }, 5); + + await this.prostgles.opts.onLog?.({ + type: "auth", + command: "getClientInfo", + duration: Date.now() - authStart, + sid: result.sid, + socketId: maybeClientReq.socket?.id, + }); + return result; +} diff --git a/lib/DboBuilder/DboBuilder.ts b/lib/DboBuilder/DboBuilder.ts index 98d74741..508043ad 100644 --- a/lib/DboBuilder/DboBuilder.ts +++ b/lib/DboBuilder/DboBuilder.ts @@ -188,7 +188,7 @@ export class DboBuilder { query: string, params: any, options: SQLOptions | undefined, - localParams?: LocalParams + localParams: LocalParams | undefined ) => { return runSQL .bind(this)(query, params, options, localParams) @@ -325,8 +325,12 @@ export class DboBuilder { const handlerClass = tov.is_view ? ViewHandler : TableHandler; dbTX[tov.name] = new handlerClass(this.db, tov, this, { t, dbTX }, this.shortestJoinPaths); }); - dbTX.sql = (q, args, opts, localP) => - this.runSQL(q, args, opts, { tx: { dbTX, t }, ...(localP ?? {}) }); + dbTX.sql = (q, args, opts, localParams) => { + if (localParams?.tx) { + throw "Cannot run transaction within transaction"; + } + return this.runSQL(q, args, opts, { ...localParams, tx: { dbTX, t } }); + }; return cb(dbTX, t); }); diff --git a/lib/DboBuilder/DboBuilderTypes.ts b/lib/DboBuilder/DboBuilderTypes.ts index 168cc4b2..ae7f6130 100644 --- a/lib/DboBuilder/DboBuilderTypes.ts +++ b/lib/DboBuilder/DboBuilderTypes.ts @@ -15,7 +15,7 @@ import { TableInfo as TInfo, UserLike, } from "prostgles-types"; -import { BasicSession, ExpressReq } from "../Auth/AuthTypes"; +import { AuthClientRequest, BasicSession } from "../Auth/AuthTypes"; import { BasicCallback } from "../PubSubManager/PubSubManager"; import { PublishAllOrNothing } from "../PublishParser/PublishParser"; import { FieldSpec } from "./QueryBuilder/Functions"; @@ -105,21 +105,15 @@ export type DbTxTableHandlers = { [key: string]: Omit, "dbTx"> | Omit; }; +type SQLHandlerServer = SQLHandler; + export type DBHandlerServerExtra = { - sql: SQLHandler; + sql: SQLHandlerServer; } & (WithTransactions extends true ? { tx: TX } : Record); -// export type DBHandlerServer = -// TH & -// Partial & { -// sql?: SQLHandler -// } & { -// tx?: TX -// } - export type DBHandlerServer = TH & Partial & { - sql?: SQLHandler; + sql?: SQLHandlerServer; } & { tx?: TX; }; @@ -187,16 +181,15 @@ export type PRGLIOSocket = { }; export type LocalParams = { - httpReq?: ExpressReq; - socket?: PRGLIOSocket; - func?: () => any; + // httpReq?: ExpressReq; + // socket?: PRGLIOSocket; + clientReq?: AuthClientRequest | undefined; isRemoteRequest?: { user?: UserLike | undefined; }; + func?: () => any; testRule?: boolean; tableAlias?: string; - // subOne?: boolean; - tx?: { dbTX: TableHandlers; t: pgPromise.ITask<{}>; @@ -207,9 +200,18 @@ export type LocalParams = { returnQuery?: boolean | "noRLS" | "where-condition"; returnNewQuery?: boolean; - /** Used for count/size queries */ + + /** + * Used for count/size queries + * */ bypassLimit?: boolean; + /** + * Used to allow inserting linked data. + * For example, if we have users( id, name ) and user_emails( id, user_id, email ) + * and we want to insert a user and an email in a single transaction we can just: + * db.users.insert({ name: "John", emails: [{ email: "john@abc.com" }] }) + */ nestedInsert?: { depth: number; previousData: AnyObject; diff --git a/lib/DboBuilder/QueryBuilder/getNewQuery.ts b/lib/DboBuilder/QueryBuilder/getNewQuery.ts index bdde8fe0..c63f89da 100644 --- a/lib/DboBuilder/QueryBuilder/getNewQuery.ts +++ b/lib/DboBuilder/QueryBuilder/getNewQuery.ts @@ -173,12 +173,12 @@ export async function getNewQuery( } let isLocal = true; - if (localParams && (localParams.socket || localParams.httpReq)) { + if (localParams && localParams.clientReq) { isLocal = false; j_tableRules = await _this.dboBuilder.publishParser?.getValidatedRequestRuleWusr({ tableName: jTable, command: "find", - localParams, + clientReq: localParams.clientReq, }); } diff --git a/lib/DboBuilder/QueryStreamer.ts b/lib/DboBuilder/QueryStreamer.ts index c85a215d..d34a3f61 100644 --- a/lib/DboBuilder/QueryStreamer.ts +++ b/lib/DboBuilder/QueryStreamer.ts @@ -112,7 +112,7 @@ export class QueryStreamer { const errorWithoutQuery = getSerializedClientErrorFromPGError(rawError, { type: "sql", - localParams: { socket }, + localParams: { clientReq: { socket } }, }); // For some reason query is not present on the error object from sql stream mode const error = { ...errorWithoutQuery, query: query.query }; diff --git a/lib/DboBuilder/TableHandler/TableHandler.ts b/lib/DboBuilder/TableHandler/TableHandler.ts index 9f5967be..a33a0cd0 100644 --- a/lib/DboBuilder/TableHandler/TableHandler.ts +++ b/lib/DboBuilder/TableHandler/TableHandler.ts @@ -146,8 +146,8 @@ export class TableHandler extends ViewHandler { } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!localParams) throw "Sync not allowed within the server code"; - const { socket } = localParams; + if (!localParams.clientReq) throw "Sync not allowed within the server code"; + const { socket } = localParams.clientReq; if (!socket) throw "socket missing"; // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition @@ -212,7 +212,6 @@ export class TableHandler extends ViewHandler { condition, id_fields, synced_field, - // allow_delete, socket, table_rules, filter: { ...filter }, diff --git a/lib/DboBuilder/TableHandler/insert.ts b/lib/DboBuilder/TableHandler/insert.ts index 68f83959..bf6420bd 100644 --- a/lib/DboBuilder/TableHandler/insert.ts +++ b/lib/DboBuilder/TableHandler/insert.ts @@ -23,8 +23,8 @@ export async function insert( const ACTION = "insert"; const start = Date.now(); try { - const { removeDisallowedFields = false } = insertParams || {}; - const { returnQuery = false, nestedInsert } = localParams || {}; + const { removeDisallowedFields = false } = insertParams ?? {}; + const { returnQuery = false, nestedInsert } = localParams ?? {}; const finalDBtx = this.getFinalDBtx(localParams); const rule = tableRules?.[ACTION]; diff --git a/lib/DboBuilder/TableHandler/update.ts b/lib/DboBuilder/TableHandler/update.ts index d66bd903..a645b8d2 100644 --- a/lib/DboBuilder/TableHandler/update.ts +++ b/lib/DboBuilder/TableHandler/update.ts @@ -115,7 +115,7 @@ export async function update( throw `nestedInsert Tablehandler not found for ${nestedInsert.tableName}`; const refTableRules = !localParams ? undefined : ( - await getInsertTableRules(this, nestedInsert.tableName, localParams) + await getInsertTableRules(this, nestedInsert.tableName, localParams.clientReq) ); const nestedLocalParams: LocalParams = { ...localParams, diff --git a/lib/DboBuilder/ViewHandler/ViewHandler.ts b/lib/DboBuilder/ViewHandler/ViewHandler.ts index 2f6b98ea..6c69f3d3 100644 --- a/lib/DboBuilder/ViewHandler/ViewHandler.ts +++ b/lib/DboBuilder/ViewHandler/ViewHandler.ts @@ -109,12 +109,12 @@ export class ViewHandler { error?: any; }) => { if (localParams?.noLog) { - if (localParams.socket || localParams.httpReq) { + if (localParams.clientReq) { throw new Error("noLog option is not allowed from a remote client"); } return; } - const sid = this.dboBuilder.prostgles.authHandler?.getSIDNoError(localParams); + const sid = this.dboBuilder.prostgles.authHandler?.getSIDNoError(localParams?.clientReq); return this.dboBuilder.prostgles.opts.onLog?.({ type: "table", command, @@ -122,7 +122,7 @@ export class ViewHandler { error, txInfo: this.tx?.t.ctx, sid, - socketId: localParams?.socket?.id, + socketId: localParams?.clientReq?.socket?.id, tableName: this.name, data, localParams, @@ -148,39 +148,40 @@ export class ViewHandler { validateViewRules = validateViewRules.bind(this); - getShortestJoin( - table1: string, - table2: string, - startAlias: number, - isInner = false - ): { query: string; toOne: boolean } { - const getJoinCondition = ( - on: Record[], - leftTable: string, - rightTable: string - ) => { - return on - .map((cond) => - Object.keys(cond) - .map((lKey) => `${leftTable}.${lKey} = ${rightTable}.${cond[lKey]}`) - .join("\nAND ") - ) - .join(" OR "); - }; - - // let toOne = true; - const query = this.joins - .map(({ tables, on, type }, i) => { - if (type.split("-")[1] === "many") { - // toOne = false; - } - const tl = `tl${startAlias + i}`, - tr = `tr${startAlias + i}`; - return `FROM ${tables[0]} ${tl} ${isInner ? "INNER" : "LEFT"} JOIN ${tables[1]} ${tr} ON ${getJoinCondition(on, tl, tr)}`; - }) - .join("\n"); - return { query, toOne: false }; - } + // DEAD CODE?! + // getShortestJoin( + // table1: string, + // table2: string, + // startAlias: number, + // isInner = false + // ): { query: string; toOne: boolean } { + // const getJoinCondition = ( + // on: Record[], + // leftTable: string, + // rightTable: string + // ) => { + // return on + // .map((cond) => + // Object.keys(cond) + // .map((lKey) => `${leftTable}.${lKey} = ${rightTable}.${cond[lKey]}`) + // .join("\nAND ") + // ) + // .join(" OR "); + // }; + + // // let toOne = true; + // const query = this.joins + // .map(({ tables, on, type }, i) => { + // if (type.split("-")[1] === "many") { + // // toOne = false; + // } + // const tl = `tl${startAlias + i}`, + // tr = `tr${startAlias + i}`; + // return `FROM ${tables[0]} ${tl} ${isInner ? "INNER" : "LEFT"} JOIN ${tables[1]} ${tr} ON ${getJoinCondition(on, tl, tr)}`; + // }) + // .join("\n"); + // return { query, toOne: false }; + // } checkFilter(filter: any) { if (filter === null || (filter && !isObject(filter))) @@ -192,7 +193,7 @@ export class ViewHandler { getColumns = getColumns.bind(this); getValidatedRules(tableRules?: TableRule, localParams?: LocalParams): ValidatedTableRules { - if (localParams?.socket && !tableRules) { + if (localParams?.clientReq?.socket && !tableRules) { throw "INTERNAL ERROR: Unexpected case -> localParams && !tableRules"; } diff --git a/lib/DboBuilder/ViewHandler/find.ts b/lib/DboBuilder/ViewHandler/find.ts index 679f7b31..7b037c85 100644 --- a/lib/DboBuilder/ViewHandler/find.ts +++ b/lib/DboBuilder/ViewHandler/find.ts @@ -203,7 +203,7 @@ export const runQueryReturnType = async ({ }) ); } else if (sqlTypes.some((v) => v === returnType)) { - if (!(await canRunSQL(handler.dboBuilder.prostgles, localParams))) { + if (!(await canRunSQL(handler.dboBuilder.prostgles, localParams?.clientReq))) { throw `Not allowed: { returnType: ${JSON.stringify(returnType)} } requires execute sql privileges `; } if (returnType === "statement-no-rls") { diff --git a/lib/DboBuilder/ViewHandler/getExistsCondition.ts b/lib/DboBuilder/ViewHandler/getExistsCondition.ts index 73190ea3..5a70ee72 100644 --- a/lib/DboBuilder/ViewHandler/getExistsCondition.ts +++ b/lib/DboBuilder/ViewHandler/getExistsCondition.ts @@ -29,15 +29,15 @@ export async function getExistsCondition( tableAlias; /* Check if allowed to view data - forcedFilters will bypass this check through isForcedFilterBypass */ - if (localParams?.isRemoteRequest && !localParams.socket && !localParams.httpReq) { - throw "Unexpected: localParams isRemoteRequest and missing socket/httpReq: "; + if (localParams?.isRemoteRequest && !localParams.clientReq) { + throw "Unexpected: localParams isRemoteRequest and missing clientReq"; } const targetTable = eConfig.isJoined ? eConfig.parsedPath.at(-1)!.table : eConfig.targetTable; - if ((localParams?.socket || localParams?.httpReq) && this.dboBuilder.publishParser) { + if (localParams?.clientReq && this.dboBuilder.publishParser) { t2Rules = (await this.dboBuilder.publishParser.getValidatedRequestRuleWusr({ tableName: targetTable, command: "find", - localParams, + clientReq: localParams.clientReq, })) as TableRule | undefined; if (!t2Rules || !t2Rules.select) throw "Dissallowed"; diff --git a/lib/DboBuilder/ViewHandler/subscribe.ts b/lib/DboBuilder/ViewHandler/subscribe.ts index cef2bd91..1b636d3f 100644 --- a/lib/DboBuilder/ViewHandler/subscribe.ts +++ b/lib/DboBuilder/ViewHandler/subscribe.ts @@ -1,8 +1,4 @@ -import { - AnyObject, - SubscribeParams, - SubscriptionChannels, -} from "prostgles-types"; +import { AnyObject, SubscribeParams, SubscriptionChannels } from "prostgles-types"; import { TableRule } from "../../PublishParser/PublishParser"; import { Filter, @@ -23,38 +19,33 @@ export type LocalFuncs = } | OnData; -export const getOnDataFunc = ( - localFuncs: LocalFuncs | undefined, -): OnData | undefined => { +export const getOnDataFunc = (localFuncs: LocalFuncs | undefined): OnData | undefined => { return typeof localFuncs === "function" ? localFuncs : localFuncs?.onData; }; export const matchesLocalFuncs = ( localFuncs1: LocalFuncs | undefined, - localFuncs2: LocalFuncs | undefined, + localFuncs2: LocalFuncs | undefined ) => { - return ( - localFuncs1 && - localFuncs2 && - getOnDataFunc(localFuncs1) === getOnDataFunc(localFuncs2) - ); + return localFuncs1 && localFuncs2 && getOnDataFunc(localFuncs1) === getOnDataFunc(localFuncs2); }; export const parseLocalFuncs = ( - localFuncs1: LocalFuncs | undefined, + localFuncs1: LocalFuncs | undefined ): Extract | undefined => { - return !localFuncs1 - ? undefined - : typeof localFuncs1 === "function" - ? { - onData: localFuncs1, - } - : localFuncs1; + return ( + !localFuncs1 ? undefined + : typeof localFuncs1 === "function" ? + { + onData: localFuncs1, + } + : localFuncs1 + ); }; async function subscribe( this: ViewHandler, filter: Filter, params: SubscribeParams, - localFuncs: LocalFuncs, + localFuncs: LocalFuncs ): Promise<{ unsubscribe: () => any }>; async function subscribe( this: ViewHandler, @@ -62,7 +53,7 @@ async function subscribe( params: SubscribeParams, localFuncs: undefined, table_rules: TableRule | undefined, - localParams: LocalParams, + localParams: LocalParams ): Promise; async function subscribe( this: ViewHandler, @@ -70,7 +61,7 @@ async function subscribe( params: SubscribeParams, localFuncs?: LocalFuncs, table_rules?: TableRule, - localParams?: LocalParams, + localParams?: LocalParams ): Promise<{ unsubscribe: () => any } | SubscriptionChannels> { const start = Date.now(); try { @@ -81,10 +72,11 @@ async function subscribe( if (this.tx) { throw "subscribe not allowed within transactions"; } - if (!localParams && !localFuncs) { + const clientReq = localParams?.clientReq; + if (!clientReq && !localFuncs) { throw " missing data. provide -> localFunc | localParams { socket } "; } - if (localParams?.socket && localFuncs) { + if (clientReq?.socket && localFuncs) { console.error({ localParams, localFuncs }); throw " Cannot have localFunc AND socket "; } @@ -92,25 +84,14 @@ async function subscribe( const { throttle = 0, throttleOpts, ...selectParams } = params; /** Ensure request is valid */ - await this.find( - filter, - { ...selectParams, limit: 0 }, - undefined, - table_rules, - localParams, - ); - - // TODO: Implement comprehensive canSubscribe check - // if (!this.dboBuilder.prostgles.isSuperUser) { - // throw "Subscribe not possible. Must be superuser"; - // } + await this.find(filter, { ...selectParams, limit: 0 }, undefined, table_rules, localParams); const newQuery: NewQuery = (await this.find( filter, { ...selectParams, limit: 0 }, undefined, table_rules, - { ...localParams, returnNewQuery: true }, + { ...localParams, returnNewQuery: true } )) as any; const viewOptions = await getSubscribeRelatedTables.bind(this)({ filter, @@ -135,7 +116,7 @@ async function subscribe( const pubSubManager = await this.dboBuilder.getPubSubManager(); if (!localFuncs) { - const { socket } = localParams ?? {}; + const { socket } = clientReq ?? {}; const result = await pubSubManager.addSub({ ...commonSubOpts, socket, diff --git a/lib/DboBuilder/dboBuilderUtils.ts b/lib/DboBuilder/dboBuilderUtils.ts index 486714f0..818fe58f 100644 --- a/lib/DboBuilder/dboBuilderUtils.ts +++ b/lib/DboBuilder/dboBuilderUtils.ts @@ -89,7 +89,9 @@ export function getSerializedClientErrorFromPGError( const isServerSideRequest = !args.localParams; //TODO: add a rawSQL check for HTTP requests const showFullError = - isServerSideRequest || args.type === "sql" || args.localParams?.socket?.prostgles?.rawSQL; + isServerSideRequest || + args.type === "sql" || + args.localParams?.clientReq?.socket?.prostgles?.rawSQL; if (showFullError) { return err; } diff --git a/lib/DboBuilder/insertNestedRecords.ts b/lib/DboBuilder/insertNestedRecords.ts index 6e77d460..6821437e 100644 --- a/lib/DboBuilder/insertNestedRecords.ts +++ b/lib/DboBuilder/insertNestedRecords.ts @@ -3,12 +3,13 @@ import { LocalParams, TableHandlers } from "./DboBuilder"; import { TableRule } from "../PublishParser/PublishParser"; import { omitKeys } from "../PubSubManager/PubSubManager"; import { TableHandler } from "./TableHandler/TableHandler"; +import { AuthClientRequest } from "../Auth/AuthTypes"; type InsertNestedRecordsArgs = { data: AnyObject | AnyObject[]; param2?: InsertParams; - tableRules?: TableRule; - localParams?: LocalParams; + tableRules: TableRule | undefined; + localParams: LocalParams | undefined; }; /** @@ -16,7 +17,7 @@ type InsertNestedRecordsArgs = { */ export async function insertNestedRecords( this: TableHandler, - { data, param2, tableRules, localParams = {} }: InsertNestedRecordsArgs + { data, param2, tableRules, localParams }: InsertNestedRecordsArgs ): Promise<{ data?: AnyObject | AnyObject[]; insertResult?: AnyObject | AnyObject[]; @@ -60,7 +61,7 @@ export async function insertNestedRecords( * Make sure nested insert uses a transaction */ const dbTX = this.getFinalDBtx(localParams); - const t = localParams.tx?.t || this.tx?.t; + const t = localParams?.tx?.t || this.tx?.t; if (hasNestedInserts && (!dbTX || !t)) { return { insertResult: await this.dboBuilder.getTX((dbTX, _t) => @@ -107,7 +108,7 @@ export async function insertNestedRecords( const newLocalParams: LocalParams = { ...localParams, nestedInsert: { - depth: (localParams.nestedInsert?.depth ?? 0) + 1, + depth: (localParams?.nestedInsert?.depth ?? 0) + 1, previousData: rootData, previousTable: this.name, referencingColumn: colInsert.col, @@ -165,9 +166,14 @@ export async function insertNestedRecords( await Promise.all( extraKeys.map(async (targetTable) => { - const childDataItems = + const childDataItems: AnyObject[] = Array.isArray(row[targetTable]) ? row[targetTable] : [row[targetTable]]; + /** check */ + if (childDataItems.some((d) => !isObject(d))) { + throw "Expected array of objects"; + } + const childInsert = async (cdata: AnyObject | AnyObject[], tableName: string) => { return referencedInsert(this, dbTX, localParams, tableName, cdata); }; @@ -176,7 +182,7 @@ export async function insertNestedRecords( const { path } = joinPath; const [tbl1, tbl2, tbl3] = path; - targetTableRules = await getInsertTableRules(this, targetTable, localParams); + targetTableRules = await getInsertTableRules(this, targetTable, localParams?.clientReq); const cols2 = this.dboBuilder.dbo[tbl2!]!.columns || []; if (!this.dboBuilder.dbo[tbl2!]) throw "Invalid/disallowed table: " + tbl2; @@ -299,12 +305,12 @@ export async function insertNestedRecords( export const getInsertTableRules = async ( tableHandler: TableHandler, targetTable: string, - localParams: LocalParams + clientReq: AuthClientRequest | undefined ) => { const childRules = await tableHandler.dboBuilder.publishParser?.getValidatedRequestRuleWusr({ tableName: targetTable, command: "insert", - localParams, + clientReq, }); if (!childRules || !childRules.insert) throw "Dissallowed nested insert into table " + childRules; return childRules; @@ -332,22 +338,18 @@ const getJoinPath = async ( const referencedInsert = async ( tableHandler: TableHandler, dbTX: TableHandlers | undefined, - localParams: LocalParams, + localParams: LocalParams | undefined, targetTable: string, targetData: AnyObject | AnyObject[] ): Promise => { await getJoinPath(tableHandler, targetTable); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!targetData || !dbTX?.[targetTable] || !("insert" in dbTX[targetTable]!)) { + if (!dbTX?.[targetTable] || !("insert" in dbTX[targetTable]!)) { throw new Error("childInsertErr: Table handler missing for referenced table: " + targetTable); } - const childRules = await getInsertTableRules(tableHandler, targetTable, localParams); + const childRules = await getInsertTableRules(tableHandler, targetTable, localParams?.clientReq); - // if (thisInfo.has_media === "one" && thisInfo.media_table_name === targetTable && Array.isArray(targetData) && targetData.length > 1) { - // throw "Constraint check fail: Cannot insert more than one record into " + JSON.stringify(targetTable); - // } return Promise.all( (Array.isArray(targetData) ? targetData : [targetData]).map((m) => (dbTX![targetTable] as TableHandler) diff --git a/lib/DboBuilder/runSQL.ts b/lib/DboBuilder/runSQL.ts index cd8e23b1..91ac22b7 100644 --- a/lib/DboBuilder/runSQL.ts +++ b/lib/DboBuilder/runSQL.ts @@ -3,13 +3,14 @@ import pg from "pg-promise/typescript/pg-subset"; import { AnyObject, SQLOptions, SQLResult, SQLResultInfo } from "prostgles-types"; import { DB, Prostgles } from "../Prostgles"; import { DboBuilder, LocalParams, pgp, postgresToTsType } from "./DboBuilder"; +import { AuthClientRequest } from "../Auth/AuthTypes"; export async function runSQL( this: DboBuilder, queryWithoutRLS: string, args: undefined | AnyObject | any[], options: SQLOptions | undefined, - localParams?: LocalParams + localParams: LocalParams | undefined ) { const queryWithRLS = queryWithoutRLS; if ( @@ -29,17 +30,17 @@ export async function runSQL( await this.cacheDBTypes(); - if (!(await canRunSQL(this.prostgles, localParams))) { + if (!(await canRunSQL(this.prostgles, localParams?.clientReq))) { throw "Not allowed to run SQL"; } const { returnType, allowListen, hasParams = true }: SQLOptions = options || ({} as SQLOptions); - const { socket } = localParams || {}; + const { socket } = localParams?.clientReq ?? {}; const db = localParams?.tx?.t || this.db; if (returnType === "stream") { if (localParams?.tx) throw "Cannot use stream with localParams transaction"; - if (!socket) throw "Only allowed with client socket"; + if (!socket) throw "stream allowed only with client socket"; const streamInfo = await this.queryStreamer.create({ socket, query: pgp.as.format(queryWithRLS, args), @@ -47,7 +48,7 @@ export async function runSQL( }); return streamInfo; } else if (returnType === "noticeSubscription") { - if (!socket) throw "Only allowed with client socket"; + if (!socket) throw "noticeSubscription allowed only with client socket"; return await this.prostgles.dbEventsManager?.addNotice(socket); } else if (returnType === "statement") { try { @@ -86,7 +87,7 @@ export async function runSQL( queryWithoutRLS, queryResult, allowListen, - localParams + localParams?.clientReq ); if (listenHandlers) { return listenHandlers; @@ -115,7 +116,7 @@ const onSQLResult = async function ( queryWithoutRLS: string, { command }: Omit, allowListen: boolean | undefined, - localParams?: LocalParams + clientReq: AuthClientRequest | undefined ) { this.prostgles.schemaWatch?.onSchemaChangeFallback?.({ command, @@ -123,12 +124,12 @@ const onSQLResult = async function ( }); if (command === "LISTEN") { - const { socket } = localParams || {}; + const { socket } = clientReq || {}; if (!allowListen) throw new Error( `Your query contains a LISTEN command. Set { allowListen: true } to get subscription hooks. Or ignore this message` ); - if (!socket) throw "Only allowed with client socket"; + if (!socket) throw "LISTEN allowed only with client socket"; return await this.prostgles.dbEventsManager?.addNotify(queryWithoutRLS, socket); } }; @@ -188,14 +189,11 @@ export function getDetailedFieldInfo(this: DboBuilder, fields: pg.IColumn[]) { export const canRunSQL = async ( prostgles: Prostgles, - localParams?: LocalParams + clientReq: AuthClientRequest | undefined ): Promise => { - if (!localParams?.socket || !localParams.httpReq) return true; + if (!clientReq) return true; - const { socket } = localParams; - const publishParams = await prostgles.publishParser!.getPublishParams({ - socket, - }); + const publishParams = await prostgles.publishParser!.getPublishParams(clientReq); //@ts-ignore const res = await prostgles.opts.publishRawSQL?.(publishParams); return Boolean((res && typeof res === "boolean") || res === "*"); @@ -204,5 +202,5 @@ export const canRunSQL = async ( export const canCreateTables = async (db: DB): Promise => { return db .any(`SELECT has_database_privilege(current_database(), 'create') as yes`) - .then((rows) => rows[0].yes === true); + .then((rows: { yes: boolean }[]) => rows[0]?.yes === true); }; diff --git a/lib/FileManager/initFileManager.ts b/lib/FileManager/initFileManager.ts index 4f646c4a..b2fdaf81 100644 --- a/lib/FileManager/initFileManager.ts +++ b/lib/FileManager/initFileManager.ts @@ -1,5 +1,5 @@ import * as fs from "fs"; -import { asName, tryCatch } from "prostgles-types"; +import { asName, tryCatch, tryCatchV2 } from "prostgles-types"; import { TableHandler } from "../DboBuilder/TableHandler/TableHandler"; import { canCreateTables } from "../DboBuilder/runSQL"; import { Prostgles } from "../Prostgles"; @@ -24,7 +24,7 @@ export async function initFileManager(this: FileManager, prg: Prostgles) { const canCreate = await canCreateTables(this.db); const runQuery = async (q: string, debugInfo: string): Promise => { - const res = await tryCatch(async () => { + const res = await tryCatchV2(async () => { if (!canCreate) throw "File table creation failed. Your postgres user does not have CREATE table privileges"; await this.db.any(q); @@ -35,7 +35,7 @@ export async function initFileManager(this: FileManager, prg: Prostgles) { ...res, data: { debugInfo }, }); - if (res.error) { + if (res.hasError) { throw res.error; } }; @@ -161,15 +161,19 @@ export async function initFileManager(this: FileManager, prg: Prostgles) { content_type: 1, }, }; - const media = await runClientRequest.bind(this.prostgles)({ - type: "http", - httpReq: req, - command: "findOne", - tableName, - param1: { id }, - param2: selectParams, - param3: undefined, - }); + const media = await runClientRequest.bind(this.prostgles)( + { + command: "findOne", + tableName, + param1: { id }, + param2: selectParams, + param3: undefined, + }, + { + res, + httpReq: req, + } + ); if (!media) { res.status(HTTP_FAIL_CODES.NOT_FOUND).send("File not found or not allowed"); diff --git a/lib/Prostgles.ts b/lib/Prostgles.ts index 895e4309..15019f54 100644 --- a/lib/Prostgles.ts +++ b/lib/Prostgles.ts @@ -20,7 +20,6 @@ import TableConfigurator from "./TableConfig/TableConfig"; import { DBHandlerServer, DboBuilder, - LocalParams, PRGLIOSocket, getErrorAsObject, } from "./DboBuilder/DboBuilder"; @@ -67,6 +66,7 @@ const DEFAULT_KEYWORDS = { import { randomUUID } from "crypto"; import * as fs from "fs"; +import { AuthClientRequest } from "./Auth/AuthTypes"; export class Prostgles { /** @@ -383,14 +383,7 @@ export class Prostgles { if (!this.dbo) throw "dbo missing"; - const publishParser = new PublishParser( - this.opts.publish, - this.opts.publishMethods, - this.opts.publishRawSQL, - this.dbo, - this.db!, - this - ); + const publishParser = new PublishParser(this); this.publishParser = publishParser; if (!this.opts.io) return; @@ -413,15 +406,14 @@ export class Prostgles { onSocketConnected = onSocketConnected.bind(this); - getClientSchema = async (clientReq: Pick) => { + getClientSchema = async (clientReq: AuthClientRequest) => { const result = await tryCatchV2(async () => { const clientInfo = - clientReq.socket ? { type: "socket" as const, socket: clientReq.socket } - : clientReq.httpReq ? { type: "http" as const, httpReq: clientReq.httpReq } - : undefined; - if (!clientInfo) throw "Invalid client"; + clientReq.socket ? + { type: "socket" as const, ...clientReq } + : { type: "http" as const, ...clientReq }; - const userData = await this.authHandler?.getClientInfo(clientInfo); + const userData = await this.authHandler?.getUserFromRequest(clientInfo); const { publishParser } = this; let fullSchema: Awaited> | undefined; let publishValidationError; @@ -501,7 +493,7 @@ export class Prostgles { userData, }; }); - const sid = result.data?.userData?.sid ?? this.authHandler?.getSIDNoError(clientReq); + const sid = this.authHandler?.getSIDNoError(clientReq); await this.opts.onLog?.({ type: "connect.getClientSchema", duration: result.duration, @@ -522,19 +514,13 @@ export class Prostgles { socket.on( CHANNELS.SQL, async ( - { query, params, options }: SQLRequest, + sqlRequestData: SQLRequest, cb = (..._callback: any) => { /* Empty */ } ) => { runClientSqlRequest - .bind(this)({ - type: "socket", - socket, - query, - args: params, - options, - }) + .bind(this)(sqlRequestData, { socket }) .then((res) => { cb(null, res); }) diff --git a/lib/ProstglesTypes.ts b/lib/ProstglesTypes.ts index e1ed67b6..ead19ab7 100644 --- a/lib/ProstglesTypes.ts +++ b/lib/ProstglesTypes.ts @@ -1,11 +1,7 @@ import { FileColumnConfig } from "prostgles-types"; import { Auth, AuthRequestParams, SessionUser } from "./Auth/AuthTypes"; import { EventTriggerTagFilter } from "./Event_Trigger_Tags"; -import { - CloudClient, - ImageOptions, - LocalConfig, -} from "./FileManager/FileManager"; +import { CloudClient, ImageOptions, LocalConfig } from "./FileManager/FileManager"; import { DbConnection, OnReadyCallback } from "./initProstgles"; import { EventInfo } from "./Logging"; import { ExpressApp, RestApiConfig } from "./RestApi"; @@ -20,11 +16,7 @@ import pg from "pg-promise/typescript/pg-subset"; import { AnyObject } from "prostgles-types"; import type { Server } from "socket.io"; import { DB } from "./Prostgles"; -import { - Publish, - PublishMethods, - PublishParams, -} from "./PublishParser/PublishParser"; +import { Awaitable, Publish, PublishMethods, PublishParams } from "./PublishParser/PublishParser"; /** * Allows uploading and downloading files. @@ -102,12 +94,7 @@ export type FileTableConfig = { localConfig?: LocalConfig; }; -export const JOIN_TYPES = [ - "one-many", - "many-one", - "one-one", - "many-many", -] as const; +export const JOIN_TYPES = ["one-many", "many-one", "one-one", "many-many"] as const; export type Join = { tables: [string, string]; on: { [key: string]: string }[]; // Allow multi references to table @@ -115,10 +102,7 @@ export type Join = { }; type Joins = Join[] | "inferred"; -export type ProstglesInitOptions< - S = void, - SUser extends SessionUser = SessionUser, -> = { +export type ProstglesInitOptions = { /** * Database connection details and options */ @@ -187,9 +171,7 @@ export type ProstglesInitOptions< /** * If defined and resolves to true then the connected client can run SQL queries */ - publishRawSQL?( - params: PublishParams, - ): (boolean | "*") | Promise; + publishRawSQL?(params: PublishParams): Awaitable; /** * Server-side functions that can be invoked by the client @@ -236,14 +218,14 @@ export type ProstglesInitOptions< * Use for connection verification. Will disconnect socket on any errors */ onSocketConnect?: ( - args: AuthRequestParams & { socket: PRGLIOSocket }, + args: AuthRequestParams & { socket: PRGLIOSocket } ) => void | Promise; /** * Called when a socket disconnects */ onSocketDisconnect?: ( - args: AuthRequestParams & { socket: PRGLIOSocket }, + args: AuthRequestParams & { socket: PRGLIOSocket } ) => void | Promise; /** @@ -281,11 +263,7 @@ export type ProstglesInitOptions< * - `OnSchemaChangeCallback` - custom callback to be fired. Nothing else triggered * Useful for development */ - watchSchema?: - | boolean - | EventTriggerTagFilter - | "hotReloadMode" - | OnSchemaChangeCallback; + watchSchema?: boolean | EventTriggerTagFilter | "hotReloadMode" | OnSchemaChangeCallback; /** * Called when a notice is received from the database @@ -357,6 +335,6 @@ type OnMigrate = (args: { getConstraints: ( table: string, column?: string, - types?: ColConstraint["type"][], + types?: ColConstraint["type"][] ) => Promise; }) => void; diff --git a/lib/PubSubManager/PubSubManager.ts b/lib/PubSubManager/PubSubManager.ts index b758dcf1..2d0882fb 100644 --- a/lib/PubSubManager/PubSubManager.ts +++ b/lib/PubSubManager/PubSubManager.ts @@ -295,7 +295,7 @@ export class PubSubManager { await this.db .tx((t) => t.any(query)) - .catch((e) => { + .catch((e: any) => { console.error("prepareTriggers failed: ", e); throw e; }); @@ -545,10 +545,10 @@ export class PubSubManager { socketId: socket?.id, state: !addedTrigger.tbl ? "fail" : "ok", error: addedTrigger.error, - sid: this.dboBuilder.prostgles.authHandler?.getSIDNoError({ socket }), + sid: socket && this.dboBuilder.prostgles.authHandler?.getSIDNoError({ socket }), tableName: addedTrigger.tbl ?? params.table_name, connectedSocketIds: this.dboBuilder.prostgles.connectedSockets.map((s) => s.id), - localParams: { socket }, + localParams: socket && { clientReq: { socket } }, }); if (addedTrigger.error) throw addedTrigger.error; diff --git a/lib/PubSubManager/addSync.ts b/lib/PubSubManager/addSync.ts index 509a9b0d..3c76c50e 100644 --- a/lib/PubSubManager/addSync.ts +++ b/lib/PubSubManager/addSync.ts @@ -15,9 +15,7 @@ export async function addSync( this: PubSubManager, syncParams: AddSyncParams ): Promise<{ channelName: string }> { - const sid = this.dboBuilder.prostgles.authHandler?.getSIDNoError({ - socket: syncParams.socket, - }); + const sid = this.dboBuilder.prostgles.authHandler?.getSIDNoError({ socket: syncParams.socket }); const res = await tryCatchV2(async () => { const { socket = null, diff --git a/lib/PublishParser/PublishParser.ts b/lib/PublishParser/PublishParser.ts index 93162053..57363f28 100644 --- a/lib/PublishParser/PublishParser.ts +++ b/lib/PublishParser/PublishParser.ts @@ -1,7 +1,8 @@ import { Method, getObjectEntries, isObject } from "prostgles-types"; -import { AuthResult, SessionUser } from "../Auth/AuthTypes"; -import { LocalParams } from "../DboBuilder/DboBuilder"; +import { AuthClientRequest, AuthResult, SessionUser } from "../Auth/AuthTypes"; +import { PublishFullyTyped } from "../DBSchemaBuilder"; import { DB, DBHandlerServer, Prostgles } from "../Prostgles"; +import { ProstglesInitOptions } from "../ProstglesTypes"; import { VoidFunction } from "../SchemaWatch/SchemaWatch"; import { getFileTableRules } from "./getFileTableRules"; import { getSchemaFromPublish } from "./getSchemaFromPublish"; @@ -16,8 +17,6 @@ import { RULE_TO_METHODS, TableRule, } from "./publishTypesAndUtils"; -import { ProstglesInitOptions } from "../ProstglesTypes"; -import { PublishFullyTyped } from "../DBSchemaBuilder"; export class PublishParser { publish: ProstglesInitOptions["publish"]; @@ -27,44 +26,37 @@ export class PublishParser { db: DB; prostgles: Prostgles; - constructor( - publish: any, - publishMethods: PublishMethods | undefined, - publishRawSQL: any, - dbo: DBHandlerServer, - db: DB, - prostgles: Prostgles - ) { - this.publish = publish; - this.publishMethods = publishMethods; - this.publishRawSQL = publishRawSQL; + constructor(prostgles: Prostgles) { + this.prostgles = prostgles; + this.publish = prostgles.opts.publish; + this.publishMethods = prostgles.opts.publishMethods; + this.publishRawSQL = prostgles.opts.publishRawSQL; + const { dbo, db } = prostgles; + if (!dbo || !db) throw "INTERNAL ERROR: dbo and/or db missing"; this.dbo = dbo; this.db = db; - this.prostgles = prostgles; - - if (!this.publish) throw "INTERNAL ERROR: dbo and/or publish missing"; } async getPublishParams( - localParams: LocalParams, + clientReq: AuthClientRequest, clientInfo?: AuthResult ): Promise { return { - ...(clientInfo || (await this.prostgles.authHandler?.getClientInfo(localParams))), + ...(clientInfo || (await this.prostgles.authHandler?.getUserFromRequest(clientReq))), dbo: this.dbo as any, db: this.db, - socket: localParams.socket!, + clientReq, tables: this.prostgles.dboBuilder.tables, }; } async getAllowedMethods( - reqInfo: Pick, - userData?: AuthResult + clientReq: AuthClientRequest, + userData: AuthResult | undefined ): Promise<{ [key: string]: Method }> { const methods: { [key: string]: Method } = {}; - const publishParams = await this.getPublishParams(reqInfo, userData); + const publishParams = await this.getPublishParams(clientReq, userData); const _methods = await applyParamsIfFunc(this.publishMethods, publishParams); if (_methods && Object.keys(_methods).length) { @@ -90,10 +82,10 @@ export class PublishParser { * Parses the first level of publish. (If false then nothing if * then all tables and views) */ async getPublish( - localParams: LocalParams, - clientInfo?: AuthResult + clientReq: AuthClientRequest, + clientInfo: AuthResult ): Promise { - const publishParams = await this.getPublishParams(localParams, clientInfo); + const publishParams = await this.getPublishParams(clientReq, clientInfo); const _publish = await applyParamsIfFunc(this.publish, publishParams); if (_publish === "*") { @@ -106,32 +98,30 @@ export class PublishParser { return _publish || undefined; } + async getValidatedRequestRuleWusr({ tableName, command, - localParams, + clientReq, }: DboTableCommand): Promise { - const clientInfo = await this.prostgles.authHandler!.getClientInfo(localParams); - const rules = await this.getValidatedRequestRule( - { tableName, command, localParams }, - clientInfo - ); + const clientInfo = await this.prostgles.authHandler?.getUserFromRequest(clientReq); + const rules = await this.getValidatedRequestRule({ tableName, command, clientReq }, clientInfo); return rules; } async getValidatedRequestRule( - { tableName, command, localParams }: DboTableCommand, - clientInfo?: AuthResult + { tableName, command, clientReq }: DboTableCommand, + clientInfo: AuthResult | undefined ): Promise { if (!command || !tableName) throw "command OR tableName are missing"; - const rtm = RULE_TO_METHODS.find((rtms) => (rtms.methods as any).includes(command)); + const rtm = RULE_TO_METHODS.find((rtms) => rtms.methods.some((v) => v === command)); if (!rtm) { throw "Invalid command: " + command; } /* Must be local request -> allow everything */ - if (!localParams.socket && !localParams.httpReq) { + if (!clientReq) { return RULE_TO_METHODS.reduce( (a, v) => ({ ...a, @@ -145,19 +135,19 @@ export class PublishParser { if (!this.publish) throw "publish is missing"; /* Get any publish errors for socket */ - const errorInfo = localParams.socket?.prostgles?.tableSchemaErrors[tableName]?.[command]; + const errorInfo = clientReq.socket?.prostgles?.tableSchemaErrors[tableName]?.[command]; if (errorInfo) throw errorInfo.error; - const table_rule = await this.getTableRules({ tableName, localParams }, clientInfo); - if (!table_rule) + const tableRule = await this.getTableRules({ tableName, clientReq }, clientInfo); + if (!tableRule) throw { stack: ["getValidatedRequestRule()"], message: "Invalid or disallowed table: " + tableName, }; if (command === "upsert") { - if (!table_rule.update || !table_rule.insert) { + if (!tableRule.update || !tableRule.insert) { throw { stack: ["getValidatedRequestRule()"], message: `Invalid or disallowed command: upsert`, @@ -165,8 +155,8 @@ export class PublishParser { } } - if (table_rule[rtm.rule]) { - return table_rule; + if (tableRule[rtm.rule]) { + return tableRule; } else throw { stack: ["getValidatedRequestRule()"], @@ -176,14 +166,14 @@ export class PublishParser { async getTableRules( args: DboTable, - clientInfo?: AuthResult + clientInfo: AuthResult | undefined ): Promise { if (this.dbo[args.tableName]?.is_media) { const fileTablePublishRules = await this.getTableRulesWithoutFileTable(args, clientInfo); const { rules } = await getFileTableRules.bind(this)( args.tableName, fileTablePublishRules, - args.localParams, + args.clientReq, clientInfo ); return rules; diff --git a/lib/PublishParser/getFileTableRules.ts b/lib/PublishParser/getFileTableRules.ts index 8fc8a973..7b889ecf 100644 --- a/lib/PublishParser/getFileTableRules.ts +++ b/lib/PublishParser/getFileTableRules.ts @@ -1,6 +1,5 @@ import { AnyObject, FullFilter, isDefined } from "prostgles-types"; -import { AuthResult } from "../Auth/AuthTypes"; -import { LocalParams } from "../DboBuilder/DboBuilder"; +import { AuthClientRequest, AuthResult } from "../Auth/AuthTypes"; import { parseFieldFilter } from "../DboBuilder/ViewHandler/parseFieldFilter"; import { PublishParser } from "./PublishParser"; import { ParsedPublishTable, UpdateRule } from "./publishTypesAndUtils"; @@ -17,8 +16,8 @@ export async function getFileTableRules( this: PublishParser, fileTableName: string, fileTablePublishRules: ParsedPublishTable | undefined, - localParams: LocalParams, - clientInfo: AuthResult | undefined, + clientReq: AuthClientRequest | undefined, + clientInfo: AuthResult | undefined ) { const forcedDeleteFilters: FullFilter[] = []; const forcedSelectFilters: FullFilter[] = []; @@ -28,7 +27,7 @@ export async function getFileTableRules( ?.filter((t) => !t.is_view && t.name !== fileTableName) .map((t) => { const refCols = t.columns.filter((c) => - c.references?.some((r) => r.ftable === fileTableName), + c.references?.some((r) => r.ftable === fileTableName) ); if (!refCols.length) return undefined; return { @@ -39,65 +38,46 @@ export async function getFileTableRules( }) .filter(isDefined); if (referencedColumns?.length) { - for await (const { - tableName, - fileColumns, - allColumns, - } of referencedColumns) { - const table_rules = await this.getTableRules( - { localParams, tableName }, - clientInfo, - ); - if (table_rules) { + for await (const { tableName, fileColumns, allColumns } of referencedColumns) { + const tableRules = await this.getTableRules({ clientReq, tableName }, clientInfo); + if (tableRules) { fileColumns.map((column) => { const path = [{ table: tableName, on: [{ id: column }] }]; - if (table_rules.delete) { + if (tableRules.delete) { forcedDeleteFilters.push({ $existsJoined: { path, - filter: table_rules.delete.forcedFilter ?? {}, + filter: tableRules.delete.forcedFilter ?? {}, }, }); } - if (table_rules.select) { - const parsedFields = parseFieldFilter( - table_rules.select.fields, - false, - allColumns, - ); + if (tableRules.select) { + const parsedFields = parseFieldFilter(tableRules.select.fields, false, allColumns); /** Must be allowed to view this column */ if (parsedFields.includes(column as any)) { forcedSelectFilters.push({ $existsJoined: { path, - filter: table_rules.select.forcedFilter ?? {}, + filter: tableRules.select.forcedFilter ?? {}, }, }); } } - if (table_rules.insert) { - const parsedFields = parseFieldFilter( - table_rules.insert.fields, - false, - allColumns, - ); + if (tableRules.insert) { + const parsedFields = parseFieldFilter(tableRules.insert.fields, false, allColumns); /** Must be allowed to view this column */ if (parsedFields.includes(column as any)) { allowedNestedInserts.push({ table: tableName, column }); } } - if (table_rules.update) { - const parsedFields = parseFieldFilter( - table_rules.update.fields, - false, - allColumns, - ); + if (tableRules.update) { + const parsedFields = parseFieldFilter(tableRules.update.fields, false, allColumns); /** Must be allowed to view this column */ if (parsedFields.includes(column as any)) { forcedUpdateFilters.push({ $existsJoined: { path, - filter: table_rules.update.forcedFilter ?? {}, + filter: tableRules.update.forcedFilter ?? {}, }, }); } @@ -113,15 +93,13 @@ export async function getFileTableRules( const getForcedFilter = ( rule: Pick | undefined, - forcedFilters: FullFilter[], + forcedFilters: FullFilter[] ) => { - return rule && !rule.forcedFilter - ? {} + return rule && !rule.forcedFilter ? + {} : { forcedFilter: { - $or: forcedFilters.concat( - rule?.forcedFilter ? [rule.forcedFilter] : [], - ), + $or: forcedFilters.concat(rule?.forcedFilter ? [rule.forcedFilter] : []), }, }; }; @@ -151,17 +129,15 @@ export async function getFileTableRules( fileTableRule.insert = { fields: "*", ...fileTablePublishRules?.insert, - allowedNestedInserts: fileTablePublishRules?.insert - ? undefined - : allowedNestedInserts, + allowedNestedInserts: fileTablePublishRules?.insert ? undefined : allowedNestedInserts, }; } /** Add missing implied methods (getColumns, getInfo) */ const rules = await this.getTableRulesWithoutFileTable.bind(this)( - { localParams, tableName: fileTableName }, + { clientReq, tableName: fileTableName }, clientInfo, - { [fileTableName]: fileTableRule }, + { [fileTableName]: fileTableRule } ); return { rules, allowedInserts: allowedNestedInserts }; } diff --git a/lib/PublishParser/getSchemaFromPublish.ts b/lib/PublishParser/getSchemaFromPublish.ts index aae546c6..cbf1b8e8 100644 --- a/lib/PublishParser/getSchemaFromPublish.ts +++ b/lib/PublishParser/getSchemaFromPublish.ts @@ -1,27 +1,18 @@ import { DBSchemaTable, + getKeys, MethodKey, + pickKeys, TableInfo, TableSchemaErrors, TableSchemaForClient, - getKeys, - pickKeys, } from "prostgles-types"; -import { AuthResult, ExpressReq } from "../Auth/AuthTypes"; -import { getErrorAsObject, PRGLIOSocket } from "../DboBuilder/DboBuilder"; -import { PublishObject, PublishParser } from "./PublishParser"; +import { AuthClientRequest, AuthResult } from "../Auth/AuthTypes"; +import { getErrorAsObject } from "../DboBuilder/DboBuilder"; import { TABLE_METHODS } from "../Prostgles"; +import { PublishObject, PublishParser } from "./PublishParser"; -type Args = ( - | { - socket: PRGLIOSocket; - httpReq?: undefined; - } - | { - httpReq: ExpressReq; - socket?: undefined; - } -) & { +type Args = AuthClientRequest & { userData: AuthResult | undefined; }; @@ -39,7 +30,8 @@ export async function getSchemaFromPublish( try { /* Publish tables and views based on socket */ - const clientInfo = userData ?? (await this.prostgles.authHandler?.getClientInfo(clientReq)); + const clientInfo = + userData ?? (await this.prostgles.authHandler?.getUserFromRequest(clientReq)); let _publish: PublishObject | undefined; try { @@ -81,10 +73,7 @@ export async function getSchemaFromPublish( throw errMsg; } - const table_rules = await this.getTableRules( - { localParams: clientReq, tableName }, - clientInfo - ); + const table_rules = await this.getTableRules({ clientReq, tableName }, clientInfo); if (table_rules && Object.keys(table_rules).length) { schema[tableName] = {}; @@ -117,13 +106,13 @@ export async function getSchemaFromPublish( : {}; /* Test for issues with the common table CRUD methods () */ - if (TABLE_METHODS.includes(method as any)) { + if (TABLE_METHODS.some((tm) => tm === method)) { try { const valid_table_command_rules = await this.getValidatedRequestRule( { tableName, command: method, - localParams: clientReq, + clientReq, }, clientInfo ); @@ -156,7 +145,7 @@ export async function getSchemaFromPublish( if (method === "getInfo" || method === "getColumns") { const tableRules = await this.getValidatedRequestRule( - { tableName, command: method, localParams: clientReq }, + { tableName, command: method, clientReq }, clientInfo ); const res = await (this.dbo[tableName] as any)[method]( diff --git a/lib/PublishParser/getTableRulesWithoutFileTable.ts b/lib/PublishParser/getTableRulesWithoutFileTable.ts index f26549fa..e0a1cfaa 100644 --- a/lib/PublishParser/getTableRulesWithoutFileTable.ts +++ b/lib/PublishParser/getTableRulesWithoutFileTable.ts @@ -16,25 +16,20 @@ import { export async function getTableRulesWithoutFileTable( this: PublishParser, - { tableName, localParams }: DboTable, + { tableName, clientReq }: DboTable, clientInfo?: AuthResult, overridenPublish?: PublishObject ): Promise { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (!localParams || !tableName) - throw new Error("publish OR socket OR dbo OR tableName are missing"); + if (!tableName) throw new Error("publish OR socket OR dbo OR tableName are missing"); - const _publish = overridenPublish ?? (await this.getPublish(localParams, clientInfo)); + const publish = overridenPublish ?? (clientReq && (await this.getPublish(clientReq, clientInfo))); - const raw_table_rules = _publish?.[tableName]; - if ( - !raw_table_rules || - (isObject(raw_table_rules) && Object.values(raw_table_rules).every((v) => !v)) - ) { + const rawTableRule = publish?.[tableName]; + if (!rawTableRule || (isObject(rawTableRule) && Object.values(rawTableRule).every((v) => !v))) { return undefined; } - let parsed_table: ParsedPublishTable = {}; + let parsedTableRule: ParsedPublishTable = {}; /* Get view or table specific rules */ const tHandler = this.dbo[tableName] as TableHandler | ViewHandler | undefined; @@ -57,8 +52,8 @@ export async function getTableRulesWithoutFileTable( if ( !pgUserIsAllowedThis && - isObject(raw_table_rules) && - (raw_table_rules as PublishTableRule)[r.sqlRule] + isObject(rawTableRule) && + (rawTableRule as PublishTableRule)[r.sqlRule] ) { throw `Your postgres user is not allowed ${r.sqlRule} on table ${tableName}`; } @@ -79,19 +74,16 @@ export async function getTableRulesWithoutFileTable( }); /* All methods allowed. Add no limits for table rules */ - if ([true, "*"].includes(raw_table_rules as any)) { - parsed_table = {}; + if ([true, "*"].includes(rawTableRule as any)) { + parsedTableRule = {}; MY_RULES.filter((r) => r.no_limits).forEach((r) => { - parsed_table[r.rule] = { ...(r.no_limits as object) } as any; + parsedTableRule[r.rule] = { ...(r.no_limits as object) } as any; }); /** Specific rules allowed */ - } else if (isObject(raw_table_rules) && getKeys(raw_table_rules).length) { - const allRuleKeys: (keyof PublishViewRule | keyof PublishTableRule)[] = - getKeys(raw_table_rules); - const dissallowedRuleKeys = allRuleKeys.filter( - (m) => !(raw_table_rules as PublishTableRule)[m] - ); + } else if (isObject(rawTableRule) && getKeys(rawTableRule).length) { + const allRuleKeys: (keyof PublishViewRule | keyof PublishTableRule)[] = getKeys(rawTableRule); + const dissallowedRuleKeys = allRuleKeys.filter((m) => !(rawTableRule as PublishTableRule)[m]); MY_RULES.map((r) => { /** Unless specifically disabled these are allowed */ @@ -99,27 +91,24 @@ export async function getTableRulesWithoutFileTable( ["getInfo", "getColumns"].includes(r.rule) && !dissallowedRuleKeys.includes(r.rule as any) ) { - parsed_table[r.rule] = r.no_limits as any; + parsedTableRule[r.rule] = r.no_limits as any; return; } /** Add no_limit values for implied/ fully allowed methods */ - if ( - [true, "*"].includes((raw_table_rules as PublishTableRule)[r.rule] as any) && - r.no_limits - ) { - parsed_table[r.rule] = Object.assign({}, r.no_limits) as any; + if ([true, "*"].includes((rawTableRule as PublishTableRule)[r.rule] as any) && r.no_limits) { + parsedTableRule[r.rule] = Object.assign({}, r.no_limits) as any; /** Carry over detailed config */ - } else if (isObject((raw_table_rules as any)[r.rule])) { - parsed_table[r.rule] = (raw_table_rules as any)[r.rule]; + } else if (isObject((rawTableRule as any)[r.rule])) { + parsedTableRule[r.rule] = (rawTableRule as any)[r.rule]; } }); allRuleKeys - .filter((m) => parsed_table[m]) + .filter((m) => parsedTableRule[m]) .forEach((method) => { - const rule = parsed_table[method]; + const rule = parsedTableRule[method]; const ruleInfo = MY_RULES.find( (r) => r.rule === method || (r.methods as readonly string[]).includes(method) @@ -150,15 +139,15 @@ export async function getTableRulesWithoutFileTable( /* Add default params (if missing) */ if (method === "sync") { - if ([true, "*"].includes(parsed_table[method] as any)) { + if ([true, "*"].includes(parsedTableRule[method] as any)) { throw "Invalid sync rule. Expecting { id_fields: string[], synced_field: string } "; } - if (typeof parsed_table[method]?.throttle !== "number") { - parsed_table[method]!.throttle = 100; + if (typeof parsedTableRule[method]?.throttle !== "number") { + parsedTableRule[method]!.throttle = 100; } - if (typeof parsed_table[method]?.batch_size !== "number") { - parsed_table[method]!.batch_size = DEFAULT_SYNC_BATCH_SIZE; + if (typeof parsedTableRule[method]?.batch_size !== "number") { + parsedTableRule[method]!.batch_size = DEFAULT_SYNC_BATCH_SIZE; } } @@ -168,8 +157,8 @@ export async function getTableRulesWithoutFileTable( if (method === "select" && !dissallowedRuleKeys.includes(subKey)) { const sr = MY_RULES.find((r) => r.rule === subKey); if (sr && canSubscribe) { - parsed_table[subKey] = { ...(sr.no_limits as SubscribeRule) }; - parsed_table.subscribeOne = { ...(sr.no_limits as SubscribeRule) }; + parsedTableRule[subKey] = { ...(sr.no_limits as SubscribeRule) }; + parsedTableRule.subscribeOne = { ...(sr.no_limits as SubscribeRule) }; } } }); @@ -203,7 +192,7 @@ export async function getTableRulesWithoutFileTable( return res; }; - parsed_table = getImpliedMethods(parsed_table); + parsedTableRule = getImpliedMethods(parsedTableRule); - return parsed_table; + return parsedTableRule; } diff --git a/lib/PublishParser/publishTypesAndUtils.ts b/lib/PublishParser/publishTypesAndUtils.ts index d2962b23..84ac7b05 100644 --- a/lib/PublishParser/publishTypesAndUtils.ts +++ b/lib/PublishParser/publishTypesAndUtils.ts @@ -1,12 +1,6 @@ import { AnyObject, DBSchema, FullFilter, Method, RULE_METHODS } from "prostgles-types"; import type { DBOFullyTyped, PublishFullyTyped } from "../DBSchemaBuilder"; -import { - CommonTableRules, - Filter, - LocalParams, - PRGLIOSocket, - TableOrViewInfo, -} from "../DboBuilder/DboBuilder"; +import { CommonTableRules, Filter, LocalParams, TableOrViewInfo } from "../DboBuilder/DboBuilder"; import { DB, DBHandlerServer } from "../Prostgles"; export type PublishMethods = ( @@ -15,20 +9,13 @@ export type PublishMethods = export type Awaitable = T | Promise; -type Request = { - socket?: any; - httpReq?: any; -}; - -export type DboTable = Request & { +export type DboTable = { tableName: string; - localParams: LocalParams; + clientReq: AuthClientRequest | undefined; +}; +export type DboTableCommand = DboTable & { + command: string; }; -export type DboTableCommand = Request & - DboTable & { - command: string; - localParams: LocalParams; - }; export const RULE_TO_METHODS = [ { @@ -147,8 +134,8 @@ export const RULE_TO_METHODS = [ ] as const; import { FieldFilter, SelectParams } from "prostgles-types"; +import { AuthClientRequest, SessionUser } from "../Auth/AuthTypes"; import { TableSchemaColumn } from "../DboBuilder/DboBuilderTypes"; -import { SessionUser } from "../Auth/AuthTypes"; export type InsertRequestData = { data: object | object[]; @@ -449,7 +436,7 @@ export type PublishParams = { dbo: DBOFullyTyped; db: DB; user?: SUser["user"]; - socket: PRGLIOSocket; + clientReq: AuthClientRequest; tables: DbTableInfo[]; }; export type RequestParams = { dbo?: DBHandlerServer; socket?: any }; diff --git a/lib/RestApi.ts b/lib/RestApi.ts index 9be15c91..ad09fe73 100644 --- a/lib/RestApi.ts +++ b/lib/RestApi.ts @@ -58,14 +58,14 @@ export class RestApi { schema: `${routePrefix}/schema`, }; this.expressApp = expressApp; - expressApp.post(this.routes.db, jsonParser, this.onPost); + expressApp.post(this.routes.db, jsonParser, this.onPostTableCommand); expressApp.post(this.routes.sql, jsonParser, this.onPostSql); expressApp.post(this.routes.methods, jsonParser, this.onPostMethod); expressApp.post(this.routes.schema, jsonParser, this.onPostSchema); } destroy = () => { - this.expressApp.removeListener(this.routes.db, this.onPost); + this.expressApp.removeListener(this.routes.db, this.onPostTableCommand); this.expressApp.removeListener(this.routes.sql, this.onPostSql); this.expressApp.removeListener(this.routes.methods, this.onPostMethod); }; @@ -74,54 +74,62 @@ export class RestApi { const params = req.body || []; try { - const data = await runClientMethod.bind(this.prostgles)({ - type: "http", - httpReq: req, - method, - params, - }); + const data = await runClientMethod.bind(this.prostgles)( + { + method, + params, + }, + { + res, + httpReq: req, + } + ); res.json(data); } catch (rawError) { const error = getSerializedClientErrorFromPGError(rawError, { type: "method", - localParams: { httpReq: req }, + localParams: { clientReq: { httpReq: req, res } }, }); res.status(400).json({ error }); } }; onPostSchema = async (req: ExpressReq, res: ExpressRes) => { try { - const data = await this.prostgles.getClientSchema({ httpReq: req }); + const data = await this.prostgles.getClientSchema({ httpReq: req, res }); res.json(data); } catch (rawError) { const error = getSerializedClientErrorFromPGError(rawError, { type: "method", - localParams: { httpReq: req }, + localParams: { clientReq: { httpReq: req, res } }, }); res.status(400).json({ error }); } }; onPostSql = async (req: ExpressReq, res: ExpressRes) => { - const [query, args, options] = req.body || []; + const [query, params, options] = req.body || []; try { - const data = await runClientSqlRequest.bind(this.prostgles)({ - type: "http", - httpReq: req, - query, - args, - options, - }); + const data = await runClientSqlRequest.bind(this.prostgles)( + { + query, + params, + options, + }, + { + res, + httpReq: req, + } + ); res.json(data); } catch (rawError) { const error = getSerializedClientErrorFromPGError(rawError, { type: "sql", - localParams: { httpReq: req }, + localParams: { clientReq: { httpReq: req, res } }, }); res.status(400).json({ error }); } }; - onPost = async (req: ExpressReq, res: ExpressRes) => { + onPostTableCommand = async (req: ExpressReq, res: ExpressRes) => { const { params } = req; const { tableName, command } = params; if (!tableName || typeof tableName !== "string") { @@ -135,20 +143,24 @@ export class RestApi { try { const [param1, param2, param3] = req.body || []; - const data = await runClientRequest.bind(this.prostgles)({ - type: "http", - httpReq: req, - tableName, - command, - param1, - param2, - param3, - }); + const data = await runClientRequest.bind(this.prostgles)( + { + tableName, + command, + param1, + param2, + param3, + }, + { + httpReq: req, + res, + } + ); res.json(data); } catch (rawError) { const error = getSerializedClientErrorFromPGError(rawError, { type: "tableMethod", - localParams: { httpReq: req }, + localParams: { clientReq: { httpReq: req, res } }, view: this.prostgles.dboBuilder.dbo[tableName], }); res.status(400).json({ error }); diff --git a/lib/initProstgles.ts b/lib/initProstgles.ts index f042e38c..d4a5361f 100644 --- a/lib/initProstgles.ts +++ b/lib/initProstgles.ts @@ -70,14 +70,12 @@ export type InitResult = { options: ProstglesInitOptions; }; -const clientOnlyUpdateKeys = [ - "auth", -] as const satisfies (keyof UpdateableOptions)[]; +const clientOnlyUpdateKeys = ["auth"] as const satisfies (keyof UpdateableOptions)[]; export const initProstgles = async function ( this: Prostgles, onReady: OnReadyCallbackBasic, - reason: OnInitReason, + reason: OnInitReason ): Promise { this.loaded = false; @@ -96,16 +94,14 @@ export const initProstgles = async function ( try { const url = new URL(connString); existingAppName = - url.searchParams.get("application_name") ?? - url.searchParams.get("ApplicationName") ?? - ""; + url.searchParams.get("application_name") ?? url.searchParams.get("ApplicationName") ?? ""; } catch (e) {} } const conObj = - typeof this.opts.dbConnection === "string" - ? { connectionString: this.opts.dbConnection } - : this.opts.dbConnection; + typeof this.opts.dbConnection === "string" ? + { connectionString: this.opts.dbConnection } + : this.opts.dbConnection; const application_name = `prostgles ${this.appId} ${existingAppName}`; /* 1. Connect to db */ @@ -149,14 +145,7 @@ export const initProstgles = async function ( /* 3.9 Check auth config */ await this.initAuthHandler(); - this.publishParser = new PublishParser( - this.opts.publish, - this.opts.publishMethods as any, - this.opts.publishRawSQL, - this.dbo!, - this.db, - this as any, - ); + this.publishParser = new PublishParser(this); this.dboBuilder.publishParser = this.publishParser; /* 4. Set publish and auth listeners */ @@ -221,9 +210,7 @@ export const initProstgles = async function ( * While others also affect the server and onReady should be called */ if ( - getKeys(newOpts).every((updatedKey) => - clientOnlyUpdateKeys.includes(updatedKey as any), - ) + getKeys(newOpts).every((updatedKey) => clientOnlyUpdateKeys.includes(updatedKey as any)) ) { await this.setSocketEvents(); } else { @@ -279,52 +266,52 @@ const getDbConnection = function ({ const onQueryOrError: | undefined | ((error: any, ctx: pgPromise.IEventContext) => void) = - !onQuery && !DEBUG_MODE - ? undefined - : (error, ctx) => { - if (onQuery) { - onQuery(error, ctx); - } else if (DEBUG_MODE) { - if (error) { - console.error(error, ctx); - } else { - console.log(ctx); - } + !onQuery && !DEBUG_MODE ? + undefined + : (error, ctx) => { + if (onQuery) { + onQuery(error, ctx); + } else if (DEBUG_MODE) { + if (error) { + console.error(error, ctx); + } else { + console.log(ctx); } - }; + } + }; const pgp: PGP = pgPromise({ - ...(onQueryOrError - ? { - query: (ctx) => onQueryOrError(undefined, ctx), - error: onQueryOrError, - } - : {}), - ...(onNotice || DEBUG_MODE - ? { - connect: function ({ client, useCount }) { - const isFresh = !useCount; - if (isFresh && !client.listeners("notice").length) { - client.on("notice", function (msg) { - if (onNotice) { - onNotice(msg, msg?.message); - } else { - console.log("notice: %j", msg?.message); - } - }); - } - if (isFresh && !client.listeners("error").length) { - client.on("error", function (msg) { - if (onNotice) { - onNotice(msg, msg?.message); - } else { - console.log("error: %j", msg?.message); - } - }); - } - }, - } - : {}), + ...(onQueryOrError ? + { + query: (ctx) => onQueryOrError(undefined, ctx), + error: onQueryOrError, + } + : {}), + ...(onNotice || DEBUG_MODE ? + { + connect: function ({ client, useCount }) { + const isFresh = !useCount; + if (isFresh && !client.listeners("notice").length) { + client.on("notice", function (msg) { + if (onNotice) { + onNotice(msg, msg?.message); + } else { + console.log("notice: %j", msg?.message); + } + }); + } + if (isFresh && !client.listeners("error").length) { + client.on("error", function (msg) { + if (onNotice) { + onNotice(msg, msg?.message); + } else { + console.log("error: %j", msg?.message); + } + }); + } + }, + } + : {}), }); // pgp.pg.defaults.max = 70; diff --git a/lib/onSocketConnected.ts b/lib/onSocketConnected.ts index 9eea9a7c..3ef6ae6a 100644 --- a/lib/onSocketConnected.ts +++ b/lib/onSocketConnected.ts @@ -26,7 +26,7 @@ export async function onSocketConnected(this: Prostgles, socket: PRGLIOSocket) { if (this.opts.onSocketConnect) { try { const getUser = async () => { - return await this.authHandler?.getClientInfo({ socket }); + return await this.authHandler?.getUserFromRequest({ socket }); }; await this.opts.onSocketConnect({ socket, @@ -55,7 +55,7 @@ export async function onSocketConnected(this: Prostgles, socket: PRGLIOSocket) { } ) => { runClientRequest - .bind(this)({ ...args, type: "socket", socket }) + .bind(this)(args, { socket }) .then((res) => { cb(null, res); }) @@ -79,7 +79,7 @@ export async function onSocketConnected(this: Prostgles, socket: PRGLIOSocket) { if (this.opts.onSocketDisconnect) { const getUser = async () => { - return await this.authHandler?.getClientInfo({ socket }); + return await this.authHandler?.getUserFromRequest({ socket }); }; this.opts.onSocketDisconnect({ socket, dbo: dbo as any, db, getUser }); } @@ -95,12 +95,15 @@ export async function onSocketConnected(this: Prostgles, socket: PRGLIOSocket) { } ) => { runClientMethod - .bind(this)({ - type: "socket", - socket, - method, - params, - }) + .bind(this)( + { + method, + params, + }, + { + socket, + } + ) .then((res) => { cb(null, res); }) diff --git a/lib/runClientRequest.ts b/lib/runClientRequest.ts index 630105b0..192695d9 100644 --- a/lib/runClientRequest.ts +++ b/lib/runClientRequest.ts @@ -1,31 +1,20 @@ -import { AnyObject, TableHandler, UserLike, getKeys, pickKeys } from "prostgles-types"; -import { ExpressReq } from "./Auth/AuthTypes"; -import { LocalParams, PRGLIOSocket } from "./DboBuilder/DboBuilder"; +import { + AnyObject, + SQLOptions, + SQLRequest, + TableHandler, + UserLike, + getKeys, + pickKeys, +} from "prostgles-types"; +import { AuthClientRequest } from "./Auth/AuthTypes"; +import { LocalParams } from "./DboBuilder/DboBuilder"; +import { TableHandler as TableHandlerServer } from "./DboBuilder/TableHandler/TableHandler"; import { parseFieldFilter } from "./DboBuilder/ViewHandler/parseFieldFilter"; import { canRunSQL } from "./DboBuilder/runSQL"; import { Prostgles } from "./Prostgles"; -import { TableHandler as TableHandlerServer } from "./DboBuilder/TableHandler/TableHandler"; import { TableRule } from "./PublishParser/publishTypesAndUtils"; -type ReqInfo = - | { - type: "socket"; - socket: PRGLIOSocket; - httpReq?: undefined; - } - | { - type: "http"; - httpReq: ExpressReq; - socket?: undefined; - }; -type ReqInfoClient = - | { - socket: PRGLIOSocket; - } - | { - httpReq: ExpressReq; - }; - const TABLE_METHODS = { find: 1, findOne: 1, @@ -50,7 +39,7 @@ const SOCKET_ONLY_COMMANDS = [ "sync", ] as const satisfies typeof TABLE_METHODS_KEYS; -type Args = ReqInfo & { +type Args = { tableName: string; command: string; param1: any; @@ -58,13 +47,6 @@ type Args = ReqInfo & { param3: any; }; -const getReqInfoClient = (reqInfo: ReqInfo): ReqInfoClient => { - if (reqInfo.type === "socket") { - return { socket: reqInfo.socket }; - } - return { httpReq: reqInfo.httpReq }; -}; - type TableMethodFunctionWithRulesAndLocalParams = ( arg1: any, arg2: any, @@ -73,17 +55,13 @@ type TableMethodFunctionWithRulesAndLocalParams = ( localParams: LocalParams ) => any; -export const runClientRequest = async function (this: Prostgles, args: Args) { +export const runClientRequest = async function ( + this: Prostgles, + args: Args, + clientReq: AuthClientRequest +) { /* Channel name will only include client-sent params so we ignore table_rules enforced params */ - if ( - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - (args.type === "socket" && !args.socket) || - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - (args.type === "http" && !args.httpReq) || - // !this.authHandler || - !this.publishParser || - !this.dbo - ) { + if (!this.publishParser || !this.dbo) { throw "socket/httpReq or authhandler missing"; } @@ -92,17 +70,16 @@ export const runClientRequest = async function (this: Prostgles, args: Args) { throw `Invalid command: ${nonValidatedCommand}. Expecting one of: ${TABLE_METHODS_KEYS};`; } const command = nonValidatedCommand as keyof TableHandler; - if (args.type !== "socket" && SOCKET_ONLY_COMMANDS.some((v) => v === command)) { + if (!clientReq.socket && SOCKET_ONLY_COMMANDS.some((v) => v === command)) { throw ( "The following commands cannot be completed over a non-websocket connection: " + SOCKET_ONLY_COMMANDS ); } - const reqInfo = getReqInfoClient(args); - const clientInfo = await this.authHandler?.getClientInfo(args); + const clientInfo = await this.authHandler?.getUserFromRequest(clientReq); const validRules = await this.publishParser.getValidatedRequestRule( - { tableName, command, localParams: reqInfo }, + { tableName, command, clientReq }, clientInfo ); @@ -123,11 +100,11 @@ export const runClientRequest = async function (this: Prostgles, args: Args) { ...(pickKeys(clientInfo.user, ["id", "type"]) as UserLike), }; const localParams: LocalParams = { - ...reqInfo, + clientReq, isRemoteRequest: { user: sessionUser }, }; if (param3 && (param3 as LocalParams).returnQuery) { - const isAllowed = await canRunSQL(this, localParams); + const isAllowed = await canRunSQL(this, clientReq); if (isAllowed) { localParams.returnQuery = (param3 as LocalParams).returnQuery; } else { @@ -156,47 +133,54 @@ export const runClientRequest = async function (this: Prostgles, args: Args) { // return result; }; -export const clientCanRunSqlRequest = async function (this: Prostgles, args: ReqInfo) { - const reqInfo = getReqInfoClient(args); +// const getReqInfoClient = (args: A): AuthClientRequest => +// args.httpReq ? { res: args.res, httpReq: args.httpReq } : { socket: args.socket }; + +export const clientCanRunSqlRequest = async function ( + this: Prostgles, + clientReq: AuthClientRequest +) { if (!this.opts.publishRawSQL || typeof this.opts.publishRawSQL !== "function") { - return { allowed: false, reqInfo }; + return { allowed: false, clientReq }; } const canRunSQL = async () => { if (!this.authHandler) { throw "authHandler missing"; } - const publishParams = await this.publishParser?.getPublishParams(reqInfo); - const res = await this.opts.publishRawSQL?.(publishParams as any); + const publishParams = await this.publishParser?.getPublishParams(clientReq); + const res = publishParams && (await this.opts.publishRawSQL?.(publishParams)); return Boolean((res && typeof res === "boolean") || res === "*"); }; const allowed = await canRunSQL(); - return { allowed, reqInfo }; + return { allowed, reqInfo: clientReq }; }; -type ArgsSql = ReqInfo & { - query: string; - args?: AnyObject | any[]; - options?: any; -}; -export const runClientSqlRequest = async function (this: Prostgles, params: ArgsSql) { - const { allowed, reqInfo } = await clientCanRunSqlRequest.bind(this)(params); +export const runClientSqlRequest = async function ( + this: Prostgles, + reqData: SQLRequest, + clientReq: AuthClientRequest +) { + const { allowed } = await clientCanRunSqlRequest.bind(this)(clientReq); if (!allowed) { throw "Not allowed to execute sql"; } if (!this.dbo?.sql) throw "Internal error: sql handler missing"; - const { query, args, options } = params; - return this.dbo.sql(query, args, options, reqInfo); + const { query, params, options } = reqData; + return this.dbo.sql(query, params, options, { clientReq }); }; -type ArgsMethod = ReqInfo & { +type ArgsMethod = { method: string; params?: any[]; }; -export const runClientMethod = async function (this: Prostgles, reqArgs: ArgsMethod) { - const reqInfo = getReqInfoClient(reqArgs); +export const runClientMethod = async function ( + this: Prostgles, + reqArgs: ArgsMethod, + clientReq: AuthClientRequest +) { const { method, params = [] } = reqArgs; - const methods = await this.publishParser?.getAllowedMethods(reqInfo); + const methods = await this.publishParser?.getAllowedMethods(clientReq, undefined); if (!methods || !methods[method]) { throw "Disallowed/missing method " + JSON.stringify(method); diff --git a/package-lock.json b/package-lock.json index 63b3c6ca..55e6ec2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prostgles-server", - "version": "4.2.191", + "version": "4.2.192", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prostgles-server", - "version": "4.2.191", + "version": "4.2.192", "license": "MIT", "dependencies": { "@aws-sdk/client-ses": "^3.699.0", @@ -28,12 +28,12 @@ "pg": "^8.11.5", "pg-cursor": "^2.11.0", "pg-promise": "^11.9.1", - "prostgles-types": "^4.0.130" + "prostgles-types": "^4.0.133" }, "devDependencies": { "@types/express": "^4.17.21", "@types/json-schema": "^7.0.15", - "@types/node": "^22.8.1", + "@types/node": "^22.10.2", "@types/nodemailer": "^6.4.17", "@types/pg": "^8.11.5", "@types/pg-cursor": "^2.7.2", @@ -1500,12 +1500,12 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "22.8.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", - "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.8" + "undici-types": "~6.20.0" } }, "node_modules/@types/nodemailer": { @@ -3643,9 +3643,9 @@ } }, "node_modules/prostgles-types": { - "version": "4.0.130", - "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.130.tgz", - "integrity": "sha512-TV2vjCMAS7Fb/v/WzPpHCLaXVzh2F/8xeUZMMJgYf5XgBdctePPByjmEfrGUPGA5SRI2Abw2BXzfpLb1gYEzQQ==", + "version": "4.0.133", + "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.133.tgz", + "integrity": "sha512-Xr3k3HxkC/B3cdpfc9wpfregSDwOaMTtZgRnKcS+RtTuQ1dWpeMy0LfW77Fv5B2s0Bt8ZI6kK/SPcX1O89QP3g==", "license": "MIT" }, "node_modules/punycode": { @@ -4143,9 +4143,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", "license": "MIT" }, "node_modules/unpipe": { @@ -5389,11 +5389,11 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/node": { - "version": "22.8.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.1.tgz", - "integrity": "sha512-k6Gi8Yyo8EtrNtkHXutUu2corfDf9su95VYVP10aGYMMROM6SAItZi0w1XszA6RtWTHSVp5OeFof37w0IEqCQg==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "requires": { - "undici-types": "~6.19.8" + "undici-types": "~6.20.0" } }, "@types/nodemailer": { @@ -6863,9 +6863,9 @@ "dev": true }, "prostgles-types": { - "version": "4.0.130", - "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.130.tgz", - "integrity": "sha512-TV2vjCMAS7Fb/v/WzPpHCLaXVzh2F/8xeUZMMJgYf5XgBdctePPByjmEfrGUPGA5SRI2Abw2BXzfpLb1gYEzQQ==" + "version": "4.0.133", + "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.133.tgz", + "integrity": "sha512-Xr3k3HxkC/B3cdpfc9wpfregSDwOaMTtZgRnKcS+RtTuQ1dWpeMy0LfW77Fv5B2s0Bt8ZI6kK/SPcX1O89QP3g==" }, "punycode": { "version": "2.3.1", @@ -7179,9 +7179,9 @@ "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" }, "undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" }, "unpipe": { "version": "1.0.0", diff --git a/package.json b/package.json index 0c911243..0779fb4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prostgles-server", - "version": "4.2.191", + "version": "4.2.192", "description": "", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -55,12 +55,12 @@ "pg": "^8.11.5", "pg-cursor": "^2.11.0", "pg-promise": "^11.9.1", - "prostgles-types": "^4.0.130" + "prostgles-types": "^4.0.133" }, "devDependencies": { "@types/express": "^4.17.21", "@types/json-schema": "^7.0.15", - "@types/node": "^22.8.1", + "@types/node": "^22.10.2", "@types/nodemailer": "^6.4.17", "@types/pg": "^8.11.5", "@types/pg-cursor": "^2.7.2", diff --git a/tests/client/package-lock.json b/tests/client/package-lock.json index 16efcc46..9e87ba0f 100644 --- a/tests/client/package-lock.json +++ b/tests/client/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@types/node": "^20.9.2", - "prostgles-client": "^4.0.198", + "prostgles-client": "^4.0.199", "prostgles-types": "^4.0.51", "socket.io-client": "^4.8.1" }, @@ -327,12 +327,12 @@ } }, "node_modules/prostgles-client": { - "version": "4.0.198", - "resolved": "https://registry.npmjs.org/prostgles-client/-/prostgles-client-4.0.198.tgz", - "integrity": "sha512-GZp4Kwbij5eJkluKNzDD8Nq2pgye8sriFrCWq8H4MuodGWcBpz9Nc9FPs/Ha1kjzz8lxicHEJ/5+0y9yMSsGtA==", + "version": "4.0.199", + "resolved": "https://registry.npmjs.org/prostgles-client/-/prostgles-client-4.0.199.tgz", + "integrity": "sha512-t+h7bGuRiCTaheRJfUinxJ7tK3D8aZzolapiotr1gtJ6js2SG5iylxnMUJpv8anufDCCvvnkZ9FLqQNZztt1Qg==", "license": "MIT", "dependencies": { - "prostgles-types": "^4.0.130" + "prostgles-types": "^4.0.133" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0", @@ -348,9 +348,9 @@ } }, "node_modules/prostgles-types": { - "version": "4.0.130", - "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.130.tgz", - "integrity": "sha512-TV2vjCMAS7Fb/v/WzPpHCLaXVzh2F/8xeUZMMJgYf5XgBdctePPByjmEfrGUPGA5SRI2Abw2BXzfpLb1gYEzQQ==", + "version": "4.0.133", + "resolved": "https://registry.npmjs.org/prostgles-types/-/prostgles-types-4.0.133.tgz", + "integrity": "sha512-Xr3k3HxkC/B3cdpfc9wpfregSDwOaMTtZgRnKcS+RtTuQ1dWpeMy0LfW77Fv5B2s0Bt8ZI6kK/SPcX1O89QP3g==", "license": "MIT" }, "node_modules/psl": { diff --git a/tests/client/package.json b/tests/client/package.json index e5b7a850..24918191 100644 --- a/tests/client/package.json +++ b/tests/client/package.json @@ -13,7 +13,7 @@ "license": "ISC", "dependencies": { "@types/node": "^20.9.2", - "prostgles-client": "^4.0.198", + "prostgles-client": "^4.0.199", "prostgles-types": "^4.0.51", "socket.io-client": "^4.8.1" }, diff --git a/tests/client/renderReactHook.ts b/tests/client/renderReactHook.ts index 9d35835c..ce318dc6 100644 --- a/tests/client/renderReactHook.ts +++ b/tests/client/renderReactHook.ts @@ -80,10 +80,7 @@ export const renderReactHookManual = async (rootArgs: { onEnd?: OnEnd; onRender?: OnEnd; }): Promise<{ - setProps: ( - props: Parameters, - opts: { waitFor?: number; onEnd?: OnEnd }, - ) => void; + setProps: (props: Parameters, opts: { waitFor?: number; onEnd?: OnEnd }) => void; getResults: () => ReturnType[]; }> => { const { hook, onUnmount, renderDuration = 250, onEnd, onRender } = rootArgs; @@ -127,19 +124,11 @@ export const renderReactHookManual = async (rootArgs: { onCompRender(result); return React.createElement("h1", null, `Hello`); }; - root.render( - React.createElement( - BasicComponent, - { props: rootArgs.initialProps }, - null, - ), - ); + root.render(React.createElement(BasicComponent, { props: rootArgs.initialProps }, null)); }); }; -export const renderReactHook = ( - rootArgs: RenderHookArgs, -): Promise => { +export const renderReactHook = (rootArgs: RenderHookArgs): Promise => { const { hook, props, @@ -189,8 +178,8 @@ export const renderReactHook = ( if (!resolved) { reject( new Error( - `Expected ${expectedRerenders} rerenders, got ${results.length}:\n${JSON.stringify(results)}`, - ), + `Expected ${expectedRerenders} rerenders, got ${results.length}:\n${JSON.stringify(results)}` + ) ); } }, timeout); diff --git a/tests/clientFileTests.spec.ts b/tests/clientFileTests.spec.ts index d7f99dfe..69be5cfb 100644 --- a/tests/clientFileTests.spec.ts +++ b/tests/clientFileTests.spec.ts @@ -7,8 +7,8 @@ export const clientFileTests = async (db: DBHandlerClient) => { await describe("clientFileTests", async () => { const fileFolder = `${__dirname}/../../server/dist/server/media/`; const getFiles = () => - db.sql("SELECT id, original_name FROM files", {}, { returnType: "rows" }); - await db.sql( + db.sql?.("SELECT id, original_name FROM files", {}, { returnType: "rows" }); + await db.sql?.( ` ALTER TABLE users_public_info DROP CONSTRAINT "users_public_info_avatar_fkey"; @@ -23,7 +23,9 @@ export const clientFileTests = async (db: DBHandlerClient) => { const initialFiles = await getFiles(); /** - * Although only users_public_info is published, file table should show because it is referenced by users_public_info + * Although only users_public_info is published, + * file table should show because it is referenced by users_public_info + * and show only show files that are referenced by users_public_info */ await test("Files table is present", async () => { const files = await db.files.find!(); @@ -90,8 +92,8 @@ export const clientFileTests = async (db: DBHandlerClient) => { { avatar: newData }, { returning: "*" } ); - const avatarFile = await db.files.findOne!({ id: d?.at(0).avatar.id }); - const initialFileStr = fs.readFileSync(fileFolder + avatarFile!.name).toString("utf8"); + const avatarFile = await db.files.findOne?.({ id: d?.at(0)?.avatar.id }); + const initialFileStr = fs.readFileSync(fileFolder + avatarFile?.name).toString("utf8"); assert.equal(newData.data.toString(), initialFileStr); }); @@ -106,7 +108,7 @@ export const clientFileTests = async (db: DBHandlerClient) => { const files = await db.files.find!(); assert.deepStrictEqual(files, []); const latestFiles = await getFiles(); - assert.equal(initialFiles.length, latestFiles.length); + assert.equal(initialFiles?.length, latestFiles?.length); }); }); }; diff --git a/tests/server/package-lock.json b/tests/server/package-lock.json index ca193beb..fab2c558 100644 --- a/tests/server/package-lock.json +++ b/tests/server/package-lock.json @@ -21,7 +21,7 @@ }, "../..": { "name": "prostgles-server", - "version": "4.2.191", + "version": "4.2.192", "license": "MIT", "dependencies": { "@aws-sdk/client-ses": "^3.699.0", @@ -43,12 +43,12 @@ "pg": "^8.11.5", "pg-cursor": "^2.11.0", "pg-promise": "^11.9.1", - "prostgles-types": "^4.0.130" + "prostgles-types": "^4.0.133" }, "devDependencies": { "@types/express": "^4.17.21", "@types/json-schema": "^7.0.15", - "@types/node": "^22.8.1", + "@types/node": "^22.10.2", "@types/nodemailer": "^6.4.17", "@types/pg": "^8.11.5", "@types/pg-cursor": "^2.7.2", @@ -1802,7 +1802,7 @@ "@aws-sdk/credential-provider-node": "^3.699.0", "@types/express": "^4.17.21", "@types/json-schema": "^7.0.15", - "@types/node": "^22.8.1", + "@types/node": "^22.10.2", "@types/nodemailer": "^6.4.17", "@types/passport": "^1.0.17", "@types/passport-facebook": "^3.0.3", @@ -1828,7 +1828,7 @@ "pg-cursor": "^2.11.0", "pg-promise": "^11.9.1", "prettier": "^3.4.2", - "prostgles-types": "^4.0.130", + "prostgles-types": "^4.0.133", "socket.io": "^4.8.1", "typescript": "^5.3.3" }