0
Fork 0
mirror of https://github.com/stonith404/pingvin-share.git synced 2025-02-19 01:55:48 -05:00

fix: store only 10 share tokens in the cookies and clear the expired ones

This commit is contained in:
Elias Schneider 2024-07-16 19:17:53 +02:00
parent 414bcecbb5
commit e5a0c649e3
No known key found for this signature in database
GPG key ID: 07E623B294202B6C
2 changed files with 53 additions and 11 deletions

View file

@ -10,9 +10,11 @@ import {
Res, Res,
UseGuards, UseGuards,
} from "@nestjs/common"; } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Throttle } from "@nestjs/throttler"; import { Throttle } from "@nestjs/throttler";
import { User } from "@prisma/client"; import { User } from "@prisma/client";
import { Request, Response } from "express"; import { Request, Response } from "express";
import * as moment from "moment";
import { GetUser } from "src/auth/decorator/getUser.decorator"; import { GetUser } from "src/auth/decorator/getUser.decorator";
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard"; import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
import { JwtGuard } from "src/auth/guard/jwt.guard"; import { JwtGuard } from "src/auth/guard/jwt.guard";
@ -29,7 +31,10 @@ 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(private shareService: ShareService) {} constructor(
private shareService: ShareService,
private jwtService: JwtService,
) {}
@Get("all") @Get("all")
@UseGuards(JwtGuard, AdministratorGuard) @UseGuards(JwtGuard, AdministratorGuard)
@ -121,10 +126,13 @@ export class ShareController {
@Post(":id/token") @Post(":id/token")
async getShareToken( async getShareToken(
@Param("id") id: string, @Param("id") id: string,
@Req() request: Request,
@Res({ passthrough: true }) response: Response, @Res({ passthrough: true }) response: Response,
@Body() body: SharePasswordDto, @Body() body: SharePasswordDto,
) { ) {
const token = await this.shareService.getShareToken(id, body.password); const token = await this.shareService.getShareToken(id, body.password);
this.clearShareTokenCookies(request, response);
response.cookie(`share_${id}_token`, token, { response.cookie(`share_${id}_token`, token, {
path: "/", path: "/",
httpOnly: true, httpOnly: true,
@ -132,4 +140,32 @@ export class ShareController {
return { token }; return { token };
} }
/**
* Keeps the 10 most recent share token cookies and deletes the rest and all expired ones
*/
private clearShareTokenCookies(request: Request, response: Response) {
const shareTokenCookies = Object.entries(request.cookies)
.filter(([key]) => key.startsWith("share_") && key.endsWith("_token"))
.map(([key, value]) => ({
key,
payload: this.jwtService.decode(value),
}));
const expiredTokens = shareTokenCookies.filter(
(cookie) => cookie.payload.exp < moment().unix(),
);
const validTokens = shareTokenCookies.filter(
(cookie) => cookie.payload.exp >= moment().unix(),
);
expiredTokens.forEach((cookie) => response.clearCookie(cookie.key));
if (validTokens.length > 10) {
validTokens
.sort((a, b) => a.payload.exp - b.payload.exp)
.slice(0, -10)
.forEach((cookie) => response.clearCookie(cookie.key));
}
}
} }

View file

@ -4,7 +4,7 @@ import {
Injectable, Injectable,
NotFoundException, NotFoundException,
} from "@nestjs/common"; } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt"; import { JwtService, JwtSignOptions } from "@nestjs/jwt";
import { Share, User } from "@prisma/client"; import { Share, User } from "@prisma/client";
import * as archiver from "archiver"; import * as archiver from "archiver";
import * as argon from "argon2"; import * as argon from "argon2";
@ -328,15 +328,21 @@ export class ShareService {
const { expiration } = await this.prisma.share.findUnique({ const { expiration } = await this.prisma.share.findUnique({
where: { id: shareId }, where: { id: shareId },
}); });
return this.jwtService.sign(
{ const tokenPayload = {
shareId, shareId,
}, iat: moment().unix(),
{ };
expiresIn: moment(expiration).diff(new Date(), "seconds") + "s",
secret: this.config.get("internal.jwtSecret"), const tokenOptions: JwtSignOptions = {
}, secret: this.config.get("internal.jwtSecret"),
); };
if (!moment(expiration).isSame(0)) {
tokenOptions.expiresIn = moment(expiration).diff(new Date(), "seconds");
}
return this.jwtService.sign(tokenPayload, tokenOptions);
} }
async verifyShareToken(shareId: string, token: string) { async verifyShareToken(shareId: string, token: string) {