diff --git a/backend/middlewares/customtypings/ExtendedRequest.d.ts b/backend/middlewares/customtypings/ExtendedRequest.d.ts index dfb845a4..59f41c7b 100644 --- a/backend/middlewares/customtypings/ExtendedRequest.d.ts +++ b/backend/middlewares/customtypings/ExtendedRequest.d.ts @@ -12,6 +12,7 @@ declare module Express { export interface Session { user?; + rememberMe?: boolean; } } diff --git a/backend/middlewares/user/AuthenticationMWs.ts b/backend/middlewares/user/AuthenticationMWs.ts index 90d57ada..a2c5aadc 100644 --- a/backend/middlewares/user/AuthenticationMWs.ts +++ b/backend/middlewares/user/AuthenticationMWs.ts @@ -5,6 +5,7 @@ import {UserDTO, UserRoles} from "../../../common/entities/UserDTO"; import {ObjectManagerRepository} from "../../model/ObjectManagerRepository"; import {Config} from "../../../common/config/private/Config"; import {PasswordHelper} from "../../model/PasswordHelper"; +import {Utils} from "../../../common/Utils"; export class AuthenticationMWs { @@ -69,6 +70,11 @@ export class AuthenticationMWs { if (typeof req.session.user === 'undefined') { return next(new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED)); } + if (req.session.rememberMe === true) { + req.sessionOptions.expires = new Date(Date.now() + Config.Server.sessionTimeout); + } else { + delete(req.sessionOptions.expires); + } return next(); } @@ -113,23 +119,29 @@ export class AuthenticationMWs { //TODO: implement remember me try { //lets find the user - req.session.user = await ObjectManagerRepository.getInstance().UserManager.findOne({ + const user = Utils.clone(await ObjectManagerRepository.getInstance().UserManager.findOne({ name: req.body.loginCredential.username, password: req.body.loginCredential.password - }); + })); + delete (user.password); + req.session.user = user; + if (req.body.loginCredential.rememberMe) { + req.sessionOptions.expires = new Date(Date.now() + Config.Server.sessionTimeout); + } return next(); } catch (err) { //if its a shared link, login as guest - try { - const user = await AuthenticationMWs.getSharingUser(req); - if (user) { - req.session.user = user; - return next(); - } - } catch (err) { - return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND, null, err)); - } + /* try { + const user = Utils.clone(await AuthenticationMWs.getSharingUser(req)); + if (user) { + delete (user.password); + req.session.user = user; + return next(); + } + } catch (err) { + return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND, null, err)); + }*/ return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND)); } @@ -176,6 +188,7 @@ export class AuthenticationMWs { public static logout(req: Request, res: Response, next: NextFunction) { delete req.session.user; + delete req.session.rememberMe; return next(); } diff --git a/backend/routes/UserRouter.ts b/backend/routes/UserRouter.ts index 7e247098..db491402 100644 --- a/backend/routes/UserRouter.ts +++ b/backend/routes/UserRouter.ts @@ -28,7 +28,6 @@ export class UserRouter { private static addLogout(app) { app.post("/api/user/logout", - AuthenticationMWs.authenticate, AuthenticationMWs.logout, RenderingMWs.renderOK ); diff --git a/backend/server.ts b/backend/server.ts index 873e49b1..1d2a9db8 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -1,5 +1,5 @@ import * as _express from "express"; -import * as _session from "express-session"; +import * as _session from "cookie-session"; import * as _bodyParser from "body-parser"; import * as _http from "http"; import {PublicRouter} from "./routes/PublicRouter"; @@ -47,15 +47,15 @@ export class Server { /** * Session above all */ + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + this.app.use(_session({ name: "pigallery2-session", - secret: 'PiGallery2 secret', - cookie: { - maxAge: 60000 * 10, - httpOnly: false - }, - resave: true, - saveUninitialized: false + keys: ["key1" + s4() + s4() + s4() + s4(), "key2" + s4() + s4() + s4() + s4(), "key3" + s4() + s4() + s4() + s4()] })); /** diff --git a/common/config/private/IPrivateConfig.ts b/common/config/private/IPrivateConfig.ts index 66c9d95b..a3d10c1d 100644 --- a/common/config/private/IPrivateConfig.ts +++ b/common/config/private/IPrivateConfig.ts @@ -37,6 +37,7 @@ export interface ServerConfig { database: DataBaseConfig; enableThreading: boolean; sharing: SharingConfig; + sessionTimeout: number } export interface IPrivateConfig { Server: ServerConfig; diff --git a/common/config/private/PrivateConfigClass.ts b/common/config/private/PrivateConfigClass.ts index ee577f45..a6846989 100644 --- a/common/config/private/PrivateConfigClass.ts +++ b/common/config/private/PrivateConfigClass.ts @@ -16,6 +16,7 @@ export class PrivateConfigClass extends PublicConfigClass implements IPrivateCon processingLibrary: ThumbnailProcessingLib.sharp, qualityPriority: true }, + sessionTimeout: 1000 * 60 * 60 * 24 * 7, database: { type: DatabaseType.mysql, mysql: { diff --git a/frontend/app/gallery/gallery.component.ts b/frontend/app/gallery/gallery.component.ts index 2289c668..fc31207d 100644 --- a/frontend/app/gallery/gallery.component.ts +++ b/frontend/app/gallery/gallery.component.ts @@ -115,14 +115,11 @@ export class GalleryComponent implements OnInit, OnDestroy { }; private onRoute = async (params: Params) => { - console.log("onRoute", params); const searchText = params['searchText']; if (searchText && searchText != "") { - console.log("searching"); let typeString = params['type']; if (typeString && typeString != "") { - console.log("with type"); let type: SearchTypes = SearchTypes[typeString]; this._galleryService.search(searchText, type); return; diff --git a/frontend/app/gallery/gallery.service.ts b/frontend/app/gallery/gallery.service.ts index 87402526..8ffa2bae 100644 --- a/frontend/app/gallery/gallery.service.ts +++ b/frontend/app/gallery/gallery.service.ts @@ -46,6 +46,10 @@ export class GalleryService { cw = await this.networkService.getJson("/gallery/content/" + directoryName); } + if (!cw) { + return; + } + this.galleryCacheService.setDirectory(cw.directory); //save it before adding references if (this.lastRequest.directory != directoryName) { diff --git a/frontend/app/gallery/lightbox/photo/photo.lightbox.gallery.component.ts b/frontend/app/gallery/lightbox/photo/photo.lightbox.gallery.component.ts index f8439769..2f4d7a0b 100644 --- a/frontend/app/gallery/lightbox/photo/photo.lightbox.gallery.component.ts +++ b/frontend/app/gallery/lightbox/photo/photo.lightbox.gallery.component.ts @@ -1,4 +1,4 @@ -import {Component, Input, OnChanges} from "@angular/core"; +import {Component, ElementRef, Input, OnChanges} from "@angular/core"; import {GridPhoto} from "../../grid/GridPhoto"; @Component({ @@ -15,7 +15,7 @@ export class GalleryLightboxPhotoComponent implements OnChanges { imageLoaded: boolean = false; - constructor() { + constructor(public elementRef: ElementRef) { } ngOnChanges() { diff --git a/frontend/app/login/login.component.ts b/frontend/app/login/login.component.ts index 5499abc1..d30698ab 100644 --- a/frontend/app/login/login.component.ts +++ b/frontend/app/login/login.component.ts @@ -30,11 +30,11 @@ export class LoginComponent implements OnInit { this.loginError = null; try { - console.log(await this._authService.login(this.loginCredential)); + await this._authService.login(this.loginCredential); } catch (error) { - console.log(error); if (error && error.code === ErrorCodes.CREDENTIAL_NOT_FOUND) { this.loginError = "Wrong username or password"; + return; } } } diff --git a/frontend/app/model/network/authentication.service.ts b/frontend/app/model/network/authentication.service.ts index 0c1f394a..962742ce 100644 --- a/frontend/app/model/network/authentication.service.ts +++ b/frontend/app/model/network/authentication.service.ts @@ -5,6 +5,8 @@ import {UserService} from "./user.service"; import {LoginCredential} from "../../../../common/entities/LoginCredential"; import {Cookie} from "ng2-cookies"; import {Config} from "../../../../common/config/public/Config"; +import {NetworkService} from "./network.service"; +import {ErrorCodes, ErrorDTO} from "../../../../common/entities/Error"; declare module ServerInject { export let user: UserDTO; @@ -15,7 +17,8 @@ export class AuthenticationService { public user: BehaviorSubject; - constructor(private _userService: UserService) { + constructor(private _userService: UserService, + private _networkService: NetworkService) { this.user = new BehaviorSubject(null); //picking up session.. @@ -26,10 +29,22 @@ export class AuthenticationService { this.getSessionUser(); } else { if (Config.Client.authenticationRequired === false) { - this.user.next({name: "", password: "", role: UserRoles.Admin}); + this.user.next({name: "", role: UserRoles.Admin}); } } + _networkService.addGlobalErrorHandler((error: ErrorDTO) => { + if (error.code == ErrorCodes.NOT_AUTHENTICATED) { + this.user.next(null); + return true; + } + if (error.code == ErrorCodes.NOT_AUTHORISED) { + this.logout(); + return true; + } + return false; + }); + } private async getSessionUser(): Promise { diff --git a/frontend/app/model/network/network.service.ts b/frontend/app/model/network/network.service.ts index cfd48742..3a0e7491 100644 --- a/frontend/app/model/network/network.service.ts +++ b/frontend/app/model/network/network.service.ts @@ -3,16 +3,19 @@ import {Headers, Http, RequestOptions} from "@angular/http"; import {Message} from "../../../../common/entities/Message"; import {SlimLoadingBarService} from "ng2-slim-loading-bar"; import "rxjs/Rx"; -import {ErrorCodes} from "../../../../common/entities/Error"; +import {ErrorCodes, ErrorDTO} from "../../../../common/entities/Error"; @Injectable() export class NetworkService { _baseUrl = "/api"; + private globalErrorHandlers: Array<(error: ErrorDTO) => boolean> = []; - constructor(protected _http: Http, private slimLoadingBarService: SlimLoadingBarService) { + constructor(protected _http: Http, + private slimLoadingBarService: SlimLoadingBarService) { } + private callJson(method: string, url: string, data: any = {}): Promise { let body = JSON.stringify(data); let headers = new Headers({'Content-Type': 'application/json'}); @@ -37,7 +40,7 @@ export class NetworkService { const err = (err) => { this.slimLoadingBarService.complete(); - return NetworkService.handleError(err); + return this.handleError(err); }; if (method == "get" || method == "delete") { @@ -69,8 +72,13 @@ export class NetworkService { return this.callJson("delete", url); } - private static handleError(error: any) { - if (error.code) { + private handleError(error: any) { + if (typeof error.code !== "undefined") { + for (let i = 0; i < this.globalErrorHandlers.length; i++) { + if (this.globalErrorHandlers[i](error) == true) { + return; + } + } return Promise.reject(error); } // TODO: in a real world app do something better @@ -78,4 +86,9 @@ export class NetworkService { console.error(error); return Promise.reject(error.message || error || 'Server error'); } + + + addGlobalErrorHandler(fn: (error: ErrorDTO) => boolean) { + this.globalErrorHandlers.push(fn); + } } diff --git a/package.json b/package.json index b83ce597..8bfe4888 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,10 @@ "dependencies": { "bcryptjs": "2.4.3", "body-parser": "1.17.2", + "cookie-session": "^2.0.0-beta.2", "ejs": "2.5.6", "exif-parser": "0.1.9", "express": "4.15.3", - "express-session": "1.15.3", "flat-file-db": "1.0.0", "jimp": "0.2.28", "mysql": "2.13.0", @@ -56,8 +56,8 @@ "@angular/router": "~4.3.0", "@types/bcryptjs": "^2.4.0", "@types/chai": "^4.0.1", + "@types/cookie-session": "^2.0.32", "@types/express": "^4.0.36", - "@types/express-session": "1.15.1", "@types/gm": "^1.17.31", "@types/jasmine": "^2.5.53", "@types/jimp": "^0.2.1",