1
0
mirror of https://github.com/stonith404/pingvin-share.git synced 2024-07-04 08:20:13 +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")) if (!this.config.get("ALLOW_REGISTRATION"))
throw new ForbiddenException("Registration is not allowed"); throw new ForbiddenException("Registration is not allowed");
const result = await this.authService.signUp(dto); const result = await this.authService.signUp(dto);
response = this.addTokensToResponse( response = this.addTokensToResponse(

View File

@ -12,8 +12,6 @@ import {
import { SkipThrottle } from "@nestjs/throttler"; import { SkipThrottle } from "@nestjs/throttler";
import * as contentDisposition from "content-disposition"; import * as contentDisposition from "content-disposition";
import { Response } from "express"; 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 { CreateShareGuard } from "src/share/guard/createShare.guard";
import { ShareOwnerGuard } from "src/share/guard/shareOwner.guard"; import { ShareOwnerGuard } from "src/share/guard/shareOwner.guard";
import { ShareSecurityGuard } from "src/share/guard/shareSecurity.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") @Get("zip")
@UseGuards(FileDownloadGuard) @UseGuards(ShareSecurityGuard)
async getZip( async getZip(
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
@Param("shareId") shareId: string @Param("shareId") shareId: string
@ -82,7 +58,7 @@ export class FileController {
} }
@Get(":fileId") @Get(":fileId")
@UseGuards(FileDownloadGuard) @UseGuards(ShareSecurityGuard)
async getFile( async getFile(
@Res({ passthrough: true }) res: Response, @Res({ passthrough: true }) res: Response,
@Param("shareId") shareId: string, @Param("shareId") shareId: string,

View File

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

View File

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

View File

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