Merge branch 'rebase/integration/refactors' of github.com:Start9Labs/start-os into rebase/feat/domains

This commit is contained in:
Matt Hill
2023-07-26 11:01:56 -06:00
87 changed files with 3983 additions and 2856 deletions

View File

@@ -141,7 +141,6 @@ export class EmbassyPage {
await this.navCtrl.navigateForward(`/loading`)
} catch (e: any) {
this.errorService.handleError(e)
console.error(e)
} finally {
loader.unsubscribe()
}

View File

@@ -27,31 +27,15 @@
<section
style="
padding: 1rem 3rem 2rem 3rem;
border: solid #c4c4c5 3px;
margin-bottom: 24px;
border: solid #c4c4c5 3px;
border-radius: 20px;
"
>
<h2 style="font-variant-caps: all-small-caps">
Access from home (LAN)
</h2>
<p>
Visit the address below when you are connected to the same WiFi or
Local Area Network (LAN) as your server:
</p>
<p
style="
padding: 16px;
font-weight: bold;
font-size: 1.1rem;
overflow: auto;
"
>
<code id="lan-addr"></code>
</p>
<div>
<h3 style="color: #f8546a; font-weight: bold">Important!</h3>
<p>
Be sure to
Download your server's Root CA and
<a
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
target="_blank"
@@ -60,12 +44,10 @@
>
follow the instructions
</a>
to establish a secure connection by installing your server's root
certificate authority.
to establish a secure connection with your server.
</p>
</div>
<div style="padding: 2rem; text-align: center">
<div style="text-align: center">
<a
id="cert"
[download]="crtName"
@@ -88,12 +70,49 @@
</a>
</div>
</section>
<section
style="
padding: 1rem 3rem 2rem 3rem;
border: solid #c4c4c5 3px;
border-radius: 20px;
margin-bottom: 24px;
"
>
<h2 style="font-variant-caps: all-small-caps">
Access from home (LAN)
</h2>
<p>
Visit the address below when you are connected to the same WiFi or
Local Area Network (LAN) as your server.
</p>
<p
style="
padding: 16px;
font-weight: bold;
font-size: 1.1rem;
overflow: auto;
"
>
<code id="lan-addr"></code>
</p>
<section style="padding: 1rem 3rem 2rem 3rem; border: solid #c4c4c5 3px">
<h2 style="font-variant-caps: all-small-caps">
Access on the go (Tor)
</h2>
<p>Visit the address below when you are away from home:</p>
<p>Visit the address below when you are away from home.</p>
<p>
<span style="font-weight: bold">Note:</span>
This address will only work from a Tor-enabled browser.
<a
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
target="_blank"
rel="noreferrer"
style="color: #6866cc; font-weight: bold; text-decoration: none"
>
Follow the instructions
</a>
to get setup.
</p>
<p
style="
padding: 16px;
@@ -104,21 +123,6 @@
>
<code id="tor-addr"></code>
</p>
<div>
<h3 style="color: #f8546a; font-weight: bold">Important!</h3>
<p>
This address will only work from a Tor-enabled browser.
<a
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
target="_blank"
rel="noreferrer"
style="color: #6866cc; font-weight: bold; text-decoration: none"
>
Follow the instructions
</a>
to get setup.
</p>
</div>
</section>
</div>
</body>

View File

