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 Inherits BaseClass
Private Scheduler As IScheduler Private Scheduler As IScheduler
Private ConnectionString As String Private ReadOnly ConnectionString As String
Private LicenseKey As String Private ReadOnly LicenseKey As String
Private Const JobName = "CertificateDocumentJob" Private Const JobName = "CertificateDocumentJob"

View File

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

View File

@@ -27,6 +27,11 @@ namespace EnvelopeGenerator.Web.Controllers
envelopeService.EnsureValidEnvelopeKey(envelopeKey); envelopeService.EnsureValidEnvelopeKey(envelopeKey);
EnvelopeResponse response = envelopeService.LoadEnvelope(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); logger.Debug("Loaded envelope [{0}] for receiver [{1}]", response.Envelope.Id, response.Envelope.Id);
return Json(response); return Json(response);
} }
@@ -63,7 +68,7 @@ namespace EnvelopeGenerator.Web.Controllers
var signResult = actionService?.SignEnvelope(response.Envelope, response.Receiver); var signResult = actionService?.SignEnvelope(response.Envelope, response.Receiver);
return Ok(); return Ok(new object());
} }
catch (Exception e) catch (Exception e)
{ {

View File

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

View File

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

View File

@@ -23,17 +23,17 @@ class App {
this.Instance = null this.Instance = null
this.currentDocument = null this.currentDocument = null
this.currentReceiver = 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 // and will trigger loading of the Editor Interface
async init() { async init() {
// Load the envelope from the database // Load the envelope from the database
console.debug('Loading envelope from database..') console.debug('Loading envelope from database..')
const envelopeResponse = await this.Network.getEnvelope(this.envelopeKey) const envelopeResponse = await this.Network.getEnvelope(this.envelopeKey)
const envelopeError = !!envelopeResponse.error
if (envelopeError) { if (envelopeResponse.fatal) {
return Swal.fire({ return Swal.fire({
title: 'Fehler', title: 'Fehler',
text: 'Umschlag konnte nicht geladen werden!', 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.currentDocument = envelopeResponse.data.envelope.documents[0]
this.currentReceiver = envelopeResponse.data.receiver this.currentReceiver = envelopeResponse.data.receiver
@@ -50,9 +58,8 @@ class App {
this.envelopeKey, this.envelopeKey,
this.currentDocument.id this.currentDocument.id
) )
const documentError = !!documentResponse.error
if (documentError) { if (documentResponse.fatal || documentResponse.error) {
console.error(documentResponse.error) console.error(documentResponse.error)
return Swal.fire({ return Swal.fire({
title: 'Fehler', title: 'Fehler',
@@ -85,12 +92,21 @@ class App {
console.debug('Loading annotations..') console.debug('Loading annotations..')
try { try {
this.signatureCount = this.currentDocument.elements.length
const annotations = this.Annotation.createAnnotations( const annotations = this.Annotation.createAnnotations(
this.currentDocument 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) { } catch (e) {
console.error(e) console.error(e)
} }
@@ -169,8 +185,6 @@ class App {
if (result == true) { if (result == true) {
// Redirect to success page after saving to database // Redirect to success page after saving to database
window.location.href = `/EnvelopeKey/${this.envelopeKey}/Success` window.location.href = `/EnvelopeKey/${this.envelopeKey}/Success`
} else {
alert('Fehler beim Abschließen des Dokuments!')
} }
break break
@@ -181,11 +195,28 @@ class App {
} }
async handleFinish(event) { 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 // Save changes before doing anything
try { try {
await this.Instance.save() await this.Instance.save()
} catch (e) { } catch (e) {
console.error(e) console.error(e)
Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht signiert werden!',
icon: 'error',
})
return false return false
} }
@@ -195,20 +226,64 @@ class App {
const postEnvelopeResult = await this.Network.postEnvelope( const postEnvelopeResult = await this.Network.postEnvelope(
this.envelopeKey, this.envelopeKey,
this.currentDocument.id, this.currentDocument.id,
JSON.stringify(json) json
) )
console.log(postEnvelopeResult) if (postEnvelopeResult.fatal) {
Swal.fire({
if (postEnvelopeResult === false) { title: 'Fehler',
text: 'Umschlag konnte nicht signiert werden!',
icon: 'error',
})
return false return false
} }
if (postEnvelopeResult.error) {
Swal.fire({
title: 'Warnung',
text: 'Umschlag ist nicht mehr verfügbar.',
icon: 'warning',
})
return false
}
return true
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return false 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) { async handleReset(event) {

View File

@@ -1,107 +1,167 @@
class Network { class Network {
getEnvelope(envelopeKey) {
return fetch(
`/api/envelope/${envelopeKey}`,
this.withCSRFToken({ credentials: 'include' })
).then(this.wrapJsonResponse.bind(this))
}
getDocument(envelopeKey, documentId) { /**
return fetch( * Load envelope json data
`/api/document/${envelopeKey}?index=${documentId}`, * @param {any} envelopeKey
this.withCSRFToken({ credentials: 'include' }) */
).then(this.wrapBinaryResponse.bind(this)) async getEnvelope(envelopeKey) {
} console.log("getEnvelope")
return this.getRequest(`/api/envelope/${envelopeKey}`)
postEnvelope(envelopeKey, documentId, jsonString) { .then(this.wrapJsonResponse.bind(this))
const url = `/api/envelope/${envelopeKey}?index=${documentId}`
const options = {
credentials: 'include',
method: 'POST',
body: jsonString,
} }
console.debug('PostEnvelope/Calling url: ' + url) /**
return fetch(url, this.withCSRFToken(options)) * Save signature data to server
.then(this.handleResponse) * @param {any} envelopeKey
.then((res) => { * @param {any} documentId
if (!res.ok) { * @param {any} json
return false */
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) { return options
const url = `/api/document/${envelopeKey}`
const options = {
credentials: 'include',
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify({}),
} }
console.debug('OpenDocument/Calling url: ' + url) /**
return fetch(url, this.withCSRFToken(options)) * Fetches CSRF Token from page
.then(this.handleResponse) * @returns
.then((res) => { */
if (!res.ok) { getCSRFToken() {
return false 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) { return fetch(url, 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 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) { return fetch(url, options)
if (!res.ok) { }
console.log(`Request failed with status ${res.status}`)
return res /**
} else { * Reads and wraps a json response
return res * @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 { class WrappedResponse {
constructor(data, error) { constructor(data, error) {
this.data = data this.data = data
this.error = error this.error = error
} this.fatal = (data === null && error === null)
}
} }