mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() },
|
||||
})
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
margin-top: 32px;
|
||||
h1 {
|
||||
font-size: 42px;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)">
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user