* feat(reverse-share): optional simplified interface for reverse sharing. issue #155. * chore: Remove useless form validation. * feat: Share Ready modal adds a prompt that an email has been sent to the reverse share creator. * fix: Simplified reverse shared interface elements lack spacing when not logged in. * fix: Share Ready modal prompt contrast is too low in dark mode. * feat: add public access options to reverse share. * feat: remember reverse share simplified and publicAccess options in cookies. * style: npm run format. * chore(i18n): Improve translation. Co-authored-by: Elias Schneider <login@eliasschneider.com> Update frontend/src/i18n/translations/en-US.ts Co-authored-by: Elias Schneider <login@eliasschneider.com> Update frontend/src/i18n/translations/en-US.ts Co-authored-by: Elias Schneider <login@eliasschneider.com> chore(i18n): Improve translation. * chore: Improved variable naming. * chore(i18n): Improve translation. x2. * fix(backend/shares): Misjudged the permission of the share of the reverse share.
173 lines
4.9 KiB
TypeScript
173 lines
4.9 KiB
TypeScript
import {
|
|
Body,
|
|
Controller,
|
|
Delete,
|
|
Get,
|
|
HttpCode,
|
|
Param,
|
|
Post,
|
|
Req,
|
|
Res,
|
|
UseGuards,
|
|
} from "@nestjs/common";
|
|
import { JwtService } from "@nestjs/jwt";
|
|
import { Throttle } from "@nestjs/throttler";
|
|
import { User } from "@prisma/client";
|
|
import { Request, Response } from "express";
|
|
import * as moment from "moment";
|
|
import { GetUser } from "src/auth/decorator/getUser.decorator";
|
|
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
|
|
import { JwtGuard } from "src/auth/guard/jwt.guard";
|
|
import { AdminShareDTO } from "./dto/adminShare.dto";
|
|
import { CreateShareDTO } from "./dto/createShare.dto";
|
|
import { MyShareDTO } from "./dto/myShare.dto";
|
|
import { ShareDTO } from "./dto/share.dto";
|
|
import { ShareMetaDataDTO } from "./dto/shareMetaData.dto";
|
|
import { SharePasswordDto } from "./dto/sharePassword.dto";
|
|
import { CreateShareGuard } from "./guard/createShare.guard";
|
|
import { ShareOwnerGuard } from "./guard/shareOwner.guard";
|
|
import { ShareSecurityGuard } from "./guard/shareSecurity.guard";
|
|
import { ShareTokenSecurity } from "./guard/shareTokenSecurity.guard";
|
|
import { ShareService } from "./share.service";
|
|
import { CompletedShareDTO } from "./dto/shareComplete.dto";
|
|
@Controller("shares")
|
|
export class ShareController {
|
|
constructor(
|
|
private shareService: ShareService,
|
|
private jwtService: JwtService,
|
|
) {}
|
|
|
|
@Get("all")
|
|
@UseGuards(JwtGuard, AdministratorGuard)
|
|
async getAllShares() {
|
|
return new AdminShareDTO().fromList(await this.shareService.getShares());
|
|
}
|
|
|
|
@Get()
|
|
@UseGuards(JwtGuard)
|
|
async getMyShares(@GetUser() user: User) {
|
|
return new MyShareDTO().fromList(
|
|
await this.shareService.getSharesByUser(user.id),
|
|
);
|
|
}
|
|
|
|
@Get(":id")
|
|
@UseGuards(ShareSecurityGuard)
|
|
async get(@Param("id") id: string) {
|
|
return new ShareDTO().from(await this.shareService.get(id));
|
|
}
|
|
|
|
@Get(":id/from-owner")
|
|
@UseGuards(ShareOwnerGuard)
|
|
async getFromOwner(@Param("id") id: string) {
|
|
return new ShareDTO().from(await this.shareService.get(id));
|
|
}
|
|
|
|
@Get(":id/metaData")
|
|
@UseGuards(ShareSecurityGuard)
|
|
async getMetaData(@Param("id") id: string) {
|
|
return new ShareMetaDataDTO().from(await this.shareService.getMetaData(id));
|
|
}
|
|
|
|
@Post()
|
|
@UseGuards(CreateShareGuard)
|
|
async create(
|
|
@Body() body: CreateShareDTO,
|
|
@Req() request: Request,
|
|
@GetUser() user: User,
|
|
) {
|
|
const { reverse_share_token } = request.cookies;
|
|
return new ShareDTO().from(
|
|
await this.shareService.create(body, user, reverse_share_token),
|
|
);
|
|
}
|
|
|
|
@Post(":id/complete")
|
|
@HttpCode(202)
|
|
@UseGuards(CreateShareGuard, ShareOwnerGuard)
|
|
async complete(@Param("id") id: string, @Req() request: Request) {
|
|
const { reverse_share_token } = request.cookies;
|
|
return new CompletedShareDTO().from(
|
|
await this.shareService.complete(id, reverse_share_token),
|
|
);
|
|
}
|
|
|
|
@Delete(":id/complete")
|
|
@UseGuards(ShareOwnerGuard)
|
|
async revertComplete(@Param("id") id: string) {
|
|
return new ShareDTO().from(await this.shareService.revertComplete(id));
|
|
}
|
|
|
|
@Delete(":id")
|
|
@UseGuards(ShareOwnerGuard)
|
|
async remove(@Param("id") id: string, @GetUser() user: User) {
|
|
const isDeleterAdmin = user?.isAdmin === true;
|
|
await this.shareService.remove(id, isDeleterAdmin);
|
|
}
|
|
|
|
@Throttle({
|
|
default: {
|
|
limit: 10,
|
|
ttl: 60,
|
|
},
|
|
})
|
|
@Get("isShareIdAvailable/:id")
|
|
async isShareIdAvailable(@Param("id") id: string) {
|
|
return this.shareService.isShareIdAvailable(id);
|
|
}
|
|
|
|
@HttpCode(200)
|
|
@Throttle({
|
|
default: {
|
|
limit: 20,
|
|
ttl: 5 * 60,
|
|
},
|
|
})
|
|
@UseGuards(ShareTokenSecurity)
|
|
@Post(":id/token")
|
|
async getShareToken(
|
|
@Param("id") id: string,
|
|
@Req() request: Request,
|
|
@Res({ passthrough: true }) response: Response,
|
|
@Body() body: SharePasswordDto,
|
|
) {
|
|
const token = await this.shareService.getShareToken(id, body.password);
|
|
|
|
this.clearShareTokenCookies(request, response);
|
|
response.cookie(`share_${id}_token`, token, {
|
|
path: "/",
|
|
httpOnly: true,
|
|
});
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|