1
0
mirror of https://github.com/stonith404/pingvin-share.git synced 2024-06-30 06:30:11 +02:00

refactor: use cookie instead of local storage for share token

This commit is contained in:
Elias Schneider 2023-01-26 21:18:22 +01:00
parent b98fe7911f
commit 0a2b7b1243
No known key found for this signature in database
GPG Key ID: 07E623B294202B6C
8 changed files with 28 additions and 114 deletions

View File

@ -42,6 +42,7 @@ export class AuthController {
) {
if (!this.config.get("ALLOW_REGISTRATION"))
throw new ForbiddenException("Registration is not allowed");
const result = await this.authService.signUp(dto);
response = this.addTokensToResponse(

View File

@ -12,8 +12,6 @@ import {
import { SkipThrottle } from "@nestjs/throttler";
import * as contentDisposition from "content-disposition";
import { Response } from "express";
import { JwtGuard } from "src/auth/guard/jwt.guard";
import { FileDownloadGuard } from "src/file/guard/fileDownload.guard";
import { CreateShareGuard } from "src/share/guard/createShare.guard";
import { ShareOwnerGuard } from "src/share/guard/shareOwner.guard";
import { ShareSecurityGuard } from "src/share/guard/shareSecurity.guard";
@ -44,30 +42,8 @@ export class FileController {
);
}
@Get(":fileId/download")
@UseGuards(ShareSecurityGuard)
async getFileDownloadUrl(
@Param("shareId") shareId: string,
@Param("fileId") fileId: string
) {
const url = this.fileService.getFileDownloadUrl(shareId, fileId);
return { url };
}
@Get("zip/download")
@UseGuards(ShareSecurityGuard)
async getZipArchiveDownloadURL(
@Param("shareId") shareId: string,
@Param("fileId") fileId: string
) {
const url = this.fileService.getFileDownloadUrl(shareId, fileId);
return { url };
}
@Get("zip")
@UseGuards(FileDownloadGuard)
@UseGuards(ShareSecurityGuard)
async getZip(
@Res({ passthrough: true }) res: Response,
@Param("shareId") shareId: string
@ -82,7 +58,7 @@ export class FileController {
}
@Get(":fileId")
@UseGuards(FileDownloadGuard)
@UseGuards(ShareSecurityGuard)
async getFile(
@Res({ passthrough: true }) res: Response,
@Param("shareId") shareId: string,

View File

@ -136,37 +136,5 @@ export class FileService {
return fs.createReadStream(`./data/uploads/shares/${shareId}/archive.zip`);
}
getFileDownloadUrl(shareId: string, fileId: string) {
const downloadToken = this.generateFileDownloadToken(shareId, fileId);
return `${this.config.get(
"APP_URL"
)}/api/shares/${shareId}/files/${fileId}?token=${downloadToken}`;
}
generateFileDownloadToken(shareId: string, fileId: string) {
if (fileId == "zip") fileId = undefined;
return this.jwtService.sign(
{
shareId,
fileId,
},
{
expiresIn: "10min",
secret: this.config.get("JWT_SECRET"),
}
);
}
verifyFileDownloadToken(shareId: string, token: string) {
try {
const claims = this.jwtService.verify(token, {
secret: this.config.get("JWT_SECRET"),
});
return claims.shareId == shareId;
} catch {
return false;
}
}
}

View File

@ -1,17 +0,0 @@
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Request } from "express";
import { FileService } from "src/file/file.service";
@Injectable()
export class FileDownloadGuard implements CanActivate {
constructor(private fileService: FileService) {}
async canActivate(context: ExecutionContext) {
const request: Request = context.switchToHttp().getRequest();
const token = request.query.token as string;
const { shareId } = request.params;
return this.fileService.verifyFileDownloadToken(shareId, token);
}
}

View File

@ -5,7 +5,6 @@ import {
Injectable,
NotFoundException,
} from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Request } from "express";
import * as moment from "moment";
import { PrismaService } from "src/prisma/prisma.service";
@ -14,14 +13,13 @@ import { ShareService } from "src/share/share.service";
@Injectable()
export class ShareSecurityGuard implements CanActivate {
constructor(
private reflector: Reflector,
private shareService: ShareService,
private prisma: PrismaService
) {}
async canActivate(context: ExecutionContext) {
const request: Request = context.switchToHttp().getRequest();
const shareToken = request.get("X-Share-Token");
const shareId = Object.prototype.hasOwnProperty.call(
request.params,
"shareId"
@ -29,6 +27,8 @@ export class ShareSecurityGuard implements CanActivate {
? request.params.shareId
: request.params.id;
const shareToken = request.cookies[`share_${shareId}_token`];
const share = await this.prisma.share.findUnique({
where: { id: shareId },
include: { security: true },

View File

@ -7,14 +7,14 @@ import {
Param,
Post,
Req,
Res,
UseGuards,
} from "@nestjs/common";
import { Throttle } from "@nestjs/throttler";
import { User } from "@prisma/client";
import { Request } from "express";
import { Request, Response } from "express";
import { GetUser } from "src/auth/decorator/getUser.decorator";
import { JwtGuard } from "src/auth/guard/jwt.guard";
import { ConfigService } from "src/config/config.service";
import { CreateShareDTO } from "./dto/createShare.dto";
import { MyShareDTO } from "./dto/myShare.dto";
import { ShareDTO } from "./dto/share.dto";
@ -27,10 +27,7 @@ import { ShareTokenSecurity } from "./guard/shareTokenSecurity.guard";
import { ShareService } from "./share.service";
@Controller("shares")
export class ShareController {
constructor(
private shareService: ShareService,
private config: ConfigService
) {}
constructor(private shareService: ShareService) {}
@Get()
@UseGuards(JwtGuard)
@ -88,10 +85,20 @@ export class ShareController {
}
@HttpCode(200)
@Throttle(10, 5 * 60)
@Throttle(20, 5 * 60)
@UseGuards(ShareTokenSecurity)
@Post(":id/token")
async getShareToken(@Param("id") id: string, @Body() body: SharePasswordDto) {
return this.shareService.getShareToken(id, body.password);
async getShareToken(
@Param("id") id: string,
@Res({ passthrough: true }) response: Response,
@Body() body: SharePasswordDto
) {
const token = await this.shareService.getShareToken(id, body.password);
response.cookie(`share_${id}_token`, token, {
path: "/",
httpOnly: true,
});
return { token };
}
}

View File

@ -278,7 +278,7 @@ export class ShareService {
const token = await this.generateShareToken(shareId);
await this.increaseViewCount(share);
return { token };
return token;
}
async generateShareToken(shareId: string) {

View File

@ -27,21 +27,11 @@ const completeShare = async (id: string) => {
};
const get = async (id: string): Promise<Share> => {
const shareToken = sessionStorage.getItem(`share_${id}_token`);
return (
await api.get(`shares/${id}`, {
headers: { "X-Share-Token": shareToken ?? "" },
})
).data;
return (await api.get(`shares/${id}`)).data;
};
const getMetaData = async (id: string): Promise<ShareMetaData> => {
const shareToken = sessionStorage.getItem(`share_${id}_token`);
return (
await api.get(`shares/${id}/metaData`, {
headers: { "X-Share-Token": shareToken ?? "" },
})
).data;
return (await api.get(`shares/${id}/metaData`)).data;
};
const remove = async (id: string) => {
@ -53,26 +43,15 @@ const getMyShares = async (): Promise<MyShare[]> => {
};
const getShareToken = async (id: string, password?: string) => {
const { token } = (await api.post(`/shares/${id}/token`, { password })).data;
sessionStorage.setItem(`share_${id}_token`, token);
await api.post(`/shares/${id}/token`, { password });
};
const isShareIdAvailable = async (id: string): Promise<boolean> => {
return (await api.get(`shares/isShareIdAvailable/${id}`)).data.isAvailable;
};
const getFileDownloadUrl = async (shareId: string, fileId: string) => {
const shareToken = sessionStorage.getItem(`share_${shareId}_token`);
return (
await api.get(`shares/${shareId}/files/${fileId}/download`, {
headers: { "X-Share-Token": shareToken ?? "" },
})
).data.url;
};
const downloadFile = async (shareId: string, fileId: string) => {
window.location.href = await getFileDownloadUrl(shareId, fileId);
window.location.href = `${window.location.origin}/api/shares/${shareId}/files/${fileId}`;
};
const uploadFile = async (