diff --git a/general/README.md b/general/README.md
index 337d46fd..71ef907d 100644
--- a/general/README.md
+++ b/general/README.md
@@ -66,6 +66,8 @@ The following values can be defined in `variables`:
- `jenkinsJobUrl` - used to group versions based on Jenkins jobs. DEFAULT: `null`, EXAMPLE: `https://jenkins.edmcouncil.org/job/idmp/`
+- `authEnabled` - used to enable authorization feature, login and register functionality will be enabled when value is set to `true`.
+
## Build and run frontend
- create directory for frontend application instance
diff --git a/general/api/ontology.js b/general/api/ontology.js
index ff07c156..f8c5839b 100644
--- a/general/api/ontology.js
+++ b/general/api/ontology.js
@@ -1,3 +1,50 @@
+import axios from 'axios';
+import {
+ useAuthStore,
+ useOntologyStore,
+ useConfigurationStore,
+ useToastStore
+} from '#imports';
+export const axiosClient = axios.create();
+
+axiosClient.interceptors.request.use((config) => {
+ const authStore = useAuthStore();
+ const configStore = useConfigurationStore();
+
+ const currentOrigin = window.location.origin;
+ const requestUrl = new URL(config.url, currentOrigin);
+
+ if (
+ configStore.config.authEnabled === 'true' &&
+ authStore.jwt &&
+ !config.noAuth &&
+ requestUrl.origin === currentOrigin
+ ) {
+ config.headers.Authorization = `Bearer ${authStore.jwt}`;
+ }
+
+ return config;
+});
+
+axiosClient.interceptors.response.use(
+ (response) => response,
+ (error) => {
+ const toastStore = useToastStore();
+ const authStore = useAuthStore();
+ const ontologyStore = useOntologyStore();
+
+ if (error.response && error.response.status === 401) {
+ toastStore.addToast('Session expired. Please log in again.');
+ authStore.clear();
+ }
+ if (error.response && error.response.status === 403) {
+ ontologyStore.unauthorizedError = true;
+ }
+
+ return Promise.reject(error);
+ }
+);
+
function ServerError(message, status) {
this.message = message;
this.status = status;
@@ -10,59 +57,83 @@ const parseServerError = (response) => {
return response;
};
-const getEntity = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/json' }
- }).then(parseServerError);
+const getEntity = (domain, config = {}) =>
+ axiosClient
+ .get(domain, {
+ ...config,
+ headers: { Accept: 'application/json', ...config.headers }
+ })
+ .then(parseServerError)
+ .then((response) => response.data);
-const getModules = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/json' }
- }).then(parseServerError);
+const getModules = (domain, config = {}) =>
+ axiosClient
+ .get(domain, {
+ ...config,
+ headers: { Accept: 'application/json', ...config.headers }
+ })
+ .then(parseServerError)
+ .then((response) => response.data);
-const getOntologyVersions = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/json' }
- }).then(parseServerError);
+const getOntologyVersions = (domain, config = {}) =>
+ axiosClient
+ .get(domain, {
+ ...config,
+ headers: { Accept: 'application/json', ...config.headers }
+ })
+ .then(parseServerError)
+ .then((response) => response.data);
const getJenkinsJobs = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/json' }
- }).then(parseServerError);
-
-const getFindSearch = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/json' }
- }).then(parseServerError);
-
-const getFindProperties = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/json' }
- }).then(parseServerError);
-
-const getStats = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/json' }
- }).then(parseServerError);
-
-const getMissingImports = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/json' }
- }).then(parseServerError);
-
-const getDescribeIntegration = (domain) =>
- fetch(domain, {
- method: 'GET',
- headers: { Accept: 'application/rdf+xml' }
- }).then(parseServerError);
+ axiosClient
+ .get(domain, { noAuth: true, headers: { Accept: 'application/json' } })
+ .then(parseServerError)
+ .then((response) => response.data);
+
+const getFindSearch = (domain, config = {}) =>
+ axiosClient
+ .get(domain, {
+ ...config,
+ headers: { Accept: 'application/json', ...config.headers }
+ })
+ .then(parseServerError)
+ .then((response) => response.data);
+
+const getFindProperties = (domain, config = {}) =>
+ axiosClient
+ .get(domain, {
+ ...config,
+ headers: { Accept: 'application/json', ...config.headers }
+ })
+ .then(parseServerError)
+ .then((response) => response.data);
+
+const getStats = (domain, config = {}) =>
+ axiosClient
+ .get(domain, {
+ ...config,
+ headers: { Accept: 'application/json', ...config.headers }
+ })
+ .then(parseServerError)
+ .then((response) => response.data);
+
+const getMissingImports = (domain, config = {}) =>
+ axiosClient
+ .get(domain, {
+ ...config,
+ headers: { Accept: 'application/json', ...config.headers }
+ })
+ .then(parseServerError)
+ .then((response) => response.data);
+
+const getDescribeIntegration = (domain, config = {}) =>
+ axiosClient
+ .get(domain, {
+ ...config,
+ headers: { Accept: 'application/rdf+xml', ...config.headers }
+ })
+ .then(parseServerError)
+ .then((response) => response.data);
export {
getEntity,
diff --git a/general/api/strapi.js b/general/api/strapi.js
index ad05e8d8..fda637de 100644
--- a/general/api/strapi.js
+++ b/general/api/strapi.js
@@ -174,6 +174,7 @@ export async function getAppConfigurationData(runtimeConfig) {
data.uriSpace = response.data.attributes.uriSpace;
data.ontologyRepositoryUrl = response.data.attributes.ontologyRepositoryUrl;
data.jenkinsJobUrl = response.data.attributes.jenkinsJobUrl;
+ data.authEnabled = response.data.attributes.authEnabled;
// overwrite with 'variables' data
const { variables } = response.data.attributes;
diff --git a/general/assets/icons/user.svg b/general/assets/icons/user.svg
new file mode 100644
index 00000000..9cca142c
--- /dev/null
+++ b/general/assets/icons/user.svg
@@ -0,0 +1,5 @@
+
diff --git a/general/assets/scss/global.scss b/general/assets/scss/global.scss
index 2fc02210..a73f22d0 100644
--- a/general/assets/scss/global.scss
+++ b/general/assets/scss/global.scss
@@ -236,6 +236,7 @@ body.modal-open {
}
&:focus {
background: $normal-button-focus-bg;
+ color: rgba(255, 255, 255, 0.9);
box-shadow: rgba(0, 0, 0, 0.4) 0px 0px 0px 4px;
}
&:active {
@@ -651,3 +652,154 @@ a.deprecated {
a.deprecated:hover {
color: #ec241d !important;
}
+
+.modal-card {
+ .modal-error {
+ color: #ec241d;
+ }
+
+ .modal-form {
+ display: flex;
+ flex-direction: column;
+
+ .muted-link {
+ cursor: pointer;
+ align-self: flex-end;
+ color: rgba(0, 0, 0, 0.6);
+
+ &:focus-visible {
+ box-shadow: $form-focus-shadow;
+ }
+
+ &:active {
+ filter: none;
+ box-shadow: none;
+ }
+ }
+
+ .normal-button {
+ width: fit-content;
+ }
+
+ .alert {
+ color: rgba(0, 0, 0, 0.8);
+ font-style: normal;
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 20px;
+ letter-spacing: 0.01em;
+ border: none;
+ border-radius: 2px;
+ margin-top: 20px;
+ padding: 5px 15px 5px 15px;
+ background-color: #ec241d;
+ }
+ }
+}
+
+.modal.login-modal,
+.modal.recover-modal,
+.modal.reset-modal,
+.modal.confirmEmail-modal,
+.modal.register-modal {
+ .modal-header {
+ position: relative;
+ box-shadow: 0px 5px 20px -5px rgba(8, 84, 150, 0.15);
+ border: none;
+ padding: 15px 40px;
+
+ justify-content: space-between;
+
+ .close-btn {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ align-items: center;
+ width: 24px;
+ height: 30px;
+ padding: 0;
+ margin-left: 20px;
+
+ &::before {
+ content: '';
+ background-image: url('../../assets/icons/close.svg');
+ background-repeat: no-repeat;
+ background-size: 24px 24px;
+ width: 24px;
+ height: 24px;
+ }
+ }
+
+ .return-btn {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+ align-items: center;
+ width: 24px;
+ height: 30px;
+ padding: 0;
+ margin-right: 20px;
+
+ &::before {
+ content: '';
+ background-image: url('../../assets/icons/return-arrow.svg');
+ background-repeat: no-repeat;
+ background-size: 24px 24px;
+ width: 24px;
+ height: 24px;
+ }
+ }
+
+ .left {
+ display: flex;
+ align-items: center;
+ }
+
+ h5.modal-title {
+ font-style: normal;
+ font-weight: bold;
+ font-size: 18px;
+ line-height: 30px;
+ color: #000000;
+
+ padding: 0;
+ margin: 0;
+ position: relative;
+ }
+ }
+ .modal-content {
+ border-radius: 2px;
+ border: none;
+ background: white;
+ height: 100%;
+ }
+ .modal-body {
+ padding: 30px 40px;
+ height: 100%;
+ position: relative;
+
+ a {
+ color: rgba(0, 0, 0, 0.8);
+ text-decoration: none;
+
+ &:hover {
+ color: $link-hover-color;
+ }
+
+ &:active {
+ color: $link-active-color;
+ }
+ }
+ }
+ .modal-footer {
+ border: none;
+ box-shadow: 0px -5px 20px -5px rgba(8, 84, 150, 0.15);
+ padding: 18px 30px;
+
+ justify-content: space-between;
+
+ .normal-button.small {
+ margin-bottom: 0 !important;
+ }
+ }
+}
diff --git a/general/components/Articles/SerializationListSection.vue b/general/components/Articles/SerializationListSection.vue
index cd7507cd..8f109d1e 100644
--- a/general/components/Articles/SerializationListSection.vue
+++ b/general/components/Articles/SerializationListSection.vue
@@ -306,10 +306,9 @@ export default {
},
async fetchVersions() {
try {
- const result = await getOntologyVersions(
+ const ontologyVersions = await getOntologyVersions(
`/${this.ontologyName}/ontology/api/`
);
- const ontologyVersions = await result.json();
const first = 'master/latest';
ontologyVersions.sort((x, y) =>
@@ -352,24 +351,21 @@ export default {
const tagName = runtimeConfig.public.tagName;
// group versions by tags, pull requests and releases
- const tagsResult = await getJenkinsJobs(
+ const tagsJson = await getJenkinsJobs(
`${jenkinsJobUrl}/view/tags/api/json`
);
- const tagsJson = await tagsResult.json();
const tags = tagsJson.jobs.map((item) => item.name.toLowerCase());
- const pullRequestsResult = await getJenkinsJobs(
+ const pullRequestsJson = await getJenkinsJobs(
`${jenkinsJobUrl}/view/change-requests/api/json`
);
- const pullRequestsJson = await pullRequestsResult.json();
const pullRequests = pullRequestsJson.jobs.map((item) =>
item.name.toLowerCase()
);
- const defaultViewResult = await getJenkinsJobs(
+ const defaultViewJson = await getJenkinsJobs(
`${jenkinsJobUrl}/view/default/api/json`
);
- const defaultViewJson = await defaultViewResult.json();
const defaultView = defaultViewJson.jobs.map((item) =>
item.name.toLowerCase()
);
diff --git a/general/components/Header/AuthComponent.vue b/general/components/Header/AuthComponent.vue
new file mode 100644
index 00000000..bbaceef5
--- /dev/null
+++ b/general/components/Header/AuthComponent.vue
@@ -0,0 +1,145 @@
+
+
+ Your e-mail address has been verified successfully. You can now log in.
+
+ A recovery e-mail has been sent to the provided e-mail address. Please
+ check your inbox and follow the instructions provided to reset your
+ password.
+
+ You created an account. A verification e-mail has been sent to the
+ provided e-mail address. Please check your inbox and follow the
+ instructions provided to verify your e-mail address.
+ The password has been updated successfully.E-mail verification
+ Login
+
+
+ Recover password
+ Register
+ Reset password
+