#4 - add support for sharing and viewing a page publicly without login

master
Garrett Mills 3 years ago
parent 4f14a40994
commit 01c2fc18f2
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246

@ -3,6 +3,7 @@ import { PreloadAllModules, RouterModule, Routes } from '@angular/router';
import {LoginPage} from './pages/login/login.page';
import {AuthService} from './service/auth.service';
import {GuestOnlyGuard} from './service/guard/GuestOnly.guard';
import {EditorGuard} from './service/guard/Editor.guard';
const routes: Routes = [
{
@ -17,7 +18,7 @@ const routes: Routes = [
},
{
path: 'editor',
canActivate: [AuthService],
canActivate: [EditorGuard],
loadChildren: () => import('./components/components.module').then( m => m.ComponentsModule)
},
{

@ -17,6 +17,9 @@
</ion-item>
</ion-card>
</ion-col>
<ion-col>
<div class="not-available" *ngIf="fileRecords.length < 1 && readonly">There are no files in this group yet.</div>
</ion-col>
</ion-row>
</ion-grid>
</div>

@ -2,11 +2,11 @@ div.files-wrapper {
border: 2px solid #8c8c8c;
border-radius: 3px;
margin-top: 15px;
}
&.not-available {
height: 150px;
text-align: center;
padding-top: 40px;
color: #494949;
}
.not-available {
height: 150px;
text-align: center;
padding-top: 40px;
color: #494949;
}

@ -50,6 +50,8 @@ export class FilesComponent extends EditorNodeContract implements OnInit {
this.node.Value = {};
}
console.log('files load', this)
if ( !this.node.Value.Value && !this.readonly ) {
this.dbRecord = await this.api.createFileGroup(this.page.UUID, this.node.UUID);
this.fileRecords = this.dbRecord.files;

@ -22,9 +22,9 @@
(click)="dismiss()"
><i class="fa fa-times"></i></button>
</div>
<ion-buttons *ngIf="!readonly" style="flex-wrap: wrap;">
<ion-button [disabled]="isLoading" (click)="onActionClick('folder-add')"><i class="fa fa-folder-plus"></i>&nbsp;&nbsp;Folder</ion-button>
<ion-button [disabled]="isLoading" (click)="onUploadFilesClick($event)"><i class="fa fa-upload"></i>&nbsp;&nbsp;Upload Files</ion-button>
<ion-buttons style="flex-wrap: wrap;">
<ion-button *ngIf="!readonly" [disabled]="isLoading" (click)="onActionClick('folder-add')"><i class="fa fa-folder-plus"></i>&nbsp;&nbsp;Folder</ion-button>
<ion-button *ngIf="!readonly" [disabled]="isLoading" (click)="onUploadFilesClick($event)"><i class="fa fa-upload"></i>&nbsp;&nbsp;Upload Files</ion-button>
<ion-button [disabled]="isLoading" (click)="openFileBox()" *ngIf="!fullPage"><i class="fa fa-external-link-alt"></i>&nbsp;&nbsp;Open</ion-button>
<div class="buffer"></div>
@ -38,7 +38,7 @@
></ion-input>
</div>
<input type="file" name="fileInput" #fileInput multiple style="display: none;" (change)="onUploadFilesChange($event)">
<input *ngIf="!readonly" type="file" name="fileInput" #fileInput multiple style="display: none;" (change)="onUploadFilesChange($event)">
</ion-buttons>
</ion-toolbar>
<div class="content-wrapper" (contextmenu)="onSurfaceContextMenu($event)">

@ -1,6 +1,6 @@
.container {
display: flex;
min-height: 600px;
min-height: 200px;
flex-direction: row;
.editor-container, .display {

@ -22,9 +22,13 @@
<ion-button fill="outline" color="medium" (click)="getShareLink('update')">
<ion-icon name="link" color="dark"></ion-icon>&nbsp;&nbsp;Edit
</ion-button>
<ion-button fill="outline" color="medium" (click)="getShareLink('manage')">
<ion-button fill="outline" color="medium" (click)="getShareLink('manage')" [disabled]="publicLink">
<ion-icon name="link" color="dark"></ion-icon>&nbsp;&nbsp;Manage
</ion-button>
<ion-item title="If checked, the link will never expire, and can be accessed by anyone without logging in.">
<ion-checkbox [(ngModel)]="publicLink"></ion-checkbox>
<ion-label>&nbsp;&nbsp;Public link?</ion-label>
</ion-item>
</ion-buttons>
</ion-col>
</ion-row>
@ -49,7 +53,7 @@
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'update')">
<ion-icon name="create" color="medium"></ion-icon>
</ion-button>
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'manage')">
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.view[i], 'manage')" *ngIf="!group.public">
<ion-icon name="build" color="medium"></ion-icon>
</ion-button>
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.view[i])">
@ -66,7 +70,7 @@
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'view')">
<ion-icon name="eye" color="medium"></ion-icon>
</ion-button>
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'manage')">
<ion-button fill="invisible" (click)="setShareLevel(sharingInfo.update[i], 'manage')" *ngIf="!group.public">
<ion-icon name="build" color="medium"></ion-icon>
</ion-button>
<ion-button fill="invisible" (click)="unsharePage(sharingInfo.update[i])">