@@ -50,7 +50,7 @@ export class SuccessPage {
const ret = await this.api.complete()
if (!this.isKiosk) {
this.torAddress = ret['tor-address']
this.lanAddress = ret['lan-address'].replace('https', 'http')
this.lanAddress = ret['lan-address'].replace(/^https:/, 'http:')
this.cert = ret['root-ca']
await this.api.exit()

View File

@@ -7,10 +7,10 @@ import {
} from '@start9labs/shared'
import {
ApiService,
CifsRecoverySource,
AttachReq,
ExecuteReq,
CifsRecoverySource,
CompleteRes,
ExecuteReq,
} from './api.service'
import * as jose from 'node-jose'
import { interval, map, Observable } from 'rxjs'
@@ -151,7 +151,7 @@ export class MockApiService extends ApiService {
async complete(): Promise<CompleteRes> {
await pauseFor(1000)
return {
'tor-address': 'http://asdafsadasdasasdasdfasdfasdf.onion',
'tor-address': 'https://asdafsadasdasasdasdfasdfasdf.onion',
'lan-address': 'https://adjective-noun.local',
'root-ca': encodeBase64(rootCA),
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -52,7 +52,7 @@ export class AppComponent implements OnDestroy {
readonly themeSwitcher: ThemeSwitcherService,
) {}
ngOnInit() {
async ngOnInit() {
this.patch
.watch$('ui', 'name')
.subscribe(name => this.titleService.setTitle(name || 'StartOS'))

View File

@@ -41,6 +41,7 @@ const ICONS = [
'file-tray-stacked-outline',
'finger-print-outline',
'flash-outline',
'flask-outline',
'flash-off-outline',
'folder-open-outline',
'globe-outline',
@@ -48,7 +49,6 @@ const ICONS = [
'hammer-outline',
'help-circle-outline',
'hammer-outline',
'home-outline',
'information-circle-outline',
'key-outline',
'list-outline',
@@ -76,6 +76,7 @@ const ICONS = [
'remove-circle-outline',
'remove-outline',
'repeat-outline',
'ribbon-outline',
'rocket-outline',
'save-outline',
'server-outline',

View File

@@ -49,7 +49,6 @@
<!-- INSECURE -->
<ng-template #insecure>
<insecure-warning></insecure-warning>
<h2>This page cannot safely be accessed over an insecure connection</h2>
</ng-template>
</ng-template>
</ion-content>

View File

@@ -1,6 +1,11 @@
import { DOCUMENT } from '@angular/common'
import { Component, Inject } from '@angular/core'
import { NavController } from '@ionic/angular'
import {
AlertController,
LoadingController,
ModalController,
NavController,
ToastController,
} from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ActivatedRoute } from '@angular/router'
import { PatchDB } from 'patch-db-client'
@@ -17,6 +22,9 @@ import { TuiAlertService, TuiDialogService } from '@taiga-ui/core'
import { PROMPT } from 'src/app/apps/ui/modals/prompt/prompt.component'
import { PolymorpheusComponent } from '@tinkoff/ng-polymorpheus'
import { TUI_PROMPT } from '@taiga-ui/kit'
import { DOCUMENT } from '@angular/common'
import { getServerInfo } from 'src/app/util/get-server-info'
import * as argon2 from '@start9labs/argon2'
@Component({
selector: 'server-show',
@@ -32,6 +40,8 @@ export class ServerShowPage {
readonly showDiskRepair$ = this.clientStorageService.showDiskRepair$
readonly secure = this.config.isSecure()
readonly isTorHttp =
this.config.isTor() && this.document.location.protocol === 'http:'
constructor(
private readonly dialogs: TuiDialogService,
@@ -88,6 +98,109 @@ export class ServerShowPage {
this.dialogs.open(new PolymorpheusComponent(OSUpdatePage)).subscribe()
}
async presentAlertResetPassword() {
const alert = await this.alertCtrl.create({
header: 'Warning',
message:
'You will still need your current password to decrypt existing backups!',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Continue',
handler: () => this.presentModalResetPassword(),
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
}
async presentModalResetPassword(): Promise<void> {
const modal = await this.modalCtrl.create({
component: GenericFormPage,
componentProps: {
title: 'Change Master Password',
spec: PasswordSpec,
buttons: [
{
text: 'Save',
handler: (value: any) => {
return this.resetPassword(value)
},
isSubmit: true,
},
],
},
})
await modal.present()
}
private async resetPassword(value: {
currPass: string
newPass: string
newPass2: string
}): Promise<boolean> {
let err = ''
if (value.newPass !== value.newPass2) {
err = 'New passwords do not match'
} else if (value.newPass.length < 12) {
err = 'New password must be 12 characters or greater'
} else if (value.newPass.length > 64) {
err = 'New password must be less than 65 characters'
}
// confirm current password is correct
const { 'password-hash': passwordHash } = await getServerInfo(this.patch)
try {
argon2.verify(passwordHash, value.currPass)
} catch (e) {
err = 'Current password is invalid'
}
if (err) {
this.errToast.present(err)
return false
}
const loader = await this.loadingCtrl.create({
message: 'Changing master password...',
})
await loader.present()
try {
await this.embassyApi.resetPassword({
'old-password': value.currPass,
'new-password': value.newPass,
})
const toast = await this.toastCtrl.create({
header: 'Password changed!',
position: 'bottom',
duration: 2000,
})
toast.present()
return true
} catch (e: any) {
this.errToast.present(e)
return false
} finally {
loader.dismiss()
}
}
async updateEos(): Promise<void> {
const modal = await this.modalCtrl.create({
component: OSUpdatePage,
})
modal.present()
}
private presentAlertLogout() {
this.dialogs
.open(TUI_PROMPT, {
@@ -320,6 +433,14 @@ export class ServerShowPage {
detail: true,
disabled$: of(false),
},
{
title: 'Change Master Password',
description: `Change your StartOS master password`,
icon: 'key-outline',
action: () => this.presentAlertResetPassword(),
detail: false,
disabled$: of(!this.secure),
},
{
title: 'Experimental Features',
description: 'Try out new and potentially unstable new features',
@@ -563,3 +684,30 @@ interface SettingBtn {
detail: boolean
disabled$: Observable<boolean>
}
const PasswordSpec: ConfigSpec = {
currPass: {
type: 'string',
name: 'Current Password',
placeholder: 'CurrentPass',
nullable: false,
masked: true,
copyable: false,
},
newPass: {
type: 'string',
name: 'New Password',
placeholder: 'NewPass',
nullable: false,
masked: true,
copyable: false,
},
newPass2: {
type: 'string',
name: 'Retype New Password',
placeholder: 'NewPass',
nullable: false,
masked: true,
copyable: false,
},
}

View File

@@ -1,5 +1,29 @@
<h2>This Release</h2>
<h4>0.3.4.4</h4>
<p class="note-padding">
View the complete
<a
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.4.4"
target="_blank"
noreferrer
>
release notes
</a>
for more details.
</p>
<h6>Highlights</h6>
<ul class="spaced-list">
<li>Https over Tor for faster UI loading times</li>
<li>Change password through UI</li>
<li>Use IP address for Network Folder backups</li>
<li>
Multiple bug fixes, performance enhancements, and other small features
</li>
</ul>
<h2>Previous Releases</h2>
<h4>0.3.4.3</h4>
<p class="note-padding">
View the complete
@@ -16,12 +40,10 @@
<ul class="spaced-list">
<li>Improved Tor reliability</li>
<li>Experimental features tab</li>
<li>multiple bugfixes and general performance enhancements</li>
<li>Multiple bugfixes and general performance enhancements</li>
<li>Update branding</li>
</ul>
<h2>Previous Releases</h2>
<h4>0.3.4.2</h4>
<p class="note-padding">
View the complete
@@ -44,23 +66,6 @@
</li>
</ul>
<h4>0.3.4.1</h4>
<p class="note-padding">
View the complete
<a
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.4.1"
target="_blank"
noreferrer
>
release notes
</a>
for more details.
</p>
<h6>Highlights</h6>
<ul class="spaced-list">
<li>0.3.4 bug fixes</li>
</ul>
<h4>0.3.4</h4>
<p class="note-padding">
View the complete

View File

@@ -2,6 +2,8 @@ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
import { Observable, Subject, merge, debounceTime } from 'rxjs'
import { RefreshAlertService } from './refresh-alert.service'
import { SwUpdate } from '@angular/service-worker'
import { LoadingController } from '@ionic/angular'
@Component({
selector: 'refresh-alert',
@@ -13,10 +15,36 @@ export class RefreshAlertComponent {
readonly show$ = merge(this.dismiss$, this.refresh$).pipe(debounceTime(0))
// @TODO use this like we did on 0344
onPwa = false
constructor(
@Inject(RefreshAlertService) private readonly refresh$: Observable<boolean>,
private readonly updates: SwUpdate,
private readonly loadingCtrl: LoadingController,
) {}
ngOnInit() {
this.onPwa = window.matchMedia('(display-mode: standalone)').matches
}
async pwaReload() {
const loader = await this.loadingCtrl.create({
message: 'Reloading PWA...',
})
await loader.present()
try {
// attempt to update to the latest client version available
await this.updates.activateUpdate()
} catch (e) {
console.error('Error activating update from service worker: ', e)
} finally {
loader.dismiss()
// always reload, as this resolves most out of sync cases
window.location.reload()
}
}
onDismiss() {
this.dismiss$.next(false)
}

View File

@@ -46,11 +46,11 @@ export class WidgetListComponent {
qp: { back: 'true' },
},
{
title: 'Secure LAN',
icon: 'home-outline',
title: 'Root CA',
icon: 'ribbon-outline',
color: 'var(--alt-orange)',
description: `Download and trust your server's certificate`,
link: '/system/lan',
description: `Download and trust your server's root certificate authority`,
link: '/system/root-ca',
},
{
title: 'Create Backup',

View File

@@ -35,9 +35,10 @@ export module Mock {
'shutting-down': false,
}
export const MarketplaceEos: RR.GetMarketplaceEosRes = {
version: '0.3.4.3',
version: '0.3.4.4',
headline: 'Our biggest release ever.',
'release-notes': {
'0.3.4.4': 'Some **Markdown** release _notes_ for 0.3.4.4',
'0.3.4.3': 'Some **Markdown** release _notes_ for 0.3.4.3',
'0.3.4.2': 'Some **Markdown** release _notes_ for 0.3.4.2',
'0.3.4.1': 'Some **Markdown** release _notes_ for 0.3.4.1',

View File

@@ -34,6 +34,12 @@ export module RR {
export type LogoutReq = {} // auth.logout
export type LogoutRes = null
export type ResetPasswordReq = {
'old-password': string
'new-password': string
} // auth.reset-password
export type ResetPasswordRes = null
// server
export type EchoReq = { message: string } // server.echo
@@ -94,11 +100,6 @@ export module RR {
export type KillSessionsReq = { ids: string[] } // sessions.kill
export type KillSessionsRes = null
// password
export type UpdatePasswordReq = { password: string } // password.set
export type UpdatePasswordRes = null
// notification
export type GetNotificationsReq = {

View File

@@ -53,6 +53,10 @@ export abstract class ApiService {
abstract killSessions(params: RR.KillSessionsReq): Promise<RR.KillSessionsRes>
abstract resetPassword(
params: RR.ResetPasswordReq,
): Promise<RR.ResetPasswordRes>
// server
abstract echo(params: RR.EchoReq): Promise<RR.EchoRes>
@@ -132,9 +136,6 @@ export abstract class ApiService {
abstract getEos(): Promise<RR.GetMarketplaceEosRes>
// password
// abstract updatePassword (params: RR.UpdatePasswordReq): Promise<RR.UpdatePasswordRes>
// notification
abstract getNotifications(

View File

@@ -105,6 +105,12 @@ export class LiveApiService extends ApiService {
return this.rpcRequest({ method: 'auth.session.kill', params })
}
async resetPassword(
params: RR.ResetPasswordReq,
): Promise<RR.ResetPasswordRes> {
return this.rpcRequest({ method: 'auth.reset-password', params })
}
// server
async echo(params: RR.EchoReq): Promise<RR.EchoRes> {

View File

@@ -158,6 +158,13 @@ export class MockApiService extends ApiService {
return null
}
async resetPassword(
params: RR.ResetPasswordReq,
): Promise<RR.ResetPasswordRes> {
await pauseFor(2000)
return null
}
// server
async echo(params: RR.EchoReq): Promise<RR.EchoRes> {
@@ -402,12 +409,6 @@ export class MockApiService extends ApiService {
return Mock.MarketplaceEos
}
// password
// async updatePassword (params: RR.UpdatePasswordReq): Promise<RR.UpdatePasswordRes> {
// await pauseFor(2000)
// return null
// }
// notification
async getNotifications(

View File

@@ -278,6 +278,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
> = {},
): Observable<MarketplacePkg[]> {
return this.patch.watch$('server-info', 'eos-version-compat').pipe(
take(1),
switchMap(versionCompat => {
const qp: RR.GetMarketplacePackagesReq = {
...params,

View File

@@ -4,7 +4,7 @@
export const environment = {
production: false,
useServiceWorker: true,
useServiceWorker: false,
}
/*

View File

@@ -14,6 +14,11 @@
<meta name="msapplication-tap-highlight" content="no" />
<link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="256x256"
href="assets/img/icon_apple_touch.png"
/>
<link rel="manifest" href="manifest.webmanifest" />
<meta name="theme-color" content="#ff5b71" />
</head>

View File

@@ -5,8 +5,8 @@
"background_color": "#1e1e1e",
"display": "standalone",
"scope": ".",
"start_url": "/",
"id": "/",
"start_url": "/?version=0344",
"id": "/?version=0344",
"icons": [
{
"src": "assets/img/icon_pwa.png",