From 3748eca7869fd9fd57b65bd250d8e68e2fb1079d Mon Sep 17 00:00:00 2001 From: Mats Kramer Date: Wed, 16 Oct 2024 16:54:04 +0200 Subject: [PATCH 1/9] UI: add web support for /credential --- internal/ui/apiv1/apigw_client.go | 8 +++ internal/ui/apiv1/handlers.go | 16 ++++++ internal/ui/httpserver/api.go | 1 + internal/ui/httpserver/endpoints.go | 14 +++++ internal/ui/httpserver/service.go | 1 + internal/ui/static/index.html | 16 +++--- internal/ui/static/ui.js | 86 ++++++++++++++++++++++++++++- 7 files changed, 132 insertions(+), 10 deletions(-) diff --git a/internal/ui/apiv1/apigw_client.go b/internal/ui/apiv1/apigw_client.go index fc9f5f7d..99a2fc75 100644 --- a/internal/ui/apiv1/apigw_client.go +++ b/internal/ui/apiv1/apigw_client.go @@ -40,3 +40,11 @@ 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 +} diff --git a/internal/ui/apiv1/handlers.go b/internal/ui/apiv1/handlers.go index f3a66484..51ae6e98 100644 --- a/internal/ui/apiv1/handlers.go +++ b/internal/ui/apiv1/handlers.go @@ -77,6 +77,22 @@ 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 MockNextRequest struct { PortalRequest } diff --git a/internal/ui/httpserver/api.go b/internal/ui/httpserver/api.go index 9cd5f7fc..72356083 100644 --- a/internal/ui/httpserver/api.go +++ b/internal/ui/httpserver/api.go @@ -18,6 +18,7 @@ type Apiv1 interface { 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) + Credential(ctx context.Context, request *apiv1.CredentialRequest) (any, error) // mockas MockNext(ctx context.Context, req *apiv1.MockNextRequest) (any, error) diff --git a/internal/ui/httpserver/endpoints.go b/internal/ui/httpserver/endpoints.go index 4a59cf5d..4776bb56 100644 --- a/internal/ui/httpserver/endpoints.go +++ b/internal/ui/httpserver/endpoints.go @@ -126,6 +126,20 @@ func (s *Service) endpointUpload(ctx context.Context, c *gin.Context) (any, erro return reply, nil } +func (s *Service) endpointCredential(ctx context.Context, c *gin.Context) (any, error) { + request := &apiv1.CredentialRequest{} + //TODO(mk): use pkg bind.go after it has been fixed instead of context.go + if err := c.ShouldBindJSON(&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) 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 diff --git a/internal/ui/httpserver/service.go b/internal/ui/httpserver/service.go index 01a31f5a..7bb20ad9 100644 --- a/internal/ui/httpserver/service.go +++ b/internal/ui/httpserver/service.go @@ -91,6 +91,7 @@ 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) 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..06ec5fed 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..106b6969 100644 --- a/internal/ui/static/ui.js +++ b/internal/ui/static/ui.js @@ -208,7 +208,7 @@ function doPostForDemo(path, articleHeaderText) { if (!(validateHasValueAndNotEmpty(documentTypeElement) && validateHasValueAndNotEmpty(authenticSourceElement) && validateHasValueAndNotEmpty(authenticSourcePersonIdElement))) { //TODO(mk): show an error message for input params - return + return; } const postBody = { @@ -405,6 +405,90 @@ const addUploadFormArticleToContainer = () => { getElementById("upload-textarea").focus(); }; + +const addCredentialFormArticleToContainer = () => { + const buildCredentialFormElements = () => { + + const schemaNameElement = document.createElement('input'); + schemaNameElement.id = generateUUID(); + schemaNameElement.value = 'SE'; + schemaNameElement.classList.add('input'); + schemaNameElement.type = 'text'; + schemaNameElement.placeholder = 'schema name'; + + const documentTypeElement = document.createElement('input'); + documentTypeElement.id = generateUUID(); + documentTypeElement.value = 'EHIC'; + documentTypeElement.classList.add('input'); + documentTypeElement.type = 'text'; + documentTypeElement.placeholder = 'document type (EHIC/PDA1)'; + + const credentialTypeElement = document.createElement('input'); + credentialTypeElement.id = generateUUID(); + credentialTypeElement.value = 'SD-JWT'; + credentialTypeElement.classList.add('input'); + credentialTypeElement.type = 'text'; + credentialTypeElement.placeholder = 'credential type'; + + const authenticSourceElement = document.createElement('input'); + authenticSourceElement.id = generateUUID(); + authenticSourceElement.value = 'SUNET'; + authenticSourceElement.classList.add('input'); + authenticSourceElement.type = 'text'; + authenticSourceElement.placeholder = 'authentic source'; + + const collectIdElement = document.createElement('input'); + collectIdElement.id = generateUUID(); + collectIdElement.classList.add('input'); + collectIdElement.type = 'text'; + collectIdElement.placeholder = 'collect id'; + + const createButton = document.createElement('button'); + createButton.id = generateUUID(); + createButton.classList.add('button', 'is-link'); + createButton.textContent = 'Create'; + + const doCredential = (schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton) => { + createButton.disabled = true; + + const credentialRequest = { + authentic_source: authenticSourceElement.value, + identity: { + schema: { + name: schemaNameElement.value, + version: "1.0.0" + } + }, + document_type: documentTypeElement.value, + credential_type: credentialTypeElement.value, + collect_id: collectIdElement.value, + }; + + schemaNameElement.disabled = true; + documentTypeElement.disabled = true; + credentialTypeElement.disabled = true; + authenticSourceElement.disabled = true; + collectIdElement.disabled = true; + + postAndDisplayInArticleContainerFor("/secure/apigw/credential", credentialRequest, "Credential result"); + }; + createButton.onclick = () => doCredential(schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton); + + const buttonControl = document.createElement('div'); + buttonControl.classList.add('control'); + buttonControl.appendChild(createButton); + + return [collectIdElement, documentTypeElement, credentialTypeElement, authenticSourceElement, schemaNameElement, buttonControl]; + }; + + const articleIdBasis = generateArticleIDBasis(); + const articleDiv = buildArticle(articleIdBasis.articleID, "Credential", buildCredentialFormElements()); + const articleContainer = getElementById('article-container'); + articleContainer.prepend(articleDiv); + + authenticSourcePersonIdElement.focus(); +}; + const addLoginArticleToContainer = () => { const buildLoginElements = () => { const usernameField = document.createElement('div'); From e41f1f9fc9bea82518cd18d68a124d9b2a797f29 Mon Sep 17 00:00:00 2001 From: Mats Kramer Date: Wed, 16 Oct 2024 17:46:33 +0200 Subject: [PATCH 2/9] UI: add web support for /credential using authentic_source_person_id (EIDAS attributes not yet supported) --- internal/ui/static/ui.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/internal/ui/static/ui.js b/internal/ui/static/ui.js index 106b6969..1362eab8 100644 --- a/internal/ui/static/ui.js +++ b/internal/ui/static/ui.js @@ -246,8 +246,7 @@ const postDocumentList = () => { identity: { authentic_source_person_id: authenticSourcePersonIdElement.value, schema: { - name: "SE", - version: "1.0.0" + name: "SE" } }, document_type: documentTypeElement.value @@ -406,9 +405,16 @@ const addUploadFormArticleToContainer = () => { }; + const addCredentialFormArticleToContainer = () => { const buildCredentialFormElements = () => { + const authenticSourcePersonIdElement = document.createElement('input'); + authenticSourcePersonIdElement.id = generateUUID(); + authenticSourcePersonIdElement.classList.add('input'); + authenticSourcePersonIdElement.type = 'text'; + authenticSourcePersonIdElement.placeholder = 'authentic source person id'; + const schemaNameElement = document.createElement('input'); schemaNameElement.id = generateUUID(); schemaNameElement.value = 'SE'; @@ -448,15 +454,15 @@ const addCredentialFormArticleToContainer = () => { createButton.classList.add('button', 'is-link'); createButton.textContent = 'Create'; - const doCredential = (schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton) => { + const doCredential = (authenticSourcePersonIdElement, schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton) => { createButton.disabled = true; const credentialRequest = { authentic_source: authenticSourceElement.value, identity: { + authentic_source_person_id: authenticSourcePersonIdElement.value, //required if EIDAS attributes is set (family_name, given_name and birth_date) schema: { - name: schemaNameElement.value, - version: "1.0.0" + name: schemaNameElement.value } }, document_type: documentTypeElement.value, @@ -464,6 +470,7 @@ const addCredentialFormArticleToContainer = () => { collect_id: collectIdElement.value, }; + authenticSourcePersonIdElement.disabled = true; schemaNameElement.disabled = true; documentTypeElement.disabled = true; credentialTypeElement.disabled = true; @@ -472,13 +479,13 @@ const addCredentialFormArticleToContainer = () => { postAndDisplayInArticleContainerFor("/secure/apigw/credential", credentialRequest, "Credential result"); }; - createButton.onclick = () => doCredential(schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton); + createButton.onclick = () => doCredential(authenticSourcePersonIdElement, schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton); const buttonControl = document.createElement('div'); buttonControl.classList.add('control'); buttonControl.appendChild(createButton); - return [collectIdElement, documentTypeElement, credentialTypeElement, authenticSourceElement, schemaNameElement, buttonControl]; + return [authenticSourcePersonIdElement, collectIdElement, documentTypeElement, credentialTypeElement, authenticSourceElement, schemaNameElement, buttonControl]; }; const articleIdBasis = generateArticleIDBasis(); From 8fd27fc25c4867f77fb278293ac371495aa2ba8b Mon Sep 17 00:00:00 2001 From: Mats Kramer Date: Wed, 16 Oct 2024 18:34:58 +0200 Subject: [PATCH 3/9] UI: EIDAS attributes added in html but disabled. --- internal/ui/static/ui.js | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/internal/ui/static/ui.js b/internal/ui/static/ui.js index 1362eab8..b2bed468 100644 --- a/internal/ui/static/ui.js +++ b/internal/ui/static/ui.js @@ -405,7 +405,6 @@ const addUploadFormArticleToContainer = () => { }; - const addCredentialFormArticleToContainer = () => { const buildCredentialFormElements = () => { @@ -415,6 +414,27 @@ const addCredentialFormArticleToContainer = () => { authenticSourcePersonIdElement.type = 'text'; authenticSourcePersonIdElement.placeholder = 'authentic source person id'; + const familyNameElement = document.createElement('input'); + familyNameElement.id = generateUUID(); + familyNameElement.classList.add('input'); + familyNameElement.type = 'text'; + familyNameElement.placeholder = 'family name'; + familyNameElement.disabled = true; + + const givenNameElement = document.createElement('input'); + givenNameElement.id = generateUUID(); + givenNameElement.classList.add('input'); + givenNameElement.type = 'text'; + givenNameElement.placeholder = 'given name'; + givenNameElement.disabled = true; + + const birthdateElement = document.createElement('input'); + birthdateElement.id = generateUUID(); + birthdateElement.classList.add('input'); + birthdateElement.type = 'text'; + birthdateElement.placeholder = 'birth date'; + birthdateElement.disabled = true; + const schemaNameElement = document.createElement('input'); schemaNameElement.id = generateUUID(); schemaNameElement.value = 'SE'; @@ -454,7 +474,7 @@ const addCredentialFormArticleToContainer = () => { createButton.classList.add('button', 'is-link'); createButton.textContent = 'Create'; - const doCredential = (authenticSourcePersonIdElement, schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton) => { + const doCredential = (authenticSourcePersonIdElement, familyNameElement, givenNameElement, birthdateElement, schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton) => { createButton.disabled = true; const credentialRequest = { @@ -463,7 +483,10 @@ const addCredentialFormArticleToContainer = () => { authentic_source_person_id: authenticSourcePersonIdElement.value, //required if 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, @@ -471,6 +494,9 @@ const addCredentialFormArticleToContainer = () => { }; authenticSourcePersonIdElement.disabled = true; + familyNameElement.disabled = true; + givenNameElement.disabled = true; + birthdateElement.disabled = true; schemaNameElement.disabled = true; documentTypeElement.disabled = true; credentialTypeElement.disabled = true; @@ -479,13 +505,17 @@ const addCredentialFormArticleToContainer = () => { postAndDisplayInArticleContainerFor("/secure/apigw/credential", credentialRequest, "Credential result"); }; - createButton.onclick = () => doCredential(authenticSourcePersonIdElement, schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton); + createButton.onclick = () => doCredential(authenticSourcePersonIdElement, familyNameElement, givenNameElement, birthdateElement, schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton); const buttonControl = document.createElement('div'); buttonControl.classList.add('control'); buttonControl.appendChild(createButton); - return [authenticSourcePersonIdElement, collectIdElement, documentTypeElement, credentialTypeElement, authenticSourceElement, schemaNameElement, buttonControl]; + const lineElement = document.createElement('hr'); + const orTextElement = document.createElement('p'); + orTextElement.textContent = 'OR'; + + return [authenticSourcePersonIdElement, orTextElement, familyNameElement, givenNameElement, birthdateElement, lineElement, collectIdElement, documentTypeElement, credentialTypeElement, authenticSourceElement, schemaNameElement, buttonControl]; }; const articleIdBasis = generateArticleIDBasis(); From f144b10ad5713e5cce96f0aba15f3356337d1f1a Mon Sep 17 00:00:00 2001 From: Mats Kramer Date: Thu, 17 Oct 2024 13:28:15 +0200 Subject: [PATCH 4/9] UI: EIDAS attributes added and working for /credential. Session timeout for ui is now also configurable in config. --- config.yaml | 1 + internal/ui/httpserver/service.go | 2 +- internal/ui/static/ui.js | 120 ++++++++++-------------------- pkg/model/config.go | 13 ++-- 4 files changed, 50 insertions(+), 86 deletions(-) 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/ui/httpserver/service.go b/internal/ui/httpserver/service.go index 7bb20ad9..68e1f529 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, diff --git a/internal/ui/static/ui.js b/internal/ui/static/ui.js index b2bed468..0518a76b 100644 --- a/internal/ui/static/ui.js +++ b/internal/ui/static/ui.js @@ -406,81 +406,46 @@ const addUploadFormArticleToContainer = () => { const addCredentialFormArticleToContainer = () => { + + 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 buildCredentialFormElements = () => { - const authenticSourcePersonIdElement = document.createElement('input'); - authenticSourcePersonIdElement.id = generateUUID(); - authenticSourcePersonIdElement.classList.add('input'); - authenticSourcePersonIdElement.type = 'text'; - authenticSourcePersonIdElement.placeholder = 'authentic source person id'; - - const familyNameElement = document.createElement('input'); - familyNameElement.id = generateUUID(); - familyNameElement.classList.add('input'); - familyNameElement.type = 'text'; - familyNameElement.placeholder = 'family name'; - familyNameElement.disabled = true; - - const givenNameElement = document.createElement('input'); - givenNameElement.id = generateUUID(); - givenNameElement.classList.add('input'); - givenNameElement.type = 'text'; - givenNameElement.placeholder = 'given name'; - givenNameElement.disabled = true; - - const birthdateElement = document.createElement('input'); - birthdateElement.id = generateUUID(); - birthdateElement.classList.add('input'); - birthdateElement.type = 'text'; - birthdateElement.placeholder = 'birth date'; - birthdateElement.disabled = true; - - const schemaNameElement = document.createElement('input'); - schemaNameElement.id = generateUUID(); - schemaNameElement.value = 'SE'; - schemaNameElement.classList.add('input'); - schemaNameElement.type = 'text'; - schemaNameElement.placeholder = 'schema name'; - - const documentTypeElement = document.createElement('input'); - documentTypeElement.id = generateUUID(); - documentTypeElement.value = 'EHIC'; - documentTypeElement.classList.add('input'); - documentTypeElement.type = 'text'; - documentTypeElement.placeholder = 'document type (EHIC/PDA1)'; - - const credentialTypeElement = document.createElement('input'); - credentialTypeElement.id = generateUUID(); - credentialTypeElement.value = 'SD-JWT'; - credentialTypeElement.classList.add('input'); - credentialTypeElement.type = 'text'; - credentialTypeElement.placeholder = 'credential type'; - - const authenticSourceElement = document.createElement('input'); - authenticSourceElement.id = generateUUID(); - authenticSourceElement.value = 'SUNET'; - authenticSourceElement.classList.add('input'); - authenticSourceElement.type = 'text'; - authenticSourceElement.placeholder = 'authentic source'; - - const collectIdElement = document.createElement('input'); - collectIdElement.id = generateUUID(); - collectIdElement.classList.add('input'); - collectIdElement.type = 'text'; - collectIdElement.placeholder = 'collect id'; + 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'; - const doCredential = (authenticSourcePersonIdElement, familyNameElement, givenNameElement, birthdateElement, schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton) => { + const doCredential = () => { createButton.disabled = true; const credentialRequest = { authentic_source: authenticSourceElement.value, identity: { - authentic_source_person_id: authenticSourcePersonIdElement.value, //required if EIDAS attributes is set (family_name, given_name and birth_date) + authentic_source_person_id: authenticSourcePersonIdElement.value, //required if not EIDAS attributes is set (family_name, given_name and birth_date) schema: { name: schemaNameElement.value }, @@ -493,37 +458,34 @@ const addCredentialFormArticleToContainer = () => { collect_id: collectIdElement.value, }; - authenticSourcePersonIdElement.disabled = true; - familyNameElement.disabled = true; - givenNameElement.disabled = true; - birthdateElement.disabled = true; - schemaNameElement.disabled = true; - documentTypeElement.disabled = true; - credentialTypeElement.disabled = true; - authenticSourceElement.disabled = true; - collectIdElement.disabled = true; + disableElements([ + authenticSourcePersonIdElement, familyNameElement, givenNameElement, + birthdateElement, schemaNameElement, documentTypeElement, + credentialTypeElement, authenticSourceElement, collectIdElement + ]); postAndDisplayInArticleContainerFor("/secure/apigw/credential", credentialRequest, "Credential result"); }; - createButton.onclick = () => doCredential(authenticSourcePersonIdElement, familyNameElement, givenNameElement, birthdateElement, schemaNameElement, documentTypeElement, credentialTypeElement, authenticSourceElement, collectIdElement, createButton); - const buttonControl = document.createElement('div'); - buttonControl.classList.add('control'); - buttonControl.appendChild(createButton); + createButton.onclick = doCredential; const lineElement = document.createElement('hr'); const orTextElement = document.createElement('p'); - orTextElement.textContent = 'OR'; + orTextElement.textContent = 'or'; - return [authenticSourcePersonIdElement, orTextElement, familyNameElement, givenNameElement, birthdateElement, lineElement, collectIdElement, documentTypeElement, credentialTypeElement, authenticSourceElement, schemaNameElement, buttonControl]; + return [ + authenticSourcePersonIdElement, orTextElement, familyNameElement, givenNameElement, + birthdateElement, lineElement, collectIdElement, schemaNameElement, documentTypeElement, + credentialTypeElement, authenticSourceElement, createButton + ]; }; const articleIdBasis = generateArticleIDBasis(); const articleDiv = buildArticle(articleIdBasis.articleID, "Credential", buildCredentialFormElements()); - const articleContainer = getElementById('article-container'); + const articleContainer = document.getElementById('article-container'); articleContainer.prepend(articleDiv); - authenticSourcePersonIdElement.focus(); + document.getElementById(articleIdBasis.articleID).querySelector('input').focus(); }; const addLoginArticleToContainer = () => { 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"` From e79c2158ee4c1570e2fae720ac3946305c26a93e Mon Sep 17 00:00:00 2001 From: Mats Kramer Date: Thu, 17 Oct 2024 14:13:36 +0200 Subject: [PATCH 5/9] UI: endpoints now using httpHelpers.Binding --- internal/ui/httpserver/endpoints.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/internal/ui/httpserver/endpoints.go b/internal/ui/httpserver/endpoints.go index 4776bb56..13584c9b 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,11 @@ 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 } @@ -128,12 +123,11 @@ func (s *Service) endpointUpload(ctx context.Context, c *gin.Context) (any, erro func (s *Service) endpointCredential(ctx context.Context, c *gin.Context) (any, error) { request := &apiv1.CredentialRequest{} - //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.Credential(ctx, request) + reply, err := s.apiv1.Credential(ctx, request) if err != nil { return nil, err } @@ -142,12 +136,11 @@ func (s *Service) endpointCredential(ctx context.Context, c *gin.Context) (any, 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 } From cb02b65db3cc136774e6a585034a6fb5d6c57178 Mon Sep 17 00:00:00 2001 From: Mats Kramer Date: Thu, 17 Oct 2024 17:10:56 +0200 Subject: [PATCH 6/9] UI: now possible to change authentic_source and identity_schema_name for mock/next and document/list in UI + minor text and validation improvements. --- internal/mockas/apiv1/utils.go | 8 ++++- internal/ui/apiv1/handlers.go | 11 +++--- internal/ui/static/index.html | 38 +++++++++++--------- internal/ui/static/ui.js | 63 ++++++++++++++++++---------------- 4 files changed, 65 insertions(+), 55 deletions(-) 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/handlers.go b/internal/ui/apiv1/handlers.go index 51ae6e98..98f5a7b0 100644 --- a/internal/ui/apiv1/handlers.go +++ b/internal/ui/apiv1/handlers.go @@ -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 { @@ -94,7 +88,10 @@ func (c *Client) Credential(ctx context.Context, req *CredentialRequest) (any, e } type MockNextRequest struct { - PortalRequest + DocumentType string `json:"document_type" binding:"required"` + AuthenticSource string `json:"authentic_source" binding:"required"` + AuthenticSourcePersonId string `json:"authentic_source_person_id" binding:"required"` + IdentitySchemaName string `json:"identity_schema_name" binding:"required"` } func (c *Client) MockNext(ctx context.Context, req *MockNextRequest) (any, error) { diff --git a/internal/ui/static/index.html b/internal/ui/static/index.html index 06ec5fed..14485b1f 100644 --- a/internal/ui/static/index.html +++ b/internal/ui/static/index.html @@ -32,34 +32,34 @@ Dev/test support @@ -68,7 +68,7 @@ + placeholder="authentic source person id">
+ class="input" type="text" placeholder="authentic source" value="SUNET"> +
+
+
diff --git a/internal/ui/static/ui.js b/internal/ui/static/ui.js index 0518a76b..3d92a5e8 100644 --- a/internal/ui/static/ui.js +++ b/internal/ui/static/ui.js @@ -182,7 +182,6 @@ async function getAndDisplayInArticleContainerFor(path, articleHeaderText) { await doFetchAPICallAndHandleResult(url, options, elements); } - async function postAndDisplayInArticleContainerFor(path, postBody, articleHeaderText) { const url = new URL(path, baseUrl); console.debug("Call to postAndDisplayInArticleContainerFor: " + url); @@ -201,52 +200,56 @@ 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"); + const identitySchemaNameElement = getElementById("identity-schema-name"); - if (!(validateHasValueAndNotEmpty(documentTypeElement) && validateHasValueAndNotEmpty(authenticSourceElement) && validateHasValueAndNotEmpty(authenticSourcePersonIdElement))) { - //TODO(mk): show an error message for input params - return; - } + // if (!(validateHasValueAndNotEmpty(documentTypeElement) + // && validateHasValueAndNotEmpty(authenticSourceElement) + // && validateHasValueAndNotEmpty(authenticSourcePersonIdElement) + // && validateHasValueAndNotEmpty(identitySchemaNameElement))) { + // //TODO(mk): show an error message for input params + // return; + // } 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"); + const identitySchemaName = getElementById("identity-schema-name"); - if (!(validateHasValueAndNotEmpty(documentTypeElement) && validateHasValueAndNotEmpty(authenticSourceElement) && validateHasValueAndNotEmpty(authenticSourcePersonIdElement))) { - //TODO(mk): show an error message for input params - return; - } + // if (!(validateHasValueAndNotEmpty(documentTypeElement) && validateHasValueAndNotEmpty(authenticSourceElement) && validateHasValueAndNotEmpty(authenticSourcePersonIdElement) && validateHasValueAndNotEmpty(identitySchemaName))) { + // //TODO(mk): show an error message for input params + // return; + // } const documentListRequest = { authentic_source: authenticSourceElement.value, identity: { authentic_source_person_id: authenticSourcePersonIdElement.value, schema: { - name: "SE" + name: identitySchemaName.value } }, document_type: documentTypeElement.value @@ -278,23 +281,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()) { @@ -312,7 +315,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; @@ -397,7 +400,7 @@ 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); @@ -425,7 +428,7 @@ const addCredentialFormArticleToContainer = () => { const buildCredentialFormElements = () => { const authenticSourcePersonIdElement = createInputElement('authentic source person id'); - const familyNameElement = createInputElement('family name', '', 'text' ); + 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'); @@ -464,7 +467,7 @@ const addCredentialFormArticleToContainer = () => { credentialTypeElement, authenticSourceElement, collectIdElement ]); - postAndDisplayInArticleContainerFor("/secure/apigw/credential", credentialRequest, "Credential result"); + postAndDisplayInArticleContainerFor("/secure/apigw/credential", credentialRequest, "Credential"); }; createButton.onclick = doCredential; @@ -481,7 +484,7 @@ const addCredentialFormArticleToContainer = () => { }; const articleIdBasis = generateArticleIDBasis(); - const articleDiv = buildArticle(articleIdBasis.articleID, "Credential", buildCredentialFormElements()); + const articleDiv = buildArticle(articleIdBasis.articleID, "Create credential", buildCredentialFormElements()); const articleContainer = document.getElementById('article-container'); articleContainer.prepend(articleDiv); From 28e353b1828411b92a61eb5a238699e16b065e3c Mon Sep 17 00:00:00 2001 From: Mats Kramer Date: Thu, 17 Oct 2024 19:48:35 +0200 Subject: [PATCH 7/9] UI: view document and some minor code improvements --- internal/ui/apiv1/apigw_client.go | 8 +++ internal/ui/apiv1/handlers.go | 14 +++++ internal/ui/httpserver/api.go | 1 + internal/ui/httpserver/endpoints.go | 12 +++++ internal/ui/httpserver/service.go | 1 + internal/ui/static/index.html | 9 +++- internal/ui/static/ui.js | 80 ++++++++++++++++++----------- 7 files changed, 93 insertions(+), 32 deletions(-) diff --git a/internal/ui/apiv1/apigw_client.go b/internal/ui/apiv1/apigw_client.go index 99a2fc75..6ab36374 100644 --- a/internal/ui/apiv1/apigw_client.go +++ b/internal/ui/apiv1/apigw_client.go @@ -48,3 +48,11 @@ func (c *APIGWClient) Credential(req *CredentialRequest) (any, error) { } 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 +} diff --git a/internal/ui/apiv1/handlers.go b/internal/ui/apiv1/handlers.go index 98f5a7b0..36dafebc 100644 --- a/internal/ui/apiv1/handlers.go +++ b/internal/ui/apiv1/handlers.go @@ -87,6 +87,20 @@ func (c *Client) Credential(ctx context.Context, req *CredentialRequest) (any, e 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 MockNextRequest struct { DocumentType string `json:"document_type" binding:"required"` AuthenticSource string `json:"authentic_source" binding:"required"` diff --git a/internal/ui/httpserver/api.go b/internal/ui/httpserver/api.go index 72356083..35e10771 100644 --- a/internal/ui/httpserver/api.go +++ b/internal/ui/httpserver/api.go @@ -19,6 +19,7 @@ type Apiv1 interface { DocumentList(ctx context.Context, req *apiv1.DocumentListRequest) (any, error) Upload(ctx context.Context, req *apigw_apiv1.UploadRequest) (any, error) Credential(ctx context.Context, request *apiv1.CredentialRequest) (any, error) + GetDocument(ctx context.Context, request *apiv1.GetDocumentRequest) (any, error) // mockas MockNext(ctx context.Context, req *apiv1.MockNextRequest) (any, error) diff --git a/internal/ui/httpserver/endpoints.go b/internal/ui/httpserver/endpoints.go index 13584c9b..33f284a5 100644 --- a/internal/ui/httpserver/endpoints.go +++ b/internal/ui/httpserver/endpoints.go @@ -134,6 +134,18 @@ func (s *Service) endpointCredential(ctx context.Context, c *gin.Context) (any, 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) endpointMockNext(ctx context.Context, c *gin.Context) (any, error) { request := &apiv1.MockNextRequest{} if err := s.httpHelpers.Binding.Request(ctx, c, request); err != nil { diff --git a/internal/ui/httpserver/service.go b/internal/ui/httpserver/service.go index 68e1f529..5febf592 100644 --- a/internal/ui/httpserver/service.go +++ b/internal/ui/httpserver/service.go @@ -92,6 +92,7 @@ func New(ctx context.Context, cfg *model.Cfg, apiv1 *apiv1.Client, tracer *trace 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) 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 14485b1f..4c850eb9 100644 --- a/internal/ui/static/index.html +++ b/internal/ui/static/index.html @@ -32,13 +32,18 @@ Dev/test support + + + +
diff --git a/internal/ui/static/ui.js b/internal/ui/static/ui.js index 27489598..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,7 +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); @@ -192,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)}`) @@ -410,7 +430,46 @@ const disableElements = (elements) => { }; const addViewDocumentFormArticleToContainer = () => { - const buildCredentialFormElements = () => { + 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'); @@ -423,7 +482,7 @@ const addViewDocumentFormArticleToContainer = () => { viewButton.onclick = () => { viewButton.disabled = true; - const credentialRequest = { + const requestBody = { document_id: documentIDElement.value, authentic_source: authenticSourceElement.value, document_type: documentTypeElement.value, @@ -433,14 +492,14 @@ const addViewDocumentFormArticleToContainer = () => { documentIDElement, documentTypeElement, authenticSourceElement ]); - postAndDisplayInArticleContainerFor("/secure/apigw/document", credentialRequest, "Document"); + postAndDisplayInArticleContainerFor("/secure/apigw/notification", requestBody, "Notification"); }; return [documentIDElement, documentTypeElement, authenticSourceElement, viewButton]; }; const articleIdBasis = generateArticleIDBasis(); - const articleDiv = buildArticle(articleIdBasis.articleID, "View document", buildCredentialFormElements()); + const articleDiv = buildArticle(articleIdBasis.articleID, "View notification", buildFormElements()); const articleContainer = document.getElementById('article-container'); articleContainer.prepend(articleDiv); @@ -448,7 +507,7 @@ const addViewDocumentFormArticleToContainer = () => { }; const addCredentialFormArticleToContainer = () => { - const buildCredentialFormElements = () => { + const buildFormElements = () => { const authenticSourcePersonIdElement = createInputElement('authentic source person id'); const familyNameElement = createInputElement('family name', '', 'text'); @@ -467,7 +526,7 @@ const addCredentialFormArticleToContainer = () => { createButton.onclick = () => { createButton.disabled = true; - const credentialRequest = { + 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) @@ -489,7 +548,7 @@ const addCredentialFormArticleToContainer = () => { credentialTypeElement, authenticSourceElement, collectIdElement ]); - postAndDisplayInArticleContainerFor("/secure/apigw/credential", credentialRequest, "Credential"); + postAndDisplayInArticleContainerFor("/secure/apigw/credential", requestBody, "Credential"); }; const lineElement = document.createElement('hr'); @@ -504,7 +563,7 @@ const addCredentialFormArticleToContainer = () => { }; const articleIdBasis = generateArticleIDBasis(); - const articleDiv = buildArticle(articleIdBasis.articleID, "Create credential", buildCredentialFormElements()); + const articleDiv = buildArticle(articleIdBasis.articleID, "Create credential", buildFormElements()); const articleContainer = document.getElementById('article-container'); articleContainer.prepend(articleDiv); From 9826ba6e6b3e6121b91a1d410ce6ea48d1736d4f Mon Sep 17 00:00:00 2001 From: Mats Kramer Date: Fri, 18 Oct 2024 10:17:32 +0200 Subject: [PATCH 9/9] UI: uses validate instead of binding for json structs --- internal/ui/apiv1/handlers.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/ui/apiv1/handlers.go b/internal/ui/apiv1/handlers.go index 95a1a73a..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) { @@ -116,10 +116,10 @@ func (c *Client) Notification(ctx context.Context, request *NotificationRequest) } type MockNextRequest struct { - DocumentType string `json:"document_type" binding:"required"` - AuthenticSource string `json:"authentic_source" binding:"required"` - AuthenticSourcePersonId string `json:"authentic_source_person_id" binding:"required"` - IdentitySchemaName string `json:"identity_schema_name" binding:"required"` + 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) {