mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-24 09:35:05 +00:00
fix(account): handle pending console login polling (#18281)
This commit is contained in:
@@ -148,6 +148,12 @@ export namespace AccountEffect {
|
|||||||
mapAccountServiceError("HTTP request failed"),
|
mapAccountServiceError("HTTP request failed"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const executeEffect = <E>(request: Effect.Effect<HttpClientRequest.HttpClientRequest, E>) =>
|
||||||
|
request.pipe(
|
||||||
|
Effect.flatMap((req) => http.execute(req)),
|
||||||
|
mapAccountServiceError("HTTP request failed"),
|
||||||
|
)
|
||||||
|
|
||||||
const resolveToken = Effect.fnUntraced(function* (row: AccountRow) {
|
const resolveToken = Effect.fnUntraced(function* (row: AccountRow) {
|
||||||
const now = yield* Clock.currentTimeMillis
|
const now = yield* Clock.currentTimeMillis
|
||||||
if (row.token_expiry && row.token_expiry > now) return row.access_token
|
if (row.token_expiry && row.token_expiry > now) return row.access_token
|
||||||
@@ -290,7 +296,7 @@ export namespace AccountEffect {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const poll = Effect.fn("Account.poll")(function* (input: Login) {
|
const poll = Effect.fn("Account.poll")(function* (input: Login) {
|
||||||
const response = yield* executeEffectOk(
|
const response = yield* executeEffect(
|
||||||
HttpClientRequest.post(`${input.server}/auth/device/token`).pipe(
|
HttpClientRequest.post(`${input.server}/auth/device/token`).pipe(
|
||||||
HttpClientRequest.acceptJson,
|
HttpClientRequest.acceptJson,
|
||||||
HttpClientRequest.schemaBodyJson(DeviceTokenRequest)(
|
HttpClientRequest.schemaBodyJson(DeviceTokenRequest)(
|
||||||
|
|||||||
@@ -34,6 +34,24 @@ const encodeOrg = Schema.encodeSync(Org)
|
|||||||
|
|
||||||
const org = (id: string, name: string) => encodeOrg(new Org({ id: OrgID.make(id), name }))
|
const org = (id: string, name: string) => encodeOrg(new Org({ id: OrgID.make(id), name }))
|
||||||
|
|
||||||
|
const login = () =>
|
||||||
|
new Login({
|
||||||
|
code: DeviceCode.make("device-code"),
|
||||||
|
user: UserCode.make("user-code"),
|
||||||
|
url: "https://one.example.com/verify",
|
||||||
|
server: "https://one.example.com",
|
||||||
|
expiry: Duration.seconds(600),
|
||||||
|
interval: Duration.seconds(5),
|
||||||
|
})
|
||||||
|
|
||||||
|
const deviceTokenClient = (body: unknown, status = 400) =>
|
||||||
|
HttpClient.make((req) =>
|
||||||
|
Effect.succeed(req.url === "https://one.example.com/auth/device/token" ? json(req, body, status) : json(req, {}, 404)),
|
||||||
|
)
|
||||||
|
|
||||||
|
const poll = (body: unknown, status = 400) =>
|
||||||
|
AccountEffect.Service.use((s) => s.poll(login())).pipe(Effect.provide(live(deviceTokenClient(body, status))))
|
||||||
|
|
||||||
it.effect("orgsByAccount groups orgs per account", () =>
|
it.effect("orgsByAccount groups orgs per account", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
yield* AccountRepo.use((r) =>
|
yield* AccountRepo.use((r) =>
|
||||||
@@ -172,15 +190,6 @@ it.effect("config sends the selected org header", () =>
|
|||||||
|
|
||||||
it.effect("poll stores the account and first org on success", () =>
|
it.effect("poll stores the account and first org on success", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const login = new Login({
|
|
||||||
code: DeviceCode.make("device-code"),
|
|
||||||
user: UserCode.make("user-code"),
|
|
||||||
url: "https://one.example.com/verify",
|
|
||||||
server: "https://one.example.com",
|
|
||||||
expiry: Duration.seconds(600),
|
|
||||||
interval: Duration.seconds(5),
|
|
||||||
})
|
|
||||||
|
|
||||||
const client = HttpClient.make((req) =>
|
const client = HttpClient.make((req) =>
|
||||||
Effect.succeed(
|
Effect.succeed(
|
||||||
req.url === "https://one.example.com/auth/device/token"
|
req.url === "https://one.example.com/auth/device/token"
|
||||||
@@ -198,7 +207,7 @@ it.effect("poll stores the account and first org on success", () =>
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const res = yield* AccountEffect.Service.use((s) => s.poll(login)).pipe(Effect.provide(live(client)))
|
const res = yield* AccountEffect.Service.use((s) => s.poll(login())).pipe(Effect.provide(live(client)))
|
||||||
|
|
||||||
expect(res._tag).toBe("PollSuccess")
|
expect(res._tag).toBe("PollSuccess")
|
||||||
if (res._tag === "PollSuccess") {
|
if (res._tag === "PollSuccess") {
|
||||||
@@ -215,3 +224,59 @@ it.effect("poll stores the account and first org on success", () =>
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
for (const [name, body, expectedTag] of [
|
||||||
|
[
|
||||||
|
"pending",
|
||||||
|
{
|
||||||
|
error: "authorization_pending",
|
||||||
|
error_description: "The authorization request is still pending",
|
||||||
|
},
|
||||||
|
"PollPending",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"slow",
|
||||||
|
{
|
||||||
|
error: "slow_down",
|
||||||
|
error_description: "Polling too frequently, please slow down",
|
||||||
|
},
|
||||||
|
"PollSlow",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"denied",
|
||||||
|
{
|
||||||
|
error: "access_denied",
|
||||||
|
error_description: "The authorization request was denied",
|
||||||
|
},
|
||||||
|
"PollDenied",
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"expired",
|
||||||
|
{
|
||||||
|
error: "expired_token",
|
||||||
|
error_description: "The device code has expired",
|
||||||
|
},
|
||||||
|
"PollExpired",
|
||||||
|
],
|
||||||
|
] as const) {
|
||||||
|
it.effect(`poll returns ${name} for ${body.error}`, () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const result = yield* poll(body)
|
||||||
|
expect(result._tag).toBe(expectedTag)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.effect("poll returns poll error for other OAuth errors", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const result = yield* poll({
|
||||||
|
error: "server_error",
|
||||||
|
error_description: "An unexpected error occurred",
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result._tag).toBe("PollError")
|
||||||
|
if (result._tag === "PollError") {
|
||||||
|
expect(String(result.cause)).toContain("server_error")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user