1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-09-16 09:16:27 +02:00

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 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
* keyboard support for navigation - `In progress`
* keyboard support for navigation
* showing low-res thumbnail while full image loads
* Information panel for showing **Exif info** - `In progress`
* Client side caching (directories and search results)
* 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**
* both modes supports
* user management
@@ -77,11 +77,11 @@ To configure it. Run `PiGallery2` first to create `config.json` file, then edit
* faster directory listing
* searching
* instant search, auto complete
* sharing - `In progress`
* sharing
* setting link expiration time
* Nice design - `In progress`
* responsive design (phone, tablet desktop support)
* 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
* 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 {Logger} from "../Logger";
import {Config} from "../../common/config/private/Config";
import {UserUtil} from "../../common/entities/UserDTO";
import {UserDTO} from "../../common/entities/UserDTO";
const LOG_TAG = "[GalleryMWs]";
@@ -32,7 +32,7 @@ export class GalleryMWs {
req.session.user.permissions.length > 0 &&
req.session.user.permissions[0] != "/") {
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);
return next();

View File

@@ -1,7 +1,7 @@
///<reference path="../customtypings/ExtendedRequest.d.ts"/>
import {NextFunction, Request, Response} from "express";
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 {Config} from "../../../common/config/private/Config";
@@ -9,12 +9,15 @@ export class AuthenticationMWs {
private static async getSharingUser(req: Request) {
if (Config.Client.Sharing.enabled === true &&
Config.Client.Sharing.passwordProtected === false &&
(!!req.query.sk || !!req.params.sharingKey)) {
const sharing = await ObjectManagerRepository.getInstance().SharingManager.findOne({
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;
}
@@ -22,7 +25,7 @@ export class AuthenticationMWs {
if (sharing.includeSubfolders == true) {
path += "*";
}
return <UserDTO>{name: "Guest", role: UserRoles.Guest, permissions: [path]};
return <UserDTO>{name: "Guest", role: UserRoles.LimitedGuest, permissions: [path]};
}
return null;
@@ -67,7 +70,7 @@ export class AuthenticationMWs {
}
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();
}
@@ -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) {
delete req.session.user;
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: "admin", password: "admin", role: UserRoles.Admin});
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 {RenderingMWs} from "../middlewares/RenderingMWs";
import {ThumbnailGeneratorMWs} from "../middlewares/thumbnail/ThumbnailGeneratorMWs";
import {UserRoles} from "../../common/entities/UserDTO";
export class GalleryRouter {
public static route(app: any) {
@@ -31,6 +32,7 @@ export class GalleryRouter {
private static addGetImage(app) {
app.get(["/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))"],
AuthenticationMWs.authenticate,
//TODO: authorize path
GalleryMWs.loadImage,
RenderingMWs.renderFile
);
@@ -39,6 +41,7 @@ export class GalleryRouter {
private static addGetImageThumbnail(app) {
app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/thumbnail/:size?",
AuthenticationMWs.authenticate,
//TODO: authorize path
GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateThumbnail,
RenderingMWs.renderFile
@@ -48,6 +51,7 @@ export class GalleryRouter {
private static addGetImageIcon(app) {
app.get("/api/gallery/content/:imagePath(*\.(jpg|bmp|png|gif|jpeg))/icon",
AuthenticationMWs.authenticate,
//TODO: authorize path
GalleryMWs.loadImage,
ThumbnailGeneratorMWs.generateIcon,
RenderingMWs.renderFile
@@ -57,6 +61,7 @@ export class GalleryRouter {
private static addSearch(app) {
app.get("/api/search/:text",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),
GalleryMWs.search,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,
@@ -67,6 +72,7 @@ export class GalleryRouter {
private static addInstantSearch(app) {
app.get("/api/instant-search/:text",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),
GalleryMWs.instantSearch,
ThumbnailGeneratorMWs.addThumbnailInformation,
GalleryMWs.removeCyclicDirectoryReferences,
@@ -77,6 +83,7 @@ export class GalleryRouter {
private static addAutoComplete(app) {
app.get("/api/autocomplete/:text",
AuthenticationMWs.authenticate,
AuthenticationMWs.authorise(UserRoles.Guest),
GalleryMWs.autocomplete,
RenderingMWs.renderResult
);

View File

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

View File

@@ -8,7 +8,13 @@ declare module ServerInject {
export let Config = new PublicConfigClass();
if (typeof ServerInject !== "undefined" && typeof ServerInject.ConfigInject !== "undefined") {
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;
authenticationRequired: boolean;
googleApiKey: string;
publicUrl: string;
}
/**
@@ -46,7 +47,8 @@ export class PublicConfigClass {
enableOnScrollRendering: true,
enableOnScrollThumbnailPrioritising: true,
authenticationRequired: true,
googleApiKey: ""
googleApiKey: "",
publicUrl: ""
};
}

View File

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

View File

@@ -1,8 +1,8 @@
import {DirectoryDTO} from "./DirectoryDTO";
import {Utils} from "../Utils";
export enum UserRoles{
Guest = 0,
TrustedGuest = 1,
LimitedGuest = 0,
Guest = 1,
User = 2,
Admin = 3,
Developer = 4,
@@ -17,7 +17,7 @@ export interface UserDTO {
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 => {
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 {Title} from "@angular/platform-browser";
import {NotificationService} from "./model/notification.service";
import {ShareService} from "./gallery/share.service";
@Component({
@@ -16,23 +17,23 @@ export class AppComponent implements OnInit {
constructor(private _router: Router,
private _authenticationService: AuthenticationService,
private _shareService: ShareService,
private _title: Title, vcr: ViewContainerRef,
notificatin: NotificationService) {
notificatin.setRootViewContainerRef(vcr);
}
ngOnInit() {
async ngOnInit() {
this._title.setTitle(Config.Client.applicationTitle);
await this._shareService.wait();
this._authenticationService.user.subscribe((user: UserDTO) => {
if (user != null) {
if (this._router.isActive('login', true)) {
console.log("routing");
this._router.navigate(["gallery", ""]);
if (this._authenticationService.isAuthenticated()) {
if (this.isLoginPage()) {
return this.toGallery();
}
} else {
if (!this._router.isActive('login', true)) {
console.log("routing");
this._router.navigate(["login"]);
if (!this.isLoginPage()) {
return this.toLogin();
}
}
@@ -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 {NotificationService} from "./model/notification.service";
import {ClipboardModule} from "ngx-clipboard";
import {NavigationService} from "./model/navigation.service";
@Injectable()
export class GoogleMapsConfig {
apiKey: string;
@@ -58,6 +61,7 @@ export class GoogleMapsConfig {
HttpModule,
BrowserAnimationsModule,
appRoutes,
ClipboardModule,
ToastModule.forRoot(),
ModalModule.forRoot(),
AgmCoreModule.forRoot(),
@@ -98,6 +102,7 @@ export class GoogleMapsConfig {
ThumbnailManagerService,
NotificationService,
FullScreenService,
NavigationService,
OverlayService],
bootstrap: [AppComponent]

View File

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

View File

@@ -2,6 +2,12 @@
<app-frame>
<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">
<gallery-search #search></gallery-search>
</li>

View File

@@ -9,6 +9,9 @@ import {Config} from "../../../common/config/public/Config";
import {DirectoryDTO} from "../../../common/entities/DirectoryDTO";
import {SearchResultDTO} from "../../../common/entities/SearchResult";
import {ShareService} from "./share.service";
import {NavigationService} from "../model/navigation.service";
import {UserRoles} from "../../../common/entities/UserDTO";
import {Observable} from "rxjs/Rx";
@Component({
selector: 'gallery',
@@ -20,42 +23,61 @@ export class GalleryComponent implements OnInit, OnDestroy {
@ViewChild(GallerySearchComponent) search: GallerySearchComponent;
@ViewChild(GalleryGridComponent) grid: GalleryGridComponent;
public showSearchBar: boolean = true;
public showShare: boolean = true;
public showSearchBar: boolean = false;
public showShare: boolean = false;
public directories: DirectoryDTO[] = [];
public isPhotoWithLocation = false;
private $counter;
private subscription = {
content: null,
route: null
route: null,
timer: null
};
public countDown = null;
constructor(public _galleryService: GalleryService,
private _authService: AuthenticationService,
private _router: Router,
private shareService: ShareService,
private _route: ActivatedRoute) {
private _route: ActivatedRoute,
private _navigation: NavigationService) {
this.showSearchBar = Config.Client.Search.searchEnabled;
this.showShare = Config.Client.Sharing.enabled;
}
ngOnInit() {
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']);
}
updateTimer(t: number) {
if (this.shareService.sharing.value == null) {
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.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() {
@@ -109,7 +131,7 @@ export class GalleryComponent implements OnInit, OnDestroy {
}
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()}});
return;
}

View File

@@ -1,7 +1,7 @@
import {Injectable} from "@angular/core";
import {NetworkService} from "../model/network/network.service";
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 {GalleryCacheService} from "./cache.gallery.service";
import {BehaviorSubject} from "rxjs/BehaviorSubject";
@@ -53,7 +53,7 @@ export class GalleryService {
}
DirectoryUtil.addReferences(cw.directory);
DirectoryDTO.addReferences(cw.directory);
this.lastDirectory = cw.directory;

View File

@@ -1,7 +1,7 @@
import {Component, Input, OnChanges} from "@angular/core";
import {DirectoryDTO} from "../../../../common/entities/DirectoryDTO";
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 {ShareService} from "../share.service";
@@ -49,7 +49,7 @@ export class GalleryNavigatorComponent implements OnChanges {
if (dirs.length == 0) {
arr.push({name: "Images", route: null});
} 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) {
arr.push({name: name, route: null});
} 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 {CreateSharingDTO, SharingDTO} from "../../../common/entities/SharingDTO";
import {Router, RoutesRecognized} from "@angular/router";
import {BehaviorSubject} from "rxjs/BehaviorSubject";
@Injectable()
export class ShareService {
public sharing: BehaviorSubject<SharingDTO>;
param = null;
queryParam = null;
sharingKey = null;
@@ -15,7 +17,7 @@ export class ShareService {
constructor(private _networkService: NetworkService, private router: Router) {
this.sharing = new BehaviorSubject(null);
this.ReadyPR = new Promise((resolve) => {
if (this.inited == true) {
return resolve();
@@ -27,7 +29,11 @@ export class ShareService {
if (val instanceof RoutesRecognized) {
this.param = val.state.root.firstChild.params["sharingKey"] || null;
this.queryParam = val.state.root.firstChild.queryParams["sk"] || null;
const changed = this.sharingKey != this.param || this.queryParam;
if (changed) {
this.sharingKey = this.param || this.queryParam;
this.getSharing();
}
if (this.resolve) {
this.resolve();
this.inited = true;
@@ -43,7 +49,7 @@ export class ShareService {
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, {
createSharing: <CreateSharingDTO>{
includeSubfolders: includeSubfolders,
@@ -70,4 +76,10 @@ export class ShareService {
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;
}
button {
padding-left: 0;
padding-right: 0;
.full-width {
width: 100%;
}
.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"
data-toggle="modal" data-target="#shareModal" [disabled]="!enabled" (click)="get()">
<button id="shareButton" class="btn btn-default navbar-btn btn-link"
type="button" [disabled]="!enabled" (click)="showModal()">
<span class="glyphicon glyphicon-share-alt"></span>
Share
</button>
<!-- sharing Modal-->
<div class="modal fade" id="shareModal" tabindex="-1" role="dialog" aria-labelledby="shareModalLabel"
data-backdrop="false"
<div bsModal #shareModal="bs-modal"
class="modal fade" id="shareModal"
tabindex="-1" role="dialog" aria-labelledby="shareModalLabel"
[config]="{ backdrop: false }"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<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>
<h4 class="modal-title" id="shareModalLabel">Share</h4>
</div>
<div class="modal-body">
<div class="row">
<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">
</div>
<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>
</div>
</div>
<hr/>
<div class="form-horizontal">
<div class="form-group" style="padding: 0 15px 0 15px;">
<div style="display: inline;">
<label class="control-label">sharing:</label>
<div class="form-control-static" id="sharingPath">{{currentDir}}</div>
</div>
<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 class="row">
<div class="col-sm-4">
Valid:
<p id="sliderText"></p>
<div class="col-sm-2">
<label class="control-label">Sharing:</label>
</div>
<div class="col-sm-10">
<input disabled type="text"
class="full-width form-control"
[ngModel]="currentDir">
</div>
</div>
<div class="row">
<div class="col-sm-2">
<label class="control-label">Include subfolders:</label>
</div>
<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"
type="number" min="0" step="1"/>
</div>
<div class="col-sm-4">
<select class="form-control" [(ngModel)]="input.valid.type" (change)="update()" name="validType" required>
<div class="col-sm-3" style="padding-left: 1px">
<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>
</select>
</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>

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 {ShareService} from "../share.service";
import {GalleryService} from "../gallery.service";
import {ContentWrapper} from "../../../../common/entities/ConentWrapper";
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({
selector: 'gallery-share',
@@ -11,6 +15,7 @@ import {SharingDTO} from "../../../../common/entities/SharingDTO";
styleUrls: ['./share.gallery.component.css'],
})
export class GalleryShareComponent implements OnInit, OnDestroy {
@ViewChild('shareModal') public childModal: ModalDirective;
enabled: boolean = true;
url: string = "";
@@ -26,8 +31,11 @@ export class GalleryShareComponent implements OnInit, OnDestroy {
currentDir: string = "";
sharing: SharingDTO;
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);
@@ -42,6 +50,7 @@ export class GalleryShareComponent implements OnInit, OnDestroy {
}
this.currentDir = Utils.concatUrls(content.directory.path, content.directory.name);
});
this.passwordProtection = Config.Client.Sharing.passwordProtected;
}
ngOnDestroy() {
@@ -68,14 +77,23 @@ export class GalleryShareComponent implements OnInit, OnDestroy {
this.url = "loading..";
this.sharing = await this._sharingService.updateSharing(this.currentDir, this.sharing.id, this.input.includeSubfolders, this.calcValidity());
console.log(this.sharing);
this.url = location.origin + "/share/" + this.sharing.sharingKey
this.url = Config.Client.publicUrl + "/share/" + this.sharing.sharingKey
}
async get() {
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);
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 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 {LoginCredential} from "../../../common/entities/LoginCredential";
import {AuthenticationService} from "../model/network/authentication.service";
import {Router} from "@angular/router";
import {ErrorCodes} from "../../../common/entities/Error";
import {Config} from "../../../common/config/public/Config";
import {NavigationService} from "../model/navigation.service";
@Component({
selector: 'login',
@@ -15,14 +15,14 @@ export class LoginComponent implements OnInit {
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() {
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

@@ -49,6 +49,13 @@ export class AuthenticationService {
}
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;
@@ -56,7 +63,9 @@ export class AuthenticationService {
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();

View File

@@ -18,7 +18,11 @@ export class UserService {
}
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> {

View File

@@ -1,12 +1,21 @@
import {Injectable} from "@angular/core";
import {NetworkService} from "../../model/network/network.service";
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()
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> {

View File

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

View File

@@ -1,3 +1,57 @@
body {
background: #eee;
.container {
}
.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="col-sm-offset-3 col-sm-6 col-lg-4 col-lg-offset-4">
<form class="form-signin" #LoginForm="ngForm">
<h2 class="form-signin-heading">The link is password protected</h2>
<div *ngIf="loginError">
{{loginError}}
<div class="row title">
<h1><img src="assets/icon.png"/>{{title}}</h1>
</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
<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>
</form>
</div>
</div> <!-- /container -->
</div>
</form>
</div>
</div>
</div>

View File

@@ -1,8 +1,8 @@
import {Component, OnInit} from "@angular/core";
import {LoginCredential} from "../../../common/entities/LoginCredential";
import {AuthenticationService} from "../model/network/authentication.service";
import {Router} from "@angular/router";
import {ErrorCodes} from "../../../common/entities/Error";
import {Config} from "../../../common/config/public/Config";
import {NavigationService} from "../model/navigation.service";
@Component({
selector: 'share-login',
@@ -10,16 +10,17 @@ import {ErrorCodes} from "../../../common/entities/Error";
styleUrls: ['./share-login.component.css'],
})
export class ShareLoginComponent implements OnInit {
loginCredential: LoginCredential;
password: string;
loginError: any = null;
title: string;
constructor(private _authService: AuthenticationService, private _router: Router) {
this.loginCredential = new LoginCredential();
constructor(private _authService: AuthenticationService, private _navigation: NavigationService) {
this.title = Config.Client.applicationTitle;
}
ngOnInit() {
if (this._authService.isAuthenticated()) {
this._router.navigate(['gallery', "/"]);
this._navigation.toGallery();
}
}
@@ -27,11 +28,11 @@ export class ShareLoginComponent implements OnInit {
this.loginError = null;
try {
await this._authService.login(this.loginCredential);
await this._authService.shareLogin(this.password);
} catch (error) {
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-toastr": "^4.1.2",
"ngx-bootstrap": "^1.7.1",
"ngx-clipboard": "^8.0.3",
"phantomjs-prebuilt": "^2.1.14",
"protractor": "^5.1.2",
"remap-istanbul": "^0.9.5",

View File

@@ -86,7 +86,7 @@ describe('Authentication middleware', () => {
let req: any = {
session: {
user: {
role: UserRoles.Guest
role: UserRoles.LimitedGuest
}
}
};
@@ -94,7 +94,7 @@ describe('Authentication middleware', () => {
expect(err).to.be.undefined;
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 = {
session: {
user: {
role: UserRoles.Guest
role: UserRoles.LimitedGuest
}
}
};
@@ -230,7 +230,7 @@ describe('Authentication middleware', () => {
let req: any = {
session: {
user: {
role: UserRoles.Guest
role: UserRoles.LimitedGuest
}
}
};