@ -2,6 +2,7 @@ import {Component, Input, OnInit} from '@angular/core';
import {ModalController} from '@ionic/angular';
import {ApiService} from '../../../service/api.service';
import {Observable} from 'rxjs';
import {environment} from '../../../../environments/environment';
@Component({
selector: 'app-selector',
@ -11,11 +12,12 @@ import {Observable} from 'rxjs';
export class SelectorComponent implements OnInit {
@Input() node: any;
public sharingInfo: {
view: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
update: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
manage: Array<{username: string, id: string, level: 'view'|'update'|'manage'}>,
view: Array<{username: string, public?: boolean, id: string, level: 'view'|'update'|'manage'}>,
update: Array<{username: string, public?: boolean, id: string, level: 'view'|'update'|'manage'}>,
manage: Array<{username: string, public?: boolean, id: string, level: 'view'|'update'|'manage'}>,
} = {view: [], update: [], manage: []};
public generatedLink = '';
public publicLink = false;
constructor(
protected modals: ModalController,
@ -54,7 +56,7 @@ export class SelectorComponent implements OnInit {
}
setShareLevel(group, level) {
this.api.post(`/share/page/${this.node.id}/share`, { user_id: group.id, level }).subscribe(result => {
this.api.post(`/share/page/${this.node.id}/share?public=${!!group.public}`, { user_id: group.id, level }).subscribe(result => {
this.loadShareInfo().subscribe(data => {
this.sharingInfo = data;
});
@ -62,7 +64,7 @@ export class SelectorComponent implements OnInit {
}
unsharePage(group) {
this.api.post(`/share/page/${this.node.id}/revoke`, { user_id: group.id }).subscribe(result => {
this.api.post(`/share/page/${this.node.id}/revoke?public=${!!group.public}`, { user_id: group.id }).subscribe(result => {
this.loadShareInfo().subscribe(data => {
this.sharingInfo = data;
});
@ -70,8 +72,13 @@ export class SelectorComponent implements OnInit {
}
getShareLink(level) {
this.api.get(`/share/page/${this.node.id}/link/${level}`).subscribe(result => {
this.generatedLink = result.data.link;
this.api.get(`/share/page/${this.node.id}/link/${level}?public=${this.publicLink}`).subscribe(result => {
if ( this.publicLink ) {
this.generatedLink = `${window.location.origin}${environment.appBase}editor;id=${this.node.id}`;
this.ngOnInit();
} else {
this.generatedLink = result.data.link;
}
});
}
}

@ -1,6 +1,7 @@
import {Component, OnInit} from '@angular/core';
import {ApiService} from '../service/api.service';
import {isDebug, debug} from '../utility';
import {Router} from '@angular/router';
@Component({
selector: 'app-home',
@ -10,6 +11,7 @@ import {isDebug, debug} from '../utility';
export class HomePage implements OnInit {
constructor(
public readonly api: ApiService,
public readonly router: Router,
) {}
ngOnInit() {
@ -17,10 +19,10 @@ export class HomePage implements OnInit {
if ( isDebug() ) {
debug('Forcing authentication...');
setTimeout(() => {
this.api.forceRestart();
this.router.navigate(['/login']);
}, 2000);
} else {
this.api.forceRestart();
this.router.navigate(['/login']);
}
}
}

@ -1463,6 +1463,36 @@ export class ApiService {
});
}
public checkPermission(permission: string): Promise<boolean> {
return new Promise(async (res, rej) => {
if ( this.isOffline ) {
return rej(new ResourceNotAvailableOfflineError());
}
this.post('/share/check', { permission }).subscribe({
next: result => {
return res(result.data.check);
},
error: rej,
});
});
}
public checkPagePermission(PageId: string, level: string = 'view'): Promise<boolean> {
return new Promise(async (res, rej) => {
if ( this.isOffline ) {
return rej(new ResourceNotAvailableOfflineError());
}
this.post(`/share/check-page/${PageId}/${level}`, {}).subscribe({
next: result => {
return res(result.data.check);
},
error: rej,
});
});
}
public attemptLogin(uid: string, password: string): Promise<{ success: boolean, message?: string }> {
return new Promise(async (res, rej) => {
if ( this.isOffline ) {

@ -0,0 +1,43 @@
import {ActivatedRoute, ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {ApiService} from '../api.service';
import {NavigationService} from '../navigation.service';
import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class EditorGuard implements CanActivate {
constructor(
protected readonly api: ApiService,
protected readonly router: Router,
protected readonly nav: NavigationService,
) { }
async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
const checkCanActivate = async () => {
const PageId = route.paramMap.get('id');
const canView = await this.api.checkPagePermission(String(PageId));
if ( !canView ) {
await this.router.navigate(['/login']);
return false;
} else {
return true;
}
};
return new Promise(async res => {
if ( !this.nav.initialized$.getValue() ) {
const sub = this.nav.initialized$.subscribe(async initialized => {
if ( initialized ) {
sub.unsubscribe();
return res(await checkCanActivate());
}
});
} else {
return res(await checkCanActivate());
}
});
}
}

@ -6,5 +6,6 @@ export const environment = {
versionUrl: '/i/version.html?ngsw-bypass',
logoUrl: '/i/assets/icon/logo_lines.svg',
starshipUrl: '/auth/starship_oauth/login',
appBase: '/i/',
outputDebug: false,
};

@ -10,6 +10,7 @@ export const environment = {
versionUrl: '/link_api/assets/version.html?ngsw-bypass',
logoUrl: '/assets/icon/logo_lines.svg',
starshipUrl: '/link_api/auth/starship_oauth/login',
appBase: '/',
outputDebug: true,
};

Loading…
Cancel
Save