You've already forked pigallery2
							
							
				mirror of
				https://github.com/bpatrik/pigallery2.git
				synced 2025-10-30 23:57:43 +02:00 
			
		
		
		
	improving authentication handling
implementing remember me
This commit is contained in:
		| @@ -12,6 +12,7 @@ declare module Express { | ||||
|  | ||||
|   export interface Session { | ||||
|     user?; | ||||
|     rememberMe?: boolean; | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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(); | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -28,7 +28,6 @@ export class UserRouter { | ||||
|  | ||||
|   private static addLogout(app) { | ||||
|     app.post("/api/user/logout", | ||||
|       AuthenticationMWs.authenticate, | ||||
|       AuthenticationMWs.logout, | ||||
|       RenderingMWs.renderOK | ||||
|     ); | ||||
|   | ||||
| @@ -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()] | ||||
|     })); | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -37,6 +37,7 @@ export interface ServerConfig { | ||||
|   database: DataBaseConfig; | ||||
|   enableThreading: boolean; | ||||
|   sharing: SharingConfig; | ||||
|   sessionTimeout: number | ||||
| } | ||||
| export interface IPrivateConfig { | ||||
|   Server: ServerConfig; | ||||
|   | ||||
| @@ -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: { | ||||
|   | ||||
| @@ -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 = <any>SearchTypes[typeString]; | ||||
|         this._galleryService.search(searchText, type); | ||||
|         return; | ||||
|   | ||||
| @@ -46,6 +46,10 @@ export class GalleryService { | ||||
|       cw = await this.networkService.getJson<ContentWrapper>("/gallery/content/" + directoryName); | ||||
|     } | ||||
|  | ||||
|     if (!cw) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     this.galleryCacheService.setDirectory(cw.directory); //save it before adding references | ||||
|  | ||||
|     if (this.lastRequest.directory != directoryName) { | ||||
|   | ||||
| @@ -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() { | ||||
|   | ||||
| @@ -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; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -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<UserDTO>; | ||||
|  | ||||
|   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(<UserDTO>{name: "", password: "", role: UserRoles.Admin}); | ||||
|         this.user.next(<UserDTO>{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<void> { | ||||
|   | ||||
| @@ -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<T>(method: string, url: string, data: any = {}): Promise<T> { | ||||
|     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); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user