diff --git a/frontend/angular.json b/frontend/angular.json index a2e161ba13..3a91dd00be 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -36,6 +36,11 @@ "input": "node_modules/monaco-editor", "output": "assets/monaco-editor/" }, + { + "glob": "**/*", + "input": "node_modules/@taiga-ui/icons/src", + "output": "assets/taiga-ui/icons" + }, "projects/ui/src/manifest.webmanifest", { "glob": "ngsw.json", @@ -44,6 +49,7 @@ } ], "styles": [ + "node_modules/@taiga-ui/core/styles/taiga-ui-theme.less", "projects/shared/styles/variables.scss", "projects/shared/styles/global.scss", "projects/shared/styles/shared.scss", diff --git a/frontend/config-sample.json b/frontend/config-sample.json index fc8593f181..4d2bd94dd0 100644 --- a/frontend/config-sample.json +++ b/frontend/config-sample.json @@ -14,6 +14,7 @@ }, "mocks": { "maskAs": "tor", + "maskAsHttps": true, "skipStartupAlerts": true } }, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c5ad718d54..76f6b820e5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -45,7 +45,7 @@ "monaco-editor": "^0.33.0", "mustache": "^4.2.0", "ng-qrcode": "^7.0.0", - "node-jose": "^2.1.1", + "node-jose": "^2.2.0", "patch-db-client": "file: ../../../patch-db/client", "pbkdf2": "^3.1.2", "rxjs": "^7.5.6", @@ -9506,9 +9506,9 @@ } }, "node_modules/long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/lru-cache": { "version": "7.14.0", @@ -10955,9 +10955,9 @@ } }, "node_modules/pako": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", - "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -22148,9 +22148,9 @@ } }, "long": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.0.tgz", - "integrity": "sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "lru-cache": { "version": "7.14.0", @@ -23251,9 +23251,9 @@ } }, "pako": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", - "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" }, "parent-module": { "version": "1.0.1", diff --git a/frontend/package.json b/frontend/package.json index 244f58ac70..e18cb29dba 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -70,7 +70,7 @@ "monaco-editor": "^0.33.0", "mustache": "^4.2.0", "ng-qrcode": "^7.0.0", - "node-jose": "^2.1.1", + "node-jose": "^2.2.0", "patch-db-client": "file: ../../../patch-db/client", "pbkdf2": "^3.1.2", "rxjs": "^7.5.6", diff --git a/frontend/projects/shared/src/services/http.service.ts b/frontend/projects/shared/src/services/http.service.ts index e294d4ef6c..d79f07f852 100644 --- a/frontend/projects/shared/src/services/http.service.ts +++ b/frontend/projects/shared/src/services/http.service.ts @@ -38,12 +38,13 @@ export class HttpService { async rpcRequest( opts: RPCOptions, + fullUrl?: string, ): Promise>> { const { method, headers, params, timeout } = opts return this.httpRequest>({ method: Method.POST, - url: this.relativeUrl, + url: fullUrl || this.relativeUrl, headers, body: { method, params }, timeout, diff --git a/frontend/projects/shared/src/types/workspace-config.ts b/frontend/projects/shared/src/types/workspace-config.ts index 997ded733e..8394ac95f8 100644 --- a/frontend/projects/shared/src/types/workspace-config.ts +++ b/frontend/projects/shared/src/types/workspace-config.ts @@ -15,7 +15,9 @@ export type WorkspaceConfig = { community: 'https://community-registry.start9.com/' } mocks: { - maskAs: 'tor' | 'lan' + maskAs: 'tor' | 'local' | 'localhost' + // enables local development in secure mode + maskAsHttps: boolean skipStartupAlerts: boolean } } diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html index 11993c9138..7de824787b 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.html @@ -15,63 +15,33 @@ - - - - - - + + + + + + + + + - - - - - - - - - - - - - + > + + + + + + - - - - - - -

- Http detected -

-

- Your connection is insecure. - - Download and trust your server's Root CA - - , then switch to https. -

