diff --git a/CHANGELOG.md b/CHANGELOG.md index c5250884..68cc0f20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- added: `EdgeContext.fetchChallenge`, to request an account-creation CAPTCHA. +- added: Accept a challengeId in `EdgeContext.usernameAvailable` + ## 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 92dcdda7..549b962e 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,9 +72,16 @@ export function makeContextApi(ai: ApiInput): EdgeContext { await removeStash(ai, loginId) }, - async usernameAvailable(username: string): Promise { + async fetchChallenge() { + const response = await loginFetch(ai, 'POST', '/v2/captcha/create', {}) + const { challengeId, challengeUri } = asChallengeErrorPayload(response) + return { challengeId, challengeUri } + }, + + 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 eb15a728..bf36a32e 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) @@ -147,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 diff --git a/src/types/server-cleaners.ts b/src/types/server-cleaners.ts index 46958347..bc8c6403 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 0ccbdc3d..c2b2bf78 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 b863bace..d5cbbbbb 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1840,8 +1840,17 @@ 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 usernameAvailable: ( + username: string, + opts?: { challengeId?: string } + ) => Promise readonly createAccount: ( opts: EdgeCreateAccountOptions & EdgeAccountOptions ) => Promise