Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const once = (fn) => {
return function (...args) {
if (called) return;
called = true;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return fn.apply(this, args);
};
};
Expand Down
2 changes: 2 additions & 0 deletions src/services/auth/plugins/captcha/captcha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export default function captchaPreHandler(
action: RecaptchaActionType,
options?: { shouldFail: boolean },
): RouteHandlerMethod {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return async (request: FastifyRequest<{ Body: { captcha: string } }>, _reply: FastifyReply) => {
const { captcha } = request.body;
return await validateCaptcha(request, captcha, action, options);
Expand Down
2 changes: 2 additions & 0 deletions src/services/auth/plugins/magicLink/magicLink.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
schema: auth,
preHandler: fastifyPassport.authenticate(
PassportStrategy.WebMagicLink,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
async (request, reply, err, user?: PassportUser, info?: PassportInfo) => {
// This function is called after the strategy has been executed.
// It is necessary, so we match the behavior of the original implementation.
Expand Down
2 changes: 2 additions & 0 deletions src/services/auth/plugins/passport/preHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export const guestAuthenticateAppsJWT = fastifyPassport.authenticate(
export function matchOne<R extends RouteGenericInterface>(
...strategies: RessourceAuthorizationStrategy<R>[]
): RouteHandlerMethod {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
return async (req: FastifyRequest<R>) => {
if (!strategies.some((strategy) => strategy.test(req))) {
// If none of the strategies pass, throw an error.
Expand Down
9 changes: 6 additions & 3 deletions src/services/auth/plugins/passport/strategies/emailChange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Authenticator } from '@fastify/passport';

import { EMAIL_CHANGE_JWT_SECRET } from '../../../../../config/secrets';
import { db } from '../../../../../drizzle/db';
import { MemberNotFound, UnauthorizedMember } from '../../../../../utils/errors';
import { MemberNotFound, UnauthorizedMember, buildError } from '../../../../../utils/errors';
import { MemberRepository } from '../../../../member/member.repository';
import { PassportStrategy } from '../strategies';
import type { CustomStrategyOptions, StrictVerifiedCallback } from '../types';
Expand Down Expand Up @@ -47,9 +47,12 @@ export default (
false,
);
}
} catch (err) {
} catch (error: unknown) {
// Exception occurred while fetching member
return done(options?.propagateError ? err : new UnauthorizedMember(), false);
return done(
options?.propagateError ? buildError(error) : new UnauthorizedMember(),
false,
);
}
},
),
Expand Down
9 changes: 6 additions & 3 deletions src/services/auth/plugins/passport/strategies/jwtApps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Authenticator } from '@fastify/passport';

import { APPS_JWT_SECRET } from '../../../../../config/secrets';
import { db } from '../../../../../drizzle/db';
import { UnauthorizedMember } from '../../../../../utils/errors';
import { UnauthorizedMember, buildError } from '../../../../../utils/errors';
import { AccountRepository } from '../../../../account/account.repository';
import { ItemRepository } from '../../../../item/item.repository';
import { PassportStrategy } from '../strategies';
Expand Down Expand Up @@ -53,11 +53,14 @@ export default (
key,
},
});
} catch (err) {
} catch (error: unknown) {
// Exception occurred while fetching item
// itemRepository.getOneOrThrow() can fail for many reasons like the item was not found, database error, etc.
// To avoid leaking information, we prefer to return UnauthorizedMember error.
return done(options?.propagateError ? err : new UnauthorizedMember(), false);
return done(
options?.propagateError ? buildError(error) : new UnauthorizedMember(),
false,
);
}
},
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import { Authenticator } from '@fastify/passport';

