Убрал long-press меню каналов и обновил deploy-проверку sudo

This commit is contained in:
AidarKC
2026-05-31 19:30:36 +04:00
parent 1b0e1cf1d4
commit 5899bd2f77
25 changed files with 3343 additions and 192 deletions
+18
View File
@@ -8,6 +8,7 @@ import {
exportPkcs8B64,
generateEd25519Pair,
importPkcs8Ed25519,
publicKeyB64FromPkcs8Ed25519,
randomBase64,
sha256Bytes,
signBytes,
@@ -857,6 +858,23 @@ export class AuthService {
return { ...session, keyBundle };
}
async createSessionFromImportedSecrets(login, secrets) {
const cleanLogin = (login || '').trim();
if (!cleanLogin) throw new Error('В QR-коде нет логина');
const deviceKey = String(secrets?.deviceKey || '').trim();
if (!deviceKey) throw new Error('В QR-коде нет device key для входа');
const privateKey = await importPkcs8Ed25519(deviceKey);
const publicKeyB64 = await publicKeyB64FromPkcs8Ed25519(deviceKey);
const session = await this.createAuthSession(cleanLogin, {
devicePair: {
privateKey,
publicKeyB64,
},
});
return session;
}
async persistSelectedKeys(login, storagePwd, keyBundle, saveOptions = { saveRoot: true, saveBlockchain: true }) {
let currentSecrets = {};
try {
+7
View File
@@ -254,6 +254,13 @@ export async function importPkcs8Ed25519(pkcs8B64) {
return getSubtleApi().importKey('pkcs8', base64ToBytes(pkcs8B64), { name: 'Ed25519' }, false, ['sign']);
}
export async function publicKeyB64FromPkcs8Ed25519(pkcs8B64) {
const privateKey = await getSubtleApi().importKey('pkcs8', base64ToBytes(pkcs8B64), { name: 'Ed25519' }, true, ['sign']);
const jwk = await getSubtleApi().exportKey('jwk', privateKey);
if (!jwk.x) throw new Error('Не удалось получить публичный ключ Ed25519');
return bytesToBase64(base64ToBytes(base64UrlToBase64(jwk.x)));
}
export async function signBase64(privateKey, text) {
const signature = await getSubtleApi().sign({ name: 'Ed25519' }, privateKey, utf8Bytes(text));
return bytesToBase64(new Uint8Array(signature));
@@ -0,0 +1,87 @@
import qrcode from '../vendor-qrcode-generator.js';
const TRANSFER_PREFIX = 'shine-key-transfer-v1:';
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function bytesToBase64Url(bytes) {
let binary = '';
bytes.forEach((b) => {
binary += String.fromCharCode(b);
});
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
}
function base64UrlToBytes(value) {
const normalized = String(value || '').trim().replace(/-/g, '+').replace(/_/g, '/');
const padded = normalized + '='.repeat((4 - (normalized.length % 4)) % 4);
const binary = atob(padded);
const out = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
return out;
}
export function keyLabel(id) {
if (id === 'root') return 'root';
if (id === 'blockchain') return 'blockchain';
if (id === 'device') return 'device';
return id;
}
export function describeTransferKeys(keys = {}) {
const out = [];
if (keys.deviceKey) out.push('device');
if (keys.blockchainKey) out.push('blockchain');
if (keys.rootKey) out.push('root');
return out;
}
export function makeKeyTransferText({ login, keys }) {
const payload = {
v: 1,
type: 'shine-key-transfer',
login: String(login || '').trim(),
keys: {
deviceKey: String(keys?.deviceKey || ''),
blockchainKey: String(keys?.blockchainKey || ''),
rootKey: String(keys?.rootKey || ''),
},
createdAtMs: Date.now(),
};
const json = JSON.stringify(payload);
return `${TRANSFER_PREFIX}${bytesToBase64Url(encoder.encode(json))}`;
}
export function parseKeyTransferText(text) {
const raw = String(text || '').trim();
if (!raw.startsWith(TRANSFER_PREFIX)) {
throw new Error('Это не QR-код переноса ключей SHiNE');
}
const json = decoder.decode(base64UrlToBytes(raw.slice(TRANSFER_PREFIX.length)));
const payload = JSON.parse(json);
if (payload?.v !== 1 || payload?.type !== 'shine-key-transfer') {
throw new Error('Неподдерживаемый формат QR-кода');
}
const login = String(payload.login || '').trim();
if (!login) throw new Error('В QR-коде нет логина');
const keys = payload.keys && typeof payload.keys === 'object' ? payload.keys : {};
if (!keys.deviceKey && !keys.blockchainKey && !keys.rootKey) {
throw new Error('В QR-коде нет ключей');
}
return {
login,
keys: {
deviceKey: String(keys.deviceKey || ''),
blockchainKey: String(keys.blockchainKey || ''),
rootKey: String(keys.rootKey || ''),
},
keyTypes: describeTransferKeys(keys),
};
}
export function renderQrSvg(text, { cellSize = 4, margin = 4 } = {}) {
const qr = qrcode(0, 'L');
qr.addData(String(text || ''), 'Byte');
qr.make();
return qr.createSvgTag(cellSize, margin);
}