feat(server,web,mobile): Add optional password option for share links. (#4655)

* feat(server,web,mobile): Add optional password option for share links.

Signed-off-by: jarvis2f <137974272+jarvis2f@users.noreply.github.com>

* feat(server,web): Update shared-link.controller and page.svelte for improved cookie handling and metadata updates.

Signed-off-by: jarvis2f <137974272+jarvis2f@users.noreply.github.com>

---------

Signed-off-by: jarvis2f <137974272+jarvis2f@users.noreply.github.com>
This commit is contained in:
jarvis2f 2023-10-29 09:35:38 +08:00 committed by GitHub
parent b34cbd881a
commit 8a6889529c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 556 additions and 41 deletions

View file

@ -1,11 +1,11 @@
import { AssetEntity, SharedLinkEntity, SharedLinkType } from '@app/infra/entities';
import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common';
import { BadRequestException, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
import { AccessCore, Permission } from '../access';
import { AssetIdErrorReason, AssetIdsDto, AssetIdsResponseDto } from '../asset';
import { AuthUserDto } from '../auth';
import { IAccessRepository, ICryptoRepository, ISharedLinkRepository } from '../repositories';
import { SharedLinkResponseDto, mapSharedLink, mapSharedLinkWithoutMetadata } from './shared-link-response.dto';
import { SharedLinkCreateDto, SharedLinkEditDto } from './shared-link.dto';
import { SharedLinkCreateDto, SharedLinkEditDto, SharedLinkPasswordDto } from './shared-link.dto';
@Injectable()
export class SharedLinkService {
@ -23,7 +23,7 @@ export class SharedLinkService {
return this.repository.getAll(authUser.id).then((links) => links.map(mapSharedLink));
}
async getMine(authUser: AuthUserDto): Promise<SharedLinkResponseDto> {
async getMine(authUser: AuthUserDto, dto: SharedLinkPasswordDto): Promise<SharedLinkResponseDto> {
const { sharedLinkId: id, isPublicUser, isShowMetadata: isShowExif } = authUser;
if (!isPublicUser || !id) {
@ -32,7 +32,15 @@ export class SharedLinkService {
const sharedLink = await this.findOrFail(authUser, id);
return this.map(sharedLink, { withExif: isShowExif ?? true });
let newToken;
if (sharedLink.password) {
newToken = this.validateAndRefreshToken(sharedLink, dto);
}
return {
...this.map(sharedLink, { withExif: isShowExif ?? true }),
token: newToken,
};
}
async get(authUser: AuthUserDto, id: string): Promise<SharedLinkResponseDto> {
@ -66,6 +74,7 @@ export class SharedLinkService {
albumId: dto.albumId || null,
assets: (dto.assetIds || []).map((id) => ({ id }) as AssetEntity),
description: dto.description || null,
password: dto.password,
expiresAt: dto.expiresAt || null,
allowUpload: dto.allowUpload ?? true,
allowDownload: dto.allowDownload ?? true,
@ -81,6 +90,7 @@ export class SharedLinkService {
id,
userId: authUser.id,
description: dto.description,
password: dto.password,
expiresAt: dto.changeExpiryTime && !dto.expiresAt ? null : dto.expiresAt,
allowUpload: dto.allowUpload,
allowDownload: dto.allowDownload,
@ -159,4 +169,17 @@ export class SharedLinkService {
private map(sharedLink: SharedLinkEntity, { withExif }: { withExif: boolean }) {
return withExif ? mapSharedLink(sharedLink) : mapSharedLinkWithoutMetadata(sharedLink);
}
private validateAndRefreshToken(sharedLink: SharedLinkEntity, dto: SharedLinkPasswordDto): string {
const token = this.cryptoRepository.hashSha256(`${sharedLink.id}-${sharedLink.password}`);
const sharedLinkTokens = dto.token?.split(',') || [];
if (sharedLink.password !== dto.password && !sharedLinkTokens.includes(token)) {
throw new UnauthorizedException('Invalid password');
}
if (!sharedLinkTokens.includes(token)) {
sharedLinkTokens.push(token);
}
return sharedLinkTokens.join(',');
}
}