Compare commits
9 Commits
feat/annot
...
16bdc7820d
| Author | SHA1 | Date | |
|---|---|---|---|
| 16bdc7820d | |||
| 06e32b99ea | |||
| c7c78f96a6 | |||
| 5c232e61f2 | |||
| 24c9321c0f | |||
| c75c2b1dd5 | |||
| 8445757f34 | |||
| b088eb089f | |||
| 1f745ae79c |
49
EnvelopeGenerator.Web/EnvelopeCookieManager.cs
Normal file
49
EnvelopeGenerator.Web/EnvelopeCookieManager.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
||||
namespace EnvelopeGenerator.Web;
|
||||
|
||||
public class EnvelopeCookieManager : ICookieManager
|
||||
{
|
||||
private readonly IEnumerable<string> _envelopeKeyBasedCookieNames;
|
||||
|
||||
private readonly ChunkingCookieManager _inner = new();
|
||||
|
||||
public EnvelopeCookieManager(params string[] envelopeKeyBasedCookieNames)
|
||||
{
|
||||
_envelopeKeyBasedCookieNames = envelopeKeyBasedCookieNames;
|
||||
}
|
||||
|
||||
private string GetCookieName(HttpContext context, string key)
|
||||
{
|
||||
if (!_envelopeKeyBasedCookieNames.Contains(key))
|
||||
return key;
|
||||
|
||||
var envId = context.GetRouteValue("envelopeReceiverId")?.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(envId) && context.Request.Query.TryGetValue("envKey", out var envKeyValue))
|
||||
envId = envKeyValue;
|
||||
|
||||
if (string.IsNullOrEmpty(envId))
|
||||
return key;
|
||||
|
||||
return $"{key}-{envId}";
|
||||
}
|
||||
|
||||
public string? GetRequestCookie(HttpContext context, string key)
|
||||
{
|
||||
var cookieName = GetCookieName(context, key);
|
||||
return _inner.GetRequestCookie(context, cookieName);
|
||||
}
|
||||
|
||||
public void AppendResponseCookie(HttpContext context, string key, string? value, CookieOptions options)
|
||||
{
|
||||
var cookieName = GetCookieName(context, key);
|
||||
_inner.AppendResponseCookie(context, cookieName, value, options);
|
||||
}
|
||||
|
||||
public void DeleteCookie(HttpContext context, string key, CookieOptions options)
|
||||
{
|
||||
var cookieName = GetCookieName(context, key);
|
||||
_inner.DeleteCookie(context, cookieName, options);
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,9 @@
|
||||
<PackageTags>digital data envelope generator web</PackageTags>
|
||||
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
|
||||
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
|
||||
<Version>3.7.0</Version>
|
||||
<AssemblyVersion>3.7.0</AssemblyVersion>
|
||||
<FileVersion>3.7.0</FileVersion>
|
||||
<Version>3.8.0</Version>
|
||||
<AssemblyVersion>3.8.0</AssemblyVersion>
|
||||
<FileVersion>3.8.0</FileVersion>
|
||||
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using EnvelopeGenerator.Web.Models.Annotation;
|
||||
using DigitalData.UserManager.DependencyInjection;
|
||||
using EnvelopeGenerator.Web.Middleware;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using EnvelopeGenerator.Web;
|
||||
|
||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||
logger.Info("Logging initialized!");
|
||||
@@ -134,41 +135,22 @@ try
|
||||
options.ConsentCookie.Name = "cookie-consent-settings";
|
||||
});
|
||||
|
||||
var authCookieName = "env_auth";
|
||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only
|
||||
options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites
|
||||
options.Cookie.Name = authCookieName;
|
||||
options.CookieManager = new EnvelopeCookieManager(authCookieName);
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
||||
options.Cookie.SameSite = SameSiteMode.Strict;
|
||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
|
||||
|
||||
options.Events = new CookieAuthenticationEvents
|
||||
{
|
||||
OnRedirectToLogin = context =>
|
||||
{
|
||||
// Dynamically calculate the redirection path, for example:
|
||||
var envelopeReceiverId = context.HttpContext.Request.RouteValues["envelopeReceiverId"];
|
||||
context.RedirectUri = $"/EnvelopeKey/{envelopeReceiverId}";
|
||||
|
||||
context.Response.Redirect(context.RedirectUri);
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnRedirectToLogout = context =>
|
||||
{
|
||||
// Apply a similar redirection logic for logout
|
||||
var envelopeReceiverId = context.HttpContext.Request.RouteValues["envelopeReceiverId"];
|
||||
context.RedirectUri = $"/EnvelopeKey/{envelopeReceiverId}";
|
||||
|
||||
context.Response.Redirect(context.RedirectUri);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddSingleton(config.GetSection("ContactLink").Get<ContactLink>() ?? new());
|
||||
|
||||
builder.Services.AddCookieBasedLocalizer();
|
||||
|
||||
|
||||
builder.Services.AddSingleton(HtmlEncoder.Default);
|
||||
builder.Services.AddSingleton(UrlEncoder.Default);
|
||||
builder.Services.AddSanitizer<HtmlSanitizer>();
|
||||
@@ -249,7 +231,7 @@ try
|
||||
app.UseAuthorization();
|
||||
|
||||
var cultures = app.Services.GetRequiredService<Cultures>();
|
||||
if(!cultures.Any())
|
||||
if (!cultures.Any())
|
||||
throw new InvalidOperationException(@"Languages section is missing in the appsettings. Please configure like following.
|
||||
Language is both a name of the culture and the name of the resx file such as Resource.de-DE.resx
|
||||
FIClass is the css class (in wwwroot/lib/flag-icons-main) for the flag of country.
|
||||
@@ -264,7 +246,7 @@ try
|
||||
}
|
||||
]");
|
||||
|
||||
if(!config.GetValue<bool>("DisableMultiLanguage"))
|
||||
if (!config.GetValue<bool>("DisableMultiLanguage"))
|
||||
app.UseCookieBasedLocalizer(cultures.Languages.ToArray());
|
||||
|
||||
app.UseCors("SameOriginPolicy");
|
||||
@@ -273,7 +255,7 @@ try
|
||||
app.MapFallbackToController("Error404", "Home");
|
||||
app.Run();
|
||||
}
|
||||
catch(Exception ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.Error(ex, "Stopped program because of exception");
|
||||
throw;
|
||||
|
||||
@@ -1,106 +1,111 @@
|
||||
//#region parameters
|
||||
const env = Object.freeze({
|
||||
__lazyXsrfToken: new Lazy(() => document.getElementsByName('__RequestVerificationToken')[0].value),
|
||||
get xsrfToken() {
|
||||
return this.__lazyXsrfToken.value;
|
||||
}
|
||||
__lazyXsrfToken: new Lazy(() => document.getElementsByName('__RequestVerificationToken')[0].value),
|
||||
get xsrfToken() {
|
||||
return this.__lazyXsrfToken.value;
|
||||
}
|
||||
})
|
||||
|
||||
const url = Object.freeze({
|
||||
reject: `/api/annotation/reject`,
|
||||
share: `/api/readonly`
|
||||
reject: `/api/annotation/reject`,
|
||||
share: `/api/readonly`
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region request helper methods
|
||||
function sendRequest(method, url, body = undefined) {
|
||||
const options = {
|
||||
credentials: 'include',
|
||||
method: method,
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': env.xsrfToken
|
||||
const urlObj = new URL(url, window.location.origin);
|
||||
if (!urlObj.searchParams.has("envKey")) {
|
||||
urlObj.searchParams.set("envKey", ENV_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
if (body !== undefined) {
|
||||
options.body = JSON.stringify(body);
|
||||
options.headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
const options = {
|
||||
credentials: 'include',
|
||||
method: method,
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': env.xsrfToken
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(url, options);
|
||||
if (body !== undefined) {
|
||||
options.body = JSON.stringify(body);
|
||||
options.headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
return fetch(urlObj, options);
|
||||
}
|
||||
|
||||
function getRequest(url) {
|
||||
return sendRequest('GET', url);
|
||||
return sendRequest('GET', url);
|
||||
}
|
||||
|
||||
function getJson(url) {
|
||||
return sendRequest('GET', url).then(res => {
|
||||
if (res.ok)
|
||||
return res.json();
|
||||
throw new Error(`Request failed with status ${res.status}`);
|
||||
});
|
||||
return sendRequest('GET', url).then(res => {
|
||||
if (res.ok)
|
||||
return res.json();
|
||||
throw new Error(`Request failed with status ${res.status}`);
|
||||
});
|
||||
}
|
||||
|
||||
function postRequest(url, body = undefined) {
|
||||
return sendRequest('POST', url, body);
|
||||
return sendRequest('POST', url, body);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
window.location.reload();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function redirect(url) {
|
||||
window.location.href = url;
|
||||
window.location.href = url;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region envelope
|
||||
function signEnvelope(annotations) {
|
||||
return postRequest(`/api/annotation`, annotations)
|
||||
return postRequest(`/api/annotation`, annotations)
|
||||
}
|
||||
|
||||
async function getAnnotationParams(leftInInch = 0, topInInch = 0, inchToPointFactor = 72) {
|
||||
const annotParams = await getJson("/api/Config/Annotations");
|
||||
const annotParams = await getJson("/api/Config/Annotations");
|
||||
|
||||
for (var key in annotParams) {
|
||||
var annot = annotParams[key];
|
||||
annot.width *= inchToPointFactor;
|
||||
annot.height *= inchToPointFactor;
|
||||
annot.left += leftInInch - 0.7;
|
||||
annot.left *= inchToPointFactor;
|
||||
annot.top += topInInch - 0.5;
|
||||
annot.top *= inchToPointFactor;
|
||||
}
|
||||
for (var key in annotParams) {
|
||||
var annot = annotParams[key];
|
||||
annot.width *= inchToPointFactor;
|
||||
annot.height *= inchToPointFactor;
|
||||
annot.left += leftInInch - 0.7;
|
||||
annot.left *= inchToPointFactor;
|
||||
annot.top += topInInch - 0.5;
|
||||
annot.top *= inchToPointFactor;
|
||||
}
|
||||
|
||||
return annotParams;
|
||||
return annotParams;
|
||||
}
|
||||
|
||||
function rejectEnvelope(reason) {
|
||||
return postRequest(url.reject, reason);
|
||||
return postRequest(url.reject, reason);
|
||||
}
|
||||
|
||||
function shareEnvelope(receiverMail, dateValid) {
|
||||
return postRequest(url.share, { receiverMail: receiverMail, dateValid: dateValid });
|
||||
return postRequest(url.share, { receiverMail: receiverMail, dateValid: dateValid });
|
||||
}
|
||||
//#endregion
|
||||
|
||||
async function setLanguage(language) {
|
||||
const hasLang = await getJson('/api/localization/lang')
|
||||
.then(langs => langs.includes(language));
|
||||
const hasLang = await getJson('/api/localization/lang')
|
||||
.then(langs => langs.includes(language));
|
||||
|
||||
if (hasLang)
|
||||
postRequest(`/api/localization/lang/${language}`)
|
||||
.then(response => {
|
||||
if (response.redirected)
|
||||
redirect(response.url);
|
||||
});
|
||||
if (hasLang)
|
||||
postRequest(`/api/localization/lang/${language}`)
|
||||
.then(response => {
|
||||
if (response.redirected)
|
||||
redirect(response.url);
|
||||
});
|
||||
}
|
||||
|
||||
function logout() {
|
||||
return postRequest(`/auth/logout`)
|
||||
.then(res => {
|
||||
if (res.ok)
|
||||
window.location.href = "/";
|
||||
});
|
||||
return postRequest(`/auth/logout`)
|
||||
.then(res => {
|
||||
if (res.ok)
|
||||
window.location.href = "/";
|
||||
});
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
function sendRequest(n,t,i=undefined){const r={credentials:"include",method:n,headers:{"X-XSRF-TOKEN":env.xsrfToken}};return i!==undefined&&(r.body=JSON.stringify(i),r.headers["Content-Type"]="application/json"),fetch(t,r)}function getRequest(n){return sendRequest("GET",n)}function getJson(n){return sendRequest("GET",n).then(n=>{if(n.ok)return n.json();throw new Error(`Request failed with status ${n.status}`);})}function postRequest(n,t=undefined){return sendRequest("POST",n,t)}function reload(){window.location.reload()}function redirect(n){window.location.href=n}function signEnvelope(n){return postRequest(`/api/annotation`,n)}async function getAnnotationParams(n=0,t=0,i=72){var f,r;const u=await getJson("/api/Config/Annotations");for(f in u)r=u[f],r.width*=i,r.height*=i,r.left+=n-.7,r.left*=i,r.top+=t-.5,r.top*=i;return u}function rejectEnvelope(n){return postRequest(url.reject,n)}function shareEnvelope(n,t){return postRequest(url.share,{receiverMail:n,dateValid:t})}async function setLanguage(n){const t=await getJson("/api/localization/lang").then(t=>t.includes(n));t&&postRequest(`/api/localization/lang/${n}`).then(n=>{n.redirected&&redirect(n.url)})}function logout(){return postRequest(`/auth/logout`).then(n=>{n.ok&&(window.location.href="/")})}const env=Object.freeze({__lazyXsrfToken:new Lazy(()=>document.getElementsByName("__RequestVerificationToken")[0].value),get xsrfToken(){return this.__lazyXsrfToken.value}}),url=Object.freeze({reject:`/api/annotation/reject`,share:`/api/readonly`});
|
||||
function sendRequest(n,t,i=undefined){const r=new URL(t,window.location.origin);r.searchParams.has("envKey")||r.searchParams.set("envKey",ENV_KEY);const u={credentials:"include",method:n,headers:{"X-XSRF-TOKEN":env.xsrfToken}};return i!==undefined&&(u.body=JSON.stringify(i),u.headers["Content-Type"]="application/json"),fetch(r,u)}function getRequest(n){return sendRequest("GET",n)}function getJson(n){return sendRequest("GET",n).then(n=>{if(n.ok)return n.json();throw new Error(`Request failed with status ${n.status}`);})}function postRequest(n,t=undefined){return sendRequest("POST",n,t)}function reload(){window.location.reload()}function redirect(n){window.location.href=n}function signEnvelope(n){return postRequest(`/api/annotation`,n)}async function getAnnotationParams(n=0,t=0,i=72){var f,r;const u=await getJson("/api/Config/Annotations");for(f in u)r=u[f],r.width*=i,r.height*=i,r.left+=n-.7,r.left*=i,r.top+=t-.5,r.top*=i;return u}function rejectEnvelope(n){return postRequest(url.reject,n)}function shareEnvelope(n,t){return postRequest(url.share,{receiverMail:n,dateValid:t})}async function setLanguage(n){const t=await getJson("/api/localization/lang").then(t=>t.includes(n));t&&postRequest(`/api/localization/lang/${n}`).then(n=>{n.redirected&&redirect(n.url)})}function logout(){return postRequest(`/auth/logout`).then(n=>{n.ok&&(window.location.href="/")})}const env=Object.freeze({__lazyXsrfToken:new Lazy(()=>document.getElementsByName("__RequestVerificationToken")[0].value),get xsrfToken(){return this.__lazyXsrfToken.value}}),url=Object.freeze({reject:`/api/annotation/reject`,share:`/api/readonly`});
|
||||
@@ -4,14 +4,14 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Datenschutzinformation für das Fernsignatursystem signFLOW</title>
|
||||
<title>Datenschutzinformation für das Fernsignatursystem: signFLOW</title>
|
||||
<link rel="stylesheet" href="css/privacy-policy.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Datenschutzinformation für das Fernsignatursystem signFLOW</h1>
|
||||
<p><strong>Stand:</strong> 19.09.2024</p>
|
||||
<p><strong>Stand:</strong> 18.11.2025</p>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
@@ -55,7 +55,7 @@
|
||||
<h2>3. Datenerhebung</h2>
|
||||
<h3>3.1 Die folgenden Kategorien personenbezogener Daten werden verarbeitet</h3>
|
||||
<ul>
|
||||
<li>Namen: Vor- und Zunamen sowie Ihre digitale Unterschrift</li>
|
||||
<li>Namen: Benutzername, Vor- und Zunamen sowie Ihre digitale Unterschrift</li>
|
||||
<li>Kontaktdaten: Telefonnummer, Mobilfunknummer und E-Mail-Adresse</li>
|
||||
<li>Technische Daten: IP-Adresse, Zeitpunkt des Zugriffs oder Zugriffsversuchs</li>
|
||||
</ul>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Data Protection Information for the Remote Signature System signFLOW</h1>
|
||||
<p><strong>As of:</strong> 19.09.2024</p>
|
||||
<h1>Data Protection Information for the Remote Signature System: signFLOW</h1>
|
||||
<p><strong>As of:</strong> 18.11.2025</p>
|
||||
</header>
|
||||
<section>
|
||||
<h2>1. General Information</h2>
|
||||
@@ -53,7 +53,7 @@
|
||||
<h2>3. Data Collection</h2>
|
||||
<h3>3.1 The following categories of personal data are processed</h3>
|
||||
<ul>
|
||||
<li>Names: First and last names as well as your digital signature</li>
|
||||
<li>Names: Username, first and last names as well as your digital signature</li>
|
||||
<li>Contact details: Phone number, mobile phone number, and email address</li>
|
||||
<li>Technical data: IP address, time of access, or access attempts</li>
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user