Skip to content

Commit

Permalink
Upgrade giraffeql boilerplate to latest
Browse files Browse the repository at this point in the history
  • Loading branch information
big213 committed Oct 11, 2021
1 parent e2b2760 commit e6f34e2
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 185 deletions.
6 changes: 3 additions & 3 deletions backend/functions/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"express": "^4.17.1",
"firebase-admin": "^9.2.0",
"firebase-functions": "^3.13.1",
"giraffeql": "^2.0.1",
"giraffeql": "^2.0.3",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.17",
"pg": "^8.5.1",
Expand Down
9 changes: 6 additions & 3 deletions backend/functions/src/schema/core/generators/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import {
import { ObjectTypeDefinition, ObjectTypeDefinitionField } from "giraffeql";

type ServicesObjectMap = {
[x: string]: NormalService;
[x: string]: {
allowNull?: boolean;
service: NormalService;
};
};

export function generateLinkTypeDef(
Expand All @@ -23,8 +26,8 @@ export function generateLinkTypeDef(

for (const field in servicesObjectMap) {
typeDefFields[field] = generateJoinableField({
allowNull: false,
service: servicesObjectMap[field],
service: servicesObjectMap[field].service,
allowNull: servicesObjectMap[field].allowNull ?? false,
sqlOptions: { unique: "compositeIndex" },
});
}
Expand Down
10 changes: 10 additions & 0 deletions backend/functions/src/schema/core/helpers/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ export abstract class Kenum {
);
}

static fromUnknown(key: unknown): Kenum {
if (typeof key === "number") return this.fromIndex(key);

if (typeof key === "string") return this.fromName(key);

throw new Error(
"Invalid key type for kenum. Only Number or String allowed"
);
}

public get parsed(): number {
return this.index;
}
Expand Down
19 changes: 17 additions & 2 deletions backend/functions/src/schema/core/helpers/error.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import { GiraffeqlBaseError } from "giraffeql";

export class PermissionsError extends GiraffeqlBaseError {
constructor(params: { message: string; fieldPath: string[] }) {
const { message, fieldPath } = params;
super({
errorName: "PermissionsError",
message,
fieldPath,
statusCode: 401,
});
}
}

export function generateError(
message: string,
fieldPath: string[],
Expand All @@ -16,8 +28,11 @@ export function itemNotFoundError(fieldPath: string[]): GiraffeqlBaseError {
return generateError("Record was not found", fieldPath, 404);
}

export function badPermissionsError(fieldPath: string[]): GiraffeqlBaseError {
return generateError("Insufficient permissions", fieldPath, 401);
export function badPermissionsError(
fieldPath: string[],
message?: string
): PermissionsError {
return generateError(message ?? "Insufficient permissions", fieldPath, 401);
}

export function invalidSqlError(): GiraffeqlBaseError {
Expand Down
97 changes: 3 additions & 94 deletions backend/functions/src/schema/core/helpers/permissions.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,6 @@
import { userRoleKenum } from "../../enums";
import { BaseService, NormalService } from "../services";
import * as errorHelper from "./error";
import { ServiceFunctionInputs, AccessControlFunction } from "../../../types";
import { StringKeyObject } from "giraffeql";

export function generateItemCreatedByUserGuard(
service: NormalService
): AccessControlFunction {
return async function ({ req, args, fieldPath }) {
// args should be validated already
const validatedArgs = <StringKeyObject>args;
//check if logged in
if (!req.user) return false;

try {
const itemRecord = await service.lookupRecord(
[{ field: "createdBy" }],
validatedArgs.item ?? validatedArgs,
fieldPath
);

return itemRecord?.createdBy === req.user.id;
} catch (err) {
return false;
}
};
}

export function generateUserAdminGuard(): AccessControlFunction {
return generateUserRoleGuard([userRoleKenum.ADMIN]);
}

export function generateUserRoleGuard(
allowedRoles: userRoleKenum[]
): AccessControlFunction {
return async function ({ req }) {
//check if logged in
if (!req.user) return false;

try {
// role is loaded in helpers/auth on token decode
/*
const userRecords = await sqlHelper.fetchTableRows({
select: [{ field: "role" }],
from: User.typename,
where: {
fields: [{ field: "id", value: req.user.id }],
},
});
*/

if (!req.user.role) return false;
return allowedRoles.includes(req.user.role);
} catch (err) {
return false;
}
};
}

/*
export function userRoleGuard(allowedRoles: userRoleKenum[]) {
return function (
target: BaseService,
propertyName: string,
propertyDescriptor: PropertyDescriptor
): PropertyDescriptor {
// target === Employee.prototype
// propertyName === "greet"
// propertyDesciptor === Object.getOwnPropertyDescriptor(Employee.prototype, "greet")
const method = propertyDescriptor.value;
propertyDescriptor.value = async function (req, args, query) {
// convert list of greet arguments to string
//const params = args.map((a) => JSON.stringify(a)).join();
const params = "bar";
//if it does not pass the access control, throw an error
if (!(await target.testPermissions("get", req, args, query))) {
throw errorHelper.badPermissionsError();
}
// invoke greet() and get its return value
const result = await method.apply(this, [req, args, query]);
// convert result to string
const r = JSON.stringify(result);
// display in console the function call details
console.log(`Call: ${propertyName}(${params}) => ${r}`);
// return the result of invoking the method
return result;
};
return propertyDescriptor;
};
}
*/

export function permissionsCheck(methodKey: string) {
return function (
Expand All @@ -116,6 +22,7 @@ export function permissionsCheck(methodKey: string) {
isAdmin = false,
}: ServiceFunctionInputs) {
//if it does not pass the access control, throw an error

if (
!(await target.testPermissions.apply(this, [
methodKey,
Expand All @@ -129,8 +36,10 @@ export function permissionsCheck(methodKey: string) {
},
]))
) {
// if returns false, fallback to a generic bad permissions error
throw errorHelper.badPermissionsError(fieldPath);
}

// invoke greet() and get its return value
const result = await method.apply(this, [
{
Expand Down
2 changes: 1 addition & 1 deletion backend/functions/src/schema/core/helpers/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export async function createObjectType({
for (const field in addFields) {
if (!(field in typeDef.definition.fields)) {
throw new GiraffeqlBaseError({
message: `Invalid field`,
message: `Invalid add field: ${field}`,
fieldPath,
});
}
Expand Down
13 changes: 13 additions & 0 deletions backend/functions/src/schema/core/helpers/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,16 @@ export function capitalizeString(str: string): string {
export function lowercaseString(str: string): string {
return str.charAt(0).toLowerCase() + str.slice(1);
}

export function objectOnlyHasFields(
obj: StringKeyObject,
fields: string[],
allFieldsRequired = false
) {
const objKeys = Object.keys(obj);

return allFieldsRequired
? objKeys.length === fields.length &&
objKeys.every((key) => fields.includes(key))
: objKeys.every((key) => fields.includes(key));
}
82 changes: 66 additions & 16 deletions backend/functions/src/schema/core/helpers/sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,13 @@ export type SqlWhereFieldOperator =
| "regex"
| "like"
| "gt"
| "gtornull"
| "gte"
| "gteornull"
| "lt"
| "lte";
| "ltornull"
| "lte"
| "lteornull";

export type SqlSelectQuery = {
select: SqlSelectQueryObject[];
Expand Down Expand Up @@ -102,9 +106,18 @@ export type SqlDeleteQuery = {
extendFn?: KnexExtendFunction;
};

function generateError(err: Error, fieldPath?: string[]) {
const errMessage = isDev ? err.message : "A SQL error has occurred";
function generateError(err: unknown, fieldPath?: string[]) {
console.log(err);

// double check if err is type Error
let errMessage: string;
if (err instanceof Error) {
errMessage = isDev ? err.message : "A SQL error has occurred";
} else {
console.log("Invalid error was thrown");
errMessage = "A SQL error has occurred";
}

return new GiraffeqlBaseError({
message: errMessage,
fieldPath,
Expand Down Expand Up @@ -197,21 +210,26 @@ function processFields(relevantFields: Set<string>, table: string) {
// set the actualFieldPart to the 2nd part
actualFieldPart = subParts[1];

const linkTableAlias = acquireTableAlias(tableIndexMap, linkJoinType);

// set and advance the join table
currentJoinObject[fieldPart] = {
table: linkJoinType,
alias: linkTableAlias,
field: "id",
joinField,
nested: {},
};

currentJoinObject = currentJoinObject[fieldPart].nested;
// only proceed IF the ${linkJoinType}/* is not already in the joinObject
const linkJoinTypeStr = linkJoinType + "/*";
if (!(linkJoinTypeStr in currentJoinObject)) {
const linkTableAlias = acquireTableAlias(tableIndexMap, linkJoinType);

// set and advance the join table
currentJoinObject[linkJoinTypeStr] = {
table: linkJoinType,
alias: linkTableAlias,
field: "id",
joinField,
nested: {},
};
}

// set currentTableAlias
currentTableAlias = linkTableAlias;
currentTableAlias = currentJoinObject[linkJoinTypeStr].alias;

// advance the join object
currentJoinObject = currentJoinObject[linkJoinTypeStr].nested;
}

// find the field on the currentTypeDef
Expand Down Expand Up @@ -327,6 +345,14 @@ function applyWhere(
bindings.push(whereSubObject.value);
}
break;
case "gtornull":
if (whereSubObject.value === null) {
throw new Error("Can't use this operator with null");
} else {
whereSubstatement = `(${whereSubstatement} > ? OR ${whereSubstatement} IS NULL)`;
bindings.push(whereSubObject.value);
}
break;
case "gte":
if (whereSubObject.value === null) {
throw new Error("Can't use this operator with null");
Expand All @@ -335,6 +361,14 @@ function applyWhere(
bindings.push(whereSubObject.value);
}
break;
case "gteornull":
if (whereSubObject.value === null) {
throw new Error("Can't use this operator with null");
} else {
whereSubstatement = `(${whereSubstatement} >= ? OR ${whereSubstatement} IS NULL)`;
bindings.push(whereSubObject.value);
}
break;
case "lt":
if (whereSubObject.value === null) {
throw new Error("Can't use this operator with null");
Expand All @@ -343,6 +377,14 @@ function applyWhere(
bindings.push(whereSubObject.value);
}
break;
case "ltornull":
if (whereSubObject.value === null) {
throw new Error("Can't use this operator with null");
} else {
whereSubstatement = `(${whereSubstatement} < ? OR ${whereSubstatement} IS NULL)`;
bindings.push(whereSubObject.value);
}
break;
case "lte":
if (whereSubObject.value === null) {
throw new Error("Can't use this operator with null");
Expand All @@ -351,6 +393,14 @@ function applyWhere(
bindings.push(whereSubObject.value);
}
break;
case "lteornull":
if (whereSubObject.value === null) {
throw new Error("Can't use this operator with null");
} else {
whereSubstatement = `(${whereSubstatement} <= ? OR ${whereSubstatement} IS NULL)`;
bindings.push(whereSubObject.value);
}
break;
case "in":
if (Array.isArray(whereSubObject.value)) {
// if array is empty, is equivalent of FALSE
Expand Down
Loading

0 comments on commit e6f34e2

Please sign in to comment.