- - Open https - - -
-
-
-
diff --git a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts index b7df8da9a6..a56a7565b1 100644 --- a/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts +++ b/frontend/projects/ui/src/app/pages/apps-routes/app-show/app-show.page.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Inject } from '@angular/core' +import { ChangeDetectionStrategy, Component } from '@angular/core' import { NavController } from '@ionic/angular' import { PatchDB } from 'patch-db-client' import { @@ -13,9 +13,6 @@ 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' -import { getServerInfo } from 'src/app/util/get-server-info' const STATES = [ PackageState.Installing, @@ -29,8 +26,6 @@ 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( @@ -44,8 +39,6 @@ export class AppShowPage { private readonly route: ActivatedRoute, private readonly navCtrl: NavController, private readonly patch: PatchDB, - private readonly config: ConfigService, - @Inject(DOCUMENT) private readonly document: Document, ) {} isInstalled({ state }: PackageDataEntry): boolean { @@ -63,11 +56,4 @@ export class AppShowPage { showProgress({ state }: PackageDataEntry): boolean { return STATES.includes(state) } - - async launchHttps() { - const onTor = this.config.isTor() - const { 'lan-address': lanAddress, 'tor-address': torAddress } = - await getServerInfo(this.patch) - onTor ? window.open(torAddress) : window.open(lanAddress) - } } diff --git a/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.html b/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.html new file mode 100644 index 0000000000..326eedd334 --- /dev/null +++ b/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.html @@ -0,0 +1,108 @@ + + + + + + + + + +

Trust your Root Certificate Authority (CA)

+

+ Download and trust your server's Root CA to establish secure, encrypted + ( + HTTPS + ) connections with your server +

+
+
+ + +
+ + 1 + + +
+

Download Root CA

+

Download your server's Root CA

+
+ + + Download + +
+
+ +
+ + 2 + + +
+

Trust Root CA

+

Follow instructions for your OS

+
+ + View Docs + + +
+
+ +
+ 3 +
+

Go To Login

+

+ + + + +  {{ caTrusted ? 'Root CA trusted!' : 'Waiting for trust...' }} +

+ + +


+
+
+ + Open + + +
+
+
+ + + + Skip + + + + +
+ diff --git a/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.scss b/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.scss new file mode 100644 index 0000000000..4e7af7aa8e --- /dev/null +++ b/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.scss @@ -0,0 +1,44 @@ +.grid-wiz { + --ion-grid-padding: 36px; + height: 100% +} + +.wiz-icon { + font-size: 84px; +} + +.wiz-card { + background: #414141; + margin: 24px; + padding: 16px; + height: 280px; + border-radius: 16px; + display: grid; + + & h2 { + font-weight: 600; + } +} + +.wiz-card-button { + justify-self: center; + white-space: normal; +} + +.wiz-spinner { + width: 14px; + height: 14px; +} + +.disabled { + filter: saturate(0.2) contrast(0.5) +} + +.wiz-step { + margin-top: 4px; +} + +.inline-center { + display: inline-flex; + align-items: center; +} \ No newline at end of file diff --git a/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.ts b/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.ts new file mode 100644 index 0000000000..444210fe23 --- /dev/null +++ b/frontend/projects/ui/src/app/pages/login/ca-wizard/ca-wizard.component.ts @@ -0,0 +1,76 @@ +import { Component, Inject } from '@angular/core' +import { ApiService } from 'src/app/services/api/embassy-api.service' +import { ConfigService } from 'src/app/services/config.service' +import { pauseFor, RELATIVE_URL } from '@start9labs/shared' +import { DOCUMENT } from '@angular/common' +import { WINDOW } from '@ng-web-apis/common' + +@Component({ + selector: 'ca-wizard', + templateUrl: './ca-wizard.component.html', + styleUrls: ['./ca-wizard.component.scss'], +}) +export class CAWizardComponent { + downloadClicked = false + instructionsClicked = false + polling = false + caTrusted = false + + constructor( + private readonly api: ApiService, + public readonly config: ConfigService, + @Inject(RELATIVE_URL) private readonly relativeUrl: string, + @Inject(DOCUMENT) public readonly document: Document, + @Inject(WINDOW) private readonly windowRef: Window, + ) {} + + async ngOnInit() { + if (!this.config.isSecure()) { + await this.testHttps().catch(e => + console.warn('Failed Https connection attempt'), + ) + } + } + + download() { + this.downloadClicked = true + this.document.getElementById('install-cert')?.click() + } + + instructions() { + this.windowRef.open( + 'https://docs.start9.com/getting-started/trust-ca/#trust-your-root-ca', + '_blank', + 'noreferrer', + ) + this.instructionsClicked = true + this.startDaemon() + } + + private async startDaemon(): Promise { + this.polling = true + while (this.polling) { + try { + await this.testHttps() + this.polling = false + } catch (e) { + console.warn('Failed Https connection attempt') + await pauseFor(2000) + } + } + } + + launchHttps() { + const host = this.config.getHost() + this.windowRef.open(`https://${host}`, '_blank', 'noreferrer') + } + + private async testHttps() { + const url = `https://${this.document.location.host}${this.relativeUrl}` + await this.api.echo({ message: 'ping' }, url).then(() => { + this.downloadClicked = true + this.instructionsClicked = true + this.caTrusted = true + }) + } +} diff --git a/frontend/projects/ui/src/app/pages/login/login.module.ts b/frontend/projects/ui/src/app/pages/login/login.module.ts index 937eb48851..753bfe94e4 100644 --- a/frontend/projects/ui/src/app/pages/login/login.module.ts +++ b/frontend/projects/ui/src/app/pages/login/login.module.ts @@ -4,7 +4,9 @@ import { CommonModule } from '@angular/common' import { FormsModule } from '@angular/forms' import { IonicModule } from '@ionic/angular' import { LoginPage } from './login.page' +import { CAWizardComponent } from './ca-wizard/ca-wizard.component' import { SharedPipesModule } from '@start9labs/shared' +import { TuiHintModule, TuiTooltipModule } from '@taiga-ui/core' const routes: Routes = [ { @@ -20,7 +22,9 @@ const routes: Routes = [ IonicModule, SharedPipesModule, RouterModule.forChild(routes), + TuiTooltipModule, + TuiHintModule, ], - declarations: [LoginPage], + declarations: [LoginPage, CAWizardComponent], }) export class LoginPageModule {} diff --git a/frontend/projects/ui/src/app/pages/login/login.page.html b/frontend/projects/ui/src/app/pages/login/login.page.html index d85fd532e8..a61553e449 100644 --- a/frontend/projects/ui/src/app/pages/login/login.page.html +++ b/frontend/projects/ui/src/app/pages/login/login.page.html @@ -1,55 +1,81 @@ - - - - + + + + - - - StartOS Login - + + + - -
- - - - - + + + + + + + + StartOS Login + + + + + + - - - -
-

- {{ error }} -

-
-
-
-
-
+ + + + + + + + +

+ {{ error }} +

+ + + + + +
diff --git a/frontend/projects/ui/src/app/pages/login/login.page.scss b/frontend/projects/ui/src/app/pages/login/login.page.scss index ead93410c4..27524ee797 100644 --- a/frontend/projects/ui/src/app/pages/login/login.page.scss +++ b/frontend/projects/ui/src/app/pages/login/login.page.scss @@ -18,7 +18,7 @@ } .row { - height: 90%; + height: 100%; align-items: center; text-align: center; } @@ -34,18 +34,30 @@ padding-top: 4px; } -ion-button { +.banner { + position: absolute; + padding: 20px; + width: 100%; + display: inline-block; + + ion-item { + max-width: 800px; + margin: auto; + } +} + +.side-button { --border-radius: 0 4px 4px 0; } -ion-item { +.login-item { --border-style: solid; --border-color: var(--ion-color-light); --border-radius: 4px 0 0 4px; box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12); - ion-button { + .side-button { --border-radius: 4px; } } @@ -80,12 +92,12 @@ ion-card { @media (max-width: 500px) { - ion-button { + .side-button { --border-radius: 4px; margin-top: 0.7rem; } - ion-item { + .login-item { --border-radius: 4px; } } diff --git a/frontend/projects/ui/src/app/pages/login/login.page.ts b/frontend/projects/ui/src/app/pages/login/login.page.ts index c86f6057e3..a0c564aadf 100644 --- a/frontend/projects/ui/src/app/pages/login/login.page.ts +++ b/frontend/projects/ui/src/app/pages/login/login.page.ts @@ -1,9 +1,12 @@ -import { Component } from '@angular/core' -import { LoadingController, getPlatforms } from '@ionic/angular' +import { Component, Inject } from '@angular/core' +import { getPlatforms, LoadingController } 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' +import { pauseFor, RELATIVE_URL } from '@start9labs/shared' +import { DOCUMENT } from '@angular/common' +import { WINDOW } from '@ng-web-apis/common' @Component({ selector: 'login', @@ -14,42 +17,71 @@ export class LoginPage { password = '' unmasked = false error = '' - loader?: HTMLIonLoadingElement - secure = this.config.isSecure() + + downloadClicked = false + instructionsClicked = false + polling = false + caTrusted = false constructor( private readonly router: Router, private readonly authService: AuthService, private readonly loadingCtrl: LoadingController, private readonly api: ApiService, - private readonly config: ConfigService, + public readonly config: ConfigService, + @Inject(RELATIVE_URL) private readonly relativeUrl: string, + @Inject(DOCUMENT) public readonly document: Document, + @Inject(WINDOW) private readonly windowRef: Window, ) {} - async ionViewDidEnter() { - if (!this.secure) { - try { - await this.api.getPubKey() - } catch (e: any) { - this.error = e.message - } + async ngOnInit() { + if (!this.config.isSecure()) { + await this.testHttps().catch(e => + console.warn('Failed Https connection attempt'), + ) } } - ngOnDestroy() { - this.loader?.dismiss() + download() { + this.downloadClicked = true + this.document.getElementById('install-cert')?.click() } - toggleMask() { - this.unmasked = !this.unmasked + instructions() { + this.windowRef.open( + 'https://docs.start9.com/getting-started/trust-ca/#trust-your-server-s-root-ca', + '_blank', + 'noreferrer', + ) + this.instructionsClicked = true + this.startDaemon() + } + + private async startDaemon(): Promise { + this.polling = true + while (this.polling) { + try { + await this.testHttps() + this.polling = false + } catch (e) { + console.warn('Failed Https connection attempt') + await pauseFor(2000) + } + } + } + + launchHttps() { + const host = this.config.getHost() + this.windowRef.open(`https://${host}`, '_blank', 'noreferrer') } async submit() { this.error = '' - this.loader = await this.loadingCtrl.create({ + const loader = await this.loadingCtrl.create({ message: 'Logging in...', }) - await this.loader.present() + await loader.present() try { document.cookie = '' @@ -58,9 +90,7 @@ export class LoginPage { return } await this.api.login({ - password: this.secure - ? this.password - : await this.api.encrypt(this.password), + password: this.password, metadata: { platforms: getPlatforms() }, }) @@ -71,7 +101,16 @@ export class LoginPage { // code 7 is for incorrect password this.error = e.code === 7 ? 'Invalid Password' : e.message } finally { - this.loader.dismiss() + loader.dismiss() } } + + private async testHttps() { + const url = `https://${this.document.location.host}${this.relativeUrl}` + await this.api.echo({ message: 'ping' }, url).then(() => { + this.downloadClicked = true + this.instructionsClicked = true + this.caTrusted = true + }) + } } diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html index da1e49656b..2f119590aa 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html +++ b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.html @@ -15,13 +15,12 @@ - +

Http detected

- {{ isTorHttp ? 'Tor is faster over https.' : 'Your connection is - insecure.' }} + Tor is faster over https. this.navCtrl.navigateForward(['backup'], { relativeTo: this.route }), detail: true, - disabled$: of(!this.secure), + disabled$: of(false), }, { title: 'Restore From Backup', @@ -474,10 +470,7 @@ export class ServerShowPage { action: () => this.navCtrl.navigateForward(['restore'], { relativeTo: this.route }), detail: true, - disabled$: combineLatest([ - this.eosService.updatingOrBackingUp$, - of(this.secure), - ]).pipe(map(([updating, secure]) => updating || !secure)), + disabled$: this.eosService.updatingOrBackingUp$, }, ], Manage: [ @@ -545,7 +538,7 @@ export class ServerShowPage { icon: 'key-outline', action: () => this.presentAlertResetPassword(), detail: false, - disabled$: of(!this.secure), + disabled$: of(false), }, { title: 'Experimental Features', diff --git a/frontend/projects/ui/src/app/pages/server-routes/wifi/wifi.page.ts b/frontend/projects/ui/src/app/pages/server-routes/wifi/wifi.page.ts index 9d064fb41e..fea8dc48b9 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/wifi/wifi.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/wifi/wifi.page.ts @@ -57,7 +57,7 @@ export class WifiPage { } async presentAlertCountry(): Promise { - if (!this.config.isLan) { + if (!this.config.isLan()) { const alert = await this.alertCtrl.create({ header: 'Cannot Complete Action', message: diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index b1f3db2061..372ec8e75e 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -22,7 +22,7 @@ export module RR { // auth export type LoginReq = { - password: Encrypted | string + password: string metadata: SessionMetadata } // auth.login - unauthed export type loginRes = null @@ -465,7 +465,3 @@ declare global { parse(text: Stringified, reviver?: (key: any, value: any) => any): T } } - -export type Encrypted = { - encrypted: string -} diff --git a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts index 168993c116..2a67de4ca2 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts @@ -1,28 +1,12 @@ import { BehaviorSubject, Observable } from 'rxjs' import { Update } from 'patch-db-client' -import { Encrypted, RR } from './api.types' +import { RR } from './api.types' import { DataModel } from 'src/app/services/patch-db/data-model' import { Log } from '@start9labs/shared' import { WebSocketSubjectConfig } from 'rxjs/webSocket' -import type { JWK } from 'node-jose' export abstract class ApiService { - protected readonly jose = import('node-jose') - readonly patchStream$ = new BehaviorSubject[]>([]) - pubkey?: JWK.Key - - async encrypt(toEncrypt: string): Promise { - const { pubkey } = this - - if (!pubkey) throw new Error('No pubkey found!') - - const encrypted = await this.jose.then(jose => - jose.JWE.createEncrypt(pubkey).update(toEncrypt).final(), - ) - - return { encrypted } - } // http @@ -41,8 +25,6 @@ export abstract class ApiService { // auth - abstract getPubKey(): Promise - abstract login(params: RR.LoginReq): Promise abstract logout(params: RR.LogoutReq): Promise @@ -57,7 +39,7 @@ export abstract class ApiService { // server - abstract echo(params: RR.EchoReq): Promise + abstract echo(params: RR.EchoReq, urlOverride?: string): Promise abstract openPatchWebsocket$(): Observable> diff --git a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts index 002baf6077..bf436b6e11 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -66,18 +66,6 @@ export class LiveApiService extends ApiService { // auth - /** - * We want to update the pubkey, which means that we will call in clearnet the - * getPubKey, and all the information is never in the clear, and only public - * information is sent across the network. - */ - async getPubKey() { - this.pubkey = await this.rpcRequest({ - method: 'auth.get-pubkey', - params: {}, - }) - } - async login(params: RR.LoginReq): Promise { return this.rpcRequest({ method: 'auth.login', params }, false) } @@ -102,8 +90,8 @@ export class LiveApiService extends ApiService { // server - async echo(params: RR.EchoReq): Promise { - return this.rpcRequest({ method: 'echo', params }, false) + async echo(params: RR.EchoReq, urlOverride?: string): Promise { + return this.rpcRequest({ method: 'echo', params }, false, urlOverride) } openPatchWebsocket$(): Observable> { @@ -453,6 +441,7 @@ export class LiveApiService extends ApiService { private async rpcRequest( options: RPCOptions, addHeader = true, + urlOverride?: string, ): Promise { if (addHeader) { options.headers = { @@ -461,7 +450,7 @@ export class LiveApiService extends ApiService { } } - const res = await this.http.rpcRequest(options) + const res = await this.http.rpcRequest(options, urlOverride) const encodedUpdates = res.headers.get('x-patch-updates') const encodedError = res.headers.get('x-patch-error') diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 8a18247a78..c80bab73ba 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -113,24 +113,6 @@ export class MockApiService extends ApiService { // auth - async getPubKey() { - await pauseFor(1000) - - // randomly generated - // const keystore = jose.JWK.createKeyStore() - // this.pubkey = await keystore.generate('EC', 'P-256') - - // generated from backend - this.pubkey = await this.jose.then(jose => - jose.JWK.asKey({ - kty: 'EC', - crv: 'P-256', - x: 'yHTDYSfjU809fkSv9MmN4wuojf5c3cnD7ZDN13n-jz4', - y: '8Mpkn744A5KDag0DmX2YivB63srjbugYZzWc3JOpQXI', - }), - ) - } - async login(params: RR.LoginReq): Promise { await pauseFor(2000) @@ -165,7 +147,13 @@ export class MockApiService extends ApiService { // server - async echo(params: RR.EchoReq): Promise { + async echo(params: RR.EchoReq, url?: string): Promise { + if (url) { + const num = Math.floor(Math.random() * 10) + 1 + console.warn(num) + if (num > 8) return params.message + throw new Error() + } await pauseFor(2000) return params.message } diff --git a/frontend/projects/ui/src/app/services/config.service.ts b/frontend/projects/ui/src/app/services/config.service.ts index a7740e525b..343e7a0598 100644 --- a/frontend/projects/ui/src/app/services/config.service.ts +++ b/frontend/projects/ui/src/app/services/config.service.ts @@ -23,6 +23,10 @@ export class ConfigService { constructor(@Inject(DOCUMENT) private readonly document: Document) {} hostname = this.document.location.hostname + // includes port + host = this.document.location.host + // includes ":" (e.g. "http:") + protocol = this.document.location.protocol version = require('../../../../../package.json').version as string useMocks = useMocks mocks = mocks @@ -36,17 +40,32 @@ export class ConfigService { supportsWebSockets = !!window.WebSocket || this.isConsulate isTor(): boolean { - return ( - this.hostname.endsWith('.onion') || (useMocks && mocks.maskAs === 'tor') - ) + return useMocks ? mocks.maskAs === 'tor' : this.hostname.endsWith('.onion') + } + + isLocal(): boolean { + return useMocks + ? mocks.maskAs === 'local' + : this.hostname.endsWith('.local') + } + + isLocalhost(): boolean { + return useMocks + ? mocks.maskAs === 'localhost' + : this.hostname === 'localhost' } isLan(): boolean { - return ( - this.hostname === 'localhost' || - this.hostname.endsWith('.local') || - (useMocks && mocks.maskAs === 'lan') - ) + // @TODO will not work once clearnet arrives + return !this.isTor() + } + + isTorHttp(): boolean { + return this.isTor() && !this.isHttps() + } + + isLocalHttp(): boolean { + return this.isLocal() && !this.isHttps() } isSecure(): boolean { @@ -66,13 +85,21 @@ export class ConfigService { } launchableURL(pkg: PackageDataEntry): string { - if (this.isLan() && hasLanUi(pkg.manifest.interfaces)) { + if (this.isLan() && hasLocalUi(pkg.manifest.interfaces)) { return `https://${lanUiAddress(pkg)}` } else { // leave http for services return `http://${torUiAddress(pkg)}` } } + + getHost(): string { + return this.host + } + + private isHttps(): boolean { + return useMocks ? mocks.maskAsHttps : this.protocol === 'https:' + } } export function hasTorUi(interfaces: Record): boolean { @@ -80,7 +107,7 @@ export function hasTorUi(interfaces: Record): boolean { return !!int?.['tor-config'] } -export function hasLanUi(interfaces: Record): boolean { +export function hasLocalUi(interfaces: Record): boolean { const int = getUiInterfaceValue(interfaces) return !!int?.['lan-config'] } @@ -102,7 +129,7 @@ export function lanUiAddress({ } export function hasUi(interfaces: Record): boolean { - return hasTorUi(interfaces) || hasLanUi(interfaces) + return hasTorUi(interfaces) || hasLocalUi(interfaces) } export function removeProtocol(str: string): string { diff --git a/frontend/projects/ui/src/polyfills.ts b/frontend/projects/ui/src/polyfills.ts index 0344079834..a392d45cfc 100644 --- a/frontend/projects/ui/src/polyfills.ts +++ b/frontend/projects/ui/src/polyfills.ts @@ -53,8 +53,7 @@ */ (window as any).global = window -global.Buffer = global.Buffer || require('buffer').Buffer; -(window as any).process = { env: { DEBUG: undefined }, browser: true } +; (window as any).process = { env: { DEBUG: undefined }, browser: true } import './zone-flags' @@ -62,8 +61,7 @@ import './zone-flags' * Zone JS is required by default for Angular itself. */ -import 'zone.js/dist/zone' // Included with Angular CLI. - +import 'zone.js/dist/zone' // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS diff --git a/frontend/projects/ui/src/styles.scss b/frontend/projects/ui/src/styles.scss index dbca8cae3a..a00ca4ae2d 100644 --- a/frontend/projects/ui/src/styles.scss +++ b/frontend/projects/ui/src/styles.scss @@ -339,3 +339,12 @@ ul { padding-left: 40px; list-style-type: disc; } + +// override for taiga styles +p { + font-size: 1rem; +} + +svg:not(:root) { + overflow: auto; +} \ No newline at end of file