Ctrl K
ComunidadGuías…

lib/linear-store.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');

function attachLinearStore(root) {
  var directory = path.join(root, 'linear');
  function filename(owner) {
    if (typeof owner !== 'string' || !/^[a-zA-Z0-9_-]{1,128}$/.test(owner)) throw new Error('Invalid Linear account owner.');
    return path.join(directory, owner + '.json');
  }
  function read(owner) {
    try { return JSON.parse(fs.readFileSync(filename(owner), 'utf8')); }
    catch (error) { if (error.code === 'ENOENT') return {}; throw new Error('Could not read Linear connection.'); }
  }
  function masterKey() {
    fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
    var file = path.join(directory, '.key');
    try { fs.writeFileSync(file, crypto.randomBytes(32), { flag: 'wx', mode: 0o600 }); }
    catch (error) { if (error.code !== 'EEXIST') throw error; }
    return fs.readFileSync(file);
  }
  function record(owner, id) {
    var data = read(owner);
    return !id || id === "default" ? data : Object.hasOwn(data.connections || {}, id) ? data.connections[id] : {};
  }
  function key(owner, id) {
    var data = record(owner, id);
    if (!data.secret) return null;
    var parts = data.secret.split('.').map(function (part) { return Buffer.from(part, 'base64'); });
    var cipher = crypto.createDecipheriv('aes-256-gcm', masterKey(), parts[0]);
    cipher.setAAD(Buffer.from(id && id !== 'default' ? owner + ':' + id : owner)); cipher.setAuthTag(parts[1]);
    return Buffer.concat([cipher.update(parts[2]), cipher.final()]).toString('utf8');
  }
  function view(owner, id) {
    var data = record(owner, id);
    return { connected: !!data.secret, workspace: data.workspace || null, revision: data.revision || null };
  }
  function save(owner, secret, workspace, id, name) {
    if (id && (!/^[a-zA-Z0-9_-]{1,64}$/.test(id) || ['__proto__', 'constructor', 'prototype'].indexOf(id) >= 0)) throw new Error("Invalid connection.");
    var previous = read(owner);
    var file = filename(owner), data = { revision: crypto.randomBytes(16).toString('hex') };
    if (secret !== null) {
      if (typeof secret !== 'string' || !secret.trim() || secret.length > 4096 || /[\r\n]/.test(secret)) throw new Error('Enter a valid Linear API key.');
      var iv = crypto.randomBytes(12), cipher = crypto.createCipheriv('aes-256-gcm', masterKey(), iv);
      cipher.setAAD(Buffer.from(id && id !== 'default' ? owner + ':' + id : owner));
      var bytes = Buffer.concat([cipher.update(secret.trim(), 'utf8'), cipher.final()]);
      data.secret = [iv, cipher.getAuthTag(), bytes].map(function (part) { return part.toString('base64'); }).join('.');
      data.workspace = workspace;
    }
    if (name !== undefined) {
      if (typeof name !== 'string' || !name.trim() || name.trim().length > 80) throw new Error('Enter a connection name of up to 80 characters.');
      data.name = name.trim();
    } else data.name = record(owner, id).name;
    if (id && id !== 'default') {
      previous.connections = previous.connections || {};
      if (secret === null) delete previous.connections[id];
      else {
        if (!previous.connections[id] && Object.keys(previous.connections).length >= 30) throw new Error('Connection limit reached.');
        previous.connections[id] = data;
      }
      data = previous;
    } else data.connections = previous.connections;
    fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
    var temp = file + '.' + crypto.randomBytes(8).toString('hex') + '.tmp';
    try { fs.writeFileSync(temp, JSON.stringify(data), { mode: 0o600 }); fs.renameSync(temp, file); }
    finally { try { fs.unlinkSync(temp); } catch (error) { if (error.code !== 'ENOENT') throw error; } }
    return view(owner, id);
  }
  function list(owner) {
    var data = read(owner), entries = [];
    if (data.secret) entries.push(Object.assign({ id: 'default', name: data.name || 'Existing connection', legacy: true }, view(owner)));
    Object.keys(data.connections || {}).forEach(function (id) {
      entries.push(Object.assign({ id: id, name: data.connections[id].name || 'Linear', legacy: false }, view(owner, id)));
    });
    return entries;
  }
  return { key: key, view: view, save: save, list: list };
}
module.exports = { attachLinearStore: attachLinearStore };