refactor(frontend): simplify API client and delete obsolete auth routes

This commit is contained in:
Lewis
2026-03-19 14:26:26 +00:00
committed by Tangled
parent 6225d2bc1d
commit 20212a413f
4 changed files with 12 additions and 894 deletions
+12 -634
View File
@@ -441,9 +441,13 @@ export const api = {
params: {
did: Did;
handle: Handle;
email: EmailAddress;
email?: EmailAddress;
password: string;
inviteCode?: string;
verificationChannel?: string;
discordUsername?: string;
telegramUsername?: string;
signalUsername?: string;
},
): Promise<Session> {
const url = `${API_BASE}/com.atproto.server.createAccount`;
@@ -459,6 +463,10 @@ export const api = {
email: params.email,
password: params.password,
inviteCode: params.inviteCode,
verificationChannel: params.verificationChannel,
discordUsername: params.discordUsername,
telegramUsername: params.telegramUsername,
signalUsername: params.signalUsername,
}),
});
const data = await response.json();
@@ -1231,11 +1239,12 @@ export const api = {
},
resendMigrationVerification(
email: EmailAddress,
channel: string,
identifier: string,
): Promise<ResendMigrationVerificationResponse> {
return xrpc("com.atproto.server.resendMigrationVerification", {
method: "POST",
body: { email },
body: { channel, identifier },
});
},
@@ -1509,634 +1518,3 @@ export const api = {
},
};
export const typedApi = {
createSession(
identifier: string,
password: string,
): Promise<Result<Session, ApiError>> {
return xrpcResult<Session>("com.atproto.server.createSession", {
method: "POST",
body: { identifier, password },
}).then((r) => r.ok ? ok(castSession(r.value)) : r);
},
getSession(token: AccessToken): Promise<Result<Session, ApiError>> {
return xrpcResult<Session>("com.atproto.server.getSession", { token })
.then((r) => r.ok ? ok(castSession(r.value)) : r);
},
refreshSession(refreshJwt: RefreshToken): Promise<Result<Session, ApiError>> {
return xrpcResult<Session>("com.atproto.server.refreshSession", {
method: "POST",
token: refreshJwt,
}).then((r) => r.ok ? ok(castSession(r.value)) : r);
},
describeServer(): Promise<Result<ServerDescription, ApiError>> {
return xrpcResult("com.atproto.server.describeServer");
},
listAppPasswords(
token: AccessToken,
): Promise<Result<{ passwords: AppPassword[] }, ApiError>> {
return xrpcResult("com.atproto.server.listAppPasswords", { token });
},
createAppPassword(
token: AccessToken,
name: string,
scopes?: string,
): Promise<Result<CreatedAppPassword, ApiError>> {
return xrpcResult("com.atproto.server.createAppPassword", {
method: "POST",
token,
body: { name, scopes },
});
},
revokeAppPassword(
token: AccessToken,
name: string,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.revokeAppPassword", {
method: "POST",
token,
body: { name },
});
},
listSessions(
token: AccessToken,
): Promise<Result<ListSessionsResponse, ApiError>> {
return xrpcResult("_account.listSessions", { token });
},
revokeSession(
token: AccessToken,
sessionId: string,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("_account.revokeSession", {
method: "POST",
token,
body: { sessionId },
});
},
getTotpStatus(token: AccessToken): Promise<Result<TotpStatus, ApiError>> {
return xrpcResult("com.atproto.server.getTotpStatus", { token });
},
createTotpSecret(token: AccessToken): Promise<Result<TotpSecret, ApiError>> {
return xrpcResult("com.atproto.server.createTotpSecret", {
method: "POST",
token,
});
},
enableTotp(
token: AccessToken,
code: string,
): Promise<Result<EnableTotpResponse, ApiError>> {
return xrpcResult("com.atproto.server.enableTotp", {
method: "POST",
token,
body: { code },
});
},
disableTotp(
token: AccessToken,
password: string,
code: string,
): Promise<Result<SuccessResponse, ApiError>> {
return xrpcResult("com.atproto.server.disableTotp", {
method: "POST",
token,
body: { password, code },
});
},
listPasskeys(
token: AccessToken,
): Promise<Result<ListPasskeysResponse, ApiError>> {
return xrpcResult("com.atproto.server.listPasskeys", { token });
},
deletePasskey(
token: AccessToken,
id: string,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.deletePasskey", {
method: "POST",
token,
body: { id },
});
},
listTrustedDevices(
token: AccessToken,
): Promise<Result<ListTrustedDevicesResponse, ApiError>> {
return xrpcResult("_account.listTrustedDevices", { token });
},
getReauthStatus(token: AccessToken): Promise<Result<ReauthStatus, ApiError>> {
return xrpcResult("_account.getReauthStatus", { token });
},
getNotificationPrefs(
token: AccessToken,
): Promise<Result<NotificationPrefs, ApiError>> {
return xrpcResult("_account.getNotificationPrefs", { token });
},
updateHandle(
token: AccessToken,
handle: Handle,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.identity.updateHandle", {
method: "POST",
token,
body: { handle },
});
},
describeRepo(
token: AccessToken,
repo: Did,
): Promise<Result<RepoDescription, ApiError>> {
return xrpcResult("com.atproto.repo.describeRepo", {
token,
params: { repo },
});
},
listRecords(
token: AccessToken,
repo: Did,
collection: Nsid,
options?: { limit?: number; cursor?: string; reverse?: boolean },
): Promise<Result<ListRecordsResponse, ApiError>> {
const params: Record<string, string> = { repo, collection };
if (options?.limit) params.limit = String(options.limit);
if (options?.cursor) params.cursor = options.cursor;
if (options?.reverse) params.reverse = "true";
return xrpcResult("com.atproto.repo.listRecords", { token, params });
},
getRecord(
token: AccessToken,
repo: Did,
collection: Nsid,
rkey: Rkey,
): Promise<Result<RecordResponse, ApiError>> {
return xrpcResult("com.atproto.repo.getRecord", {
token,
params: { repo, collection, rkey },
});
},
deleteRecord(
token: AccessToken,
repo: Did,
collection: Nsid,
rkey: Rkey,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.repo.deleteRecord", {
method: "POST",
token,
body: { repo, collection, rkey },
});
},
searchAccounts(
token: AccessToken,
options?: { handle?: string; cursor?: string; limit?: number },
): Promise<Result<SearchAccountsResponse, ApiError>> {
const params: Record<string, string> = {};
if (options?.handle) params.handle = options.handle;
if (options?.cursor) params.cursor = options.cursor;
if (options?.limit) params.limit = String(options.limit);
return xrpcResult("com.atproto.admin.searchAccounts", { token, params });
},
getAccountInfo(
token: AccessToken,
did: Did,
): Promise<Result<AccountInfo, ApiError>> {
return xrpcResult("com.atproto.admin.getAccountInfo", {
token,
params: { did },
});
},
getServerStats(token: AccessToken): Promise<Result<ServerStats, ApiError>> {
return xrpcResult("_admin.getServerStats", { token });
},
getDidDocument(token: AccessToken): Promise<Result<DidDocument, ApiError>> {
return xrpcResult("_account.getDidDocument", { token });
},
deleteSession(token: AccessToken): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.deleteSession", {
method: "POST",
token,
});
},
revokeAllSessions(
token: AccessToken,
): Promise<Result<{ revokedCount: number }, ApiError>> {
return xrpcResult("_account.revokeAllSessions", {
method: "POST",
token,
});
},
getAccountInviteCodes(
token: AccessToken,
): Promise<Result<{ codes: InviteCodeInfo[] }, ApiError>> {
return xrpcResult("com.atproto.server.getAccountInviteCodes", { token });
},
createInviteCode(
token: AccessToken,
useCount: number = 1,
): Promise<Result<{ code: string }, ApiError>> {
return xrpcResult("com.atproto.server.createInviteCode", {
method: "POST",
token,
body: { useCount },
});
},
changePassword(
token: AccessToken,
currentPassword: string,
newPassword: string,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("_account.changePassword", {
method: "POST",
token,
body: { currentPassword, newPassword },
});
},
getPasswordStatus(
token: AccessToken,
): Promise<Result<PasswordStatus, ApiError>> {
return xrpcResult("_account.getPasswordStatus", { token });
},
getServerConfig(): Promise<Result<ServerConfig, ApiError>> {
return xrpcResult("_server.getConfig");
},
getLegacyLoginPreference(
token: AccessToken,
): Promise<Result<LegacyLoginPreference, ApiError>> {
return xrpcResult("_account.getLegacyLoginPreference", { token });
},
updateLegacyLoginPreference(
token: AccessToken,
allowLegacyLogin: boolean,
): Promise<Result<UpdateLegacyLoginResponse, ApiError>> {
return xrpcResult("_account.updateLegacyLoginPreference", {
method: "POST",
token,
body: { allowLegacyLogin },
});
},
getNotificationHistory(
token: AccessToken,
): Promise<Result<NotificationHistoryResponse, ApiError>> {
return xrpcResult("_account.getNotificationHistory", { token });
},
updateNotificationPrefs(
token: AccessToken,
prefs: {
preferredChannel?: string;
discordUsername?: string;
telegramUsername?: string;
signalUsername?: string;
},
): Promise<Result<UpdateNotificationPrefsResponse, ApiError>> {
return xrpcResult("_account.updateNotificationPrefs", {
method: "POST",
token,
body: prefs,
});
},
revokeTrustedDevice(
token: AccessToken,
deviceId: string,
): Promise<Result<SuccessResponse, ApiError>> {
return xrpcResult("_account.revokeTrustedDevice", {
method: "POST",
token,
body: { deviceId },
});
},
updateTrustedDevice(
token: AccessToken,
deviceId: string,
friendlyName: string,
): Promise<Result<SuccessResponse, ApiError>> {
return xrpcResult("_account.updateTrustedDevice", {
method: "POST",
token,
body: { deviceId, friendlyName },
});
},
reauthPassword(
token: AccessToken,
password: string,
): Promise<Result<ReauthResponse, ApiError>> {
return xrpcResult("_account.reauthPassword", {
method: "POST",
token,
body: { password },
});
},
reauthTotp(
token: AccessToken,
code: string,
): Promise<Result<ReauthResponse, ApiError>> {
return xrpcResult("_account.reauthTotp", {
method: "POST",
token,
body: { code },
});
},
reauthPasskeyStart(
token: AccessToken,
): Promise<Result<ReauthPasskeyStartResponse, ApiError>> {
return xrpcResult("_account.reauthPasskeyStart", {
method: "POST",
token,
});
},
reauthPasskeyFinish(
token: AccessToken,
credential: unknown,
): Promise<Result<ReauthResponse, ApiError>> {
return xrpcResult("_account.reauthPasskeyFinish", {
method: "POST",
token,
body: { credential },
});
},
confirmSignup(
did: Did,
verificationCode: string,
): Promise<Result<ConfirmSignupResult, ApiError>> {
return xrpcResult("com.atproto.server.confirmSignup", {
method: "POST",
body: { did, verificationCode },
});
},
resendVerification(
did: Did,
): Promise<Result<{ success: boolean }, ApiError>> {
return xrpcResult("com.atproto.server.resendVerification", {
method: "POST",
body: { did },
});
},
requestEmailUpdate(
token: AccessToken,
): Promise<Result<EmailUpdateResponse, ApiError>> {
return xrpcResult("com.atproto.server.requestEmailUpdate", {
method: "POST",
token,
});
},
updateEmail(
token: AccessToken,
email: string,
emailToken?: string,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.updateEmail", {
method: "POST",
token,
body: { email, token: emailToken },
});
},
requestAccountDelete(token: AccessToken): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.requestAccountDelete", {
method: "POST",
token,
});
},
deleteAccount(
did: Did,
password: string,
deleteToken: string,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.deleteAccount", {
method: "POST",
body: { did, password, token: deleteToken },
});
},
updateDidDocument(
token: AccessToken,
params: {
verificationMethods?: VerificationMethod[];
alsoKnownAs?: string[];
serviceEndpoint?: string;
},
): Promise<Result<SuccessResponse, ApiError>> {
return xrpcResult("_account.updateDidDocument", {
method: "POST",
token,
body: params,
});
},
deactivateAccount(
token: AccessToken,
deleteAfter?: string,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.deactivateAccount", {
method: "POST",
token,
body: { deleteAfter },
});
},
activateAccount(token: AccessToken): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.activateAccount", {
method: "POST",
token,
});
},
createRecord(
token: AccessToken,
repo: Did,
collection: Nsid,
record: unknown,
rkey?: Rkey,
): Promise<Result<CreateRecordResponse, ApiError>> {
return xrpcResult("com.atproto.repo.createRecord", {
method: "POST",
token,
body: { repo, collection, record, rkey },
});
},
putRecord(
token: AccessToken,
repo: Did,
collection: Nsid,
rkey: Rkey,
record: unknown,
): Promise<Result<CreateRecordResponse, ApiError>> {
return xrpcResult("com.atproto.repo.putRecord", {
method: "POST",
token,
body: { repo, collection, rkey, record },
});
},
getInviteCodes(
token: AccessToken,
options?: { sort?: "recent" | "usage"; cursor?: string; limit?: number },
): Promise<Result<GetInviteCodesResponse, ApiError>> {
const params: Record<string, string> = {};
if (options?.sort) params.sort = options.sort;
if (options?.cursor) params.cursor = options.cursor;
if (options?.limit) params.limit = String(options.limit);
return xrpcResult("com.atproto.admin.getInviteCodes", { token, params });
},
disableAccountInvites(
token: AccessToken,
account: Did,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.admin.disableAccountInvites", {
method: "POST",
token,
body: { account },
});
},
enableAccountInvites(
token: AccessToken,
account: Did,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.admin.enableAccountInvites", {
method: "POST",
token,
body: { account },
});
},
adminDeleteAccount(
token: AccessToken,
did: Did,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.admin.deleteAccount", {
method: "POST",
token,
body: { did },
});
},
startPasskeyRegistration(
token: AccessToken,
friendlyName?: string,
): Promise<Result<StartPasskeyRegistrationResponse, ApiError>> {
return xrpcResult("com.atproto.server.startPasskeyRegistration", {
method: "POST",
token,
body: { friendlyName },
});
},
finishPasskeyRegistration(
token: AccessToken,
credential: unknown,
friendlyName?: string,
): Promise<Result<FinishPasskeyRegistrationResponse, ApiError>> {
return xrpcResult("com.atproto.server.finishPasskeyRegistration", {
method: "POST",
token,
body: { credential, friendlyName },
});
},
updatePasskey(
token: AccessToken,
id: string,
friendlyName: string,
): Promise<Result<void, ApiError>> {
return xrpcResult<void>("com.atproto.server.updatePasskey", {
method: "POST",
token,
body: { id, friendlyName },
});
},
regenerateBackupCodes(
token: AccessToken,
password: string,
code: string,
): Promise<Result<RegenerateBackupCodesResponse, ApiError>> {
return xrpcResult("com.atproto.server.regenerateBackupCodes", {
method: "POST",
token,
body: { password, code },
});
},
updateLocale(
token: AccessToken,
preferredLocale: string,
): Promise<Result<UpdateLocaleResponse, ApiError>> {
return xrpcResult("_account.updateLocale", {
method: "POST",
token,
body: { preferredLocale },
});
},
confirmChannelVerification(
token: AccessToken,
channel: string,
identifier: string,
code: string,
): Promise<Result<SuccessResponse, ApiError>> {
return xrpcResult("_account.confirmChannelVerification", {
method: "POST",
token,
body: { channel, identifier, code },
});
},
removePassword(
token: AccessToken,
): Promise<Result<SuccessResponse, ApiError>> {
return xrpcResult("_account.removePassword", {
method: "POST",
token,
});
},
};
-112
View File
@@ -1,112 +0,0 @@
<script lang="ts">
import { navigate, routes } from '../lib/router.svelte'
import { _ } from '../lib/i18n'
let code = $state('')
let submitting = $state(false)
let error = $state<string | null>(null)
function getRequestUri(): string | null {
const params = new URLSearchParams(window.location.search)
return params.get('request_uri')
}
function getChannel(): string {
const params = new URLSearchParams(window.location.search)
return params.get('channel') || 'email'
}
async function handleSubmit(e: Event) {
e.preventDefault()
const requestUri = getRequestUri()
if (!requestUri) {
error = $_('oauth.twoFactorCode.errors.missingRequestUri')
return
}
submitting = true
error = null
try {
const response = await fetch('/oauth/authorize/2fa', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
request_uri: requestUri,
code: code.trim()
})
})
const data = await response.json()
if (!response.ok) {
error = data.error_description || data.error || $_('oauth.twoFactorCode.errors.verificationFailed')
submitting = false
return
}
if (data.redirect_uri) {
window.location.href = data.redirect_uri
return
}
error = $_('oauth.twoFactorCode.errors.unexpectedResponse')
submitting = false
} catch {
error = $_('oauth.twoFactorCode.errors.connectionFailed')
submitting = false
}
}
function handleCancel() {
const requestUri = getRequestUri()
if (requestUri) {
navigate(routes.oauthLogin, { params: { request_uri: requestUri } })
} else {
window.history.back()
}
}
let channel = $derived(getChannel())
</script>
<div class="oauth-2fa-container">
<h1>{$_('oauth.twoFactorCode.title')}</h1>
<p class="subtitle">
{$_('oauth.twoFactorCode.subtitle', { values: { channel } })}
</p>
{#if error}
<div class="error">{error}</div>
{/if}
<form onsubmit={handleSubmit}>
<div>
<label for="code">{$_('oauth.twoFactorCode.codeLabel')}</label>
<input
id="code"
type="text"
bind:value={code}
placeholder={$_('oauth.twoFactorCode.codePlaceholder')}
disabled={submitting}
required
maxlength="6"
pattern="[0-9]{6}"
autocomplete="one-time-code"
inputmode="numeric"
/>
</div>
<div class="actions">
<button type="button" class="cancel" onclick={handleCancel} disabled={submitting}>
{$_('common.cancel')}
</button>
<button type="submit" disabled={submitting || code.trim().length !== 6}>
{submitting ? $_('common.verifying') : $_('common.verify')}
</button>
</div>
</form>
</div>
-121
View File
@@ -1,121 +0,0 @@
<script lang="ts">
import { navigate, routes } from '../lib/router.svelte'
import { _ } from '../lib/i18n'
let code = $state('')
let trustDevice = $state(false)
let submitting = $state(false)
let error = $state<string | null>(null)
function getRequestUri(): string | null {
const params = new URLSearchParams(window.location.search)
return params.get('request_uri')
}
async function handleSubmit(e: Event) {
e.preventDefault()
const requestUri = getRequestUri()
if (!requestUri) {
error = $_('common.error')
return
}
submitting = true
error = null
try {
const response = await fetch('/oauth/authorize/2fa', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
request_uri: requestUri,
code: code.trim().toUpperCase(),
trust_device: trustDevice
})
})
const data = await response.json()
if (!response.ok) {
error = data.error_description || data.error || $_('common.error')
submitting = false
return
}
if (data.redirect_uri) {
window.location.href = data.redirect_uri
return
}
error = $_('common.error')
submitting = false
} catch {
error = $_('common.error')
submitting = false
}
}
function handleCancel() {
const requestUri = getRequestUri()
if (requestUri) {
navigate(routes.oauthLogin, { params: { request_uri: requestUri } })
} else {
window.history.back()
}
}
let isBackupCode = $derived(code.trim().length === 8 && /^[A-Z0-9]+$/i.test(code.trim()))
let isTotpCode = $derived(code.trim().length === 6 && /^[0-9]+$/.test(code.trim()))
let canSubmit = $derived(isBackupCode || isTotpCode)
</script>
<div class="oauth-totp-container">
<h1>{$_('oauth.totp.title')}</h1>
{#if error}
<div class="error">{error}</div>
{/if}
<form onsubmit={handleSubmit}>
<div>
<label for="code">{$_('oauth.totp.codePlaceholder')}</label>
<input
id="code"
type="text"
bind:value={code}
placeholder={isBackupCode ? $_('oauth.totp.backupCodePlaceholder') : $_('oauth.totp.codePlaceholder')}
disabled={submitting}
required
maxlength="8"
autocomplete="one-time-code"
autocapitalize="characters"
/>
{#if isBackupCode || isTotpCode}
<p class="hint">
{isBackupCode ? $_('oauth.totp.hintBackupCode') : $_('oauth.totp.hintTotpCode')}
</p>
{/if}
</div>
<label class="trust-device-label">
<input
type="checkbox"
bind:checked={trustDevice}
disabled={submitting}
/>
<span>{$_('oauth.totp.trustDevice')}</span>
</label>
<div class="actions">
<button type="button" class="cancel" onclick={handleCancel} disabled={submitting}>
{$_('common.cancel')}
</button>
<button type="submit" disabled={submitting || !canSubmit}>
{submitting ? $_('common.verifying') : $_('common.verify')}
</button>
</div>
</form>
</div>
@@ -1,27 +0,0 @@
<script lang="ts">
import { startOAuthRegister } from '../lib/oauth'
import { _ } from '../lib/i18n'
let error = $state<string | null>(null)
let initiated = false
$effect(() => {
if (!initiated) {
initiated = true
startOAuthRegister().catch((err) => {
error = err instanceof Error ? err.message : 'Failed to start registration'
})
}
})
</script>
<div class="register-redirect">
{#if error}
<div class="message error">{error}</div>
<a href="/app/login">{$_('register.signIn')}</a>
{:else}
<div class="loading-content">
<p>{$_('common.loading')}</p>
</div>
{/if}
</div>