From 6f66400f3c11e568e4565ee757cf18468681fa7f Mon Sep 17 00:00:00 2001 From: William Swanson Date: Mon, 2 Sep 2024 10:28:21 -0700 Subject: [PATCH 1/3] Add an `EdgeContext.fetchChallenge` method --- CHANGELOG.md | 2 ++ src/core/context/context-api.ts | 8 ++++++++ src/types/server-cleaners.ts | 10 ++++++++++ src/types/server-types.ts | 8 ++++++++ src/types/types.ts | 6 ++++++ 5 files changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c52508842..e5e9a7b69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- added: `EdgeContext.fetchChallenge`, to request an account-creation CAPTCHA. + ## 2.14.0 (2024-09-03) - added: Log HTTP 409 errors from login server with breadcrumbs. diff --git a/src/core/context/context-api.ts b/src/core/context/context-api.ts index 92dcdda7e..19da4087d 100644 --- a/src/core/context/context-api.ts +++ b/src/core/context/context-api.ts @@ -2,6 +2,7 @@ import { bridgifyObject, onMethod, watchMethod } from 'yaob' import { checkPasswordRules, fixUsername } from '../../client-side' import { + asChallengeErrorPayload, EdgeAccount, EdgeAccountOptions, EdgeContext, @@ -17,6 +18,7 @@ import { findAppLogin, makeAccount } from '../account/account-init' import { createLogin, usernameAvailable } from '../login/create' import { requestEdgeLogin } from '../login/edge' import { makeLoginTree, syncLogin } from '../login/login' +import { loginFetch } from '../login/login-fetch' import { fetchLoginMessages } from '../login/login-messages' import { getEmptyStash, @@ -70,6 +72,12 @@ export function makeContextApi(ai: ApiInput): EdgeContext { await removeStash(ai, loginId) }, + async fetchChallenge() { + const response = await loginFetch(ai, 'POST', '/v2/captcha/create', {}) + const { challengeId, challengeUri } = asChallengeErrorPayload(response) + return { challengeId, challengeUri } + }, + async usernameAvailable(username: string): Promise { username = fixUsername(username) return await usernameAvailable(ai, username) diff --git a/src/types/server-cleaners.ts b/src/types/server-cleaners.ts index 469583475..bc8c64038 100644 --- a/src/types/server-cleaners.ts +++ b/src/types/server-cleaners.ts @@ -26,6 +26,7 @@ import type { ChangeSecretPayload, ChangeUsernamePayload, ChangeVouchersPayload, + CreateChallengePayload, CreateKeysPayload, CreateLoginPayload, EdgeBox, @@ -257,6 +258,12 @@ export const asChallengeErrorPayload: Cleaner = asObject( } ) +export const asCreateChallengePayload: Cleaner = + asObject({ + challengeId: asString, + challengeUri: asOptional(asString) + }) + export const asLobbyPayload: Cleaner = asObject({ request: asEdgeLobbyRequest, replies: asArray(asEdgeLobbyReply) @@ -407,6 +414,9 @@ export const wasCreateLoginPayload = export const wasChallengeErrorPayload = uncleaner( asChallengeErrorPayload ) +export const wasCreateChallengePayload = uncleaner( + asCreateChallengePayload +) export const wasLobbyPayload = uncleaner(asLobbyPayload) export const wasLoginPayload = uncleaner(asLoginPayload) export const wasMessagesPayload = uncleaner(asMessagesPayload) diff --git a/src/types/server-types.ts b/src/types/server-types.ts index 0ccbdc3df..c2b2bf78a 100644 --- a/src/types/server-types.ts +++ b/src/types/server-types.ts @@ -194,6 +194,14 @@ export interface ChallengeErrorPayload { challengeUri: string } +/** + * Data sent back when preemptively requesting a CAPTCHA. + */ +export interface CreateChallengePayload { + challengeId: string + challengeUri?: string +} + /** * Data sent back when looking up a login barcode. */ diff --git a/src/types/types.ts b/src/types/types.ts index b863bacec..ba7eb487b 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1840,6 +1840,12 @@ export interface EdgeContext { readonly forgetAccount: (rootLoginId: string) => Promise // Account creation: + /** Preemptively requests a CAPTCHA for account creation. */ + readonly fetchChallenge: () => Promise<{ + challengeId: string + /** If this is missing, the challenge is already solved. */ + challengeUri?: string + }> readonly fixUsername: (username: string) => string readonly usernameAvailable: (username: string) => Promise readonly createAccount: ( From 26aa8a87f035da1bec134f9bbbcc2f885d41c46f Mon Sep 17 00:00:00 2001 From: William Swanson Date: Mon, 2 Sep 2024 10:30:51 -0700 Subject: [PATCH 2/3] Accept a challengeId in `usernameAvailable` --- CHANGELOG.md | 1 + src/core/context/context-api.ts | 5 +++-- src/core/login/create.ts | 4 +++- src/types/types.ts | 5 ++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e9a7b69..68cc0f200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - added: `EdgeContext.fetchChallenge`, to request an account-creation CAPTCHA. +- added: Accept a challengeId in `EdgeContext.usernameAvailable` ## 2.14.0 (2024-09-03) diff --git a/src/core/context/context-api.ts b/src/core/context/context-api.ts index 19da4087d..549b962e1 100644 --- a/src/core/context/context-api.ts +++ b/src/core/context/context-api.ts @@ -78,9 +78,10 @@ export function makeContextApi(ai: ApiInput): EdgeContext { return { challengeId, challengeUri } }, - async usernameAvailable(username: string): Promise { + async usernameAvailable(username: string, opts = {}): Promise { + const { challengeId } = opts username = fixUsername(username) - return await usernameAvailable(ai, username) + return await usernameAvailable(ai, username, challengeId) }, async createAccount( diff --git a/src/core/login/create.ts b/src/core/login/create.ts index eb15a7287..af44d00d3 100644 --- a/src/core/login/create.ts +++ b/src/core/login/create.ts @@ -29,10 +29,12 @@ export interface LoginCreateOpts { */ export async function usernameAvailable( ai: ApiInput, - username: string + username: string, + challengeId?: string ): Promise { const userId = await hashUsername(ai, username) const request = { + challengeId, userId } return await loginFetch(ai, 'POST', '/v2/login', request) diff --git a/src/types/types.ts b/src/types/types.ts index ba7eb487b..d5cbbbbb8 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1847,7 +1847,10 @@ export interface EdgeContext { challengeUri?: string }> readonly fixUsername: (username: string) => string - readonly usernameAvailable: (username: string) => Promise + readonly usernameAvailable: ( + username: string, + opts?: { challengeId?: string } + ) => Promise readonly createAccount: ( opts: EdgeCreateAccountOptions & EdgeAccountOptions ) => Promise From e41eb1f3414e61416ef1b109bfaa96b3c423cd99 Mon Sep 17 00:00:00 2001 From: William Swanson Date: Wed, 4 Sep 2024 10:10:19 -0700 Subject: [PATCH 3/3] Pass a `challengeId` to the challenge endpoint --- src/core/login/create.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/login/create.ts b/src/core/login/create.ts index af44d00d3..bf36a32e3 100644 --- a/src/core/login/create.ts +++ b/src/core/login/create.ts @@ -149,13 +149,13 @@ export async function createLogin( accountOpts: EdgeAccountOptions, opts: LoginCreateOpts ): Promise { + const { challengeId, now = new Date() } = accountOpts + // For crash errors: ai.props.log.breadcrumb('createLogin', {}) - const { now = new Date() } = accountOpts - const kit = await makeCreateKit(ai, undefined, '', opts) - const request = { data: kit.server } + const request = { challengeId, data: kit.server } await loginFetch(ai, 'POST', kit.serverPath, request) kit.stash.lastLogin = now