mirror of
https://github.com/fluxerapp/fluxer.git
synced 2026-09-02 21:04:06 +03:00
fix(auth): require a hashed poll secret for desktop handoff (#2357)
This commit is contained in:
@@ -9,10 +9,12 @@ import {
|
||||
AuthTokenWithUserIdResponse,
|
||||
EmailRevertRequest,
|
||||
ForgotPasswordRequest,
|
||||
HandoffCancelRequest,
|
||||
HandoffCodeParam,
|
||||
HandoffCompleteRequest,
|
||||
HandoffInfoResponse,
|
||||
HandoffInitiateResponse,
|
||||
HandoffStatusRequest,
|
||||
HandoffStatusResponse,
|
||||
IpAuthorizationPollQuery,
|
||||
IpAuthorizationPollResponse,
|
||||
@@ -632,10 +634,39 @@ export function AuthController(app: HonoApp) {
|
||||
return ctx.json(response);
|
||||
},
|
||||
);
|
||||
app.post(
|
||||
'/auth/handoff/:code/status',
|
||||
RateLimitMiddleware(RateLimitConfigs.AUTH_HANDOFF_STATUS),
|
||||
Validator('param', HandoffCodeParam),
|
||||
Validator('json', HandoffStatusRequest),
|
||||
OpenAPI({
|
||||
operationId: 'get_handoff_status_with_secret',
|
||||
summary: 'Get handoff status with secret',
|
||||
responseSchema: HandoffStatusResponse,
|
||||
statusCode: 200,
|
||||
security: [],
|
||||
tags: ['Auth'],
|
||||
description:
|
||||
'Check the status of a handoff session using the poll secret from initiation. Returns the authentication token once the handoff is complete and the presented secret matches.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
const clientIp = requireClientIp(ctx.req.raw, {
|
||||
trustClientIpHeader: Config.proxy.trust_client_ip_header,
|
||||
clientIpHeaderName: Config.proxy.client_ip_header,
|
||||
});
|
||||
const response = await ctx.get('authRequestService').getHandoffStatus({
|
||||
code: ctx.req.valid('param').code,
|
||||
clientIp,
|
||||
pollSecret: ctx.req.valid('json').poll_secret,
|
||||
});
|
||||
return ctx.json(response);
|
||||
},
|
||||
);
|
||||
app.delete(
|
||||
'/auth/handoff/:code',
|
||||
RateLimitMiddleware(RateLimitConfigs.AUTH_HANDOFF_CANCEL),
|
||||
Validator('param', HandoffCodeParam),
|
||||
Validator('json', HandoffCancelRequest),
|
||||
OpenAPI({
|
||||
operationId: 'cancel_handoff',
|
||||
summary: 'Cancel handoff',
|
||||
@@ -646,7 +677,10 @@ export function AuthController(app: HonoApp) {
|
||||
description: 'Cancel an ongoing handoff session. The handoff code will no longer be valid for authentication.',
|
||||
}),
|
||||
async (ctx) => {
|
||||
await ctx.get('authRequestService').cancelHandoff({code: ctx.req.valid('param').code});
|
||||
await ctx.get('authRequestService').cancelHandoff({
|
||||
code: ctx.req.valid('param').code,
|
||||
pollSecret: ctx.req.valid('json').poll_secret,
|
||||
});
|
||||
return ctx.body(null, 204);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -133,6 +133,12 @@ interface AuthHandoffInfoRequest {
|
||||
interface AuthHandoffStatusRequest {
|
||||
code: string;
|
||||
clientIp: string;
|
||||
pollSecret?: string;
|
||||
}
|
||||
|
||||
interface AuthHandoffCancelRequest {
|
||||
code: string;
|
||||
pollSecret: string;
|
||||
}
|
||||
|
||||
export class AuthRequestService {
|
||||
@@ -305,6 +311,7 @@ export class AuthRequestService {
|
||||
return {
|
||||
code: result.code,
|
||||
expires_at: result.expiresAt.toISOString(),
|
||||
poll_secret: result.pollSecret,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -352,8 +359,8 @@ export class AuthRequestService {
|
||||
);
|
||||
}
|
||||
|
||||
async getHandoffStatus({code, clientIp}: AuthHandoffStatusRequest): Promise<HandoffStatusResponse> {
|
||||
const result = await this.desktopHandoffService.getHandoffStatus(code, clientIp);
|
||||
async getHandoffStatus({code, clientIp, pollSecret}: AuthHandoffStatusRequest): Promise<HandoffStatusResponse> {
|
||||
const result = await this.desktopHandoffService.getHandoffStatus(code, clientIp, pollSecret);
|
||||
return {
|
||||
status: result.status,
|
||||
token: result.token,
|
||||
@@ -362,8 +369,8 @@ export class AuthRequestService {
|
||||
};
|
||||
}
|
||||
|
||||
async cancelHandoff({code}: {code: string}): Promise<void> {
|
||||
await this.desktopHandoffService.cancelHandoff(code);
|
||||
async cancelHandoff({code, pollSecret}: AuthHandoffCancelRequest): Promise<void> {
|
||||
await this.desktopHandoffService.cancelHandoff(code, pollSecret);
|
||||
}
|
||||
|
||||
private async getUserPartial(userId: string): Promise<UserPartialResponse> {
|
||||
|
||||
@@ -1,60 +1,84 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {randomBytes} from 'node:crypto';
|
||||
import {createHash, randomBytes, timingSafeEqual} from 'node:crypto';
|
||||
import {HandoffCodeExpiredError} from '@fluxer/errors/src/domains/auth/HandoffCodeExpiredError';
|
||||
import {InvalidHandoffCodeError} from '@fluxer/errors/src/domains/auth/InvalidHandoffCodeError';
|
||||
import {
|
||||
DESKTOP_HANDOFF_CODE_ALPHABET,
|
||||
DESKTOP_HANDOFF_CODE_LENGTH,
|
||||
formatDesktopHandoffCode,
|
||||
parseDesktopHandoffCode,
|
||||
} from '@fluxer/schema/src/domains/auth/DesktopHandoffCode';
|
||||
import {ms, seconds} from 'itty-time';
|
||||
import type {ApiContext} from '../../ApiContext';
|
||||
import type {SessionOrigin} from '../AuthSession';
|
||||
|
||||
const HANDOFF_CODE_PREFIX = 'desktop-handoff-v2:';
|
||||
const HANDOFF_TOKEN_PREFIX = 'desktop-handoff-token:';
|
||||
const CODE_CHARACTERS = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
|
||||
const CODE_LENGTH = 12;
|
||||
const NORMALIZED_CODE_REGEX = /^[ABCDEFGHJKMNPQRSTUVWXYZ23456789]{12}$/;
|
||||
const HANDOFF_ATTEMPT_PREFIX = 'desktop-handoff-attempts:';
|
||||
const HANDOFF_APPROVER_PREFIX = 'desktop-handoff-approver:';
|
||||
const MAX_FAILED_ATTEMPTS = 5;
|
||||
const ATTEMPT_TTL_SECONDS = 900;
|
||||
const MAX_INFO_LOOKUPS = 3;
|
||||
const POLL_SECRET_BYTES = 32;
|
||||
|
||||
interface HandoffData {
|
||||
createdAt: number;
|
||||
origin: SessionOrigin;
|
||||
infoLookupCount: number;
|
||||
pollSecretHash: string;
|
||||
}
|
||||
|
||||
interface HandoffTokenData {
|
||||
token: string;
|
||||
userId: string;
|
||||
pollSecretHash: string;
|
||||
}
|
||||
|
||||
interface HandoffApproverData {
|
||||
approvedAt: number;
|
||||
}
|
||||
|
||||
function generateHandoffCode(): string {
|
||||
const maxUnbiased = 256 - (256 % CODE_CHARACTERS.length);
|
||||
function generateNormalizedHandoffCode(): string {
|
||||
const maxUnbiased = 256 - (256 % DESKTOP_HANDOFF_CODE_ALPHABET.length);
|
||||
let code = '';
|
||||
while (code.length < CODE_LENGTH) {
|
||||
const bytes = randomBytes(CODE_LENGTH - code.length);
|
||||
for (let i = 0; i < bytes.length && code.length < CODE_LENGTH; i++) {
|
||||
while (code.length < DESKTOP_HANDOFF_CODE_LENGTH) {
|
||||
const bytes = randomBytes(DESKTOP_HANDOFF_CODE_LENGTH - code.length);
|
||||
for (let i = 0; i < bytes.length && code.length < DESKTOP_HANDOFF_CODE_LENGTH; i++) {
|
||||
if (bytes[i] < maxUnbiased) {
|
||||
code += CODE_CHARACTERS[bytes[i] % CODE_CHARACTERS.length];
|
||||
code += DESKTOP_HANDOFF_CODE_ALPHABET[bytes[i] % DESKTOP_HANDOFF_CODE_ALPHABET.length];
|
||||
}
|
||||
}
|
||||
}
|
||||
return `${code.slice(0, 6)}-${code.slice(6, 12)}`;
|
||||
return code;
|
||||
}
|
||||
|
||||
function normalizeHandoffCode(code: string): string {
|
||||
return code.replace(/[-\s]/g, '').toUpperCase();
|
||||
}
|
||||
|
||||
function assertValidHandoffCode(code: string): void {
|
||||
if (!NORMALIZED_CODE_REGEX.test(code)) {
|
||||
function requireNormalizedHandoffCode(code: string): string {
|
||||
const normalized = parseDesktopHandoffCode(code);
|
||||
if (normalized == null) {
|
||||
throw new InvalidHandoffCodeError();
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function generatePollSecret(): string {
|
||||
return randomBytes(POLL_SECRET_BYTES).toString('base64url');
|
||||
}
|
||||
|
||||
function hashPollSecret(secret: string): string {
|
||||
return createHash('sha256').update(secret).digest('hex');
|
||||
}
|
||||
|
||||
function pollSecretMatches(presented: string | undefined, storedHash: string | undefined): boolean {
|
||||
if (!presented || !storedHash) {
|
||||
return false;
|
||||
}
|
||||
const presentedHash = Buffer.from(hashPollSecret(presented), 'hex');
|
||||
const stored = Buffer.from(storedHash, 'hex');
|
||||
if (presentedHash.length !== stored.length) {
|
||||
return false;
|
||||
}
|
||||
return timingSafeEqual(presentedHash, stored);
|
||||
}
|
||||
|
||||
export class DesktopHandoffService {
|
||||
@@ -63,19 +87,21 @@ export class DesktopHandoffService {
|
||||
async initiateHandoff(args: {origin: SessionOrigin}): Promise<{
|
||||
code: string;
|
||||
expiresAt: Date;
|
||||
pollSecret: string;
|
||||
}> {
|
||||
const {cache} = this.apiContext.services;
|
||||
const code = generateHandoffCode();
|
||||
const normalizedCode = normalizeHandoffCode(code);
|
||||
const normalizedCode = generateNormalizedHandoffCode();
|
||||
const pollSecret = generatePollSecret();
|
||||
const handoffData: HandoffData = {
|
||||
createdAt: Date.now(),
|
||||
origin: args.origin,
|
||||
infoLookupCount: 0,
|
||||
pollSecretHash: hashPollSecret(pollSecret),
|
||||
};
|
||||
const expirySeconds = seconds('5 minutes');
|
||||
await cache.set(`${HANDOFF_CODE_PREFIX}${normalizedCode}`, handoffData, expirySeconds);
|
||||
const expiresAt = new Date(Date.now() + ms('5 minutes'));
|
||||
return {code, expiresAt};
|
||||
return {code: formatDesktopHandoffCode(normalizedCode), expiresAt, pollSecret};
|
||||
}
|
||||
|
||||
async completeHandoff(
|
||||
@@ -84,8 +110,7 @@ export class DesktopHandoffService {
|
||||
approverIp: string,
|
||||
): Promise<void> {
|
||||
const {cache} = this.apiContext.services;
|
||||
const normalizedCode = normalizeHandoffCode(code);
|
||||
assertValidHandoffCode(normalizedCode);
|
||||
const normalizedCode = requireNormalizedHandoffCode(code);
|
||||
await this.checkAttemptLimit(approverIp);
|
||||
const storedApprover = await cache.get<HandoffApproverData>(`${HANDOFF_APPROVER_PREFIX}${normalizedCode}`);
|
||||
if (!storedApprover) {
|
||||
@@ -108,6 +133,7 @@ export class DesktopHandoffService {
|
||||
const tokenData: HandoffTokenData = {
|
||||
token,
|
||||
userId,
|
||||
pollSecretHash: handoffData.pollSecretHash,
|
||||
};
|
||||
await cache.set(`${HANDOFF_TOKEN_PREFIX}${normalizedCode}`, tokenData, remainingSeconds);
|
||||
await cache.delete(`${HANDOFF_CODE_PREFIX}${normalizedCode}`);
|
||||
@@ -122,8 +148,7 @@ export class DesktopHandoffService {
|
||||
origin?: SessionOrigin;
|
||||
}> {
|
||||
const {cache} = this.apiContext.services;
|
||||
const normalizedCode = normalizeHandoffCode(code);
|
||||
assertValidHandoffCode(normalizedCode);
|
||||
const normalizedCode = requireNormalizedHandoffCode(code);
|
||||
await this.checkAttemptLimit(approverIp);
|
||||
const codeKey = `${HANDOFF_CODE_PREFIX}${normalizedCode}`;
|
||||
const handoffData = await cache.get<HandoffData>(codeKey);
|
||||
@@ -149,18 +174,23 @@ export class DesktopHandoffService {
|
||||
|
||||
async getHandoffStatus(
|
||||
code: string,
|
||||
_pollerIp: string,
|
||||
pollerIp: string,
|
||||
pollSecret: string | undefined,
|
||||
): Promise<{
|
||||
status: 'pending' | 'completed' | 'expired';
|
||||
token?: string;
|
||||
userId?: string;
|
||||
}> {
|
||||
const {cache} = this.apiContext.services;
|
||||
const normalizedCode = normalizeHandoffCode(code);
|
||||
assertValidHandoffCode(normalizedCode);
|
||||
const normalizedCode = requireNormalizedHandoffCode(code);
|
||||
await this.checkAttemptLimit(pollerIp);
|
||||
const tokenKey = `${HANDOFF_TOKEN_PREFIX}${normalizedCode}`;
|
||||
const tokenData = await cache.get<HandoffTokenData>(tokenKey);
|
||||
if (tokenData) {
|
||||
if (!pollSecretMatches(pollSecret, tokenData.pollSecretHash)) {
|
||||
await this.recordFailedAttempt(pollerIp);
|
||||
return {status: 'pending'};
|
||||
}
|
||||
await cache.delete(tokenKey);
|
||||
return {
|
||||
status: 'completed',
|
||||
@@ -175,12 +205,19 @@ export class DesktopHandoffService {
|
||||
return {status: 'expired'};
|
||||
}
|
||||
|
||||
async cancelHandoff(code: string): Promise<void> {
|
||||
async cancelHandoff(code: string, pollSecret: string): Promise<void> {
|
||||
const {cache} = this.apiContext.services;
|
||||
const normalizedCode = normalizeHandoffCode(code);
|
||||
assertValidHandoffCode(normalizedCode);
|
||||
await cache.delete(`${HANDOFF_CODE_PREFIX}${normalizedCode}`);
|
||||
await cache.delete(`${HANDOFF_TOKEN_PREFIX}${normalizedCode}`);
|
||||
const normalizedCode = requireNormalizedHandoffCode(code);
|
||||
const codeKey = `${HANDOFF_CODE_PREFIX}${normalizedCode}`;
|
||||
const tokenKey = `${HANDOFF_TOKEN_PREFIX}${normalizedCode}`;
|
||||
const handoffData = await cache.get<HandoffData>(codeKey);
|
||||
const tokenData = await cache.get<HandoffTokenData>(tokenKey);
|
||||
const storedHash = handoffData?.pollSecretHash ?? tokenData?.pollSecretHash;
|
||||
if (!pollSecretMatches(pollSecret, storedHash)) {
|
||||
throw new InvalidHandoffCodeError();
|
||||
}
|
||||
await cache.delete(codeKey);
|
||||
await cache.delete(tokenKey);
|
||||
await cache.delete(`${HANDOFF_APPROVER_PREFIX}${normalizedCode}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import {createAuthHarness, createTestAccount, loginAccount} from './AuthTestUtil
|
||||
|
||||
interface HandoffInitiateResponse {
|
||||
code: string;
|
||||
poll_secret: string;
|
||||
}
|
||||
|
||||
interface HandoffInfoResponse {
|
||||
@@ -59,7 +60,8 @@ describe('Auth desktop handoff code normalization', () => {
|
||||
.expect(204)
|
||||
.execute();
|
||||
const status2 = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
expect(status2.status).toBe('completed');
|
||||
expect(status2.token).toBeTruthy();
|
||||
|
||||
@@ -12,6 +12,7 @@ import {createAuthHarness, createTestAccount, fetchMe, loginAccount} from './Aut
|
||||
interface HandoffInitiateResponse {
|
||||
code: string;
|
||||
expires_at: string;
|
||||
poll_secret: string;
|
||||
}
|
||||
|
||||
interface HandoffInfoResponse {
|
||||
@@ -93,7 +94,8 @@ describe('Auth desktop handoff flow', () => {
|
||||
.expect(204)
|
||||
.execute();
|
||||
const completed = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
const sessions = await createBuilder<Array<AuthSessionsResponseItem>>(harness, completed.token!)
|
||||
.get('/auth/sessions')
|
||||
@@ -116,13 +118,15 @@ describe('Auth desktop handoff flow', () => {
|
||||
expect(initResp.code).toBeTruthy();
|
||||
expect(validateHandoffCodeFormat(initResp.code)).toBe(true);
|
||||
expect(initResp.expires_at).toBeTruthy();
|
||||
expect(initResp.poll_secret).toBeTruthy();
|
||||
const info = await createBuilderWithoutAuth<HandoffInfoResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/info`)
|
||||
.execute();
|
||||
expect(info.status).toBe('pending');
|
||||
expect(info.client_info).toBeTruthy();
|
||||
const pending = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
expect(pending.status).toBe('pending');
|
||||
await createBuilderWithoutAuth(harness)
|
||||
@@ -135,7 +139,8 @@ describe('Auth desktop handoff flow', () => {
|
||||
.expect(204)
|
||||
.execute();
|
||||
const completed = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
expect(completed.status).toBe('completed');
|
||||
expect(completed.token).toBeTruthy();
|
||||
@@ -150,7 +155,8 @@ describe('Auth desktop handoff flow', () => {
|
||||
const handoffUser = handoffSession.json as UserMeResponse;
|
||||
expect(handoffUser.id).toBe(login.userId);
|
||||
const retrieved = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
expect(retrieved.status).toBe('expired');
|
||||
});
|
||||
@@ -159,7 +165,11 @@ describe('Auth desktop handoff flow', () => {
|
||||
.post('/auth/handoff/initiate')
|
||||
.body(null)
|
||||
.execute();
|
||||
await createBuilderWithoutAuth(harness).delete(`/auth/handoff/${initResp.code}`).expect(204).execute();
|
||||
await createBuilderWithoutAuth(harness)
|
||||
.delete(`/auth/handoff/${initResp.code}`)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.expect(204)
|
||||
.execute();
|
||||
const cancelled = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.execute();
|
||||
@@ -184,7 +194,8 @@ describe('Auth desktop handoff flow', () => {
|
||||
.expect(204)
|
||||
.execute();
|
||||
const completed = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
expect(completed.status).toBe('completed');
|
||||
expect(completed.token).toBeTruthy();
|
||||
@@ -233,8 +244,9 @@ describe('Auth desktop handoff flow', () => {
|
||||
.expect(204)
|
||||
.execute();
|
||||
const completed = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.header('x-forwarded-for', pollerIp)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
expect(completed.status).toBe('completed');
|
||||
expect(completed.user_id).toBe(login.userId);
|
||||
@@ -267,8 +279,9 @@ describe('Auth desktop handoff flow', () => {
|
||||
.expect(204)
|
||||
.execute();
|
||||
const completed = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.header('x-forwarded-for', pollerIp)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
expect(completed.status).toBe('completed');
|
||||
expect(completed.user_id).toBe(login.userId);
|
||||
|
||||
@@ -72,6 +72,7 @@ describe('Auth desktop handoff negative paths', () => {
|
||||
it('handles cancel for unknown handoff code gracefully', async () => {
|
||||
await createBuilderWithoutAuth(harness)
|
||||
.delete('/auth/handoff/unknown-code')
|
||||
.body({poll_secret: 'not-the-secret'})
|
||||
.expect(HTTP_STATUS.BAD_REQUEST, APIErrorCodes.INVALID_HANDOFF_CODE)
|
||||
.execute();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {createAuthHarness, createTestAccount, loginAccount} from './AuthTestUtil
|
||||
|
||||
interface HandoffInitiateResponse {
|
||||
code: string;
|
||||
poll_secret?: string;
|
||||
}
|
||||
|
||||
interface HandoffStatusResponse {
|
||||
@@ -56,8 +57,38 @@ describe('Auth desktop handoff complete single use', () => {
|
||||
const status = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.execute();
|
||||
expect(status.status).toBe('completed');
|
||||
expect(status.token).toBeTruthy();
|
||||
expect(status.token).not.toBe(login.token);
|
||||
expect(status.status).toBe('pending');
|
||||
expect(status.token).toBeUndefined();
|
||||
});
|
||||
it('releases the token only to a poller presenting the correct secret', async () => {
|
||||
const account = await createTestAccount(harness);
|
||||
const login = await loginAccount(harness, account);
|
||||
const initResp = await createBuilderWithoutAuth<HandoffInitiateResponse>(harness)
|
||||
.post('/auth/handoff/initiate')
|
||||
.body(null)
|
||||
.execute();
|
||||
expect(initResp.poll_secret).toBeTruthy();
|
||||
await createBuilderWithoutAuth(harness).get(`/auth/handoff/${initResp.code}/info`).execute();
|
||||
await createBuilderWithoutAuth(harness)
|
||||
.post('/auth/handoff/complete')
|
||||
.header('Authorization', login.token)
|
||||
.body({
|
||||
code: initResp.code,
|
||||
user_id: login.userId,
|
||||
})
|
||||
.expect(204)
|
||||
.execute();
|
||||
const withoutSecret = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.get(`/auth/handoff/${initResp.code}/status`)
|
||||
.execute();
|
||||
expect(withoutSecret.status).toBe('pending');
|
||||
expect(withoutSecret.token).toBeUndefined();
|
||||
const withSecret = await createBuilderWithoutAuth<HandoffStatusResponse>(harness)
|
||||
.post(`/auth/handoff/${initResp.code}/status`)
|
||||
.body({poll_secret: initResp.poll_secret})
|
||||
.execute();
|
||||
expect(withSecret.status).toBe('completed');
|
||||
expect(withSecret.token).toBeTruthy();
|
||||
expect(withSecret.token).not.toBe(login.token);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -113,6 +113,7 @@ export type ResetPasswordResponse = AuthTokenResponse | MfaLoginResponse;
|
||||
interface DesktopHandoffInitiateResponse {
|
||||
code: string;
|
||||
expires_at: string;
|
||||
poll_secret?: string;
|
||||
}
|
||||
|
||||
interface DesktopHandoffStatusResponse {
|
||||
@@ -537,9 +538,19 @@ export async function initiateDesktopHandoff(): Promise<DesktopHandoffInitiateRe
|
||||
return response.body;
|
||||
}
|
||||
|
||||
export async function pollDesktopHandoffStatus(code: string): Promise<DesktopHandoffStatusResponse> {
|
||||
const response = await http.get<DesktopHandoffStatusResponse>(Endpoints.AUTH_HANDOFF_STATUS(code), {
|
||||
export async function pollDesktopHandoffStatus(
|
||||
code: string,
|
||||
pollSecret?: string | null,
|
||||
): Promise<DesktopHandoffStatusResponse> {
|
||||
if (pollSecret == null || pollSecret.length === 0) {
|
||||
const response = await http.get<DesktopHandoffStatusResponse>(Endpoints.AUTH_HANDOFF_STATUS(code), {
|
||||
auth: 'none',
|
||||
});
|
||||
return response.body;
|
||||
}
|
||||
const response = await http.post<DesktopHandoffStatusResponse>(Endpoints.AUTH_HANDOFF_STATUS(code), {
|
||||
auth: 'none',
|
||||
body: {poll_secret: pollSecret},
|
||||
});
|
||||
return response.body;
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillEmail}: BrowserLog
|
||||
const [isGenerating, setIsGenerating] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const pollingRef = useRef(false);
|
||||
const handoffPollSecretRef = useRef<string | null>(null);
|
||||
const completedRef = useRef(false);
|
||||
const generateCode = useCallback(async () => {
|
||||
setIsGenerating(true);
|
||||
@@ -46,6 +47,7 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillEmail}: BrowserLog
|
||||
setHandoffExpiresAt(null);
|
||||
try {
|
||||
const result = await AuthenticationCommands.initiateDesktopHandoff();
|
||||
handoffPollSecretRef.current = result.poll_secret ?? null;
|
||||
setHandoffCode(result.code);
|
||||
setHandoffExpiresAt(result.expires_at);
|
||||
} catch (e) {
|
||||
@@ -63,7 +65,7 @@ const BrowserLoginHandoffModal = observer(({onSuccess, prefillEmail}: BrowserLog
|
||||
const timer = setInterval(async () => {
|
||||
if (!pollingRef.current) return;
|
||||
try {
|
||||
const result = await AuthenticationCommands.pollDesktopHandoffStatus(handoffCode);
|
||||
const result = await AuthenticationCommands.pollDesktopHandoffStatus(handoffCode, handoffPollSecretRef.current);
|
||||
if (result.status === 'completed' && result.token && result.user_id) {
|
||||
pollingRef.current = false;
|
||||
completedRef.current = true;
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "لا يمكن تحميل الملفات هنا"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "فتح خيارات الشعار"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "فتح الإشارات المرجعية"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "فتح المتصفح"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "فتح رابط الفيديو"
|
||||
msgid "Open voice"
|
||||
msgstr "فتح المحادثة الصوتية"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "افتح متصفحك، سجّل الدخول، ثم أدخل الرمز أدناه لربط حسابك."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "لقد قمنا بتغيير حجم هذه الملصق وضغطه إل
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "أرسلنا رمزًا إلى {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "سنقوم بملء {prefillEmail} مسبقًا بمجرد فتح تسجيل الدخول في المتصفح."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Не може да се качват файлове тук"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Отвори опциите за банера"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Отваряне на отметки"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Отваряне на браузъра"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Отваряне на видео връзка"
|
||||
msgid "Open voice"
|
||||
msgstr "Отваряне на гласов чат"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Отворете браузъра си, влезте в профила си, след което въведете кода по-долу, за да свържете акаунта си."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Преоразмерихме и компресирахме този ст
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Изпратихме код на {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Ще попълним предварително {prefillEmail}, след като се отвори влизането в браузъра."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Sem nelze nahrávat soubory"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Otevřít možnosti banneru"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Otevřít záložky"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Otevřít prohlížeč"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Otevřít odkaz na video"
|
||||
msgid "Open voice"
|
||||
msgstr "Otevřít hlasový chat"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Otevřete prohlížeč, přihlaste se a poté zadejte níže uvedený kód pro propojení účtu."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Tento sticker jsme zmenšili a zkomprimovali na 320x320 pixelů, ale st
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Kód jsme odeslali na {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Předvyplníme {prefillEmail}, jakmile se otevře přihlášení v prohlížeči."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Kan ikke uploade filer her"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Åbn bannerindstillinger"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Åbn bogmærker"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Åbn browser"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Åbn videolink"
|
||||
msgid "Open voice"
|
||||
msgstr "Åbn talechat"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Åbn din browser, log ind, og indtast derefter koden herunder for at forbinde din konto."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Vi har ændret størrelsen på og komprimeret dette sticker til 320x320
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Vi har sendt en kode til {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Vi udfylder automatisk {prefillEmail}, når browser-login åbnes."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Dateien können hier nicht hochgeladen werden"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Banner-Optionen öffnen"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Lesezeichen öffnen"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Browser öffnen"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Videolink öffnen"
|
||||
msgid "Open voice"
|
||||
msgstr "Sprachchat öffnen"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Öffne deinen Browser, melde dich an und gib dann den folgenden Code ein, um deinen Account zu verknüpfen."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Wir haben diesen Sticker auf 320x320 Pixel verkleinert und komprimiert,
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Wir haben einen Code an {email} gesendet."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Wir tragen \"{prefillEmail}\" automatisch ein, sobald sich die Browser-Anmeldung öffnet."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Δεν μπορείς να ανεβάσεις αρχεία εδώ"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Άνοιγμα επιλογών banner"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Άνοιγμα σελιδοδεικτών"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Άνοιγμα προγράμματος περιήγησης"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Άνοιγμα συνδέσμου βίντεο"
|
||||
msgid "Open voice"
|
||||
msgstr "Άνοιγμα φωνητικής συνομιλίας"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Ανοίξτε το πρόγραμμα περιήγησής σας, συνδεθείτε και, στη συνέχεια, εισαγάγετε τον παρακάτω κωδικό για να συνδέσετε τον λογαριασμό σας."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Αλλάξαμε το μέγεθος και συμπιέσαμε αυτ
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Στείλαμε έναν κωδικό στο {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Θα συμπληρώσουμε αυτόματα το {prefillEmail} μόλις ανοίξει η σύνδεση στο πρόγραμμα περιήγησης."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Can't upload files here"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Open banner options"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Open bookmarks"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Open browser"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Open video link"
|
||||
msgid "Open voice"
|
||||
msgstr "Open voice chat"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Open your browser, sign in, then enter the code below to link your account."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "We resized and compressed this sticker to 320x320 pixels, but it is stil
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "We sent a code to {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "We'll pre-fill {prefillEmail} once browser sign-in opens."
|
||||
|
||||
|
||||
@@ -5684,7 +5684,7 @@ msgstr "Can't upload files here"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22337,7 +22337,7 @@ msgstr "Open banner options"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Open bookmarks"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Open browser"
|
||||
|
||||
@@ -22681,7 +22681,7 @@ msgstr "Open video link"
|
||||
msgid "Open voice"
|
||||
msgstr "Open voice"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Open your browser, sign in, then enter the code below to link your account."
|
||||
|
||||
@@ -36515,7 +36515,7 @@ msgstr "We resized and compressed this sticker to 320x320 pixels, but it is stil
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "We sent a code to {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "No se pueden subir archivos aquí"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Abrir opciones del banner"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Abrir marcadores"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Abrir navegador"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Abrir enlace de video"
|
||||
msgid "Open voice"
|
||||
msgstr "Abrir chat de voz"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Abre tu navegador, inicia sesión y luego ingresa el código a continuación para vincular tu cuenta."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Redimensionamos y comprimimos este sticker a 320x320 píxeles, pero sigu
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Enviamos un código a {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Vamos a rellenar automáticamente {prefillEmail} cuando se abra el inicio de sesión en el navegador."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "No se pueden subir archivos aquí"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Abrir opciones del banner"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Abrir marcadores"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Abrir navegador"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Abrir enlace de vídeo"
|
||||
msgid "Open voice"
|
||||
msgstr "Abrir chat de voz"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Abre tu navegador, inicia sesión y, a continuación, introduce el código de abajo para vincular tu cuenta."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Hemos redimensionado y comprimido este sticker a 320x320 píxeles, pero
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Hemos enviado un código a {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Rellenaremos automáticamente {prefillEmail} cuando se abra el inicio de sesión en el navegador."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Tiedostojen lähetys ei onnistu tähän"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Avaa banneriasetukset"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Avaa kirjanmerkit"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Avaa selain"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Avaa videolinkki"
|
||||
msgid "Open voice"
|
||||
msgstr "Avaa puhekanava"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Avaa selaimesi, kirjaudu sisään ja syötä sitten alla oleva koodi tilisi yhdistämiseksi."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Pienensimme ja pakkasimme tämän tarran 320x320 pikseliin, mutta sen ko
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Lähetimme koodin osoitteeseen {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Esitäytämme sähköpostiosoitteen {prefillEmail}, kun kirjautuminen avautuu selaimessa."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Impossible de charger des fichiers ici"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Ouvrir les options de la bannière"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Ouvrir les signets"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Ouvrir le navigateur"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Ouvrir le lien vidéo"
|
||||
msgid "Open voice"
|
||||
msgstr "Ouvrir la discussion vocale"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Ouvrez votre navigateur, connectez-vous, puis saisissez le code ci-dessous pour associer votre compte."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Nous avons redimensionné et compressé cet autocollant à 320x320 pixel
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Nous avons envoyé un code à {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Nous allons préremplir {prefillEmail} une fois que la connexion par navigateur s'ouvrira."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "לא ניתן להעלות קבצים לכאן"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "אפשרויות באנר"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "פתיחת סימניות"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "פתח דפדפן"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "פתח קישור וידאו"
|
||||
msgid "Open voice"
|
||||
msgstr "פתח שיחה קולית"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "פתח את הדפדפן שלך, היכנס, ואז הזן את הקוד למטה כדי לקשר את חשבונך."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "שינינו את גודל המדבקה ודחסנו אותה ל-320x320
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "שלחנו קוד לכתובת {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "נמלא מראש את {prefillEmail} לאחר שההתחברות בדפדפן תיפתח."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "यहां फ़ाइलें अपलोड नहीं की
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "बैनर विकल्प खोलें"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "बुकमार्क खोलें"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "ब्राउज़र खोलें"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "वीडियो लिंक खोलें"
|
||||
msgid "Open voice"
|
||||
msgstr "वॉइस खोलें"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "अपना ब्राउज़र खोलें, साइन इन करें, फिर अपना अकाउंट लिंक करने के लिए नीचे दिया गया कोड डालें।"
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "हमने इस स्टिकर का आकार बदलक
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "हमने {email} पर एक कोड भेजा है।"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "ब्राउज़र साइन-इन खुलने पर हम {prefillEmail} पहले से भर देंगे।"
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Ovdje nije moguće učitati datoteke"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Otvori opcije natpisa"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Otvori oznake"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Otvori preglednik"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Otvori videopoveznicu"
|
||||
msgid "Open voice"
|
||||
msgstr "Otvori glasovni chat"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Otvorite preglednik, prijavite se, a zatim unesite kôd u nastavku za povezivanje računa."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Promijenili smo veličinu i komprimirali ovu naljepnicu na 320x320 pikse
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Poslali smo kôd na adresu {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Automatski ćemo popuniti {prefillEmail} kada se otvori prijava u pregledniku."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Ide nem tölthetsz fel fájlokat"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Bannerbeállítások megnyitása"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Könyvjelzők megnyitása"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Böngésző megnyitása"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Videólink megnyitása"
|
||||
msgid "Open voice"
|
||||
msgstr "Hangüzenet megnyitása"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Nyissa meg a böngészőjét, jelentkezzen be, majd adja meg az alábbi kódot a fiókja összekapcsolásához."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Átméreteztük és tömörítettük ezt a matricát 320x320 képpontra,
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Kódot küldtünk a következő címre: {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Előre kitöltjük a(z) {prefillEmail} címet, amint megnyílik a böngészős bejelentkezés."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Tidak bisa mengunggah file di sini"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Buka opsi banner"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Buka markah"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Buka browser"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Buka tautan video"
|
||||
msgid "Open voice"
|
||||
msgstr "Buka obrolan suara"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Buka browser Anda, masuk, lalu masukkan kode di bawah untuk menautkan akun Anda."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Kami mengubah ukuran dan mengompres stiker ini menjadi 320x320 piksel, t
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Kami telah mengirim kode ke {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Kami akan mengisi otomatis {prefillEmail} setelah login browser terbuka."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Impossibile caricare file qui"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Apri opzioni banner"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Apri i preferiti"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Apri il browser"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Apri link video"
|
||||
msgid "Open voice"
|
||||
msgstr "Apri la chat vocale"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Apri il browser, accedi, quindi inserisci il codice qui sotto per collegare il tuo account."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Abbiamo ridimensionato e compresso questo sticker a 320x320 pixel, ma è
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Abbiamo inviato un codice a {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Precompileremo {prefillEmail} quando si aprirà l'accesso tramite browser."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "ここにファイルをアップロードできません"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "バナーオプションを開く"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "ブックマークを開く"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "ブラウザを開く"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "動画リンクを開く"
|
||||
msgid "Open voice"
|
||||
msgstr "音声チャットを開始"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "ブラウザを開いてサインインし、以下のコードを入力してアカウントをリンクしてください。"
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "このスタンプは320x320ピクセルにリサイズおよび圧縮
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "{email} にコードを送信しました。"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "ブラウザでサインイン画面が開いたら、{prefillEmail}を自動入力します。"
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "여기에 파일을 올릴 수 없어요"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "배너 옵션 열기"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "북마크 열기"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "브라우저 열기"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "동영상 링크 열기"
|
||||
msgid "Open voice"
|
||||
msgstr "음성 채팅 시작"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "브라우저를 열고 로그인한 다음 아래 코드를 입력하여 계정을 연결하세요."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "스티커 크기를 320x320픽셀로 조정하고 압축했지만, 여
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "{email}으로 코드를 전송했습니다."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "브라우저 로그인 창이 열리면 {prefillEmail}이(가) 자동으로 입력됩니다."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Čia negalima įkelti failų"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Atidaryti reklamjuostės parinktis"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Atidaryti žymes"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Atidaryti naršyklę"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Atidaryti vaizdo įrašo nuorodą"
|
||||
msgid "Open voice"
|
||||
msgstr "Atidaryti balso pokalbį"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Atidarykite naršyklę, prisijunkite, tada įveskite toliau pateiktą kodą, kad susietumėte paskyrą."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Šį lipduką pakeitėme ir suglaudinome iki 320x320 pikselių, bet jo d
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Išsiuntėme kodą į {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Atidarius prisijungimo naršyklėje langą, iš anksto užpildysime {prefillEmail}."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Kan hier geen bestanden uploaden"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Banneropties openen"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Bladwijzers openen"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Browser openen"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Videolink openen"
|
||||
msgid "Open voice"
|
||||
msgstr "Spraak openen"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Open je browser, meld je aan en voer de onderstaande code in om je account te koppelen."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "We hebben deze sticker verkleind en gecomprimeerd naar 320x320 pixels, m
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "We hebben een code gestuurd naar {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "We vullen {prefillEmail} alvast in zodra het inlogscherm in je browser opent."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Kan ikke laste opp filer her"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Åpne bannerinnstillinger"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Åpne bokmerker"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Åpne nettleser"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Åpne videolenke"
|
||||
msgid "Open voice"
|
||||
msgstr "Åpne talechat"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Åpne nettleseren din, logg på, og skriv inn koden nedenfor for å koble til kontoen din."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Vi endret størrelsen og komprimerte dette klistremerket til 320x320 pik
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Vi sendte en kode til {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Vi fyller ut {prefillEmail} automatisk når nettleserpåloggingen åpnes."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Nie można tu przesyłać plików"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Otwórz opcje banera"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Otwórz zakładki"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Otwórz przeglądarkę"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Otwórz link do wideo"
|
||||
msgid "Open voice"
|
||||
msgstr "Otwórz czat głosowy"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Otwórz przeglądarkę, zaloguj się, a następnie wprowadź poniższy kod, aby połączyć swoje konto."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Zmieniliśmy rozmiar i skompresowaliśmy tę naklejkę do 320x320 piksel
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Wysłaliśmy kod na adres {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Wypełnimy wstępnie pole adresem {prefillEmail}, gdy otworzy się logowanie w przeglądarce."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Não é possível enviar arquivos aqui"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Abrir opções do banner"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Abrir favoritos"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Abrir navegador"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Abrir link do vídeo"
|
||||
msgid "Open voice"
|
||||
msgstr "Abrir áudio"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Abra seu navegador, faça login e insira o código abaixo para vincular sua conta."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Redimensionamos e compactamos este sticker para 320x320 pixels, mas ele
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Enviamos um código para {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Vamos preencher automaticamente {prefillEmail} assim que o login no navegador abrir."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Nu se pot încărca fișiere aici"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Deschide opțiunile bannerului"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Deschide marcajele"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Deschide browserul"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Deschide linkul video"
|
||||
msgid "Open voice"
|
||||
msgstr "Deschide mesajul vocal"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Deschide browserul, conectează-te, apoi introdu codul de mai jos pentru a-ți asocia contul."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Am redimensionat și am comprimat acest sticker la 320x320 pixeli, dar a
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Am trimis un cod la {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Vom precompleta {prefillEmail} odată ce se deschide autentificarea în browser."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Не удается загрузить файлы сюда"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Открыть настройки баннера"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Открыть закладки"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Открыть браузер"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Открыть ссылку на видео"
|
||||
msgid "Open voice"
|
||||
msgstr "Открыть голосовой чат"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Откройте браузер, войдите в аккаунт, затем введите код ниже, чтобы привязать свою учётную запись."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Мы изменили размер и сжали этот стикер
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Мы отправили код на {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Мы автоматически заполним поле с адресом {prefillEmail}, как только откроется окно входа в браузере."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Kan inte ladda upp filer här"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Öppna banneralternativ"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Öppna bokmärken"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Öppna webbläsaren"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Öppna videolänk"
|
||||
msgid "Open voice"
|
||||
msgstr "Öppna röstchatt"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Öppna din webbläsare, logga in och ange sedan koden nedan för att länka ditt konto."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Vi ändrade storlek och komprimerade den här stickern till 320x320 pixl
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Vi skickade en kod till {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Vi fyller i {prefillEmail} automatiskt när inloggningen öppnas i webbläsaren."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "อัปโหลดไฟล์ที่นี่ไม่ได้"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "เปิดตัวเลือกแบนเนอร์"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "เปิดบุ๊กมาร์ก"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "เปิดเบราว์เซอร์"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "เปิดลิงก์วิดีโอ"
|
||||
msgid "Open voice"
|
||||
msgstr "เปิดเสียง"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "เปิดเบราว์เซอร์ของคุณ ลงชื่อเข้าใช้ แล้วป้อนรหัสข้างล่างเพื่อเชื่อมโยงบัญชีของคุณ"
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "เราได้ปรับขนาดและบีบอัดส
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "เราได้ส่งรหัสไปยัง {email} แล้ว"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "เราจะกรอก {prefillEmail} ให้โดยอัตโนมัติเมื่อเปิดหน้าลงชื่อเข้าใช้ในเบราว์เซอร์"
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Buraya dosya yüklenemiyor"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Banner seçeneklerini aç"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Yer işaretlerini aç"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Tarayıcıyı aç"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Video bağlantısını aç"
|
||||
msgid "Open voice"
|
||||
msgstr "Sesi aç"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Tarayıcınızı açın, oturum açın ve hesabınızı bağlamak için aşağıdaki kodu girin."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Bu çıkartmayı 320x320 piksele yeniden boyutlandırıp sıkıştırdı
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "{email} adresine bir kod gönderdik."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Tarayıcıda oturum açma ekranı açıldığında {prefillEmail} adresini önceden dolduracağız."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Не вдається завантажити файли сюди"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Відкрити параметри банера"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Відкрити закладки"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Відкрити браузер"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Відкрити посилання на відео"
|
||||
msgid "Open voice"
|
||||
msgstr "Відкрити голосовий чат"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Відкрийте браузер, увійдіть, а потім введіть код нижче, щоб зв'язати свій обліковий запис."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Ми змінили розмір і стиснули цей стікер
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Ми надіслали код на {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Ми попередньо заповнимо {prefillEmail}, щойно відкриється вхід у браузері."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "Không thể tải tệp lên đây"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "Mở tùy chọn biểu ngữ"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "Mở dấu trang"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "Mở trình duyệt"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "Mở liên kết video"
|
||||
msgid "Open voice"
|
||||
msgstr "Mở trò chuyện thoại"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "Mở trình duyệt, đăng nhập, sau đó nhập mã bên dưới để liên kết tài khoản của bạn."
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "Chúng tôi đã đổi kích thước và nén nhãn dán này thành 3
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "Chúng tôi đã gửi mã đến {email}."
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "Chúng tôi sẽ điền trước {prefillEmail} sau khi trình duyệt đăng nhập mở."
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "无法在此上传文件"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "打开横幅选项"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "打开书签"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "打开浏览器"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "打开视频链接"
|
||||
msgid "Open voice"
|
||||
msgstr "开启语音"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "打开浏览器,登录后输入以下代码以关联你的帐户。"
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "我们已将此贴纸调整并压缩到 320x320 像素,但它仍然是
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "验证码已发送至 {email}。"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "浏览器登录页面打开后,我们将预填 {prefillEmail}。"
|
||||
|
||||
|
||||
@@ -5683,7 +5683,7 @@ msgstr "無法在這裡上傳檔案"
|
||||
#: src/features/auth/components/modals/PasskeyNameModal.tsx:80
|
||||
#: src/features/auth/components/modals/PasskeyPinModal.tsx:104
|
||||
#: src/features/auth/components/modals/SudoVerificationModal.tsx:263
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:138
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:140
|
||||
#: src/features/channel/components/bottomsheets/CreateDMBottomSheet.tsx:137
|
||||
#: src/features/channel/components/MentionEveryonePopout.tsx:173
|
||||
#: src/features/channel/components/modals/ChannelDeleteModal.tsx:53
|
||||
@@ -22336,7 +22336,7 @@ msgstr "開啟橫幅選項"
|
||||
msgid "Open bookmarks"
|
||||
msgstr "開啟書籤"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:151
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:153
|
||||
msgid "Open browser"
|
||||
msgstr "開啟瀏覽器"
|
||||
|
||||
@@ -22680,7 +22680,7 @@ msgstr "開啟影片連結"
|
||||
msgid "Open voice"
|
||||
msgstr "開啟語音"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:111
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:113
|
||||
msgid "Open your browser, sign in, then enter the code below to link your account."
|
||||
msgstr "開啟您的瀏覽器,登入後輸入以下代碼,即可連結您的帳號。"
|
||||
|
||||
@@ -36514,7 +36514,7 @@ msgstr "我們已將此貼圖調整大小並壓縮至 320x320 像素,但它仍
|
||||
msgid "We sent a code to {email}."
|
||||
msgstr "我們已將驗證碼傳送至 {email}。"
|
||||
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:126
|
||||
#: src/features/auth/flow/BrowserLoginHandoffModal.tsx:128
|
||||
msgid "We will prefill {prefillEmail} once browser sign-in opens."
|
||||
msgstr "瀏覽器登入開啟後,我們會預填 {prefillEmail}。"
|
||||
|
||||
|
||||
@@ -227,6 +227,7 @@ export type UsernameSuggestionsResponse = z.infer<typeof UsernameSuggestionsResp
|
||||
export const HandoffInitiateResponse = z.object({
|
||||
code: z.string().describe('Handoff code to share with the receiving device'),
|
||||
expires_at: z.iso.datetime().describe('ISO 8601 timestamp when the handoff code expires'),
|
||||
poll_secret: z.string().optional().describe('Secret the initiating device must present to retrieve the token'),
|
||||
});
|
||||
|
||||
export type HandoffInitiateResponse = z.infer<typeof HandoffInitiateResponse>;
|
||||
@@ -331,6 +332,18 @@ export const HandoffCodeParam = z.object({
|
||||
|
||||
export type HandoffCodeParam = z.infer<typeof HandoffCodeParam>;
|
||||
|
||||
export const HandoffStatusRequest = z.object({
|
||||
poll_secret: createStringType().describe('The poll secret issued when the handoff was initiated'),
|
||||
});
|
||||
|
||||
export type HandoffStatusRequest = z.infer<typeof HandoffStatusRequest>;
|
||||
|
||||
export const HandoffCancelRequest = z.object({
|
||||
poll_secret: createStringType().describe('The poll secret issued when the handoff was initiated'),
|
||||
});
|
||||
|
||||
export type HandoffCancelRequest = z.infer<typeof HandoffCancelRequest>;
|
||||
|
||||
export const EnableMfaTotpRequest = z
|
||||
.object({
|
||||
secret: createStringType(1, 256).describe('The TOTP secret key'),
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export const DESKTOP_HANDOFF_CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
|
||||
export const DESKTOP_HANDOFF_CODE_LENGTH = 12;
|
||||
|
||||
const DESKTOP_HANDOFF_CODE_GROUP_LENGTH = 6;
|
||||
const DESKTOP_HANDOFF_CODE_SEPARATOR = '-';
|
||||
const DESKTOP_HANDOFF_CODE_SEPARATOR_PATTERN = /[^A-Za-z0-9]/gu;
|
||||
|
||||
function normalizeDesktopHandoffCode(value: string): string {
|
||||
return value.replace(DESKTOP_HANDOFF_CODE_SEPARATOR_PATTERN, '').toUpperCase();
|
||||
}
|
||||
|
||||
export function isDesktopHandoffCode(value: string): boolean {
|
||||
if (value.length !== DESKTOP_HANDOFF_CODE_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
return Array.from(value).every((character) => DESKTOP_HANDOFF_CODE_ALPHABET.includes(character));
|
||||
}
|
||||
|
||||
export function parseDesktopHandoffCodeInput(value: string): string {
|
||||
return normalizeDesktopHandoffCode(value).slice(0, DESKTOP_HANDOFF_CODE_LENGTH);
|
||||
}
|
||||
|
||||
export function parseDesktopHandoffCode(value: string | null | undefined): string | null {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
const normalized = normalizeDesktopHandoffCode(value);
|
||||
if (!isDesktopHandoffCode(normalized)) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function formatDesktopHandoffCode(value: string): string {
|
||||
const normalized = parseDesktopHandoffCodeInput(value);
|
||||
const groups: Array<string> = [];
|
||||
for (let index = 0; index < normalized.length; index += DESKTOP_HANDOFF_CODE_GROUP_LENGTH) {
|
||||
groups.push(normalized.slice(index, index + DESKTOP_HANDOFF_CODE_GROUP_LENGTH));
|
||||
}
|
||||
return groups.join(DESKTOP_HANDOFF_CODE_SEPARATOR);
|
||||
}
|
||||
Reference in New Issue
Block a user