1
0
mirror of https://github.com/xuthus83/pigallery2.git synced 2025-01-14 14:43:17 +08:00

fixing folder navigation error

This commit is contained in:
Patrik J. Braun 2019-02-22 23:39:01 +01:00
parent 06dd15a0ab
commit 6ce8e081ab
21 changed files with 283 additions and 163 deletions

View File

@ -4,23 +4,13 @@ import {ObjectManagers} from '../model/ObjectManagers';
import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; import {ErrorCodes, ErrorDTO} from '../../common/entities/Error';
import {Config} from '../../common/config/private/Config'; import {Config} from '../../common/config/private/Config';
import {QueryParams} from '../../common/QueryParams'; import {QueryParams} from '../../common/QueryParams';
import * as path from 'path';
const LOG_TAG = '[SharingMWs]'; const LOG_TAG = '[SharingMWs]';
export class SharingMWs { export class SharingMWs {
private static generateKey(): string {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4();
}
public static async getSharing(req: Request, res: Response, next: NextFunction) { public static async getSharing(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Sharing.enabled === false) { if (Config.Client.Sharing.enabled === false) {
return next(); return next();
@ -59,7 +49,7 @@ export class SharingMWs {
} }
const directoryName = req.params.directory || '/'; const directoryName = path.normalize(req.params.directory || '/');
const sharing: SharingDTO = { const sharing: SharingDTO = {
id: null, id: null,
sharingKey: sharingKey, sharingKey: sharingKey,
@ -90,7 +80,7 @@ export class SharingMWs {
return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing')); return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing'));
} }
const updateSharing: CreateSharingDTO = req.body.updateSharing; const updateSharing: CreateSharingDTO = req.body.updateSharing;
const directoryName = req.params.directory || '/'; const directoryName = path.normalize(req.params.directory || '/');
const sharing: SharingDTO = { const sharing: SharingDTO = {
id: updateSharing.id, id: updateSharing.id,
path: directoryName, path: directoryName,
@ -110,4 +100,14 @@ export class SharingMWs {
} }
} }
private static generateKey(): string {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return s4() + s4();
}
} }

View File

@ -85,7 +85,7 @@ export class ThumbnailGeneratorMWs {
} }
// load parameters // load parameters
const mediaPath = path.resolve(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name); const mediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name);
const size: number = Config.Client.Thumbnail.personThumbnailSize; const size: number = Config.Client.Thumbnail.personThumbnailSize;
const personName = photo.metadata.faces[0].name; const personName = photo.metadata.faces[0].name;
// generate thumbnail path // generate thumbnail path

View File

@ -7,6 +7,7 @@ import {Config} from '../../../common/config/private/Config';
import {PasswordHelper} from '../../model/PasswordHelper'; import {PasswordHelper} from '../../model/PasswordHelper';
import {Utils} from '../../../common/Utils'; import {Utils} from '../../../common/Utils';
import {QueryParams} from '../../../common/QueryParams'; import {QueryParams} from '../../../common/QueryParams';
import * as path from 'path';
export class AuthenticationMWs { export class AuthenticationMWs {
@ -54,21 +55,30 @@ export class AuthenticationMWs {
return next(); return next();
} }
public static authoriseDirectory(req: Request, res: Response, next: NextFunction) {
if (req.session.user.permissions == null ||
req.session.user.permissions.length === 0 ||
req.session.user.permissions[0] === '/*') {
return next();
}
const directoryName = req.params.directory || '/'; public static normalizePathParam(paramName: string) {
if (UserDTO.isPathAvailable(directoryName, req.session.user.permissions) === true) { return (req: Request, res: Response, next: NextFunction) => {
req.params[paramName] = path.normalize(req.params[paramName] || path.sep).replace(/^(\.\.[\/\\])+/, '');
return next(); return next();
} };
return next(new ErrorDTO(ErrorCodes.PERMISSION_DENIED));
} }
public static authorisePath(paramName: string, isDirectory: boolean) {
return (req: Request, res: Response, next: NextFunction) => {
let p: string = req.params[paramName];
if (!isDirectory) {
p = path.dirname(p);
}
if (!UserDTO.isDirectoryPathAvailable(p, req.session.user.permissions, path.sep)) {
return res.sendStatus(403);
}
return next();
};
}
public static authorise(role: UserRoles) { public static authorise(role: UserRoles) {
return (req: Request, res: Response, next: NextFunction) => { return (req: Request, res: Response, next: NextFunction) => {
if (req.session.user.role < role) { if (req.session.user.role < role) {
@ -102,12 +112,12 @@ export class AuthenticationMWs {
return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND)); return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND));
} }
let path = sharing.path; let sharingPath = sharing.path;
if (sharing.includeSubfolders === true) { if (sharing.includeSubfolders === true) {
path += '*'; sharingPath += '*';
} }
req.session.user = <UserDTO>{name: 'Guest', role: UserRoles.LimitedGuest, permissions: [path]}; req.session.user = <UserDTO>{name: 'Guest', role: UserRoles.LimitedGuest, permissions: [sharingPath]};
return next(); return next();
} catch (err) { } catch (err) {
@ -152,6 +162,12 @@ export class AuthenticationMWs {
} }
public static logout(req: Request, res: Response, next: NextFunction) {
delete req.session.user;
delete req.session.rememberMe;
return next();
}
private static async getSharingUser(req: Request) { private static async getSharingUser(req: Request) {
if (Config.Client.Sharing.enabled === true && if (Config.Client.Sharing.enabled === true &&
(!!req.params[QueryParams.gallery.sharingKey_short] || !!req.params[QueryParams.gallery.sharingKey_long])) { (!!req.params[QueryParams.gallery.sharingKey_short] || !!req.params[QueryParams.gallery.sharingKey_long])) {
@ -166,20 +182,19 @@ export class AuthenticationMWs {
return null; return null;
} }
let path = sharing.path; let sharingPath = sharing.path;
if (sharing.includeSubfolders === true) { if (sharing.includeSubfolders === true) {
path += '*'; sharingPath += '*';
} }
return <UserDTO>{name: 'Guest', role: UserRoles.LimitedGuest, permissions: [path]}; return <UserDTO>{
name: 'Guest',
role: UserRoles.LimitedGuest,
permissions: [sharingPath],
usedSharingKey: sharing.sharingKey
};
} }
return null; return null;
} }
public static logout(req: Request, res: Response, next: NextFunction) {
delete req.session.user;
delete req.session.rememberMe;
return next();
}
} }

