''',
'report_styles': '''@page { size: A4 portrait; } h1 { font-size: 3em; font-weight: bold; }''',
'report_preview_data': {
- 'report': {'title': 'Demo Report', 'field_string': 'test', 'field_int': 5, 'undefined_field': 'test'},
- 'findings': [{'title': 'Demo finding', 'undefined_field': 'test'}]
+ 'report': {'title': 'Demo Report', 'field_string': 'test', 'field_int': 5, 'unknown_field': 'test'},
+ 'findings': [{'title': 'Demo finding', 'unknown_field': 'test'}]
}
} | kwargs)
UploadedAsset.objects.create(linked_object=project_type, name='file1.png', file=SimpleUploadedFile(name='file1.png', content=b'file1'))
@@ -127,11 +148,11 @@ def create_finding(project, template=None, **kwargs) -> PentestFinding:
'cvss': 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H',
'description': 'Finding Description',
'recommendation': 'Finding Recommendation',
- 'undefined_field': 'test',
- } | (template.data if template else {}),
+ 'unknown_field': 'test',
+ } | (template.main_translation.data if template else {}),
definition=project.project_type.finding_fields_obj,
handle_undefined=HandleUndefinedFieldsOptions.FILL_DEFAULT,
- include_undefined=True,
+ include_unknown=True,
) | kwargs.pop('data', {})
finding = PentestFinding.objects.create(**{
'project': project,
@@ -163,7 +184,7 @@ def create_project(project_type=None, members=[], report_data={}, findings_kwarg
} | kwargs)
project.update_data({
'title': 'Report title',
- 'undefined_field': 'test',
+ 'unknown_field': 'test',
} | report_data)
project.save()
diff --git a/api/src/reportcreator_api/tests/test_api.py b/api/src/reportcreator_api/tests/test_api.py
index db8bdff6a..55fece6bf 100644
--- a/api/src/reportcreator_api/tests/test_api.py
+++ b/api/src/reportcreator_api/tests/test_api.py
@@ -7,7 +7,7 @@
from rest_framework.test import APIClient
from reportcreator_api.users.models import AuthIdentity
from reportcreator_api.pentests.models import ProjectType, FindingTemplate, PentestProject, ProjectTypeScope, SourceEnum, \
- UploadedUserNotebookImage, UploadedUserNotebookFile
+ UploadedUserNotebookImage, UploadedUserNotebookFile, Language
from reportcreator_api.notifications.models import NotificationSpec
from reportcreator_api.tests.mock import create_archived_project, create_user, create_project, create_project_type, create_template, create_png_file
from reportcreator_api.archive.import_export import export_project_types, export_projects, export_templates
@@ -174,6 +174,8 @@ def guest_urls():
('usernotebook upload-image-or-file', lambda s, c: c.post(reverse('usernotebookpage-upload-image-or-file', kwargs={'pentestuser_pk': 'self'}), data={'name': 'test.pdf', 'file': ContentFile(name='text.pdf', content=b'text')}, format='multipart')),
*viewset_urls('findingtemplate', get_kwargs=lambda s, detail: {'pk': s.template.pk} if detail else {}, list=True, retrieve=True),
+ *viewset_urls('findingtemplatetranslation', get_kwargs=lambda s, detail: {'template_pk': s.template.pk} | ({'pk': s.template.main_translation.pk} if detail else {}), list=True, retrieve=True),
+ *file_viewset_urls('uploadedtemplateimage', get_obj=lambda s: s.template.images.first(), get_base_kwargs=lambda s: {'template_pk': s.template.pk}, read=True),
('findingtemplate fielddefinition', lambda s, c: c.get(reverse('findingtemplate-fielddefinition'))),
('projecttype create private', lambda s, c: c.post(reverse('projecttype-list'), data=c.get(reverse('projecttype-detail', kwargs={'pk': s.project_type.pk})).data | {'scope': ProjectTypeScope.PRIVATE})),
@@ -209,8 +211,11 @@ def regular_user_urls():
def template_editor_urls():
return {
*viewset_urls('findingtemplate', get_kwargs=lambda s, detail: {'pk': s.template.pk} if detail else {}, create=True, update=True, update_partial=True, destroy=True, lock=True, unlock=True),
+ *viewset_urls('findingtemplatetranslation', get_kwargs=lambda s, detail: {'template_pk': s.template.pk} | ({'pk': s.template.main_translation.pk} if detail else {}), create=True, create_data={'language': Language.GERMAN, 'data': {'title': 'test'}}, update=True, update_partial=True),
+ *file_viewset_urls('uploadedtemplateimage', get_obj=lambda s: s.template.images.first(), get_base_kwargs=lambda s: {'template_pk': s.template.pk}, write=True),
('findingtemplate export', lambda s, c: c.post(reverse('findingtemplate-export', kwargs={'pk': s.template.pk}))),
('findingtemplate import', lambda s, c: c.post(reverse('findingtemplate-import'), data={'file': export_archive(s.template)}, format='multipart')),
+ ('findingtemplate fromfinding', lambda s, c: c.post(reverse('findingtemplate-fromfinding'), data={'project': s.project.id, 'translations': [{'is_main': True, 'data': {'title': 'title'}}]})),
}
diff --git a/api/src/reportcreator_api/tests/test_api2.py b/api/src/reportcreator_api/tests/test_api2.py
index 4d5392a08..250e9caaf 100644
--- a/api/src/reportcreator_api/tests/test_api2.py
+++ b/api/src/reportcreator_api/tests/test_api2.py
@@ -4,8 +4,10 @@
import pytest
from reportcreator_api.archive.import_export.import_export import export_project_types
-from reportcreator_api.pentests.models import ProjectType, ProjectTypeScope, SourceEnum
-from reportcreator_api.tests.mock import create_project, create_project_type, create_user, api_client
+from reportcreator_api.pentests.cvss import CVSSLevel
+from reportcreator_api.pentests.models import ProjectType, ProjectTypeScope, SourceEnum, FindingTemplate, FindingTemplateTranslation, Language, ReviewStatus
+from reportcreator_api.tests.mock import create_project, create_project_type, create_template, create_user, api_client
+from reportcreator_api.tests.utils import assertKeysEqual
@pytest.mark.django_db
@@ -139,3 +141,184 @@ def test_copy_design(self, user, project_type, scope, expected):
assert pt.linked_user == (user if scope == ProjectTypeScope.PRIVATE else None)
assert pt.copy_of == project_type
+
+@pytest.mark.django_db
+class TestTemplateApi:
+ @pytest.fixture(autouse=True)
+ def setUp(self):
+ self.user = create_user(is_template_editor=True)
+ self.client = api_client(self.user)
+ self.template = create_template(language=Language.ENGLISH, translations_kwargs=[{'language': Language.GERMAN}])
+ self.trans_en = self.template.main_translation
+ self.trans_de = self.template.translations.get(language=Language.GERMAN)
+
+ @pytest.mark.parametrize(['translations', 'expected'], [
+ ([], False),
+ ([{'is_main': False}], False),
+ ([{'is_main': True, 'language': Language.ENGLISH}, {'is_main': True, 'language': Language.GERMAN}], False),
+ ([{'is_main': True, 'language': Language.ENGLISH}, {'is_main': False, 'language': Language.ENGLISH}], False),
+ ([{'is_main': True, 'language': Language.ENGLISH}, {'is_main': False, 'language': Language.GERMAN}], True),
+ ])
+ def test_create(self, translations, expected):
+ res = self.client.post(reverse('findingtemplate-list'), data={
+ 'tags': ['test'],
+ 'translations': [{'language': Language.ENGLISH, 'data': {'title': 'test'}} | t for t in translations],
+ })
+ assert (res.status_code == 201) is expected, res.data
+
+ def update_template(self, template, data):
+ res = self.client.put(reverse('findingtemplate-detail', kwargs={'pk': self.template.id}), data)
+ self.template.refresh_from_db()
+ try:
+ self.trans_en.refresh_from_db()
+ except FindingTemplateTranslation.DoesNotExist:
+ self.trans_en = None
+ try:
+ self.trans_de.refresh_from_db()
+ except FindingTemplateTranslation.DoesNotExist:
+ self.trans_de = None
+ return res
+
+ def test_update_swap_languages(self):
+ data = self.client.get(reverse('findingtemplate-detail', kwargs={'pk': self.template.id})).data
+ data['translations'][0]['language'] = Language.GERMAN
+ data['translations'][1]['language'] = Language.ENGLISH
+ assert self.update_template(self.template, data).status_code == 200
+ assert self.trans_en.language == Language.GERMAN.value
+ assert self.trans_de.language == Language.ENGLISH.value
+
+ def test_update_change_main(self):
+ data = self.client.get(reverse('findingtemplate-detail', kwargs={'pk': self.template.id})).data
+ data['translations'][0]['is_main'] = False
+ data['translations'][1]['is_main'] = True
+ assert self.update_template(self.template, data).status_code == 200
+ assert not self.trans_en.is_main
+ assert self.trans_de.is_main
+ assert self.template.main_translation == self.trans_de
+
+ def test_update_add_delete_translations(self):
+ data = self.client.get(reverse('findingtemplate-detail', kwargs={'pk': self.template.id})).data
+ data['translations'][1] = {
+ 'language': Language.SPANISH,
+ 'status': ReviewStatus.IN_PROGRESS,
+ 'data': {
+ 'title': 'Spanish translation',
+ 'cvss': 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N/CR:H',
+ },
+ }
+ assert self.update_template(self.template, data).status_code == 200
+ assert self.template.translations.all().count() == 2
+ trans_es = self.template.translations.get(language=Language.SPANISH)
+ assertKeysEqual(trans_es, data['translations'][1], ['language', 'status', 'data'])
+ assert trans_es.risk_score == 10.0
+ assert trans_es.risk_level == CVSSLevel.CRITICAL.value
+
+ def test_update_delete_main(self):
+ data = self.client.get(reverse('findingtemplate-detail', kwargs={'pk': self.template.id})).data
+ del data['translations'][0]
+ assert self.update_template(self.template, data).status_code == 400
+
+ data['translations'][0]['is_main'] = True
+ assert self.update_template(self.template, data).status_code == 200
+ assert self.template.translations.all().count() == 1
+ assert self.trans_de.is_main
+ assert self.template.main_translation == self.trans_de
+
+ def test_update_multiple_translations(self):
+ data = self.client.get(reverse('findingtemplate-detail', kwargs={'pk': self.template.id})).data
+ data['translations'][0]['data'] = {
+ 'title': 'Updated EN',
+ 'cvss': 'CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N/CR:H',
+ }
+ data['translations'][1]['data'] = {
+ 'title': 'Updated DE',
+ }
+ assert self.update_template(self.template, data).status_code == 200
+ assert self.trans_en.data == data['translations'][0]['data'] | {'unknown_field': 'test'}
+ assert self.trans_de.data == data['translations'][1]['data']
+ # Risk score inherited from main translation if not defined
+ assert self.trans_en.risk_score == self.trans_de.risk_score == 10.0
+ assert self.trans_en.risk_level == self.trans_de.risk_level == CVSSLevel.CRITICAL.value
+
+ def test_delete_main_translation(self):
+ assert self.client.delete(reverse('findingtemplatetranslation-detail', kwargs={'template_pk': self.template.id, 'pk': self.trans_en.id})).status_code == 400
+
+ def test_create_finding_from_template(self):
+ project = create_project(members=[self.user], images_kwargs=[])
+ # Override field from main
+ self.trans_en.update_data({'title': 'title main', 'description': 'description main'})
+ self.trans_en.save()
+ assert self.client.patch(reverse('findingtemplatetranslation-detail', kwargs={'template_pk': self.template.id, 'pk': self.trans_de.id}), {
+ 'data': {
+ 'title': 'title translation',
+ 'description': 'description translation',
+ }
+ }).status_code == 200
+ f1 = self.client.post(reverse('finding-fromtemplate', kwargs={'project_pk': project.id}), data={
+ 'template': self.template.id,
+ 'template_language': self.trans_de.language,
+ })
+ assert f1.status_code == 201
+ assert f1.data['data']['title'] == 'title translation'
+ assert f1.data['data']['description'] == 'description translation'
+
+ # Reset field to main
+ self.client.patch(reverse('findingtemplatetranslation-detail', kwargs={'template_pk': self.template.id, 'pk': self.trans_de.id}), {
+ 'data': {
+ 'title': 'title translation',
+ }
+ })
+ f2 = self.client.post(reverse('finding-fromtemplate', kwargs={'project_pk': project.id}), data={
+ 'template': self.template.id,
+ 'template_language': self.trans_de.language,
+ })
+ assert f2.status_code == 201
+ assert f2.data['data']['title'] == 'title translation'
+ assert f2.data['data']['description'] == 'description main'
+
+ def test_create_finding_from_template_images(self):
+ template = create_template(data={'description': '![image](/images/name/image.png)'}, images_kwargs=[{'name': 'image.png'}])
+ project = create_project(members=[self.user], findings_kwargs=[], images_kwargs=[])
+
+ # Template image copied to project
+ f1 = self.client.post(reverse('finding-fromtemplate', kwargs={'project_pk': project.id}), data={'template': template.id})
+ assert f1.status_code == 201
+ assert project.images.count() == 1
+ img1 = project.images.filter_name('image.png').get()
+ assert img1.file.read() == template.images.first().file.read()
+ assert f1.data['data']['description'] == '![image](/images/name/image.png)'
+
+ # Template image name already exists in project. Image is renamed and copied
+ f2 = self.client.post(reverse('finding-fromtemplate', kwargs={'project_pk': project.id}), data={'template': template.id})
+ assert f2.status_code == 201
+ assert project.images.count() == 2
+ img2 = project.images.order_by('-created').first()
+ assert img2.name != 'image.png'
+ assert img2.file.read() == template.images.first().file.read()
+ assert f2.data['data']['description'] == f'![image](/images/name/{img2.name})'
+
+ def test_create_template_from_finding(self):
+ project = create_project(members=[self.user], findings_kwargs=[{
+ 'data': {
+ 'title': 'finding title',
+ 'description': '![image](/images/name/image.png)',
+ }
+ }], images_kwargs=[{'name': 'image.png'}, {'name': 'image_unreferenced.png'}])
+ finding = project.findings.first()
+
+ res = self.client.post(reverse('findingtemplate-fromfinding'), data={
+ 'project': project.id,
+ 'translations': [{
+ 'is_main': True,
+ 'language': project.language,
+ 'data': finding.data,
+ }],
+ })
+ assert res.status_code == 201, res.data
+ template = FindingTemplate.objects.get(id=res.data['id'])
+ assert template.main_translation.data == finding.data
+ assert template.images.count() == 1
+ img = template.images.first()
+ assert img.name == 'image.png'
+ assert img.file.read() == project.images.filter_name(img.name).get().file.read()
+
diff --git a/api/src/reportcreator_api/tests/test_crypto.py b/api/src/reportcreator_api/tests/test_crypto.py
index 32f4fe561..ead97ea5f 100644
--- a/api/src/reportcreator_api/tests/test_crypto.py
+++ b/api/src/reportcreator_api/tests/test_crypto.py
@@ -197,8 +197,9 @@ def test_plaintext_fallback_disabled_decryption(self):
class TestEncryptedStorage:
@pytest.fixture(autouse=True)
def setUp(self) -> None:
- self.storage_plain = FileSystemStorage(location='/tmp/test/')
- self.storage_crypto = EncryptedFileSystemStorage(location='/tmp/test/')
+ location = f'/tmp/test-{random.randint(1, 10000000)}/'
+ self.storage_plain = FileSystemStorage(location=location)
+ self.storage_crypto = EncryptedFileSystemStorage(location=location)
self.plaintext = b'This is a test file content which should be encrypted'
with override_settings(
@@ -217,8 +218,8 @@ def test_save(self):
assert dec == self.plaintext
def test_open(self):
- with self.storage_crypto.open('test.txt', mode='wb') as f:
- filename = f.name
+ filename = self.storage_crypto.save('test.txt', io.BytesIO(b''))
+ with self.storage_crypto.open(filename, mode='wb') as f:
f.write(self.plaintext)
enc = self.storage_plain.open(filename, mode='rb').read()
@@ -227,10 +228,7 @@ def test_open(self):
assert dec == self.plaintext
def test_size(self):
- with self.storage_crypto.open('test.txt', mode='wb') as f:
- filename = f.name
- f.write(self.plaintext)
-
+ filename = self.storage_crypto.save('test.txt', io.BytesIO(self.plaintext))
assert self.storage_crypto.size(filename) == len(self.storage_crypto.open(filename, mode='rb').read())
@@ -375,6 +373,7 @@ def assert_backup(self, content):
self.assert_backup_obj(backup, self.project.notes.first())
self.assert_backup_obj(backup, self.project_type)
self.assert_backup_obj(backup, self.template)
+ self.assert_backup_obj(backup, self.template.main_translation)
self.assert_backup_obj(backup, self.user.notes.first())
self.assert_backup_obj(backup, self.user.mfa_methods.first())
self.assert_backup_obj(backup, self.archived_project)
@@ -383,6 +382,7 @@ def assert_backup(self, content):
self.assert_backup_file(backup, z, 'uploadedimages', self.project.images.all().first())
self.assert_backup_file(backup, z, 'uploadedimages', self.user.images.all().first())
+ self.assert_backup_file(backup, z, 'uploadedimages', self.template.images.all().first())
self.assert_backup_file(backup, z, 'uploadedassets', self.project_type.assets.all().first())
self.assert_backup_file(backup, z, 'uploadedfiles', self.project.files.first())
self.assert_backup_file(backup, z, 'uploadedfiles', self.user.files.all().first())
diff --git a/api/src/reportcreator_api/tests/test_customfields.py b/api/src/reportcreator_api/tests/test_customfields.py
index 4ded5517e..330c02188 100644
--- a/api/src/reportcreator_api/tests/test_customfields.py
+++ b/api/src/reportcreator_api/tests/test_customfields.py
@@ -8,7 +8,7 @@
from reportcreator_api.pentests.customfields.types import FieldDataType, field_definition_to_dict, parse_field_definition
from reportcreator_api.pentests.customfields.validators import FieldDefinitionValidator, FieldValuesValidator
from reportcreator_api.pentests.customfields.utils import check_definitions_compatible
-from reportcreator_api.pentests.models import FindingTemplate
+from reportcreator_api.pentests.models import FindingTemplate, FindingTemplateTranslation, Language
from reportcreator_api.tests.mock import create_finding, create_project_type, create_project, create_template, create_user
from reportcreator_api.utils.utils import copy_keys
@@ -400,9 +400,6 @@ def setUp(self):
self.project_type_hidden.save()
self.template = create_template(data={'title': 'test', 'field1': 'f1 value', 'field2': 'f2 value'})
-
- with override_settings(CACHES={'default': { 'BACKEND': 'django.core.cache.backends.dummy.DummyCache'}}):
- yield
def test_get_template_field_definition(self):
assert \
@@ -411,13 +408,13 @@ def test_get_template_field_definition(self):
assert FindingTemplate.field_definition['field_conflict'].type == FieldDataType.STRING
def test_delete_field_definition(self):
- old_value = self.template.data['field1']
+ old_value = self.template.main_translation.data['field1']
del self.project_type1.finding_fields['field1']
self.project_type1.save()
- self.template.refresh_from_db()
+ self.template.main_translation.refresh_from_db()
assert 'field1' not in FindingTemplate.field_definition
- assert self.template.data_all['field1'] == old_value
+ assert self.template.main_translation.data_all['field1'] == old_value
def test_change_field_type(self):
self.project_type1.finding_fields |= {'field1': {'type': 'list', 'label': 'changed field type', 'items': {'type': 'string', 'default': 'default'}}}
@@ -425,7 +422,7 @@ def test_change_field_type(self):
self.template.refresh_from_db()
assert FindingTemplate.field_definition['field1'].type == FieldDataType.LIST
- assert self.template.data['field1'] == []
+ assert self.template.main_translation.data['field1'] == []
@pytest.mark.django_db
@@ -484,3 +481,62 @@ def test_move_field_to_other_section(self):
assert self.project.sections.filter(section_id='section1').exists()
assert self.project.sections.get(section_id='section2').data['field1'] == old_value
+
+@pytest.mark.django_db
+class TestTemplateTranslation:
+ @pytest.fixture(autouse=True)
+ def setUp(self):
+ create_project_type() # create dummy project_type to get field_defintions
+ self.template = create_template(language=Language.ENGLISH, data={
+ 'title': 'Title main',
+ 'description': 'Description main',
+ 'recommendation': 'Recommendation main',
+ 'field_list': ['first', 'second'],
+ 'field_unknown': 'unknown',
+ })
+ self.main = self.template.main_translation
+ self.trans = FindingTemplateTranslation.objects.create(template=self.template, language=Language.GERMAN, title='Title translation')
+
+ def test_template_translation_inheritance(self):
+ self.trans.update_data({'title': 'Title translation', 'description': 'Description translation'})
+ self.trans.save()
+
+ data_inherited = self.trans.get_data(inherit_main=True)
+ assert data_inherited['title'] == self.trans.title == 'Title translation'
+ assert data_inherited['description'] == 'Description translation'
+ assert data_inherited['recommendation'] == self.main.data['recommendation'] == 'Recommendation main'
+ assert 'recommendation' not in self.trans.data
+ assert 'field_list' not in self.trans.data
+
+ def test_template_formatting(self):
+ self.trans.custom_fields = {
+ 'recommendation': {'value': 'invalid format'},
+ 'field_list': ['first', {'value': 'invalid format'}],
+ 'field_object': {},
+ }
+ self.trans.save()
+ assert 'description' not in self.trans.data
+ assert self.trans.data['recommendation'] == None
+ assert self.trans.data['field_list'] == ['first', None]
+ assert self.trans.data['field_object'] == {'nested1': None}
+
+ def test_undefined_in_main(self):
+ self.main.custom_fields = {}
+ self.main.save()
+ data_inherited = self.trans.get_data(inherit_main=True)
+ assert 'description' not in data_inherited
+ assert 'field_list' not in data_inherited
+ assert 'field_object' not in data_inherited
+
+ def test_update_data(self):
+ self.main.update_data({
+ 'title': 'new',
+ 'description': 'new',
+ })
+ self.main.save()
+ data_inherited = self.trans.get_data(inherit_main=True)
+ assert data_inherited['title'] != 'new'
+ assert data_inherited['description'] == 'new'
+ assert 'recommendation' not in data_inherited
+ assert data_inherited['field_unknown'] == 'unknown'
+ assert 'field_unknown' not in self.trans.data
diff --git a/api/src/reportcreator_api/tests/test_import_export.py b/api/src/reportcreator_api/tests/test_import_export.py
index 2246d5e63..c2da0e50a 100644
--- a/api/src/reportcreator_api/tests/test_import_export.py
+++ b/api/src/reportcreator_api/tests/test_import_export.py
@@ -1,9 +1,13 @@
+from datetime import datetime
+import json
+import tarfile
import pytest
import io
from django.core.files.base import ContentFile
from django.test import override_settings
from rest_framework.exceptions import ValidationError
-from reportcreator_api.pentests.models import PentestProject, ProjectType, SourceEnum, UploadedAsset, UploadedImage, ProjectMemberRole
+from reportcreator_api.archive.import_export.import_export import build_tarinfo
+from reportcreator_api.pentests.models import PentestProject, ProjectType, SourceEnum, UploadedAsset, UploadedImage, Language
from reportcreator_api.tests.utils import assertKeysEqual
from reportcreator_api.archive.import_export import export_project_types, export_projects, export_templates, import_project_types, import_projects, import_templates
from reportcreator_api.tests.mock import create_notebookpage, create_project, create_project_type, create_template, create_user, create_finding
@@ -13,6 +17,16 @@ def archive_to_file(archive_iterator):
return io.BytesIO(b''.join(archive_iterator))
+def create_archive(archive_data: list[dict]):
+ buffer = io.BytesIO()
+ with tarfile.open(fileobj=buffer, mode='w|gz') as archive:
+ for data in archive_data:
+ data_json = json.dumps(data).encode()
+ archive.addfile(tarinfo=build_tarinfo(name=data['id'] + '.json', size=len(data_json)), fileobj=io.BytesIO(data_json))
+ buffer.seek(0)
+ return buffer
+
+
def members_equal(a, b):
def format_members(m):
return sorted([(m['user'], set(m['roles'])) for m in a.values('user', 'roles')], key=lambda i: i[0])
@@ -25,7 +39,11 @@ class TestImportExport:
@pytest.fixture(autouse=True)
def setUp(self) -> None:
self.user = create_user()
- self.template = create_template()
+ self.template = create_template(
+ language=Language.ENGLISH,
+ translations_kwargs=[
+ {'language': Language.GERMAN, 'data': {'title': 'Template translation', 'description': 'Template description translation'}}
+ ])
self.project_type = create_project_type()
self.project = create_project(
project_type=self.project_type,
@@ -42,17 +60,54 @@ def setUp(self) -> None:
with override_settings(COMPRESS_IMAGES=False):
yield
- def test_export_import_template(self):
+ def test_export_import_template_v2(self):
archive = archive_to_file(export_templates([self.template]))
imported = import_templates(archive)
-
assert len(imported) == 1
t = imported[0]
- assertKeysEqual(t, self.template, ['created', 'language', 'status', 'data', 'data_all'])
+ assert t.created == self.template.created
assert set(t.tags) == set(self.template.tags)
assert t.source == SourceEnum.IMPORTED
+ for i, o in zip(t.translations.all(), self.template.translations.all()):
+ assertKeysEqual(i, o, ['created', 'is_main', 'language', 'status', 'data', 'data_all', 'title', 'custom_fields'])
+ assert t.main_translation in set(t.translations.all())
+
+ assert {(i.name, i.file.read()) for i in t.images.all()} == {(i.name, i.file.read()) for i in self.template.images.all()}
+
+ def test_import_template_v1(self):
+ template_data = {
+ "format": "templates/v1",
+ "id": "674f559c-ca41-4925-a24a-586a8b74c51e",
+ "created": "2023-01-19T18:27:50.592000Z",
+ "updated": "2023-06-29T11:21:42.996947Z",
+ "tags": [
+ "web",
+ "dev",
+ ],
+ "language": "de-DE",
+ "status": "finished",
+ "data": {
+ "title": "Test template",
+ "description": "Test description",
+ },
+ }
+ archive = create_archive([template_data])
+ imported = import_templates(archive)
+
+ assert len(imported) == 1
+ t = imported [0]
+
+ assert set(t.tags) == set(template_data['tags'])
+ assert t.source == SourceEnum.IMPORTED
+
+ assert t.translations.count() == 1
+ assert t.main_translation.template == t
+ assertKeysEqual(t.main_translation, template_data, ['language', 'status'])
+ assert t.main_translation.data == template_data['data']
+ assert t.images.all().count() == 0
+
def test_export_import_project_type(self):
archive = archive_to_file(export_project_types([self.project_type]))
self.project_type.refresh_from_db()
diff --git a/api/src/reportcreator_api/tests/test_locking.py b/api/src/reportcreator_api/tests/test_locking.py
index c30bdfd72..32c1baa7a 100644
--- a/api/src/reportcreator_api/tests/test_locking.py
+++ b/api/src/reportcreator_api/tests/test_locking.py
@@ -1,20 +1,21 @@
import pytest
from django.conf import settings
from django.urls import reverse
-from rest_framework.test import APIClient
-from reportcreator_api.pentests.models import LockStatus
-from reportcreator_api.tests.mock import create_project, create_user, mock_time
+from reportcreator_api.pentests.models import LockStatus, Language
+from reportcreator_api.tests.mock import api_client, create_project, create_project_type, create_template, create_user, mock_time
@pytest.mark.django_db
class TestLocking:
@pytest.fixture(autouse=True)
def setUp(self) -> None:
- self.user1 = create_user()
- self.user2 = create_user()
+ self.user1 = create_user(is_template_editor=True, is_designer=True)
+ self.user2 = create_user(is_template_editor=True, is_designer=True)
self.project = create_project(members=[self.user1, self.user2])
self.finding = self.project.findings.first()
self.section = self.project.sections.first()
+ self.project_type = create_project_type()
+ self.template = create_template()
def test_locking(self):
assert self.finding.lock(user=self.user1) == LockStatus.CREATED
@@ -31,10 +32,8 @@ def test_locking(self):
assert self.finding.lock(user=self.user2) == LockStatus.CREATED
def assert_api_locking(self, obj, url_basename, url_kwargs):
- client_u1 = APIClient()
- client_u1.force_authenticate(user=self.user1)
- client_u2 = APIClient()
- client_u2.force_authenticate(user=self.user2)
+ client_u1 = api_client(self.user1)
+ client_u2 = api_client(self.user2)
# Lock and update
assert client_u1.post(reverse(url_basename + '-lock', kwargs=url_kwargs)).status_code == 201
@@ -65,3 +64,30 @@ def test_api_lock_finding(self):
def test_api_lock_section(self):
self.assert_api_locking(obj=self.section, url_basename='section', url_kwargs={'project_pk': self.project.pk, 'id': self.section.section_id})
+ def test_lock_project_type(self):
+ self.assert_api_locking(obj=self.project_type, url_basename='projecttype', url_kwargs={'pk': self.project_type.id})
+
+ def test_api_lock_template(self):
+ self.assert_api_locking(obj=self.template, url_basename='findingtemplate', url_kwargs={'pk': self.template.id})
+
+ def test_api_lock_template_translation(self):
+ self.template.lock(self.user1)
+
+ client_u1 = api_client(self.user1)
+ assert client_u1.patch(reverse('findingtemplatetranslation-detail', kwargs={'template_pk': self.template.id, 'pk': self.template.main_translation.id}), data={}).status_code == 200
+ res_create = client_u1.post(reverse('findingtemplatetranslation-list', kwargs={'template_pk': self.template.id}), data={
+ 'language': Language.FRENCH,
+ 'data': {'title': 'French template'}
+ })
+ assert res_create.status_code == 201
+
+ client_u2 = api_client(self.user2)
+ assert client_u2.patch(reverse('findingtemplatetranslation-detail', kwargs={'template_pk': self.template.id, 'pk': self.template.main_translation.id}), data={}).status_code == 403
+ assert client_u2.post(reverse('findingtemplatetranslation-list', kwargs={'template_pk': self.template.id}), data={
+ 'language': Language.SPANISH,
+ 'data': {'title': 'Spanish template'},
+ }).status_code == 403
+ assert client_u2.delete(reverse('findingtemplatetranslation-detail', kwargs={'template_pk': self.template.id, 'pk': res_create.data['id']})).status_code == 403
+
+ assert client_u1.delete(reverse('findingtemplatetranslation-detail', kwargs={'template_pk': self.template.id, 'pk': res_create.data['id']})).status_code == 204
+
diff --git a/api/src/reportcreator_api/tests/test_periodic_tasks.py b/api/src/reportcreator_api/tests/test_periodic_tasks.py
index 16f53eb45..5687d95a0 100644
--- a/api/src/reportcreator_api/tests/test_periodic_tasks.py
+++ b/api/src/reportcreator_api/tests/test_periodic_tasks.py
@@ -10,9 +10,9 @@
from reportcreator_api.tasks.models import PeriodicTask, TaskStatus
from reportcreator_api.pentests.models import PentestProject, ArchivedProject
-from reportcreator_api.pentests.tasks import cleanup_project_files, cleanup_usernotebook_files, reset_stale_archive_restores, \
- automatically_archive_projects, automatically_delete_archived_projects
-from reportcreator_api.tests.mock import create_archived_project, create_project, create_user, mock_time
+from reportcreator_api.pentests.tasks import cleanup_project_files, cleanup_usernotebook_files, cleanup_template_files, \
+ reset_stale_archive_restores, automatically_archive_projects, automatically_delete_archived_projects
+from reportcreator_api.tests.mock import create_archived_project, create_project, create_template, create_user, mock_time
def task_success():
@@ -45,7 +45,7 @@ def setUp(self):
def run_tasks(self):
res = APIClient().get(reverse('utils-healthcheck'))
- assert res.status_code == 200
+ assert res.status_code == 200, res.data
def test_initial_run(self):
self.run_tasks()
@@ -129,6 +129,12 @@ def run_cleanup_user_files(self, num_queries, last_success=None):
async_to_sync(cleanup_usernotebook_files)(task_info={
'model': PeriodicTask(last_success=last_success)
})
+
+ def run_cleanup_template_files(self, num_queries, last_success=None):
+ with assertNumQueries(num_queries):
+ async_to_sync(cleanup_template_files)(task_info={
+ 'model': PeriodicTask(last_success=last_success)
+ })
def test_unreferenced_files_removed(self):
with mock_time(before=timedelta(days=10)):
@@ -144,19 +150,25 @@ def test_unreferenced_files_removed(self):
)
user_image = user.images.first()
user_file = user.files.first()
- # self.run_cleanup(num_queries=2 + 6 + 3 * 2 + 3)
+ template = create_template(
+ images_kwargs=[{'name': 'image.png'}],
+ )
+ template_image = template.images.first()
self.run_cleanup_project_files(num_queries=1 + 4 + 2 * 2 + 2 * 1)
self.run_cleanup_user_files(num_queries=1 + 3 + 2 * 2 + 2 * 1)
+ self.run_cleanup_template_files(num_queries=1 + 2 + 1 * 2 + 1 * 1)
# Deleted from DB
assert project.images.count() == 0
assert project.files.count() == 0
assert user.images.count() == 0
assert user.files.count() == 0
+ assert template.images.count() == 0
# Deleted from FS
assert not self.file_exists(project_image)
assert not self.file_exists(project_file)
assert not self.file_exists(user_image)
assert not self.file_exists(user_file)
+ assert not self.file_exists(template_image)
def test_recently_created_unreferenced_files_not_removed(self):
project = create_project(
@@ -166,16 +178,22 @@ def test_recently_created_unreferenced_files_not_removed(self):
user = create_user(
images_kwargs=[{'name': 'image.png'}]
)
+ template = create_template(
+ images_kwargs=[{'name': 'image.png'}]
+ )
self.run_cleanup_project_files(num_queries=1)
self.run_cleanup_user_files(num_queries=1)
+ self.run_cleanup_template_files(num_queries=1)
# DB objects exist
assert project.images.count() == 1
assert project.files.count() == 1
assert user.images.count() == 1
+ assert template.images.count() == 1
# Files exist
assert self.file_exists(project.images.first())
assert self.file_exists(project.files.first())
assert self.file_exists(user.images.first())
+ assert self.file_exists(template.images.first())
def test_referenced_files_in_section_not_removed(self):
with mock_time(before=timedelta(days=10)):
@@ -221,6 +239,15 @@ def test_referenced_files_in_user_notes_not_removed(self):
assert user.images.count() == 1
assert user.files.count() == 1
+ def test_referenced_files_in_templates_not_removed(self):
+ with mock_time(before=timedelta(days=10)):
+ template = create_template(
+ data={'description': '![](/images/name/image.png)'},
+ images_kwargs=[{'name': 'image.png'}]
+ )
+ self.run_cleanup_template_files(num_queries=1 + 2)
+ assert template.images.count() == 1
+
def test_file_referenced_by_multiple_projects(self):
with mock_time(before=timedelta(days=10)):
project_unreferenced = create_project(
@@ -252,30 +279,40 @@ def test_optimized_cleanup(self):
images_kwargs=[{'name': 'image.png'}],
files_kwargs=[{'name': 'file.pdf'}],
)
+ template_old = create_template(
+ images_kwargs=[{'name': 'image.png'}],
+ )
project_new = create_project(
images_kwargs=[{'name': 'image.png'}],
- files_kwargs=[{'name': 'file.pdf'}]
+ files_kwargs=[{'name': 'file.pdf'}],
)
user_new = create_user(
images_kwargs=[{'name': 'image.png'}],
)
+ template_new = create_template(
+ images_kwargs=[{'name': 'image.png'}],
+ )
with mock_time(before=timedelta(days=10)):
project_new.save()
user_new.notes.first().save()
+ template_new.save()
last_task_run = timezone.now() - timedelta(days=15)
self.run_cleanup_project_files(num_queries=1 + 4 + 2 * 2 + 2 * 1, last_success=last_task_run)
self.run_cleanup_user_files(num_queries=1 + 3 + 2 * 2 + 2 * 1, last_success=last_task_run)
+ self.run_cleanup_template_files(num_queries=1 + 2 + 1 * 2 + 1 * 1, last_success=last_task_run)
# Old project should be ignored because it was already cleaned in the last run
assert project_old.images.count() == 1
assert project_old.files.count() == 1
assert user_old.images.count() == 1
assert user_old.files.count() == 1
+ assert template_old.images.count() == 1
# New project should be cleaned because it was modified after the last run
assert project_new.images.count() == 0
assert project_new.files.count() == 0
assert user_new.images.count() == 0
assert user_new.files.count() == 0
+ assert template_new.images.count() == 0
@pytest.mark.django_db
diff --git a/api/src/reportcreator_api/users/models.py b/api/src/reportcreator_api/users/models.py
index 40e82f45c..38475fee4 100644
--- a/api/src/reportcreator_api/users/models.py
+++ b/api/src/reportcreator_api/users/models.py
@@ -62,7 +62,7 @@ def scope(self) -> list[str]:
@property
def can_login_local(self) -> bool:
- return settings.LOCAL_USER_AUTH_ENABLED and self.password and self.has_usable_password()
+ return (settings.LOCAL_USER_AUTH_ENABLED or not license.is_professional()) and self.password and self.has_usable_password()
@functools.cached_property
def can_login_sso(self) -> bool:
diff --git a/api/src/reportcreator_api/utils/api.py b/api/src/reportcreator_api/utils/api.py
index 89a970568..6fd152b07 100644
--- a/api/src/reportcreator_api/utils/api.py
+++ b/api/src/reportcreator_api/utils/api.py
@@ -1,9 +1,14 @@
+from functools import reduce
+import json
+import operator
+from types import NoneType
from asgiref.sync import sync_to_async
from django.conf import settings
+from django.db.models import Q
from django.http import StreamingHttpResponse, FileResponse, Http404
from django.core.exceptions import PermissionDenied
from adrf.views import APIView as AsyncAPIView
-from rest_framework import exceptions, views, generics
+from rest_framework import exceptions, views, generics, pagination
from rest_framework.response import Response
from reportcreator_api.utils import license
@@ -96,6 +101,126 @@ def _set_streaming_content(self, value):
super()._set_streaming_content(sync_iterable_to_async((value)))
+class CursorMultiPagination(pagination.CursorPagination):
+ """
+ Cursor pagination that supports ordering by multiple fields.
+ Uses all order_by fields in the cursor, not just the first one.
+ """
+
+ def get_ordering(self, request, queryset, view):
+ return queryset.query.order_by if queryset.ordered else self.ordering
+
+ def _get_position_from_instance(self, instance, ordering):
+ out = []
+ for field_name in ordering:
+ field_value = instance
+ for k in field_name.lstrip('-').split('__'):
+ if isinstance(instance, dict):
+ field_value = field_value[k]
+ else:
+ field_value = getattr(field_value, k)
+ out.append(field_value if isinstance(field_value, (str, bool, int, float, NoneType)) else str(field_value))
+ return json.dumps(out)
+
+ def paginate_queryset(self, queryset, request, view=None):
+ self.page_size = self.get_page_size(request)
+ if not self.page_size:
+ return None
+
+ self.base_url = request.build_absolute_uri()
+ self.ordering = self.get_ordering(request, queryset, view)
+
+ self.cursor = self.decode_cursor(request)
+ if self.cursor is None:
+ (offset, reverse, current_position) = (0, False, None)
+ else:
+ (offset, reverse, current_position) = self.cursor
+
+ # Cursor pagination always enforces an ordering.
+ if reverse:
+ queryset = queryset.order_by(*pagination._reverse_ordering(self.ordering))
+ else:
+ queryset = queryset.order_by(*self.ordering)
+
+ # If we have a cursor with a fixed position then filter by that.
+ if current_position is not None:
+ current_position_list = json.loads(current_position)
+
+ q_objects_equals = {}
+ q_objects_compare = {}
+
+ for order, position in zip(self.ordering, current_position_list):
+ is_reversed = order.startswith("-")
+ order_attr = order.lstrip("-")
+
+ q_objects_equals[order] = Q(**{order_attr: position})
+
+ # Test for: (cursor reversed) XOR (queryset reversed)
+ if self.cursor.reverse != is_reversed:
+ q_objects_compare[order] = Q(
+ **{(order_attr + "__lt"): position}
+ )
+ else:
+ q_objects_compare[order] = Q(
+ **{(order_attr + "__gt"): position}
+ )
+
+ filter_list = []
+ # starting with the second field
+ for i in range(len(self.ordering)):
+ # The first operands need to be equals
+ # the last operands need to be gt
+ equals = list(self.ordering[:i+1])
+ greater_than_q = q_objects_compare[equals.pop()]
+ sub_filters = [q_objects_equals[e] for e in equals]
+ sub_filters.append(greater_than_q)
+ filter_list.append(reduce(operator.and_, sub_filters))
+
+ queryset = queryset.filter(reduce(operator.or_, filter_list))
+
+ # If we have an offset cursor then offset the entire page by that amount.
+ # We also always fetch an extra item in order to determine if there is a
+ # page following on from this one.
+ results = list(queryset[offset:offset + self.page_size + 1])
+ self.page = list(results[:self.page_size])
+
+ # Determine the position of the final item following the page.
+ if len(results) > len(self.page):
+ has_following_position = True
+ following_position = self._get_position_from_instance(results[-1], self.ordering)
+ else:
+ has_following_position = False
+ following_position = None
+
+ if reverse:
+ # If we have a reverse queryset, then the query ordering was in reverse
+ # so we need to reverse the items again before returning them to the user.
+ self.page = list(reversed(self.page))
+
+ # Determine next and previous positions for reverse cursors.
+ self.has_next = (current_position is not None) or (offset > 0)
+ self.has_previous = has_following_position
+ if self.has_next:
+ self.next_position = current_position
+ if self.has_previous:
+ self.previous_position = following_position
+ else:
+ # Determine next and previous positions for forward cursors.
+ self.has_next = has_following_position
+ self.has_previous = (current_position is not None) or (offset > 0)
+ if self.has_next:
+ self.next_position = following_position
+ if self.has_previous:
+ self.previous_position = current_position
+
+ # Display page controls in the browsable API if there is more
+ # than one page.
+ if (self.has_previous or self.has_next) and self.template is not None:
+ self.display_page_controls = True
+
+ return self.page
+
+
def exception_handler(exc, context):
"""
Returns the response that should be used for any given exception.
diff --git a/deploy/.env b/deploy/.env
index e18cd5665..74678e0b4 100644
--- a/deploy/.env
+++ b/deploy/.env
@@ -1 +1 @@
-SYSREPTOR_VERSION=0.102
+SYSREPTOR_VERSION=0.110
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100755
index 000000000..47c0dcbed
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+site
+__pycache__
+.cache
\ No newline at end of file
diff --git a/docs/LICENSE b/docs/LICENSE
new file mode 100644
index 000000000..7d4f96c54
--- /dev/null
+++ b/docs/LICENSE
@@ -0,0 +1,427 @@
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
diff --git a/docs/README.md b/docs/README.md
new file mode 100755
index 000000000..ab769369b
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,36 @@
+# How to build and deploy the docs manually
+## Getting started
+
+```
+pip3 install -r requirements.txt
+
+# Local deployment
+cd docs
+mkdocs serve
+
+# Manually compiling report software list
+python3 -c 'from hooks import *; generate_software_lists()'
+```
+
+## Build
+
+```
+# Build docs
+cd docs/
+python3 -c 'from hooks import *; generate_software_lists()'
+mkdocs build
+```
+
+## Deploy
+
+```
+git clone https://github.com/Syslifters/sysreptor-docs.git ghpages
+rm -rf ghpages/*
+cp -r docs/sites/* ghpages/
+
+# Stash history and deploy to GitHub
+git add .
+git commit -m "INIT"
+git reset $(git commit-tree HEAD^{tree} -m "INIT")
+git push --force
+```
diff --git a/docs/docs/CNAME b/docs/docs/CNAME
new file mode 100755
index 000000000..7b5e1710f
--- /dev/null
+++ b/docs/docs/CNAME
@@ -0,0 +1 @@
+docs.sysreptor.com
diff --git a/docs/docs/assets/demo-designs.tar.gz b/docs/docs/assets/demo-designs.tar.gz
new file mode 100644
index 000000000..0b11613ae
Binary files /dev/null and b/docs/docs/assets/demo-designs.tar.gz differ
diff --git a/docs/docs/assets/demo-projects.tar.gz b/docs/docs/assets/demo-projects.tar.gz
new file mode 100644
index 000000000..c09396954
Binary files /dev/null and b/docs/docs/assets/demo-projects.tar.gz differ
diff --git a/docs/docs/assets/demo-templates.tar.gz b/docs/docs/assets/demo-templates.tar.gz
new file mode 100644
index 000000000..c91c33cab
Binary files /dev/null and b/docs/docs/assets/demo-templates.tar.gz differ
diff --git a/docs/docs/assets/emojis/bicycle.svg b/docs/docs/assets/emojis/bicycle.svg
new file mode 100644
index 000000000..11b8d40c3
--- /dev/null
+++ b/docs/docs/assets/emojis/bicycle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/docs/assets/emojis/flexed-biceps.svg b/docs/docs/assets/emojis/flexed-biceps.svg
new file mode 100644
index 000000000..8a9e5c16b
--- /dev/null
+++ b/docs/docs/assets/emojis/flexed-biceps.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/docs/assets/emojis/lemon.svg b/docs/docs/assets/emojis/lemon.svg
new file mode 100644
index 000000000..e09f43ba7
--- /dev/null
+++ b/docs/docs/assets/emojis/lemon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/docs/assets/emojis/pie.svg b/docs/docs/assets/emojis/pie.svg
new file mode 100644
index 000000000..17580129c
--- /dev/null
+++ b/docs/docs/assets/emojis/pie.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/docs/assets/emojis/red-heart-noto.svg b/docs/docs/assets/emojis/red-heart-noto.svg
new file mode 100644
index 000000000..a47d2ac25
--- /dev/null
+++ b/docs/docs/assets/emojis/red-heart-noto.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/docs/assets/emojis/red-heart.svg b/docs/docs/assets/emojis/red-heart.svg
new file mode 100644
index 000000000..c59c58d85
--- /dev/null
+++ b/docs/docs/assets/emojis/red-heart.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/docs/assets/emojis/wood.svg b/docs/docs/assets/emojis/wood.svg
new file mode 100644
index 000000000..9ffb30317
--- /dev/null
+++ b/docs/docs/assets/emojis/wood.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-100.eot
new file mode 100755
index 000000000..ea5c4d0d0
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-100.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-100.svg
new file mode 100755
index 000000000..7bf69634b
--- /dev/null
+++ b/docs/docs/assets/fonts/exo/exo-v20-latin-100.svg
@@ -0,0 +1,332 @@
+
+
+
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-100.ttf
new file mode 100755
index 000000000..e41d6c0cb
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-100.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-100.woff
new file mode 100755
index 000000000..0b9a22d3d
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-100.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-100.woff2
new file mode 100755
index 000000000..8c2d09dc8
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-100.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.eot
new file mode 100755
index 000000000..8c9ef5ed9
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.ttf
new file mode 100755
index 000000000..579648e98
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.woff
new file mode 100755
index 000000000..f9f84f31e
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.woff2
new file mode 100755
index 000000000..bb609e091
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-100italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-200.eot
new file mode 100755
index 000000000..14b6cc0a6
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-200.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-200.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-200.ttf
new file mode 100755
index 000000000..2f97ff055
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-200.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-200.woff
new file mode 100755
index 000000000..9afc210a6
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-200.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-200.woff2
new file mode 100755
index 000000000..c19e5411c
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-200.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.eot
new file mode 100755
index 000000000..0dec08613
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.ttf
new file mode 100755
index 000000000..934aef3ca
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.woff
new file mode 100755
index 000000000..4f3a254f1
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.woff2
new file mode 100755
index 000000000..ca12e13e9
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-200italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-300.eot
new file mode 100755
index 000000000..f50251f20
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-300.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-300.svg
new file mode 100755
index 000000000..a526d2ce3
--- /dev/null
+++ b/docs/docs/assets/fonts/exo/exo-v20-latin-300.svg
@@ -0,0 +1,331 @@
+
+
+
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-300.ttf
new file mode 100755
index 000000000..3b9ac02c4
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-300.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-300.woff
new file mode 100755
index 000000000..e13a754a5
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-300.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-300.woff2
new file mode 100755
index 000000000..e3037d3dc
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-300.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.eot
new file mode 100755
index 000000000..ae570b412
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.ttf
new file mode 100755
index 000000000..6aedcae24
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.woff
new file mode 100755
index 000000000..1e60e92eb
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.woff2
new file mode 100755
index 000000000..350edc6be
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-300italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-500.eot
new file mode 100755
index 000000000..363f4fc33
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-500.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-500.svg
new file mode 100755
index 000000000..88a0cbf54
--- /dev/null
+++ b/docs/docs/assets/fonts/exo/exo-v20-latin-500.svg
@@ -0,0 +1,332 @@
+
+
+
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-500.ttf
new file mode 100755
index 000000000..373bdecc6
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-500.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-500.woff
new file mode 100755
index 000000000..82729fe04
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-500.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-500.woff2
new file mode 100755
index 000000000..16d3b56a1
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-500.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.eot
new file mode 100755
index 000000000..335f47fb4
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.ttf
new file mode 100755
index 000000000..f2e21cf23
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.woff
new file mode 100755
index 000000000..ec53b16fb
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.woff2
new file mode 100755
index 000000000..dd86d5651
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-500italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-600.eot
new file mode 100755
index 000000000..177e17c82
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-600.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-600.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-600.ttf
new file mode 100755
index 000000000..8d9e5e6d7
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-600.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-600.woff
new file mode 100755
index 000000000..dc6cc6de7
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-600.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-600.woff2
new file mode 100755
index 000000000..1527dca6f
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-600.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.eot
new file mode 100755
index 000000000..4cc4fb8bf
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.ttf
new file mode 100755
index 000000000..9861f3631
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.woff
new file mode 100755
index 000000000..e74917da2
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.woff2
new file mode 100755
index 000000000..b87577d6b
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-600italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-700.eot
new file mode 100755
index 000000000..84a8b5d40
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-700.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-700.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-700.ttf
new file mode 100755
index 000000000..36e60a865
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-700.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-700.woff
new file mode 100755
index 000000000..54ac1e0bf
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-700.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-700.woff2
new file mode 100755
index 000000000..275ce6891
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-700.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.eot
new file mode 100755
index 000000000..082d0ae6d
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.ttf
new file mode 100755
index 000000000..811ba83e4
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.woff
new file mode 100755
index 000000000..1672127f5
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.woff2
new file mode 100755
index 000000000..61f8408aa
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-700italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-800.eot
new file mode 100755
index 000000000..c30db6088
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-800.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-800.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-800.ttf
new file mode 100755
index 000000000..7a67cd33b
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-800.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-800.woff
new file mode 100755
index 000000000..047ae9a75
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-800.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-800.woff2
new file mode 100755
index 000000000..6cb44f82f
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-800.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.eot
new file mode 100755
index 000000000..220ee2547
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.ttf
new file mode 100755
index 000000000..46f0fc35e
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.woff
new file mode 100755
index 000000000..e960e4fe0
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.woff2
new file mode 100755
index 000000000..9eefec631
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-800italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-900.eot
new file mode 100755
index 000000000..4d57a6c6c
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-900.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-900.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-900.ttf
new file mode 100755
index 000000000..56ea9c364
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-900.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-900.woff
new file mode 100755
index 000000000..e7ccdeab6
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-900.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-900.woff2
new file mode 100755
index 000000000..80b2b52d2
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-900.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.eot
new file mode 100755
index 000000000..993d65cb4
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.ttf
new file mode 100755
index 000000000..1acde241e
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.woff
new file mode 100755
index 000000000..0b42f69f5
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.woff2
new file mode 100755
index 000000000..f8deff606
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-900italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-italic.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.eot
new file mode 100755
index 000000000..5b459add7
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-italic.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-italic.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.ttf
new file mode 100755
index 000000000..e6727982b
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-italic.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.woff
new file mode 100755
index 000000000..75ec87ec9
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-italic.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.woff2
new file mode 100755
index 000000000..aef929913
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-italic.woff2 differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-regular.eot b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.eot
new file mode 100755
index 000000000..3e20fb9dc
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.eot differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-regular.svg b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.svg
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-regular.ttf b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.ttf
new file mode 100755
index 000000000..e9c70cd7d
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.ttf differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-regular.woff b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.woff
new file mode 100755
index 000000000..ce6c1dafe
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.woff differ
diff --git a/docs/docs/assets/fonts/exo/exo-v20-latin-regular.woff2 b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.woff2
new file mode 100755
index 000000000..0b81af6d2
Binary files /dev/null and b/docs/docs/assets/fonts/exo/exo-v20-latin-regular.woff2 differ
diff --git a/docs/docs/assets/offsec-designs.tar.gz b/docs/docs/assets/offsec-designs.tar.gz
new file mode 100644
index 000000000..55eb8e396
Binary files /dev/null and b/docs/docs/assets/offsec-designs.tar.gz differ
diff --git a/docs/docs/assets/reports/HTB-CPTS-Report-Preview.png b/docs/docs/assets/reports/HTB-CPTS-Report-Preview.png
new file mode 100644
index 000000000..ed738d7ac
Binary files /dev/null and b/docs/docs/assets/reports/HTB-CPTS-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/HTB-CPTS-Report.pdf b/docs/docs/assets/reports/HTB-CPTS-Report.pdf
new file mode 100644
index 000000000..616bf236b
Binary files /dev/null and b/docs/docs/assets/reports/HTB-CPTS-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSCP-Exam-Report-Preview.png b/docs/docs/assets/reports/OSCP-Exam-Report-Preview.png
new file mode 100755
index 000000000..48e879afe
Binary files /dev/null and b/docs/docs/assets/reports/OSCP-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSCP-Exam-Report.pdf b/docs/docs/assets/reports/OSCP-Exam-Report.pdf
new file mode 100755
index 000000000..2af5e471c
Binary files /dev/null and b/docs/docs/assets/reports/OSCP-Exam-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSCP-Lab-Report-Preview.png b/docs/docs/assets/reports/OSCP-Lab-Report-Preview.png
new file mode 100755
index 000000000..646046c9d
Binary files /dev/null and b/docs/docs/assets/reports/OSCP-Lab-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSCP-Lab-Report.pdf b/docs/docs/assets/reports/OSCP-Lab-Report.pdf
new file mode 100755
index 000000000..7f7397580
Binary files /dev/null and b/docs/docs/assets/reports/OSCP-Lab-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSDA-Exam-Report-Preview.png b/docs/docs/assets/reports/OSDA-Exam-Report-Preview.png
new file mode 100644
index 000000000..5c59d89f4
Binary files /dev/null and b/docs/docs/assets/reports/OSDA-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSDA-Exam-Report.pdf b/docs/docs/assets/reports/OSDA-Exam-Report.pdf
new file mode 100644
index 000000000..59d2963f1
Binary files /dev/null and b/docs/docs/assets/reports/OSDA-Exam-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSED-Exam-Report-Preview.png b/docs/docs/assets/reports/OSED-Exam-Report-Preview.png
new file mode 100644
index 000000000..3e75c392c
Binary files /dev/null and b/docs/docs/assets/reports/OSED-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSED-Exam-Report.pdf b/docs/docs/assets/reports/OSED-Exam-Report.pdf
new file mode 100644
index 000000000..4069c3ba3
Binary files /dev/null and b/docs/docs/assets/reports/OSED-Exam-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSEE-Exam-Report-Preview.png b/docs/docs/assets/reports/OSEE-Exam-Report-Preview.png
new file mode 100755
index 000000000..3ff8795a8
Binary files /dev/null and b/docs/docs/assets/reports/OSEE-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSEE-Exam-Report.pdf b/docs/docs/assets/reports/OSEE-Exam-Report.pdf
new file mode 100755
index 000000000..f46e41bfb
Binary files /dev/null and b/docs/docs/assets/reports/OSEE-Exam-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSEP-Exam-Report-Preview.png b/docs/docs/assets/reports/OSEP-Exam-Report-Preview.png
new file mode 100755
index 000000000..7b06f42ae
Binary files /dev/null and b/docs/docs/assets/reports/OSEP-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSEP-Exam-Report.pdf b/docs/docs/assets/reports/OSEP-Exam-Report.pdf
new file mode 100755
index 000000000..766251357
Binary files /dev/null and b/docs/docs/assets/reports/OSEP-Exam-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSMR-Exam-Report-Preview.png b/docs/docs/assets/reports/OSMR-Exam-Report-Preview.png
new file mode 100644
index 000000000..d4b6b655c
Binary files /dev/null and b/docs/docs/assets/reports/OSMR-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSMR-Exam-Report.pdf b/docs/docs/assets/reports/OSMR-Exam-Report.pdf
new file mode 100644
index 000000000..560167dac
Binary files /dev/null and b/docs/docs/assets/reports/OSMR-Exam-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSWA-Exam-Report-Preview.png b/docs/docs/assets/reports/OSWA-Exam-Report-Preview.png
new file mode 100644
index 000000000..4051983a8
Binary files /dev/null and b/docs/docs/assets/reports/OSWA-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSWA-Exam-Report.pdf b/docs/docs/assets/reports/OSWA-Exam-Report.pdf
new file mode 100644
index 000000000..1b86567e4
Binary files /dev/null and b/docs/docs/assets/reports/OSWA-Exam-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSWE-Exam-Report-Preview.png b/docs/docs/assets/reports/OSWE-Exam-Report-Preview.png
new file mode 100755
index 000000000..ac47cf3e8
Binary files /dev/null and b/docs/docs/assets/reports/OSWE-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSWE-Exam-Report.pdf b/docs/docs/assets/reports/OSWE-Exam-Report.pdf
new file mode 100755
index 000000000..968df55aa
Binary files /dev/null and b/docs/docs/assets/reports/OSWE-Exam-Report.pdf differ
diff --git a/docs/docs/assets/reports/OSWP-Exam-Report-Preview.png b/docs/docs/assets/reports/OSWP-Exam-Report-Preview.png
new file mode 100755
index 000000000..6590f427b
Binary files /dev/null and b/docs/docs/assets/reports/OSWP-Exam-Report-Preview.png differ
diff --git a/docs/docs/assets/reports/OSWP-Exam-Report.pdf b/docs/docs/assets/reports/OSWP-Exam-Report.pdf
new file mode 100755
index 000000000..857188a73
Binary files /dev/null and b/docs/docs/assets/reports/OSWP-Exam-Report.pdf differ
diff --git a/docs/docs/data-privacy.md b/docs/docs/data-privacy.md
new file mode 100755
index 000000000..44e6884c4
--- /dev/null
+++ b/docs/docs/data-privacy.md
@@ -0,0 +1,30 @@
+# Datenschutzerklärung
+
+Der Schutz deiner persönlichen Daten ist uns bei Syslifters ein besonderes Anliegen. Wir verarbeiten deine Daten daher ausschließlich auf Grundlage der gesetzlichen Bestimmungen (DSGVO, TKG 2003). In diesen Datenschutzinformationen informieren wir dich über die wichtigsten Aspekte der Datenverarbeitung im Rahmen unserer Website.
+
+Diese Datenschutzerklärung gilt für docs.syslifters.com und ist ab 1. Juni 2022 gültig.
+
+## Hosting
+
+Wir hosten die Website bei GitHub. Anbieter ist die GitHub B.V., Prins Bernhardplein 200, Amsterdam, 1097JB, Niederlande (nachfolgend: GitHub). Wenn du die Website besuchst, erfasst GitHub verschiedene Logfiles inklusive deiner IP-Adressen.
+
+Details entnehme bitte der [Datenschutzerklärung von GitHub](https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement).
+
+Die Verwendung von GitHub erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Wir haben ein berechtigtes Interesse an einer möglichst zuverlässigen Darstellung unserer Website. Sofern eine entsprechende Einwilligung abgefragt wurde, erfolgt die Verarbeitung ausschließlich auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO, soweit die Einwilligung die Speicherung von Cookies oder den Zugriff auf Informationen im Endgerät des Nutzers (z. B. Device-Fingerprinting) umfasst. Die Einwilligung ist jederzeit widerrufbar.
+
+## Deine Rechte
+
+Dir stehen grundsätzlich die Rechte auf Auskunft, Berichtigung, Löschung, Einschränkung, Datenübertragbarkeit, Widerruf und Widerspruch zu. Wenn du glaubst, dass die Verarbeitung deiner Daten gegen das Datenschutzrecht verstößt oder deiner datenschutzrechtlichen Ansprüche sonst in einer Weise verletzt worden sind, hast du das Recht eine Beschwerde bei der Aufsichtsbehörde einzureichen. In Österreich ist das die Datenschutzbehörde, E-Mail: dsb@dsb.gv.at, Web: https://www.dsb.gv.at/.
+
+## Änderungen dieser Datenschutzerklärung
+
+Wir behalten uns das Recht vor, unsere Datenschutzerklärung bei Bedarf zu ändern, falls dies zum Beispiel aufgrund technischer Entwicklungen oder rechtlicher Änderungen notwendig sein sollte. Die jeweils aktuellste Version der Datenschutzerklärung findest du auf unserer Webseite veröffentlicht. Bitte stelle sicher, dass dir die aktuellste Version vorliegt.
+
+Du erreichst uns unter folgenden Kontaktdaten:
+
+Syslifters GmbH
+Eitzersthal 75, 2013 Göllersdorf
+E-Mail: hello@syslifters.com
+Telefon: +43 660 923 40 60
+
+**Stand:** 01. Juni 2022
\ No newline at end of file
diff --git a/docs/docs/designer/charts.md b/docs/docs/designer/charts.md
new file mode 100755
index 000000000..155dd6db2
--- /dev/null
+++ b/docs/docs/designer/charts.md
@@ -0,0 +1,42 @@
+# Charts
+Charts can be embedded into reports with a `` component.
+The `` component uses [ChartJS](https://www.chartjs.org/docs/latest/){ target=_blank } for rendering charts.
+The resulting chart is embedded as an image in the PDF.
+
+How the chart looks like is specified by the `:config` argument. This argument accepts a [ChartJS config object](https://www.chartjs.org/docs/latest/configuration/){ target=_blank }.
+You can configure different chart types (e.g. pie chart, bar chart, line chart, etc.), chart styling, labels.
+The `config` also takes the datasets to be rendered in the `data` property.
+
+You can use all available ChartJS configuration options (except animations, since they are not possible in PDFs) to customize charts for your needs.
+
+Other options:
+
+* `width`: Width of the chart in centimeter
+* `height`: Height of the chart in centimeter
+
+## Example: Bar Chart of vulnerability risks
+The following charts shows the number of vulnerabilities for each risk level (none, low, medium, high, critical) in a bar chart.
+Each risk level bar has a different color.
+
+```html
+
+```
diff --git a/docs/docs/designer/debugging.md b/docs/docs/designer/debugging.md
new file mode 100755
index 000000000..1edfff20a
--- /dev/null
+++ b/docs/docs/designer/debugging.md
@@ -0,0 +1,25 @@
+# Debugging
+## Template Data Debugging
+JSON data of reports is available in templates for rendering.
+The structure of this data depends on your defined report and finding fields, i.e. it may be different for each Design.
+
+You can view the current data structure by dumping it in the PDF.
+
+```html linenums="1"
+
All available data
+
{{ data }}
+
+
Report
+
{{ report }}
+
+
Findings
+
{{ findings }}
+```
+
+## CSS Debugging
+There is no way to interactively debug CSS rules.
+The PDFs are rendered statically and returned as a file.
+There exists no interactive CSS editor like dev tools console in browsers.
+
+However, you can set background colors or borders on elements to see where they are positioned and how big they are.
+
diff --git a/docs/docs/designer/design-guides.md b/docs/docs/designer/design-guides.md
new file mode 100755
index 000000000..6342a05ee
--- /dev/null
+++ b/docs/docs/designer/design-guides.md
@@ -0,0 +1,128 @@
+# Design Guides
+We provide many useful default styles in our `base.css`. You can import them to your report's CSS using:
+
+```css
+@import "/assets/global/base.css"
+```
+
+If you want to customize the styles (like fonts, code blocks, etc.), have a look at the following chapters.
+
+!!! tip "Use the following snippets as a guide how to override the base styles."
+
+ You do not need them, if you imported the base styles and don't need further customization.
+
+
+## Headings
+```css linenums="1"
+/* Avoid page breaks in headlines */
+h1, h2, h3, h4, h5, h6 {
+ break-inside: avoid;
+ break-after: avoid;
+}
+```
+
+## Code
+* `code`: code block and inline code
+* `pre code`: code block
+* `.code-block`: code block rendered from markdown
+* `.code-inline`: inline code rendered from markdown
+
+```css linenums="1"
+pre code {
+ display: block !important;
+ border: 1px solid black;
+ padding: 0.2em;
+}
+code {
+ background-color: whitesmoke;
+}
+
+/* Allow line wrapping in code blocks: prevent code from overflowing page */
+pre {
+ white-space: pre-wrap;
+}
+```
+
+## Prevent page overflow of long texts
+```css linenums="1"
+html {
+ overflow-wrap: break-word;
+}
+```
+
+## Justified texts
+```css linenums="1"
+p {
+ text-align: justify;
+ text-align-last: start;
+}
+```
+
+## Lists
+Style list marker separately with `::marker`
+```css linenums="1"
+li::marker {
+ color: red;
+}
+```
+
+## Fonts
+Fonts can be used in elements with the CSS rule `font-family`.
+
+Following example uses two fonts for the document:
+`Roboto` for regular text (set for the whole `html` document) and
+the monospace font `Source Code Pro` for `code` blocks.
+
+```css linenums="1"
+html {
+ font-family: "Noto Sans", sans-serif;
+ font-size: 10pt;
+}
+
+code {
+ font-family: "Noto Sans Mono", monospace;
+}
+```
+
+We provide a range of fonts ready to use. Following fonts are available:
+
+* [Noto Sans](https://fonts.google.com/noto/specimen/Noto+Sans){target=_blank}
+* [Noto Serif](https://fonts.google.com/noto/specimen/Noto+Serif){target=_blank}
+* [Open Sans](https://fonts.google.com/specimen/Open+Sans){target=_blank} - similar to Arial
+* [Roboto Flex](https://fonts.google.com/specimen/Roboto+Flex){target=_blank}
+* [Roboto Serif](https://fonts.google.com/specimen/Roboto+Serif){target=_blank}
+* [STIX Two Text](https://fonts.google.com/specimen/STIX+Two+Text){target=_blank} - similar to Times New Roman
+* [Arimo](https://fonts.google.com/specimen/Arimo){target=_blank} - similar to Verdana
+* [Exo](https://fonts.google.com/specimen/Exo){target=_blank}
+* ~~[Lato](https://fonts.google.com/specimen/Lato){target=_blank}~~*
+* ~~[Roboto](https://fonts.google.com/specimen/Roboto){target=_blank}~~*
+* ~~[Tinos](https://fonts.google.com/specimen/Tinos){target=_blank}~~*
+
+
+Monospace fonts (for code blocks):
+
+* [Roboto Mono](https://fonts.google.com/specimen/Roboto+Mono){target=_blank}
+* [Noto Sans Mono](https://fonts.google.com/noto/specimen/Noto+Sans+Mono){target=_blank}
+* [Source Code Pro](https://fonts.google.com/specimen/Source+Sans+Pro){target=_blank}
+* [Red Hat Mono](https://fonts.google.com/specimen/Red+Hat+Mono){target=_blank}
+* ~~[Courier Prime](https://fonts.google.com/specimen/Courier+Prime){target=_blank}~~*
+
+*Deprecated, replaced by similar-looking fonts
+
+### Custom Fonts
+Custom fonts can be added with CSS `@font-face` rules.
+Requests to external systems are blocked.
+Therefore you have to upload font files as assets and include them with their relative asset URL starting with `/asset/name/`.
+
+For google fonts you can generate the font files with this tool: https://google-webfonts-helper.herokuapp.com/fonts/
+This generates all CSS rules and provides font files for download.
+
+It is possible to upload the `@font-face` CSS rules in a separate file and include it in the main stylesheet with their asset URLs.
+
+```css
+@font-face {
+ font-family: 'Roboto';
+ font-weight: 400;
+ src: url('/assets/name/roboto-regular.woff2')
+}
+```
diff --git a/docs/docs/designer/designer.md b/docs/docs/designer/designer.md
new file mode 100644
index 000000000..13768175b
--- /dev/null
+++ b/docs/docs/designer/designer.md
@@ -0,0 +1,36 @@
+# Report Designer
+
+The report designer lets you customize how your final PDF reports look like.
+We do not limit your report look and feel in any way and allow you to customize your reports to your needs.
+
+A report design consists of following parts:
+
+* Field definition
+ * Defines what input fields are available in report sections and findings when writing report.
+ * Fields are available as form input fields when writing reports in the web interface.
+ * Field values are also available in Vue templates as variables.
+* HTML+VueJS template:
+ The [VueJS template language](https://vuejs.org/guide/essentials/template-syntax.html){ target=_blank } is used for rendering HTML.
+ It is based on HTML and JavaScript.
+* CSS Styles: Applied to HTML for styling the PDF.
+
+
+Here are basics of the Vue template language:
+
+* Render variables with double curly braces `{{ var }}`:
+ ```html
+
+ ```
+* Interations with `v-for="var_item in var_list"`-loops:
+ ```html
+
+
Finding title: {{ finding.title }}
+
+ ```
+* For more details and advanced features see the [VueJS documentation](https://vuejs.org/guide/essentials/template-syntax.html)
+
diff --git a/docs/docs/designer/faqs.md b/docs/docs/designer/faqs.md
new file mode 100644
index 000000000..1d1d85c07
--- /dev/null
+++ b/docs/docs/designer/faqs.md
@@ -0,0 +1,138 @@
+# Frequently Asked Questions
+
+??? note "How to set solid color as page background?"
+
+ Set background color for all pages
+ ```css
+ @page {
+ background-color: red;
+ }
+ ```
+
+ Set background color only on the first page (cover page)
+ ```css
+ @page:first {
+ background-color: red;
+ }
+ ```
+
+
+??? note "How to set a header background color?"
+
+ ```css
+ @page {
+ --header-background-color: red;
+ --header-margin-bottom: 5mm;
+
+ @top-left-corner {
+ content: "";
+ background-color: var(--header-background-color);
+ margin-bottom: var(--header-margin-bottom);
+ }
+ @top-center {
+ content: "";
+ background-color: var(--header-background-color);
+ margin-bottom: var(--header-margin-bottom);
+ width: 100%;
+ }
+ @top-right-corner {
+ content: "";
+ background-color: var(--header-background-color);
+ margin-bottom: var(--header-margin-bottom);
+ }
+ }
+ ```
+
+??? note "Why are my font styles (e.g. italic or bold) not working?"
+
+ We provide some [preinstalled fonts](/designer/design-guides/#fonts) that should work out of the box.
+
+ If you want to use custom font, make sure to [upload and include](/designer/design-guides/#custom-fonts) them in your CSS.
+
+
+??? note "Why are my images or markdown not rendered in the report?"
+
+ Your design may reference the variable incorrectly. Make sure to use this syntax:
+ ```html
+
+ ```
+
+??? note "How to format links like normal text?"
+
+ If you want all links to appear as normal text, use following CSS:
+ ```css
+ a {
+ color: inherit;
+ text-decoration: none;
+ font-style: inherit;
+ }
+ ```
+
+ If you want only target specific links, define a CSS class:
+ ```css
+ .link-none {
+ color: inherit;
+ text-decoration: none;
+ font-style: inherit;
+ }
+ ```
+
+ Then, add the defined class to your links.
+
+ HTML:
+ ```html
+ https://www.example.com
+ ```
+ Markdown:
+ ```markdown
+ [example.com](https://www.example.com){.link-none}
+ ```
+
+
+??? note "How to reference the filename in the report?"
+
+ This is not possible, unfortunately.
+
+ However, if you want to display your filename in your report, you might define a custom report field (or generate a dynamic filename like `report_{report.customer_name}_{report.title}.pdf`) and copy the filename from the preview to the filename textbox.
+
+
+??? note "How to highlight parts of code blocks with custom style?"
+
+ Highlighting within code-blocks works with the attribute `highlight-manual` and the marker `§§` ([see also](/reporting/markdown-features/#code-blocks)):
+
+ ````
+ ```http highlight-manual
+ POST /§§important.php§§ HTTP/1.1
+ ```
+ ````
+
+ To customize the hightlight style, add CSS styles for the `` tag, e.g.:
+ ```
+ mark {
+ background-color: red;
+ }
+ ```
+
+??? note "How to reduce the padding of code blocks?"
+
+ Add following rules to CSS
+ ```css
+ pre code {
+ padding: 0.3em !important;
+ }
+ ```
+
+
+??? note "How to increase the space between list marker and text in lists?"
+
+ Add following rules to CSS
+ ```css
+ /* Bullet list */
+ ul > li {
+ list-style: "\2022 ";
+ }
+ /* Numbered list */
+ ol > li::marker {
+ content: counter(list-item) ". \200b";
+ }
+ ```
diff --git a/docs/docs/designer/figures.md b/docs/docs/designer/figures.md
new file mode 100755
index 000000000..8310043a3
--- /dev/null
+++ b/docs/docs/designer/figures.md
@@ -0,0 +1,99 @@
+# Figures
+
+## Markdown images
+When you embed images in markdown with `![title](img.png)` the `` tags are wrapped in `
` tags.
+This allows to add captions with `` tags.
+
+It is recommended that you also use `
` tags when placing images in your HTML template in text.
+Except for logos in headers or background images on the title page.
+
+```html linenums="1"
+
+```
+
+### Image width
+```md linenums="1"
+![Image with half the page width](img.png){width="50%"}
+![Exactly sized image](img.png){width="10cm" height="7cm"}
+```
+
+
+
+## Basic styling
+```css linenums="1"
+/* Image styling */
+/* Prevent images from overflowing figure or page width */
+img {
+ max-width: 100%;
+}
+figure {
+ break-inside: avoid;
+ text-align: center;
+ margin-left: 0;
+ margin-right: 0;
+}
+figcaption {
+ font-weight: bold;
+ break-before: avoid;
+}
+```
+
+
+## Figure numbering
+```css linenums="1"
+html {
+ counter-reset: figure-counter;
+}
+
+figcaption::before {
+ counter-increment: figure-counter;
+ content: "Figure " counter(figure-counter) ": ";
+}
+```
+
+
+## List of Figures
+
+### Template Component
+Works similar like table of contents.
+The component uses multi-pass rendering.
+In the first render-pass it does nothing, in the second pass it collects all previously rendered `` tags and provides them in the variable `items`.
+
+```html linenums="1"
+
+
+
List of Figures
+
+
+
+
+
+
+
+
+```
+
+### Referencing figure numbers
+```css linenums="1"
+#lof li {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+#lof .ref-figure::before {
+ content: var(--prefix-figure) target-counter(attr(href), figure-counter) " - ";
+}
+#lof .ref-figure > .ref-title {
+ display: inline;
+}
+#lof .ref-figure::after {
+ content: " " leader(".") " " target-counter(attr(href), page);
+}
+```
+
diff --git a/docs/docs/designer/footnotes.md b/docs/docs/designer/footnotes.md
new file mode 100755
index 000000000..507b05573
--- /dev/null
+++ b/docs/docs/designer/footnotes.md
@@ -0,0 +1,54 @@
+# Footnotes
+
+## Markdown
+```md linenums="1"
+Text text^[I'm a footnote [link](https://example.com)] text.
+```
+
+
+## Template Styling
+Elements are marked as footnotes with `float: footnote`.
+Footnotes use the built-in CSS counter `footnote`. It is incremented automatically.
+
+* Styling the footnote area at the bottom of the page: `@page { @footnote { ... }}`
+* Styling the footnote reference in text: `::footnote-call { ... }`
+* Styling the footnote number in the footnote box at the bottom of the page: `::footnote-marker { ... }`
+* Styling footnote text content (shown at the bottom of the page): same selector (and child elements) where you applied `float: footnote` (e.g. `footnote` element)
+
+```html linenums="1"
+This is a text with footnotesI'm the footnote content in it.
+```
+
+```css linenums="1"
+/* Footnotes */
+@page {
+ @footnote {
+ padding-top: 0.5em;
+ border-top: 1px solid black;
+ }
+}
+footnote {
+ float: footnote;
+}
+/* Footnote number in text */
+::footnote-call {
+ content: counter(footnote);
+}
+.footnote-call-separator {
+ content: ',';
+}
+/* Footnote number in footnote area */
+::footnote-marker {
+ content: counter(footnote) '.';
+ display: inline-block;
+ width: 2em;
+ padding-right: 1em;
+ text-align: right;
+}
+/* Styling links in footnotes */
+footnote a {
+ color: black;
+ text-decoration: none;
+}
+```
+
diff --git a/docs/docs/designer/formatting-utils.md b/docs/docs/designer/formatting-utils.md
new file mode 100755
index 000000000..016e0e259
--- /dev/null
+++ b/docs/docs/designer/formatting-utils.md
@@ -0,0 +1,40 @@
+# Formatting Utilities
+Multiple utility functions for formatting are available.
+
+## Date Formatting
+The `formatDate()` function takes three arguments:
+
+* the date to be formatted
+* (optional) format options
+ * if not specified, the date is formatted as `{dateStyle: 'long'}` in the current locale of the report
+ * a string for either: `iso` (format: `yyyy-mm-dd`) or `full`, `long`, `medium`, `short` date style in the current locale of the report
+ * a object for [Intl.DateTimeFormat options](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options){ target=_blank }
+* (optional) locale to override the default locale: see [Intl.DateTimeFormat locales](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#locales){ target=_blank }
+
+Examples:
+```html
+2022-09-21: {{ formatDate(report.report_date, 'iso') }}
+
+21.09.22: {{ formatDate(report.report_date, 'short', 'de-DE') }}
+21.09.2022: {{ formatDate(report.report_date, 'medium', 'de-DE') }}
+21. September 2022: {{ formatDate(report.report_date, 'long', 'de-DE') }}
+Mittwoch, 21. September 2022: {{ formatDate(report.report_date, 'full', 'de-DE') }}
+
+9/21/22: {{ formatDate(report.report_date, 'short', 'en-US') }}
+Sep 21, 2022: {{ formatDate(report.report_date, 'medium', 'en-US') }}
+September 21, 2022: {{ formatDate(report.report_date, 'long', 'en-US') }}
+Wednesday, September 21, 2022: {{ formatDate(report.report_date, 'full', 'en-US') }}
+
+S 21, 22: {{ formatDate('2022-09-21', {year: '2-digit', month: 'narrow', day: '2-digit', numberingSystem: 'latn'}, 'en-US') }}
+
+```
+
+## Lodash Utilies
+All lodash utility functions are available in templates as `lodash`.
+See https://lodash.com/docs/ for a list of available functions.
+
+Examples:
+```html
+{{ lodash.capitalize(finding.cvss.level) }}
+{{ lodash.toUpper(finding.cvss.level) }}
+```
\ No newline at end of file
diff --git a/docs/docs/designer/headings-and-table-of-contents.md b/docs/docs/designer/headings-and-table-of-contents.md
new file mode 100755
index 000000000..ff8b637be
--- /dev/null
+++ b/docs/docs/designer/headings-and-table-of-contents.md
@@ -0,0 +1,308 @@
+# Headings and Table of Contents
+We provide many useful default styles in our `base.css`. You can import them to your report's CSS using:
+
+```css
+@import "/assets/global/base.css"
+```
+
+Headings and Table of Contents can be used out of the box with the imported styles.
+If you want to customize heading numberings or table of content (like margins, etc.), have a look at the following chapters.
+
+## Customization
+
+!!! tip "Use the following snippets as a guide how to override the base styles."
+
+ You do not need them, if you imported the base styles and don't need further customization.
+
+
+CSS has counters to automatically number items such as headings, figures, etc. and also generate table of contents and list of figures with these numbers.
+
+This allows you to automatically produce a structure similar to
+```md linenums="1"
+1 Heading
+1.1 Subheading
+1.2 Subheading
+1.2.1 Subsubheading
+2 Heading
+A Appendix
+A.1 Appendix Subheading
+```
+
+Additional resources:
+
+* CSS Counters
+ * https://printcss.net/articles/counter-and-cross-references
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Counter_Styles/Using_CSS_counters
+
+
+## Heading Numbers
+This example contains code for numbering headings with pure CSS.
+The heading number is placed in the `::before` pseudo-element in the DOM using CSS.
+
+CSS counters first have to be defined with `counter-reset: ` (best place this rule in `html`).
+The counters by default start at `0`, but the start value can also be overwritten.
+
+Before the counter value is used, it should be incremented (such that chapter numbers start at 1, not 0) with `counter-increment: `.
+Now the counter has the correct value, we can embed it with `content: counter()` in `::before` pseudo-elements.
+
+Counters are incremented and referenced with CSS rules in selectors.
+Counters have no global value at a given time, instead their values depend on the DOM-position of the elements that use them.
+For example: the custom `h1-counter` is incremented at every `
` tag.
+This means that between the first and the second `
` tag in the DOM structure, the counter has the value `1`,
+between the second and third `
` it has the value 2 and so on.
+When the CSS rule `h2::before { counter-increment: h2-counter; content: counter(h1-counter) "." counter(h1-counter); }`
+accesses the counter value of `h1-counter` the value is different depending on where the targeted `h2` element is placed in the DOM.
+Note that this `h2::before` rule is defined only once and applies to all `
` tags.
+
+### Basic Heading Numbering
+Add numbering to heading tags which have the class `numbered`.
+
+```css linenums="1"
+html {
+ /* Define counters and reset them */
+ counter-reset: h1-counter h2-counter h3-counter;
+}
+
+/* Heading numbers
+Usage in HTML:
+
Heading
=> 1 Heading
+
Subheading
=> 1.1 Subheading
+*/
+h1.numbered::before {
+ padding-right: 5mm;
+ counter-increment: h1-counter;
+ content: counter(h1-counter);
+}
+h2.numbered::before{
+ padding-right: 5mm;
+ counter-increment: h2-counter;
+ content: counter(h1-counter) "." counter(h2-counter);
+}
+h3.numbered::before{
+ padding-right: 5mm;
+ counter-increment: h3-counter;
+ content: counter(h1-counter) "." counter(h2-counter) "." counter(h3-counter);
+}
+
+/* Reset counters of sub-headings below the current level */
+h1.numbered {
+ counter-reset: h2-counter h3-counter;
+}
+h2.numbered {
+ counter-reset: h3-counter;
+}
+```
+
+### Appendix Numbering
+If you want appendix sections that are numbered differently, an additional counter can be used that uses a different number formatting.
+E.g. with A, A.1, A.2, B, B.1, etc. instead contiuned numbering 4, 4.1, 4.2, 5, 5.1, etc.
+
+CSS counters can specify a counter style to use such as `upper-alpha` instead of decimal numbers.
+
+```css linenums="1"
+html {
+ /* NOTE: only one html {} block with counter-reset rules should exist; if there exist multiple, they overwrite each other */
+ counter-reset: h1-counter h2-counter h3-counter h1-appendix-counter;
+}
+
+/* Appendix heading numbers
+Usage in HTML:
+
Heading
=> A Heading
+
Subheading
=> A.1 Subheading
+*/
+h1.numbered-appendix::before {
+ padding-right: 5mm;
+ counter-increment: h1-appendix-counter;
+ content: counter(h1-appendix-counter, upper-alpha);
+}
+h2.numbered-appendix::before {
+ padding-right: 5mm;
+ counter-increment: h2-counter;
+ content: counter(h1-appendix-counter, upper-alpha) "." counter(h2-counter);
+}
+h3.numbered-appendix::before{
+ padding-right: 5mm;
+ counter-increment: h3-counter;
+ content: counter(h1-appendix-counter, upper-alpha) "." counter(h2-counter) "." counter(h3-counter);
+}
+
+/* Reset counters of sub-headings below the current level */
+h1.numbered-appendix {
+ counter-reset: h2-counter h3-counter;
+}
+h2.numbered-appendix {
+ counter-reset: h3-counter;
+}
+```
+
+
+
+## Table of Contents
+A table of contents can be included in reports via the `` component.
+This component collects all elements with the class `in-toc`, and provides them as variables.
+This component uses delayed multi-pass rendering to ensure that all items referenced in the TOC are already rendered and can be referenced.
+
+### Heading Numbers in TOC
+Heading numbers can be added purely with CSS using counters.
+However, in order to use the correct counters, the nesting level of the heading needs to be known by CSS rules.
+These cannot be determined soely in CSS.
+
+The `` component determines the nesting level and provides this information.
+`h1` to `h6` tags are assigned the correct level.
+
+All HTML attributes of the target element are collected and passed to ``.
+This can be used to e.g. determine if an item is in an appendix section or regular chapter.
+
+
+### Table of Contents Simple Example
+This example renders a table of contents with
+
+* heading title
+* page number
+* links entries to the target pages, such that you can click on the TOC entries and jump to the referenced pa
+
+```html
+
+
+
+Apache/2.2.3 (Debian) PHP/5.2.0-8+etch16 mod_ssl/2.2.3 OpenSSL/0.9.8c Server
+
diff --git a/docs/docs/htb-reporting-with-sysreptor.md b/docs/docs/htb-reporting-with-sysreptor.md
new file mode 100644
index 000000000..8b875c6ee
--- /dev/null
+++ b/docs/docs/htb-reporting-with-sysreptor.md
@@ -0,0 +1,44 @@
+# Hack The Box Reporting
+
+Use our cloud service for free to write your Hack The Box CPTS or CBBH reports.
+
+💲 Free
+📝 Write in markdown
+⚙️ Render your report to PDF
+🛡️ CPTS, CBBH
+🚀 Fully customizable
+🎉 No need for Word
+👌 No local software troubleshooting
+
+
[:rocket: Sign Up Now](https://htb.sysreptor.com/htb/signup/){ .md-button }
+
Already have an account? [Login here.](https://labs.sysre.pt){ target=_blank }
+
+
+## Creating an HTB CPTS Report
+
\ No newline at end of file
diff --git a/docs/docs/images/Logo-only.png b/docs/docs/images/Logo-only.png
new file mode 100755
index 000000000..0ebfc407b
Binary files /dev/null and b/docs/docs/images/Logo-only.png differ
diff --git a/docs/docs/images/Trust_IT_logo.png b/docs/docs/images/Trust_IT_logo.png
new file mode 100644
index 000000000..7ebd75d4e
Binary files /dev/null and b/docs/docs/images/Trust_IT_logo.png differ
diff --git a/docs/docs/images/add_identity.png b/docs/docs/images/add_identity.png
new file mode 100644
index 000000000..8cc1912f6
Binary files /dev/null and b/docs/docs/images/add_identity.png differ
diff --git a/docs/docs/images/add_to_dictionary.png b/docs/docs/images/add_to_dictionary.png
new file mode 100644
index 000000000..82d833118
Binary files /dev/null and b/docs/docs/images/add_to_dictionary.png differ
diff --git a/docs/docs/images/archive-create.png b/docs/docs/images/archive-create.png
new file mode 100644
index 000000000..ea88d80b5
Binary files /dev/null and b/docs/docs/images/archive-create.png differ
diff --git a/docs/docs/images/archive-restore.png b/docs/docs/images/archive-restore.png
new file mode 100644
index 000000000..c5ee24a53
Binary files /dev/null and b/docs/docs/images/archive-restore.png differ
diff --git a/docs/docs/images/archive-restore2.png b/docs/docs/images/archive-restore2.png
new file mode 100644
index 000000000..6af6bcff5
Binary files /dev/null and b/docs/docs/images/archive-restore2.png differ
diff --git a/docs/docs/images/archiving-crypto.drawio b/docs/docs/images/archiving-crypto.drawio
new file mode 100644
index 000000000..2cd5c7b05
--- /dev/null
+++ b/docs/docs/images/archiving-crypto.drawio
@@ -0,0 +1 @@
+7Vxtc5s4EP41nms/hOFFvH2M3bTXueuN59KbNh8xKFgtRj4hJ3Z/fSVANiA7po2NIHGSidGChNhdPdpHKzyyJov1BxIs559wBJORqUfrkfVuZJoGsAD74JJNITF1vZTEBEXlVTvBLfoBS6FeSlcoglntQopxQtGyLgxxmsKQ1mQBIfixftk9Tup3XQYxlAS3YZDI0i8oovNC6pnuTv4nRPFc3Nlw/OLMIhAXl0+SzYMIP1ZE1s3ImhCMaXG0WE9gwrUn9FLUe3/g7LZjBKa0TYXrL8Twsq/0a0zvZvHHz7efP/5zBZyimYcgWZVPXPaWboQKYMQ0UhZTnLKPMcGrNIK8YZ2VMKFzHOM0SP7GeMmEBhN+g5RuSnsGK4qZaE4XSXmW9ZlsvvL6mi2Kd2VzeeHdulbalKWic7xHB7VQijK8IiF84tGFNwUkhvQpFdlbYzE3h3gBWYdYRQKTgKKHekeC0t3i7XU7i7CD0ii/YiBfkYHWiFbsw0p3lTM76/CCSuO4Ko1j65fRc8xAnkoDGZJ9LI2Vb+fBAhF+AEPCup9LCEpjyXp1Uz3OEYW3yyDXzCOb8epmKW8GCYXrpxUs66OsYLqlS5Xz5ZUHSsHjbvYxxJQyr8w8jn4mHVpObwGnB+MAtB4I+n7Dtx4IZdUpRindOYxlNR3GbThC8QhltWp8ILVkaq5Z+bUPeKJouHhkqeHcybYP+Ay/A68FSy2rrQ8dAI9T+5BvdGxqS8LpKdMizHgnpwR/45H9YWg2jkPzPUqSCU4wYeUI3gerhJ4JsI0tOqtDbK9jxPb9KmbrGnDtp3GbFaaQIPa4kEhYXhuFu0HZwTj01WL5lvRuIdf8TSw3vWZLhq2ZwNV12/c83QSNvp15fIOuobz/LvRcJH8eQMik8t8gjfCCya5vbtn/73AjmWzJ3SPvij1mf8zDJly37P/IZucnXMacLBfUhflFmuk1xBw2+AkOF/squOa+xg0hZH/WmIM3CpmTBDOYTHGGKMIp6+MMU8qexxpn3yEN56WlK84iKl4nKOYVKHeycZAti1Wke7TmrjgOytMhn48YVo1zLUBy8wALZfC2snmw5EparGO+DKaFKAux4Wtci/W5Z2Raev7Da1GCv0NxphwEJ5iSDLs5+D15RrL3TEimqdln8jhgdYIAJxzJItrt90gG8gqEyRnuTRqSzZLPCAEJ57wbppOHPDNSs4Lz/wrnYkwiSK7Cwhmv8xbJm6urqvxtrmBRgx3F/PMRMZfKYePqw+TTE1FaCwLdGCnv858zxWm+cmIN5NUJI7fdeslcnx1EAQ24B+LCDbX4R9WeQ1G0oVuqNW13syx3SvSxW6KPrRZ+bMmFAXfhv3j4kC+qMUfVDUn7fXFWv+GrrnpXtQfnqm5bV7WUuqrbxlVloOiLq0q42gNf7WhlWP0KHfDa+jhQ6eN+N/PckAziKc0/iW4+DToyN+oL6NjNFakegI4cNdvaju6UdKRKdwqe8l8GifFHxs5NV7MEhcIKTrDg+kxn2bJgNY16vbVNM3ZxlK872/IyvmyaM9FQbl6zhXl7akxpdu+BNYFaa1p7rNljKgFA/ywoh7w5Cko6FOuXq0VyHVKunePrqvvXTyu6xiuaoBROthvz9PMawPCtugFsXTaA1an+O8rD/WY4lpeaabgTxmh2a2KodhOXnAyZEvQQULhFnTf4/p778lvJnpecyEByImzuMs06PoA9K8DA3V5Wy4wYmnmu1IiAHyX7q3KH+tVU/Skxom2+tLhQFUY4g1tAdtruS1O7gOzIbK4e8b0IvO0DxHo6/5UgdrcZqpCLlxw4KrBWI8Tu14BjIX6HCOt08ahcFCRFn4q3HPitQrxglqyCxjNB3GsguOfICA40sd2khuDnCvIcd2jwfTBe5KNDccg4jOlAkPUGsZIniRdBrKzm2oRyYuXKFPY1ESthj6OjxH3uPsXnWcm8ECvVs/7ZiZWlN14T6AmrcrvZcNZPViVG3nGAMJUChNr8sXEExk9pENA2rlG6R8KVl/wvbOzCxg4hv6u5EiHbA/4dEzK3mx1Ur4OQDWUm2Z/pkmOAF0HIbKdByICvmpC97kyX2zbT5SrNdLnHM13GhZS9AFIGhKV7Rsq815zqctuubbpK1za9wb2p5bVOdSndnCu6eZBc7dncNEzA7QPGvgCCBWR2pT7d5clJjp5DeJ/Z1UCmBHkXu/qdoLIf9mUnqN388gX1O0G945E/Py3vfhdcgE8Hpn63miF29YUYDJUYOEZzSukHMfDVJnQ7zAT4bXO3al9cEt08HKzK+DtMEOjDuH8BwaqMLB44Y7DKirtvzi2+pmj3BcTWzU8=
\ No newline at end of file
diff --git a/docs/docs/images/archiving-crypto.drawio.png b/docs/docs/images/archiving-crypto.drawio.png
new file mode 100644
index 000000000..eef6ca2d5
Binary files /dev/null and b/docs/docs/images/archiving-crypto.drawio.png differ
diff --git a/docs/docs/images/assign-notes-emojis.png b/docs/docs/images/assign-notes-emojis.png
new file mode 100644
index 000000000..5f816acd9
Binary files /dev/null and b/docs/docs/images/assign-notes-emojis.png differ
diff --git a/docs/docs/images/create_finding_from_template.gif b/docs/docs/images/create_finding_from_template.gif
new file mode 100644
index 000000000..278306e60
Binary files /dev/null and b/docs/docs/images/create_finding_from_template.gif differ
diff --git a/docs/docs/images/create_finding_from_template.png b/docs/docs/images/create_finding_from_template.png
new file mode 100644
index 000000000..910806fa6
Binary files /dev/null and b/docs/docs/images/create_finding_from_template.png differ
diff --git a/docs/docs/images/edit-anyway.png b/docs/docs/images/edit-anyway.png
new file mode 100755
index 000000000..ada5e9079
Binary files /dev/null and b/docs/docs/images/edit-anyway.png differ
diff --git a/docs/docs/images/export_project.gif b/docs/docs/images/export_project.gif
new file mode 100644
index 000000000..34a2b8dd6
Binary files /dev/null and b/docs/docs/images/export_project.gif differ
diff --git a/docs/docs/images/finding_id.png b/docs/docs/images/finding_id.png
new file mode 100755
index 000000000..704a50ed6
Binary files /dev/null and b/docs/docs/images/finding_id.png differ
diff --git a/docs/docs/images/google_add_scopes.png b/docs/docs/images/google_add_scopes.png
new file mode 100644
index 000000000..7256221c1
Binary files /dev/null and b/docs/docs/images/google_add_scopes.png differ
diff --git a/docs/docs/images/google_app_domain.png b/docs/docs/images/google_app_domain.png
new file mode 100644
index 000000000..e0f3f728d
Binary files /dev/null and b/docs/docs/images/google_app_domain.png differ
diff --git a/docs/docs/images/google_app_information.png b/docs/docs/images/google_app_information.png
new file mode 100644
index 000000000..8faf8cc68
Binary files /dev/null and b/docs/docs/images/google_app_information.png differ
diff --git a/docs/docs/images/google_authorized_redirect_uri.png b/docs/docs/images/google_authorized_redirect_uri.png
new file mode 100644
index 000000000..6155d10c9
Binary files /dev/null and b/docs/docs/images/google_authorized_redirect_uri.png differ
diff --git a/docs/docs/images/google_call_create_project.png b/docs/docs/images/google_call_create_project.png
new file mode 100644
index 000000000..78046514d
Binary files /dev/null and b/docs/docs/images/google_call_create_project.png differ
diff --git a/docs/docs/images/google_client_data.png b/docs/docs/images/google_client_data.png
new file mode 100644
index 000000000..f435f001a
Binary files /dev/null and b/docs/docs/images/google_client_data.png differ
diff --git a/docs/docs/images/google_cloud_console.png b/docs/docs/images/google_cloud_console.png
new file mode 100644
index 000000000..c1e81d31f
Binary files /dev/null and b/docs/docs/images/google_cloud_console.png differ
diff --git a/docs/docs/images/google_create_credentials.png b/docs/docs/images/google_create_credentials.png
new file mode 100644
index 000000000..feea5adf6
Binary files /dev/null and b/docs/docs/images/google_create_credentials.png differ
diff --git a/docs/docs/images/google_create_project.png b/docs/docs/images/google_create_project.png
new file mode 100644
index 000000000..ab6a1fb39
Binary files /dev/null and b/docs/docs/images/google_create_project.png differ
diff --git a/docs/docs/images/google_developer_info.png b/docs/docs/images/google_developer_info.png
new file mode 100644
index 000000000..3881b10fe
Binary files /dev/null and b/docs/docs/images/google_developer_info.png differ
diff --git a/docs/docs/images/google_user_type_internal.png b/docs/docs/images/google_user_type_internal.png
new file mode 100644
index 000000000..54bf5118f
Binary files /dev/null and b/docs/docs/images/google_user_type_internal.png differ
diff --git a/docs/docs/images/htb-reporting.gif b/docs/docs/images/htb-reporting.gif
new file mode 100644
index 000000000..353912fa1
Binary files /dev/null and b/docs/docs/images/htb-reporting.gif differ
diff --git a/docs/docs/images/john-is-editing.png b/docs/docs/images/john-is-editing.png
new file mode 100755
index 000000000..0c8c1cbc3
Binary files /dev/null and b/docs/docs/images/john-is-editing.png differ
diff --git a/docs/docs/images/logo-invert-with-text.png b/docs/docs/images/logo-invert-with-text.png
new file mode 100644
index 000000000..2bdd7640b
Binary files /dev/null and b/docs/docs/images/logo-invert-with-text.png differ
diff --git a/docs/docs/images/logo-invert.svg b/docs/docs/images/logo-invert.svg
new file mode 100755
index 000000000..cbc4e9480
--- /dev/null
+++ b/docs/docs/images/logo-invert.svg
@@ -0,0 +1,73 @@
+
+
+
diff --git a/docs/docs/images/logo.svg b/docs/docs/images/logo.svg
new file mode 100755
index 000000000..c09d166a4
--- /dev/null
+++ b/docs/docs/images/logo.svg
@@ -0,0 +1,14 @@
+
+
+
\ No newline at end of file
diff --git a/docs/docs/images/oidc_1_register.png b/docs/docs/images/oidc_1_register.png
new file mode 100755
index 000000000..48229221b
Binary files /dev/null and b/docs/docs/images/oidc_1_register.png differ
diff --git a/docs/docs/images/oidc_2_claims.png b/docs/docs/images/oidc_2_claims.png
new file mode 100755
index 000000000..9d41a920a
Binary files /dev/null and b/docs/docs/images/oidc_2_claims.png differ
diff --git a/docs/docs/images/oscp-reporting.gif b/docs/docs/images/oscp-reporting.gif
new file mode 100755
index 000000000..c11de77e3
Binary files /dev/null and b/docs/docs/images/oscp-reporting.gif differ
diff --git a/docs/docs/images/render-workflow.drawio b/docs/docs/images/render-workflow.drawio
new file mode 100755
index 000000000..832ed7e26
--- /dev/null
+++ b/docs/docs/images/render-workflow.drawio
@@ -0,0 +1 @@
+7Vldb+I4FP01SLsPRYlDCDzyUbba7WjQMjuz82iwSdxx4sgxBfbX73XiEBJDy8zw0UqDKhEf27F97r3H95aWN4o3f0icRh8EobyFHLJpeeMWQm4HoZb+c8i2QIK+XwChZMQMqoAZ+48a0DHoihGa1QYqIbhiaR1ciCShC1XDsJRiXR+2FLy+aopDagGzBeY2+oURFRVoDwUV/kBZGJUru91+0RPjcrA5SRZhItZ7kHff8kZSCFU8xZsR5Zq8kpdi3uRI725jkibqlAnBw/3TEn0Mwy0ffFYfnX/+wvM7Y4xMbcsDUwLnN00hVSRCkWB+X6FDKVYJofqtDrSqMY9CpAC6AD5RpbbGmHilBECRirnpLdbUCx09ioEysZIL+sL+S5fAMqTqhXHejnDwVCpiquQW5knKsWLP9X1g4zLhblzFKjwYYr+DZPPeZ8xXZqW/aQq8Afbw6cNjCw0/r+ifM2h+onEKO6KWVeqcryOm6CzFOTVrCLxD/D5TqejmZYZtRsoJZfyZsEVd015XQeCWnh3tBUA57uwkdt65p6L34KnI8tSpFE9aV5EzxgrD12+y9F2cEK2oLCEsCTOt8TDi9zfnum7/1q7bu4XrAlty+6+Znze+6kbbL5vjzX7neGtaGXioGuibE4BEJPTMgt07MQ6Cn4wDM3UqWKIq5+h0vLqu+Q2jFwcwsxp2323jx13BswLsgWLCaabjZxRJWMAOoEjE81V2leBB/Ybuu3bw9A7ETu9SsdM9cHdCYEjAyktT7S5NSA13d2qDRKBE1dnKlBTf6EhwIStPXzLOGxDmLEyguQAKYV1vqAlmkCEOTEfMCMlj9pB56nF8Dgv1GvIWHJA354CJvEuZyEXv/GoOTpSk/i2v5sAKhC8UZ9upLJXqVpLRCd6YZPQtpix+oBZL9SOL8/Jvn5FmdCvtjzv0Ec8pn4qMKSZ071woJWIYwHXHEC++hblnlxrSQt4y/8CQfLFBlhZlqnZ7XDaWbKNjYWj2M46U0vXtQBOBJguSuG0GFa7OtahsL2BFNCF5PjbROFwekzkUkiBpOL17FlzfMxMXtHMCgkbVNqV3KVlCpPbaaRKewehdr2F01LWM3unZRi+x84uQaxn5qklWlVd93e87kmSdM6FyrpRQ/Zx5HCsoRzN9eRv9cnKTZLaQ3bh48LxbFw9u53gGZP57UDCZ5z7T8eSNpj5HBJLQJV5xvb+lSJSNnuOC8v3XMyb3qhmT//oVdRX1Ipcr9LwTZakoCM9e6HnNrOTEQs+uGJ1XXlSc8GIVY1kw/0pnaumMPuIun7mkeDTNj4K2b6c6/oFUx/9u9YBm9XtA4UDVryre/f8=
\ No newline at end of file
diff --git a/docs/docs/images/render-workflow.drawio.png b/docs/docs/images/render-workflow.drawio.png
new file mode 100755
index 000000000..e14f003f7
Binary files /dev/null and b/docs/docs/images/render-workflow.drawio.png differ
diff --git a/docs/docs/images/show/2fa.png b/docs/docs/images/show/2fa.png
new file mode 100644
index 000000000..c8f41219a
Binary files /dev/null and b/docs/docs/images/show/2fa.png differ
diff --git a/docs/docs/images/show/SSO.png b/docs/docs/images/show/SSO.png
new file mode 100644
index 000000000..060ceee35
Binary files /dev/null and b/docs/docs/images/show/SSO.png differ
diff --git a/docs/docs/images/show/add_chart.gif b/docs/docs/images/show/add_chart.gif
new file mode 100644
index 000000000..fb0f95481
Binary files /dev/null and b/docs/docs/images/show/add_chart.gif differ
diff --git a/docs/docs/images/show/archiving.png b/docs/docs/images/show/archiving.png
new file mode 100644
index 000000000..e154dfadb
Binary files /dev/null and b/docs/docs/images/show/archiving.png differ
diff --git a/docs/docs/images/show/bulk-edit-list.gif b/docs/docs/images/show/bulk-edit-list.gif
new file mode 100644
index 000000000..463253508
Binary files /dev/null and b/docs/docs/images/show/bulk-edit-list.gif differ
diff --git a/docs/docs/images/show/change_colors.gif b/docs/docs/images/show/change_colors.gif
new file mode 100644
index 000000000..26f4ec8b9
Binary files /dev/null and b/docs/docs/images/show/change_colors.gif differ
diff --git a/docs/docs/images/show/cvss-editor.gif b/docs/docs/images/show/cvss-editor.gif
new file mode 100644
index 000000000..7364cb26f
Binary files /dev/null and b/docs/docs/images/show/cvss-editor.gif differ
diff --git a/docs/docs/images/show/data-at-rest.png b/docs/docs/images/show/data-at-rest.png
new file mode 100644
index 000000000..80992c1cb
Binary files /dev/null and b/docs/docs/images/show/data-at-rest.png differ
diff --git a/docs/docs/images/show/drop-file.gif b/docs/docs/images/show/drop-file.gif
new file mode 100644
index 000000000..e6ca922a3
Binary files /dev/null and b/docs/docs/images/show/drop-file.gif differ
diff --git a/docs/docs/images/show/drop-image.gif b/docs/docs/images/show/drop-image.gif
new file mode 100644
index 000000000..4623fc650
Binary files /dev/null and b/docs/docs/images/show/drop-image.gif differ
diff --git a/docs/docs/images/show/finding-from-template.gif b/docs/docs/images/show/finding-from-template.gif
new file mode 100644
index 000000000..011114d9c
Binary files /dev/null and b/docs/docs/images/show/finding-from-template.gif differ
diff --git a/docs/docs/images/show/github-discussions-1.png b/docs/docs/images/show/github-discussions-1.png
new file mode 100644
index 000000000..7dacdb12c
Binary files /dev/null and b/docs/docs/images/show/github-discussions-1.png differ
diff --git a/docs/docs/images/show/github_discussions.png b/docs/docs/images/show/github_discussions.png
new file mode 100644
index 000000000..5fb1607eb
Binary files /dev/null and b/docs/docs/images/show/github_discussions.png differ
diff --git a/docs/docs/images/show/markdown_in_html.png b/docs/docs/images/show/markdown_in_html.png
new file mode 100644
index 000000000..ab401b114
Binary files /dev/null and b/docs/docs/images/show/markdown_in_html.png differ
diff --git a/docs/docs/images/show/note-taking.png b/docs/docs/images/show/note-taking.png
new file mode 100644
index 000000000..91407ce0e
Binary files /dev/null and b/docs/docs/images/show/note-taking.png differ
diff --git a/docs/docs/images/show/offsec_reports.png b/docs/docs/images/show/offsec_reports.png
new file mode 100644
index 000000000..0c0c64c9b
Binary files /dev/null and b/docs/docs/images/show/offsec_reports.png differ
diff --git a/docs/docs/images/show/reference_image.png b/docs/docs/images/show/reference_image.png
new file mode 100644
index 000000000..099b973b2
Binary files /dev/null and b/docs/docs/images/show/reference_image.png differ
diff --git a/docs/docs/images/show/reptor_sslyze.gif b/docs/docs/images/show/reptor_sslyze.gif
new file mode 100644
index 000000000..c8f2cd9ac
Binary files /dev/null and b/docs/docs/images/show/reptor_sslyze.gif differ
diff --git a/docs/docs/images/show/spellcheck.gif b/docs/docs/images/show/spellcheck.gif
new file mode 100644
index 000000000..9f286a5f5
Binary files /dev/null and b/docs/docs/images/show/spellcheck.gif differ
diff --git a/docs/docs/images/show/subcontractors.png b/docs/docs/images/show/subcontractors.png
new file mode 100644
index 000000000..14fedcab6
Binary files /dev/null and b/docs/docs/images/show/subcontractors.png differ
diff --git a/docs/docs/images/show/sudo.png b/docs/docs/images/show/sudo.png
new file mode 100644
index 000000000..5766d3624
Binary files /dev/null and b/docs/docs/images/show/sudo.png differ
diff --git a/docs/docs/images/show/sysreptor.png b/docs/docs/images/show/sysreptor.png
new file mode 100644
index 000000000..7f4de4587
Binary files /dev/null and b/docs/docs/images/show/sysreptor.png differ
diff --git a/docs/docs/images/show/updates.png b/docs/docs/images/show/updates.png
new file mode 100644
index 000000000..52b46a231
Binary files /dev/null and b/docs/docs/images/show/updates.png differ
diff --git a/docs/docs/images/show/vulnerability.png b/docs/docs/images/show/vulnerability.png
new file mode 100644
index 000000000..f242c0fb7
Binary files /dev/null and b/docs/docs/images/show/vulnerability.png differ
diff --git a/docs/docs/images/slashsec.svg b/docs/docs/images/slashsec.svg
new file mode 100644
index 000000000..d3b9bc8d9
--- /dev/null
+++ b/docs/docs/images/slashsec.svg
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/docs/docs/images/syslifters-logo.png b/docs/docs/images/syslifters-logo.png
new file mode 100644
index 000000000..3437a785e
Binary files /dev/null and b/docs/docs/images/syslifters-logo.png differ
diff --git a/docs/docs/images/sysreptor_120x120.png b/docs/docs/images/sysreptor_120x120.png
new file mode 100644
index 000000000..abb5f7f91
Binary files /dev/null and b/docs/docs/images/sysreptor_120x120.png differ
diff --git a/docs/docs/images/template_fields.png b/docs/docs/images/template_fields.png
new file mode 100644
index 000000000..4c4608f67
Binary files /dev/null and b/docs/docs/images/template_fields.png differ
diff --git a/docs/docs/index.md b/docs/docs/index.md
new file mode 100755
index 000000000..78b3098c5
--- /dev/null
+++ b/docs/docs/index.md
@@ -0,0 +1,119 @@
+---
+title: SysReptor Pentest Report Creator
+---
+
Pentest Reports Easy As Pie.
+
+
+
+
+
+
+- __Customize Reports__
+
+
+
+
+ Design in HTML.
+
+ As easy as falling off a log.
+
+
+- __Write Reports__
+
+
+
+
+ Write in Markdown.
+
+ It gives you an easy ride.
+
+- __Render and Download__
+
+
+
+
+ Render to PDF.
+
+ Easier done than said.
+
+- __Operate Platform__
+
+
+
+
+ Self-Hosted or Cloud.
+
+ Easy peasy lemon squeezy.
+
+
+
+
+
+ Pentesters taking reporting easy:
+
+
+
+
+
+
+
+
+
[:fire: Get Started](/setup/installation/){ .md-button target="_blank" }
+
+
+
+
+
diff --git a/docs/docs/insights/rendering-workflow.md b/docs/docs/insights/rendering-workflow.md
new file mode 100644
index 000000000..3bfbbf88e
--- /dev/null
+++ b/docs/docs/insights/rendering-workflow.md
@@ -0,0 +1,72 @@
+# Rendering Workflow
+Each pentest project needs a design which specifies how the final report looks like and what fields are available in the report and findings.
+The report designer lets you customize how your final PDF reports look like.
+We do not limit your report look and feel in any way and allow you to customize your reports to your needs.
+
+PDF rendering is a two-step process.
+First, the VueJS template is rendered to plain HTML with Headless Chromium. In this step report variables (section fields, findings) are embedded in the final HTML.
+Second, the HTML and CSS styles are rendered to a PDF using WeasyPrint.
+
+![Rendering Workflow](/images/render-workflow.drawio.png)
+
+
+## Two rendering engines: Chromium and Weasyprint
+You might be wondering why we combine two rendering engines.
+
+The short answer is: To make it easy to design amazing PDF reports with all features expected from a reporting tool.
+
+And here is the long answer:
+
+The rendering workflow may seem to require lot of resources and slow since we utilize Chromium.
+
+Yes, this approach is resource-intensive and rendering can take some time (typically between 3 and 10 seconds depending on the complexity and size of the report).
+However, we want to note that Chromium is not solely responsible for this.
+On average, the VueJS rendering with Chromium takes about 1 second. The remaining time is required by WeasyPrint to generate the PDF.
+
+## WeasyPrint supports advanced CSS printing rules
+You may be wondering why we didn't just use Chromium to generate the PDFs and added the slow WeasyPrint.
+While Chromium does have a print to PDF feature, it is only suitable for printing web pages to save their content and is not ideal for creating aesthetically pleasing PDFs.
+This is because Chromium has not implemented many CSS rules that are specific to the CSS printing spec, which are crucial for printing and generating PDFs. On the other hand, WeasyPrint was designed specifically to render PDFs and supports many of these printing CSS rules.
+
+## Server-side rendering renders in a single pass
+You may also be wondering why we chose to use VueJS with Chromium instead of a simpler or faster template engine to render HTML.
+Most template engines are designed for server-side rendering.
+They process the template from start to end, insert variables, evaluate expressions, iterate through loops and output the final HTML.
+Everything is rendered in a single pass.
+
+
+## Complex documents need multi-pass rendering
+Let's consider following scenario:
+You are designing a pentest report. It contains a fancy title page, management summary, section with static text (e.g. disclaimer) and list of findings.
+You had an interesting pentest and found many vulnerabilities, the report grows in size.
+It is already 50 pages long and its hard to have an overview.
+Therefore, you want to add a table of contents to the beginning.
+With single-pass rendering, you would have to generate the table of contents upfront before everything else, because it is on top of the template.
+
+This is not very flexible because the table of contents may contain sections with static texts defined in HTML, finding list, conditional sections that are rendered only in some situations (e.g. list of figures hidden when there are no figures in the report, optional appendix section for portscan results, etc.) or you might event want to include sections/headlines from markdown fields.
+
+## Manual handling of dynamic references is error-prone
+When generating the table of contents you would have to make sure that you do not forget anything.
+Manually syncing the table of contents with the actual chapters is error-prone, especially, when make quick changes after some time and forget to update the table of contents.
+
+It would be better and more convenient when the table of contents is automatically generated from the renderd HTML content, such that includes all chapters from static text, dynamic finding lists and even markdown text.
+The same problem applies for all kind of lists that should be auto-generated based on the content.
+Another examples are list of figures or list of tables.
+They should include all figures or tables from the whole document.
+Figures might occur in static texts from the design or markdown fields of sections or findings.
+All should appear in the list of figures, regardless of their source.
+
+## Multi-pass: Render chapters first, then the references
+In order to achive that, we need multi-pass rendering:
+First render the actual chapters, in the second pass collect the defined chapters and render the table of contents.
+In LaTeX you need to compile at least twice for all references to be correct (table of contents, bibliography, citings, etc.).
+
+## VueJS is super-dynamic...
+Here is where VueJS comes into play. Vue and other client-side JavaScript frameworks (React, Angular, etc.) are designed to be reactive.
+When some state changes or users interact with the website (user inputs, clicking, DOM events, etc.), the framework re-renders the HTML.
+It natively supports re-rendering parts of the template and therefore we can easily achieve multi-pass rendering.
+With Vue we can re-render the table of contents and other references until nothing changes anymore.
+
+## ...and delivers a great ecosystem with additional features
+Besides multi-pass rendering, Vue (and JS) are client-side technologies with a great ecosystem of UI libraries, such as [charts](/designer/charts).
+We can reuse these libraries for PDF rendering and take advantage of existing, mature, actively maintained and well-documented UI libraries.
diff --git a/docs/docs/insights/security-considerations.md b/docs/docs/insights/security-considerations.md
new file mode 100644
index 000000000..951769117
--- /dev/null
+++ b/docs/docs/insights/security-considerations.md
@@ -0,0 +1,42 @@
+# Security Considerations
+
+## Template Injection
+SysReptor uses server-side rendering for generating PDF reports.
+This allows template injection attackers.
+
+This is intentional.
+
+The template injection is sandboxed in a dedicated Chromium process.
+Chromium is running in offline mode. It has no possibility to connect to remote locations.
+It requires an exploit in the Chromium browser to get access to the container.
+
+:octicons-cloud-24: Cloud · The Chromium process in our cloud is isolated in a dedicated Kubernetes pod. It receives rendering jobs via RabbitMQ, completes its rendering process and shuts down. An attacker breaking out of the Chromium process could prevent it from shutting down until a defined timeout. However, it would **not** be able to receive further rendering jobs.
+
+:octicons-server-24: Self-Hosted · The Chromium process in self-hosted environments runs in an isolated process in the web application's docker container by default. An attacker breaking out of the Chromium process can also compromise the web application. However, it is possible to outsource the rendering process into a dedicated docker container. This requires two additional docker containers: Chromium and RabbitMQ. However, the Chromium process will be able to receive further rendering jobs (in contrast to the cloud setup).
+For resource reasons, we do not use this setup in the standard installation.
+
+
+## Denial of Service (DoS)
+PDF rendering is a long-running and resource intensive process.
+Especially WeasyPrint can sometimes be slow when rendering long and complex reports.
+
+Attackers can inject long-running instructions (via Vue, HTML or CSS) in templates. This might cause DoS of the rendering process.
+A timeout cancels the Vue template rendering process as soon as rendering time reaches a certain threshold.
+
+DoS prevention is currently not implemented for WeasyPrint.
+For now, we accept the risk of DoS in WeasyPrint, since we do not want to prevent rendering long and complex reports which might take some time and system resources.
+
+This behavior might change in the future.
+
+## Server-Side Request Forgery Prevention
+All Requests to external systems from within the rendering workflow are blocked.
+This prevents data exfiltration to external systems if attackers inject templates or if there are vulnerabilities in third-party JS libraries.
+
+This is ensured by two measures:
+
+1. The headless Chromium instances uses the offline mode. This simulates that the browser is offline and blocks all outgoing requests.
+2. For WeasyPrint, we use a custom URL fetcher. This prevents requests to external systems.
+ It allows `data:`-URLs and access to files uploaded to SysReptor (designer assets, images) only.
+ No HTTP requests are involved when including these resources (neither to localhost),
+ but a custom handler that returns the resources as data following the
+ [WeasyPrint security recommendations](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#security){ target=_blank }.
diff --git a/docs/docs/install.sh b/docs/docs/install.sh
new file mode 100644
index 000000000..1051c12c5
--- /dev/null
+++ b/docs/docs/install.sh
@@ -0,0 +1,150 @@
+#!/bin/bash
+set -e # exit on error
+
+echo "Good to see you."
+echo "Get ready for the easiest pentest reporting tool."
+echo ""
+
+error=1
+docker=1
+for cmd in curl openssl tar uuidgen docker "docker compose" sed
+do
+ if
+ ! command -v $cmd >/dev/null
+ then
+ echo "Error: $cmd is not installed."
+ if
+ [[ $cmd = docker* ]]
+ then
+ docker=0
+ fi
+ error=0
+ fi
+done
+if
+ test 0 -eq "$docker"
+then
+ echo "Follow the installation instructions at https://docs.docker.com/engine/install/ubuntu/"
+fi
+if
+ test 0 -eq "$error"
+then
+ exit -1
+fi
+if
+ docker --version | grep podman
+then
+ echo "Error: You have podman installed. Please install official Docker instead."
+ echo "Follow the installation instructions at https://docs.docker.com/engine/install/ubuntu/"
+ exit -3
+fi
+
+if
+ docker volume inspect sysreptor-app-data 1>/dev/null 2>&1 || docker volume inspect sysreptor-db-data 1>/dev/null 2>&1
+then
+ echo "Old SysReptor volumes exist."
+ echo "Remove volumes if you don't need them any more. (This will delete all your data: \"docker rm -f sysreptor-app sysreptor-db && docker volume rm -f sysreptor-app-data sysreptor-db-data\")"
+ echo "Want to update instead? See: https://docs.sysreptor.com/setup/updates/"
+ exit -4
+fi
+
+download_url=https://github.com/syslifters/sysreptor/releases/latest/download/source-prebuilt.tar.gz
+echo "Downloading SysReptor from $download_url ..."
+curl -s -L --output sysreptor.tar.gz "$download_url"
+echo "Checking download..."
+if
+ ! tar -tzf sysreptor.tar.gz >/dev/null 2>&1
+then
+ echo "Download did not succeed..."
+ exit -5
+fi
+echo "Unpacking sysreptor.tar.gz..."
+tar xzf sysreptor.tar.gz
+
+cd sysreptor/deploy
+if
+ test -f app.env
+then
+ echo "deploy/app.env exists. Will not create new secrets."
+else
+ echo "Creating app.env..."
+ cp app.env.example app.env
+
+ echo "Generating secret key..."
+ secret_key="SECRET_KEY=\"$(openssl rand -base64 64 | tr -d '\n=')\""
+ sed -i'' -e "s#.*SECRET_KEY=.*#$secret_key#" app.env
+
+ echo "Generating data at rest encryption keys..."
+ KEY_ID=$(uuidgen)
+ encryption_keys="ENCRYPTION_KEYS=[{\"id\": \"${KEY_ID}\", \"key\": \"$(openssl rand -base64 32)\", \"cipher\": \"AES-GCM\", \"revoked\": false}]"
+ default_encryption_key_id="DEFAULT_ENCRYPTION_KEY_ID=\"${KEY_ID}\""
+ sed -i'' -e "s#.*ENCRYPTION_KEYS=.*#$encryption_keys#" app.env
+ sed -i'' -e "s#.*DEFAULT_ENCRYPTION_KEY_ID=.*#$default_encryption_key_id#" app.env
+fi
+if [ -n "$SYSREPTOR_LICENSE" ]
+then
+ echo "Adding your license key..."
+ sed -i'' -e "s#.*LICENSE=.*#LICENSE='$SYSREPTOR_LICENSE'#" app.env
+else
+ echo "No license key found. Going with Community edition."
+fi
+
+echo "Creating docker volumes..."
+echo -n "Volume: "
+docker volume create sysreptor-db-data
+echo -n "Volume: "
+docker volume create sysreptor-app-data
+
+echo "Build and launch SysReptor via docker compose..."
+echo "We are downloading and installing all dependencies."
+echo "This may take a few minutes."
+
+if [ -n "$SYSREPTOR_LICENSE" ]
+then
+ compose_args=""
+else
+ compose_args="-f docker-compose.yml"
+fi
+
+if
+ ! docker compose $compose_args up -d
+then
+ echo "Ups. Something did not work while bringing up your containers."
+ exit -2
+fi
+
+echo "Running migrations..."
+while
+ sleep 1
+ ! echo '' | docker compose exec --no-TTY app python3 manage.py migrate --check 1>/dev/null 2>&1
+do
+ true
+done
+
+echo "Great! Everything seems to be up now."
+echo ""
+
+echo "Setting up initial data..."
+echo "Creating initial user..."
+password=`openssl rand -base64 20 | tr -d '\n='`
+echo '' | docker compose exec --no-TTY -e DJANGO_SUPERUSER_USERNAME="reptor" -e DJANGO_SUPERUSER_PASSWORD="$password" app python3 manage.py createsuperuser --noinput
+echo "Importing demo projects..."
+url="https://docs.sysreptor.com/assets/demo-projects.tar.gz"
+curl -s "$url" | docker compose exec --no-TTY app python3 manage.py importdemodata --type=project --add-member=reptor 2>/dev/null
+echo "Importing demo designs..."
+url="https://docs.sysreptor.com/assets/demo-designs.tar.gz"
+curl -s "$url" | docker compose exec --no-TTY app python3 manage.py importdemodata --type=design 2>/dev/null
+echo "Importing finding templates..."
+url="https://docs.sysreptor.com/assets/demo-templates.tar.gz"
+curl -s "$url" | docker compose exec --no-TTY app python3 manage.py importdemodata --type=template 2>/dev/null
+echo "All imported."
+
+echo ""
+echo "Very nice."
+echo "You can now login at http://127.0.0.1:8000"
+echo "Username: reptor"
+echo "Password: $password"
+echo ""
+echo "This was easy, wasn't it?"
+echo "We recommend to setup a web server with HTTPS."
+echo "Find instructions at: https://docs.sysreptor.com/setup/webserver/"
\ No newline at end of file
diff --git a/docs/docs/license.md b/docs/docs/license.md
new file mode 100755
index 000000000..67f1a211e
--- /dev/null
+++ b/docs/docs/license.md
@@ -0,0 +1,118 @@
+# SysReptor Community License 1.0 (SysReptorL)
+## Acceptance
+
+In order to get any Permissions to Use the Software under the
+SysReptorL, you must agree to it as both strict obligations
+and conditions to all your Licenses.
+
+## Copyright License
+
+The licensor grants you a non-exclusive copyright Permission
+to Use the Software for everything you might do with the Software
+that would otherwise infringe the licensor's copyright in it for
+any permitted purpose, other than distributing the software or
+making changes or new works based on the Software. Attempts to
+circumvent technical License restrictions are prohibited (e.g.
+to unlock or extend functionalities), even if they result from
+errors in the Software. You must not use a license key that you
+did not obtain from us or an authorized reseller.
+
+## Patent License
+
+The licensor grants you a non-exclusive patent License for the
+Software that covers patent claims the licensor can license, or
+becomes able to license, that you would infringe by using the
+Software after its Intended Use.
+
+## Internal Business Use
+
+Use of the Software for the internal business operations of
+you and your Company is use for a permitted purpose.
+
+## Personal Uses
+
+Personal use for research, experiment, and testing for the
+benefit of public knowledge, personal study, private entertainment,
+hobby projects, amateur pursuits, or religious observance,
+without any anticipated commercial application, is use for a
+permitted purpose.
+
+## Fair Use
+
+You may have "Fair Use" rights for the Software under the law.
+The SysReptorL does not limit them unless otherwise agreed.
+
+Pursuant to Section 40d of the Act on Copyright and Related
+Rights (Urheberrechtsgesetz, UrhG), computer programs may be
+edited and reproduced within the framework of the Fair Use of
+works to the extent that this is necessary for the Intended
+Use of the Software by the person entitled to use it. The
+Intended Use is limited to the permitted purpose of the Software
+in accordance with the SysReptorL.
+
+## No Other Rights
+
+The SysReptorL does not allow you to sublicense or transfer
+any of your Licenses to anyone else or prevent the licensor
+from granting Licenses to anyone else. The SysReptorL does not
+imply any other Licenses than those mentioned therein.
+
+## Patent Defense
+
+If you make any written claim that the Software infringes or
+contributes to infringement of any patent, your patent License
+for the Software granted under this SysReptorL ends immediately. If
+your Company makes such a claim, your patent License ends
+immediately for work on behalf of your Company. Irrespective of the
+withdrawal of Permission to Use the Software, we reserve the right
+to assert claims for damages.
+
+## Violations
+
+The first time you are notified in writing that you have
+violated any of these terms, or done anything with the software
+not covered by your licenses, your licenses can nonetheless
+continue if you come into full compliance with these terms,
+and take practical steps to correct past violations, within
+32 days of receiving notice. Otherwise, all your licenses
+end immediately.
+
+## No Liability
+
+***As far as the law allows, the Software comes “as is”, without
+any warranty or condition, and the licensor will not be liable
+to you for any damages arising out of this SysReptorL or the use
+or nature of the Software, under any kind of legal claim.***
+
+## Definitions
+
+The SysReptor Community License 1.0 (**SysReptorL**) is granted by
+Syslifters GmbH, FN 578505v, registered office Göllersdorf
+(**Syslifters**; **we**; **licensor**) to **you**.
+
+**License**: Is the overall term for the authorization to use the
+Software. The term "License" says nothing about the copyright
+classification.
+
+**Software**: is the software the licensor makes available under
+these terms.
+
+**Permission to Use the Software** (*Werknutzungsbewilligung*):
+Non-exclusive copyright Permission to Use the Software. **Use**
+means anything you do with the software requiring one of your
+licenses.
+
+**Your Company**: Is any legal entity, sole proprietorship, or
+other kind of organization that you work for, plus all organizations
+that have control over, are under the control of, or are under common
+control with that organization. **Control** means ownership of
+substantially all the assets of an entity, or the power to direct its
+management and policies by vote, contract, or otherwise. Control can
+be direct or indirect.
+
+**Your licenses** are all the licenses granted to you for the
+software under these terms.
+
+
+------------
+**Last Updated:** 24 March 2023
\ No newline at end of file
diff --git a/docs/docs/n/hello-dorker.md b/docs/docs/n/hello-dorker.md
new file mode 100755
index 000000000..2a1389d7d
--- /dev/null
+++ b/docs/docs/n/hello-dorker.md
@@ -0,0 +1,98 @@
+---
+exclude: yes
+title: ""
+search:
+ exclude: true
+---
+
+
+
+ We have no for you.
+
+ But an awesome Pentest Reporting tool.
+
+
+
+
+
+
+ SysReptor makes Pentest Reporting easy.
+ Design your report in HTML.
+ Render to PDF.
+ On-Premises.
+ Or Cloud.
+ ❤
+
+
+
[:rocket: Get it for Free](/features-and-pricing){ .md-button }
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/docs/n/this_directory_should_not_be_indexed b/docs/docs/n/this_directory_should_not_be_indexed
new file mode 100755
index 000000000..e69de29bb
diff --git a/docs/docs/offsec-reporting-with-sysreptor.md b/docs/docs/offsec-reporting-with-sysreptor.md
new file mode 100755
index 000000000..b632bbf88
--- /dev/null
+++ b/docs/docs/offsec-reporting-with-sysreptor.md
@@ -0,0 +1,120 @@
+# OffSec Reporting
+
+Use our cloud service for free to write your OffSec OSCP, OSWP, OSEP, OSWA, OSWE, OSED, OSMR, OSEE, OSDA reports.
+
+💲 Free
+📝 Write in markdown
+⚙️ Render your report to PDF
+🛡️ OSCP, OSWP, OSEP, OSWA, OSWE, OSED, OSMR, OSEE, OSDA
+🚀 Fully customizable
+🎉 No need for Word
+👌 No local software troubleshooting
+
+
[:rocket: Sign Up Now](https://oscp.sysreptor.com/oscp/signup/){ .md-button }
+
Already have an account? [Login here.](https://labs.sysre.pt){ target=_blank }
+
+
+*The cover pages are based on noraj's great OSCP LaTeX templates. The structure follows the official OffSec reports (with kind permission by OffSec).
+
+## Creating an OSCP Exam Report
+
+
+
+ Not happy with our solution?
+ [:material-tools: Check out alternatives](/oscp-reporting-tools/){ .md-button target="_blank" }
+
+- :octicons-browser-16:{ .lg .middle } __SysReptor's Online Reporting__
+
+ ---
+ Easy pentest reporting tailored to OSCP reports.
+
+ __Pro tip:__ 🔥 Lowest reporting efforts online!
+
+ [:octicons-arrow-right-24: Sign up and start off](/oscp-reporting-with-sysreptor/)
+
+- :material-laptop:{ .lg .middle } __SysReptor Self-Hosted__
+
+ ---
+ Easy pentest reporting without cloud.
+
+ __Pro tip:__ 🔥 Run everything local.
+
+ [:octicons-arrow-right-24: Easy Peasy Lemon Squeezy.](/oscp-reporting-with-sysreptor/#prefer-self-hosting)
+
+- :material-file-word-box:{ .lg .middle } __Microsoft Word__
+
+ ---
+
+ Use the official templates from "OffSec".
+
+ __Pro tip:__ We all love Word. Don't we? :thinking:
+
+ [:octicons-arrow-right-24: Get your Word-Foo ready](https://help.offsec.com/hc/en-us/articles/360046787731-PEN-200-Reporting-Requirements){ target=_blank }
+
+
+- :simple-markdown:{ .lg .middle } __Markdown to LaTeX__
+
+ ---
+
+ Compile your markdown with pandoc and noraj's LaTeX-template.
+
+ __Pro tip:__ Upload template to [Overleaf](https://www.overleaf.com/){ target=_blank } and compile online!
+
+ [:octicons-arrow-right-24: git clone your template](https://github.com/noraj/OSCP-Exam-Report-Template-Markdown){ target=_blank }
+
+- :octicons-server-16:{ .lg .middle } __Dradis self-hosted__
+
+ ---
+
+ Get the OSCP Exam Report Kit from Dradis.
+
+ __Pro tip:__ Most functionality included by Dradis Professional!
+
+ [:octicons-arrow-right-24: Connect to localhost](https://dradisframework.com/academy/industry/compliance/oscp/){ target=_blank }
+
+
+
+
+
+## Creating an OSCP Exam Report with SysReptor
+
+
+
+
[:rocket: Sign Up to SysReptor](https://oscp.sysreptor.com/oscp/signup/){ .md-button }
+
+
+Make sure to test your tool of choice before the exam.
+
+You know other tools that work well for OSCP reporting?
+Please write us to hello@syslifters.com.
diff --git a/docs/docs/reporting/archiving.md b/docs/docs/reporting/archiving.md
new file mode 100644
index 000000000..43c8a0a05
--- /dev/null
+++ b/docs/docs/reporting/archiving.md
@@ -0,0 +1,330 @@
+# Archiving
+:octicons-heart-fill-24: Pro only
+
+This page describes how SysReptor archives and encrypts old pentest projects.
+It gives an overview of the cryptographic architecture used to protect archives and explains the motivations behind.
+
+
+## Motivation - Why do we need to archive pentest projects?
+As a penetration tester, you know how important it is to keep your pentest data safe and secure.
+It contains highly sensitive data such as vulnerabilities of customer systems and how to exploit them.
+Sometimes it takes some time to fix the vulnerabilities (or they marked it as "risk accepted").
+It's crucial to safeguard pentest reports and pentest data to protect your customer's systems from malicious actors.
+In fact, you may even have signed an NDA or be subject to contractual penalties if this data is stolen, leaked, or published.
+
+But what happens when a pentest is completed and you no longer need to access that data on a regular basis?
+The most secure option is to delete all data associated with the pentest.
+However, this is often not possible.
+The report and pentest evidence (e.g. burp state, command history, scripts, etc.) have to be kept for the purposes of proof of work and warranty.
+
+Old pentest data should not be stored in plaintext. Instead they should be encrypted.
+Restricting access with a permissions is not sufficient,
+since a system administrator or service provider, for example, could also have access to the data.
+Access restrictions must be enforced through the use of cryptography.
+
+When encrypting pentest data, the question araises who will be able to decrypt the archive again?
+Balancing confidentiality with availability is an important question.
+Here are some thoughts to consider:
+
+* Data is secure if no one can decrypt it anymore: This is certainly true, but it's important to remember that encryption is only one aspect of data security.
+ There are other factors to consider, such availability.
+ If no one can decrypt the data, it may become unavailable when it's needed, which can be a problem.
+* What happens if someone leaves the company or loses the key?
+ * If one key is used to encrypt all pentest archives, this key may not get lost. Else all data is inaccessible.
+ * If a different key is used to encrypt pentest archives (i.e. one key per archive), and they are all managed by the same person (e.g. in a password manager) and this person leaves the company, forgets the master key or dies. Again, everything is lost.
+ * If archives are encrypted with multiple keys and these keys are distributed to different persons, when one person loses their key, you have the same problem. And once again, everything is lost.
+* Should one person be able to decrypt everything alone?
+ To prevent unavailability through key loss, you can give the key to multiple persons.
+ Or you can also design an archiving system where each pentest archive is encrypted with multiple keys and each key is given to a different person.
+ Now everyone is able to decrypt all data on their own.
+
+Consider you are a pentesting team of four persons.
+The optimal compromise between confidientiality and availablity of pentest archives would be to require two persons to access pentest archives.
+This prevents losing all data when one person (or even a second person) loses their key.
+It also prevents everyone from accessing all data (e.g. if a key is compromizes or someone leaves the company and wants to steals all data) alone.
+At least two persons are required, thus enforcing a 4-eye principle.
+
+
+
+## Crypto Architecture
+We use a threshold cryptography scheme in combination with key management based on public key cryptography to cryptographically enforce the 4-eye principle.
+
+The core component to cryptographically enforce the 4-eye principle is Shamir Secret Sharing.
+Shamir Secret Sharing is a threshold sheme for sharing a secret to a group of _n_ people whereas _k_ people are required to work together to reconstruct the secret.
+The secret is split into _n_ shares and every user is given one share. The threshold _k_ defines how many shares are required to reconstruct the secret.
+Shamir Secret Sharing has the property of information-theoretic security, meaning that even if an attacker steals some shares, it is impossible for the attacker to reconstruct the secret unless they have stolen _k_ number of shares. No information about the secret can be gained from any number of shares below than the threshold
+
+In order to enforce a 4-eye principle to restore encrypted pentest archives, the threshold needs to be _k=2_.
+However it is possible to increase the threshold _k_ to require 3 or more users for restoring pentest archives for larger companies.
+
+Shamir Secret Sharing only allows splitting secrets into shares. It does not handle encryption or key management.
+We use Shamir Secret Sharing for splitting an AES-Key into multiple Key Shares.
+Each Key Share is assigned to a different user.
+
+Key Shares are encrypted with user's public keys.
+The private keys are managed offline by users themselves.
+You can use software keys generated on your computer, but also security tokens such as YubiKeys that generate keys on hardware.
+Public-key cryptography allows users to create pentest project archives where multiple users have access, without requiring user interaction.
+For decrypting, user interaction is required. Each user has to decrypt their own Shamir Key Share with their private key.
+
+We use OpenPGP for public key encryption, because it supports RSA and elliptic curves and offers support for hardware tokens such as YubiKeys.
+OpenPGP is a secure, established and trustworthy crypto protocol with great tooling support.
+It is more user-friendly than using plain openssl and YubiKey CLI tools, and more trustworthy than custom developed crypto tools.
+
+Offloading cryptographic operations to hardware tokens such as YubiKeys is considered more secure than using software based encryption,
+because the secret key is generated on hardware and never leaves the device.
+This prevents the private key from being leaked or exported.
+The downside, however, is that it cannot be backed up. If you lose the hardware token, the encrypted data is inaccessible.
+This is why we support multiple public/private key pairs per user.
+For example if you use two public keys stored on hardware tokens and if you lose one, you can still restore archives with the second one.
+
+
+Following diagram outlines the process of archiving and encrypting a pentest project:
+![Archive and encrypt pentest project](/images/archiving-crypto.drawio.png)
+
+
+1. Export all project data to a tar.gz archive.
+ This is the same format as directly exporting projects via the web interface.
+ All project data, sections, findings, notes, images, files including the design are exported.
+2. The tar.gz archive is encrypted with 256-bit AES-GCM. A random key is generated for each archive.
+ AES-GCM is an authenticated cipher mode (AEAD). Besides encrypting the data, a authentication tag is calculated which is able to detect modifications and corruptions of encrypted data, adding integrity-protection of the ciphertext.
+ The encrypted archive is stored in a file storage ([ARCHIVED_FILE_STORAGE](/setup/configuration#archiving)).
+3. The AES-key is distributed to multiple users with Shamir Secret Sharing.
+4. The Key Shares are encrypted with randomly generated 256-bit AES-GCM keys. Each Key Share is encrypted with a different key.
+ Plain Shamir Secret Sharing does not offer integrity-protection of Key Shares and does not detect if a Key Share used for decryption is valid or not.
+ This step adds integrity protection of Key Shares with the AES-GCM (and confidentiality protection with encryption).
+ The encrypted Key Shares are stored in the database.
+5. The AES keys are encrypted with user's public keys.
+
+
+
+## How to use
+
+### Prerequisite: Register user public keys
+Before users are able to archive pentest projects, all archiving users have to register their public keys.
+Public Keys need to be generated offline and uploaded to the user profile.
+
+SysReptor uses OpenPGP encryption keys as the public key format.
+RSA and elliptic curve keys are supported.
+Minimum key lengths are enforced to ensure a sufficient security level for some years.
+For RSA, the minimum accepted key length is 3072 bit.
+For elliptic curve, the minium curve size is 256 bit.
+
+=== "Generate private keys with GPG"
+
+ Use following commands to generate an elliptic curve encryption key with `gpg`.
+ Be sure to protect the key with a strong password and make backups.
+ If you lose all your private keys, you can no longer restore archives.
+
+ ```
+ cat << EOF > config.txt
+ Key-Type: ECDSA
+ Key-Curve: nistp521
+ Subkey-Type: ECDH
+ Subkey-Curve: nistp521
+ Subkey-Usage: encrypt
+ Expire-Date: 0
+ Name-Comment: SysReptor Archiving
+ Name-Real:
+ Name-Email:
+ EOF
+ gpg --batch --generate-key config.txt
+
+ gpg --list-secret-keys --keyid-format=long
+ gpg --armor --export
+ ```
+
+=== "Generate private keys on YubiKey 5"
+
+ Use the following command to generate a new Elliptic Curve key pair on a YubiKey 5.
+ The private key is generated on the YubiKey and never leaves the device.
+ Beware that you cannot backup the key. We recommend that you add a second key as a fallback in case you lose your YubiKey.
+
+
+ ```
+ gpg --card-edit
+
+ Reader ...........: Yubico YubiKey FIDO CCID 00 00
+ Application ID ...: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ Application type .: OpenPGP
+ Version ..........: 3.4
+ Manufacturer .....: Yubico
+ Serial number ....: 19763721
+ Name of cardholder: [not set]
+ Language prefs ...: [not set]
+ Salutation .......:
+ URL of public key : [not set]
+ Login data .......: [not set]
+ Signature PIN ....: not forced
+ Key attributes ...: rsa2048 rsa2048 rsa2048
+ Max. PIN lengths .: 127 127 127
+ PIN retry counter : 3 0 3
+ Signature counter : 0
+ KDF setting ......: off
+ UIF setting ......: Sign=off Decrypt=off Auth=off
+ Signature key ....: [none]
+ Encryption key....: [none]
+ Authentication key: [none]
+ General key info..: [none]
+
+ gpg/card> admin
+ Admin commands are allowed
+
+ # Change Yubikey Pin (optional)
+ # Hint: default pin is 123456, default admin pin is 12345678
+ gpg/card> passwd
+ gpg: OpenPGP card no. XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX detected
+
+ 1 - change PIN
+ 2 - unblock PIN
+ 3 - change Admin PIN
+ 4 - set the Reset Code
+ Q - quit
+
+ Your selection? 3
+ PIN changed.
+
+ 1 - change PIN
+ 2 - unblock PIN
+ 3 - change Admin PIN
+ 4 - set the Reset Code
+ Q - quit
+
+ Your selection? 1
+ PIN changed.
+
+ 1 - change PIN
+ 2 - unblock PIN
+ 3 - change Admin PIN
+ 4 - set the Reset Code
+ Q - quit
+
+ Your selection? Q
+
+ gpg/card> name
+ Cardholder's surname: {{ $auth.user.last_name }}
+ Cardholder's given name: {{ $auth.user.first_name }}
+
+ # Change key type to elliptic curve (optional)
+ gpg/card> key-attr
+ Changing card key attribute for: Signature key
+ Please select what kind of key you want:
+ (1) RSA
+ (2) ECC
+ Your selection? 2
+ Please select which elliptic curve you want:
+ (1) Curve 25519 *default*
+ (4) NIST P-384
+ Your selection? 1
+ The card will now be re-configured to generate a key of type: ed25519
+ Note: There is no guarantee that the card supports the requested
+ key type or size. If the key generation does not succeed,
+ please check the documentation of your card to see which
+ key types and sizes are supported.
+ Changing card key attribute for: Encryption key
+ Please select what kind of key you want:
+ (1) RSA
+ (2) ECC
+ Your selection? 2
+ Please select which elliptic curve you want:
+ (1) Curve 25519 *default*
+ (4) NIST P-384
+ Your selection? 1
+ The card will now be re-configured to generate a key of type: cv25519
+ Changing card key attribute for: Authentication key
+ Please select what kind of key you want:
+ (1) RSA
+ (2) ECC
+ Your selection? 2
+ Please select which elliptic curve you want:
+ (1) Curve 25519 *default*
+ (4) NIST P-384
+ Your selection? 1
+ The card will now be re-configured to generate a key of type: ed25519
+
+ # Generate key pair
+ gpg/card> generate
+ Make off-card backup of encryption key? (Y/n) n
+ Please specify how long the key should be valid.
+ 0 = key does not expire
+ = key expires in n days
+ w = key expires in n weeks
+ m = key expires in n months
+ y = key expires in n years
+ Key is valid for? (0) 0
+ Key does not expire at all
+ Is this correct? (y/N) y
+
+ GnuPG needs to construct a user ID to identify your key.
+
+ Real name:
+ Email address:
+ Comment: SysReptor Archiving Key
+ You selected this USER-ID:
+ " (SysReptor Archiving Key) "
+
+ Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
+ public and secret key created and signed.
+
+ gpg/card> quit
+
+ gpg --list-secret-keys --keyid-format=long
+ gpg --armor --export
+ ```
+
+
+During public key registration, you have to prove that you own the private key.
+A random verification message is generated and encrypted with the public key.
+You have to decrypt it with your private keys to prove that you own the private key and know how to decrypt data.
+
+
+
+### Archive Project
+Pentest projects first have to be marked as finished, then they can be archived.
+
+Before the archive is created and encrypted, all users are displayed that will have access to the archive and are able to restore it.
+This includes all project members and global archivers.
+Global archivers are added to every archived project and can be considered archiving backup users.
+Users can be marked as global archivers in the user permission settings.
+
+If too few users (below threshold) are project members or global archivers or do not have any public keys, archiving is not possible.
+
+![Archive project](/images/archive-create.png)
+
+
+### Restore Archived Projects
+Archived projects are restored when the required number of users decrypt their key share with their private keys.
+Users decrypt their key shares separately, independently of each other.
+When the user threshold is reached, the archived project is restored.
+
+![Restore archived project](/images/archive-restore.png)
+![Restore archived project](/images/archive-restore2.png)
+
+All users should restore their key parts within 3 days.
+When some users decrypted their key shares, but others did not, the archive is reset.
+Decrypted key shares are deleted, meaning that users have to decrypt their key shares again later with their public keys.
+This prevents partly dearchived projects being stored in the database forever, lowering the required user threshold when archives are actually restored.
+
+
+### Threshold Recommendations
+The recommended Shamir Secret Sharing threshold _k_ is about half the number of users _n_, but at least 2.
+This ensures the best combination of confidentiality and availability.
+For large teams (e.g. >5 global archivers), you might want to use a _k_ below _n / 2_ to not require as many users for restoring archives.
+
+Note that not every user is added to an archive.
+Only project members and global archivers with public keys are added to archives and are able to access them.
+
+Example: You are a large pentesting company with 100 users.
+A finished project should be archived, where 3 pentesters are project members.
+Only the 3 project members and (lets say) 2 global archivers will be added to the archive.
+
+Our recommendations:
+
+* _n = 1_ users: _k = 1_ recommended
+* _n = 2_ users: _k = 1_ or _k = 2_ recommended
+* _n = 3_ users: _k = 2_ recommended
+* _n = 4_ users: _k = 2_ recommended
+* _n = 5_ users: _k = 2_ recommended
+* _n = 10_ users: _k = 3_ or _k = 4_ recommended
+
+The threshold value is configured globally per instance by the settings [ARCHIVING_THRESHOLD](/setup/configuration#archiving).
diff --git a/docs/docs/reporting/locking.md b/docs/docs/reporting/locking.md
new file mode 100755
index 000000000..ae5256c88
--- /dev/null
+++ b/docs/docs/reporting/locking.md
@@ -0,0 +1,18 @@
+# Locking of Findings and Sections
+When multiple pentesters are working simultanuously on section, one pentester might overwrite changes of the other pentester.
+Therefore, we lock sections while one pentester is working on them.
+
+
+
+As soon as the pentester closes the tab or switches to another issue, the lock releases. In case of a sudden network interruption, we release the lock after 90 seconds of inactivity. Note that in this case, unsaved data might be overwritten.
+
+We also lock sections if a pentester tries to edit a finding in a second tab. This might also lead to data loss if he overwrites previously written content. In this case, the pentester can claim the lock by hitting "Edit Anyway".
+
+
+
diff --git a/docs/docs/reporting/markdown-features.md b/docs/docs/reporting/markdown-features.md
new file mode 100755
index 000000000..debeba692
--- /dev/null
+++ b/docs/docs/reporting/markdown-features.md
@@ -0,0 +1,188 @@
+# Markdown Features
+The markdown syntax used in this project is based on the [CommonMark spec](https://spec.commonmark.org/){ target=_blank} with some extensions.
+
+This document briefly describes the most important markdown syntax.
+Non-standard markdown syntax is described more detailed.
+
+## Common Markdown
+~~~md linenums="1"
+# Heading h1
+## Heading h2
+### Heading h3
+#### Heading h4
+
+Inline text styling: **bold**, _italic_, ~~strikethrough~~, `code`
+
+Links: [Example Link](https://example.com)
+
+* list
+* items
+ * nested list
+
+1. numbered
+2. list
+
+```bash
+echo "multiline code block";
+# with syntax highlighting
+```
+~~~
+
+## Underline
+Underline is not supported in markdown. However you can insert HTML `` tags to underline text.
+
+```md linenums="1"
+Text with underlined content.
+```
+
+## Images
+Images use the standard markdown syntax, but are rendered as figures with captions.
+
+```md linenums="1"
+![Figure Caption](img.png){width="50%"}
+
+![caption _with_ **markdown** `code`](img.png)
+```
+
+``` html linenums="1"
+
+```
+
+## Footnotes
+```md linenums="1"
+Text text^[footnote content] text.
+```
+
+## Tables
+For tables the GFM-like table syntax is used.
+This syntax is extended to support table captions.
+
+```md linenums="1"
+| table | header |
+| ------- | ------- |
+| cell | value |
+
+: table caption
+```
+
+Markdown tables are somewhat limited and do not support rowspans, colspans or multiline cell values.
+If you need one of these features, you can fall back to writing tables as [inline HTML](#inline-html).
+
+
+## Code blocks
+Code blocks allow including source code and highlight it.
+
+
+The following example shows how to apply syntax highlighting to a HTTP request.
+Many other programming languages are also supported.
+````md linenums="1"
+```http
+POST /login.php HTTP/1.1
+Host: sqli.example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 33
+
+username='or'1'='1&password=dummy
+```
+````
+
+Syntax highlighting is great for readability, but it only highlights predefined keywords of the specified language.
+However, it does not allow to manually highlight certain text parts to draw the readers attention to it.
+
+You can enable manual highlighting by adding code-block meta attribute `highlight-manual`.
+It is now possible to encapsulate highlighted areas with `§§highlighted content§§`.
+In the rendered HTML code, the content inside the two `§§`-placeholders is wrapped by a HTML `` tag.
+This works in combination with language-based syntax highlighting.
+
+This example highlights the vulnerable POST-parameter `username` in the HTTP body.
+````md linenums="1"
+```http highlight-manual
+POST /login.php HTTP/1.1
+Host: sqli.example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 33
+
+§§username='or'1'='1§§&password=dummy
+```
+````
+
+If you need more advanced highlighting, you can place cutom HTML code inside the `§§` placeholders e.g. `§§`.
+If your code snippet includes `§`-characters, you cannot use them as escape characters for manual highlighting.
+It is possible to specify a different escaple character via the `highlight-manual=""` attribute.
+Make sure that the escape character is not present in the code block.
+
+The following example uses `"|"` as escape character and a custom HTML markup for highlighting.
+````md linenums="1"
+```http highlight-manual="|"
+POST /login.php HTTP/1.1
+Host: sqli.example.com
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 33
+
+||username='or'1'='1||&password=dummy
+```
+````
+
+
+## HTML Attributes
+This extension allows you to set HTML attributes from markdown.
+Place attributes in curly braces directly after the targeted element (without spaces between).
+Attributes are key value pairs (`attr-name="attr-value"`)
+Shortcuts for setting the attribute `id` (`#id-value`) and `class` (`.class-value`) are supported.
+
+```md linenums="1"
+## Headline in Table of Contents {.in-toc .numbered}
+
+![image](img.png){#img-id .image-class1 .image-class2 width="50%"}
+
+Text with [styled link](https://example.com/){class="link-class" style="color: red"} in it.
+```
+
+## Inline HTML
+If something is not possible with markdown, you can fall back to writing HTML code and embed it in the markdown document.
+
+
+Following example shows a figure containing two images side-by-side.
+```md linenums="1"
+Text *with* **markdown** `code`.
+
+
+```
+
+It is also possible to embed markdown inside HTML blocks. An empty line is required as a seperator between HTML and markdown.
+Following example shows a complex table that is not possible with the markdown table syntax.
+
+```md linenums="1"
+Text *with* **markdown** `code`.
+
+
+
+
Col1
+
Col2
+
+
+
+
Sub-header spanning two columns
+
+
+
Cell 1
+
+
+ This cell is rendered as markdown. You can use
+ * `markdown` _elements_
+ * such as **lists** for example
+ * but make sure to seperate HTML and markdown blocks with an empty line
+
+
+
+
+
+```
+
diff --git a/docs/docs/reporting/references.md b/docs/docs/reporting/references.md
new file mode 100644
index 000000000..3fc85613c
--- /dev/null
+++ b/docs/docs/reporting/references.md
@@ -0,0 +1,58 @@
+---
+upcoming:
+ - As you see in [the image below](#sqli) (rendered as "the image below")
+ - As you see in the image below (rendered as "the image below")
+ - See [below](#00000000-0000-0000-0000-000000000000)... (rendered as "below")
+ - See below... (rendered as "below")
+ - Find details in the findings chapter (rendered as "the findings chapter").
+ - Find details in [the findings chapter]"#findings" /> (rendered as "the findings chapter").
+---
+
+# References
+Use the `id`-attributes of HTML elements for referencing items in your report.
+This allows you to reference for example:
+
+* Headings
+* Figures
+* Tables
+* Findings
+* and everything that has an `id`
+
+## Reference Images
+```md linenums="1" title="Markdown"
+![SQL Injection](/assets/name/image.png){#sqli}
+As you see in [](#sqli) (e.g. rendered as "Figure 3")
+```
+
+```html linenums="1" title="HTML"
+
+As you see in (e.g. rendered as "Figure 3")
+```
+
+## Reference Findings
+You need the finding ID of the finding you want to reference.
+
+ 1. Open the other finding that should be linked
+ 2. Copy the last UUID from the URL ![Grab the finding ID](/images/finding_id.png)
+
+```md linenums="1" title="Markdown"
+See [](#00000000-0000-0000-0000-000000000000)... (e.g. rendered as "1.3 SQL injection")
+```
+
+```html linenums="1" title="HTML"
+See ... (e.g. rendered as "1.3 SQL injection")
+```
+
+## Reference Headings
+You can reference headings if your [design supports it](/designer/headings-and-table-of-contents/#referencing-sections-in-text-outside-of-toc).
+
+
+```md linenums="1" title="Markdown"
+# Findings {#findings .in-toc.numbered}
+Find details in []"#findings" /> (rendered as "1 Findings").
+```
+
+```html linenums="1" title="HTML"
+
Findings
+Find details in (rendered as "1 Findings").
+```
\ No newline at end of file
diff --git a/docs/docs/reporting/spell-check.md b/docs/docs/reporting/spell-check.md
new file mode 100644
index 000000000..f87c27095
--- /dev/null
+++ b/docs/docs/reporting/spell-check.md
@@ -0,0 +1,21 @@
+# Spell Check
+:octicons-heart-fill-24: Pro only
+
+We provide spell checking via the Open Source version of [LanguageTool](https://github.com/languagetool-org/languagetool). Language Tool runs isolated from other processes in separate container. The application reaches LanguageTool via REST-API.
+
+## Add Words to Dictionary
+Users can add words to the LanguageTool dictionary.
+
+
+
+This updates the dictionary for all users by default. You can configure your installation to add words to a per-user dictionary.
+Per-user dictionaries are not shared between users. When one user adds an unknown word to his dictionary, it will still be unknown for other users. This is even when they are working on the same project and the same finding.
+
+This is an installation-wide setting. It cannot be configured per user or project.
+
+:octicons-cloud-24: Cloud · Please [contact us](https://docs.syslifters.com/contact-us/){ target=_blank } and we will reconfigure your installation.
+
+:octicons-server-24: Self-Hosted · Set the `SPELLCHECK_DICTIONARY_PER_USER` in your `app.env` to true.
\ No newline at end of file
diff --git a/docs/docs/robots.txt b/docs/docs/robots.txt
new file mode 100755
index 000000000..14b5d79fb
--- /dev/null
+++ b/docs/docs/robots.txt
@@ -0,0 +1,3 @@
+User-agent: *
+User-agent: AdsBot-Google
+Disallow: /n/
\ No newline at end of file
diff --git a/docs/docs/security.txt b/docs/docs/security.txt
new file mode 100755
index 000000000..66ee00bf2
--- /dev/null
+++ b/docs/docs/security.txt
@@ -0,0 +1,4 @@
+Contact: https://docs.syslifters.com/vulnerability-disclosure/
+Expires: 2023-05-30T22:00:00.000Z
+Encryption: https://docs.syslifters.com/assets/aron.cer
+Preferred-Languages: en, de
diff --git a/docs/docs/setup/backups.md b/docs/docs/setup/backups.md
new file mode 100644
index 000000000..5bef6be9b
--- /dev/null
+++ b/docs/docs/setup/backups.md
@@ -0,0 +1,53 @@
+# Backups
+:octicons-heart-fill-24: Pro only
+
+## Create backups
+Create backups via REST API. The backup archive contains a database export and all uploaded files.
+
+### Prerequisites
+Creating backups is a high-privilege operation. Therefore, access to the backup API endpoint is restricted.
+Only [`system`-users](/setup/user-permissions/#system) can access this endpoint in combination with a `BACKUP_KEY`.
+Neither regular users nor superusers have access to the backup API endpoint.
+
+Additionally, you need to configure a `BACKUP_KEY` as environment variable.
+This backup key has to be at least 20 characters long.
+If no `BACKUP_KEY` is configured, the backup API endpoint is disabled.
+
+Optionally, the backup can be encrypted via a 256-bit AES key provided in the HTTP request body.
+
+### API Requests
+```
+# Create backup
+curl -X POST https://sysreptor.example.com/api/v1/utils/backup/ -d '{"key": ""}' -H 'Cookie: sessionid=' -H "Content-Type: application/json" -o backup.zip
+
+# Create encrypted backup
+curl -X POST https://sysreptor.example.com/api/v1/utils/backup/ -d '{"key": "", "aes_key": ""}' -H 'Cookie: sessionid=' -H "Content-Type: application/json" -o backup.zip.crypt
+```
+
+## Restore backups
+Make sure that you have an empty database and empty data directories (i.e. empty docker volumes). Otherwise, you will **lose your old data**.
+
+Make sure that it is the same SysReptor version like the one that was used to create the backup.
+If a different version is used the backup might not be importable, because of a differing database schema.
+
+```bash
+cd deploy
+
+# Optionally decrypt backup using your AES key
+
+# Unpack backup
+unzip backup.zip -d backup
+
+# Restore files
+docker compose up -d
+docker compose cp backup/uploadedassets app:/data/
+docker compose cp backup/uploadedimages app:/data/
+docker compose cp backup/uploadedfiles app:/data/
+docker compose down
+
+# Restore database
+docker compose run app python3 manage.py flush --no-input
+docker compose run app python3 manage.py migrate
+echo "select 'TRUNCATE \"' || tablename || '\" RESTART IDENTITY CASCADE;' from pg_tables where schemaname = 'public' and tablename != 'django_migrations';" | docker compose run --no-TTY app python3 manage.py dbshell -- -t | docker compose run --no-TTY app python3 manage.py dbshell
+cat backup/backup.jsonl | docker compose run --no-TTY app python3 manage.py loaddata --format=jsonl -
+```
diff --git a/docs/docs/setup/configuration.md b/docs/docs/setup/configuration.md
new file mode 100644
index 000000000..d1cddfa87
--- /dev/null
+++ b/docs/docs/setup/configuration.md
@@ -0,0 +1,228 @@
+# Configuration
+`app.env` (located in `deploy` directory) controls the behaviour of your SysReptor installation.
+**Note:** Environment variables in your `docker-compose.yml` override those in your `app.env`.
+
+After making changes, go to `sysreptor/deploy` and restart the containers:
+
+=== "Professional"
+ ```shell linenums="1"
+ docker compose up -d
+ ```
+
+=== "Community"
+ ```shell linenums="1"
+ docker compose -f docker-compose.yml up -d
+ ```
+
+
+:octicons-cloud-24: Cloud · We take care of all configurations. If you want to change anything, please [contact us](https://docs.syslifters.com/contact-us/){ target=_blank }.
+
+## Avaliable Options
+:octicons-server-24: Self-Hosted
+
+### Django Secret Key
+Django server secret key (see https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-SECRET_KEY).
+Make sure this key remains secret.
+
+``` title="Generate random secret key:"
+printf "SECRET_KEY=$(openssl rand -base64 64 | tr -d '\n=')\n"
+```
+
+``` title="Example (regenerate this value!):"
+SECRET_KEY="TODO-change-me-Z6cuMithzO0fMn3ZqJ7nTg0YJznoHiJXoJCNngQM4Kqzzd3fiYKdVx9ZidvTzqsm"
+```
+
+### Data Encryption at Rest
+Encrypt data at rest by configuring an encryption key. This will encrypt sensitive data in your database and files uploaded in your notes (except images).
+
+Database and file storage administrators cannot access encrypted data. The key is held in the web application. Data encryption at rest does not help against malicious actors with access to the web server.
+
+You have to define one `DEFAULT_ENCRYPTION_KEY_ID` which will be used for data encryption. However, you can rotate your keys by defining multiple keys in `ENCRYPTION_KEYS`.
+All specified keys are used for decrypting stored data.
+
+Note that the `DEFAULT_ENCRYPTION_KEY_ID` must be part of `ENCRYPTION_KEYS`.
+
+``` title="Generate random encryption keys:"
+KEY_ID=$(uuidgen) && printf "ENCRYPTION_KEYS=[{\"id\": \"${KEY_ID}\", \"key\": \"$(openssl rand -base64 32)\", \"cipher\": \"AES-GCM\", \"revoked\": false}]\nDEFAULT_ENCRYPTION_KEY_ID=\"${KEY_ID}\"\n"
+```
+
+``` title="Example (regenerate these values!):"
+ENCRYPTION_KEYS='[{"id": "TODO-change-me-unique-key-id-5cdda4c0-a16c-4ae2-8a16-aa2ff258530d", "key": "256 bit (32 byte) base64 encoded AES key", "cipher": "AES-GCM", "revoked": false}]'
+DEFAULT_ENCRYPTION_KEY_ID="TODO-change-me-unique-key-id-5cdda4c0-a16c-4ae2-8a16-aa2ff258530d"
+```
+
+### Debug mode
+Debug mode enables Django's debug toolbar and stack traces. Do not use debug mode in production environments.
+
+``` title="Example:"
+DEBUG=off
+```
+
+### FIDO2/WebAuthn
+If you want to use FIDO2/WebAuthn for MFA, you have to define the hostname ([WebAuthn Relying Party ID](https://www.w3.org/TR/webauthn-2/#relying-party-identifier)) of your installation.
+
+``` title="Example:"
+MFA_FIDO2_RP_ID="sysreptor.example.com"
+```
+
+### License Key
+:octicons-heart-fill-24: Pro only
+
+License key for SysReptor Professional.
+
+``` title="Example:"
+LICENSE="your-license-key"
+```
+
+### Single Sign-On (SSO)
+:octicons-heart-fill-24: Pro only
+
+Configuration for SSO via OIDC. Find detailed instructions at https://docs.sysreptor.com/setup/oidc-setup/.
+
+``` title="OIDC example:"
+OIDC_AZURE_TENANT_ID="azure-tenant-id"
+OIDC_AZURE_CLIENT_ID="azure-client-id"
+OIDC_AZURE_CLIENT_SECRET="azure-client-secret"
+
+OIDC_GOOGLE_CLIENT_ID="google-client-id"
+OIDC_GOOGLE_CLIENT_SECRET="google-client-secret"
+```
+
+If your reverse proxy enforces authentication and provides the username via a HTTP-Header, use following settings to enable SSO.
+
+``` title="Remote-User example"
+REMOTE_USER_AUTH_ENABLED=true
+REMOTE_USER_AUTH_HEADER="Remote-User"
+```
+
+By default users can decide whether they want to log in via SSO or username/password. It is possible to disable login via username/password.
+Make sure all users have SSO identities configured before enabling this option. Else they will not be able to log in anymore.
+
+``` title="Disable username/password authentication example"
+LOCAL_USER_AUTH_ENABLED=false
+```
+
+Configuration of the default authentication provider when multiple authentication providers are enabled (e.g. OIDC via Azure AD and username/password).
+This setting will redirect users to the default authentication provider, skipping the selection. Other authentication providers can still be used if login via the default provider fails.
+
+Possible values: `azure`, `google`, `remoteuser`, `local` (username/password authentication)
+
+``` title="Default authentication provider example"
+DEFAULT_AUTH_PROVIDER="azure"
+DEFAULT_REAUTH_PROVIDER="local"
+```
+
+
+
+### Spell Check
+:octicons-heart-fill-24: Pro only
+
+You can add words to the spell check dictionary in the markdown editor (see https://docs.sysreptor.com/reporting/spell-check/).
+
+Words are added to a global spell check dictionary by default, which is available to all users. If words should be added to user's personal spell check dictionaries, set this setting to `true`.
+
+Using both global and personal dictionaries at the same time is not possible. Words of personal dictionaries are not shared between users. If one user adds an unknown word to their personal dictionary, the spell checker will still detect an error for other users, even when they are working in the same project or finding.
+
+
+``` title="Example:"
+SPELLCHECK_DICTIONARY_PER_USER=false
+```
+
+### Languages
+Configure which languages are available in the language selection.
+By default all languages are shown.
+When this setting is configured, only selected languages are shown.
+All other languages are hidden.
+
+This setting also defines the order of languages in the selection.
+The first language is used as default.
+
+``` title="Example:"
+PREFERRED_LANGUAGES="de-DE,en-US"
+```
+
+
+### Archiving
+:octicons-heart-fill-24: Pro only
+
+Archived projects require at least `ARCHIVING_THRESHOLD` number of users to restore the archive (see https://docs.sysreptor.com/reporting/archiving/).
+By default two users are required, enforcing a 4-eye principle.
+If `ARCHIVING_THRESHOLD=1` every user is able to restore archived projects on their own, disabling the 4-eye principle.
+Changing this setting does not affect previously archived projects.
+
+``` title="Example:"
+ARCHIVING_THRESHOLD=2
+```
+
+The process of archiving finished projects and deleting old archives can be automated by following settings. The values are time spans in days.
+
+``` title="Example:"
+# Automatically archive finished projects after 3 months
+AUTOMATICALLY_ARCHIVE_PROJECTS_AFTER=90
+# Automatically delete archived projects after 2 years
+AUTOMATICALLY_DELETE_ARCHIVED_PROJECTS_AFTER=730
+```
+
+
+
+### Private Designs
+Users without Designer permission can create and edit private designs that cannot be read or used by other users. If a pentest project is created using a private design, a copy of the private design becomes accessible by project members. Use this setting to enable private designs.
+
+``` title="Example:"
+ENABLE_PRIVATE_DESIGNS=true
+```
+
+### Guest Users
+Restrict capabilities of guest users.
+
+``` title="Example:"
+GUEST_USERS_CAN_CREATE_PROJECTS=True
+GUEST_USERS_CAN_IMPORT_PROJECTS=False
+GUEST_USERS_CAN_UPDATE_PROJECT_SETTINGS=True
+GUEST_USERS_CAN_DELETE_PROJECTS=True
+```
+
+### S3 Storage
+Uploaded files (except images) in notes can be uploaded to an S3 bucket. Files are stored on the filesystem in a docker volume by default. If data at rest encryption is configured files are encrypted.
+
+``` title="Example:"
+UPLOADED_FILE_STORAGE="s3" # Default: "filesystem"
+UPLOADED_FILE_S3_ACCESS_KEY="access-key"
+UPLOADED_FILE_S3_SECRET_KEY="secret-key"
+UPLOADED_FILE_S3_SESSION_TOKEN="session-token" # optional
+UPLOADED_FILE_S3_BUCKET_NAME="bucket-name"
+UPLOADED_FILE_S3_ENDPOINT_URL="endpoint-url"
+```
+
+Archived project files can also be uploaded to an S3 bucket. Archives are stored on the filesystem in a docker volume by default.
+
+``` title="Example"
+ARCHIVED_FILE_STORAGE="s3" # Default: "filesystem"
+ARCHIVED_FILE_S3_ACCESS_KEY="access-key"
+ARCHIVED_FILE_S3_SECRET_KEY="secret-key"
+ARCHIVED_FILE_S3_SESSION_TOKEN="session-token" # optional
+ARCHIVED_FILE_S3_BUCKET_NAME="bucket-name"
+ARCHIVED_FILE_S3_ENDPOINT_URL="endpoint-url"
+```
+
+
+### Backup Key
+:octicons-heart-fill-24: Pro only
+
+API key used for creating backups via REST API. The key should be random and must have 20 or more characters. Find more information at https://docs.sysreptor.com/backups/.
+Make sure this key remains secret.
+
+``` title="Generate random backup key:"
+printf "BACKUP_KEY=$(openssl rand -base64 25 | tr -d '\n=')\n"
+```
+
+``` title="Example (do not use this value!):"
+BACKUP_KEY="WfyqYzRVZAOFbCtltYEFN36XBzRz6Ys6ZA"
+```
+
+### Compress Images
+Uploaded images are compressed to reduce file size, but to retain quality suitable for PDF files. Disable image compression using this setting.
+
+``` title="Example:"
+COMPRESS_IMAGES=false
+```
diff --git a/docs/docs/setup/installation.md b/docs/docs/setup/installation.md
new file mode 100755
index 000000000..9c0babfd4
--- /dev/null
+++ b/docs/docs/setup/installation.md
@@ -0,0 +1,130 @@
+# Prerequisites
+## Server
+:octicons-server-24: Self-Hosted
+
+* Ubuntu
+* 4GB RAM
+* Latest [Docker](https://docs.docker.com/engine/install/ubuntu/){ target=_blank } (with docker-compose-plugin)
+
+## Client
+:octicons-cloud-24: Cloud · :octicons-server-24: Self-Hosted
+
+* Network connection to the server
+* Up-to-date desktop browser, one of:
+ * Chrome
+ * Edge
+ * Firefox
+ * Safari
+
+
+# Installation
+:octicons-server-24: Self-Hosted
+
+=== "Installation via Script"
+
+ Installation via script is the easiest option. You need (official) [Docker](https://docs.docker.com/engine/install/ubuntu/){ target=_blank } installed.
+
+ Install additional requirements of script installation:
+ ```shell linenums="1"
+ sudo apt update
+ sudo apt install sed curl openssl uuid-runtime coreutils
+ ```
+
+ The installation script will create a new `sysreptor` directory holding the source code and everything you need. It will build a docker image, create volumes, secrets and bring up your containers.
+
+ The user running the installation script must have the permission to use docker.
+
+ Download and run:
+ === "Professional"
+ ```shell linenums="1"
+ export SYSREPTOR_LICENSE='your_license_key'
+ curl -s https://docs.sysreptor.com/install.sh | bash
+ ```
+
+ === "Community"
+ ```shell linenums="1"
+ curl -s https://docs.sysreptor.com/install.sh | bash
+ ```
+
+=== "Manual Installation"
+
+ You need (official) [Docker](https://docs.docker.com/engine/install/ubuntu/){ target=_blank } installed.
+
+ Download and extract the latest SysReptor release:
+ ```shell linenums="1"
+ curl -s -L --output sysreptor.tar.gz https://github.com/syslifters/sysreptor/releases/latest/download/source-prebuilt.tar.gz
+ tar xzf sysreptor.tar.gz
+ ```
+
+ Create your `app.env`:
+ ```shell linenums="1"
+ cd sysreptor/deploy
+ cp app.env.example app.env
+ ```
+
+ Generate Django secret key and add to `app.env`:
+ ```shell linenums="1"
+ printf "SECRET_KEY=\"$(openssl rand -base64 64 | tr -d '\n=')\"\n"
+ ```
+
+ Generate data at rest encryption keys and add to `app.env`:
+ ```shell linenums="1"
+ KEY_ID=$(uuidgen) && printf "ENCRYPTION_KEYS=[{\"id\": \"${KEY_ID}\", \"key\": \"$(openssl rand -base64 32)\", \"cipher\": \"AES-GCM\", \"revoked\": false}]\nDEFAULT_ENCRYPTION_KEY_ID=\"${KEY_ID}\"\n"
+ ```
+
+ Optional: Add Professional license key to `app.env`:
+ ```
+ LICENSE=""
+ ```
+
+ Create docker volumes:
+ ```shell linenums="1"
+ docker volume create sysreptor-db-data
+ docker volume create sysreptor-app-data
+ ```
+
+ Build Docker image and run container:
+ === "Professional"
+ ```shell linenums="1"
+ docker compose up -d
+ ```
+
+ === "Community"
+ ```shell linenums="1"
+ docker compose -f docker-compose.yml up -d
+ ```
+
+ `-f docker-compose.yml` is specified for Community only to avoid inclusion of Docker Compose Override. This avoids to run an additional Docker container for spell checking and saves resources.
+
+ Add initial superuser:
+ ```shell linenums="1"
+ username=reptor
+ docker compose exec app python3 manage.py createsuperuser --username "$username"
+ ```
+
+ Add demo data:
+ ```
+ # Projects
+ url="https://docs.sysreptor.com/assets/demo-projects.tar.gz"
+ curl -s "$url" | docker compose exec --no-TTY app python3 manage.py importdemodata --type=project --add-member="$username"
+
+ # Designs
+ url="https://docs.sysreptor.com/assets/demo-designs.tar.gz"
+ curl -s "$url" | docker compose exec --no-TTY app python3 manage.py importdemodata --type=design
+
+ # Finding templates
+ url="https://docs.sysreptor.com/assets/demo-templates.tar.gz"
+ curl -s "$url" | docker compose exec --no-TTY app python3 manage.py importdemodata --type=template
+ ```
+
+
+Access your application at http://127.0.0.1:8000/.
+
+We recommend to [use a webserver](/setup/nginx-server) like nginx or Apache and to enable https.
+
+Further [configurations](/setup/configuration/) can be edited in `sysreptor/deploy/app.env`.
+
+# Upgrade to Professional
+1. Add your license key to `deploy/app.env` (`LICENSE='your_license_key'`)
+2. `cd` to `deploy/app.env` and run `docker compose up -d`
+3. Enjoy
diff --git a/docs/docs/setup/oidc-azure-active-directory.md b/docs/docs/setup/oidc-azure-active-directory.md
new file mode 100644
index 000000000..ddbfd50c6
--- /dev/null
+++ b/docs/docs/setup/oidc-azure-active-directory.md
@@ -0,0 +1,60 @@
+---
+title: Azure Active Directory OIDC Configuration
+---
+# Azure Active Directory OIDC Configuration
+:octicons-heart-fill-24: Pro only
+
+## Configuration in Azure AD
+1. Open [Microsoft Entra Admin Center](https://entra.microsoft.com){ target=_blank }
+2. Select Applications -> App registrations -> New registration
+3. In following menu:
+
+ - Enter a Name for your reference (1)
+ - Select the types of accounts who are allowed to login (2) - this is the first option "Single tenant" in most cases
+ - Enter the redirect url of your application in the following format: https://your.url/login/oidc/azure/callback (3)
+ - Select type "Web" for redirect url (4)
+
+ ![Register application menu](/images/oidc_1_register.png)
+
+4. In the newly created "App registration", go to the Token configuration submenu and add the following *optional* claim:
+ - TokenType: ID
+ - Claims: auth_time, login_hint
+ ![Register application menu](/images/oidc_2_claims.png)
+
+
+5. Next go to the "Certificates & Secrets" submenu and add a new client secret with 24 months validity (this is the maximum) and any description.
+6. Copy the value of the newly created secret and store it for later use.
+7. Finally go to the "Overview" submenu and copy the values *Application (client) ID* and *Directory (tenant) ID*.
+
+You should now have the following values:
+
+* Client ID
+* Client secret
+* Azure tendant ID
+
+
+## Cloud Setup
+:octicons-cloud-24: Cloud
+
+You are lucky. Just send the values from the previous steps to us and we'll take care :smiling_face_with_3_hearts:
+
+
+## Self-Hosted Setup
+:octicons-server-24: Self-Hosted
+
+The values from the previous steps need to be passed as environment variables to the SysReptor docker container.
+You can add them to `/deploy/app.env`:
+```env
+OIDC_AZURE_TENANT_ID=
+OIDC_AZURE_CLIENT_ID=
+OIDC_AZURE_CLIENT_SECRET=
+```
+
+The OIDC client needs to be able to establish a network connection to Azure AD.
+Make sure to not block outgoing traffic.
+
+Restart the docker container by going to `sysreptor/deploy` and:
+
+```shell linenums="1"
+docker compose up -d
+```
\ No newline at end of file
diff --git a/docs/docs/setup/oidc-google.md b/docs/docs/setup/oidc-google.md
new file mode 100644
index 000000000..815f93bb9
--- /dev/null
+++ b/docs/docs/setup/oidc-google.md
@@ -0,0 +1,101 @@
+---
+title: Google OIDC Configuration
+---
+
+# Google OIDC Configuration
+:octicons-heart-fill-24: Pro only
+
+## Configuration at Google
+1. Open [Google Cloud Console](https://console.cloud.google.com/){ target=_blank }
+ - Make sure to select the correct organization:
+
+ ![Google Cloud Console Organization](/images/google_cloud_console.png){ style="width: 60%" }
+
+2. Use search box and click "Create a Project"
+
+ ![Click "Create a Project"](/images/google_call_create_project.png){ style="width: 60%" }
+
+3. Enter Name, Organization, Location and "Create"
+
+ ![Enter project details](/images/google_create_project.png){ style="width: 60%" }
+
+4. Search for and call "OAuth consent screen"
+5. Select "Internal" for "User Type" and "Create"
+
+ ![Select "User Type" "Internal"](/images/google_user_type_internal.png){ style="width: 60%" }
+
+6. Enter "App information"
+
+ ![Enter App information](/images/google_app_information.png){ style="width: 60%" }
+
+7. Optional: Add App logo
+
+ - You can use [this](/images/sysreptor_120x120.png){ style="width: 60%" }
+
+8. Enter App domain info
+
+ ![App domain info](/images/google_app_domain.png){ style="width: 60%" }
+
+9. Enter Developer contact information and click "Save and Continue"
+
+ ![Add contact information and continue](/images/google_developer_info.png){ style="width: 60%" }
+
+10. Add the scopes `email`, `profile`, `openid` (don't forget to click "Update")
+
+ ![Add scopes](/images/google_add_scopes.png){ style="width: 60%" }
+
+11. Click "Save and Continue" and verify your data
+12. Go to "Credentials", "Create Credentials" and select "OAuth client ID"
+
+ ![Create credentials](/images/google_create_credentials.png){ style="width: 60%" }
+
+13. Select "Web Application" at "Application type" and enter a name
+
+ ![Enter client details](/images/google_client_data.png){ style="width: 60%" }
+
+14. You don't need any JavaScript origins
+15. Enter the URL to your SysReptor installation with the path `/login/oidc/google/callback` as Authorized redirect URI
+
+ ![Enter redirect URL](/images/google_authorized_redirect_uri.png){ style="width: 60%" }
+
+16. Click "Create"
+
+You should now have the following values:
+
+* Client ID
+* Client secret
+
+
+## Cloud Setup
+:octicons-cloud-24: Cloud
+
+You are lucky. Just send the values from the previous steps to us and we'll take care :smiling_face_with_3_hearts:
+
+
+## Self-Hosted Setup
+:octicons-server-24: Self-Hosted
+
+The values from the previous steps need to be passed as environment variables to the SysReptor docker container.
+You can add them to `/deploy/app.env`:
+```env
+OIDC_GOOGLE_CLIENT_ID=
+OIDC_GOOGLE_CLIENT_SECRET=
+```
+
+The OIDC client needs to be able to establish a network connection to Google.
+Make sure to not block outgoing traffic.
+
+Restart the docker container by going to `sysreptor/deploy` and:
+
+```shell linenums="1"
+docker compose up -d
+```
+
+## Limitations
+SysReptor reauthenticates users before critical actions. It therefore requires users to enter their authentication details (e.g. password and second factor, if configured).
+
+Google does not support enforced reauthentication. The reauthentication therefore redirects to Google. If the users are still authenticated at Google, they are redirected back and SysReptor regards the reauthentication as successful.
+
+This is a limitation by Google.
+
+To enforce reauthentication, users can set a password for their local SysReptor user. This will enforce reauthentication with the local user's credentials.
diff --git a/docs/docs/setup/oidc-setup.md b/docs/docs/setup/oidc-setup.md
new file mode 100755
index 000000000..7cd194035
--- /dev/null
+++ b/docs/docs/setup/oidc-setup.md
@@ -0,0 +1,18 @@
+# SSO Setup with OIDC
+:octicons-heart-fill-24: Pro only
+
+1. Configure your Identity Provider (IDP) and add configuration details to your `app.env`
+ * [Azure Active Directory](/setup/oidc-azure-active-directory)
+ * [Google Workplace/Google Identity](/setup/oidc-google)
+ * Need documentation for another IDP? Drop us a message at [GitHub Discussions](https://github.com/Syslifters/sysreptor/discussions/categories/ideas){ target=_blank }!
+3. Restart containers using `docker-compose up -d` in `deploy`-directory
+2. Set up local users:
+
+ a. Create user that should use SSO
+ b. Go to "Identities"
+ c. Add identity ("Add")
+ d. Select Provider and enter the email address used at your IDP
+
+![Add SSO identity](/images/add_identity.png)
+
+The user can now login via his IDP.
diff --git a/docs/docs/setup/updates.md b/docs/docs/setup/updates.md
new file mode 100644
index 000000000..349cf427c
--- /dev/null
+++ b/docs/docs/setup/updates.md
@@ -0,0 +1,63 @@
+# Updates
+
+:octicons-server-24: Self-Hosted
+
+We recommend to create a [backup](/backups/) of your installation before updating.
+
+=== "Update via Script (recommended)"
+ We deliver the shell script `update.sh` in the `sysreptor` directory.
+
+ If updates are available, the script downloads the release from GitHub. It rebuilds your Docker images and restarts all containers.
+ If no updates are available, the script checks when the Docker images were last built. If the last build date was more then seven days ago, the Docker images are rebuilt to ensure that all base images and dependencies are up to date.
+
+ Use the `--force` option to force rebuilding the Docker images.
+
+ Your current SysReptor directory will be renamed for backup purposes. The script will download the newer version and place it into the directory where the old version was.
+
+ It will then copy your `app.env` to the right location of your newer version. The new docker images are build and launched.
+
+ ```shell linenums="1" title="Run update script:"
+ bash sysreptor/update.sh
+ ```
+
+=== "Manual update"
+ Download and extract the latest SysReptor release:
+ ```shell linenums="1"
+ curl -s -L --output sysreptor.tar.gz https://github.com/syslifters/sysreptor/releases/latest/download/source-prebuilt.tar.gz
+ tar xzf sysreptor.tar.gz
+ ```
+
+ Copy `deploy/app.env` from your old installation to the new installation.
+
+ `cd` to `sysreptor/deploy`. Then, build Docker images and launch containers:
+ ```shell linenums="1" title="Community:"
+ docker compose -f docker-compose.yml up -d --build
+ ```
+
+ ```shell linenums="1" title="Professional:"
+ docker compose up -d --build
+ ```
+
+
+## Recommended: Automatic updates
+We recommend to deploy automatic updates and run the script once per day. This ensures you receive updates early and you regularly update all dependencies and base images.
+
+If `cron` is not installed, install and start:
+```shell linenums="1"
+sudo apt update
+sudo apt install cron
+sudo systemctl start cron
+#sudo /etc/init.d/cron start
+```
+
+Open `crontab`:
+```shell linenums="1"
+crontab -e
+```
+
+Schedule your update, e.g. every day at midnight:
+```shell linenums="1"
+0 0 * * * /bin/bash /home/yourpath/sysreptor/update.sh
+```
+
+Make sure your user has write permissions to the parent directory of your SysReptor directory. In this example, you need write permissions to `/home/yourpath/`.
diff --git a/docs/docs/setup/user-permissions.md b/docs/docs/setup/user-permissions.md
new file mode 100755
index 000000000..d7498499c
--- /dev/null
+++ b/docs/docs/setup/user-permissions.md
@@ -0,0 +1,59 @@
+# User Permissions
+:octicons-heart-fill-24: Pro only
+
+## Users without dedicated Permissions
+Users without dedicated permissions have access to the frontend as regular pentesters.
+They have only read-write access to pentesting reports they are assigned to.
+They cannot read other pentesting reports.
+
+## Superuser
+Superusers have the highest privileges available.
+They have all permissions without explicitly assigning them. They can access all projects, even if they are not members.
+
+**Note:** The permissions of superusers are restricted after login. Superusers must elevate their privileges via the "sudo" button in the toolbar (Pro only). This requires the user to reauthenticate with his password and (if enabled) his second factor.
+
+## User Manager
+User Managers can create and update other users, assign permissions and reset passwords (except superusers).
+
+Users without this permission can only update their own user information (e.g. name, email, phone number), change their own password, but are not allowed to modify their permissions.
+
+## Designer
+Designers can create and edit report designs. Users without this permission can create and edit private designs that cannot be used by other users. They have read access to non-private designs.
+
+## Template Editor
+Template Editors are allowed to create and edit finding templates.
+Users without this permission have only read access to templates.
+
+## Guest
+Guest users have read-write access to projects they are assigned to.
+
+Guest are not allowed to list other users and might be further restricted by the system operator:
+
+* create projects (default: yes)
+* import projects (default: no)
+* update project settings (default: yes)
+* delete projects (default: yes)
+
+:octicons-cloud-24: Cloud · Please [contact us](https://docs.syslifters.com/contact-us/){ target=_blank } and we will reconfigure your installation.
+
+:octicons-server-24: Self-Hosted
+
+Configure your installation by adding the following settings to your `app.env`:
+```
+GUEST_USERS_CAN_CREATE_PROJECTS=True
+GUEST_USERS_CAN_IMPORT_PROJECTS=False
+GUEST_USERS_CAN_UPDATE_PROJECT_SETTINGS=True
+GUEST_USERS_CAN_DELETE_PROJECTS=True
+```
+
+## System
+System is a special privilege that allows users to create backups via API. This privilege can only be set via the Django interface:
+
+1. Log in with superuser permissions
+2. Elevate your privileges using the "sudo" button
+3. Access https://sysreptor.example.com/admin/users/pentestuser/
+4. Choose the user
+5. Tick "Is system user"
+6. Save
+
+The `system` permission should only be used for backups.
\ No newline at end of file
diff --git a/docs/docs/setup/webserver.md b/docs/docs/setup/webserver.md
new file mode 100644
index 000000000..06f494a09
--- /dev/null
+++ b/docs/docs/setup/webserver.md
@@ -0,0 +1,70 @@
+# Setup Webserver
+
+:octicons-server-24: Self-Hosted
+
+The Django webserver is not recommended due to missing transport encryption, missing performance and security tests.
+We recommend a webserver like Caddy, nginx or Apache and to enable https.
+
+=== "Caddy (recommended)"
+ [Caddy](https://caddyserver.com/){ target=_blank } is an open-source webserver with automatic HTTPS written in Go.
+
+ Setup your DNS A-record pointing to your server. Make sure that ports 443 and 80 are publicly available. (You need port 80 for getting your LetEncrypt certificate.)
+
+ Create a `docker-compose.yml` (e.g. in a `caddy` directory outside your SysReptor files):
+
+ ```yml linenums="1"
+ version: '3.9'
+ name: caddy
+
+ services:
+ caddy:
+ image: caddy:latest
+ container_name: 'sysreptor-caddy'
+ restart: unless-stopped
+ command: caddy reverse-proxy --from https://:443 --to http://127.0.0.1:8000
+ volumes:
+ - type: volume
+ source: sysreptor-caddy-data
+ target: /data
+ network_mode: "host"
+
+ volumes:
+ sysreptor-caddy-data:
+ name: sysreptor-caddy-data
+ ```
+
+ Don't forget to replace `` by your domain.
+
+ `docker compose up -d` and enjoy.
+
+=== "nginx"
+
+ You can install nginx on your host system:
+
+ ```shell linenums="1"
+ sudo apt-get update
+ sudo apt-get install nginx
+ ```
+
+ Copy our nginx boilerplate configuration from the `deploy` directory to your nginx directory:
+
+ ```shell linenums="1"
+ sudo cp deploy/sysreptor.nginx /etc/nginx/sites-available/
+ sudo ln -s /etc/nginx/sites-available/sysreptor.nginx /etc/nginx/sites-enabled/
+ sudo rm /etc/nginx/sites-enabled/default
+ ```
+
+ You can optionally generate self-signed certificates:
+ ```shell linenums="1"
+ sudo apt-get update
+ sudo apt-get install ssl-cert
+ sudo make-ssl-cert generate-default-snakeoil
+ ```
+
+ Modify `sysreptor.nginx` and update the certificate paths in case you have trusted certificates (recommended).
+
+ (Re)Start nginx:
+ ```shell linenums="1"
+ sudo systemctl restart nginx
+ # sudo /etc/init.d/nginx restart
+ ```
diff --git a/docs/docs/show-and-tell/index.md b/docs/docs/show-and-tell/index.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs/docs/show-and-tell/posts/2fa.md b/docs/docs/show-and-tell/posts/2fa.md
new file mode 100644
index 000000000..f600c5c18
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/2fa.md
@@ -0,0 +1,12 @@
+---
+date: 2023-06-12
+---
+
+# 2FA for all!
+2FA is also available in SysReptor community. We support:
+
+* FIDO2/Webauthn
+* TOTP (e.g. Google Authenticator)
+* Backup Codes
+
+![Two factor authentication options](/images/show/2fa.png)
diff --git a/docs/docs/show-and-tell/posts/add-chart.md b/docs/docs/show-and-tell/posts/add-chart.md
new file mode 100644
index 000000000..5ade55f14
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/add-chart.md
@@ -0,0 +1,6 @@
+---
+date: 2023-06-26
+---
+
+# Add chart to report
+![Add chart](/images/show/add_chart.gif)
diff --git a/docs/docs/show-and-tell/posts/bulk-edit.md b/docs/docs/show-and-tell/posts/bulk-edit.md
new file mode 100644
index 000000000..620fb4e54
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/bulk-edit.md
@@ -0,0 +1,7 @@
+---
+date: 2023-05-30
+---
+
+# Bulk edit list objects
+
+![CVSS editor](/images/show/bulk-edit-list.gif)
diff --git a/docs/docs/show-and-tell/posts/change-colors.md b/docs/docs/show-and-tell/posts/change-colors.md
new file mode 100644
index 000000000..dcf72c91f
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/change-colors.md
@@ -0,0 +1,6 @@
+---
+date: 2023-06-26
+---
+
+# Change colors in your pentest report
+![Change colors](/images/show/change_colors.gif)
diff --git a/docs/docs/show-and-tell/posts/copy-and-paste.md b/docs/docs/show-and-tell/posts/copy-and-paste.md
new file mode 100644
index 000000000..6e458228e
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/copy-and-paste.md
@@ -0,0 +1,8 @@
+---
+date: 2023-05-30
+---
+
+# Copy and paste image to pentest report
+Copy + Paste: As easy as falling off a log.
+
+![Copy & Paste images](/images/show/drop-image.gif)
diff --git a/docs/docs/show-and-tell/posts/create-finding.md b/docs/docs/show-and-tell/posts/create-finding.md
new file mode 100644
index 000000000..8c156a033
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/create-finding.md
@@ -0,0 +1,7 @@
+---
+date: 2023-06-05
+---
+
+# Create findings from templates
+
+![New finding from template](/images/show/finding-from-template.gif)
diff --git a/docs/docs/show-and-tell/posts/cvss.md b/docs/docs/show-and-tell/posts/cvss.md
new file mode 100644
index 000000000..77c782516
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/cvss.md
@@ -0,0 +1,8 @@
+---
+date: 2023-05-30
+---
+
+# Builtin CVSS editor
+Set metrics, get vector, score and severity.
+
+![CVSS editor](/images/show/cvss-editor.gif)
diff --git a/docs/docs/show-and-tell/posts/data-at-rest.md b/docs/docs/show-and-tell/posts/data-at-rest.md
new file mode 100644
index 000000000..dc843415d
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/data-at-rest.md
@@ -0,0 +1,7 @@
+---
+date: 2023-06-05
+---
+
+# Not only easy. Secure by design.
+
+![Encrypted Data at Rest](/images/show/data-at-rest.png)
diff --git a/docs/docs/show-and-tell/posts/feature-request.md b/docs/docs/show-and-tell/posts/feature-request.md
new file mode 100644
index 000000000..a1aa3c2ec
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/feature-request.md
@@ -0,0 +1,8 @@
+---
+date: 2023-06-05
+---
+
+# Feature Request? GitHub Discussions!
+https://github.com/Syslifters/sysreptor/discussions/
+
+![New finding from template](/images/show/github_discussions.png)
diff --git a/docs/docs/show-and-tell/posts/guest-users.md b/docs/docs/show-and-tell/posts/guest-users.md
new file mode 100644
index 000000000..6583cfe4d
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/guest-users.md
@@ -0,0 +1,17 @@
+---
+date: 2023-06-12
+pro: true
+---
+
+# Use guest permissions for subcontractors
+Guest users cannot list your SysReptor users.
+Permissions can be further restricted to:
+
+ * create projects (default: yes)
+ * import projects (default: no)
+ * update project settings (default: yes)
+ * delete projects (default: yes)
+
+See also: https://docs.sysreptor.com/setup/user-permissions/#guest
+
+![Guest permissions for subcontractors](/images/show/subcontractors.png)
diff --git a/docs/docs/show-and-tell/posts/markdown-in-html.md b/docs/docs/show-and-tell/posts/markdown-in-html.md
new file mode 100644
index 000000000..811a371b3
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/markdown-in-html.md
@@ -0,0 +1,6 @@
+---
+date: 2023-05-25
+---
+
+# Use markdown in HTML in the report designer.
+![Write Markdown in HTML](/images/show/markdown_in_html.png)
\ No newline at end of file
diff --git a/docs/docs/show-and-tell/posts/need-help.md b/docs/docs/show-and-tell/posts/need-help.md
new file mode 100644
index 000000000..4f0d76af2
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/need-help.md
@@ -0,0 +1,8 @@
+---
+date: 2023-06-05
+---
+
+# Need help? Post us at GitHub Discussions!
+https://github.com/Syslifters/sysreptor/discussions/
+
+![Need Help? GitHub Discussions!](/images/show/github-discussions-1.png)
diff --git a/docs/docs/show-and-tell/posts/new-offsec-reports.md b/docs/docs/show-and-tell/posts/new-offsec-reports.md
new file mode 100644
index 000000000..078cfa1a2
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/new-offsec-reports.md
@@ -0,0 +1,16 @@
+---
+date: 2023-05-30
+---
+
+# OffSec certification reporting
+Our free [lab](https://docs.sysreptor.com/oscp-reporting-with-sysreptor/) for OffSec certifications now includes reports for:
+
+* OSWA - Offensive Security Web Assessor
+* OSED - Offensive Security Exploit Developer
+* OSMR - Offensive Security macOS Researcher
+* OSDA - Offensive Security Defense Analyst
+
+All available report templates are:
+OSCP/PWK, OSWP/WNA, OSEP/ETBD, OSWA/WAKL, OSWE/AWAE, OSED/WUMED, OSMR/MCB, OSEE/AWE, OSDA/SODA
+
+![New OffSec reports](/images/show/offsec_reports.png)
diff --git a/docs/docs/show-and-tell/posts/note-taking.md b/docs/docs/show-and-tell/posts/note-taking.md
new file mode 100644
index 000000000..c66df3d79
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/note-taking.md
@@ -0,0 +1,7 @@
+---
+date: 2023-05-30
+---
+
+# Note taking included
+
+![Note taking](/images/show/note-taking.png)
diff --git a/docs/docs/show-and-tell/posts/project-archiving.md b/docs/docs/show-and-tell/posts/project-archiving.md
new file mode 100644
index 000000000..de43f6f09
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/project-archiving.md
@@ -0,0 +1,12 @@
+---
+date: 2023-06-12
+pro: true
+---
+
+# Project Archiving with Encryption
+Archived projects are encrypted. You can define that they can be decrypted following four-eyes principle only.
+
+One pentester alone has no access.
+And we as the cloud provider? No access to archived projects!
+
+![Archiving Keys](/images/show/archiving.png)
diff --git a/docs/docs/show-and-tell/posts/reference-images.md b/docs/docs/show-and-tell/posts/reference-images.md
new file mode 100644
index 000000000..f3b007a01
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/reference-images.md
@@ -0,0 +1,8 @@
+---
+date: 2023-05-30
+---
+
+# Reference images in your pentest report
+More at: https://docs.sysreptor.com/reporting/references/
+
+![Referencing images](/images/show/reference_image.png)
\ No newline at end of file
diff --git a/docs/docs/show-and-tell/posts/reptor-preview.md b/docs/docs/show-and-tell/posts/reptor-preview.md
new file mode 100644
index 000000000..af7674f03
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/reptor-preview.md
@@ -0,0 +1,7 @@
+---
+date: 2023-06-26
+---
+
+# Preview: SysReptor CLI formats and uploads tool outputs.
+
+![SysReptor CLI preview with sslyze formatting](/images/show/reptor_sslyze.gif)
diff --git a/docs/docs/show-and-tell/posts/single-sign-on.md b/docs/docs/show-and-tell/posts/single-sign-on.md
new file mode 100644
index 000000000..4ffa29583
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/single-sign-on.md
@@ -0,0 +1,13 @@
+---
+date: 2023-05-30
+pro: true
+---
+
+# Single Sign-On
+We support SSO with OIDC/oAuth2.
+
+* [Azure AD](/setup/oidc-azure-active-directory)
+* [Google Workplace/Google Identity](/setup/oidc-google).
+* Need another integration? Drop us a message at [GitHub Discussions](https://github.com/Syslifters/sysreptor/discussions/categories/ideas){ target=_blank }!
+
+![Single Sign-On](/images/show/SSO.png)
\ No newline at end of file
diff --git a/docs/docs/show-and-tell/posts/spellcheck.md b/docs/docs/show-and-tell/posts/spellcheck.md
new file mode 100644
index 000000000..902553ecd
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/spellcheck.md
@@ -0,0 +1,8 @@
+---
+date: 2023-06-26
+pro: true
+---
+
+# Spellcheck in SysReptor Professional
+
+![Spellcheck](/images/show/spellcheck.gif)
diff --git a/docs/docs/show-and-tell/posts/sudo.md b/docs/docs/show-and-tell/posts/sudo.md
new file mode 100644
index 000000000..50ba0686f
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/sudo.md
@@ -0,0 +1,9 @@
+---
+date: 2023-05-25
+pro: true
+---
+
+# SUDO to Superuser
+Superusers have standard privileges, until they elevate their permissions using "sudo".
+
+![SUDO for Superusers](/images/show/sudo.png)
diff --git a/docs/docs/show-and-tell/posts/sysreptor.md b/docs/docs/show-and-tell/posts/sysreptor.md
new file mode 100644
index 000000000..a71941a9d
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/sysreptor.md
@@ -0,0 +1,7 @@
+---
+date: 2023-05-30
+---
+
+# SysReptor? Why SysReptor?
+
+![Syslifters' Report Creator](/images/show/sysreptor.png)
diff --git a/docs/docs/show-and-tell/posts/update-rebuilds.md b/docs/docs/show-and-tell/posts/update-rebuilds.md
new file mode 100644
index 000000000..719d553a7
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/update-rebuilds.md
@@ -0,0 +1,15 @@
+---
+date: 2023-05-25
+---
+
+# Keep dependencies up to date
+Set up a [daily cron job](/setup/updates/#recommended-automatic-updates):
+
+Our [update script](/setup/updates/) rebuilds your Docker images every seven days.
+This makes sure that all dependencies are kept up to date:
+
+ * Docker base images
+ * Python
+ * Debian packages
+ * Chromium
+ * ...
diff --git a/docs/docs/show-and-tell/posts/upload-files.md b/docs/docs/show-and-tell/posts/upload-files.md
new file mode 100644
index 000000000..1bdf43f55
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/upload-files.md
@@ -0,0 +1,8 @@
+---
+date: 2023-06-05
+---
+
+# Store your evidence files in notes
+Add #zip, #burp, #tar, #pdf, etc.
+
+![Drop file in notes](/images/show/drop-file.gif)
diff --git a/docs/docs/show-and-tell/posts/vulnerability.md b/docs/docs/show-and-tell/posts/vulnerability.md
new file mode 100644
index 000000000..5ab21cb35
--- /dev/null
+++ b/docs/docs/show-and-tell/posts/vulnerability.md
@@ -0,0 +1,8 @@
+---
+date: 2023-06-12
+---
+
+# Found a vulnerability?
+Find our Vulnerability Disclosure Policy: https://docs.syslifters.com/en/vulnerability-disclosure/
+
+![Report Responsibly](/images/show/vulnerability.png)
diff --git a/docs/docs/stylesheets/extra.css b/docs/docs/stylesheets/extra.css
new file mode 100755
index 000000000..df714a2dc
--- /dev/null
+++ b/docs/docs/stylesheets/extra.css
@@ -0,0 +1,337 @@
+/* exo-100 - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 100;
+ src: url('/assets/fonts/exo/exo-v20-latin-100.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-100.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-100.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-100.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-100.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-100.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-200 - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 200;
+ src: url('/assets/fonts/exo/exo-v20-latin-200.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-200.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-200.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-200.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-200.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-200.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-300 - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 300;
+ src: url('/assets/fonts/exo/exo-v20-latin-300.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-300.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-300.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-300.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-300.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-300.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-regular - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 400;
+ src: url('/assets/fonts/exo/exo-v20-latin-regular.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-regular.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-regular.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-500 - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 500;
+ src: url('/assets/fonts/exo/exo-v20-latin-500.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-500.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-500.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-500.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-500.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-500.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-600 - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 600;
+ src: url('/assets/fonts/exo/exo-v20-latin-600.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-600.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-600.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-600.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-600.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-600.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-700 - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 700;
+ src: url('/assets/fonts/exo/exo-v20-latin-700.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-700.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-700.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-800 - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 800;
+ src: url('/assets/fonts/exo/exo-v20-latin-800.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-800.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-800.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-800.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-800.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-800.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-900 - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: normal;
+ font-weight: 900;
+ src: url('/assets/fonts/exo/exo-v20-latin-900.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-900.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-900.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-900.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-900.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-900.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-100italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 100;
+ src: url('/assets/fonts/exo/exo-v20-latin-100italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-100italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-100italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-100italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-100italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-100italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-200italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 200;
+ src: url('/assets/fonts/exo/exo-v20-latin-200italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-200italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-200italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-200italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-200italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-200italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-300italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 300;
+ src: url('/assets/fonts/exo/exo-v20-latin-300italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-300italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-300italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-300italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-300italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-300italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 400;
+ src: url('/assets/fonts/exo/exo-v20-latin-italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-500italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 500;
+ src: url('/assets/fonts/exo/exo-v20-latin-500italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-500italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-500italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-500italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-500italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-500italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-600italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 600;
+ src: url('/assets/fonts/exo/exo-v20-latin-600italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-600italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-600italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-600italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-600italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-600italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-700italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 700;
+ src: url('/assets/fonts/exo/exo-v20-latin-700italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-700italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-700italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-700italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-700italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-700italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-800italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 800;
+ src: url('/assets/fonts/exo/exo-v20-latin-800italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-800italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-800italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-800italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-800italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-800italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+/* exo-900italic - latin */
+@font-face {
+ font-family: 'Exo';
+ font-style: italic;
+ font-weight: 900;
+ src: url('/assets/fonts/exo/exo-v20-latin-900italic.eot'); /* IE9 Compat Modes */
+ src: local(''),
+ url('/assets/fonts/exo/exo-v20-latin-900italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
+ url('/assets/fonts/exo/exo-v20-latin-900italic.woff2') format('woff2'), /* Super Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-900italic.woff') format('woff'), /* Modern Browsers */
+ url('/assets/fonts/exo/exo-v20-latin-900italic.ttf') format('truetype'), /* Safari, Android, iOS */
+ url('/assets/fonts/exo/exo-v20-latin-900italic.svg#Exo') format('svg'); /* Legacy iOS */
+}
+
+:root>* {
+ --md-code-hl-string-color: #0098DB;
+}
+
+[data-md-color-scheme="default"] {
+ /* Font */
+ --md-text-font: "Exo";
+
+ /* Primary color shades */
+ --md-primary-fg-color: #001827;
+ --md-primary-fg-color--light: #0098DB;
+ --md-primary-fg-color--dark: #001827;
+ --md-primary-bg-color: #F9FDFF;
+ --md-primary-bg-color--light: #F9FDFF;
+ --md-default-fg-color--light: #001827;
+
+ /* Accent color shades */
+ --md-accent-fg-color: #0098DB;
+ --md-accent-fg-color--transparent: #F9FDFF;
+ --md-accent-bg-color: #F9FDFF;
+ --md-accent-bg-color--light: #F9FDFF;
+ }
+
+ :root > * {
+ --md-typeset-a-color: #E65E00;
+ --md-text-link-color: #0098DB;
+
+ /* Code block color shades */
+ --md-code-bg-color: #0055aa1d;
+ --md-code-fg-color: #0098DB;
+
+ /* Footer */
+ --md-footer-bg-color: #001827;
+ --md-footer-bg-color--dark: #001827;
+ --md-footer-fg-color: #F9FDFF;
+ --md-footer-fg-color--light: #F9FDFF;
+ --md-footer-fg-color--lighter: #F9FDFF;
+ }
+
+ .md-header__button.md-logo {
+ margin: 0.1rem;
+ padding: 0.1rem;
+ }
+ .md-header__button.md-logo img {
+ height: 2.5rem;
+ }
+
+ .md-footer {
+ margin-top: 3em;
+ }
+
+ .md-typeset__table {
+ min-width: 100%;
+ }
+ .md-typeset table:not([class]) {
+ display: table;
+ }
+ .md-typeset h1 {
+ margin: 0;
+ }
+ .md-typeset h2 {
+ margin: 0.5em 0 0 0;
+ }
+ .md-typeset ul li {
+ margin-bottom: 0;
+ }
+ .md-typeset ol li :is(ul, ol), .md-typeset ul li :is(ul, ol) {
+ margin: 0;
+ }
+
+ .md-typeset table:not([class]) td {
+ vertical-align: middle;
+ }
+
+ .md-typeset .grid.cards > :is(ul, ol) > li {
+ border: .05rem solid #cccccc;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
+ transition: none;
+ }
+
+ @media print {
+ .no-print {
+ display:none !important;
+ }
+ }
diff --git a/docs/docs/stylesheets/hint.min.css b/docs/docs/stylesheets/hint.min.css
new file mode 100755
index 000000000..aa6c64dbb
--- /dev/null
+++ b/docs/docs/stylesheets/hint.min.css
@@ -0,0 +1,5 @@
+/*! Hint.css - v2.6.0 - 2019-04-27
+* http://kushagragour.in/lab/hint/
+* Copyright (c) 2019 Kushagra Gour */
+
+[class*=hint--]{position:relative;display:inline-block}[class*=hint--]:after,[class*=hint--]:before{position:absolute;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);transform:translate3d(0,0,0);visibility:hidden;opacity:0;z-index:1000000;pointer-events:none;-webkit-transition:.3s ease;-moz-transition:.3s ease;transition:.3s ease;-webkit-transition-delay:0s;-moz-transition-delay:0s;transition-delay:0s}[class*=hint--]:hover:after,[class*=hint--]:hover:before{visibility:visible;opacity:1;-webkit-transition-delay:.1s;-moz-transition-delay:.1s;transition-delay:.1s}[class*=hint--]:before{content:'';position:absolute;background:0 0;border:6px solid transparent;z-index:1000001}[class*=hint--]:after{background:#383838;color:#fff;padding:8px 10px;font-size:12px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;line-height:12px;white-space:nowrap;text-shadow:0 -1px 0 #000;box-shadow:4px 4px 8px rgba(0,0,0,.3)}[class*=hint--][aria-label]:after{content:attr(aria-label)}[class*=hint--][data-hint]:after{content:attr(data-hint)}[aria-label='']:after,[aria-label='']:before,[data-hint='']:after,[data-hint='']:before{display:none!important}.hint--top-left:before,.hint--top-right:before,.hint--top:before{border-top-color:#383838}.hint--bottom-left:before,.hint--bottom-right:before,.hint--bottom:before{border-bottom-color:#383838}.hint--top:after,.hint--top:before{bottom:100%;left:50%}.hint--top:before{margin-bottom:-11px;left:calc(50% - 6px)}.hint--top:after{-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);transform:translateX(-50%)}.hint--top:hover:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--top:hover:after{-webkit-transform:translateX(-50%) translateY(-8px);-moz-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}.hint--bottom:after,.hint--bottom:before{top:100%;left:50%}.hint--bottom:before{margin-top:-11px;left:calc(50% - 6px)}.hint--bottom:after{-webkit-transform:translateX(-50%);-moz-transform:translateX(-50%);transform:translateX(-50%)}.hint--bottom:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--bottom:hover:after{-webkit-transform:translateX(-50%) translateY(8px);-moz-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}.hint--right:before{border-right-color:#383838;margin-left:-11px;margin-bottom:-6px}.hint--right:after{margin-bottom:-14px}.hint--right:after,.hint--right:before{left:100%;bottom:50%}.hint--right:hover:after,.hint--right:hover:before{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translateX(8px)}.hint--left:before{border-left-color:#383838;margin-right:-11px;margin-bottom:-6px}.hint--left:after{margin-bottom:-14px}.hint--left:after,.hint--left:before{right:100%;bottom:50%}.hint--left:hover:after,.hint--left:hover:before{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translateX(-8px)}.hint--top-left:after,.hint--top-left:before{bottom:100%;left:50%}.hint--top-left:before{margin-bottom:-11px;left:calc(50% - 6px)}.hint--top-left:after{-webkit-transform:translateX(-100%);-moz-transform:translateX(-100%);transform:translateX(-100%);margin-left:12px}.hint--top-left:hover:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--top-left:hover:after{-webkit-transform:translateX(-100%) translateY(-8px);-moz-transform:translateX(-100%) translateY(-8px);transform:translateX(-100%) translateY(-8px)}.hint--top-right:after,.hint--top-right:before{bottom:100%;left:50%}.hint--top-right:before{margin-bottom:-11px;left:calc(50% - 6px)}.hint--top-right:after{-webkit-transform:translateX(0);-moz-transform:translateX(0);transform:translateX(0);margin-left:-12px}.hint--top-right:hover:after,.hint--top-right:hover:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--bottom-left:after,.hint--bottom-left:before{top:100%;left:50%}.hint--bottom-left:before{margin-top:-11px;left:calc(50% - 6px)}.hint--bottom-left:after{-webkit-transform:translateX(-100%);-moz-transform:translateX(-100%);transform:translateX(-100%);margin-left:12px}.hint--bottom-left:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--bottom-left:hover:after{-webkit-transform:translateX(-100%) translateY(8px);-moz-transform:translateX(-100%) translateY(8px);transform:translateX(-100%) translateY(8px)}.hint--bottom-right:after,.hint--bottom-right:before{top:100%;left:50%}.hint--bottom-right:before{margin-top:-11px;left:calc(50% - 6px)}.hint--bottom-right:after{-webkit-transform:translateX(0);-moz-transform:translateX(0);transform:translateX(0);margin-left:-12px}.hint--bottom-right:hover:after,.hint--bottom-right:hover:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--large:after,.hint--medium:after,.hint--small:after{white-space:normal;line-height:1.4em;word-wrap:break-word}.hint--small:after{width:80px}.hint--medium:after{width:150px}.hint--large:after{width:300px}.hint--error:after{background-color:#b34e4d;text-shadow:0 -1px 0 #592726}.hint--error.hint--top-left:before,.hint--error.hint--top-right:before,.hint--error.hint--top:before{border-top-color:#b34e4d}.hint--error.hint--bottom-left:before,.hint--error.hint--bottom-right:before,.hint--error.hint--bottom:before{border-bottom-color:#b34e4d}.hint--error.hint--left:before{border-left-color:#b34e4d}.hint--error.hint--right:before{border-right-color:#b34e4d}.hint--warning:after{background-color:#c09854;text-shadow:0 -1px 0 #6c5328}.hint--warning.hint--top-left:before,.hint--warning.hint--top-right:before,.hint--warning.hint--top:before{border-top-color:#c09854}.hint--warning.hint--bottom-left:before,.hint--warning.hint--bottom-right:before,.hint--warning.hint--bottom:before{border-bottom-color:#c09854}.hint--warning.hint--left:before{border-left-color:#c09854}.hint--warning.hint--right:before{border-right-color:#c09854}.hint--info:after{background-color:#3986ac;text-shadow:0 -1px 0 #1a3c4d}.hint--info.hint--top-left:before,.hint--info.hint--top-right:before,.hint--info.hint--top:before{border-top-color:#3986ac}.hint--info.hint--bottom-left:before,.hint--info.hint--bottom-right:before,.hint--info.hint--bottom:before{border-bottom-color:#3986ac}.hint--info.hint--left:before{border-left-color:#3986ac}.hint--info.hint--right:before{border-right-color:#3986ac}.hint--success:after{background-color:#458746;text-shadow:0 -1px 0 #1a321a}.hint--success.hint--top-left:before,.hint--success.hint--top-right:before,.hint--success.hint--top:before{border-top-color:#458746}.hint--success.hint--bottom-left:before,.hint--success.hint--bottom-right:before,.hint--success.hint--bottom:before{border-bottom-color:#458746}.hint--success.hint--left:before{border-left-color:#458746}.hint--success.hint--right:before{border-right-color:#458746}.hint--always:after,.hint--always:before{opacity:1;visibility:visible}.hint--always.hint--top:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--always.hint--top:after{-webkit-transform:translateX(-50%) translateY(-8px);-moz-transform:translateX(-50%) translateY(-8px);transform:translateX(-50%) translateY(-8px)}.hint--always.hint--top-left:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--always.hint--top-left:after{-webkit-transform:translateX(-100%) translateY(-8px);-moz-transform:translateX(-100%) translateY(-8px);transform:translateX(-100%) translateY(-8px)}.hint--always.hint--top-right:after,.hint--always.hint--top-right:before{-webkit-transform:translateY(-8px);-moz-transform:translateY(-8px);transform:translateY(-8px)}.hint--always.hint--bottom:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--always.hint--bottom:after{-webkit-transform:translateX(-50%) translateY(8px);-moz-transform:translateX(-50%) translateY(8px);transform:translateX(-50%) translateY(8px)}.hint--always.hint--bottom-left:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--always.hint--bottom-left:after{-webkit-transform:translateX(-100%) translateY(8px);-moz-transform:translateX(-100%) translateY(8px);transform:translateX(-100%) translateY(8px)}.hint--always.hint--bottom-right:after,.hint--always.hint--bottom-right:before{-webkit-transform:translateY(8px);-moz-transform:translateY(8px);transform:translateY(8px)}.hint--always.hint--left:after,.hint--always.hint--left:before{-webkit-transform:translateX(-8px);-moz-transform:translateX(-8px);transform:translateX(-8px)}.hint--always.hint--right:after,.hint--always.hint--right:before{-webkit-transform:translateX(8px);-moz-transform:translateX(8px);transform:translateX(8px)}.hint--rounded:after{border-radius:4px}.hint--no-animate:after,.hint--no-animate:before{-webkit-transition-duration:0s;-moz-transition-duration:0s;transition-duration:0s}.hint--bounce:after,.hint--bounce:before{-webkit-transition:opacity .3s ease,visibility .3s ease,-webkit-transform .3s cubic-bezier(.71,1.7,.77,1.24);-moz-transition:opacity .3s ease,visibility .3s ease,-moz-transform .3s cubic-bezier(.71,1.7,.77,1.24);transition:opacity .3s ease,visibility .3s ease,transform .3s cubic-bezier(.71,1.7,.77,1.24)}.hint--no-shadow:after,.hint--no-shadow:before{text-shadow:initial;box-shadow:initial}
diff --git a/docs/docs/templates.md b/docs/docs/templates.md
new file mode 100644
index 000000000..eac9f6435
--- /dev/null
+++ b/docs/docs/templates.md
@@ -0,0 +1,63 @@
+# Templates
+Templates are blueprints for findings. They contain common description texts for findings and vulnerabilities.
+Describe your finding texts once in a template and reuse them in multiple pentest reports.
+When writing a report, you just have to adapt the pentest specific details and add some screenshots.
+
+
+## Create a template
+The template library is managed in the `Templates` section of the navigation bar.
+Every user is able to view and use all templates.
+Only users with the `is_template_editor` permission can create, edit and delete templates.
+
+
+## Template fields
+The fields available in templates are the same as in findings.
+The template field definition is created from the finding fields of all global designs and predefined finding fields (e.g. title, cvss, description, recommendation, impact, summary, etc.).
+
+This ensures that templates are independent of designs and templates can be used in projects of any design.
+Each design can define custom fields (additionally to a set of predefined fields). These custom fields are available in templates.
+
+Some fields might not be relevant when creating templates because they are only relevant for findings in a specific design or contain project-specific settings.
+You can hide these fields in the template editor to focus on the relevant fields.
+
+![Template Fields](/images/template_fields.png)
+
+
+## Multilingual templates
+Templates can contain texts for multiple languages.
+This enabled you to manage all data of a template in one place even when it is translated in multiple languages.
+
+This is useful for pentesters in non-English speaking countries where some pentest reports are written in the native language and some in English.
+Some countries also have 2, 3 or more official languages.
+
+Each template has a (required) main language and (optionally) multiple translations.
+The main language defines all fields that are required in the template (e.g. title, cvss, description, recommendation, references, etc.).
+Translations can override language-specific fields (e.g. title, description, recommendation, etc.).
+Fields that are not overridden are inherited from the main language (e.g. cvss, references).
+
+This approach allows for maximum flexibility in template translations, since you can translate each field separately for each language.
+Consider following scenario:
+You are writing a template in English, German and Dutch, where English is the main language and German and Dutch are translations.
+In the English template you fill the text fields for title, description and recommendation with vulnerability descriptions (and some TODO markers to insert screenshots and pentest project specific details). Additionally you define the language-independent fields CVSS and references.
+In the German and Dutch translations you only translate the title, description and recommendation fields need to be translated.
+The CVSS and references fields are inherited from the English template.
+Let's consider you have found a great blog post describing the vulnerability in detail and want to use it as a reference in your report.
+However, the blog post is only available in German, so you cannot use the for the English and Dutch template.
+It is still possible to use them just for the German translation, by overriding the references field in the German translation.
+The Dutch translation still inherits the English references, but the German translation contains the additional reference.
+
+
+## Use templates (creating findings from templates)
+When creating new findings, you can select a template to use.
+The contents of template fields are copied to a new finding.
+
+The template searchbar searches in template tags and the titles of all template translations.
+For example you can search for `xss` to find all templates containing the tag `xss` or the word `xss` in the title of any translation (English, German, etc.).
+
+![Create finding from template](/images/create_finding_from_template.png)
+
+When selecting a template, you can select the language of the template to use.
+By default the language of the pentest project is selected.
+If no translation for the selected language is available, the main language of the template is used.
+
+
diff --git a/docs/docs/terms-of-service.md b/docs/docs/terms-of-service.md
new file mode 100755
index 000000000..fdaed9510
--- /dev/null
+++ b/docs/docs/terms-of-service.md
@@ -0,0 +1,54 @@
+# Terms of Service SysReptor Professional
+## Preamble
+SysReptor Professional (hereinafter: the Platform) is a web-based pentest reporting tool developed and operated by us, Syslifters GmbH (FN 578505v).
+Our order process is conducted by our online reseller Paddle.com. Paddle.com is the Merchant of Record for all our orders. Paddle provides all customer service inquiries and handles returns.
+These Terms of Service apply to you as a user of the Platform in the Cloud or On-Premises.
+Only business clients as defined in § 1 of the Austrian Consumer Protection Act (Konsumentenschutzgesetz) can be customers.
+
+Any general terms and conditions of you shall not apply. If we have concluded any contractual agreements with you, the provisions of these Terms of Service shall take precedence over these contractual agreements.
+
+## Internal Use and Licenses
+You may use the platform for internal business operations only. These Terms of Service do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These Terms of Service do not imply any other licenses.
+
+The source code of the Platform is distributed for On-Premises installations by us under the [SysReptor License](/license/). This license is an integral part of the Terms of Service.
+
+## Data Access And Processing
+You are responsible for the data stored on and shared over the Platform. We will not access your data stored within your Platform installation without your or your users' explicit consent, as long as we are not forced to do so due to legal reasons. However, in the Cloud, we regularly instruct your Platform installation to create encrypted backups that are stored outside your Platform installation in encrypted form.
+
+We process your data to fulfill this service. Your data is stored on European servers under European law. We store your email address, and personal and account details that you provide us. We might also store your IP address in our web server logs and in the course of the prevention of cyber attacks.
+
+We are allowed to contact you via email concerning the service.
+
+We will delete your data within four weeks after the cancellation.
+You have the right to information, correction, deletion, restriction, data transferability, revocation, and objection. If you believe that the processing of your data violates data protection law or your data protection claims have otherwise been violated in any way, you have the right to complain to the supervisory authority. In Austria, this is the data protection authority, email: dsb@dsb.gv.at, web: https://www.dsb.gv.at/.
+
+## Server Connections
+On-Premises Platform installations regularly contact our servers for available software updates, user notifications and license checks. You can disable this functionality. However, you must have an active outbound internet connection during setup, license checks, and for downloading and applying software updates.
+
+## Your Obligations
+The Platform is licensed per user. You must not create more users than you licensed. Service and system users that are required for automated operational purposes (like backups, license checks, etc.) and do not have permission to log in to the Platform do not have to be licensed.
+
+You must not use the Platform in the Cloud to endanger the functionality or operation of the software or to prevent other users' access to, or normal use of the Platform. The data storage in the Cloud is limited to 10 Gigabytes per licensed user.
+
+As a user of the Platform, you can manage, configure and use multiple settings and functionalities of your installation. You are responsible for those aspects under your potential control. This also includes protecting account details and sensitive data (like findings, reports, etc.), limiting user access, applying user permissions following the principle of least privilege, maintaining user security awareness, setting up multi-factor authentication, conducting regular access and permission reviews, ensure availability and confidentiality of custom encryption keys, enforce and inspect your data retention policies, and creating backups.
+In the Cloud service, we create backups for you. However, you are ultimately responsible for ensuring data availability.
+For On-Premises installations, you are furthermore responsible for applying updates, ensuring that encryption of data in transit is enforced, and all web server-specific security settings are set appropriately.
+
+## Warranty and Liability
+The Platform is provided "as is" and any express or implied warranties are disclaimed.
+In no event shall we be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of the Platform, even if advised of the possibility of such damage, to the extent permitted by law.
+
+## Technical Support
+This service does not include technical support services.
+
+## Refund Policy
+You can withdraw from the contract for the reason of a mistake if the subscription was purchased within the last seven days.
+
+## Governing Law
+Austrian laws apply to the extent permitted by law. Application of the UN Convention for the International Sale of Goods, the referral standards of the IPRG, and Regulation (EC) no. 593/2008 of the European Parliament and of the Council of June 17, 2008, on the law applicable to contractual obligations (Rome I Regulation) is excluded.
+
+## Severability
+If a provision of these Terms of Service agreement is or becomes legally invalid or if there is any gap that needs to be filled, the validity of the remainder of these Terms of Service shall not be affected thereby. Invalid provisions shall be replaced by common consent with such provisions which come as close as possible to the intended result of the invalid provision. In the event of gaps such provision shall come into force by common consent which comes as close as possible to the intended result of these Terms of Service, should the matter have been considered in advance. Any changes of or amendments to these Terms of Service must be in writing to become effective.
+
+
+**Last Updated:** 20 January 2023
\ No newline at end of file
diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml
new file mode 100755
index 000000000..3f50431c8
--- /dev/null
+++ b/docs/mkdocs.yml
@@ -0,0 +1,120 @@
+site_name: "SysReptor Docs"
+site_url: "https://docs.sysreptor.com/"
+repo_url: https://github.com/syslifters/sysreptor/
+site_dir: ./site/ # Where to put the HTML files
+nav:
+ - Features and Pricing: features-and-pricing.md
+ - Setup:
+ - Installation: setup/installation.md
+ - Configuration: setup/configuration.md
+ - Setup Webserver: setup/webserver.md
+ - Single Sign-On:
+ - SSO Setup: setup/oidc-setup.md
+ - Azure AD: setup/oidc-azure-active-directory.md
+ - Google: setup/oidc-google.md
+ - User Permissions: setup/user-permissions.md
+ - Updates: setup/updates.md
+ - Backups: setup/backups.md
+ - Writing Reports:
+ - Markdown Syntax: reporting/markdown-features.md
+ - Spell Check: reporting/spell-check.md
+ - References: reporting/references.md
+ - Locked Sections: reporting/locking.md
+ - Archiving: reporting/archiving.md
+ - Designing Reports:
+ - Designer: designer/designer.md
+ - Page Layout: designer/page-layout.md
+ - Headings and ToC: designer/headings-and-table-of-contents.md
+ - Tables: designer/tables.md
+ - Figures: designer/figures.md
+ - Footnotes: designer/footnotes.md
+ - Charts: designer/charts.md
+ - Design Guides: designer/design-guides.md
+ - Formatting Utilities: designer/formatting-utils.md
+ - Debugging: designer/debugging.md
+ - FAQs: designer/faqs.md
+ - Templates: templates.md
+ - Tech Insights:
+ - Rendering Workflow: insights/rendering-workflow.md
+ - Security Considerations: insights/security-considerations.md
+ - Show and Tell: show-and-tell/index.md
+ - Free Reporting:
+ - OffSec: offsec-reporting-with-sysreptor.md
+
+
+theme:
+ name: material
+ custom_dir: overrides
+ logo: images/logo.svg
+ features:
+ - content.code.annotate
+ - search.share
+ #- navigation.expand
+ palette:
+ - scheme: default
+ theme:
+ icon:
+ repo: fontawesome/brands/github
+ admonition:
+ note: octicons/question-16
+
+extra_css:
+ - stylesheets/extra.css
+ - stylesheets/hint.min.css
+
+plugins:
+ - blog:
+ enabled: true
+ blog_dir: show-and-tell
+ archive: false
+ categories: false
+ post_readtime: false
+ - social:
+ cards: !ENV [CARDS, true]
+ cards_color:
+ fill: "#001827"
+ text: "#F9FDFF"
+ cards_font: Exo
+ - tooltips
+ - search
+ - privacy
+ - redirects:
+ redirect_maps:
+ "setup/nginx-server.md": "setup/webserver.md"
+ "reporting/referencing-sections.md": "reporting/references.md"
+ "setup/prerequisites.md": "setup/installation.md"
+ "backups.md": "setup/backups.md"
+
+markdown_extensions:
+ - attr_list
+ - pymdownx.details
+ - pymdownx.emoji:
+ emoji_generator: !!python/name:materialx.emoji.to_svg
+ emoji_index: !!python/name:materialx.emoji.twemoji
+ - pymdownx.magiclink
+ - pymdownx.highlight:
+ use_pygments: true
+ - admonition
+ - pymdownx.details
+ - pymdownx.superfences
+ - md_in_html
+ - pymdownx.critic
+ - pymdownx.caret
+ - pymdownx.keys
+ - pymdownx.mark
+ - pymdownx.tilde
+ - pymdownx.tabbed:
+ alternate_style: true
+ slugify: !!python/object/apply:pymdownx.slugs.slugify
+ kwds:
+ case: lower
+
+extra:
+ social:
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/sysreptor
+ - icon: fontawesome/brands/linkedin
+ link: https://at.linkedin.com/showcase/sysreptor
+ generator: false
+
+copyright: 'Our Website | Imprint | Data Privacy | Contact
Die FFG ist die zentrale nationale Förderorganisation und stärkt Österreichs Innovationskraft. Dieses Projekt wird aus Mitteln der FFG gefördert. www.ffg.at '
diff --git a/docs/overrides/partials/header.html b/docs/overrides/partials/header.html
new file mode 100644
index 000000000..aace0e629
--- /dev/null
+++ b/docs/overrides/partials/header.html
@@ -0,0 +1,132 @@
+{#-
+ This file was automatically generated - do not edit
+-#}
+{% set class = "md-header" %}
+{% if "navigation.tabs.sticky" in features %}
+ {% set class = class ~ " md-header--shadow md-header--lifted" %}
+{% elif "navigation.tabs" not in features %}
+ {% set class = class ~ " md-header--shadow" %}
+{% endif %}
+
+
+
+ {% if "navigation.tabs.sticky" in features %}
+ {% if "navigation.tabs" in features %}
+ {% include "partials/tabs.html" %}
+ {% endif %}
+ {% endif %}
+
+
+
+
\ No newline at end of file
diff --git a/docs/overrides/partials/post.html b/docs/overrides/partials/post.html
new file mode 100644
index 000000000..7d7da5f7c
--- /dev/null
+++ b/docs/overrides/partials/post.html
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+ {% if post.authors %}
+
+ {% endif %}
+
+
+
+
+
+
+
+
+ {% include ".icons/octicons/heart-fill-24.svg" %}
+
+ Professional
+
+
+
diff --git a/frontend/pages/templates/fromfinding.vue b/frontend/pages/templates/fromfinding.vue
new file mode 100644
index 000000000..051678ac7
--- /dev/null
+++ b/frontend/pages/templates/fromfinding.vue
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+ Remove customer data
+
+
+
+ Ensure that no customer specific data is left in the template before saving.
+
+
+ Make sure that the following data is removed and replaced with TODO markers:
+