mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-24 14:43:37 -04:00
fix(opencode): validate device login URL
This commit is contained in:
@@ -396,10 +396,22 @@ const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient.HttpCl
|
||||
const parsed = yield* HttpClientResponse.schemaBodyJson(DeviceAuth)(response).pipe(
|
||||
mapAccountServiceError("Failed to decode response"),
|
||||
)
|
||||
const verification = yield* Effect.try({
|
||||
try: () => {
|
||||
const url = new URL(parsed.verification_uri_complete, `${normalizedServer}/`)
|
||||
if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("expected HTTP(S)")
|
||||
return url
|
||||
},
|
||||
catch: (cause) =>
|
||||
new AccountServiceError({
|
||||
message: `Invalid device verification URL: ${cause instanceof Error ? cause.message : String(cause)}`,
|
||||
cause,
|
||||
}),
|
||||
})
|
||||
return new Login({
|
||||
code: parsed.device_code,
|
||||
user: parsed.user_code,
|
||||
url: new URL(parsed.verification_uri_complete, normalizedServer).href,
|
||||
url: verification.href,
|
||||
server: normalizedServer,
|
||||
expiry: parsed.expires_in,
|
||||
interval: parsed.interval,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Account } from "../../src/account/account"
|
||||
import {
|
||||
AccessToken,
|
||||
AccountID,
|
||||
AccountServiceError,
|
||||
AccountTransportError,
|
||||
DeviceCode,
|
||||
Login,
|
||||
@@ -121,6 +122,72 @@ it.live("login resolves the verification URL against the server origin", () =>
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("login resolves relative verification URLs beneath the server path", () =>
|
||||
Effect.gen(function* () {
|
||||
const client = HttpClient.make((req) =>
|
||||
Effect.succeed(
|
||||
json(req, {
|
||||
device_code: "device-code",
|
||||
user_code: "user-code",
|
||||
verification_uri_complete: "device?user_code=user-code",
|
||||
expires_in: 600,
|
||||
interval: 5,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const result = yield* Account.use.login("https://one.example.com/console").pipe(Effect.provide(live(client)))
|
||||
|
||||
expect(result.url).toBe("https://one.example.com/console/device?user_code=user-code")
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("login rejects malformed verification URLs", () =>
|
||||
Effect.gen(function* () {
|
||||
const client = HttpClient.make((req) =>
|
||||
Effect.succeed(
|
||||
json(req, {
|
||||
device_code: "device-code",
|
||||
user_code: "user-code",
|
||||
verification_uri_complete: "http://[::1",
|
||||
expires_in: 600,
|
||||
interval: 5,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const error = yield* Effect.flip(
|
||||
Account.use.login("https://one.example.com/console").pipe(Effect.provide(live(client))),
|
||||
)
|
||||
|
||||
expect(error).toBeInstanceOf(AccountServiceError)
|
||||
expect(error.message).toContain("Invalid device verification URL")
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("login rejects non-HTTP verification URLs", () =>
|
||||
Effect.gen(function* () {
|
||||
const client = HttpClient.make((req) =>
|
||||
Effect.succeed(
|
||||
json(req, {
|
||||
device_code: "device-code",
|
||||
user_code: "user-code",
|
||||
verification_uri_complete: "file:///tmp/device",
|
||||
expires_in: 600,
|
||||
interval: 5,
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const error = yield* Effect.flip(
|
||||
Account.use.login("https://one.example.com/console").pipe(Effect.provide(live(client))),
|
||||
)
|
||||
|
||||
expect(error).toBeInstanceOf(AccountServiceError)
|
||||
expect(error.message).toContain("Invalid device verification URL: expected HTTP(S)")
|
||||
}),
|
||||
)
|
||||
|
||||
it.live("login maps transport failures to account transport errors", () =>
|
||||
Effect.gen(function* () {
|
||||
const client = HttpClient.make((req) =>
|
||||
|
||||
Reference in New Issue
Block a user