diff --git a/config.yaml b/config.yaml
index 46e0845b..5dcd9cbc 100644
--- a/config.yaml
+++ b/config.yaml
@@ -110,6 +110,7 @@ ui:
session_cookie_authentication_key: "PjanW5cOBIlWzjLK23Q8NIo4va53e1bsgWmcqMdznVzkW3uEozfotj7MZsD7HpBo"
#The encryption key, must be either 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256 modes.
session_store_encryption_key: "SQxqb3LKw1YFyAiy4j7FaGGJKeEzr8Db"
+ session_inactivity_timeout_in_seconds: 600
services:
apigw:
base_url: http://vc_dev_apigw:8080
diff --git a/internal/mockas/apiv1/utils.go b/internal/mockas/apiv1/utils.go
index aefae1a8..407ef5c5 100644
--- a/internal/mockas/apiv1/utils.go
+++ b/internal/mockas/apiv1/utils.go
@@ -19,6 +19,7 @@ type MockInputData struct {
FamilyName string `json:"family_name"`
BirthDate string `json:"birth_date"`
CollectID string `json:"collect_id"`
+ IdentitySchemaName string `json:"identity_schema_name"`
}
type uploadMock struct {
@@ -52,10 +53,15 @@ func (c *Client) mockOne(ctx context.Context, data MockInputData) (*uploadMock,
if data.CollectID == "" {
data.CollectID = gofakeit.UUID()
}
+
if data.DocumentID == "" {
data.DocumentID = gofakeit.UUID()
}
+ if data.IdentitySchemaName == "" {
+ data.IdentitySchemaName = "SE"
+ }
+
meta := &model.MetaData{
AuthenticSource: data.AuthenticSource,
DocumentType: data.DocumentType,
@@ -84,7 +90,7 @@ func (c *Client) mockOne(ctx context.Context, data MockInputData) (*uploadMock,
{
AuthenticSourcePersonID: data.AuthenticSourcePersonID,
Schema: &model.IdentitySchema{
- Name: "SE",
+ Name: data.IdentitySchemaName,
Version: "1.0.0",
},
FamilyName: data.FamilyName,
diff --git a/internal/ui/apiv1/apigw_client.go b/internal/ui/apiv1/apigw_client.go
index fc9f5f7d..33cca1b6 100644
--- a/internal/ui/apiv1/apigw_client.go
+++ b/internal/ui/apiv1/apigw_client.go
@@ -40,3 +40,27 @@ func (c *APIGWClient) Upload(req *apiv1_apigw.UploadRequest) (any, error) {
}
return reply, nil
}
+
+func (c *APIGWClient) Credential(req *CredentialRequest) (any, error) {
+ reply, err := c.DoPostJSON("/api/v1/credential", req)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
+
+func (c *APIGWClient) GetDocument(req *GetDocumentRequest) (any, error) {
+ reply, err := c.DoPostJSON("/api/v1/document", req)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
+
+func (c *APIGWClient) Notification(request *NotificationRequest) (any, error) {
+ reply, err := c.DoPostJSON("/api/v1/notification", request)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
diff --git a/internal/ui/apiv1/handlers.go b/internal/ui/apiv1/handlers.go
index f3a66484..5f99e915 100644
--- a/internal/ui/apiv1/handlers.go
+++ b/internal/ui/apiv1/handlers.go
@@ -16,14 +16,14 @@ func (c *Client) Health(ctx context.Context, req *apiv1_status.StatusRequest) (*
}
type LoginRequest struct {
- Username string `json:"username" binding:"required"`
- Password string `json:"password" binding:"required"`
+ Username string `json:"username" validate:"required"`
+ Password string `json:"password" validate:"required"`
}
type LoggedinReply struct {
- Username string `json:"username" binding:"required"`
+ Username string `json:"username" validate:"required"`
// LoggedInTime RFC3339
- LoggedInTime time.Time `json:"logged_in_time" binding:"required"`
+ LoggedInTime time.Time `json:"logged_in_time" validate:"required"`
}
func (c *Client) Login(ctx context.Context, req *LoginRequest) (*LoggedinReply, error) {
@@ -55,12 +55,6 @@ type DocumentListRequest struct {
ValidTo int64 `json:"valid_to"`
}
-type PortalRequest struct {
- DocumentType string `json:"document_type" binding:"required"`
- AuthenticSource string `json:"authentic_source" binding:"required"`
- AuthenticSourcePersonId string `json:"authentic_source_person_id" binding:"required"`
-}
-
func (c *Client) DocumentList(ctx context.Context, req *DocumentListRequest) (any, error) {
reply, err := c.apigwClient.DocumentList(req)
if err != nil {
@@ -77,8 +71,55 @@ func (c *Client) Upload(ctx context.Context, req *apiv1_apigw.UploadRequest) (an
return reply, nil
}
+type CredentialRequest struct {
+ AuthenticSource string `json:"authentic_source" validate:"required"`
+ Identity *model.Identity `json:"identity" validate:"required"`
+ DocumentType string `json:"document_type" validate:"required"`
+ CredentialType string `json:"credential_type" validate:"required"`
+ CollectID string `json:"collect_id" validate:"required"`
+}
+
+func (c *Client) Credential(ctx context.Context, req *CredentialRequest) (any, error) {
+ reply, err := c.apigwClient.Credential(req)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
+
+type GetDocumentRequest struct {
+ AuthenticSource string `json:"authentic_source" validate:"required"`
+ DocumentType string `json:"document_type" validate:"required"`
+ DocumentID string `json:"document_id" validate:"required"`
+}
+
+func (c *Client) GetDocument(ctx context.Context, req *GetDocumentRequest) (any, error) {
+ reply, err := c.apigwClient.GetDocument(req)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
+
+type NotificationRequest struct {
+ AuthenticSource string `json:"authentic_source" validate:"required"`
+ DocumentType string `json:"document_type" validate:"required"`
+ DocumentID string `json:"document_id" validate:"required"`
+}
+
+func (c *Client) Notification(ctx context.Context, request *NotificationRequest) (any, error) {
+ reply, err := c.apigwClient.Notification(request)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
+
type MockNextRequest struct {
- PortalRequest
+ DocumentType string `json:"document_type" validate:"required"`
+ AuthenticSource string `json:"authentic_source" validate:"required"`
+ AuthenticSourcePersonId string `json:"authentic_source_person_id" validate:"required"`
+ IdentitySchemaName string `json:"identity_schema_name" validate:"required"`
}
func (c *Client) MockNext(ctx context.Context, req *MockNextRequest) (any, error) {
diff --git a/internal/ui/httpserver/api.go b/internal/ui/httpserver/api.go
index 9cd5f7fc..6e2515f7 100644
--- a/internal/ui/httpserver/api.go
+++ b/internal/ui/httpserver/api.go
@@ -9,16 +9,19 @@ import (
type Apiv1 interface {
// ui
- Health(ctx context.Context, req *apiv1_status.StatusRequest) (*apiv1_status.StatusReply, error)
- Login(ctx context.Context, req *apiv1.LoginRequest) (*apiv1.LoggedinReply, error)
+ Health(ctx context.Context, request *apiv1_status.StatusRequest) (*apiv1_status.StatusReply, error)
+ Login(ctx context.Context, request *apiv1.LoginRequest) (*apiv1.LoggedinReply, error)
Logout(ctx context.Context) error
User(ctx context.Context) (*apiv1.LoggedinReply, error)
// apigw
StatusAPIGW(ctx context.Context, request *apiv1_status.StatusRequest) (any, error)
- DocumentList(ctx context.Context, req *apiv1.DocumentListRequest) (any, error)
- Upload(ctx context.Context, req *apigw_apiv1.UploadRequest) (any, error)
+ DocumentList(ctx context.Context, request *apiv1.DocumentListRequest) (any, error)
+ Upload(ctx context.Context, request *apigw_apiv1.UploadRequest) (any, error)
+ Credential(ctx context.Context, request *apiv1.CredentialRequest) (any, error)
+ GetDocument(ctx context.Context, request *apiv1.GetDocumentRequest) (any, error)
+ Notification(ctx context.Context, reguest *apiv1.NotificationRequest) (any, error)
// mockas
- MockNext(ctx context.Context, req *apiv1.MockNextRequest) (any, error)
+ MockNext(ctx context.Context, request *apiv1.MockNextRequest) (any, error)
}
diff --git a/internal/ui/httpserver/endpoints.go b/internal/ui/httpserver/endpoints.go
index 4a59cf5d..e0617d80 100644
--- a/internal/ui/httpserver/endpoints.go
+++ b/internal/ui/httpserver/endpoints.go
@@ -23,8 +23,7 @@ func (s *Service) endpointHealth(ctx context.Context, c *gin.Context) (any, erro
func (s *Service) endpointLogin(ctx context.Context, c *gin.Context) (any, error) {
request := &apiv1.LoginRequest{}
- //TODO(mk): use pkg bind.go after it has been fixed instead of context.go
- if err := c.ShouldBindJSON(&request); err != nil {
+ if err := s.httpHelpers.Binding.Request(ctx, c, request); err != nil {
return nil, err
}
@@ -98,12 +97,11 @@ func (s *Service) endpointAPIGWStatus(ctx context.Context, c *gin.Context) (any,
func (s *Service) endpointDocumentList(ctx context.Context, c *gin.Context) (any, error) {
request := &apiv1.DocumentListRequest{}
- //TODO(mk): use pkg bind.go after it has been fixed instead of context.go
- if err := c.ShouldBindJSON(&request); err != nil {
+ if err := s.httpHelpers.Binding.Request(ctx, c, request); err != nil {
return nil, err
}
- reply, err := s.apiv1.DocumentList(ctx, request)
+ reply, err := s.apiv1.DocumentList(ctx, request)
if err != nil {
return nil, err
}
@@ -112,14 +110,48 @@ func (s *Service) endpointDocumentList(ctx context.Context, c *gin.Context) (any
func (s *Service) endpointUpload(ctx context.Context, c *gin.Context) (any, error) {
request := &apiv1_apigw.UploadRequest{}
- //TODO(mk): use pkg bind.go after it has been fixed instead of context.go
- if err := c.ShouldBindJSON(&request); err != nil {
- s.log.Debug("Binding error", "error", err)
+ if err := s.httpHelpers.Binding.Request(ctx, c, request); err != nil {
return nil, err
}
reply, err := s.apiv1.Upload(ctx, request)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
+
+func (s *Service) endpointCredential(ctx context.Context, c *gin.Context) (any, error) {
+ request := &apiv1.CredentialRequest{}
+ if err := s.httpHelpers.Binding.Request(ctx, c, request); err != nil {
+ return nil, err
+ }
+
+ reply, err := s.apiv1.Credential(ctx, request)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
+
+func (s *Service) endpointGetDocument(ctx context.Context, c *gin.Context) (any, error) {
+ request := &apiv1.GetDocumentRequest{}
+ if err := s.httpHelpers.Binding.Request(ctx, c, request); err != nil {
+ return nil, err
+ }
+ reply, err := s.apiv1.GetDocument(ctx, request)
+ if err != nil {
+ return nil, err
+ }
+ return reply, nil
+}
+func (s *Service) endpointNotification(ctx context.Context, c *gin.Context) (any, error) {
+ request := &apiv1.NotificationRequest{}
+ if err := s.httpHelpers.Binding.Request(ctx, c, request); err != nil {
+ return nil, err
+ }
+ reply, err := s.apiv1.Notification(ctx, request)
if err != nil {
return nil, err
}
@@ -128,12 +160,11 @@ func (s *Service) endpointUpload(ctx context.Context, c *gin.Context) (any, erro
func (s *Service) endpointMockNext(ctx context.Context, c *gin.Context) (any, error) {
request := &apiv1.MockNextRequest{}
- //TODO(mk): use pkg bind.go after it has been fixed instead of context.go
- if err := c.ShouldBindJSON(&request); err != nil {
+ if err := s.httpHelpers.Binding.Request(ctx, c, request); err != nil {
return nil, err
}
- reply, err := s.apiv1.MockNext(ctx, request)
+ reply, err := s.apiv1.MockNext(ctx, request)
if err != nil {
return nil, err
}
diff --git a/internal/ui/httpserver/service.go b/internal/ui/httpserver/service.go
index 01a31f5a..c3acb1a1 100644
--- a/internal/ui/httpserver/service.go
+++ b/internal/ui/httpserver/service.go
@@ -49,7 +49,7 @@ func New(ctx context.Context, cfg *model.Cfg, apiv1 *apiv1.Client, tracer *trace
server: &http.Server{},
sessionConfig: &sessionConfig{
name: "vc_ui_auth_session",
- inactivityTimeoutInSeconds: 300,
+ inactivityTimeoutInSeconds: cfg.UI.SessionInactivityTimeoutInSeconds,
path: "/",
httpOnly: true,
secure: cfg.UI.APIServer.TLS.Enabled,
@@ -91,6 +91,9 @@ func New(ctx context.Context, cfg *model.Cfg, apiv1 *apiv1.Client, tracer *trace
s.httpHelpers.Server.RegEndpoint(ctx, rgAPIGW, http.MethodGet, "health", s.endpointAPIGWStatus)
s.httpHelpers.Server.RegEndpoint(ctx, rgAPIGW, http.MethodPost, "document/list", s.endpointDocumentList)
s.httpHelpers.Server.RegEndpoint(ctx, rgAPIGW, http.MethodPost, "upload", s.endpointUpload)
+ s.httpHelpers.Server.RegEndpoint(ctx, rgAPIGW, http.MethodPost, "credential", s.endpointCredential)
+ s.httpHelpers.Server.RegEndpoint(ctx, rgAPIGW, http.MethodPost, "document", s.endpointGetDocument)
+ s.httpHelpers.Server.RegEndpoint(ctx, rgAPIGW, http.MethodPost, "notification", s.endpointNotification)
rgMockAS := rgSecure.Group("mockas")
s.httpHelpers.Server.RegEndpoint(ctx, rgMockAS, http.MethodPost, "mock/next", s.endpointMockNext)
diff --git a/internal/ui/static/index.html b/internal/ui/static/index.html
index 08e09dca..ff579180 100644
--- a/internal/ui/static/index.html
+++ b/internal/ui/static/index.html
@@ -19,15 +19,11 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/internal/ui/static/ui.js b/internal/ui/static/ui.js
index 9dda3b59..fac5591e 100644
--- a/internal/ui/static/ui.js
+++ b/internal/ui/static/ui.js
@@ -138,6 +138,17 @@ function handleErrorInArticle(err, elements) {
updateTextContentInChildPreTagFor(elements.payloadDiv, "");
}
+
+function openModalQR() {
+ const modal = document.getElementById("qrModal");
+ modal.classList.add("is-active");
+}
+
+function closeModalQR() {
+ const modal = document.getElementById("qrModal");
+ modal.classList.remove("is-active");
+}
+
async function doFetchAPICallAndHandleResult(url, options, elements) {
try {
//TODO(mk): add timeout on clientside for fetch
@@ -158,6 +169,15 @@ async function doFetchAPICallAndHandleResult(url, options, elements) {
updateTextContentInChildPreTagFor(elements.respMetaDiv, buildResponseMeta(response));
updateTextContentInChildPreTagFor(elements.errorDiv, "");
updateTextContentInChildPreTagFor(elements.payloadDiv, JSON.stringify(jsonBody, null, 2));
+
+ //TODO(mk): refactor this quick and dirty solution to display the QR code in a modal for /notification before the standard response article
+ if (url.href.includes("notification") && jsonBody && jsonBody.data && jsonBody.data.base64_image) {
+ const base64Image = jsonBody.data.base64_image;
+ const imgElement = document.getElementById("qrCodeImage");
+ imgElement.src = "data:image/png;base64," + base64Image;
+ openModalQR();
+ }
+
} catch (err) {
handleErrorInArticle(err, elements);
}
@@ -182,8 +202,7 @@ async function getAndDisplayInArticleContainerFor(path, articleHeaderText) {
await doFetchAPICallAndHandleResult(url, options, elements);
}
-
-async function postAndDisplayInArticleContainerFor(path, postBody, articleHeaderText) {
+async function postAndDisplayInArticleContainerFor(path, requestBody, articleHeaderText) {
const url = new URL(path, baseUrl);
console.debug("Call to postAndDisplayInArticleContainerFor: " + url);
@@ -193,7 +212,7 @@ async function postAndDisplayInArticleContainerFor(path, postBody, articleHeader
'Accept': 'application/json', 'Content-Type': 'application/json; charset=utf-8',
};
const options = {
- method: `POST`, headers: headers, body: JSON.stringify(postBody),
+ method: `POST`, headers: headers, body: JSON.stringify(requestBody),
};
updateTextContentInChildPreTagFor(elements.reqMetaDiv, `${JSON.stringify(options, null, 2)}`)
@@ -201,53 +220,43 @@ async function postAndDisplayInArticleContainerFor(path, postBody, articleHeader
await doFetchAPICallAndHandleResult(url, options, elements);
}
-function doPostForDemo(path, articleHeaderText) {
+
+const createMock = () => {
+ console.debug("createMock");
+ const path = "/secure/mockas/mock/next";
+ const articleHeaderText = "Upload new mock document result";
+
const documentTypeElement = getElementById("document-type-select");
const authenticSourceElement = getElementById("authentic-source-input");
const authenticSourcePersonIdElement = getElementById("authentic_source_person_id-input");
-
- if (!(validateHasValueAndNotEmpty(documentTypeElement) && validateHasValueAndNotEmpty(authenticSourceElement) && validateHasValueAndNotEmpty(authenticSourcePersonIdElement))) {
- //TODO(mk): show an error message for input params
- return
- }
+ const identitySchemaNameElement = getElementById("identity-schema-name");
const postBody = {
document_type: documentTypeElement.value,
authentic_source: authenticSourceElement.value,
authentic_source_person_id: authenticSourcePersonIdElement.value,
+ identity_schema_name: identitySchemaNameElement.value,
};
postAndDisplayInArticleContainerFor(path, postBody, articleHeaderText);
-}
-
-const createMock = () => {
- console.debug("createMock");
- const path = "/secure/mockas/mock/next";
- const articleHeaderText = "Upload new mock";
- doPostForDemo(path, articleHeaderText);
};
const postDocumentList = () => {
console.debug("postDocumentList");
const path = "/secure/apigw/document/list";
- const articleHeaderText = "document/list";
+ const articleHeaderText = "List documents result";
const documentTypeElement = getElementById("document-type-select");
const authenticSourceElement = getElementById("authentic-source-input");
const authenticSourcePersonIdElement = getElementById("authentic_source_person_id-input");
-
- if (!(validateHasValueAndNotEmpty(documentTypeElement) && validateHasValueAndNotEmpty(authenticSourceElement) && validateHasValueAndNotEmpty(authenticSourcePersonIdElement))) {
- //TODO(mk): show an error message for input params
- return;
- }
+ const identitySchemaName = getElementById("identity-schema-name");
const documentListRequest = {
authentic_source: authenticSourceElement.value,
identity: {
authentic_source_person_id: authenticSourcePersonIdElement.value,
schema: {
- name: "SE",
- version: "1.0.0"
+ name: identitySchemaName.value
}
},
document_type: documentTypeElement.value
@@ -279,23 +288,23 @@ const buildArticle = (articleID, articleHeaderText, bodyChildrenElementArray) =>
const expandCollapseButton = document.createElement('button');
expandCollapseButton.onclick = () => toggleExpandCollapseArticle(articleID);
expandCollapseButton.classList.add("button", "is-dark");
- expandCollapseButton.textContent = "Collapse/Expand"
- expandCollapseButton.ariaLabel = "toggle collapse/expand"
+ expandCollapseButton.textContent = "Collapse/Expand";
+ expandCollapseButton.ariaLabel = "toggle collapse/expand";
const removeButton = document.createElement('button');
removeButton.onclick = () => removeElementById(articleID);
removeButton.classList.add("delete", "is-medium");
- removeButton.ariaLabel = "delete"
+ removeButton.ariaLabel = "delete";
const pElement = document.createElement('p');
pElement.textContent = articleHeaderText ? articleHeaderText : "";
const divHeader = document.createElement('div');
- divHeader.classList.add("message-header")
- divHeader.prepend(pElement, expandCollapseButton, removeButton)
+ divHeader.classList.add("message-header");
+ divHeader.prepend(pElement, expandCollapseButton, removeButton);
const divBody = document.createElement('div');
- divBody.classList.add("message-body")
+ divBody.classList.add("message-body");
if (bodyChildrenElementArray != null && bodyChildrenElementArray.length !== 0) {
// Add to body in the same order as the elements in the array
for (const bodyChildElement of bodyChildrenElementArray.reverse()) {
@@ -313,7 +322,7 @@ const buildArticle = (articleID, articleHeaderText, bodyChildrenElementArray) =>
async function doLogin() {
const url = new URL("/login", baseUrl);
- console.debug("doLogin for url: " + url)
+ console.debug("doLogin for url: " + url);
const doLoginButton = getElementById("do-login-btn");
doLoginButton.disabled = true;
@@ -398,13 +407,169 @@ const addUploadFormArticleToContainer = () => {
};
const articleIdBasis = generateArticleIDBasis();
- const articleDiv = buildArticle(articleIdBasis.articleID, "Upload", buildUploadFormElements());
+ const articleDiv = buildArticle(articleIdBasis.articleID, "Upload document", buildUploadFormElements());
const articleContainer = getElementById('article-container');
articleContainer.prepend(articleDiv);
getElementById("upload-textarea").focus();
};
+const createInputElement = (placeholder, value = '', type = 'text', disabled = false) => {
+ const input = document.createElement('input');
+ input.id = generateUUID();
+ input.classList.add('input');
+ input.type = type;
+ input.placeholder = placeholder;
+ input.value = value;
+ input.disabled = disabled;
+ return input;
+};
+
+const disableElements = (elements) => {
+ elements.forEach(el => el.disabled = true);
+};
+
+const addViewDocumentFormArticleToContainer = () => {
+ const buildFormElements = () => {
+
+ const documentIDElement = createInputElement('document id');
+ const documentTypeElement = createInputElement('document type (EHIC/PDA1)', 'EHIC');
+ const authenticSourceElement = createInputElement('authentic source', 'SUNET');
+
+ const viewButton = document.createElement('button');
+ viewButton.id = generateUUID();
+ viewButton.classList.add('button', 'is-link');
+ viewButton.textContent = 'View';
+ viewButton.onclick = () => {
+ viewButton.disabled = true;
+
+ const requestBody = {
+ document_id: documentIDElement.value,
+ authentic_source: authenticSourceElement.value,
+ document_type: documentTypeElement.value,
+ };
+
+ disableElements([
+ documentIDElement, documentTypeElement, authenticSourceElement
+ ]);
+
+ postAndDisplayInArticleContainerFor("/secure/apigw/document", requestBody, "Document");
+ };
+
+ return [documentIDElement, documentTypeElement, authenticSourceElement, viewButton];
+ };
+
+ const articleIdBasis = generateArticleIDBasis();
+ const articleDiv = buildArticle(articleIdBasis.articleID, "View document", buildFormElements());
+ const articleContainer = document.getElementById('article-container');
+ articleContainer.prepend(articleDiv);
+
+ document.getElementById(articleIdBasis.articleID).querySelector('input').focus();
+};
+
+
+const addViewNotificationFormArticleToContainer = () => {
+ const buildFormElements = () => {
+
+ const documentIDElement = createInputElement('document id');
+ const documentTypeElement = createInputElement('document type (EHIC/PDA1)', 'EHIC');
+ const authenticSourceElement = createInputElement('authentic source', 'SUNET');
+
+ const viewButton = document.createElement('button');
+ viewButton.id = generateUUID();
+ viewButton.classList.add('button', 'is-link');
+ viewButton.textContent = 'View';
+ viewButton.onclick = () => {
+ viewButton.disabled = true;
+
+ const requestBody = {
+ document_id: documentIDElement.value,
+ authentic_source: authenticSourceElement.value,
+ document_type: documentTypeElement.value,
+ };
+
+ disableElements([
+ documentIDElement, documentTypeElement, authenticSourceElement
+ ]);
+
+ postAndDisplayInArticleContainerFor("/secure/apigw/notification", requestBody, "Notification");
+ };
+
+ return [documentIDElement, documentTypeElement, authenticSourceElement, viewButton];
+ };
+
+ const articleIdBasis = generateArticleIDBasis();
+ const articleDiv = buildArticle(articleIdBasis.articleID, "View notification", buildFormElements());
+ const articleContainer = document.getElementById('article-container');
+ articleContainer.prepend(articleDiv);
+
+ document.getElementById(articleIdBasis.articleID).querySelector('input').focus();
+};
+
+const addCredentialFormArticleToContainer = () => {
+ const buildFormElements = () => {
+
+ const authenticSourcePersonIdElement = createInputElement('authentic source person id');
+ const familyNameElement = createInputElement('family name', '', 'text');
+ const givenNameElement = createInputElement('given name', '', 'text');
+ const birthdateElement = createInputElement('birth date', '', 'text');
+ const schemaNameElement = createInputElement('identity schema name', 'SE');
+ const documentTypeElement = createInputElement('document type (EHIC/PDA1)', 'EHIC');
+ const credentialTypeElement = createInputElement('credential type', 'SD-JWT');
+ const authenticSourceElement = createInputElement('authentic source', 'SUNET');
+ const collectIdElement = createInputElement('collect id');
+
+ const createButton = document.createElement('button');
+ createButton.id = generateUUID();
+ createButton.classList.add('button', 'is-link');
+ createButton.textContent = 'Create';
+ createButton.onclick = () => {
+ createButton.disabled = true;
+
+ const requestBody = {
+ authentic_source: authenticSourceElement.value,
+ identity: {
+ authentic_source_person_id: authenticSourcePersonIdElement.value, //required if not EIDAS attributes is set (family_name, given_name and birth_date)
+ schema: {
+ name: schemaNameElement.value
+ },
+ family_name: familyNameElement.value,
+ given_name: givenNameElement.value,
+ birth_date: birthdateElement.value,
+ },
+ document_type: documentTypeElement.value,
+ credential_type: credentialTypeElement.value,
+ collect_id: collectIdElement.value,
+ };
+
+ disableElements([
+ authenticSourcePersonIdElement, familyNameElement, givenNameElement,
+ birthdateElement, schemaNameElement, documentTypeElement,
+ credentialTypeElement, authenticSourceElement, collectIdElement
+ ]);
+
+ postAndDisplayInArticleContainerFor("/secure/apigw/credential", requestBody, "Credential");
+ };
+
+ const lineElement = document.createElement('hr');
+ const orTextElement = document.createElement('p');
+ orTextElement.textContent = 'or';
+
+ return [
+ authenticSourcePersonIdElement, orTextElement, familyNameElement, givenNameElement,
+ birthdateElement, lineElement, collectIdElement, schemaNameElement, documentTypeElement,
+ credentialTypeElement, authenticSourceElement, createButton
+ ];
+ };
+
+ const articleIdBasis = generateArticleIDBasis();
+ const articleDiv = buildArticle(articleIdBasis.articleID, "Create credential", buildFormElements());
+ const articleContainer = document.getElementById('article-container');
+ articleContainer.prepend(articleDiv);
+
+ document.getElementById(articleIdBasis.articleID).querySelector('input').focus();
+};
+
const addLoginArticleToContainer = () => {
const buildLoginElements = () => {
const usernameField = document.createElement('div');
diff --git a/pkg/model/config.go b/pkg/model/config.go
index 231cdc30..2d1c8edd 100644
--- a/pkg/model/config.go
+++ b/pkg/model/config.go
@@ -182,12 +182,13 @@ type OTEL struct {
// UI holds the user-interface configuration
type UI struct {
- APIServer APIServer `yaml:"api_server" validate:"required"`
- Username string `yaml:"username" validate:"required"`
- Password string `yaml:"password" validate:"required"`
- SessionCookieAuthenticationKey string `yaml:"session_cookie_authentication_key" validate:"required"`
- SessionStoreEncryptionKey string `yaml:"session_store_encryption_key" validate:"required"`
- Services struct {
+ APIServer APIServer `yaml:"api_server" validate:"required"`
+ Username string `yaml:"username" validate:"required"`
+ Password string `yaml:"password" validate:"required"`
+ SessionCookieAuthenticationKey string `yaml:"session_cookie_authentication_key" validate:"required"`
+ SessionStoreEncryptionKey string `yaml:"session_store_encryption_key" validate:"required"`
+ SessionInactivityTimeoutInSeconds int `yaml:"session_inactivity_timeout_in_seconds" validate:"required"`
+ Services struct {
APIGW struct {
BaseURL string `yaml:"base_url"`
} `yaml:"apigw"`