validation client and server

This commit is contained in:
Jonathan Jenne 2023-12-15 10:45:32 +01:00
parent 0ad1d214ba
commit bdf7bdd37a
7 changed files with 269 additions and 127 deletions

View File

@ -9,8 +9,8 @@ Public Class Scheduler
Inherits BaseClass
Private Scheduler As IScheduler
Private ConnectionString As String
Private LicenseKey As String
Private ReadOnly ConnectionString As String
Private ReadOnly LicenseKey As String
Private Const JobName = "CertificateDocumentJob"

View File

@ -58,7 +58,7 @@ namespace EnvelopeGenerator.Web.Controllers
actionService.OpenEnvelope(response.Envelope, response.Receiver);
return Ok();
return Ok(new object());
}
catch (Exception e)
{

View File

@ -26,6 +26,11 @@ namespace EnvelopeGenerator.Web.Controllers
// Validate Envelope Key and load envelope
envelopeService.EnsureValidEnvelopeKey(envelopeKey);
EnvelopeResponse response = envelopeService.LoadEnvelope(envelopeKey);
if (envelopeService.ReceiverAlreadySigned(response.Envelope, response.Receiver.Id) == true)
{
return Problem(statusCode: 403);
}
logger.Debug("Loaded envelope [{0}] for receiver [{1}]", response.Envelope.Id, response.Envelope.Id);
return Json(response);
@ -63,7 +68,7 @@ namespace EnvelopeGenerator.Web.Controllers
var signResult = actionService?.SignEnvelope(response.Envelope, response.Receiver);
return Ok();
return Ok(new object());
}
catch (Exception e)
{

View File

@ -10,6 +10,8 @@ namespace EnvelopeGenerator.Web.Services
{
private readonly ReceiverModel receiverModel;
private readonly EnvelopeModel envelopeModel;
private readonly HistoryModel historyModel;
private readonly DocumentStatusModel documentStatusModel;
public EnvelopeService(IConfiguration Config, LoggingService Logging, DatabaseService database) : base(Config, Logging)
@ -23,6 +25,7 @@ namespace EnvelopeGenerator.Web.Services
receiverModel = database.Models.receiverModel;
envelopeModel = database.Models.envelopeModel;
historyModel = database.Models.historyModel;
documentStatusModel = database.Models.documentStatusModel;
}
@ -110,6 +113,11 @@ namespace EnvelopeGenerator.Web.Services
return (List<Envelope>)envelopeModel.List(pReceiverId);
}
public bool ReceiverAlreadySigned(Envelope envelope, int receiverId)
{
return historyModel.HasReceiverSigned(envelope.Id, receiverId);
}
public async Task<string?> EnsureValidAnnotationData(HttpRequest request)
{
try

View File

@ -3,7 +3,7 @@
const annotations = []
document.elements.forEach((element) => {
console.log('Creating annotation for element', element.id)
console.debug('Creating annotation for element', element.id)
const [annotation, formField] = this.createAnnotationFromElement(element)
annotations.push(annotation)
@ -13,17 +13,20 @@
return annotations
}
async deleteAnnotations(instance) {
let pageAnnotations = (
await Promise.all(
Array.from({ length: instance.totalPageCount }).map((_, pageIndex) =>
instance.getAnnotations(pageIndex)
)
async getAnnotations(instance) {
const array = await Promise.all(
Array.from({ length: instance.totalPageCount }).map((_, pageIndex) =>
instance.getAnnotations(pageIndex)
)
)
.flatMap((annotations) =>
annotations.reduce((acc, annotation) => acc.concat(annotation), [])
)
return array.flatMap((annotations) =>
annotations.reduce((acc, annotation) => acc.concat(annotation), [])
)
}
async deleteAnnotations(instance) {
let pageAnnotations = this.getAnnotations(instance)
.filter(
(annotation) =>
!!annotation.isSignature || annotation.description == 'FRAME'
@ -33,16 +36,7 @@
}
async validateAnnotations(instance) {
let pageAnnotations = (
await Promise.all(
Array.from({ length: instance.totalPageCount }).map((_, pageIndex) =>
instance.getAnnotations(pageIndex)
)
)
)
.flatMap((annotations) =>
annotations.reduce((acc, annotation) => acc.concat(annotation), [])
)
let pageAnnotations = this.getAnnotations(instance)
.map((annotation) => {
console.log(annotation.toJS())
return annotation

View File

@ -23,17 +23,17 @@ class App {
this.Instance = null
this.currentDocument = null
this.currentReceiver = null
this.signatureCount = 0
}
// This function will be called in the ShowEnvelope.razor page
// This function will be called from the ShowEnvelope.razor page
// and will trigger loading of the Editor Interface
async init() {
// Load the envelope from the database
console.debug('Loading envelope from database..')
const envelopeResponse = await this.Network.getEnvelope(this.envelopeKey)
const envelopeError = !!envelopeResponse.error
if (envelopeError) {
if (envelopeResponse.fatal) {
return Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht geladen werden!',
@ -41,6 +41,14 @@ class App {
})
}
if (envelopeResponse.error) {
return Swal.fire({
title: 'Warnung',
text: 'Umschlag ist nicht mehr verfügbar.',
icon: 'warning',
})
}
this.currentDocument = envelopeResponse.data.envelope.documents[0]
this.currentReceiver = envelopeResponse.data.receiver
@ -50,9 +58,8 @@ class App {
this.envelopeKey,
this.currentDocument.id
)
const documentError = !!documentResponse.error
if (documentError) {
if (documentResponse.fatal || documentResponse.error) {
console.error(documentResponse.error)
return Swal.fire({
title: 'Fehler',
@ -85,12 +92,21 @@ class App {
console.debug('Loading annotations..')
try {
this.signatureCount = this.currentDocument.elements.length
const annotations = this.Annotation.createAnnotations(
this.currentDocument
)
const createdAnnotations = await this.Instance.create(annotations)
await this.Instance.create(annotations)
await this.Network.openDocument(this.envelopeKey)
const openResponse = await this.Network.openDocument(this.envelopeKey)
if (openResponse.fatal || openResponse.error) {
return Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht geöffnet werden!',
icon: 'error',
})
}
} catch (e) {
console.error(e)
}
@ -169,8 +185,6 @@ class App {
if (result == true) {
// Redirect to success page after saving to database
window.location.href = `/EnvelopeKey/${this.envelopeKey}/Success`
} else {
alert('Fehler beim Abschließen des Dokuments!')
}
break
@ -181,11 +195,28 @@ class App {
}
async handleFinish(event) {
const validationResult = await this.validateAnnotations(this.signatureCount)
if (validationResult === false) {
Swal.fire({
title: 'Warnung',
text: 'Es wurden nicht alle Signaturfelder ausgefüllt!',
icon: 'warning',
})
return false
}
// Save changes before doing anything
try {
await this.Instance.save()
} catch (e) {
console.error(e)
Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht signiert werden!',
icon: 'error',
})
return false
}
@ -195,20 +226,64 @@ class App {
const postEnvelopeResult = await this.Network.postEnvelope(
this.envelopeKey,
this.currentDocument.id,
JSON.stringify(json)
json
)
console.log(postEnvelopeResult)
if (postEnvelopeResult === false) {
if (postEnvelopeResult.fatal) {
Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht signiert werden!',
icon: 'error',
})
return false
}
if (postEnvelopeResult.error) {
Swal.fire({
title: 'Warnung',
text: 'Umschlag ist nicht mehr verfügbar.',
icon: 'warning',
})
return false
}
return true
} catch (e) {
console.error(e)
return false
}
}
return true
async validateAnnotations(totalSignatures) {
const annotations = await this.Annotation.getAnnotations(this.Instance)
const filtered = annotations
.map(a => a.toJS())
.filter(a => a.isSignature)
console.log(filtered.length, "Signatures signed!")
if (totalSignatures > filtered.length) {
return false
} else {
return true
}
/*this.Instance.getFormFields().then(formFields => {
formFields.forEach(formField => {
console.log(formField.name, formField.toJS());
});
// Filter form fields by type
formFields.filter(formField => (
formField instanceof PSPDFKit.FormFields.TextFormField
));
// Get the total number of form fields
const totalFormFields = formFields.size;
console.log(totalFormFields)
})*/
}
async handleReset(event) {

View File

@ -1,107 +1,167 @@
class Network {
getEnvelope(envelopeKey) {
return fetch(
`/api/envelope/${envelopeKey}`,
this.withCSRFToken({ credentials: 'include' })
).then(this.wrapJsonResponse.bind(this))
}
getDocument(envelopeKey, documentId) {
return fetch(
`/api/document/${envelopeKey}?index=${documentId}`,
this.withCSRFToken({ credentials: 'include' })
).then(this.wrapBinaryResponse.bind(this))
}
postEnvelope(envelopeKey, documentId, jsonString) {
const url = `/api/envelope/${envelopeKey}?index=${documentId}`
const options = {
credentials: 'include',
method: 'POST',
body: jsonString,
/**
* Load envelope json data
* @param {any} envelopeKey
*/
async getEnvelope(envelopeKey) {
console.log("getEnvelope")
return this.getRequest(`/api/envelope/${envelopeKey}`)
.then(this.wrapJsonResponse.bind(this))
}
console.debug('PostEnvelope/Calling url: ' + url)
return fetch(url, this.withCSRFToken(options))
.then(this.handleResponse)
.then((res) => {
if (!res.ok) {
return false
/**
* Save signature data to server
* @param {any} envelopeKey
* @param {any} documentId
* @param {any} json
*/
async postEnvelope(envelopeKey, documentId, json) {
console.log("postEnvelope")
return this.postRequest(`/api/envelope/${envelopeKey}?index=${documentId}`, json)
.then(this.wrapJsonResponse.bind(this))
}
/**
* Load document binary data
* @param {any} envelopeKey
* @param {any} documentId
*/
async getDocument(envelopeKey, documentId) {
console.log("getDocument")
return this.getRequest(`/api/document/${envelopeKey}?index=${documentId}`)
.then(this.wrapBinaryResponse.bind(this))
}
/**
* Tell the server that document has been seen
* @param {any} envelopeKey
*/
async openDocument(envelopeKey) {
console.log("openDocument")
return this.postRequest(`/api/document/${envelopeKey}`, {})
.then(this.wrapJsonResponse.bind(this))
}
/**
* Add CSRF Token to request headers
* @param {any} options
* @returns
*/
withCSRFToken(options) {
const token = getCSRFToken
let headers = options.headers
options.headers = {
...headers,
...token
}
return true
})
}
openDocument(envelopeKey) {
const url = `/api/document/${envelopeKey}`
const options = {
credentials: 'include',
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({}),
return options
}
console.debug('OpenDocument/Calling url: ' + url)
return fetch(url, this.withCSRFToken(options))
.then(this.handleResponse)
.then((res) => {
if (!res.ok) {
return false
/**
* Fetches CSRF Token from page
* @returns
*/
getCSRFToken() {
const token = document.getElementsByName('__RequestVerificationToken')[0].value
return { 'X-XSRF-TOKEN': token }
}
/**
* Creates a GET HTTP request to `url`
* @param {any} url
*/
getRequest(url) {
const token = this.getCSRFToken()
const options = {
credentials: 'include',
method: 'GET',
headers: {
...token
}
}
return true
})
}
withCSRFToken(options) {
const token = document.getElementsByName('__RequestVerificationToken')[0]
.value
let headers = options.headers
options.headers = { ...headers, 'X-XSRF-TOKEN': token }
return options
}
async wrapJsonResponse(response) {
return await this.wrapResponse(response, async (res) => await res.json())
}
async wrapBinaryResponse(response) {
return await this.wrapResponse(
response,
async (res) => await res.arrayBuffer()
)
}
async wrapResponse(response, responseHandler) {
let wrappedResponse
if (response.ok) {
const data = await responseHandler(response)
wrappedResponse = new WrappedResponse(data, null)
} else {
const error = await response.json()
wrappedResponse = new WrappedResponse(null, error)
return fetch(url, options)
}
return wrappedResponse
}
/**
* Creates a POST HTTP request for url
* @param {any} url
* @param {any} json
* @returns
*/
postRequest(url, json) {
const token = this.getCSRFToken()
const options = {
credentials: 'include',
method: 'POST',
headers: {
...token,
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(json)
}
handleResponse(res) {
if (!res.ok) {
console.log(`Request failed with status ${res.status}`)
return res
} else {
return res
return fetch(url, options)
}
/**
* Reads and wraps a json response
* @param {any} response
* @returns
*/
async wrapJsonResponse(response) {
return await this.wrapResponse(
response,
async (res) => await res.json())
}
/**
* Reads and wraps a binary response
* @param {any} response
* @returns
*/
async wrapBinaryResponse(response) {
return await this.wrapResponse(
response,
async (res) => await res.arrayBuffer())
}
/**
* Wraps a fetch response depending on status code
* @param {any} response
* @param {any} responseHandler
* @returns
*/
async wrapResponse(response, responseHandler) {
let wrappedResponse
console.log("Handling response from", response.url)
console.log("Status", response.status)
console.log(response)
if (response.status === 200) {
const data = await responseHandler(response)
wrappedResponse = new WrappedResponse(data, null)
} else if (response.status === 403) {
const error = await response.json()
wrappedResponse = new WrappedResponse(null, error)
} else {
wrappedResponse = new WrappedResponse(null, null)
}
console.log("Wrapped response", wrappedResponse)
return wrappedResponse
}
}
}
class WrappedResponse {
constructor(data, error) {
this.data = data
this.error = error
}
constructor(data, error) {
this.data = data
this.error = error
this.fatal = (data === null && error === null)
}
}