368 lines
14 KiB
JavaScript
368 lines
14 KiB
JavaScript
import React from "react";
|
||
import Link from "next/link";
|
||
import { SpinnerCircular } from "spinners-react";
|
||
import moment from "moment";
|
||
import dynamic from 'next/dynamic';
|
||
import { concatSeries } from 'async';
|
||
import fileDownload from 'js-file-download';
|
||
import * as Sentry from "@sentry/nextjs";
|
||
|
||
import FormMessage from "./FormMessage";
|
||
import { downloadQuestionnaire, uploadSignedFile } from "../../../actions";
|
||
//import { generateSignature } from "../../../utils/digital_signature";
|
||
|
||
|
||
export default class DigitalCertificates extends React.Component
|
||
{
|
||
constructor(props)
|
||
{
|
||
super(props);
|
||
|
||
this.state = {
|
||
loading: true,
|
||
certificates: [],
|
||
certificates_error: null,
|
||
certificate_selected: undefined,
|
||
signing: false,
|
||
show_all: false,
|
||
};
|
||
this.cryptopro = null;
|
||
}
|
||
|
||
async componentDidMount()
|
||
{
|
||
const { company, main, head_person } = this.props;
|
||
//console.log({ company, main });
|
||
//console.log("DC", "CDM");
|
||
|
||
const { CryptoPro } = await import('ruscryptojs');
|
||
|
||
this.cryptopro = new CryptoPro();
|
||
this.cryptopro.init()
|
||
.then((info) =>
|
||
{
|
||
//console.log('Initialized', info);
|
||
// console.log('-'.repeat(20));
|
||
// for(let i in this.cryptopro)
|
||
// {
|
||
// console.log(i);
|
||
// }
|
||
// console.log('-'.repeat(20));
|
||
|
||
this.cryptopro.listCertificates()
|
||
.then((certificates_list) =>
|
||
{
|
||
console.log({ certificates_list });
|
||
|
||
console.log("all certificates", JSON.stringify(certificates_list));
|
||
Sentry.captureMessage("Client digital sign step, list of all digital certificates");
|
||
|
||
concatSeries(certificates_list, (certificate, callback) =>
|
||
{
|
||
//console.log(certificate.id, certificate.name);
|
||
|
||
this.cryptopro.certificateInfo(certificate.id)
|
||
.then((cert) =>
|
||
{
|
||
const errors = [];
|
||
let apply = true;
|
||
|
||
console.log("certificate", JSON.stringify(cert));
|
||
console.log("company", JSON.stringify(company));
|
||
|
||
//console.log({ cert });
|
||
if(cert.Subject['OGRNIP'] !== undefined && cert.Subject['OGRNIP'] !== null && cert.Subject['OGRNIP'] !== "")
|
||
{
|
||
if(cert.Subject['INN'] !== undefined && cert.Subject['INN'] !== null && cert.Subject['INN'] !== "")
|
||
{
|
||
if(main.inn !== cert.Subject['INN'])
|
||
{
|
||
errors.push('inn');
|
||
}
|
||
}
|
||
else
|
||
{
|
||
apply = false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(cert.Subject['INNLE'] !== undefined && cert.Subject['INNLE'] !== null && cert.Subject['INNLE'] !== "")
|
||
{
|
||
if(main.inn !== cert.Subject['INNLE'])
|
||
{
|
||
errors.push('inn');
|
||
}
|
||
}
|
||
else
|
||
{
|
||
apply = false;
|
||
}
|
||
}
|
||
|
||
let owner_valid = true;
|
||
const fields = [ "lastname", "firstname", "middlename", ];
|
||
const cert_owner = `${ cert.Subject['SN']} ${ cert.Subject['G']}`.toUpperCase();
|
||
|
||
for(let i in fields)
|
||
{
|
||
if(head_person[fields[i]] !== null && head_person[fields[i]] !== "")
|
||
{
|
||
if(cert_owner.indexOf(head_person[fields[i]].toUpperCase().trim()) < 0)
|
||
{
|
||
owner_valid = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(!owner_valid)
|
||
{
|
||
errors.push('name');
|
||
}
|
||
|
||
if(cert.ValidToDate !== null && cert.ValidToDate !== undefined && cert.ValidToDate !== "")
|
||
{
|
||
if(moment(cert.ValidToDate) < moment())
|
||
{
|
||
errors.push('date');
|
||
}
|
||
}
|
||
else
|
||
{
|
||
errors.push('date');
|
||
}
|
||
|
||
if(!cert.IsValid)
|
||
{
|
||
errors.push('invalid');
|
||
}
|
||
if(apply)
|
||
{
|
||
callback(null, [ { id: certificate.id, fields: cert.Subject, valid_to_date: moment(cert.ValidToDate).format("DD.MM.YYYY"), errors } ]);
|
||
}
|
||
else
|
||
{
|
||
callback(null, []);
|
||
}
|
||
})
|
||
.catch((error_certificates_info) =>
|
||
{
|
||
Sentry.captureMessage("Client digital sign step error");
|
||
|
||
console.error({ error_certificates_info });
|
||
this.setState({ loading: false, certificates_error: "CERTIFICATES" });
|
||
});
|
||
}, (error, certificates) =>
|
||
{
|
||
console.log("applicable certificates", JSON.stringify(certificates));
|
||
Sentry.captureMessage("Client digital sign step, list of applicable digital certificates");
|
||
|
||
if(certificates.length === 0)
|
||
{
|
||
this.setState({ loading: false, certificates_error: "INVALID" });
|
||
}
|
||
else
|
||
{
|
||
const list = [];
|
||
let valid_certificate_exists = false;
|
||
|
||
for(let i in certificates) { if(certificates[i].errors.length === 0) { list.push(certificates[i]); valid_certificate_exists = true; } }
|
||
for(let i in certificates) { if(certificates[i].errors.length !== 0) { list.push(certificates[i]); } }
|
||
|
||
const update = { loading: false, certificates: list, certificates_error: null };
|
||
if(list[0] !== undefined)
|
||
{
|
||
if(list[0].errors.length === 0)
|
||
{
|
||
update.certificate_selected = list[0];
|
||
}
|
||
}
|
||
|
||
if(!valid_certificate_exists)
|
||
{
|
||
update.certificates_error = "INVALID";
|
||
}
|
||
|
||
this.setState(update);
|
||
}
|
||
});
|
||
})
|
||
.catch((error_certificates_list) =>
|
||
{
|
||
console.error({ error_certificates_list });
|
||
this.setState({ loading: false, certificates_error: "CERTIFICATES" });
|
||
});
|
||
})
|
||
.catch((error_init) =>
|
||
{
|
||
console.error({ error_init });
|
||
this.setState({ loading: false, certificates_error: "NOT_INSTALLED" });
|
||
});
|
||
}
|
||
|
||
_sign = () =>
|
||
{
|
||
this.setState({ signing: true }, () =>
|
||
{
|
||
const { company, main, onSignDigital } = this.props;
|
||
const filename = `${ main.inn }_questionnaire_${ moment().format("DDMMYYYY_HHmmss") }.sig`;
|
||
|
||
downloadQuestionnaire({ filename, download: false, base64: true })
|
||
.then(({ file }) =>
|
||
{
|
||
let file_to_sign = file;
|
||
|
||
const now = moment().format("DDMMYYYY_HHmmss");
|
||
this.cryptopro.signData(file_to_sign, this.state.certificate_selected.id)
|
||
.then(signature =>
|
||
{
|
||
//console.log({ signature });
|
||
|
||
let signature_file = new File([ signature ], filename);
|
||
uploadSignedFile(signature_file, company.questionnaire_id, true)
|
||
.then(() =>
|
||
{
|
||
onSignDigital();
|
||
})
|
||
.catch(() =>
|
||
{
|
||
this.setState({ signing: false });
|
||
});
|
||
})
|
||
.catch((error_sign) =>
|
||
{
|
||
console.error({ error_sign });
|
||
this.setState({ signing: false });
|
||
})
|
||
})
|
||
.catch((e) =>
|
||
{
|
||
console.error("exception on sign");
|
||
console.error(e);
|
||
});
|
||
});
|
||
}
|
||
|
||
render()
|
||
{
|
||
const { company, main, head_person } = this.props;
|
||
const { loading, certificates, certificates_error, certificate_selected, signing, show_all } = this.state;
|
||
//console.log("render()", { certificates });
|
||
|
||
console.log({ certificates_error });
|
||
|
||
if(loading)
|
||
{
|
||
return (
|
||
<div style={{ minHeight: 200, display: "flex", justifyContent: "center", alignItems: "center", }}>
|
||
<SpinnerCircular size={ 90 } thickness={ 51 } speed={ 100 } color="rgba(28, 1, 169, 1)" secondaryColor="rgba(236, 239, 244, 1)" />
|
||
</div>
|
||
);
|
||
}
|
||
else
|
||
{
|
||
return (
|
||
<>
|
||
{ certificates_error !== null && (
|
||
<>
|
||
{ (certificates_error === "NOT_INSTALLED" || certificates_error === "CERTIFICATES") && (
|
||
<FormMessage type="moderate" title="Внимание!" message={ <>
|
||
Плагин КриптоПРО не установлен или не активирован. Посмотрите <a style={{ color: "#fff", textDecoration: "underline", }} href={ `${ process.env.NEXT_PUBLIC_MAIN_SITE }/upload/docs/anketa-sign.pdf` }>инструкцию</a> как установить, активировать и проверить работу плагина КриптоПро.<br/>
|
||
<br/>
|
||
После активации плагина, пожалуйста, обновите страницу.
|
||
</> }/>
|
||
) }
|
||
{ certificates_error === "INVALID" && (
|
||
<FormMessage type="moderate" title="Внимание!" message={ <>
|
||
Анкету необходимо подписать по ЭЦП сертификатом юридического лица с ИНН: <br/>
|
||
{ main.inn }, выданного: { head_person.lastname } { head_person.firstname } { head_person.middlename }. <br/>
|
||
Такой сертификат не найден!<br/><br/>
|
||
Если Вы уверены в наличии такого сертификата:
|
||
<ol style={{ listStyle: "decimal", padding: "revert" }}>
|
||
<li>Проверьте, вставлен ли ключ с этим сертификатом.</li>
|
||
<li>Проверьте возможность подписания этим сертификатом с помощью средства <a style={{ color: "#fff", textDecoration: "underline", }} href="https://cryptopro.ru/sites/default/files/products/cades/demopage/cades_bes_sample.html">проверки КриптоПро</a>.</li>
|
||
<li>Возможно, сертификат нужно установить. <a style={{ color: "#fff", textDecoration: "underline", }} href={ `${ process.env.NEXT_PUBLIC_MAIN_SITE }/upload/docs/anketa-sign.pdf` }>Воспользуйтесь инструкцией</a>.</li>
|
||
</ol>
|
||
</> }/>
|
||
) }
|
||
{ certificates_error === "MISMATCH" && (
|
||
<FormMessage type="moderate" title="Внимание!" message={ <>Подписант не соответствует указанному подписанту в анкете.</> }/>
|
||
) }
|
||
</>
|
||
)
|
||
}
|
||
{ certificates.length > 0 && (
|
||
<>
|
||
<div className="feed">
|
||
<p>Список сертификатов, доступных на Вашем компьютере:</p>
|
||
<div className="feed_list">
|
||
|
||
{ certificates.slice(0, show_all ? certificates.length : 2).map((certificate, index) => (
|
||
<div className={`form_field checkbox digital_certificates_status ${ certificate.errors.length > 0 ? "error" : "" } ${ certificates.length > 1 && !show_all && index === 1 ? "show_all_holder" : ""}`} key={ index }>
|
||
<input type="radio"
|
||
hidden=""
|
||
id={ `certificate_${ certificate.id }` }
|
||
name={ `certificate_${ certificate.id }` }
|
||
checked={ certificate_selected !== undefined && certificate_selected.id === certificate.id ? true : false }
|
||
onChange={ () => { this.setState({ certificate_selected: certificate }) } }
|
||
/>
|
||
<label htmlFor={ `certificate_${ certificate.id }` }>
|
||
<div className="feed_item user">
|
||
<img src="/assets/images/icons/avatar.svg" alt="" />
|
||
<div>
|
||
<p className="item_title">{ certificate.fields['CN'].replace(/"/g, '') }</p>
|
||
<p className="item_desc">
|
||
{ certificate.fields['SN'] ? (<span>{ certificate.fields['SN'] } { certificate.fields['G'] }</span>) : null }
|
||
</p>
|
||
<p className="item_desc">
|
||
<span>ИНН { certificate.fields['INNLE'] !== undefined && certificate.fields['INNLE'] !== null ? certificate.fields['INNLE'] : certificate.fields['INN'] }</span>
|
||
</p>
|
||
<p className="item_desc">
|
||
{ certificate.fields['OGRNIP'] && (<span>ОГРНИП { certificate.fields['OGRNIP'] }</span>) }
|
||
{ certificate.fields['OGRN'] && (<span>ОГРН { certificate.fields['OGRN'] }</span>) }
|
||
</p>
|
||
<p className="item_desc">
|
||
<span>Подпись действительна до { certificate.valid_to_date }</span>
|
||
</p>
|
||
</div>
|
||
{ certificate.errors.length > 0 && (
|
||
<div>
|
||
<p className="item_title"> </p>
|
||
{ certificate.errors.indexOf("name") > -1 ? <p className="item_desc" style={{ color: "#A8026B" }}>Не соответствует ФИО</p> : <p className="item_desc"> </p>}
|
||
{ certificate.errors.indexOf("inn") > -1 ? <p className="item_desc" style={{ color: "#A8026B" }}>Не соответствует ИНН</p> : <p className="item_desc"> </p>}
|
||
<p className="item_desc"> </p>
|
||
{ certificate.errors.indexOf("date") > -1 ? <p className="item_desc" style={{ color: "#A8026B" }}>{ certificate.errors.indexOf('date') > 1 ? "Срок действия закончился" : certificate.errors.indexOf('invalid') > 1 ? "Сертификат недействителен или нарушена цепочка сертификации" : null }</p> : <p className="item_desc"> </p>}
|
||
{ certificate.errors.length === 1 && certificate.errors[0] === "invalid" && (<p className="item_desc" style={{ color: "#A8026B" }}>Сертификат недействителен или нарушена цепочка сертификации</p>) }
|
||
</div>
|
||
) }
|
||
</div>
|
||
</label>
|
||
{ certificates.length > 1 && !show_all && index === 1 && (
|
||
<div className="show_all" onClick={ () => this.setState({ show_all: true }) }><span>Посмотреть все</span></div>
|
||
) }
|
||
</div>
|
||
)) }
|
||
|
||
</div>
|
||
</div>
|
||
<div className="action" style={{marginBottom: "20px"}}>
|
||
<div></div>
|
||
<button
|
||
type="submit"
|
||
className="button button-blue"
|
||
disabled={ certificate_selected !== null && certificate_selected !== undefined ? false : true }
|
||
onClick={ this._sign }
|
||
style={{ minWidth: "250px" }}
|
||
>
|
||
{ signing ? (
|
||
<SpinnerCircular size={24} thickness={100} speed={100} color="rgba(255, 255, 255, 1)" secondaryColor="rgba(255, 255, 255, 0.5)" style={{ marginTop: "4px" }}/>
|
||
) : "Подписать выбранной подписью" }
|
||
</button>
|
||
</div>
|
||
</>
|
||
) }
|
||
</>
|
||
)
|
||
}
|
||
}
|
||
} |