get pubkey and encrypt password on login (#1965)

* get pubkey and encrypt password on login

* only encrypt password if insecure context

* fix logic

* fix secure context conditional

* get-pubkey to auth api

* save two lines

* feat: Add the backend to the ui (#1968)

* hide app show if insecure and update copy for LAN

* show install progress when insecure and prevent backup and restore

* ask remove USB

Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
Co-authored-by: J M <2364004+Blu-J@users.noreply.github.com>
This commit is contained in:
Lucy C
2022-11-26 09:47:00 -07:00
committed by Aiden McClelland
parent bd4c431eb4
commit 9146c31abf
24 changed files with 381 additions and 170 deletions

View File

@@ -1,40 +1,77 @@
<ng-container *ngIf="pkg$ | async as pkg">
<app-show-header [pkg]="pkg"></app-show-header>
<ion-content *ngIf="pkg | toDependencies as dependencies">
<ion-item-group *ngIf="pkg | toStatus as status">
<!-- ** status ** -->
<app-show-status
[pkg]="pkg"
[dependencies]="dependencies"
[status]="status"
></app-show-status>
<!-- ** installed && !backing-up ** -->
<ng-container *ngIf="isInstalled(pkg) && !isBackingUp(status)">
<!-- ** health checks ** -->
<app-show-health-checks
*ngIf="isRunning(status)"
[pkg]="pkg"
></app-show-health-checks>
<!-- ** dependencies ** -->
<app-show-dependencies
*ngIf="dependencies.length"
[dependencies]="dependencies"
></app-show-dependencies>
<!-- ** menu ** -->
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
<!-- ** additional ** -->
<app-show-additional [pkg]="pkg"></app-show-additional>
</ng-container>
</ion-item-group>
<ion-content>
<!-- ** installing, updating, restoring ** -->
<ion-content *ngIf="showProgress(pkg)">
<ng-container *ngIf="showProgress(pkg); else installed">
<app-show-progress
*ngIf="pkg | progressData as progressData"
[pkg]="pkg"
[progressData]="progressData"
></app-show-progress>
</ion-content>
</ng-container>
<!-- Installed -->
<ng-template #installed>
<!-- SECURE -->
<ng-container *ngIf="secure; else insecure">
<ng-container *ngIf="pkg | toDependencies as dependencies">
<ion-item-group *ngIf="pkg | toStatus as status">
<!-- ** status ** -->
<app-show-status
[pkg]="pkg"
[dependencies]="dependencies"
[status]="status"
></app-show-status>
<!-- ** installed && !backing-up ** -->
<ng-container *ngIf="isInstalled(pkg) && !isBackingUp(status)">
<!-- ** health checks ** -->
<app-show-health-checks
*ngIf="isRunning(status)"
[pkg]="pkg"
></app-show-health-checks>
<!-- ** dependencies ** -->
<app-show-dependencies
*ngIf="dependencies.length"
[dependencies]="dependencies"
></app-show-dependencies>
<!-- ** menu ** -->
<app-show-menu [buttons]="pkg | toButtons"></app-show-menu>
<!-- ** additional ** -->
<app-show-additional [pkg]="pkg"></app-show-additional>
</ng-container>
</ion-item-group>
</ng-container>
</ng-container>
<!-- INSECURE -->
<ng-template #insecure>
<ion-grid style="height: 100%; max-width: 540px">
<ion-row class="ion-align-items-center" style="height: 90%">
<ion-col class="ion-text-center">
<h2>
<ion-text color="warning">
You are using an unencrypted http connection
</ion-text>
</h2>
<p class="ion-padding-bottom">
Click the button below to switch to https. Your browser may warn
you that the page is insecure. You can safely bypass this
warning. It will go away after you
<a
[routerLink]="['/system', 'lan']"
style="color: var(--ion-color-dark)"
>download and trust your Embassy's certificate</a
>.
</p>
<ion-button (click)="launchHttps()">
Open https
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button>
</ion-col>
</ion-row>
</ion-grid>
</ng-template>
</ng-template>
</ion-content>
</ng-container>

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
import { NavController } from '@ionic/angular'
import { PatchDB } from 'patch-db-client'
import {
@@ -13,6 +13,8 @@ import {
import { tap } from 'rxjs/operators'
import { ActivatedRoute } from '@angular/router'
import { getPkgId } from '@start9labs/shared'
import { DOCUMENT } from '@angular/common'
import { ConfigService } from 'src/app/services/config.service'
const STATES = [
PackageState.Installing,
@@ -26,6 +28,8 @@ const STATES = [
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppShowPage {
readonly secure = this.config.isSecure()
private readonly pkgId = getPkgId(this.route)
readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe(
@@ -39,6 +43,8 @@ export class AppShowPage {
private readonly route: ActivatedRoute,
private readonly navCtrl: NavController,
private readonly patch: PatchDB<DataModel>,
private readonly config: ConfigService,
@Inject(DOCUMENT) private readonly document: Document,
) {}
isInstalled({ state }: PackageDataEntry): boolean {
@@ -56,4 +62,8 @@ export class AppShowPage {
showProgress({ state }: PackageDataEntry): boolean {
return STATES.includes(state)
}
launchHttps() {
window.open(this.document.location.href.replace('http', 'https'))
}
}

View File

@@ -3,6 +3,7 @@ import { LoadingController, getPlatforms } from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { AuthService } from 'src/app/services/auth.service'
import { Router } from '@angular/router'
import { ConfigService } from 'src/app/services/config.service'
@Component({
selector: 'login',
@@ -14,14 +15,26 @@ export class LoginPage {
unmasked = false
error = ''
loader?: HTMLIonLoadingElement
secure = this.config.isSecure()
constructor(
private readonly router: Router,
private readonly authService: AuthService,
private readonly loadingCtrl: LoadingController,
private readonly api: ApiService,
private readonly config: ConfigService,
) {}
async ionViewDidEnter() {
if (!this.secure) {
try {
await this.api.getPubKey()
} catch (e: any) {
this.error = e
}
}
}
ngOnDestroy() {
this.loader?.dismiss()
}
@@ -45,7 +58,9 @@ export class LoginPage {
return
}
await this.api.login({
password: this.password,
password: this.secure
? this.password
: await this.api.encrypt(this.password),
metadata: { platforms: getPlatforms() },
})

View File

@@ -11,81 +11,78 @@
</ion-header>
<ion-content class="ion-padding">
<ion-grid *ngIf="details$ | async as details">
<ion-row>
<ion-col size-lg="10" offset-lg="1" size-sm="12">
<ion-item class="description" [color]="details.color">
<ion-icon
text-wrap
size="large"
name="information-circle-outline"
></ion-icon>
<ion-label [innerHtml]="details.description"></ion-label>
</ion-item>
</ion-col>
</ion-row>
<ion-row>
<ion-col size="12">
<div class="heading">
<store-icon class="icon" [url]="details.url"></store-icon>
<h1 class="montserrat ion-text-center">{{ details.name }}</h1>
</div>
<ion-button fill="clear" (click)="presentModalMarketplaceSettings()">
<ion-icon slot="start" name="repeat-outline"></ion-icon>
Change
</ion-button>
<marketplace-search [(query)]="query"></marketplace-search>
</ion-col>
</ion-row>
<ion-row class="ion-align-items-center">
<ion-col size="12">
<ng-container *ngIf="store$ | async as store; else loading">
<ng-container *ngIf="localPkgs$ | async as localPkgs">
<marketplace-categories
[categories]="store.categories"
[category]="category"
[updatesAvailable]="
(store.packages | filterPackages: '':'updates':localPkgs).length
"
(categoryChange)="onCategoryChange($event)"
></marketplace-categories>
<ng-container *ngIf="details$ | async as details">
<ion-item [color]="details.color">
<ion-icon slot="start" name="information-circle-outline"></ion-icon>
<ion-label>
<h2 style="font-weight: 600">{{ details.description }}</h2>
</ion-label>
</ion-item>
<div class="divider"></div>
<ion-grid>
<ion-row>
<ion-col size="12">
<div class="heading">
<store-icon class="icon" [url]="details.url"></store-icon>
<h1 class="montserrat">{{ details.name }}</h1>
</div>
<ion-button fill="clear" (click)="presentModalMarketplaceSettings()">
<ion-icon slot="start" name="repeat-outline"></ion-icon>
Change
</ion-button>
<marketplace-search [(query)]="query"></marketplace-search>
</ion-col>
</ion-row>
<ion-row class="ion-align-items-center">
<ion-col size="12">
<ng-container *ngIf="store$ | async as store; else loading">
<ng-container *ngIf="localPkgs$ | async as localPkgs">
<marketplace-categories
[categories]="store.categories"
[category]="category"
[updatesAvailable]="
(store.packages | filterPackages: '':'updates':localPkgs).length
"
(categoryChange)="onCategoryChange($event)"
></marketplace-categories>
<ion-grid
*ngIf="store.packages | filterPackages: query:category:localPkgs as filtered"
>
<div
*ngIf="!filtered.length && category === 'updates'"
class="ion-padding"
<div class="divider"></div>
<ion-grid
*ngIf="store.packages | filterPackages: query:category:localPkgs as filtered"
>
<h1>All services are up to date!</h1>
</div>
<ion-row>
<ion-col
*ngFor="let pkg of filtered"
sizeXs="12"
sizeSm="12"
sizeMd="6"
<div
*ngIf="!filtered.length && category === 'updates'"
class="ion-padding"
>
<marketplace-item [pkg]="pkg">
<marketplace-status
class="status"
[version]="pkg.manifest.version"
[localPkg]="localPkgs[pkg.manifest.id]"
></marketplace-status>
</marketplace-item>
</ion-col>
</ion-row>
</ion-grid>
</ng-container>
</ng-container>
<h1>All services are up to date!</h1>
</div>
<ng-template #loading>
<marketplace-skeleton></marketplace-skeleton>
</ng-template>
</ion-col>
</ion-row>
</ion-grid>
<ion-row>
<ion-col
*ngFor="let pkg of filtered"
sizeXs="12"
sizeSm="12"
sizeMd="6"
>
<marketplace-item [pkg]="pkg">
<marketplace-status
class="status"
[version]="pkg.manifest.version"
[localPkg]="localPkgs[pkg.manifest.id]"
></marketplace-status>
</marketplace-item>
</ion-col>
</ion-row>
</ion-grid>
</ng-container>
</ng-container>
<ng-template #loading>
<marketplace-skeleton></marketplace-skeleton>
</ng-template>
</ion-col>
</ion-row>
</ion-grid>
</ng-container>
</ion-content>

View File

@@ -2,6 +2,7 @@
margin-top: 32px;
h1 {
font-size: 42px;
margin-top: 0;
}
}

View File

@@ -58,7 +58,7 @@ export class MarketplaceListPage {
// alt marketplace
color = 'warning'
description =
'Warning. This is a <b>Custom</b> Registry. Start9 cannot verify the integrity or functionality of services from this registry, and they may cause harm to your system. <b>Install at your own risk</b>.'
'This is a Custom Registry. Start9 cannot verify the integrity or functionality of services from this registry, and they may cause harm to your system. Install at your own risk.'
}
return {

View File

@@ -1,6 +1,6 @@
<ion-header>
<ion-toolbar>
<ion-title>LAN Settings</ion-title>
<ion-title>Secure LAN</ion-title>
<ion-buttons slot="start">
<ion-back-button defaultHref="embassy"></ion-back-button>
</ion-buttons>
@@ -13,25 +13,14 @@
<ion-item class="ion-padding-bottom">
<ion-label>
<h2>
Connecting to your Embassy over LAN provides a lightning fast
experience and is a reliable fallback in case Tor is having problems.
To connect to your Embassy's .local address, you must:
<ol>
<li>
Be connected to the same Local Area Network (LAN) as your Embassy.
</li>
<li>
Download and trust your Embassy's SSL Certificate Authority
(below).
</li>
</ol>
View the full
For a secure local connection,
<a
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
target="_blank"
rel="noreferrer"
>instructions</a
>.
>follow instructions</a
>
to download and trust your Embassy's Root Certificate Authority
</h2>
</ion-label>
</ion-item>
@@ -39,11 +28,7 @@
<ion-item button (click)="installCert()">
<ion-icon slot="start" name="download-outline" size="large"></ion-icon>
<ion-label>
<h1>Download Root CA</h1>
<p>
Download and trust your Embassy's Root Certificate Authority to
establish a secure, https connection over LAN.
</p>
<h1>Download Certificate</h1>
</ion-label>
</ion-item>
</ion-item-group>

View File

@@ -15,6 +15,23 @@
<!-- loaded -->
<ion-item-group *ngIf="server$ | async as server; else loading">
<ion-item *ngIf="!secure" color="warning">
<ion-icon slot="start" name="warning-outline"></ion-icon>
<ion-label>
<h2 style="font-weight: bold">You are using unencrypted http</h2>
<p style="font-weight: 600">
Click the button on the right to switch to https. Your browser may
warn you that the page is insecure. You can safely bypass this
warning. It will go away after you download and trust your Embassy's
certificate
</p>
</ion-label>
<ion-button slot="end" color="light" (click)="launchHttps()">
Open Https
<ion-icon slot="end" name="open-outline"></ion-icon>
</ion-button>
</ion-item>
<div *ngFor="let cat of settings | keyvalue : asIsOrder">
<ion-item-divider>
<ion-text color="dark" (click)="addClick(cat.key)">

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core'
import { Component, Inject } from '@angular/core'
import {
AlertController,
LoadingController,
@@ -10,7 +10,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ActivatedRoute } from '@angular/router'
import { PatchDB } from 'patch-db-client'
import { ServerNameService } from 'src/app/services/server-name.service'
import { firstValueFrom, Observable, of } from 'rxjs'
import { combineLatest, firstValueFrom, map, Observable, of } from 'rxjs'
import { ErrorToastService } from '@start9labs/shared'
import { EOSService } from 'src/app/services/eos.service'
import { ClientStorageService } from 'src/app/services/client-storage.service'
@@ -22,6 +22,8 @@ import {
GenericInputComponent,
GenericInputOptions,
} from 'src/app/modals/generic-input/generic-input.component'
import { ConfigService } from 'src/app/services/config.service'
import { DOCUMENT } from '@angular/common'
@Component({
selector: 'server-show',
@@ -36,6 +38,8 @@ export class ServerShowPage {
readonly showUpdate$ = this.eosService.showUpdate$
readonly showDiskRepair$ = this.ClientStorageService.showDiskRepair$
readonly secure = this.config.isSecure()
constructor(
private readonly alertCtrl: AlertController,
private readonly modalCtrl: ModalController,
@@ -50,6 +54,8 @@ export class ServerShowPage {
private readonly serverNameService: ServerNameService,
private readonly authService: AuthService,
private readonly toastCtrl: ToastController,
private readonly config: ConfigService,
@Inject(DOCUMENT) private readonly document: Document,
) {}
async presentModalName(): Promise<void> {
@@ -202,6 +208,10 @@ export class ServerShowPage {
await alert.present()
}
launchHttps() {
window.open(this.document.location.href.replace('http', 'https'))
}
addClick(title: string) {
switch (title) {
case 'Manage':
@@ -353,7 +363,7 @@ export class ServerShowPage {
action: () =>
this.navCtrl.navigateForward(['backup'], { relativeTo: this.route }),
detail: true,
disabled$: of(false),
disabled$: of(!this.secure),
},
{
title: 'Restore From Backup',
@@ -362,7 +372,10 @@ export class ServerShowPage {
action: () =>
this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }),
detail: true,
disabled$: this.eosService.updatingOrBackingUp$,
disabled$: combineLatest([
this.eosService.updatingOrBackingUp$,
of(this.secure),
]).pipe(map(([updating, secure]) => updating || !secure)),
},
],
Manage: [
@@ -387,8 +400,7 @@ export class ServerShowPage {
},
{
title: 'LAN',
description:
'Install your Embassy certificate for a secure local connection',
description: `Download and trust your Embassy's certificate for a secure local connection`,
icon: 'home-outline',
action: () =>
this.navCtrl.navigateForward(['lan'], { relativeTo: this.route }),

View File

@@ -61,7 +61,7 @@ export class WifiPage {
const alert = await this.alertCtrl.create({
header: 'Cannot Complete Action',
message:
'You must be connected to your Emassy via LAN to change the country.',
'You must be connected to your Embassy via LAN to change the country.',
buttons: [
{
text: 'OK',