TrustedDeviceLogin API и настройки входа через устройство

Что сделано:\n- публичный API сценария входа через доверенное устройство переведён на TrustedDeviceLogin\n- добавлен GetTrustedDeviceLoginSettings\n- отсутствие записи настроек на сервере теперь трактуется как enabled=true и hasPassword=false\n- ttlSeconds убран из клиентского API, TTL заявки фиксирован на сервере: 300 секунд\n- в shine-UI добавлен отдельный экран настроек входа через устройство и статус на основном экране\n- browser wallet переведён на новые TrustedDeviceLogin операции\n- в wallet добавлен выбор rootKey/deviceKey для будущего запроса подписи\n- документация API обновлена\n\nЧто ещё не проверено вручную end-to-end:\n- полный сценарий UI/plugin после этого деплоя не прогонялся руками до конца\n- сам signaling подписи в wallet всё ещё не реализован
This commit is contained in:
AidarKC
2026-06-18 14:19:31 +04:00
parent cf2152dcfc
commit 56db6d0add
29 changed files with 689 additions and 241 deletions
+38 -25
View File
@@ -1091,17 +1091,22 @@ export class AuthService {
if (response.status !== 200) throw opError('CloseActiveSession', response);
}
async upsertEspPairingSettings({ enabled, passwordHash = '', ttlSeconds = 180 }) {
const response = await this.ws.request('UpsertEspPairingSettings', {
enabled: !!enabled,
passwordHash: String(passwordHash || '').trim(),
ttlSeconds: Number(ttlSeconds) || 180,
});
if (response.status !== 200) throw opError('UpsertEspPairingSettings', response);
async getTrustedDeviceLoginSettings() {
const response = await this.ws.request('GetTrustedDeviceLoginSettings', {});
if (response.status !== 200) throw opError('GetTrustedDeviceLoginSettings', response);
return response.payload || {};
}
async startEspPairing({
async upsertTrustedDeviceLoginSettings({ enabled, passwordHash = '' }) {
const response = await this.ws.request('UpsertTrustedDeviceLoginSettings', {
enabled: !!enabled,
passwordHash: String(passwordHash || '').trim(),
});
if (response.status !== 200) throw opError('UpsertTrustedDeviceLoginSettings', response);
return response.payload || {};
}
async startTrustedDeviceLogin({
login,
passwordHash,
requesterSessionKey,
@@ -1109,7 +1114,7 @@ export class AuthService {
requesterClientPlatform = makeClientPlatform(),
payloadType = 3,
}) {
const response = await this.ws.request('StartEspPairing', {
const response = await this.ws.request('StartTrustedDeviceLogin', {
login: String(login || '').trim(),
passwordHash: String(passwordHash || '').trim(),
requesterSessionKey: String(requesterSessionKey || '').trim(),
@@ -1117,51 +1122,59 @@ export class AuthService {
requesterClientPlatform: String(requesterClientPlatform || '').trim() || makeClientPlatform(),
payloadType: Number(payloadType) || 3,
});
if (response.status !== 200) throw opError('StartEspPairing', response);
if (response.status !== 200) throw opError('StartTrustedDeviceLogin', response);
return response.payload || {};
}
async listEspPairingRequests() {
const response = await this.ws.request('ListEspPairingRequests', {});
if (response.status !== 200) throw opError('ListEspPairingRequests', response);
async listTrustedDeviceLoginRequests() {
const response = await this.ws.request('ListTrustedDeviceLoginRequests', {});
if (response.status !== 200) throw opError('ListTrustedDeviceLoginRequests', response);
return Array.isArray(response?.payload?.requests) ? response.payload.requests : [];
}
async approveEspPairing(pairingId, encryptedPayload) {
const response = await this.ws.request('ApproveEspPairing', {
async approveTrustedDeviceLogin(pairingId, encryptedPayload) {
const response = await this.ws.request('ApproveTrustedDeviceLogin', {
pairingId: String(pairingId || '').trim(),
encryptedPayload: String(encryptedPayload || '').trim(),
});
if (response.status !== 200) throw opError('ApproveEspPairing', response);
if (response.status !== 200) throw opError('ApproveTrustedDeviceLogin', response);
return response.payload || {};
}
async rejectEspPairing(pairingId, reason = '') {
const response = await this.ws.request('RejectEspPairing', {
async rejectTrustedDeviceLogin(pairingId, reason = '') {
const response = await this.ws.request('RejectTrustedDeviceLogin', {
pairingId: String(pairingId || '').trim(),
reason: String(reason || '').trim(),
});
if (response.status !== 200) throw opError('RejectEspPairing', response);
if (response.status !== 200) throw opError('RejectTrustedDeviceLogin', response);
return response.payload || {};
}
async cancelEspPairing(pairingId, requesterSessionKey) {
const response = await this.ws.request('CancelEspPairing', {
async cancelTrustedDeviceLogin(pairingId, requesterSessionKey) {
const response = await this.ws.request('CancelTrustedDeviceLogin', {
pairingId: String(pairingId || '').trim(),
requesterSessionKey: String(requesterSessionKey || '').trim(),
});
if (response.status !== 200) throw opError('CancelEspPairing', response);
if (response.status !== 200) throw opError('CancelTrustedDeviceLogin', response);
return response.payload || {};
}
async getEspPairingStatus(pairingId) {
const response = await this.ws.request('GetEspPairingStatus', {
async getTrustedDeviceLoginStatus(pairingId) {
const response = await this.ws.request('GetTrustedDeviceLoginStatus', {
pairingId: String(pairingId || '').trim(),
});
if (response.status !== 200) throw opError('GetEspPairingStatus', response);
if (response.status !== 200) throw opError('GetTrustedDeviceLoginStatus', response);
return response.payload || {};
}
async upsertEspPairingSettings(args) { return this.upsertTrustedDeviceLoginSettings(args); }
async startEspPairing(args) { return this.startTrustedDeviceLogin(args); }
async listEspPairingRequests() { return this.listTrustedDeviceLoginRequests(); }
async approveEspPairing(pairingId, encryptedPayload) { return this.approveTrustedDeviceLogin(pairingId, encryptedPayload); }
async rejectEspPairing(pairingId, reason = '') { return this.rejectTrustedDeviceLogin(pairingId, reason); }
async cancelEspPairing(pairingId, requesterSessionKey) { return this.cancelTrustedDeviceLogin(pairingId, requesterSessionKey); }
async getEspPairingStatus(pairingId) { return this.getTrustedDeviceLoginStatus(pairingId); }
async listSubscriptionsFeed(login, limit = 200) {
const response = await this.ws.request('ListSubscriptionsFeed', { login, limit });
if (response.status !== 200) throw opError('ListSubscriptionsFeed', response);
@@ -811,13 +811,13 @@ async function createShineUserPdaOnSolana({
};
}
export async function registerUserOnSolana({ login, keyBundle, solanaEndpoint }) {
export async function registerUserOnSolana({ login, keyBundle, solanaEndpoint, accessServers = [] }) {
return createShineUserPdaOnSolana({
login,
keyBundle,
solanaEndpoint,
isServer: false,
accessServers: ['shineup.me'],
accessServers: Array.isArray(accessServers) ? accessServers : [],
});
}
@@ -138,6 +138,6 @@ export async function checkLoginExistsOnSolana({ login, solanaEndpoint }) {
return { exists: !!ai, userPda: userPda.toBase58() };
}
export async function registerUserOnSolana({ login, keyBundle, solanaEndpoint }) {
return registerUserOnSolanaShared({ login, keyBundle, solanaEndpoint });
export async function registerUserOnSolana({ login, keyBundle, solanaEndpoint, accessServers }) {
return registerUserOnSolanaShared({ login, keyBundle, solanaEndpoint, accessServers });
}
+4
View File
@@ -54,6 +54,10 @@ export function toUserMessage(error, fallback = 'Действие не выпо
return 'К сожалению сейчас нет ни одного активного устройства этого пользователя, подключенного к этому серверу в сети, и поэтому вход таким образом выполнить невозможно.';
}
if (code === 'PAIRING_NOT_AVAILABLE') {
return 'Для этого логина ещё не включено подключение по коду. На доверенном устройстве откройте «Подключить по коду» и нажмите «Включить подключение по коду» или задайте дополнительный пароль.';
}
if (code === 'PAIRING_PASSWORD_INVALID') {
return 'Пароль подключения не подходит.';
}