fmt(js): npm run fix on merge (#69503)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
hermes-seaeye[bot] 2026-07-22 16:30:14 +00:00 committed by GitHub
parent 163fab8d00
commit bcc3396b25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 38 additions and 43 deletions

View file

@ -80,19 +80,6 @@ import { installEmbedReferer } from './embed-referer'
import { createEventDeduper } from './event-dedupe'
import { readDirForIpc } from './fs-read-dir'
import { probeGatewayWebSocket } from './gateway-ws-probe'
import { runNativeLogin } from './native-oauth-login'
import {
nativeRefreshUrl,
parseTokenResponse,
resolveLoginStrategy,
tokenNeedsRefresh,
type NativeTokenSet
} from './native-oauth'
import {
oauthSessionIsLive,
resolveJsonBody,
resolveOauthRestAuth
} from './native-auth-decisions'
import { scanGitRepos } from './git-repo-scan'
import {
fileDiffVsHead,
@ -129,6 +116,15 @@ import {
} from './hardening'
import { createLinkTitleWindow, guardLinkTitleSession, readLinkTitleWindowTitle } from './link-title-window'
import { ensureMainWindow } from './main-window-lifecycle'
import { oauthSessionIsLive, resolveJsonBody, resolveOauthRestAuth } from './native-auth-decisions'
import {
nativeRefreshUrl,
type NativeTokenSet,
parseTokenResponse,
resolveLoginStrategy,
tokenNeedsRefresh
} from './native-oauth'
import { runNativeLogin } from './native-oauth-login'
import { serializeJsonBody, setJsonRequestHeaders } from './oauth-net-request'
import { createKeepAwake } from './power-save'
import { decideProfileDeleteAction, profileNameFromDeleteRequest, resolveRouteProfile } from './profile-delete-routing'
@ -5851,6 +5847,7 @@ async function ensureNativeAccessToken(baseUrl: string): Promise<string | null>
{ refresh_token: tokens.refreshToken, provider: tokens.provider },
{ timeoutMs: 10_000 }
)
const rotated = parseTokenResponse(body)
_storeNativeTokens(baseUrl, rotated)
@ -5883,6 +5880,7 @@ async function mintGatewayWsTicket(baseUrl) {
timeoutMs: 8_000,
bearer: nativeAt
})) as any
const ticket = body?.ticket
if (!ticket || typeof ticket !== 'string') {
@ -6459,10 +6457,7 @@ async function sanitizeDesktopConnectionConfig(config = readDesktopConnectionCon
// RFC 8252 flow) counts as connected too — otherwise a completed native
// sign-in shows "not connected" in Settings. The authoritative liveness
// check is the ws-ticket mint in resolveRemoteBackend at actual connect time.
remoteOauthConnected = oauthSessionIsLive(
hasNativeSession(remoteUrl),
await hasLiveOauthSession(remoteUrl)
)
remoteOauthConnected = oauthSessionIsLive(hasNativeSession(remoteUrl), await hasLiveOauthSession(remoteUrl))
} catch {
remoteOauthConnected = false
}
@ -8955,6 +8950,7 @@ ipcMain.handle('hermes:connection-config:oauth-login', async (_event, rawUrl) =>
postJson: (url, body, opts) => postJsonNoAuth(url, body, opts),
rememberLog
})
_storeNativeTokens(baseUrl, tokens)
return { ok: true, baseUrl, connected: true }
@ -8977,6 +8973,7 @@ ipcMain.handle('hermes:connection-config:oauth-login', async (_event, rawUrl) =>
ipcMain.handle('hermes:connection-config:oauth-logout', async (_event, rawUrl) => {
const baseUrl = rawUrl ? normalizeRemoteBaseUrl(rawUrl) : ''
await clearOauthSession(baseUrl || undefined)
// Also drop any native (RFC 8252) bearer tokens for this gateway so a
// logout clears BOTH auth shapes.
if (baseUrl) {
@ -8986,9 +8983,7 @@ ipcMain.handle('hermes:connection-config:oauth-logout', async (_event, rawUrl) =
// Report against the SAME liveness notion the Settings indicator uses
// (AT-or-RT cookie, or a native token) so a logout that left any session
// behind is reflected as still-connected rather than silently signed-out.
const connected = baseUrl
? (await hasLiveOauthSession(baseUrl)) || hasNativeSession(baseUrl)
: false
const connected = baseUrl ? (await hasLiveOauthSession(baseUrl)) || hasNativeSession(baseUrl) : false
return { ok: true, connected }
})

View file

@ -10,11 +10,7 @@ import assert from 'node:assert/strict'
import { test } from 'vitest'
import {
oauthSessionIsLive,
resolveJsonBody,
resolveOauthRestAuth
} from './native-auth-decisions'
import { oauthSessionIsLive, resolveJsonBody, resolveOauthRestAuth } from './native-auth-decisions'
// --- 1. body encoding (guards the double-JSON.stringify 422) ---

View file

@ -44,9 +44,7 @@ export function oauthSessionIsLive(hasNativeToken: boolean, hasCookieSession: bo
return hasNativeToken || hasCookieSession
}
export type OauthRestAuth =
| { kind: 'bearer'; token: string }
| { kind: 'cookie' }
export type OauthRestAuth = { kind: 'bearer'; token: string } | { kind: 'cookie' }
/**
* Decide how an oauth-mode REST request authenticates: prefer the native

View file

@ -22,14 +22,18 @@ function makeFakeServerFactory(port = 51234) {
const createServer: any = (handler: any) => {
state.handler = handler
const server: any = new EventEmitter()
server.listen = (_port: number, _host: string, cb: () => void) => {
state.listening = true
cb()
}
server.address = () => ({ address: '127.0.0.1', family: 'IPv4', port })
server.close = () => {
state.closed = true
}
state.server = server
return server

View file

@ -32,10 +32,10 @@ import {
buildNativeAuthorizeUrl,
generatePkcePair,
generateState,
type NativeTokenSet,
nativeTokenUrl,
parseLoopbackCallback,
parseTokenResponse,
type NativeTokenSet
parseTokenResponse
} from './native-oauth'
// Loopback login must complete inside this window (user opens browser,
@ -90,6 +90,7 @@ export async function runNativeLogin(
return new Promise<NativeTokenSet>((resolve, reject) => {
let settled = false
let timer: NodeJS.Timeout | null = null
const server = createServer((req, res) => {
// Only the callback path carries the code; any other path (favicon,
// etc.) still gets the friendly page so the browser tab looks sane.
@ -179,6 +180,7 @@ export async function runNativeLogin(
}
const redirectUri = `http://127.0.0.1:${addr.port}/callback`
const authorizeUrl = buildNativeAuthorizeUrl(baseUrl, {
challenge,
redirectUri,

View file

@ -34,6 +34,7 @@ test('generatePkcePair produces a valid S256 verifier/challenge', () => {
assert.equal(pair.method, 'S256')
// Verifier length within RFC 7636 range (43128).
assert.ok(pair.verifier.length >= 43 && pair.verifier.length <= 128)
// Challenge must be the base64url SHA-256 of the verifier.
const expected = createHash('sha256')
.update(pair.verifier, 'ascii')
@ -41,6 +42,7 @@ test('generatePkcePair produces a valid S256 verifier/challenge', () => {
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '')
assert.equal(pair.challenge, expected)
// No padding / URL-unsafe chars.
assert.doesNotMatch(pair.verifier, /[+/=]/)
@ -91,6 +93,7 @@ test('buildNativeAuthorizeUrl encodes params and honours a path prefix', () => {
state: 'STATE',
provider: 'nous'
})
const parsed = new URL(url)
assert.equal(parsed.origin, 'https://gw.example.com')
@ -108,6 +111,7 @@ test('buildNativeAuthorizeUrl omits provider when not given and preserves prefix
redirectUri: 'http://127.0.0.1:1/cb',
state: 'S'
})
const parsed = new URL(url)
assert.equal(parsed.pathname, '/hermes/auth/native/authorize')
@ -128,10 +132,7 @@ test('parseLoopbackCallback returns the code on a state match', () => {
})
test('parseLoopbackCallback throws on state mismatch (CSRF)', () => {
assert.throws(
() => parseLoopbackCallback('/callback?code=abc&state=attacker', 'expected'),
/state mismatch/i
)
assert.throws(() => parseLoopbackCallback('/callback?code=abc&state=attacker', 'expected'), /state mismatch/i)
})
test('parseLoopbackCallback surfaces a gateway error param', () => {

View file

@ -86,10 +86,7 @@ export function statusSupportsNativeFlow(statusBody: any): boolean {
* (e.g. a corporate proxy that blocks loopback). Precedence written down here,
* in one place, as a pure function per the desktop "observable ladder" rule.
*/
export function resolveLoginStrategy(
statusBody: any,
opts: { forceEmbedded?: boolean } = {}
): 'native' | 'embedded' {
export function resolveLoginStrategy(statusBody: any, opts: { forceEmbedded?: boolean } = {}): 'native' | 'embedded' {
if (opts.forceEmbedded) {
return 'embedded'
}
@ -109,6 +106,7 @@ export function buildNativeAuthorizeUrl(
): string {
const parsed = new URL(baseUrl)
const prefix = parsed.pathname.replace(/\/+$/, '')
const q = new URLSearchParams({
code_challenge: params.challenge,
code_challenge_method: 'S256',
@ -145,10 +143,7 @@ export function nativeRefreshUrl(baseUrl: string): string {
* `expectedState` MUST match (CSRF defense RFC 6749 §10.12); a mismatch
* throws rather than proceeding.
*/
export function parseLoopbackCallback(
requestUrl: string,
expectedState: string
): { code: string } {
export function parseLoopbackCallback(requestUrl: string, expectedState: string): { code: string } {
// requestUrl is the path+query the loopback server received, e.g.
// "/callback?code=...&state=...". Resolve against a dummy origin to parse.
const parsed = new URL(requestUrl, 'http://127.0.0.1')
@ -203,7 +198,11 @@ export function parseTokenResponse(body: any): NativeTokenSet {
* before use. `skewSeconds` refreshes slightly early to avoid a race where
* the token expires in flight (mirrors the server's 60s cookie floor).
*/
export function tokenNeedsRefresh(tokens: Pick<NativeTokenSet, 'expiresAt'>, nowSeconds: number, skewSeconds = 60): boolean {
export function tokenNeedsRefresh(
tokens: Pick<NativeTokenSet, 'expiresAt'>,
nowSeconds: number,
skewSeconds = 60
): boolean {
if (!tokens || !Number.isFinite(tokens.expiresAt) || tokens.expiresAt <= 0) {
// Unknown expiry ⇒ treat as needing refresh so we validate before use.
return true