Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/src/commands/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export async function handleAuthCommand(args: string[]): Promise<void> {
if (!hasToken) {
console.log('')
console.log(chalk.yellow(' Token not configured. To get your token:'))
console.log(chalk.gray(' 1. Check the server startup logs (first run shows generated token)'))
console.log(chalk.gray(' 1. Check the server startup logs (hub now prints a local login URL)'))
console.log(chalk.gray(' 2. Read ~/.hapi/settings.json on the server'))
console.log(chalk.gray(' 3. Ask your server administrator (if token is set via env var)'))
console.log('')
Expand Down
2 changes: 1 addition & 1 deletion cli/src/ui/tokenInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async function promptForToken(): Promise<string> {

console.log(chalk.yellow('\nNo CLI_API_TOKEN found.'))
console.log(chalk.gray('Where to find the token:'))
console.log(chalk.gray(' 1. Check the server startup logs (first run shows generated token)'))
console.log(chalk.gray(' 1. Check the server startup logs (hub now prints a local login URL)'))
console.log(chalk.gray(' 2. Read ~/.hapi/settings.json on the server'))
console.log(chalk.gray(' 3. Ask your server administrator (if token is set via env var)\n'))

Expand Down
13 changes: 6 additions & 7 deletions hub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { PushNotificationChannel } from './push/pushNotificationChannel'
import { VisibilityTracker } from './visibility/visibilityTracker'
import { TunnelManager } from './tunnel'
import { waitForTunnelTlsReady } from './tunnel/tlsGate'
import { buildRelayDirectAccessUrl, buildTokenizedUrl } from './utils/directAccess'
import QRCode from 'qrcode'
import type { Server as BunServer } from 'bun'
import type { WebSocketData } from '@socket.io/bun-engine'
Expand Down Expand Up @@ -113,6 +114,8 @@ async function main() {
const relayFlag = resolveRelayFlag(process.argv)
const officialWebUrl = process.env.HAPI_OFFICIAL_WEB_URL || 'https://app.hapi.run'
const config = await createConfiguration()
const localUrl = `http://localhost:${config.listenPort}`
const localDirectAccessUrl = buildTokenizedUrl(localUrl, config.cliApiToken)
const baseCorsOrigins = normalizeOrigins(config.corsOrigins)
const relayCorsOrigin = normalizeOrigin(officialWebUrl)
const corsOrigins = relayFlag.enabled
Expand Down Expand Up @@ -225,7 +228,8 @@ async function main() {

console.log('')
console.log('[Web] Hub listening on :' + config.listenPort)
console.log('[Web] Local: http://localhost:' + config.listenPort)
console.log('[Web] Local: ' + localUrl)
console.log('[Web] Login: ' + localDirectAccessUrl)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MAJOR] Startup log prints tokenized login URL every run; CLI_API_TOKEN ends up in logs even when sourced from env/file. Evidence hub/src/index.ts:232.

Suggested fix:

if (config.cliApiTokenIsNew || process.env.HAPI_LOG_TOKENS === 'true') {
    console.log('[Web] Login:  ' + localDirectAccessUrl)
}


// Initialize tunnel AFTER web service is ready
let tunnelUrl: string | null = null
Expand Down Expand Up @@ -257,12 +261,7 @@ async function main() {

console.log('[Web] Public: ' + tunnelUrl)

// Generate direct access link with hub and token
const params = new URLSearchParams({
hub: tunnelUrl,
token: config.cliApiToken
})
const directAccessUrl = `${officialWebUrl}/?${params.toString()}`
const directAccessUrl = buildRelayDirectAccessUrl(officialWebUrl, tunnelUrl, config.cliApiToken)

console.log('')
console.log('Open in browser:')
Expand Down
26 changes: 26 additions & 0 deletions hub/src/utils/directAccess.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { describe, expect, it } from 'bun:test'
import { buildRelayDirectAccessUrl, buildTokenizedUrl } from './directAccess'

describe('buildTokenizedUrl', () => {
it('adds the token query parameter', () => {
expect(buildTokenizedUrl('http://localhost:3006', 'secret-token'))
.toBe('http://localhost:3006/?token=secret-token')
})

it('preserves existing query parameters', () => {
expect(buildTokenizedUrl('https://example.com/app?foo=bar', 'secret-token'))
.toBe('https://example.com/app?foo=bar&token=secret-token')
})
})

describe('buildRelayDirectAccessUrl', () => {
it('adds hub and token query parameters', () => {
expect(buildRelayDirectAccessUrl('https://app.hapi.run', 'https://relay.example', 'secret-token'))
.toBe('https://app.hapi.run/?hub=https%3A%2F%2Frelay.example&token=secret-token')
})

it('preserves existing query parameters', () => {
expect(buildRelayDirectAccessUrl('https://app.hapi.run/?lang=zh-CN', 'https://relay.example', 'secret-token'))
.toBe('https://app.hapi.run/?lang=zh-CN&hub=https%3A%2F%2Frelay.example&token=secret-token')
})
})
12 changes: 12 additions & 0 deletions hub/src/utils/directAccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function buildTokenizedUrl(baseUrl: string, token: string): string {
const url = new URL(baseUrl)
url.searchParams.set('token', token)
return url.toString()
}

export function buildRelayDirectAccessUrl(webUrl: string, hubUrl: string, token: string): string {
const url = new URL(webUrl)
url.searchParams.set('hub', hubUrl)
url.searchParams.set('token', token)
return url.toString()
}
Loading