SHA256
Убрал long-press меню каналов и обновил deploy-проверку sudo
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user