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 @@ + + + + + diff --git a/general/components/Header/AuthConfirmEmailModal.vue b/general/components/Header/AuthConfirmEmailModal.vue new file mode 100644 index 00000000..969f6aea --- /dev/null +++ b/general/components/Header/AuthConfirmEmailModal.vue @@ -0,0 +1,63 @@ + + + diff --git a/general/components/Header/AuthLoginModal.vue b/general/components/Header/AuthLoginModal.vue new file mode 100644 index 00000000..0fb89758 --- /dev/null +++ b/general/components/Header/AuthLoginModal.vue @@ -0,0 +1,146 @@ + + + diff --git a/general/components/Header/AuthRecoverModal.vue b/general/components/Header/AuthRecoverModal.vue new file mode 100644 index 00000000..cb006e03 --- /dev/null +++ b/general/components/Header/AuthRecoverModal.vue @@ -0,0 +1,149 @@ + + + diff --git a/general/components/Header/AuthRegisterModal.vue b/general/components/Header/AuthRegisterModal.vue new file mode 100644 index 00000000..3bab662c --- /dev/null +++ b/general/components/Header/AuthRegisterModal.vue @@ -0,0 +1,242 @@ + + + diff --git a/general/components/Header/AuthResetModal.vue b/general/components/Header/AuthResetModal.vue new file mode 100644 index 00000000..c30d9c80 --- /dev/null +++ b/general/components/Header/AuthResetModal.vue @@ -0,0 +1,210 @@ + + + diff --git a/general/components/Header/HeaderComponent.vue b/general/components/Header/HeaderComponent.vue index e7f81155..8ec2d340 100644 --- a/general/components/Header/HeaderComponent.vue +++ b/general/components/Header/HeaderComponent.vue @@ -3,7 +3,7 @@
-