improving sharing

This commit is contained in:
Braun Patrik 2017-07-09 12:03:17 +02:00
parent 8b090603b6
commit d591204740
34 changed files with 463 additions and 175 deletions

View File

@ -63,12 +63,12 @@ To configure it. Run `PiGallery2` first to create `config.json` file, then edit
* supporting several core CPU * supporting several core CPU
* supporting hardware acceleration ([sharp](https://github.com/lovell/sharp) and [gm](https://github.com/aheckmann/gm) as optional and JS-based [Jimp](https://github.com/oliver-moran/jimp) as fallback) * supporting hardware acceleration ([sharp](https://github.com/lovell/sharp) and [gm](https://github.com/aheckmann/gm) as optional and JS-based [Jimp](https://github.com/oliver-moran/jimp) as fallback)
* Custom lightbox for full screen photo viewing * Custom lightbox for full screen photo viewing
* keyboard support for navigation - `In progress` * keyboard support for navigation
* showing low-res thumbnail while full image loads * showing low-res thumbnail while full image loads
* Information panel for showing **Exif info** - `In progress` * Information panel for showing **Exif info** - `In progress`
* Client side caching (directories and search results) * Client side caching (directories and search results)
* Rendering **photos** with GPS coordinates **on google map** * Rendering **photos** with GPS coordinates **on google map**
* .gpx file support - `In progress` * .gpx file support - `future plan`
* **Two modes: SQL database and no-database mode** * **Two modes: SQL database and no-database mode**
* both modes supports * both modes supports
* user management * user management
@ -77,11 +77,11 @@ To configure it. Run `PiGallery2` first to create `config.json` file, then edit
* faster directory listing * faster directory listing
* searching * searching
* instant search, auto complete * instant search, auto complete
* sharing - `In progress` * sharing
* setting link expiration time * setting link expiration time
* Nice design - `In progress` * Nice design - `In progress`
* responsive design (phone, tablet desktop support) * responsive design (phone, tablet desktop support)
* Setup page - `In progress` * Setup page - `In progress`
* **Markdown based blogging support** - `In progress` * **Markdown based blogging support** - `future plan`
* you can write some note in the blog.md for every directory * you can write some note in the blog.md for every directory
* bug free :) - `In progress` * bug free :) - `In progress`

7
USERRIGHTS.md Normal file
View File

@ -0,0 +1,7 @@
# User rights
* Limited Guest - list dir
* Guest - +search
* User - +share
* Admin - +settings
* Developer - +see errors

View File

@ -10,7 +10,7 @@ import {PhotoDTO} from "../../common/entities/PhotoDTO";
import {ProjectPath} from "../ProjectPath"; import {ProjectPath} from "../ProjectPath";
import {Logger} from "../Logger"; import {Logger} from "../Logger";
import {Config} from "../../common/config/private/Config"; import {Config} from "../../common/config/private/Config";
import {UserUtil} from "../../common/entities/UserDTO"; import {UserDTO} from "../../common/entities/UserDTO";
const LOG_TAG = "[GalleryMWs]"; const LOG_TAG = "[GalleryMWs]";
@ -32,7 +32,7 @@ export class GalleryMWs {
req.session.user.permissions.length > 0 && req.session.user.permissions.length > 0 &&
req.session.user.permissions[0] != "/") { req.session.user.permissions[0] != "/") {
directory.directories = directory.directories.filter(d => directory.directories = directory.directories.filter(d =>
UserUtil.isDirectoryAvailable(d, req.session.user.permissions)); UserDTO.isDirectoryAvailable(d, req.session.user.permissions));
} }
req.resultPipe = new ContentWrapper(directory, null); req.resultPipe = new ContentWrapper(directory, null);
return next(); return next();

View File

