mirror of
https://github.com/stonith404/pingvin-share.git
synced 2025-01-15 01:14:27 -05:00
refactor: use cookie instead of local storage for share token
This commit is contained in:
parent
b98fe7911f
commit
0a2b7b1243
8 changed files with 28 additions and 114 deletions
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 },
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 (
|
||||
|
|
Loading…
Add table
Reference in a new issue