View File

@ -11,7 +11,7 @@ export class Localizations {
public static init() { public static init() {
const notLanguage = ['assets']; const notLanguage = ['assets'];
const dirCont = fs.readdirSync(ProjectPath.FrontendFolder) const dirCont = fs.readdirSync(ProjectPath.FrontendFolder)
.filter(f => fs.statSync(path.resolve(ProjectPath.FrontendFolder, f)).isDirectory()); .filter(f => fs.statSync(path.join(ProjectPath.FrontendFolder, f)).isDirectory());
Config.Client.languages = dirCont.filter(d => notLanguage.indexOf(d) === -1); Config.Client.languages = dirCont.filter(d => notLanguage.indexOf(d) === -1);
Config.Client.languages.push('en'); Config.Client.languages.push('en');
} }

View File

@ -257,7 +257,6 @@ export class SearchManager implements ISearchManager {
const rawAndEntities = await query.orderBy('media.id').getRawAndEntities(); const rawAndEntities = await query.orderBy('media.id').getRawAndEntities();
const media: MediaEntity[] = rawAndEntities.entities; const media: MediaEntity[] = rawAndEntities.entities;
// console.log(rawAndEntities.raw);
let rawIndex = 0; let rawIndex = 0;
for (let i = 0; i < media.length; i++) { for (let i = 0; i < media.length; i++) {
if (rawAndEntities.raw[rawIndex].faces_id === null || if (rawAndEntities.raw[rawIndex].faces_id === null ||

View File

@ -69,7 +69,7 @@ export class DiskMangerWorker {
try { try {
for (let i = 0; i < list.length; i++) { for (let i = 0; i < list.length; i++) {
const file = list[i]; const file = list[i];
const fullFilePath = path.normalize(path.resolve(absoluteDirectoryName, file)); const fullFilePath = path.normalize(path.join(absoluteDirectoryName, file));
if (fs.statSync(fullFilePath).isDirectory()) { if (fs.statSync(fullFilePath).isDirectory()) {
if (photosOnly === true) { if (photosOnly === true) {
continue; continue;

View File

@ -1,5 +1,5 @@
import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs';
import {Express, NextFunction, Request, Response} from 'express'; import {Express} from 'express';
import {GalleryMWs} from '../middlewares/GalleryMWs'; import {GalleryMWs} from '../middlewares/GalleryMWs';
import {RenderingMWs} from '../middlewares/RenderingMWs'; import {RenderingMWs} from '../middlewares/RenderingMWs';
import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs';
@ -28,7 +28,8 @@ export class GalleryRouter {
private static addDirectoryList(app: Express) { private static addDirectoryList(app: Express) {
app.get(['/api/gallery/content/:directory(*)', '/api/gallery/', '/api/gallery//'], app.get(['/api/gallery/content/:directory(*)', '/api/gallery/', '/api/gallery//'],
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
AuthenticationMWs.authoriseDirectory, AuthenticationMWs.normalizePathParam('directory'),
AuthenticationMWs.authorisePath('directory', true),
VersionMWs.injectGalleryVersion, VersionMWs.injectGalleryVersion,
GalleryMWs.listDirectory, GalleryMWs.listDirectory,
ThumbnailGeneratorMWs.addThumbnailInformation, ThumbnailGeneratorMWs.addThumbnailInformation,
@ -41,7 +42,8 @@ export class GalleryRouter {
private static addGetImage(app: Express) { private static addGetImage(app: Express) {
app.get(['/api/gallery/content/:mediaPath(*\.(jpg|jpeg|jpe|webp|png|gif|svg))'], app.get(['/api/gallery/content/:mediaPath(*\.(jpg|jpeg|jpe|webp|png|gif|svg))'],
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
// TODO: authorize path AuthenticationMWs.normalizePathParam('mediaPath'),
AuthenticationMWs.authorisePath('mediaPath', false),
GalleryMWs.loadFile, GalleryMWs.loadFile,
RenderingMWs.renderFile RenderingMWs.renderFile
); );
@ -50,7 +52,8 @@ export class GalleryRouter {
private static addGetVideo(app: Express) { private static addGetVideo(app: Express) {
app.get(['/api/gallery/content/:mediaPath(*\.(mp4|ogg|ogv|webm))'], app.get(['/api/gallery/content/:mediaPath(*\.(mp4|ogg|ogv|webm))'],
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
// TODO: authorize path AuthenticationMWs.normalizePathParam('mediaPath'),
AuthenticationMWs.authorisePath('mediaPath', false),
GalleryMWs.loadFile, GalleryMWs.loadFile,
RenderingMWs.renderFile RenderingMWs.renderFile
); );
@ -59,7 +62,8 @@ export class GalleryRouter {
private static addGetMetaFile(app: Express) { private static addGetMetaFile(app: Express) {
app.get(['/api/gallery/content/:mediaPath(*\.(gpx))'], app.get(['/api/gallery/content/:mediaPath(*\.(gpx))'],
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
// TODO: authorize path AuthenticationMWs.normalizePathParam('mediaPath'),
AuthenticationMWs.authorisePath('mediaPath', false),
GalleryMWs.loadFile, GalleryMWs.loadFile,
RenderingMWs.renderFile RenderingMWs.renderFile
); );
@ -68,8 +72,8 @@ export class GalleryRouter {
private static addRandom(app: Express) { private static addRandom(app: Express) {
app.get(['/api/gallery/random'], app.get(['/api/gallery/random'],
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),
VersionMWs.injectGalleryVersion, VersionMWs.injectGalleryVersion,
// TODO: authorize path
GalleryMWs.getRandomImage, GalleryMWs.getRandomImage,
GalleryMWs.loadFile, GalleryMWs.loadFile,
RenderingMWs.renderFile RenderingMWs.renderFile
@ -79,7 +83,8 @@ export class GalleryRouter {
private static addGetImageThumbnail(app: Express) { private static addGetImageThumbnail(app: Express) {
app.get('/api/gallery/content/:mediaPath(*\.(jpg|jpeg|jpe|webp|png|gif|svg))/thumbnail/:size?', app.get('/api/gallery/content/:mediaPath(*\.(jpg|jpeg|jpe|webp|png|gif|svg))/thumbnail/:size?',
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
// TODO: authorize path AuthenticationMWs.normalizePathParam('mediaPath'),
AuthenticationMWs.authorisePath('mediaPath', false),
GalleryMWs.loadFile, GalleryMWs.loadFile,
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Image), ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Image),
RenderingMWs.renderFile RenderingMWs.renderFile
@ -89,7 +94,8 @@ export class GalleryRouter {
private static addGetVideoThumbnail(app: Express) { private static addGetVideoThumbnail(app: Express) {
app.get('/api/gallery/content/:mediaPath(*\.(mp4|ogg|ogv|webm))/thumbnail/:size?', app.get('/api/gallery/content/:mediaPath(*\.(mp4|ogg|ogv|webm))/thumbnail/:size?',
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
// TODO: authorize path AuthenticationMWs.normalizePathParam('mediaPath'),
AuthenticationMWs.authorisePath('mediaPath', false),
GalleryMWs.loadFile, GalleryMWs.loadFile,
ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video), ThumbnailGeneratorMWs.generateThumbnailFactory(ThumbnailSourceType.Video),
RenderingMWs.renderFile RenderingMWs.renderFile
@ -100,7 +106,8 @@ export class GalleryRouter {
private static addGetVideoIcon(app: Express) { private static addGetVideoIcon(app: Express) {
app.get('/api/gallery/content/:mediaPath(*\.(mp4|ogg|ogv|webm))/icon', app.get('/api/gallery/content/:mediaPath(*\.(mp4|ogg|ogv|webm))/icon',
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
// TODO: authorize path AuthenticationMWs.normalizePathParam('mediaPath'),
AuthenticationMWs.authorisePath('mediaPath', false),
GalleryMWs.loadFile, GalleryMWs.loadFile,
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Video), ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Video),
RenderingMWs.renderFile RenderingMWs.renderFile
@ -110,7 +117,8 @@ export class GalleryRouter {
private static addGetImageIcon(app: Express) { private static addGetImageIcon(app: Express) {
app.get('/api/gallery/content/:mediaPath(*\.(jpg|jpeg|jpe|webp|png|gif|svg))/icon', app.get('/api/gallery/content/:mediaPath(*\.(jpg|jpeg|jpe|webp|png|gif|svg))/icon',
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
// TODO: authorize path AuthenticationMWs.normalizePathParam('mediaPath'),
AuthenticationMWs.authorisePath('mediaPath', false),
GalleryMWs.loadFile, GalleryMWs.loadFile,
ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Image), ThumbnailGeneratorMWs.generateIconFactory(ThumbnailSourceType.Image),
RenderingMWs.renderFile RenderingMWs.renderFile

View File

@ -44,7 +44,7 @@ export class PublicRouter {
}; };
const renderIndex = (req: Request, res: Response, next: Function) => { const renderIndex = (req: Request, res: Response, next: Function) => {
ejs.renderFile(path.resolve(ProjectPath.FrontendFolder, req['localePath'], 'index.html'), ejs.renderFile(path.join(ProjectPath.FrontendFolder, req['localePath'], 'index.html'),
res.tpl, (err, str) => { res.tpl, (err, str) => {
if (err) { if (err) {
return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, err.message)); return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, err.message));
@ -79,7 +79,7 @@ export class PublicRouter {
}); });
app.get(['/', '/login', '/gallery*', '/share*', '/admin', '/duplicates', '/faces', '/search*'], app.get(['/', '/login', '/gallery*', '/share*', '/admin', '/duplicates', '/faces', '/search*'],
AuthenticationMWs.tryAuthenticate, AuthenticationMWs.tryAuthenticate,
setLocale, setLocale,
renderIndex renderIndex
@ -90,28 +90,26 @@ export class PublicRouter {
); );
}); });
const renderFile = (subDir: string = '') => {
return (req: Request, res: Response) => {
const file = path.join(ProjectPath.FrontendFolder, req['localePath'], subDir, req.params.file);
fs.exists(file, (exists: boolean) => {
if (!exists) {
return res.sendStatus(404);
}
res.sendFile(file);
});
};
};
app.get('/assets/:file(*)', app.get('/assets/:file(*)',
setLocale, setLocale,
(req: Request, res: Response) => { AuthenticationMWs.normalizePathParam('file'),
const file = path.resolve(ProjectPath.FrontendFolder, req['localePath'], 'assets', req.params.file); renderFile('assets'));
fs.exists(file, (exists: boolean) => {
if (!exists) {
return res.sendStatus(404);
}
res.sendFile(file);
});
});
app.get('/:file', app.get('/:file',
setLocale, setLocale,
(req: Request, res: Response) => { AuthenticationMWs.normalizePathParam('file'),
const file = path.resolve(ProjectPath.FrontendFolder, req['localePath'], req.params.file); renderFile());
fs.exists(file, (exists: boolean) => {
if (!exists) {
return res.sendStatus(404);
}
res.sendFile(file);
});
});
} }
} }

View File

@ -15,25 +15,26 @@ export interface UserDTO {
name: string; name: string;
password: string; password: string;
role: UserRoles; role: UserRoles;
usedSharingKey?: string;
permissions: string[]; // user can only see these permissions. if ends with *, its recursive permissions: string[]; // user can only see these permissions. if ends with *, its recursive
} }
export module UserDTO { export module UserDTO {
export const isPathAvailable = (path: string, permissions: string[]): boolean => { export const isDirectoryPathAvailable = (path: string, permissions: string[], separator = '/'): boolean => {
if (permissions == null || permissions.length === 0 || permissions[0] === '/*') { if (permissions == null || permissions.length === 0 || permissions[0] === separator + '*') {
return true; return true;
} }
for (let i = 0; i < permissions.length; i++) { for (let i = 0; i < permissions.length; i++) {
let permission = permissions[i]; let permission = permissions[i];
if (permission[permission.length - 1] === '*') { if (permission[permission.length - 1] === '*') {
permission = permission.slice(0, -1); permission = permission.slice(0, -1);
if (path.startsWith(permission)) { if (path.startsWith(permission) && (!path[permission.length] || path[permission.length] === separator)) {
return true; return true;
} }
} else if (path === permission) { } else if (path === permission) {
return true; return true;
} else if (path === '.' && permission === '/') { } else if (path === '.' && permission === separator) {
return true; return true;
} }
@ -42,7 +43,6 @@ export module UserDTO {
}; };
export const isDirectoryAvailable = (directory: DirectoryDTO, permissions: string[]): boolean => { export const isDirectoryAvailable = (directory: DirectoryDTO, permissions: string[]): boolean => {
return isDirectoryPathAvailable(Utils.concatUrls(directory.path, directory.name), permissions);
return isPathAvailable(Utils.concatUrls(directory.path, directory.name), permissions);
}; };
} }

View File

@ -53,7 +53,6 @@ export class GalleryComponent implements OnInit, OnDestroy {
private rndService: SeededRandomService) { private rndService: SeededRandomService) {
this.mapEnabled = Config.Client.Map.enabled; this.mapEnabled = Config.Client.Map.enabled;
this.SearchTypes = SearchTypes; this.SearchTypes = SearchTypes;
PageHelper.showScrollY(); PageHelper.showScrollY();
} }

View File

@ -18,6 +18,9 @@ export class GalleryService {
public content: BehaviorSubject<ContentWrapper>; public content: BehaviorSubject<ContentWrapper>;
public sorting: BehaviorSubject<SortingMethods>; public sorting: BehaviorSubject<SortingMethods>;
lastRequest: { directory: string } = {
directory: null
};
private lastDirectory: DirectoryDTO; private lastDirectory: DirectoryDTO;
private searchId: any; private searchId: any;
private ongoingSearch: { private ongoingSearch: {
@ -38,10 +41,6 @@ export class GalleryService {
this.sorting = new BehaviorSubject<SortingMethods>(Config.Client.Other.defaultPhotoSortingMethod); this.sorting = new BehaviorSubject<SortingMethods>(Config.Client.Other.defaultPhotoSortingMethod);
} }
lastRequest: { directory: string } = {
directory: null
};
setSorting(sorting: SortingMethods): void { setSorting(sorting: SortingMethods): void {
this.sorting.next(sorting); this.sorting.next(sorting);
if (this.content.value.directory) { if (this.content.value.directory) {

View File

@ -70,7 +70,7 @@ export class GalleryNavigatorComponent implements OnChanges {
if (dirs.length === 0) { if (dirs.length === 0) {
arr.push({name: this.RootFolderName, route: null}); arr.push({name: this.RootFolderName, route: null});
} else { } else {
arr.push({name: this.RootFolderName, route: UserDTO.isPathAvailable('/', user.permissions) ? '/' : null}); arr.push({name: this.RootFolderName, route: UserDTO.isDirectoryPathAvailable('/', user.permissions) ? '/' : null});
} }
// create rest navigation // create rest navigation
@ -79,7 +79,7 @@ export class GalleryNavigatorComponent implements OnChanges {
if (dirs.length - 1 === index) { if (dirs.length - 1 === index) {
arr.push({name: name, route: null}); arr.push({name: name, route: null});
} else { } else {
arr.push({name: name, route: UserDTO.isPathAvailable(route, user.permissions) ? route : null}); arr.push({name: name, route: UserDTO.isDirectoryPathAvailable(route, user.permissions) ? route : null});
} }
}); });

View File

@ -2,8 +2,9 @@ import {Injectable} from '@angular/core';
import {NetworkService} from '../model/network/network.service'; import {NetworkService} from '../model/network/network.service';
import {CreateSharingDTO, SharingDTO} from '../../../common/entities/SharingDTO'; import {CreateSharingDTO, SharingDTO} from '../../../common/entities/SharingDTO';
import {Router, RoutesRecognized} from '@angular/router'; import {Router, RoutesRecognized} from '@angular/router';
import {BehaviorSubject} from 'rxjs'; import {BehaviorSubject, Observable} from 'rxjs';
import {QueryParams} from '../../../common/QueryParams'; import {QueryParams} from '../../../common/QueryParams';
import {UserDTO} from '../../../common/entities/UserDTO';
@Injectable() @Injectable()
export class ShareService { export class ShareService {
@ -17,7 +18,8 @@ export class ShareService {
private resolve: () => void; private resolve: () => void;
constructor(private _networkService: NetworkService, private router: Router) { constructor(private networkService: NetworkService,
private router: Router) {
this.sharing = new BehaviorSubject(null); this.sharing = new BehaviorSubject(null);
this.ReadyPR = new Promise((resolve: () => void) => { this.ReadyPR = new Promise((resolve: () => void) => {
if (this.inited === true) { if (this.inited === true) {
@ -30,28 +32,52 @@ export class ShareService {
if (val instanceof RoutesRecognized) { if (val instanceof RoutesRecognized) {
this.param = val.state.root.firstChild.params[QueryParams.gallery.sharingKey_long] || null; this.param = val.state.root.firstChild.params[QueryParams.gallery.sharingKey_long] || null;
this.queryParam = val.state.root.firstChild.queryParams[QueryParams.gallery.sharingKey_short] || null; this.queryParam = val.state.root.firstChild.queryParams[QueryParams.gallery.sharingKey_short] || null;
const changed = this.sharingKey !== this.param || this.queryParam;
const changed = this.sharingKey !== (this.param || this.queryParam);
if (changed) { if (changed) {
this.sharingKey = this.param || this.queryParam; this.sharingKey = this.param || this.queryParam || this.sharingKey;
this.getSharing(); this.getSharing();
} }
if (this.resolve) { if (this.resolve) {
this.resolve(); this.resolve();
this.resolve = null;
this.inited = true; this.inited = true;
} }
} }
}); });
}
public setUserObs(userOB: Observable<UserDTO>) {
userOB.subscribe((user) => {
console.log(user);
if (user && !!user.usedSharingKey) {
if (user.usedSharingKey !== this.sharingKey) {
this.sharingKey = user.usedSharingKey;
this.getSharing();
}
if (this.resolve) {
this.resolve();
this.resolve = null;
this.inited = true;
}
}
});
} }
public wait(): Promise<void> { public wait(): Promise<void> {
if (this.inited) {
return Promise.resolve();
}
return this.ReadyPR; return this.ReadyPR;
} }
public createSharing(dir: string, includeSubfolders: boolean, valid: number): Promise<SharingDTO> { public createSharing(dir: string, includeSubfolders: boolean, valid: number): Promise<SharingDTO> {
return this._networkService.postJson('/share/' + dir, { return this.networkService.postJson('/share/' + dir, {
createSharing: <CreateSharingDTO>{ createSharing: <CreateSharingDTO>{
includeSubfolders: includeSubfolders, includeSubfolders: includeSubfolders,
valid: valid valid: valid
@ -60,7 +86,7 @@ export class ShareService {
} }
public updateSharing(dir: string, sharingId: number, includeSubfolders: boolean, password: string, valid: number): Promise<SharingDTO> { public updateSharing(dir: string, sharingId: number, includeSubfolders: boolean, password: string, valid: number): Promise<SharingDTO> {
return this._networkService.putJson('/share/' + dir, { return this.networkService.putJson('/share/' + dir, {
updateSharing: <CreateSharingDTO>{ updateSharing: <CreateSharingDTO>{
id: sharingId, id: sharingId,
includeSubfolders: includeSubfolders, includeSubfolders: includeSubfolders,
@ -80,7 +106,7 @@ export class ShareService {
} }
public async getSharing(): Promise<SharingDTO> { public async getSharing(): Promise<SharingDTO> {
const sharing = await this._networkService.getJson<SharingDTO>('/share/' + this.getSharingKey()); const sharing = await this.networkService.getJson<SharingDTO>('/share/' + this.getSharingKey());
this.sharing.next(sharing); this.sharing.next(sharing);
return sharing; return sharing;
} }

View File

@ -28,7 +28,7 @@ export class NavigationService {
public async toGallery() { public async toGallery() {
await this._shareService.wait(); await this._shareService.wait();
if (this._shareService.isSharing()) { if (this._shareService.isSharing()) {
return this._router.navigate(['gallery', ''], {queryParams: {sk: this._shareService.getSharingKey()}}); return this._router.navigate(['share', this._shareService.getSharingKey()]);
} else { } else {
return this._router.navigate(['gallery', '']); return this._router.navigate(['gallery', '']);
} }

View File

@ -8,6 +8,7 @@ import {Config} from '../../../../common/config/public/Config';
import {NetworkService} from './network.service'; import {NetworkService} from './network.service';
import {ErrorCodes, ErrorDTO} from '../../../../common/entities/Error'; import {ErrorCodes, ErrorDTO} from '../../../../common/entities/Error';
import {CookieNames} from '../../../../common/CookieNames'; import {CookieNames} from '../../../../common/CookieNames';
import {ShareService} from '../../gallery/share.service';
declare module ServerInject { declare module ServerInject {
export let user: UserDTO; export let user: UserDTO;
@ -19,9 +20,10 @@ export class AuthenticationService {
public user: BehaviorSubject<UserDTO>; public user: BehaviorSubject<UserDTO>;
constructor(private _userService: UserService, constructor(private _userService: UserService,
private _networkService: NetworkService) { private _networkService: NetworkService,
private shareSerice: ShareService) {
this.user = new BehaviorSubject(null); this.user = new BehaviorSubject(null);
this.shareSerice.setUserObs(this.user);
// picking up session.. // picking up session..
if (this.isAuthenticated() === false && Cookie.get(CookieNames.session) != null) { if (this.isAuthenticated() === false && Cookie.get(CookieNames.session) != null) {
if (typeof ServerInject !== 'undefined' && typeof ServerInject.user !== 'undefined') { if (typeof ServerInject !== 'undefined' && typeof ServerInject.user !== 'undefined') {
@ -48,6 +50,34 @@ export class AuthenticationService {
} }
public async login(credential: LoginCredential): Promise<UserDTO> {
const user = await this._userService.login(credential);
this.user.next(user);
return user;
}
public async shareLogin(password: string): Promise<UserDTO> {
const user = await this._userService.shareLogin(password);
this.user.next(user);
return user;
}
public isAuthenticated(): boolean {
if (Config.Client.authenticationRequired === false) {
return true;
}
return !!this.user.value;
}
public isAuthorized(role: UserRoles) {
return this.user.value && this.user.value.role >= role;
}
public async logout() {
await this._userService.logout();
this.user.next(null);
}
private async getSessionUser(): Promise<void> { private async getSessionUser(): Promise<void> {
try { try {
this.user.next(await this._userService.getSessionUser()); this.user.next(await this._userService.getSessionUser());
@ -58,35 +88,4 @@ export class AuthenticationService {
} }
public async login(credential: LoginCredential): Promise<UserDTO> {
const user = await this._userService.login(credential);
this.user.next(user);
return user;
}
public async shareLogin(password: string): Promise<UserDTO> {
const user = await this._userService.shareLogin(password);
this.user.next(user);
return user;
}
public isAuthenticated(): boolean {
if (Config.Client.authenticationRequired === false) {
return true;
}
return !!(this.user.value && this.user.value != null);
}
public isAuthorized(role: UserRoles) {
return this.user.value && this.user.value.role >= role;
}
public logout() {
this._userService.logout();
this.user.next(null);
}
} }

View File

@ -29,7 +29,7 @@ export class UserService {
await this._shareService.wait(); await this._shareService.wait();
if (Config.Client.Sharing.enabled === true) { if (Config.Client.Sharing.enabled === true) {
if (this._shareService.isSharing()) { if (this._shareService.isSharing()) {
return this._networkService.getJson<UserDTO>('/user/login?sk=' + this._shareService.getSharingKey()); return this._networkService.getJson<UserDTO>('/user/login', {sk: this._shareService.getSharingKey()});
} }
} }
return this._networkService.getJson<UserDTO>('/user/login'); return this._networkService.getJson<UserDTO>('/user/login');

20
package-lock.json generated
View File

@ -14438,6 +14438,26 @@
} }
} }
}, },
"tslint-no-unused-expression-chai": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/tslint-no-unused-expression-chai/-/tslint-no-unused-expression-chai-0.1.4.tgz",
"integrity": "sha512-frEWKNTcq7VsaWKgUxMDOB2N/cmQadVkUtUGIut+2K4nv/uFXPfgJyPjuNC/cHyfUVqIkHMAvHOCL+d/McU3nQ==",
"dev": true,
"requires": {
"tsutils": "^3.0.0"
},
"dependencies": {
"tsutils": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.8.0.tgz",
"integrity": "sha512-XQdPhgcoTbCD8baXC38PQ0vpTZ8T3YrE+vR66YIj/xvDt1//8iAhafpIT/4DmvzzC1QFapEImERu48Pa01dIUA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
}
}
},
"tsutils": { "tsutils": {
"version": "2.29.0", "version": "2.29.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",

View File

@ -114,13 +114,13 @@
"run-sequence": "2.2.1", "run-sequence": "2.2.1",
"rxjs": "6.4.0", "rxjs": "6.4.0",
"rxjs-compat": "6.4.0", "rxjs-compat": "6.4.0",
"terser": "3.16.1",
"ts-helpers": "1.1.2", "ts-helpers": "1.1.2",
"ts-node": "8.0.2", "ts-node": "8.0.2",
"tslint": "5.12.1", "tslint": "5.12.1",
"typescript": "3.2.4", "typescript": "3.2.4",
"xlf-google-translate": "1.0.0-beta.13", "xlf-google-translate": "1.0.0-beta.13",
"zone.js": "0.8.29", "zone.js": "0.8.29"
"terser": "3.16.1"
}, },
"//": [ "//": [
"TODO: remove terser version lock once webpack is fixed" "TODO: remove terser version lock once webpack is fixed"

View File

@ -18,8 +18,8 @@ export class SQLTestHelper {
dbPath: string; dbPath: string;
constructor(public dbType: DatabaseType) { constructor(public dbType: DatabaseType) {
this.tempDir = path.resolve(__dirname, './tmp'); this.tempDir = path.join(__dirname, './tmp');
this.dbPath = path.resolve(__dirname, './tmp', 'test.db'); this.dbPath = path.join(__dirname, './tmp', 'test.db');
} }

View File

@ -6,8 +6,13 @@ import {ObjectManagers} from '../../../../../backend/model/ObjectManagers';
import {UserManager} from '../../../../../backend/model/memory/UserManager'; import {UserManager} from '../../../../../backend/model/memory/UserManager';
import {Config} from '../../../../../common/config/private/Config'; import {Config} from '../../../../../common/config/private/Config';
import {IUserManager} from '../../../../../backend/model/interfaces/IUserManager'; import {IUserManager} from '../../../../../backend/model/interfaces/IUserManager';
import * as path from 'path';
declare const describe: any;
declare const it: any;
declare const beforeEach: any;
describe('Authentication middleware', () => { describe('Authentication middleware', () => {
beforeEach(() => { beforeEach(() => {
@ -15,7 +20,7 @@ describe('Authentication middleware', () => {
}); });
describe('authenticate', () => { describe('authenticate', () => {
it('should call next on authenticated', (done) => { it('should call next on authenticated', (done: () => void) => {
const req: any = { const req: any = {
session: { session: {
user: 'A user' user: 'A user'
@ -24,7 +29,7 @@ describe('Authentication middleware', () => {
query: {}, query: {},
params: {} params: {}
}; };
const next: any = (err: ErrorDTO) => { const next = (err: ErrorDTO) => {
expect(err).to.be.undefined; expect(err).to.be.undefined;
done(); done();
}; };
@ -32,7 +37,7 @@ describe('Authentication middleware', () => {
}); });
it('should call next with error on not authenticated', (done) => { it('should call next with error on not authenticated', (done: () => void) => {
const req: any = { const req: any = {
session: {}, session: {},
sessionOptions: {}, sessionOptions: {},
@ -40,8 +45,7 @@ describe('Authentication middleware', () => {
params: {} params: {}
}; };
Config.Client.authenticationRequired = true; Config.Client.authenticationRequired = true;
const res: any = {}; const next = (err: ErrorDTO) => {
const next: any = (err: ErrorDTO) => {
expect(err).not.to.be.undefined; expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.NOT_AUTHENTICATED); expect(err.code).to.be.eql(ErrorCodes.NOT_AUTHENTICATED);
done(); done();
@ -51,15 +55,69 @@ describe('Authentication middleware', () => {
}); });
}); });
describe('authorisePath', () => {
const req = {
session: {
user: {permissions: <string[]>null}
},
sessionOptions: {},
query: {},
params: {
path: '/test'
}
};
const authoriseDirPath = AuthenticationMWs.authorisePath('path', true);
const test = (relativePath: string): Promise<string | number> => {
return new Promise((resolve) => {
req.params.path = path.normalize(relativePath);
authoriseDirPath(<any>req, <any>{sendStatus: resolve}, () => {
resolve('ok');
});
resolve();
});
};
it('should catch unauthorized path usage', async () => {
req.session.user.permissions = [path.normalize('/sub/subsub')];
expect(await test('/sub/subsub')).to.be.eql('ok');
expect(await test('/test')).to.be.eql(403);
expect(await test('/')).to.be.eql(403);
expect(await test('/sub/test')).to.be.eql(403);
expect(await test('/sub/subsub/test')).to.be.eql(403);
expect(await test('/sub/subsub/test/test2')).to.be.eql(403);
req.session.user.permissions = [path.normalize('/sub/subsub'), path.normalize('/sub/subsub2')];
expect(await test('/sub/subsub2')).to.be.eql('ok');
expect(await test('/sub/subsub')).to.be.eql('ok');
expect(await test('/test')).to.be.eql(403);
expect(await test('/')).to.be.eql(403);
expect(await test('/sub/test')).to.be.eql(403);
expect(await test('/sub/subsub/test')).to.be.eql(403);
expect(await test('/sub/subsub2/test')).to.be.eql(403);
req.session.user.permissions = [path.normalize('/sub/subsub*')];
expect(await test('/b')).to.be.eql(403);
expect(await test('/sub')).to.be.eql(403);
expect(await test('/sub/subsub2')).to.be.eql(403);
expect(await test('/sub/subsub2/test')).to.be.eql(403);
expect(await test('/sub/subsub')).to.be.eql('ok');
expect(await test('/sub/subsub/test')).to.be.eql('ok');
expect(await test('/sub/subsub/test/two')).to.be.eql('ok');
});
});
describe('inverseAuthenticate', () => { describe('inverseAuthenticate', () => {
it('should call next with error on authenticated', (done) => { it('should call next with error on authenticated', (done: () => void) => {
const req: any = { const req: any = {
session: {}, session: {},
sessionOptions: {}, sessionOptions: {},
}; };
const res: any = {}; const res: any = {};
const next: any = (err:ErrorDTO) => { const next: any = (err: ErrorDTO) => {
expect(err).to.be.undefined; expect(err).to.be.undefined;
done(); done();
}; };
@ -68,15 +126,14 @@ describe('Authentication middleware', () => {
}); });
it('should call next error on authenticated', (done) => { it('should call next error on authenticated', (done: () => void) => {
const req: any = { const req: any = {
session: { session: {
user: 'A user' user: 'A user'
}, },
sessionOptions: {}, sessionOptions: {},
}; };
const res: any = {}; const next = (err: ErrorDTO) => {
const next: any = (err: ErrorDTO) => {
expect(err).not.to.be.undefined; expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.ALREADY_AUTHENTICATED); expect(err.code).to.be.eql(ErrorCodes.ALREADY_AUTHENTICATED);
done(); done();
@ -87,7 +144,7 @@ describe('Authentication middleware', () => {
}); });
describe('authorise', () => { describe('authorise', () => {
it('should call next on authorised', (done) => { it('should call next on authorised', (done: () => void) => {
const req: any = { const req: any = {
session: { session: {
user: { user: {
@ -96,7 +153,7 @@ describe('Authentication middleware', () => {
}, },
sessionOptions: {} sessionOptions: {}
}; };
const next: any = (err:ErrorDTO) => { const next = (err: ErrorDTO) => {
expect(err).to.be.undefined; expect(err).to.be.undefined;
done(); done();
}; };
@ -104,7 +161,7 @@ describe('Authentication middleware', () => {
}); });
it('should call next with error on not authorised', (done) => { it('should call next with error on not authorised', (done: () => void) => {
const req: any = { const req: any = {
session: { session: {
user: { user: {
@ -113,7 +170,7 @@ describe('Authentication middleware', () => {
}, },
sessionOptions: {} sessionOptions: {}
}; };
const next: any = (err: ErrorDTO) => { const next = (err: ErrorDTO) => {
expect(err).not.to.be.undefined; expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.NOT_AUTHORISED); expect(err.code).to.be.eql(ErrorCodes.NOT_AUTHORISED);
done(); done();
@ -129,12 +186,12 @@ describe('Authentication middleware', () => {
}); });
describe('should call input ErrorDTO next on missing...', () => { describe('should call input ErrorDTO next on missing...', () => {
it('body', (done) => { it('body', (done: () => void) => {
const req: any = { const req: any = {
query: {}, query: {},
params: {} params: {}
}; };
const next: any = (err: ErrorDTO) => { const next = (err: ErrorDTO) => {
expect(err).not.to.be.undefined; expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.INPUT_ERROR); expect(err.code).to.be.eql(ErrorCodes.INPUT_ERROR);
done(); done();
@ -143,13 +200,13 @@ describe('Authentication middleware', () => {
}); });
it('loginCredential', (done) => { it('loginCredential', (done: () => void) => {
const req: any = { const req: any = {
body: {}, body: {},
query: {}, query: {},
params: {} params: {}
}; };
const next: any = (err: ErrorDTO) => { const next = (err: ErrorDTO) => {
expect(err).not.to.be.undefined; expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.INPUT_ERROR); expect(err.code).to.be.eql(ErrorCodes.INPUT_ERROR);
done(); done();
@ -160,13 +217,13 @@ describe('Authentication middleware', () => {
}); });
it('loginCredential content', (done) => { it('loginCredential content', (done: () => void) => {
const req: any = { const req: any = {
body: {loginCredential: {}}, body: {loginCredential: {}},
query: {}, query: {},
params: {} params: {}
}; };
const next: any = (err: ErrorDTO) => { const next = (err: ErrorDTO) => {
expect(err).not.to.be.undefined; expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.INPUT_ERROR); expect(err.code).to.be.eql(ErrorCodes.INPUT_ERROR);
done(); done();
@ -177,7 +234,7 @@ describe('Authentication middleware', () => {
}); });
}); });
it('should call next with error on not finding user', (done) => { it('should call next with error on not finding user', (done: () => void) => {
const req: any = { const req: any = {
body: { body: {
loginCredential: { loginCredential: {
@ -188,7 +245,7 @@ describe('Authentication middleware', () => {
query: {}, query: {},
params: {} params: {}
}; };
const next: any = (err: ErrorDTO) => { const next = (err: ErrorDTO) => {
expect(err).not.to.be.undefined; expect(err).not.to.be.undefined;
expect(err.code).to.be.eql(ErrorCodes.CREDENTIAL_NOT_FOUND); expect(err.code).to.be.eql(ErrorCodes.CREDENTIAL_NOT_FOUND);
done(); done();
@ -203,7 +260,7 @@ describe('Authentication middleware', () => {
}); });
it('should call next with user on the session on finding user', (done) => { it('should call next with user on the session on finding user', (done: () => void) => {
const req: any = { const req: any = {
session: {}, session: {},
body: { body: {
@ -215,7 +272,7 @@ describe('Authentication middleware', () => {
query: {}, query: {},
params: {} params: {}
}; };
const next: any = (err: ErrorDTO) => { const next = (err: ErrorDTO) => {
expect(err).to.be.undefined; expect(err).to.be.undefined;
expect(req.session.user).to.be.eql('test user'); expect(req.session.user).to.be.eql('test user');
done(); done();
@ -233,7 +290,7 @@ describe('Authentication middleware', () => {
describe('logout', () => { describe('logout', () => {
it('should call next on logout', (done) => { it('should call next on logout', (done: () => void) => {
const req: any = { const req: any = {
session: { session: {
user: { user: {
@ -241,7 +298,7 @@ describe('Authentication middleware', () => {
} }
} }
}; };
const next: any = (err:ErrorDTO) => { const next: any = (err: ErrorDTO) => {
expect(err).to.be.undefined; expect(err).to.be.undefined;
expect(req.session.user).to.be.undefined; expect(req.session.user).to.be.undefined;
done(); done();

View File

@ -5,14 +5,14 @@ describe('UserDTO', () => {
it('should check available path', () => { it('should check available path', () => {
expect(UserDTO.isPathAvailable('/', ['/'])).to.be.equals(true); expect(UserDTO.isDirectoryPathAvailable('/', ['/'])).to.be.equals(true);
expect(UserDTO.isPathAvailable('/', ['/subfolder', '/'])).to.be.equals(true); expect(UserDTO.isDirectoryPathAvailable('/', ['/subfolder', '/'])).to.be.equals(true);
expect(UserDTO.isPathAvailable('/abc', ['/subfolder', '/'])).to.be.equals(false); expect(UserDTO.isDirectoryPathAvailable('/abc', ['/subfolder', '/'])).to.be.equals(false);
expect(UserDTO.isPathAvailable('/abc', ['/subfolder', '/*'])).to.be.equals(true); expect(UserDTO.isDirectoryPathAvailable('/abc', ['/subfolder', '/*'])).to.be.equals(true);
expect(UserDTO.isPathAvailable('/abc', ['/subfolder'])).to.be.equals(false); expect(UserDTO.isDirectoryPathAvailable('/abc', ['/subfolder'])).to.be.equals(false);
expect(UserDTO.isPathAvailable('/abc/two', ['/subfolder'])).to.be.equals(false); expect(UserDTO.isDirectoryPathAvailable('/abc/two', ['/subfolder'])).to.be.equals(false);
expect(UserDTO.isPathAvailable('/abc/two', ['/'])).to.be.equals(false); expect(UserDTO.isDirectoryPathAvailable('/abc/two', ['/'])).to.be.equals(false);
expect(UserDTO.isPathAvailable('/abc/two', ['/*'])).to.be.equals(true); expect(UserDTO.isDirectoryPathAvailable('/abc/two', ['/*'])).to.be.equals(true);
}); });
it('should check directory', () => { it('should check directory', () => {