diff --git a/fda-authentication-service/Dockerfile b/fda-authentication-service/Dockerfile index 9af6f05c5a09e4e3533781afe60d396844a2a4fc..52ffbfc6febd5568aacde572ee6b129ecc154afe 100644 --- a/fda-authentication-service/Dockerfile +++ b/fda-authentication-service/Dockerfile @@ -5,7 +5,7 @@ MAINTAINER Martin Weise <martin.weise@tuwien.ac.at> # Enable health and metrics support ENV KC_HEALTH_ENABLED=true ENV KC_METRICS_ENABLED=true -ENV KC_FEATURES=account-api +ENV KC_FEATURES=update-email # Configure a database vendor ENV KC_DB=mariadb diff --git a/fda-authentication-service/dbrepo-realm.json b/fda-authentication-service/dbrepo-realm.json index 37c61f0a609bb5d179f24b67008cddad6e3c3a0f..461d2cb8a773ca071f1c0e2308e7de1d7225573c 100644 --- a/fda-authentication-service/dbrepo-realm.json +++ b/fda-authentication-service/dbrepo-realm.json @@ -831,7 +831,7 @@ "otpPolicyLookAheadWindow" : 1, "otpPolicyPeriod" : 30, "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppFreeOTPName", "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName" ], + "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName", "totpAppFreeOTPName" ], "webAuthnPolicyRpEntityName" : "keycloak", "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], "webAuthnPolicyRpId" : "", @@ -1033,7 +1033,80 @@ "user.attribute" : "theme_dark", "id.token.claim" : "false", "access.token.claim" : "true", - "claim.name" : "theme_dark" + "claim.name" : "metadata.theme_dark" + } + }, { + "id" : "8bfdf16e-654e-4579-a370-057dcc1c1ffa", + "name" : "User ORCID", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "aggregate.attrs" : "true", + "multivalued" : "false", + "userinfo.token.claim" : "true", + "user.attribute" : "orcid", + "id.token.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "metadata.orcid" + } + }, { + "id" : "ad9f09dc-258c-42b3-9a3f-b2a6927e6c2d", + "name" : "User Lastname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "aggregate.attrs" : "true", + "multivalued" : "false", + "userinfo.token.claim" : "true", + "user.attribute" : "family_name", + "id.token.claim" : "false", + "access.token.claim" : "true" + } + }, { + "id" : "0d6dcf5f-f79a-49af-a252-9a4be712af83", + "name" : "User Firstname", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "aggregate.attrs" : "true", + "multivalued" : "false", + "userinfo.token.claim" : "true", + "user.attribute" : "given_name", + "id.token.claim" : "false", + "access.token.claim" : "true" + } + }, { + "id" : "0654ae15-f40b-48c5-b316-7345864eaba0", + "name" : "User Titles Before", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "aggregate.attrs" : "true", + "multivalued" : "false", + "userinfo.token.claim" : "true", + "user.attribute" : "titles_before", + "id.token.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "metadata.titles_before" + } + }, { + "id" : "87da4719-cd36-4479-9f5a-216d2e9eeff6", + "name" : "User Titles After", + "protocol" : "openid-connect", + "protocolMapper" : "oidc-usermodel-attribute-mapper", + "consentRequired" : false, + "config" : { + "aggregate.attrs" : "true", + "multivalued" : "false", + "userinfo.token.claim" : "true", + "user.attribute" : "titles_after", + "id.token.claim" : "false", + "access.token.claim" : "true", + "claim.name" : "metadata.titles_after" } } ], "defaultClientScopes" : [ "web-origins", "acr", "profile", "roles", "email" ], @@ -1654,7 +1727,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "saml-role-list-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-full-name-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-address-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-user-property-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper" ] } }, { "id" : "3ab11d74-5e76-408a-b85a-26bf8950f979", @@ -1663,7 +1736,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-address-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper" ] + "allowed-protocol-mapper-types" : [ "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-usermodel-attribute-mapper", "oidc-full-name-mapper", "oidc-sha256-pairwise-sub-mapper", "oidc-address-mapper" ] } } ], "org.keycloak.keys.KeyProvider" : [ { @@ -1715,7 +1788,7 @@ "internationalizationEnabled" : false, "supportedLocales" : [ ], "authenticationFlows" : [ { - "id" : "33211b4e-675b-4013-856c-da4078de0afb", + "id" : "c5a3dd66-04ab-412f-a687-a86750997514", "alias" : "Account verification options", "description" : "Method with which to verity the existing account", "providerId" : "basic-flow", @@ -1737,7 +1810,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "109fb15d-f329-41aa-949f-64bc533e6414", + "id" : "77db7a9d-32ea-46ea-965c-7543585f0a8a", "alias" : "Authentication Options", "description" : "Authentication options.", "providerId" : "basic-flow", @@ -1766,7 +1839,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "b8f06b2f-2f1a-450f-b85c-cb3a7e869839", + "id" : "d728bf4f-c26a-4a2f-8968-7bd190abedfe", "alias" : "Browser - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1788,7 +1861,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "3493a35f-cc34-4e56-9b69-0e6ff546ba18", + "id" : "a196acb5-833c-4a34-83c7-c37a45cb144f", "alias" : "Direct Grant - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1810,7 +1883,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "8043eac4-5ff7-474b-8f95-859ed62f6842", + "id" : "73bb1748-899a-4160-b66b-d587f88ca23f", "alias" : "First broker login - Conditional OTP", "description" : "Flow to determine if the OTP is required for the authentication", "providerId" : "basic-flow", @@ -1832,7 +1905,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "b1da6548-0ef1-46e1-bd63-c4f582391435", + "id" : "e4fd72f0-e9c6-4100-99d8-fa3dc6f475c0", "alias" : "Handle Existing Account", "description" : "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId" : "basic-flow", @@ -1854,7 +1927,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "d6e4b6dc-86dc-4f8c-9663-98fea9f2d21e", + "id" : "cfae0db9-63b8-43b3-9e35-a5980edcfb92", "alias" : "Reset - Conditional OTP", "description" : "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId" : "basic-flow", @@ -1876,7 +1949,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "e1596fa3-48cc-4c0b-af4c-812155e07a5f", + "id" : "623d89f1-4fa7-4d75-a7b0-92aea5d39c9e", "alias" : "User creation or linking", "description" : "Flow for the existing/non-existing user alternatives", "providerId" : "basic-flow", @@ -1899,7 +1972,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "60001e07-a04f-4f57-a51f-2da7f3c6edac", + "id" : "c9977d2b-dfa5-4d71-9822-953d9b25a6b6", "alias" : "Verify Existing Account by Re-authentication", "description" : "Reauthentication of existing account", "providerId" : "basic-flow", @@ -1921,7 +1994,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "9bb61bd2-0a19-46d2-b2bc-30e6f0924735", + "id" : "6d3c2734-7f38-4d56-afd5-de813f0b7557", "alias" : "browser", "description" : "browser based authentication", "providerId" : "basic-flow", @@ -1957,7 +2030,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "33824d36-611d-4981-9077-2d42c6d4ff8a", + "id" : "ba8e84c8-cc24-4830-b7d0-ab8974d3877e", "alias" : "clients", "description" : "Base authentication for clients", "providerId" : "client-flow", @@ -1993,7 +2066,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "a157169d-5c3f-4bcc-b58e-633c1018dab1", + "id" : "7e5174e7-c796-47e5-878d-c9843e79b20d", "alias" : "direct grant", "description" : "OpenID Connect Resource Owner Grant", "providerId" : "basic-flow", @@ -2022,7 +2095,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "cfda8c20-da58-453a-83eb-3e665f8edab6", + "id" : "5f501679-10bd-484e-bd5e-851ff4086942", "alias" : "docker auth", "description" : "Used by Docker clients to authenticate against the IDP", "providerId" : "basic-flow", @@ -2037,7 +2110,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "79c9ed59-9f5b-48d9-9581-a10c9835d8ff", + "id" : "fd11f60f-ee22-4eb9-8721-c2feee85f36f", "alias" : "first broker login", "description" : "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId" : "basic-flow", @@ -2060,7 +2133,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "0efeb8be-12fa-44fb-9a79-cd32e7b0adf2", + "id" : "b7b5a91d-3d5a-40e6-b705-3bc9aa06c5af", "alias" : "forms", "description" : "Username, password, otp and other auth forms.", "providerId" : "basic-flow", @@ -2082,7 +2155,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "b8c03f50-9387-42a8-8da3-364194ef2855", + "id" : "22d18eb6-1172-4651-b39e-1b31884e728a", "alias" : "http challenge", "description" : "An authentication flow based on challenge-response HTTP Authentication Schemes", "providerId" : "basic-flow", @@ -2104,7 +2177,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "a44f0191-9493-4175-9eaa-7222c0f2b6c1", + "id" : "90eb8d81-b244-4d02-b513-3519cb44c5cf", "alias" : "registration", "description" : "registration flow", "providerId" : "basic-flow", @@ -2120,7 +2193,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "35a3f64b-70c8-4e8d-8544-f37a2463b83d", + "id" : "937bff8d-128a-42bf-9437-598541744fa1", "alias" : "registration form", "description" : "registration form", "providerId" : "form-flow", @@ -2156,7 +2229,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "707d0ed9-818b-4f1c-a87d-31b307e414ef", + "id" : "ce4981b4-a9c8-4389-b039-8c94517753b0", "alias" : "reset credentials", "description" : "Reset credentials for a user if they forgot their password or something", "providerId" : "basic-flow", @@ -2192,7 +2265,7 @@ "userSetupAllowed" : false } ] }, { - "id" : "16d82d84-005a-4c41-9237-f258abc1ccb5", + "id" : "45bd9132-b308-4971-841b-ade951fcd58b", "alias" : "saml ecp", "description" : "SAML ECP Profile Authentication Flow", "providerId" : "basic-flow", @@ -2208,13 +2281,13 @@ } ] } ], "authenticatorConfig" : [ { - "id" : "fcf3912f-3aad-44a3-9ae5-4f4f2d9db542", + "id" : "d243532c-fb83-4523-9aec-32dcb0d5119a", "alias" : "create unique user config", "config" : { "require.password.update.after.registration" : "false" } }, { - "id" : "663c695e-13ce-43d0-8955-e3d61a0d2daf", + "id" : "29dc7168-78ae-407a-b974-15dfb79dc5c0", "alias" : "review profile config", "config" : { "update.profile.on.first.login" : "missing" diff --git a/fda-ui/Dockerfile b/fda-ui/Dockerfile index e5ecd6473878935de025fbb42ca21223a8800e2d..8078a05225d11e8d1196ab3ae54ba7fe652dd292 100644 --- a/fda-ui/Dockerfile +++ b/fda-ui/Dockerfile @@ -21,7 +21,9 @@ RUN yarn install --frozen-lockfile > /dev/null 2>&1 COPY ./nuxt.config.js ./ COPY ./ava.config.cjs ./ COPY ./babel.config.js ./ +COPY ./config.js ./ COPY ./assets ./assets +COPY ./api ./api COPY ./components ./components COPY ./lang ./lang COPY ./layouts ./layouts diff --git a/fda-ui/api/user/index.js b/fda-ui/api/user/index.js index f4bb4590c1c55fff843af8381f484ff624265a35..8894d2bfd1f14119c500bd88128975ee91e3238c 100644 --- a/fda-ui/api/user/index.js +++ b/fda-ui/api/user/index.js @@ -42,14 +42,19 @@ export function tokenToUser (token) { const data = jwt_decode(token) return { id: data.sub, - firstname: data.given_name, - lastname: data.family_name, + firstname: data.given_name || null, + lastname: data.family_name || null, username: data.preferred_username, - theme_dark: data.theme_dark, - titles_before: data.titles_before, - titles_after: data.titles_after, - affiliation: data.affiliation, - orcid: data.orcid, - email_verified: data.email_verified + theme_dark: data.metadata?.theme_dark || false, + titles_before: data.metadata?.titles_before || null, + titles_after: data.metadata?.titles_after || null, + affiliation: data.metadata?.affiliation || null, + orcid: data.metadata?.orcid || null, + email_verified: data.metadata?.email_verified || null } } + +export function tokenToRoles (token) { + const data = jwt_decode(token) + return data.realm_access.roles +} diff --git a/fda-ui/pages/container/index.vue b/fda-ui/pages/container/index.vue index b439563e202f6e6eea3439da3efce5da89d885a9..d83e211bc1a8a959317b907bf0b435a5cf0a91a6 100644 --- a/fda-ui/pages/container/index.vue +++ b/fda-ui/pages/container/index.vue @@ -6,7 +6,7 @@ </v-toolbar-title> <v-spacer /> <v-toolbar-title> - <v-btn v-if="isResearcher" color="primary" name="create-database" @click.stop="createDbDialog = true"> + <v-btn v-if="canCreateDatabase" color="primary" name="create-database" @click.stop="createDbDialog = true"> <v-icon left>mdi-plus</v-icon> Database </v-btn> </v-toolbar-title> @@ -25,7 +25,7 @@ import { mdiDatabaseArrowRightOutline } from '@mdi/js' import CreateDB from '@/components/dialogs/CreateDB' import DatabaseList from '@/components/DatabaseList' -import { isResearcher } from '@/utils' +import { tokenToRoles } from '@/api/user' export default { components: { @@ -64,8 +64,12 @@ export default { headers: { Authorization: `Bearer ${this.token}` } } }, - isResearcher () { - return isResearcher(this.user) + canCreateDatabase () { + if (!this.token) { + return false + } + const roles = tokenToRoles(this.token) + return roles.includes('create-container') && roles.includes('create-database') } }, methods: { diff --git a/fda-ui/pages/user/info.vue b/fda-ui/pages/user/info.vue index d927c7a516ae9f9fa062180baca539c0aadc8ea0..7f0764650fcdf3d19b2b47a49da1af6481757c21 100644 --- a/fda-ui/pages/user/info.vue +++ b/fda-ui/pages/user/info.vue @@ -1,50 +1,32 @@ <template> <div v-if="token"> - <pre>{{ model }}</pre> - <pre>{{ user }}</pre> <UserToolbar /> <v-tabs-items v-model="tab"> <v-tab-item> <v-card flat> <v-card-title>User Information</v-card-title> + <v-card-subtitle>Your identity is externally managed</v-card-subtitle> <v-card-text> <v-form v-model="valid1" @submit.prevent="submit"> <v-row dense> - <v-col md="2"> + <v-col md="3"> <v-text-field v-model="model.id" disabled label="ID" /> </v-col> - <v-col md="4"> + <v-col md="3"> <v-text-field v-model="model.username" disabled label="Username" /> </v-col> </v-row> - <v-row dense> - <v-col md="6"> - <v-text-field - v-model="roles" - disabled - label="Roles" /> - </v-col> - </v-row> - <v-row dense> - <v-col md="6"> - <v-text-field - v-model="model.titles_before" - :disabled="error" - hint="e.g. Prof." - label="Titles Before" /> - </v-col> - </v-row> <v-row dense> <v-col md="6"> <v-text-field v-model="model.firstname" - :disabled="error" + disabled :rules="[v => !!v || $t('Required')]" required label="Firstname *" /> @@ -54,26 +36,17 @@ <v-col md="6"> <v-text-field v-model="model.lastname" - :disabled="error" + disabled :rules="[v => !!v || $t('Required')]" required label="Lastname *" /> </v-col> </v-row> - <v-row dense> - <v-col md="6"> - <v-text-field - v-model="model.titles_after" - :disabled="error" - hint="e.g. BSc" - label="Titles After" /> - </v-col> - </v-row> <v-row dense> <v-col md="6"> <v-text-field v-model="model.affiliation" - :disabled="error" + disabled hint="e.g. University of xyz" label="Affiliation" /> </v-col> @@ -82,42 +55,27 @@ <v-col md="6"> <v-text-field v-model="model.orcid" - :disabled="error" + disabled maxlength="19" hint="e.g. 0000-0002-1825-0097" label="ORCID" /> </v-col> </v-row> - <v-row dense> - <v-col md="6"> - <v-btn - small - color="primary" - :disabled="!valid1 || error" - type="submit" - @click="updateInfo"> - Update - </v-btn> - </v-col> - </v-row> </v-form> </v-card-text> <v-divider /> - <v-card-title>Theme</v-card-title> + <v-card-title>Roles</v-card-title> <v-card-text> - <v-form v-model="valid2" @submit.prevent="submit"> - <v-row dense> - <v-col cols="5"> - <v-switch - v-model="model.theme_dark" - inset - label="Dark Mode" - :disabled="error" - :loading="loading" - @click="toggleTheme" /> - </v-col> - </v-row> - </v-form> + <v-row dense> + <v-col> + <v-select + v-model="roles" + :items="roles" + multiple + chips + disabled /> + </v-col> + </v-row> </v-card-text> </v-card> </v-tab-item> @@ -126,7 +84,8 @@ </template> <script> -import UserToolbar from '@/components/UserToolbar' +import { UserToolbar } from '@/components/UserToolbar' +import { tokenToRoles } from '@/api/user' export default { components: { @@ -160,10 +119,9 @@ export default { return this.$store.state.user }, roles () { - if (!this.user.roles) { - return null - } - return this.user.roles.join(', ') + const roles = tokenToRoles(this.token) + console.debug('roles', roles) + return roles }, config () { if (this.token === null) {