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"))
|
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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
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 },
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
Loading…
Add table
Reference in a new issue