diff --git a/backend/src/share/share.service.ts b/backend/src/share/share.service.ts index 8c0e81b3..fbb802e0 100644 --- a/backend/src/share/share.service.ts +++ b/backend/src/share/share.service.ts @@ -315,11 +315,21 @@ export class ShareService { }, }); - if ( - share?.security?.password && - !(await argon.verify(share.security.password, password)) - ) { - throw new ForbiddenException("Wrong password", "wrong_password"); + if (share?.security?.password) { + if (!password) { + throw new ForbiddenException( + "This share is password protected", + "share_password_required", + ); + } + + const isPasswordValid = await argon.verify( + share.security.password, + password, + ); + if (!isPasswordValid) { + throw new ForbiddenException("Wrong password", "wrong_password"); + } } if (share.security?.maxViews && share.security.maxViews <= share.views) { @@ -335,12 +345,13 @@ export class ShareService { } async generateShareToken(shareId: string) { - const { expiration } = await this.prisma.share.findUnique({ + const { expiration, createdAt } = await this.prisma.share.findUnique({ where: { id: shareId }, }); const tokenPayload = { shareId, + shareCreatedAt: moment(createdAt).unix(), iat: moment().unix(), }; @@ -356,7 +367,7 @@ export class ShareService { } async verifyShareToken(shareId: string, token: string) { - const { expiration } = await this.prisma.share.findUnique({ + const { expiration, createdAt } = await this.prisma.share.findUnique({ where: { id: shareId }, }); @@ -367,7 +378,10 @@ export class ShareService { ignoreExpiration: moment(expiration).isSame(0), }); - return claims.shareId == shareId; + return ( + claims.shareId == shareId && + claims.shareCreatedAt == moment(createdAt).unix() + ); } catch { return false; } diff --git a/frontend/src/pages/share/[shareId]/index.tsx b/frontend/src/pages/share/[shareId]/index.tsx index 8ec90827..9ca4a1ee 100644 --- a/frontend/src/pages/share/[shareId]/index.tsx +++ b/frontend/src/pages/share/[shareId]/index.tsx @@ -37,8 +37,10 @@ const Share = ({ shareId }: { shareId: string }) => { modals, t("share.error.visitor-limit-exceeded.title"), t("share.error.visitor-limit-exceeded.description"), - "go-home", + "go-home" ); + } else if (error == "share_password_required") { + showEnterPasswordModal(modals, getShareToken); } else { toast.axiosError(e); } @@ -59,21 +61,21 @@ const Share = ({ shareId }: { shareId: string }) => { modals, t("share.error.removed.title"), e.response.data.message, - "go-home", + "go-home" ); } else { showErrorModal( modals, t("share.error.not-found.title"), t("share.error.not-found.description"), - "go-home", + "go-home" ); } } else if (e.response.status == 403 && error == "private_share") { showErrorModal( modals, t("share.error.access-denied.title"), - t("share.error.access-denied.description"), + t("share.error.access-denied.description") ); } else if (error == "share_password_required") { showEnterPasswordModal(modals, getShareToken); @@ -84,7 +86,7 @@ const Share = ({ shareId }: { shareId: string }) => { modals, t("common.error"), t("common.error.unknown"), - "go-home", + "go-home" ); } });