Skip to content

Commit

Permalink
ability to add and remove tor addresses, including vanity
Browse files Browse the repository at this point in the history
  • Loading branch information
MattDHill committed Jan 20, 2025
1 parent e6e31df commit 1ffb67f
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ <h2>{{ address.name }}</h2>
<ion-button
*ngIf="address.isDomain"
color="danger"
(click)="removeDomain(address.url)"
(click)="removeStandard(address.url)"
>
Remove
</ion-button>
<ion-button
*ngIf="address.isOnion"
color="danger"
(click)="removeOnion(address.url)"
>
Remove
</ion-button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type MappedAddress = {
name: string
url: string
isDomain: boolean
isOnion: boolean
acme: string | null
}

Expand Down Expand Up @@ -87,22 +88,32 @@ export class InterfaceInfoComponent {
async presentDomainForm() {
const acme = await firstValueFrom(this.patch.watch$('serverInfo', 'acme'))

const spec = getDomainSpec(Object.keys(acme))

this.formDialog.open(FormComponent, {
label: 'Add Domain',
data: {
spec: await configBuilderToSpec(getDomainSpec(Object.keys(acme))),
spec: await configBuilderToSpec(spec),
buttons: [
{
text: 'Save',
handler: async (val: { domain: string; acme: string }) =>
this.saveDomain(val.domain, val.acme),
handler: async (val: typeof spec._TYPE) => {
if (val.type.selection === 'standard') {
return this.saveStandard(
val.type.value.domain,
val.type.value.acme,
)
} else {
return this.saveTor(val.type.value.key)
}
},
},
],
},
})
}

async removeDomain(url: string) {
async removeStandard(url: string) {
const loader = this.loader.open('Removing').subscribe()

const params = {
Expand All @@ -128,6 +139,32 @@ export class InterfaceInfoComponent {
}
}

async removeOnion(url: string) {
const loader = this.loader.open('Removing').subscribe()

const params = {
onion: new URL(url).hostname,
}

try {
if (this.pkgId) {
await this.api.pkgRemoveOnion({
...params,
package: this.pkgId,
host: this.iFace.addressInfo.hostId,
})
} else {
await this.api.serverRemoveOnion(params)
}
return true
} catch (e: any) {
this.errorService.handleError(e)
return false
} finally {
loader.unsubscribe()
}
}

async showAcme(url: ACME_URL | string | null): Promise<void> {
const alert = await this.alertCtrl.create({
header: 'ACME Provider',
Expand Down Expand Up @@ -163,7 +200,7 @@ export class InterfaceInfoComponent {
await toast.present()
}

private async saveDomain(domain: string, acme: string) {
private async saveStandard(domain: string, acme: string) {
const loader = this.loader.open('Saving').subscribe()

const params = {
Expand All @@ -190,32 +227,81 @@ export class InterfaceInfoComponent {
loader.unsubscribe()
}
}

private async saveTor(key: string | null) {
const loader = this.loader.open('Creating onion address').subscribe()

try {
let onion = key
? await this.api.addTorKey({ key })
: await this.api.generateTorKey({})
onion = `${onion}.onion`

if (this.pkgId) {
await this.api.pkgAddOnion({
onion,
package: this.pkgId,
host: this.iFace.addressInfo.hostId,
})
} else {
await this.api.serverAddOnion({ onion })
}
return true
} catch (e: any) {
this.errorService.handleError(e)
return false
} finally {
loader.unsubscribe()
}
}
}

function getDomainSpec(acme: string[]) {
return ISB.InputSpec.of({
domain: ISB.Value.text({
name: 'Domain',
description: 'The domain or subdomain you want to use',
warning: null,
placeholder: `e.g. 'mydomain.com' or 'sub.mydomain.com'`,
required: true,
default: null,
patterns: [utils.Patterns.domain],
}),
acme: ISB.Value.select({
name: 'ACME Provider',
description:
'Select which ACME provider to use for obtaining your SSL certificate. Add new ACME providers in the System tab. Optionally use your system Root CA. Note: only devices that have trusted your Root CA will be able to access the domain without security warnings.',
values: acme.reduce(
(obj, url) => ({
...obj,
[url]: toAcmeName(url),
}),
{ none: 'None (use system Root CA)' } as Record<string, string>,
),
default: '',
}),
type: ISB.Value.union(
{ name: 'Type', default: 'standard' },
ISB.Variants.of({
standard: {
name: 'Standard',
spec: ISB.InputSpec.of({
domain: ISB.Value.text({
name: 'Domain',
description: 'The domain or subdomain you want to use',
placeholder: `e.g. 'mydomain.com' or 'sub.mydomain.com'`,
required: true,
default: null,
patterns: [utils.Patterns.domain],
}),
acme: ISB.Value.select({
name: 'ACME Provider',
description:
'Select which ACME provider to use for obtaining your SSL certificate. Add new ACME providers in the System tab. Optionally use your system Root CA. Note: only devices that have trusted your Root CA will be able to access the domain without security warnings.',
values: acme.reduce(
(obj, url) => ({
...obj,
[url]: toAcmeName(url),
}),
{ none: 'None (use system Root CA)' } as Record<string, string>,
),
default: '',
}),
}),
},
onion: {
name: 'Onion',
spec: ISB.InputSpec.of({
key: ISB.Value.text({
name: 'Private Key (optional)',
description:
'Optionally provide a base64-encoded ed25519 private key for generating the Tor V3 (.onion) address. If not provided, a random key will be generated and used.',
required: false,
default: null,
patterns: [utils.Patterns.base64],
}),
}),
},
}),
),
})
}

Expand Down Expand Up @@ -255,10 +341,12 @@ export function getAddresses(
const mappedAddresses = hostnames.flatMap(h => {
let name = ''
let isDomain = false
let isOnion = false
let acme: string | null = null

if (h.kind === 'onion') {
name = `Tor`
isOnion = true
} else {
const hostnameKind = h.hostname.kind

Expand All @@ -282,13 +370,15 @@ export function getAddresses(
.toUpperCase()})`,
url,
isDomain,
isOnion,
acme,
}))
} else {
return addresses.map(url => ({
name,
url,
isDomain,
isOnion,
acme,
}))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const iface = {
export class ServerSpecsPage {
readonly server$ = this.patch.watch$('serverInfo')

readonly ui$ = this.server$.pipe(
readonly ui$: Observable<MappedInterface> = this.server$.pipe(
map(server => ({
...iface,
public: server.host.bindings[iface.addressInfo.internalPort].net.public,
Expand Down
24 changes: 24 additions & 0 deletions web/projects/ui/src/app/services/api/api.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,29 @@ export module RR {
}
export type RemoveAcmeRes = null

export type AddTorKeyReq = {
// net.tor.key.add
key: string
}
export type GenerateTorKeyReq = {} // net.tor.key.generate
export type AddTorKeyRes = string // onion address without .onion suffix

export type ServerBindingSetPublicReq = {
// server.host.binding.set-public
internalPort: number
public: boolean | null // default true
}
export type BindingSetPublicRes = null

export type ServerAddOnionReq = {
// server.host.address.onion.add
onion: string // address *with* .onion suffix
}
export type AddOnionRes = null

export type ServerRemoveOnionReq = ServerAddOnionReq // server.host.address.onion.remove
export type RemoveOnionRes = null

export type ServerAddDomainReq = {
// server.host.address.domain.add
domain: string // FQDN
Expand All @@ -257,6 +273,14 @@ export module RR {
host: T.HostId // string
}

export type PkgAddOnionReq = ServerAddOnionReq & {
// package.host.address.onion.add
package: T.PackageId // string
host: T.HostId // string
}

export type PkgRemoveOnionReq = PkgAddOnionReq // package.host.address.onion.remove

export type PkgAddDomainReq = ServerAddDomainReq & {
// package.host.address.domain.add
package: T.PackageId // string
Expand Down
18 changes: 18 additions & 0 deletions web/projects/ui/src/app/services/api/embassy-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,22 @@ export abstract class ApiService {

abstract removeAcme(params: RR.RemoveAcmeReq): Promise<RR.RemoveAcmeRes>

abstract addTorKey(params: RR.AddTorKeyReq): Promise<RR.AddTorKeyRes>

abstract generateTorKey(
params: RR.GenerateTorKeyReq,
): Promise<RR.AddTorKeyRes>

abstract serverBindingSetPubic(
params: RR.ServerBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes>

abstract serverAddOnion(params: RR.ServerAddOnionReq): Promise<RR.AddOnionRes>

abstract serverRemoveOnion(
params: RR.ServerRemoveOnionReq,
): Promise<RR.RemoveOnionRes>

abstract serverAddDomain(
params: RR.ServerAddDomainReq,
): Promise<RR.AddDomainRes>
Expand All @@ -280,6 +292,12 @@ export abstract class ApiService {
params: RR.PkgBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes>

abstract pkgAddOnion(params: RR.PkgAddOnionReq): Promise<RR.AddOnionRes>

abstract pkgRemoveOnion(
params: RR.PkgRemoveOnionReq,
): Promise<RR.RemoveOnionRes>

abstract pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes>

abstract pkgRemoveDomain(
Expand Down
46 changes: 46 additions & 0 deletions web/projects/ui/src/app/services/api/embassy-live-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,20 @@ export class LiveApiService extends ApiService {
})
}

async addTorKey(params: RR.AddTorKeyReq): Promise<RR.AddTorKeyRes> {
return this.rpcRequest({
method: 'net.tor.key.add',
params,
})
}

async generateTorKey(params: RR.GenerateTorKeyReq): Promise<RR.AddTorKeyRes> {
return this.rpcRequest({
method: 'net.tor.key.generate',
params,
})
}

async serverBindingSetPubic(
params: RR.ServerBindingSetPublicReq,
): Promise<RR.BindingSetPublicRes> {
Expand All @@ -539,6 +553,22 @@ export class LiveApiService extends ApiService {
})
}

async serverAddOnion(params: RR.ServerAddOnionReq): Promise<RR.AddOnionRes> {
return this.rpcRequest({
method: 'server.host.address.onion.add',
params,
})
}

async serverRemoveOnion(
params: RR.ServerRemoveOnionReq,
): Promise<RR.RemoveOnionRes> {
return this.rpcRequest({
method: 'server.host.address.onion.remove',
params,
})
}

async serverAddDomain(
params: RR.ServerAddDomainReq,
): Promise<RR.AddDomainRes> {
Expand Down Expand Up @@ -566,6 +596,22 @@ export class LiveApiService extends ApiService {
})
}

async pkgAddOnion(params: RR.PkgAddOnionReq): Promise<RR.AddOnionRes> {
return this.rpcRequest({
method: 'package.host.address.onion.add',
params,
})
}

async pkgRemoveOnion(
params: RR.PkgRemoveOnionReq,
): Promise<RR.RemoveOnionRes> {
return this.rpcRequest({
method: 'package.host.address.onion.remove',
params,
})
}

async pkgAddDomain(params: RR.PkgAddDomainReq): Promise<RR.AddDomainRes> {
return this.rpcRequest({
method: 'package.host.address.domain.add',
Expand Down
Loading

0 comments on commit 1ffb67f

Please sign in to comment.