@ -1,7 +1,7 @@
///<reference path="../customtypings/ExtendedRequest.d.ts"/> ///<reference path="../customtypings/ExtendedRequest.d.ts"/>
import {NextFunction, Request, Response} from "express"; import {NextFunction, Request, Response} from "express";
import {Error, ErrorCodes} from "../../../common/entities/Error"; import {Error, ErrorCodes} from "../../../common/entities/Error";
import {UserDTO, UserRoles, UserUtil} from "../../../common/entities/UserDTO"; import {UserDTO, UserRoles} from "../../../common/entities/UserDTO";
import {ObjectManagerRepository} from "../../model/ObjectManagerRepository"; import {ObjectManagerRepository} from "../../model/ObjectManagerRepository";
import {Config} from "../../../common/config/private/Config"; import {Config} from "../../../common/config/private/Config";
@ -9,12 +9,15 @@ export class AuthenticationMWs {
private static async getSharingUser(req: Request) { private static async getSharingUser(req: Request) {
if (Config.Client.Sharing.enabled === true && if (Config.Client.Sharing.enabled === true &&
Config.Client.Sharing.passwordProtected === false &&
(!!req.query.sk || !!req.params.sharingKey)) { (!!req.query.sk || !!req.params.sharingKey)) {
const sharing = await ObjectManagerRepository.getInstance().SharingManager.findOne({ const sharing = await ObjectManagerRepository.getInstance().SharingManager.findOne({
sharingKey: req.query.sk || req.params.sharingKey, sharingKey: req.query.sk || req.params.sharingKey,
}); });
if (!sharing) { if (!sharing || sharing.expires < Date.now()) {
return null;
}
if (Config.Client.Sharing.passwordProtected === true && sharing.password) {
return null; return null;
} }
@ -22,7 +25,7 @@ export class AuthenticationMWs {
if (sharing.includeSubfolders == true) { if (sharing.includeSubfolders == true) {
path += "*"; path += "*";
} }
return <UserDTO>{name: "Guest", role: UserRoles.Guest, permissions: [path]}; return <UserDTO>{name: "Guest", role: UserRoles.LimitedGuest, permissions: [path]};
} }
return null; return null;
@ -67,7 +70,7 @@ export class AuthenticationMWs {
} }
const directoryName = req.params.directory || "/"; const directoryName = req.params.directory || "/";
if (UserUtil.isPathAvailable(directoryName, req.session.user.permissions) == true) { if (UserDTO.isPathAvailable(directoryName, req.session.user.permissions) == true) {
return next(); return next();
} }
@ -117,6 +120,42 @@ export class AuthenticationMWs {
} }
public static async shareLogin(req: Request, res: Response, next: NextFunction) {
if (Config.Client.Sharing.enabled === false) {
return next();
}
//not enough parameter
if ((!req.query.sk && !req.params.sharingKey)) {
return next(new Error(ErrorCodes.INPUT_ERROR));
}
try {
const password = (req.body ? req.body.password : null) || null;
const sharing = await ObjectManagerRepository.getInstance().SharingManager.findOne({
sharingKey: req.query.sk || req.params.sharingKey,
});
if (!sharing || sharing.expires < Date.now() ||
(Config.Client.Sharing.passwordProtected === true && sharing.password !== password)) {
return next(new Error(ErrorCodes.CREDENTIAL_NOT_FOUND));
}
let path = sharing.path;
if (sharing.includeSubfolders == true) {
path += "*";
}
req.session.user = <UserDTO>{name: "Guest", role: UserRoles.LimitedGuest, permissions: [path]};
return next();
} catch (err) {
return next(new Error(ErrorCodes.GENERAL_ERROR));
}
}
public static logout(req: Request, res: Response, next: NextFunction) { public static logout(req: Request, res: Response, next: NextFunction) {
delete req.session.user; delete req.session.user;
return next(); return next();

View File

@ -34,7 +34,7 @@ export class UserManager implements IUserManager {
this.createUser(<UserDTO>{name: "developer", password: "developer", role: UserRoles.Developer}); this.createUser(<UserDTO>{name: "developer", password: "developer", role: UserRoles.Developer});
this.createUser(<UserDTO>{name: "admin", password: "admin", role: UserRoles.Admin}); this.createUser(<UserDTO>{name: "admin", password: "admin", role: UserRoles.Admin});
this.createUser(<UserDTO>{name: "user", password: "user", role: UserRoles.User}); this.createUser(<UserDTO>{name: "user", password: "user", role: UserRoles.User});
this.createUser(<UserDTO>{name: "guest", password: "guest", role: UserRoles.Guest}); this.createUser(<UserDTO>{name: "guest", password: "guest", role: UserRoles.LimitedGuest});
} }

View File

@ -2,6 +2,7 @@ import {AuthenticationMWs} from "../middlewares/user/AuthenticationMWs";
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";
import {UserRoles} from "../../common/entities/UserDTO";
export class GalleryRouter { export class GalleryRouter {
public static route(app: any) { public static route(app: any) {
@ -31,6 +32,7 @@ export class GalleryRouter {
private static addGetImage(app) { private static addGetImage(app) {
app.get(["/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))"], app.get(["/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))"],
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
//TODO: authorize path
GalleryMWs.loadImage, GalleryMWs.loadImage,
RenderingMWs.renderFile RenderingMWs.renderFile
); );
@ -39,6 +41,7 @@ export class GalleryRouter {
private static addGetImageThumbnail(app) { private static addGetImageThumbnail(app) {
app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/thumbnail/:size?", app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/thumbnail/:size?",
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
//TODO: authorize path
GalleryMWs.loadImage, GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateThumbnail, ThumbnailGeneratorMWs.generateThumbnail,
RenderingMWs.renderFile RenderingMWs.renderFile
@ -48,6 +51,7 @@ export class GalleryRouter {
private static addGetImageIcon(app) { private static addGetImageIcon(app) {
app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/icon", app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/icon",
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
//TODO: authorize path
GalleryMWs.loadImage, GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateIcon, ThumbnailGeneratorMWs.generateIcon,
RenderingMWs.renderFile RenderingMWs.renderFile
@ -57,6 +61,7 @@ export class GalleryRouter {
private static addSearch(app) { private static addSearch(app) {
app.get("/api/search/:text", app.get("/api/search/:text",
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),
GalleryMWs.search, GalleryMWs.search,
ThumbnailGeneratorMWs.addThumbnailInformation, ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences, GalleryMWs.removeCyclicDirectoryReferences,
@ -67,6 +72,7 @@ export class GalleryRouter {
private static addInstantSearch(app) { private static addInstantSearch(app) {
app.get("/api/instant-search/:text", app.get("/api/instant-search/:text",
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),
GalleryMWs.instantSearch, GalleryMWs.instantSearch,
ThumbnailGeneratorMWs.addThumbnailInformation, ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences, GalleryMWs.removeCyclicDirectoryReferences,
@ -77,6 +83,7 @@ export class GalleryRouter {
private static addAutoComplete(app) { private static addAutoComplete(app) {
app.get("/api/autocomplete/:text", app.get("/api/autocomplete/:text",
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),
GalleryMWs.autocomplete, GalleryMWs.autocomplete,
RenderingMWs.renderResult RenderingMWs.renderResult
); );

View File

@ -6,15 +6,24 @@ import {SharingMWs} from "../middlewares/SharingMWs";
export class SharingRouter { export class SharingRouter {
public static route(app: any) { public static route(app: any) {
this.addShareLogin(app);
this.addGetSharing(app); this.addGetSharing(app);
this.addCreateSharing(app); this.addCreateSharing(app);
this.addUpdateSharing(app); this.addUpdateSharing(app);
} }
private static addShareLogin(app) {
app.post("/api/share/login",
AuthenticationMWs.inverseAuthenticate,
AuthenticationMWs.shareLogin,
RenderingMWs.renderSessionUser
);
};
private static addGetSharing(app) { private static addGetSharing(app) {
app.get("/api/share/:sharingKey", app.get("/api/share/:sharingKey",
AuthenticationMWs.authenticate, AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest), AuthenticationMWs.authorise(UserRoles.LimitedGuest),
SharingMWs.getSharing, SharingMWs.getSharing,
RenderingMWs.renderSharing RenderingMWs.renderSharing
); );

View File

@ -8,7 +8,13 @@ declare module ServerInject {
export let Config = new PublicConfigClass(); export let Config = new PublicConfigClass();
if (typeof ServerInject !== "undefined" && typeof ServerInject.ConfigInject !== "undefined") { if (typeof ServerInject !== "undefined" && typeof ServerInject.ConfigInject !== "undefined") {
WebConfigLoader.loadFrontendConfig(Config.Client, ServerInject.ConfigInject); WebConfigLoader.loadFrontendConfig(Config.Client, ServerInject.ConfigInject);
} }
if (Config.Client.publicUrl == "") {
Config.Client.publicUrl = location.origin;
}

View File

@ -21,6 +21,7 @@ export interface ClientConfig {
enableOnScrollThumbnailPrioritising: boolean; enableOnScrollThumbnailPrioritising: boolean;
authenticationRequired: boolean; authenticationRequired: boolean;
googleApiKey: string; googleApiKey: string;
publicUrl: string;
} }
/** /**
@ -46,7 +47,8 @@ export class PublicConfigClass {
enableOnScrollRendering: true, enableOnScrollRendering: true,
enableOnScrollThumbnailPrioritising: true, enableOnScrollThumbnailPrioritising: true,
authenticationRequired: true, authenticationRequired: true,
googleApiKey: "" googleApiKey: "",
publicUrl: ""
}; };
} }

View File

@ -10,8 +10,8 @@ export interface DirectoryDTO {
photos: Array<PhotoDTO>; photos: Array<PhotoDTO>;
} }
export module DirectoryUtil { export module DirectoryDTO {
export const addReferences = (dir: DirectoryDTO) => { export const addReferences = (dir: DirectoryDTO): void => {
dir.photos.forEach((photo: PhotoDTO) => { dir.photos.forEach((photo: PhotoDTO) => {
photo.directory = dir; photo.directory = dir;
}); });

View File

@ -1,8 +1,8 @@
import {DirectoryDTO} from "./DirectoryDTO"; import {DirectoryDTO} from "./DirectoryDTO";
import {Utils} from "../Utils"; import {Utils} from "../Utils";
export enum UserRoles{ export enum UserRoles{
Guest = 0, LimitedGuest = 0,
TrustedGuest = 1, Guest = 1,
User = 2, User = 2,
Admin = 3, Admin = 3,
Developer = 4, Developer = 4,
@ -17,7 +17,7 @@ export interface UserDTO {
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 UserUtil { export module UserDTO {
export const isPathAvailable = (path: string, permissions: string[]): boolean => { export const isPathAvailable = (path: string, permissions: string[]): boolean => {
if (permissions == null || permissions.length == 0 || permissions[0] == "/") { if (permissions == null || permissions.length == 0 || permissions[0] == "/") {

View File

@ -5,6 +5,7 @@ import {Router} from "@angular/router";
import {Config} from "../../common/config/public/Config"; import {Config} from "../../common/config/public/Config";
import {Title} from "@angular/platform-browser"; import {Title} from "@angular/platform-browser";
import {NotificationService} from "./model/notification.service"; import {NotificationService} from "./model/notification.service";
import {ShareService} from "./gallery/share.service";
@Component({ @Component({
@ -16,23 +17,23 @@ export class AppComponent implements OnInit {
constructor(private _router: Router, constructor(private _router: Router,
private _authenticationService: AuthenticationService, private _authenticationService: AuthenticationService,
private _shareService: ShareService,
private _title: Title, vcr: ViewContainerRef, private _title: Title, vcr: ViewContainerRef,
notificatin: NotificationService) { notificatin: NotificationService) {
notificatin.setRootViewContainerRef(vcr); notificatin.setRootViewContainerRef(vcr);
} }
ngOnInit() { async ngOnInit() {
this._title.setTitle(Config.Client.applicationTitle); this._title.setTitle(Config.Client.applicationTitle);
await this._shareService.wait();
this._authenticationService.user.subscribe((user: UserDTO) => { this._authenticationService.user.subscribe((user: UserDTO) => {
if (user != null) { if (this._authenticationService.isAuthenticated()) {
if (this._router.isActive('login', true)) { if (this.isLoginPage()) {
console.log("routing"); return this.toGallery();
this._router.navigate(["gallery", ""]);
} }
} else { } else {
if (!this._router.isActive('login', true)) { if (!this.isLoginPage()) {
console.log("routing"); return this.toLogin();
this._router.navigate(["login"]);
} }
} }
@ -41,5 +42,23 @@ export class AppComponent implements OnInit {
} }
private isLoginPage() {
return this._router.isActive('login', true) || this._router.isActive('shareLogin', false);
}
private toLogin() {
if (this._shareService.isSharing()) {
return this._router.navigate(["shareLogin"], {queryParams: {sk: this._shareService.getSharingKey()}});
} else {
return this._router.navigate(["login"]);
}
}
private toGallery() {
if (this._shareService.isSharing()) {
return this._router.navigate(["share", this._shareService.getSharingKey()]);
} else {
return this._router.navigate(["gallery", ""]);
}
}
} }

View File

@ -42,6 +42,9 @@ import {ToastModule} from "ng2-toastr/ng2-toastr";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {NotificationService} from "./model/notification.service"; import {NotificationService} from "./model/notification.service";
import {ClipboardModule} from "ngx-clipboard";
import {NavigationService} from "./model/navigation.service";
@Injectable() @Injectable()
export class GoogleMapsConfig { export class GoogleMapsConfig {
apiKey: string; apiKey: string;
@ -58,6 +61,7 @@ export class GoogleMapsConfig {
HttpModule, HttpModule,
BrowserAnimationsModule, BrowserAnimationsModule,
appRoutes, appRoutes,
ClipboardModule,
ToastModule.forRoot(), ToastModule.forRoot(),
ModalModule.forRoot(), ModalModule.forRoot(),
AgmCoreModule.forRoot(), AgmCoreModule.forRoot(),
@ -98,6 +102,7 @@ export class GoogleMapsConfig {
ThumbnailManagerService, ThumbnailManagerService,
NotificationService, NotificationService,
FullScreenService, FullScreenService,
NavigationService,
OverlayService], OverlayService],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -1,6 +1,6 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {PhotoDTO} from "../../../common/entities/PhotoDTO"; import {PhotoDTO} from "../../../common/entities/PhotoDTO";
import {DirectoryDTO, DirectoryUtil} from "../../../common/entities/DirectoryDTO"; import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import {Utils} from "../../../common/Utils"; import {Utils} from "../../../common/Utils";
import {Config} from "../../../common/config/public/Config"; import {Config} from "../../../common/config/public/Config";
@ -16,7 +16,7 @@ export class GalleryCacheService {
if (value != null) { if (value != null) {
let directory: DirectoryDTO = JSON.parse(value); let directory: DirectoryDTO = JSON.parse(value);
DirectoryUtil.addReferences(directory); DirectoryDTO.addReferences(directory);
return directory; return directory;
} }
return null; return null;

View File

@ -2,6 +2,12 @@
<app-frame> <app-frame>
<ng-container navbar> <ng-container navbar>
<li *ngIf="countDown">
<p class="navbar-text">Link availability: {{countDown.day}} days,
{{("0"+countDown.hour).slice(-2)}}:{{("0"+countDown.minute).slice(-2)}}:{{("0"+countDown.second).slice(-2)}}
</p>
</li>
<li *ngIf="showSearchBar"> <li *ngIf="showSearchBar">
<gallery-search #search></gallery-search> <gallery-search #search></gallery-search>
</li> </li>

View File

@ -9,6 +9,9 @@ import {Config} from "../../../common/config/public/Config";
import {DirectoryDTO} from "../../../common/entities/DirectoryDTO"; import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import {SearchResultDTO} from "../../../common/entities/SearchResult"; import {SearchResultDTO} from "../../../common/entities/SearchResult";
import {ShareService} from "./share.service"; import {ShareService} from "./share.service";
import {NavigationService} from "../model/navigation.service";
import {UserRoles} from "../../../common/entities/UserDTO";
import {Observable} from "rxjs/Rx";
@Component({ @Component({
selector: 'gallery', selector: 'gallery',
@ -20,42 +23,61 @@ export class GalleryComponent implements OnInit, OnDestroy {
@ViewChild(GallerySearchComponent) search: GallerySearchComponent; @ViewChild(GallerySearchComponent) search: GallerySearchComponent;
@ViewChild(GalleryGridComponent) grid: GalleryGridComponent; @ViewChild(GalleryGridComponent) grid: GalleryGridComponent;
public showSearchBar: boolean = true; public showSearchBar: boolean = false;
public showShare: boolean = true; public showShare: boolean = false;
public directories: DirectoryDTO[] = []; public directories: DirectoryDTO[] = [];
public isPhotoWithLocation = false; public isPhotoWithLocation = false;
private $counter;
private subscription = { private subscription = {
content: null, content: null,
route: null route: null,
timer: null
}; };
public countDown = null;
constructor(public _galleryService: GalleryService, constructor(public _galleryService: GalleryService,
private _authService: AuthenticationService, private _authService: AuthenticationService,
private _router: Router, private _router: Router,
private shareService: ShareService, private shareService: ShareService,
private _route: ActivatedRoute) { private _route: ActivatedRoute,
private _navigation: NavigationService) {
this.showSearchBar = Config.Client.Search.searchEnabled;
this.showShare = Config.Client.Sharing.enabled;
} }
updateTimer(t: number) {
ngOnInit() { if (this.shareService.sharing.value == null) {
if (!this._authService.isAuthenticated() &&
(!this.shareService.isSharing() ||
(this.shareService.isSharing() && Config.Client.Sharing.passwordProtected == true))
) {
if (this.shareService.isSharing()) {
this._router.navigate(['shareLogin']);
} else {
this._router.navigate(['login']);
}
return; return;
} }
t = Math.floor((this.shareService.sharing.value.expires - Date.now()) / 1000);
this.countDown = {};
this.countDown.day = Math.floor(t / 86400);
t -= this.countDown.day * 86400;
this.countDown.hour = Math.floor(t / 3600) % 24;
t -= this.countDown.hour * 3600;
this.countDown.minute = Math.floor(t / 60) % 60;
t -= this.countDown.minute * 60;
this.countDown.second = t % 60;
}
async ngOnInit() {
await this.shareService.wait();
if (!this._authService.isAuthenticated() &&
(!this.shareService.isSharing() ||
(this.shareService.isSharing() && Config.Client.Sharing.passwordProtected == true))) {
return this._navigation.toLogin();
}
this.showSearchBar = Config.Client.Search.searchEnabled && this._authService.isAuthorized(UserRoles.Guest);
this.showShare = Config.Client.Sharing.enabled && this._authService.isAuthorized(UserRoles.User);
this.subscription.content = this._galleryService.content.subscribe(this.onContentChange); this.subscription.content = this._galleryService.content.subscribe(this.onContentChange);
this.subscription.route = this._route.params.subscribe(this.onRoute); this.subscription.route = this._route.params.subscribe(this.onRoute);
if (this.shareService.isSharing()) {
this.$counter = Observable.interval(1000);
this.subscription.timer = this.$counter.subscribe((x) => this.updateTimer(x));
}
} }
ngOnDestroy() { ngOnDestroy() {
@ -109,7 +131,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
} }
if (params['sharingKey'] && params['sharingKey'] != "") { if (params['sharingKey'] && params['sharingKey'] != "") {
const sharing = await this._galleryService.getSharing(this.shareService.getSharingKey()); const sharing = await this.shareService.getSharing();
this._router.navigate(['/gallery', sharing.path], {queryParams: {sk: this.shareService.getSharingKey()}}); this._router.navigate(['/gallery', sharing.path], {queryParams: {sk: this.shareService.getSharingKey()}});
return; return;
} }

View File

@ -1,7 +1,7 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {NetworkService} from "../model/network/network.service"; import {NetworkService} from "../model/network/network.service";
import {ContentWrapper} from "../../../common/entities/ConentWrapper"; import {ContentWrapper} from "../../../common/entities/ConentWrapper";
import {DirectoryDTO, DirectoryUtil} from "../../../common/entities/DirectoryDTO"; import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import {SearchTypes} from "../../../common/entities/AutoCompleteItem"; import {SearchTypes} from "../../../common/entities/AutoCompleteItem";
import {GalleryCacheService} from "./cache.gallery.service"; import {GalleryCacheService} from "./cache.gallery.service";
import {BehaviorSubject} from "rxjs/BehaviorSubject"; import {BehaviorSubject} from "rxjs/BehaviorSubject";
@ -53,7 +53,7 @@ export class GalleryService {
} }
DirectoryUtil.addReferences(cw.directory); DirectoryDTO.addReferences(cw.directory);
this.lastDirectory = cw.directory; this.lastDirectory = cw.directory;

View File

@ -1,7 +1,7 @@
import {Component, Input, OnChanges} from "@angular/core"; import {Component, Input, OnChanges} from "@angular/core";
import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO"; import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
import {RouterLink} from "@angular/router"; import {RouterLink} from "@angular/router";
import {UserUtil} from "../../../../common/entities/UserDTO"; import {UserDTO} from "../../../../common/entities/UserDTO";
import {AuthenticationService} from "../../model/network/authentication.service"; import {AuthenticationService} from "../../model/network/authentication.service";
import {ShareService} from "../share.service"; import {ShareService} from "../share.service";
@ -49,7 +49,7 @@ export class GalleryNavigatorComponent implements OnChanges {
if (dirs.length == 0) { if (dirs.length == 0) {
arr.push({name: "Images", route: null}); arr.push({name: "Images", route: null});
} else { } else {
arr.push({name: "Images", route: UserUtil.isPathAvailable("/", user.permissions) ? "/" : null}); arr.push({name: "Images", route: UserDTO.isPathAvailable("/", user.permissions) ? "/" : null});
} }
@ -59,7 +59,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: UserUtil.isPathAvailable(route, user.permissions) ? route : null}); arr.push({name: name, route: UserDTO.isPathAvailable(route, user.permissions) ? route : null});
} }
}); });

View File

@ -2,10 +2,12 @@ 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/BehaviorSubject";
@Injectable() @Injectable()
export class ShareService { export class ShareService {
public sharing: BehaviorSubject<SharingDTO>;
param = null; param = null;
queryParam = null; queryParam = null;
sharingKey = null; sharingKey = null;
@ -15,7 +17,7 @@ export class ShareService {
constructor(private _networkService: NetworkService, private router: Router) { constructor(private _networkService: NetworkService, private router: Router) {
this.sharing = new BehaviorSubject(null);
this.ReadyPR = new Promise((resolve) => { this.ReadyPR = new Promise((resolve) => {
if (this.inited == true) { if (this.inited == true) {
return resolve(); return resolve();
@ -27,7 +29,11 @@ export class ShareService {
if (val instanceof RoutesRecognized) { if (val instanceof RoutesRecognized) {
this.param = val.state.root.firstChild.params["sharingKey"] || null; this.param = val.state.root.firstChild.params["sharingKey"] || null;
this.queryParam = val.state.root.firstChild.queryParams["sk"] || null; this.queryParam = val.state.root.firstChild.queryParams["sk"] || null;
this.sharingKey = this.param || this.queryParam; const changed = this.sharingKey != this.param || this.queryParam;
if (changed) {
this.sharingKey = this.param || this.queryParam;
this.getSharing();
}
if (this.resolve) { if (this.resolve) {
this.resolve(); this.resolve();
this.inited = true; this.inited = true;
@ -43,7 +49,7 @@ export class ShareService {
return this.ReadyPR; return this.ReadyPR;
} }
public getSharing(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,
@ -70,4 +76,10 @@ export class ShareService {
return this.sharingKey != null; return this.sharingKey != null;
} }
public async getSharing(): Promise<SharingDTO> {
const sharing = await this._networkService.getJson<SharingDTO>("/share/" + this.getSharingKey());
this.sharing.next(sharing);
return sharing;
}
} }

View File

@ -2,7 +2,11 @@
z-index: 9999; z-index: 9999;
} }
button { .full-width {
padding-left: 0; width: 100%;
padding-right: 0; }
.row {
padding-top: 1px;
padding-bottom: 1px;
} }

View File

@ -1,70 +1,98 @@
<button id="shareButton" class="btn btn-default navbar-btn btn-link" type="button" <button id="shareButton" class="btn btn-default navbar-btn btn-link"
data-toggle="modal" data-target="#shareModal" [disabled]="!enabled" (click)="get()"> type="button" [disabled]="!enabled" (click)="showModal()">
<span class="glyphicon glyphicon-share-alt"></span> <span class="glyphicon glyphicon-share-alt"></span>
Share Share
</button> </button>
<!-- sharing Modal--> <!-- sharing Modal-->
<div class="modal fade" id="shareModal" tabindex="-1" role="dialog" aria-labelledby="shareModalLabel" <div bsModal #shareModal="bs-modal"
data-backdrop="false" class="modal fade" id="shareModal"
tabindex="-1" role="dialog" aria-labelledby="shareModalLabel"
[config]="{ backdrop: false }"
aria-hidden="true"> aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span <button type="button" class="close" (click)="shareModal.hide()" aria-label="Close"><span
aria-hidden="true">&times;</span></button> aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="shareModalLabel">Share</h4> <h4 class="modal-title" id="shareModalLabel">Share</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="row"> <div class="row">
<div class="col-sm-10"> <div class="col-sm-10">
<input id="shareLink" name="shareLink" placeholder="link" class="form-control input-md" type="text" <input id="shareLink"
name="shareLink"
placeholder="link"
class="form-control input-md"
type="text"
[ngModel]="url"> [ngModel]="url">
</div> </div>
<div class="col-sm-2 pull-right"> <div class="col-sm-2 pull-right">
<button id="copyButton" name="copyButton" data-clipboard-target="shareLink" class="btn btn-primary">Copy <button id="copyButton" name="copyButton"
ngxClipboard [cbContent]="url"
(cbOnSuccess)="onCopy()"
class="btn btn-primary">Copy
</button> </button>
</div> </div>
</div> </div>
<hr/> <hr/>
<div class="form-horizontal"> <div class="row">
<div class="form-group" style="padding: 0 15px 0 15px;"> <div class="col-sm-2">
<label class="control-label">Sharing:</label>
<div style="display: inline;"> </div>
<label class="control-label">sharing:</label> <div class="col-sm-10">
<div class="form-control-static" id="sharingPath">{{currentDir}}</div> <input disabled type="text"
</div> class="full-width form-control"
[ngModel]="currentDir">
<label class="checkbox pull-right">
<input id="recursiveShareBox" type="checkbox" (change)="update()" [(ngModel)]="input.includeSubfolders"
checked="true" value="remember-me"> Include subfolders
</label>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-4"> <div class="col-sm-2">
Valid: <label class="control-label">Include subfolders:</label>
<p id="sliderText"></p>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input id="shareSlider" data-slider-id='shareSlider' [(ngModel)]="input.valid.amount" (change)="update()" <input id="recursiveShareBox"
type="checkbox"
(change)="update()"
[(ngModel)]="input.includeSubfolders"
checked="checked"
value="remember-me">
</div>
</div>
<div class="row">
<div class="col-sm-2">
<label class="control-label">Password:</label>
</div>
<div class="col-sm-4">
<input id="password"
class="form-control"
type="password"
(change)="update()"
[(ngModel)]="input.password"
placeholder="Password">
</div>
</div>
<div class="row">
<div class="col-sm-2">
<label class="control-label">Valid:</label>
</div>
<div class="col-sm-3" style="padding-right: 1px">
<input class="form-control" [(ngModel)]="input.valid.amount" (change)="update()"
name="validAmount" name="validAmount"
type="number" min="0" step="1"/> type="number" min="0" step="1"/>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-3" style="padding-left: 1px">
<select class="form-control" [(ngModel)]="input.valid.type" (change)="update()" name="validType" required> <select class="form-control col-md-3" [(ngModel)]="input.valid.type" (change)="update()" name="validType"
required>
<option *ngFor="let repository of validityTypes" [value]="repository.key">{{repository.value}} <option *ngFor="let repository of validityTypes" [value]="repository.key">{{repository.value}}
</option> </option>
</select> </select>
</div> </div>
</div> </div>
<div class="row">
<div class="col-sm-2 col-sm-push-10">
<button id="updatebutton" name="updatebutton" class="btn btn-primary">Update</button>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -1,9 +1,13 @@
import {Component, OnDestroy, OnInit} from "@angular/core"; import {Component, OnDestroy, OnInit, ViewChild} from "@angular/core";
import {Utils} from "../../../../common/Utils"; import {Utils} from "../../../../common/Utils";
import {ShareService} from "../share.service"; import {ShareService} from "../share.service";
import {GalleryService} from "../gallery.service"; import {GalleryService} from "../gallery.service";
import {ContentWrapper} from "../../../../common/entities/ConentWrapper"; import {ContentWrapper} from "../../../../common/entities/ConentWrapper";
import {SharingDTO} from "../../../../common/entities/SharingDTO"; import {SharingDTO} from "../../../../common/entities/SharingDTO";
import {ModalDirective} from "ngx-bootstrap/modal";
import {Config} from "../../../../common/config/public/Config";
import {NotificationService} from "../../model/notification.service";
@Component({ @Component({
selector: 'gallery-share', selector: 'gallery-share',
@ -11,6 +15,7 @@ import {SharingDTO} from "../../../../common/entities/SharingDTO";
styleUrls: ['./share.gallery.component.css'], styleUrls: ['./share.gallery.component.css'],
}) })
export class GalleryShareComponent implements OnInit, OnDestroy { export class GalleryShareComponent implements OnInit, OnDestroy {
@ViewChild('shareModal') public childModal: ModalDirective;
enabled: boolean = true; enabled: boolean = true;
url: string = ""; url: string = "";
@ -26,8 +31,11 @@ export class GalleryShareComponent implements OnInit, OnDestroy {
currentDir: string = ""; currentDir: string = "";
sharing: SharingDTO; sharing: SharingDTO;
contentSubscription = null; contentSubscription = null;
passwordProtection = false;
constructor(private _sharingService: ShareService, public _galleryService: GalleryService) { constructor(private _sharingService: ShareService,
public _galleryService: GalleryService,
private _notification: NotificationService) {
this.validityTypes = Utils.enumToArray(ValidityTypes); this.validityTypes = Utils.enumToArray(ValidityTypes);
@ -42,6 +50,7 @@ export class GalleryShareComponent implements OnInit, OnDestroy {
} }
this.currentDir = Utils.concatUrls(content.directory.path, content.directory.name); this.currentDir = Utils.concatUrls(content.directory.path, content.directory.name);
}); });
this.passwordProtection = Config.Client.Sharing.passwordProtected;
} }
ngOnDestroy() { ngOnDestroy() {
@ -68,14 +77,23 @@ export class GalleryShareComponent implements OnInit, OnDestroy {
this.url = "loading.."; this.url = "loading..";
this.sharing = await this._sharingService.updateSharing(this.currentDir, this.sharing.id, this.input.includeSubfolders, this.calcValidity()); this.sharing = await this._sharingService.updateSharing(this.currentDir, this.sharing.id, this.input.includeSubfolders, this.calcValidity());
console.log(this.sharing); console.log(this.sharing);
this.url = location.origin + "/share/" + this.sharing.sharingKey this.url = Config.Client.publicUrl + "/share/" + this.sharing.sharingKey
} }
async get() { async get() {
this.url = "loading.."; this.url = "loading..";
this.sharing = await this._sharingService.getSharing(this.currentDir, this.input.includeSubfolders, this.calcValidity()); this.sharing = await this._sharingService.createSharing(this.currentDir, this.input.includeSubfolders, this.calcValidity());
console.log(this.sharing); console.log(this.sharing);
this.url = location.origin + "/share/" + this.sharing.sharingKey this.url = Config.Client.publicUrl + "/share/" + this.sharing.sharingKey
}
async showModal() {
await this.get();
this.childModal.show();
}
onCopy() {
this._notification.success("Url has been copied to clipboard");
} }
} }

View File

@ -52,43 +52,3 @@
</div> </div>
</div> </div>
</div> </div>
<!--
<div class="login mat-typography" fxLayoutGap="80px" fxLayoutAlign="start center" fxFlexFill fxLayout="column">
<h1><img src="assets/icon.png">PiGallery 2</h1>
<form class="form-signin" #LoginForm="ngForm" (submit)="onLogin()" fxLayout="row">
<md-card fxFlex="400px">
<md-card-header>
<md-card-title>Please sign in</md-card-title>
</md-card-header>
<md-card-content fxFlexFill fxLayoutAlign="center center" fxLayout="column">
<div *ngIf="loginError">
{{loginError}}
</div>
<md-input-container>
<input fxFlexFill mdInput autofocus [(ngModel)]="loginCredential.username" name="name" required>
<md-placeholder>
<i class="material-icons app-input-icon">face</i> Username
</md-placeholder>
</md-input-container>
<md-input-container>
<input fxFlexFill mdInput type="password" [(ngModel)]="loginCredential.password" name="password" required>
<md-placeholder>
<i class="material-icons app-input-icon">lock_open</i> Password
</md-placeholder>
</md-input-container>
<div fxLayout="row" fxLayoutAlign="start center">
<md-checkbox [(ngModel)]="loginCredential.rememberM">Remember me</md-checkbox>
</div>
</md-card-content>
<md-card-actions fxLayout="row" fxLayoutAlign="end center">
<button md-raised-button color="primary"
[disabled]="!LoginForm.form.valid"
type="submit"
name="action">Login
</button>
</md-card-actions>
</md-card>
</form>
</div>
-->

View File

@ -1,9 +1,9 @@
import {Component, OnInit} from "@angular/core"; import {Component, OnInit} from "@angular/core";
import {LoginCredential} from "../../../common/entities/LoginCredential"; import {LoginCredential} from "../../../common/entities/LoginCredential";
import {AuthenticationService} from "../model/network/authentication.service"; import {AuthenticationService} from "../model/network/authentication.service";
import {Router} from "@angular/router";
import {ErrorCodes} from "../../../common/entities/Error"; import {ErrorCodes} from "../../../common/entities/Error";
import {Config} from "../../../common/config/public/Config"; import {Config} from "../../../common/config/public/Config";
import {NavigationService} from "../model/navigation.service";
@Component({ @Component({
selector: 'login', selector: 'login',
@ -15,14 +15,14 @@ export class LoginComponent implements OnInit {
loginError: any = null; loginError: any = null;
title: string; title: string;
constructor(private _authService: AuthenticationService, private _router: Router) { constructor(private _authService: AuthenticationService, private _navigation: NavigationService) {
this.loginCredential = new LoginCredential(); this.loginCredential = new LoginCredential();
this.title = Config.Client.applicationTitle; this.title = Config.Client.applicationTitle;
} }
ngOnInit() { ngOnInit() {
if (this._authService.isAuthenticated()) { if (this._authService.isAuthenticated()) {
this._router.navigate(['gallery', "/"]); this._navigation.toGallery();
} }
} }

View File

@ -0,0 +1,38 @@
import {Injectable} from "@angular/core";
import {Router} from "@angular/router";
import {ShareService} from "../gallery/share.service";
@Injectable()
export class NavigationService {
constructor(private _router: Router,
private _shareService: ShareService) {
}
public isLoginPage() {
return this._router.isActive('login', true) || this._router.isActive('shareLogin', true);
}
public async toLogin() {
console.log("toLogin");
await this._shareService.wait();
if (this._shareService.isSharing()) {
return this._router.navigate(["shareLogin"], {queryParams: {sk: this._shareService.getSharingKey()}});
} else {
return this._router.navigate(["login"]);
}
}
public async toGallery() {
console.log("toGallery");
await this._shareService.wait();
if (this._shareService.isSharing()) {
return this._router.navigate(["gallery", ""], {queryParams: {sk: this._shareService.getSharingKey()}});
} else {
return this._router.navigate(["gallery", ""]);
}
}
}

View File

@ -43,9 +43,16 @@ export class AuthenticationService {
public async login(credential: LoginCredential): Promise<UserDTO> { public async login(credential: LoginCredential): Promise<UserDTO> {
const user = await this._userService.login(credential); const user = await this._userService.login(credential);
this.user.next(user); this.user.next(user);
return user; return user;
}
public async shareLogin(password: string): Promise<UserDTO> {
const user = await this._userService.shareLogin(password);
this.user.next(user);
return user;
} }
@ -56,7 +63,9 @@ export class AuthenticationService {
return !!(this.user.value && this.user.value != null); return !!(this.user.value && this.user.value != null);
} }
public isAuthorized(role: UserRoles) {
return this.user.value && this.user.value.role >= role;
}
public logout() { public logout() {
this._userService.logout(); this._userService.logout();

View File

@ -18,7 +18,11 @@ export class UserService {
} }
public login(credential: LoginCredential): Promise<UserDTO> { public login(credential: LoginCredential): Promise<UserDTO> {
return this._networkService.postJson("/user/login", {"loginCredential": credential}); return this._networkService.postJson<UserDTO>("/user/login", {"loginCredential": credential});
}
public async shareLogin(password: string): Promise<UserDTO> {
return this._networkService.postJson<UserDTO>("/share/login?sk=" + this._shareService.getSharingKey(), {"password": password});
} }
public async getSessionUser(): Promise<UserDTO> { public async getSessionUser(): Promise<UserDTO> {

View File

@ -1,12 +1,21 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {NetworkService} from "../../model/network/network.service"; import {NetworkService} from "../../model/network/network.service";
import {DataBaseConfig, IPrivateConfig} from "../../../../common/config/private/IPrivateConfig"; import {DataBaseConfig, IPrivateConfig} from "../../../../common/config/private/IPrivateConfig";
import {NavigationService} from "../../model/navigation.service";
import {UserRoles} from "../../../../common/entities/UserDTO";
import {AuthenticationService} from "../../model/network/authentication.service";
@Injectable() @Injectable()
export class DatabaseSettingsService { export class DatabaseSettingsService {
constructor(private _networkService: NetworkService) { constructor(private _networkService: NetworkService, private _authService: AuthenticationService, private _navigation: NavigationService) {
if (!this._authService.isAuthenticated() ||
this._authService.user.value.role < UserRoles.Admin) {
this._navigation.toLogin();
return;
}
} }
public async getSettings(): Promise<DataBaseConfig> { public async getSettings(): Promise<DataBaseConfig> {

View File

@ -1,10 +1,10 @@
import {Component, OnInit, ViewChild} from "@angular/core"; import {Component, OnInit, ViewChild} from "@angular/core";
import {AuthenticationService} from "../../model/network/authentication.service"; import {AuthenticationService} from "../../model/network/authentication.service";
import {Router} from "@angular/router";
import {UserDTO, UserRoles} from "../../../../common/entities/UserDTO"; import {UserDTO, UserRoles} from "../../../../common/entities/UserDTO";
import {Utils} from "../../../../common/Utils"; import {Utils} from "../../../../common/Utils";
import {UserManagerSettingsService} from "./usermanager.settings.service"; import {UserManagerSettingsService} from "./usermanager.settings.service";
import {ModalDirective} from "ngx-bootstrap/modal"; import {ModalDirective} from "ngx-bootstrap/modal";
import {NavigationService} from "../../model/navigation.service";
@Component({ @Component({
selector: 'settings-usermanager', selector: 'settings-usermanager',
@ -18,16 +18,18 @@ export class UserMangerSettingsComponent implements OnInit {
public userRoles: Array<any> = []; public userRoles: Array<any> = [];
public users: Array<UserDTO> = []; public users: Array<UserDTO> = [];
constructor(private _authService: AuthenticationService, private _router: Router, private _userSettings: UserManagerSettingsService) { constructor(private _authService: AuthenticationService, private _navigation: NavigationService, private _userSettings: UserManagerSettingsService) {
} }
ngOnInit() { ngOnInit() {
if (!this._authService.isAuthenticated() || this._authService.user.value.role < UserRoles.Admin) { if (!this._authService.isAuthenticated() ||
this._router.navigate(['login']); this._authService.user.value.role < UserRoles.Admin) {
this._navigation.toLogin();
return; return;
} }
this.userRoles = Utils this.userRoles = Utils
.enumToArray(UserRoles) .enumToArray(UserRoles)
.filter(r => r.key != UserRoles.LimitedGuest)
.filter(r => r.key <= this._authService.user.value.role) .filter(r => r.key <= this._authService.user.value.role)
.sort((a, b) => a.key - b.key); .sort((a, b) => a.key - b.key);

View File

@ -1,3 +1,57 @@
body { .container {
background: #eee; }
.title h1 {
font-size: 80px;
font-weight: bold;
white-space: nowrap;
}
.title img {
height: 80px;
}
.title {
margin-top: 40px;
width: 100%;
text-align: center;
}
.card {
padding: 15px;
max-width: 350px;
width: 100% !important;
background-color: #F7F7F7;
margin: 0 auto;
border-radius: 2px;
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
overflow: hidden;
height: 150px;
margin-top: 0px;
}
/*Margin by pixel:*/
@media screen and ( min-height: 400px ) {
.card {
margin-top: calc((100vh - 120px - 150px) / 2 - 60px)
}
}
.has-margin {
margin-bottom: 10px;
}
.input-group .form-control, .checkbox {
padding: 10px;
font-size: 16px;
}
.error-message {
color: #d9534f;
}
button {
width: 100%;
font-size: 18px;
} }

View File

@ -1,15 +1,41 @@
<div class="container"> <div class="container">
<div class="col-sm-offset-3 col-sm-6 col-lg-4 col-lg-offset-4">
<form class="form-signin" #LoginForm="ngForm"> <div class="row title">
<h2 class="form-signin-heading">The link is password protected</h2> <h1><img src="assets/icon.png"/>{{title}}</h1>
<div *ngIf="loginError">
{{loginError}}
</div>
<input type="password" class="form-control" placeholder="Password"
[(ngModel)]="loginCredential.password" name="password" required>
<br/>
<button class="btn btn-lg btn-primary btn-block" [disabled]="!LoginForm.form.valid" (click)="onLogin()">OK
</button>
</form>
</div> </div>
</div> <!-- /container -->
<div class="row card">
<div class="col-md-12">
<form name="form" id="form" class="form-horizontal" #LoginForm="ngForm" (submit)="onLogin()">
<div class="error-message">
{{loginError}}&nbsp;
</div>
<div class="input-group has-margin">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input id="password"
class="form-control"
type="password"
[(ngModel)]="password"
name="password"
placeholder="Password"
required>
</div>
<div class="form-group">
<!-- Button -->
<div class="col-sm-12 controls">
<button class="btn btn-primary pull-right"
[disabled]="!LoginForm.form.valid"
type="submit"
name="action">Enter
</button>
</div>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,8 +1,8 @@
import {Component, OnInit} from "@angular/core"; import {Component, OnInit} from "@angular/core";
import {LoginCredential} from "../../../common/entities/LoginCredential";
import {AuthenticationService} from "../model/network/authentication.service"; import {AuthenticationService} from "../model/network/authentication.service";
import {Router} from "@angular/router";
import {ErrorCodes} from "../../../common/entities/Error"; import {ErrorCodes} from "../../../common/entities/Error";
import {Config} from "../../../common/config/public/Config";
import {NavigationService} from "../model/navigation.service";
@Component({ @Component({
selector: 'share-login', selector: 'share-login',
@ -10,16 +10,17 @@ import {ErrorCodes} from "../../../common/entities/Error";
styleUrls: ['./share-login.component.css'], styleUrls: ['./share-login.component.css'],
}) })
export class ShareLoginComponent implements OnInit { export class ShareLoginComponent implements OnInit {
loginCredential: LoginCredential; password: string;
loginError: any = null; loginError: any = null;
title: string;
constructor(private _authService: AuthenticationService, private _router: Router) { constructor(private _authService: AuthenticationService, private _navigation: NavigationService) {
this.loginCredential = new LoginCredential(); this.title = Config.Client.applicationTitle;
} }
ngOnInit() { ngOnInit() {
if (this._authService.isAuthenticated()) { if (this._authService.isAuthenticated()) {
this._router.navigate(['gallery', "/"]); this._navigation.toGallery();
} }
} }
@ -27,11 +28,11 @@ export class ShareLoginComponent implements OnInit {
this.loginError = null; this.loginError = null;
try { try {
await this._authService.login(this.loginCredential); await this._authService.shareLogin(this.password);
} catch (error) { } catch (error) {
if (error && error.code === ErrorCodes.CREDENTIAL_NOT_FOUND) { if (error && error.code === ErrorCodes.CREDENTIAL_NOT_FOUND) {
this.loginError = "Wrong username or password"; this.loginError = "Wrong password";
} }
} }
} }

View File

@ -85,6 +85,7 @@
"ng2-slim-loading-bar": "^4.0.0", "ng2-slim-loading-bar": "^4.0.0",
"ng2-toastr": "^4.1.2", "ng2-toastr": "^4.1.2",
"ngx-bootstrap": "^1.7.1", "ngx-bootstrap": "^1.7.1",
"ngx-clipboard": "^8.0.3",
"phantomjs-prebuilt": "^2.1.14", "phantomjs-prebuilt": "^2.1.14",
"protractor": "^5.1.2", "protractor": "^5.1.2",
"remap-istanbul": "^0.9.5", "remap-istanbul": "^0.9.5",

View File

@ -86,7 +86,7 @@ describe('Authentication middleware', () => {
let req: any = { let req: any = {
session: { session: {
user: { user: {
role: UserRoles.Guest role: UserRoles.LimitedGuest
} }
} }
}; };
@ -94,7 +94,7 @@ describe('Authentication middleware', () => {
expect(err).to.be.undefined; expect(err).to.be.undefined;
done(); done();
}; };
AuthenticationMWs.authorise(UserRoles.Guest)(req, null, next); AuthenticationMWs.authorise(UserRoles.LimitedGuest)(req, null, next);
}); });
@ -102,7 +102,7 @@ describe('Authentication middleware', () => {
let req: any = { let req: any = {
session: { session: {
user: { user: {
role: UserRoles.Guest role: UserRoles.LimitedGuest
} }
} }
}; };
@ -230,7 +230,7 @@ describe('Authentication middleware', () => {
let req: any = { let req: any = {
session: { session: {
user: { user: {
role: UserRoles.Guest role: UserRoles.LimitedGuest
} }
} }
}; };