import { JWT_SECRET } from '../../../../../config/secrets';
import { db } from '../../../../../drizzle/db';
import { ChallengeFailed, MemberNotFound, UnauthorizedMember } from '../../../../../utils/errors';
import {
ChallengeFailed,
MemberNotFound,
UnauthorizedMember,
buildError,
} from '../../../../../utils/errors';
import { AccountRepository } from '../../../../account/account.repository';
import { SHORT_TOKEN_PARAM } from '../constants';
import { PassportStrategy } from '../strategies';
Expand Down Expand Up @@ -57,9 +62,9 @@ export default (
false,
);
}
} catch (err) {
} catch (error: unknown) {
// Exception occurred while fetching member
return done(spreadException ? err : new UnauthorizedMember(), false);
return done(spreadException ? buildError(error) : new UnauthorizedMember(), false);
}
},
),
Expand Down
10 changes: 5 additions & 5 deletions src/services/file/file.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ describe('FileService', () => {
it('upload failure will delete file', async () => {
const uploadFileMock = jest.spyOn(s3Repository, 'uploadFiles').mockRejectedValue('error');
const deleteFileMock = jest.spyOn(s3Repository, 'deleteFiles').mockImplementation(doNothing);
await expect(s3FileService.upload(member, uploadPayload)).rejects.toMatchObject(
new UploadFileUnexpectedError(expect.anything()),
await expect(s3FileService.upload(member, uploadPayload)).rejects.toThrow(
new UploadFileUnexpectedError({ memberId: member.id }),
);
expect(uploadFileMock).toHaveBeenCalled();
expect(deleteFileMock).toHaveBeenCalled();
Expand Down Expand Up @@ -276,19 +276,19 @@ describe('FileService', () => {
const copyMock = jest
.spyOn(s3Repository, 'copyFile')
.mockImplementation(async () => 'string');
await s3FileService.copy(member, copyPayload);
await s3FileService.copy(member.id, copyPayload);
expect(copyMock).toHaveBeenCalled();
});

it('empty originalPath throws', async () => {
await expect(
s3FileService.copy(member, { ...copyPayload, originalPath: '' }),
s3FileService.copy(member.id, { ...copyPayload, originalPath: '' }),
).rejects.toMatchObject(new CopyFileInvalidPathError(expect.anything()));
});

it('empty newFilePath throws', async () => {
await expect(
s3FileService.copy(member, { ...copyPayload, newFilePath: '' }),
s3FileService.copy(member.id, { ...copyPayload, newFilePath: '' }),
).rejects.toMatchObject(new CopyFileInvalidPathError(expect.anything()));
});
});
Expand Down
4 changes: 2 additions & 2 deletions src/services/file/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class FileService {
}

async copy(
member: MinimalMember,
memberId: MinimalMember['id'],
data: {
newId?: string;
newFilePath: string;
Expand All @@ -180,7 +180,7 @@ class FileService {

return this.repository.copyFile({
newId,
memberId: member.id,
memberId,
originalPath,
newFilePath,
mimetype,
Expand Down
2 changes: 1 addition & 1 deletion src/services/file/repositories/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class LocalFileRepository implements FileRepository {
try {
await access(this.buildFullPath(filepath));
} catch (e) {
if (e.code === 'ENOENT') {
if (e !== null && typeof e === 'object' && 'code' in e && e.code === 'ENOENT') {
throw new LocalFileNotFound({ filepath });
}
throw e;
Expand Down
37 changes: 12 additions & 25 deletions src/services/file/utils/errors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { StatusCodes } from 'http-status-codes';

import { createError } from '@fastify/error';

import { ErrorFactory, FAILURE_MESSAGES } from '@graasp/sdk';

import { PLUGIN_NAME } from './constants';
Expand Down Expand Up @@ -108,32 +110,17 @@ export class S3FileNotFound extends GraaspFileError {
}
}

export class UploadEmptyFileError extends GraaspFileError {
constructor(data?: unknown) {
super(
{
code: 'GPFERR008',
statusCode: StatusCodes.BAD_REQUEST,
message: FAILURE_MESSAGES.UPLOAD_EMPTY_FILE,
},
data,
);
}
}
export const UploadEmptyFileError = createError(
'GPFERR008',
FAILURE_MESSAGES.UPLOAD_EMPTY_FILE,
StatusCodes.BAD_REQUEST,
);

export class UploadFileUnexpectedError extends GraaspFileError {
constructor(data?: unknown) {
super(
{
code: 'GPFERR010',
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
// TODO: change message
message: FAILURE_MESSAGES.UPLOAD_FILE_UNEXPECTED_ERROR,
},
data,
);
}
}
export const UploadFileUnexpectedError = createError(
'GPFERR010',
FAILURE_MESSAGES.UPLOAD_FILE_UNEXPECTED_ERROR,
StatusCodes.INTERNAL_SERVER_ERROR,
);

export class DownloadFileUnexpectedError extends GraaspFileError {
constructor(data?: unknown) {
Expand Down
56 changes: 24 additions & 32 deletions src/services/item/item.repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,15 +810,13 @@ describe('Item Repository', () => {
const itemCreatorIdsInDB = insertedItems.map((i) => i.creatorId);
const itemPathsInDb = insertedItems.map((i) => i.path);

expect(itemNamesInDB.sort(alphabeticalOrder)).toEqual(
insertedItemNames.sort(alphabeticalOrder),
expect(itemNamesInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemNames.toSorted(alphabeticalOrder),
);
expect(itemTypesInDB.sort(alphabeticalOrder)).toEqual(
insertedItemTypes.sort(alphabeticalOrder),
);
expect(itemCreatorIdsInDB.sort(alphabeticalOrder)).toEqual(
insertedItemCreatorIds.sort(alphabeticalOrder),
expect(itemTypesInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemTypes.toSorted(alphabeticalOrder),
);
expect(itemCreatorIdsInDB).toEqual(insertedItemCreatorIds);
expect(itemPathsInDb.every((path) => !path.includes('.'))).toBeTruthy();
});
it('post many with parent item', async () => {
Expand Down Expand Up @@ -848,17 +846,15 @@ describe('Item Repository', () => {
const itemCreatorIdsInDB = insertedItems.map((i) => i.creatorId);
const itemPathsInDB = insertedItems.map((i) => i.path);

expect(itemNamesInDB.sort(alphabeticalOrder)).toEqual(
insertedItemNames.sort(alphabeticalOrder),
);
expect(itemTypesInDB.sort(alphabeticalOrder)).toEqual(
insertedItemTypes.sort(alphabeticalOrder),
expect(itemNamesInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemNames.toSorted(alphabeticalOrder),
);
expect(itemCreatorIdsInDB.sort(alphabeticalOrder)).toEqual(
insertedItemCreatorIds.sort(alphabeticalOrder),
expect(itemTypesInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemTypes.toSorted(alphabeticalOrder),
);
expect(itemPathsInDB.sort(alphabeticalOrder)).toEqual(
insertedItemPaths.sort(alphabeticalOrder),
expect(itemCreatorIdsInDB).toEqual(insertedItemCreatorIds);
expect(itemPathsInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemPaths.toSorted(alphabeticalOrder),
);
expect(itemPathsInDB.every((path) => path.includes(`${parentItem.path}.`))).toBeTruthy();
});
Expand Down Expand Up @@ -891,15 +887,13 @@ describe('Item Repository', () => {
const itemCreatorIdsInDB = insertedItems.map((i) => i.creatorId);
const itemPathsInDb = insertedItems.map((i) => i.path);

expect(itemNamesInDB.sort(alphabeticalOrder)).toEqual(
insertedItemNames.sort(alphabeticalOrder),
expect(itemNamesInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemNames.toSorted(alphabeticalOrder),
);
expect(itemTypesInDB.sort(alphabeticalOrder)).toEqual(
insertedItemTypes.sort(alphabeticalOrder),
);
expect(itemCreatorIdsInDB.sort(alphabeticalOrder)).toEqual(
insertedItemCreatorIds.sort(alphabeticalOrder),
expect(itemTypesInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemTypes.toSorted(alphabeticalOrder),
);
expect(itemCreatorIdsInDB).toEqual(insertedItemCreatorIds);
expect(itemPathsInDb.every((path) => !path.includes('.'))).toBeTruthy();
});
it('post many with parent item', async () => {
Expand Down Expand Up @@ -933,17 +927,15 @@ describe('Item Repository', () => {
const itemCreatorIdsInDB = insertedItems.map((i) => i.creatorId);
const itemPathsInDB = insertedItems.map((i) => i.path);

expect(itemNamesInDB.sort(alphabeticalOrder)).toEqual(
insertedItemNames.sort(alphabeticalOrder),
);
expect(itemTypesInDB.sort(alphabeticalOrder)).toEqual(
insertedItemTypes.sort(alphabeticalOrder),
expect(itemNamesInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemNames.toSorted(alphabeticalOrder),
);
expect(itemCreatorIdsInDB.sort(alphabeticalOrder)).toEqual(
insertedItemCreatorIds.sort(alphabeticalOrder),
expect(itemTypesInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemTypes.toSorted(alphabeticalOrder),
);
expect(itemPathsInDB.sort(alphabeticalOrder)).toEqual(
insertedItemPaths.sort(alphabeticalOrder),
expect(itemCreatorIdsInDB).toEqual(insertedItemCreatorIds);
expect(itemPathsInDB.toSorted(alphabeticalOrder)).toEqual(
insertedItemPaths.toSorted(alphabeticalOrder),
);
expect(itemPathsInDB.every((path) => path.includes(`${parentItem.path}.`))).toBeTruthy();
});
Expand Down
17 changes: 1 addition & 16 deletions src/services/item/plugins/action/itemAction.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { StatusCodes } from 'http-status-codes';
import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox';
import fp from 'fastify-plugin';

import { type FileItemType } from '@graasp/sdk';

import { resolveDependency } from '../../../../di/utils';
import { db } from '../../../../drizzle/db';
import { asDefined } from '../../../../utils/assertions';
Expand All @@ -13,10 +11,6 @@ import { ActionService } from '../../../action/action.service';
import { isAuthenticated, matchOne, optionalIsAuthenticated } from '../../../auth/plugins/passport';
import { assertIsMember } from '../../../authentication';
import { AuthorizedItemService } from '../../../authorizedItem.service';
import type {
LocalFileConfiguration,
S3FileConfiguration,
} from '../../../file/interfaces/configuration';
import { validatedMemberAccountRole } from '../../../member/strategies/validatedMemberAccountRole';
import {
ItemOpFeedbackErrorEvent,
Expand All @@ -34,16 +28,7 @@ import {
import { ItemActionService } from './itemAction.service';
import { ActionRequestExportService } from './requestExport/itemAction.requestExport.service';

export interface GraaspActionsOptions {
shouldSave?: boolean;
fileItemType: FileItemType;
fileConfigurations: {
s3: S3FileConfiguration;
local: LocalFileConfiguration;
};
}

const plugin: FastifyPluginAsyncTypebox<GraaspActionsOptions> = async (fastify) => {
const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
const { websockets } = fastify;

const authorizedItemService = resolveDependency(AuthorizedItemService);
Expand Down
3 changes: 1 addition & 2 deletions src/services/item/plugins/app/appItem.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import { validatedMemberAccountRole } from '../../../member/strategies/validated
import { ItemActionService } from '../action/itemAction.service';
import { createApp, updateApp } from './app.schemas';
import { AppItemService } from './appItemService';
import type { AppsPluginOptions } from './types';

export const plugin: FastifyPluginAsyncTypebox<AppsPluginOptions> = async (fastify) => {
export const plugin: FastifyPluginAsyncTypebox = async (fastify) => {
// service for item app api
const appItemService = resolveDependency(AppItemService);
const itemActionService = resolveDependency(ItemActionService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { FastifyPluginAsyncTypebox } from '@fastify/type-provider-typebox';

import { resolveDependency } from '../../../../../di/utils';
import { type DBConnection, db } from '../../../../../drizzle/db';
import type { AuthenticatedUser } from '../../../../../types';
import { MinimalItemForInsert } from '../../../../../drizzle/types';
import type { MaybeUser } from '../../../../../types';
import { asDefined } from '../../../../../utils/assertions';
import {
authenticateAppsJWT,
Expand Down Expand Up @@ -34,9 +35,9 @@ const plugin: FastifyPluginAsyncTypebox = async (fastify) => {

// copy app settings and related files on item copy
const hook = async (
actor: AuthenticatedUser,
actor: MaybeUser,
dbConnection: DBConnection,
{ original, copy }: { original: ItemRaw; copy: ItemRaw },
{ original, copy }: { original: ItemRaw; copy: MinimalItemForInsert },
) => {
if (original.type !== 'app' || copy.type !== 'app') return;

Expand Down
Loading
Loading