@live-change/access-control-frontend 0.1.5 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2019-2022 Michał Łaszczewski
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -1,6 +1,6 @@
1
1
  const { devices } = require('playwright')
2
2
 
3
- const testServerPort = process.env.TEST_URL ? 0 : require('get-port-sync')()
3
+ const testServerPort = process.env.TEST_URL ? 0 : require('get-port-sync')()
4
4
  const testServerUrl = process.env.TEST_URL || `http://localhost:${testServerPort}`
5
5
 
6
6
  const device = devices['Pixel 2']
@@ -13,8 +13,9 @@ exports.config = {
13
13
  require: '@live-change/codeceptjs-helper',
14
14
  startServer: !process.env.TEST_URL,
15
15
  enableSessions: true,
16
- initScript: "./init.js",
16
+ //initScript: "./init.js",
17
17
  port: testServerPort,
18
+ dbAccess: true,
18
19
  dev: true
19
20
  },
20
21
  VideoHelper: {
@@ -57,4 +58,4 @@ exports.config = {
57
58
  enabled: true
58
59
  }
59
60
  }
60
- }
61
+ }
@@ -0,0 +1,23 @@
1
+ const happyPath = false
2
+
3
+ Feature('access control invite')
4
+
5
+ Scenario('invite user that already exists', async ({ I }) => {
6
+
7
+ const adminUser = await I.haveUser()
8
+ const anotherUser = await I.haveUser()
9
+ await I.haveUserWithAccess(adminUser, 'example_Example', 'one', ['administrator'])
10
+
11
+ await I.amOnPage('/')
12
+ await I.amLoggedIn(adminUser)
13
+
14
+ I.see('Access Granted!')
15
+ I.click('Share')
16
+ I.see('Access Control')
17
+ I.click('Invite with email')
18
+ I.see('Invite user with email')
19
+ I.see('Email address')
20
+ I.fillField('input[id="email"]', anotherUser.email)
21
+ I.click('Invite')
22
+ I.click('Invite')
23
+ })
package/e2e/steps_file.js CHANGED
@@ -1,11 +1,28 @@
1
- // in this file you can append custom step methods to 'I' object
1
+ const App = require('@live-change/framework')
2
+ const app = App.app()
2
3
 
3
- const userSteps = require('../../user-frontend/e2e/steps_file.js')
4
+ const userSteps = require('../../user-frontend/e2e/steps_file.js').steps
4
5
 
5
- module.exports = function() {
6
- return actor({
6
+ const steps = {
7
+
8
+ ...userSteps,
7
9
 
8
- ...userSteps
10
+ async haveUserWithAccess(user, objectType, object, roles = ['administrator']) {
11
+ const I = this
12
+ const Access = await I.haveModel('accessControl', 'Access')
13
+ await Access.create({
14
+ id: App.encodeIdentifier(['user_User', user.id, 'example_Example', 'one']),
15
+ sessionOrUserType: 'user_User', sessionOrUser: user.id,
16
+ objectType: 'example_Example', object: 'one',
17
+ roles
18
+ })
19
+ }
9
20
 
10
- })
11
21
  }
22
+
23
+ module.exports = function() {
24
+ return actor(steps)
25
+ }
26
+
27
+ module.exports.steps = steps
28
+
package/front/src/App.vue CHANGED
@@ -15,6 +15,7 @@
15
15
  import ViewRoot from "@live-change/frontend-base/ViewRoot.vue"
16
16
  import "./notifications"
17
17
 
18
+
18
19
  import { useMeta } from 'vue-meta'
19
20
  const { meta } = useMeta({
20
21
  title: 'Title',
@@ -25,10 +26,16 @@
25
26
  })
26
27
 
27
28
  import { watch } from 'vue'
28
- import { client as useClient } from '@live-change/vue3-ssr'
29
+ import { client as useClient, useApi } from '@live-change/vue3-ssr'
29
30
  const client = useClient()
30
31
  watch(client, (newClient, oldClient) => {
31
32
  console.log("WATCH CLIENT", oldClient, '=>', newClient)
32
33
  })
33
34
 
35
+ const api = useApi()
36
+ import emailValidator from "@live-change/email-service/clientEmailValidator.js"
37
+ import passwordValidator from "@live-change/password-authentication-service/clientPasswordValidator.js"
38
+ api.validators.email = emailValidator
39
+ api.validators.password = passwordValidator
40
+
34
41
  </script>
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <div class="w-full sm:w-9 md:w-8 lg:w-6 surface-card p-4 shadow-2 border-round">
3
+ <div class="text-center mb-5">
4
+ <div class="text-900 text-3xl font-medium mb-3">
5
+ Access Control
6
+ </div>
7
+ </div>
8
+
9
+ <AccessControl :objectType="objectType" :object="object" />
10
+ </div>
11
+ </template>
12
+
13
+ <script>
14
+
15
+ import AccessControl from "./configuration/AccessControl.vue"
16
+
17
+ const {
18
+ object, objectType
19
+ } = defineProps({
20
+ object: {
21
+ type: String,
22
+ default: 'example_Example'
23
+ },
24
+ objectType: {
25
+ type: String,
26
+ default: 'one'
27
+ }
28
+ })
29
+
30
+ </script>
31
+
32
+ <style scoped>
33
+
34
+ </style>
@@ -8,6 +8,27 @@
8
8
  </div>
9
9
  <div class="flex flex-grow-1"></div>
10
10
  <NotificationsIcon />
11
+ <UserIcon />
12
+
13
+
14
+ <div class="static w-auto w-full surface-overlay left-0 top-100 z-1 shadow-none hidden">
15
+ <ul class="list-none p-0 m-0 flex align-items-center select-none flex-row border-top-none">
16
+ <li>
17
+ <a v-ripple class="flex p-3 px-3 align-items-center text-600 hover:text-900 hover:surface-100
18
+ font-medium border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
19
+ <i class="pi pi-inbox text-base text-2xl mr-0"></i>
20
+ </a>
21
+ </li>
22
+ <li>
23
+ <a v-ripple class="flex p-3 px-3 align-items-center text-600 hover:text-900 hover:surface-100 font-medium
24
+ border-round cursor-pointer transition-colors transition-duration-150 p-ripple">
25
+ <i class="pi pi-bell text-base lg:text-2xl mr-2 lg:mr-0" v-badge.danger></i>
26
+ </a>
27
+ </li>
28
+ </ul>
29
+ </div>
30
+
31
+
11
32
  <a v-ripple class="cursor-pointer block lg:hidden text-700 p-ripple ml-2 hover:surface-100 p-2"
12
33
  v-styleclass="{ selector: '@next', enterClass: 'hidden', leaveToClass: 'hidden', hideOnOutsideClick: true }">
13
34
  <i class="pi pi-bars text-4xl"></i>
@@ -96,8 +117,12 @@
96
117
 
97
118
  import { NotificationsIcon } from "@live-change/user-frontend"
98
119
 
120
+ import { UserIcon } from "@live-change/user-frontend"
121
+
122
+
123
+
99
124
  </script>
100
125
 
101
126
  <style scoped>
102
127
 
103
- </style>
128
+ </style>
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <Dialog v-model:visible="shareDialog"
3
+ :modal="true">
4
+ <template #header>
5
+ <div class="text-900 text-3xl font-medium">
6
+ Access Control
7
+ </div>
8
+ </template>
9
+ <AccessControl :objectType="objectType" :object="object" />
10
+ </Dialog>
11
+ <div class="w-full sm:w-9 md:w-8 lg:w-6 surface-card p-4 shadow-2 border-round">
12
+ <LimitedAccess :objectType="objectType" :object="object" :requiredRoles="requiredRoles"
13
+ v-slot="{ authorized, roles, accesses }">
14
+ <div v-if="authorized" class="text-right">
15
+ <Button label="Share" icon="pi pi-share-alt" class="p-button mb-4" @click="showShareDialog" />
16
+ </div>
17
+ <div v-if="authorized" class="text-900 text-3xl font-medium mb-3">
18
+ Access Granted!
19
+ </div>
20
+ <div>
21
+ <p>
22
+ <strong>Roles:</strong>
23
+ <span v-for="role in roles" class="mx-1">{{ role }}</span>
24
+ </p>
25
+ <div>
26
+ <strong>Accesses:</strong>
27
+ <ol>
28
+ <li v-for="access of accesses">
29
+ <em class="mr-2">{{ access.id.split(':').join(' : ') }}</em>
30
+ <span v-for="role in access.roles" class="mx-1">{{ role }}</span>
31
+ </li>
32
+ </ol>
33
+ </div>
34
+ </div>
35
+ </LimitedAccess>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup>
40
+
41
+ import Button from "primevue/button"
42
+ import Dialog from "primevue/dialog"
43
+
44
+ import { ref } from 'vue'
45
+
46
+ import LimitedAccess from "./components/LimitedAccess.vue"
47
+ import AccessControl from "./configuration/AccessControl.vue"
48
+
49
+ const {
50
+ object, objectType, requiredRoles
51
+ } = defineProps({
52
+ object: {
53
+ type: String,
54
+ default: 'example_Example'
55
+ },
56
+ objectType: {
57
+ type: String,
58
+ default: 'one'
59
+ },
60
+ requiredRoles: {
61
+ type: Array,
62
+ default: () => ['reader', 'writer', 'moderator', 'administrator']
63
+ }
64
+ })
65
+
66
+ const shareDialog = ref(false)
67
+
68
+ function showShareDialog() {
69
+ shareDialog.value = true
70
+ }
71
+
72
+ </script>
73
+
74
+ <style scoped>
75
+
76
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <slot v-if="!authorized" name="blocked" :authorized="authorized" :roles="accessRoles" :accesses="accesses">
2
+ <slot v-if="!hidden && !authorized" name="blocked" :authorized="authorized" :roles="accessRoles" :accesses="accesses">
3
3
  <div class="flex align-items-start p-4 bg-pink-100 border-round border-1 border-pink-300 mb-4">
4
4
  <i class="pi pi-times-circle text-pink-900 text-2xl mr-3" />
5
5
  <div class="mr-3">
@@ -10,22 +10,18 @@
10
10
  </div>
11
11
  </div>
12
12
  </slot>
13
- <BlockUI :blocked="notAuthorized">
13
+ <BlockUI v-if="!hidden" :blocked="!authorized">
14
14
  <slot :authorized="authorized" :roles="accessRoles" :accesses="accesses"></slot>
15
15
  </BlockUI>
16
- <div v-if="false">
17
- <h4>Bans</h4>
18
- <pre>{{ JSON.stringify(bansState, null, ' ') }}</pre>
19
- <h4>Counters</h4>
20
- <pre>{{ JSON.stringify(countersState, null, ' ') }}</pre>
21
- </div>
16
+ <slot v-if="authorized && hidden" :roles="accessRoles" :accesses="accesses"></slot>
17
+ <slot v-if="!authorized && hidden" name="alternative" :roles="accessRoles" :accesses="accesses"></slot>
22
18
  </template>
23
19
 
24
20
  <script setup>
25
21
 
26
22
  import BlockUI from 'primevue/blockui'
27
23
 
28
- const { objectType, object, objects, requiredRoles } = defineProps({
24
+ const props = defineProps({
29
25
  objectType: {
30
26
  type: String,
31
27
  default: null
@@ -41,9 +37,15 @@
41
37
  requiredRoles: {
42
38
  type: Array,
43
39
  default: () => []
44
- }
40
+ },
41
+ hidden: {
42
+ type: Boolean,
43
+ default: false
44
+ },
45
45
  })
46
46
 
47
+ const { objectType, object, objects, requiredRoles } = props
48
+
47
49
  const allObjects = ((objectType && object) ? [{ objectType, object }] : []).concat(objects || [])
48
50
 
49
51
  import { provide, computed } from 'vue'
@@ -62,14 +64,17 @@
62
64
  for(const access of accessesList) {
63
65
  roles = roles.filter(role => access.roles.includes(role))
64
66
  }
67
+ return roles
65
68
  })
66
69
 
67
70
  const authorized = computed(() => {
68
- const roles = accessRoles.value
69
- for(const requiredRole of requiredRoles) {
70
- if(!roles.includes(requiredRole)) return false
71
+ const clientRoles = accessRoles.value
72
+ for(const requiredRolesOption of requiredRoles) {
73
+ if((Array.isArray(requiredRolesOption) ? requiredRolesOption : [requiredRolesOption])
74
+ .every(role => clientRoles.includes(role))
75
+ ) return true
71
76
  }
72
- return true
77
+ return false
73
78
  })
74
79
 
75
80
  </script>
@@ -1,10 +1,5 @@
1
1
  <template>
2
- <div class="w-full sm:w-9 md:w-8 lg:w-6 surface-card p-4 shadow-2 border-round">
3
- <div class="text-center mb-5">
4
- <div class="text-900 text-3xl font-medium mb-3">
5
- Access Control
6
- </div>
7
- </div>
2
+ <div>
8
3
 
9
4
  <div class="text-center">
10
5
  <Button label="Invite with email" icon="pi pi-envelope" class="p-button mb-4" @click="inviteDialog = true" />
@@ -20,7 +20,7 @@ export function routes(config = {}) {
20
20
  ...inviteRoutes(config),
21
21
 
22
22
  route({
23
- name: 'accessControl:testPage', path: prefix + '', meta: { },
23
+ name: 'accessControl:configurationPage', path: prefix + 'configuration', meta: { },
24
24
  component: () => import("./configuration/AccessControl.vue"),
25
25
  props: {
26
26
  objectType: 'example_Example',
@@ -28,6 +28,15 @@ export function routes(config = {}) {
28
28
  }
29
29
  }),
30
30
 
31
+ route({
32
+ name: 'accessControl:testPage', path: prefix + '', meta: { },
33
+ component: () => import("./TestPage.vue"),
34
+ props: {
35
+ objectType: 'example_Example',
36
+ object: 'one'
37
+ }
38
+ }),
39
+
31
40
  ...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, raw: true }}) })
32
41
  ]
33
42
  }
package/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import LimitedAccess from "./front/src/components/LimitedAccess.vue"
2
+ export { LimitedAccess }
3
+
4
+ import AccessControl from "./front/src/configuration/AccessControl.vue"
5
+ import AccessInvitations from "./front/src/configuration/AccessInvitations.vue"
6
+ import AccessList from "./front/src/configuration/AccessList.vue"
7
+ import AccessRequests from "./front/src/configuration/AccessRequests.vue"
8
+ import PublicAccess from "./front/src/configuration/PublicAccess.vue"
9
+ export { AccessControl, AccessInvitations, AccessList, AccessRequests, PublicAccess }
10
+
11
+ import "./front/src/notifications/index.js"
12
+
13
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/access-control-frontend",
3
- "version": "0.1.5",
3
+ "version": "0.2.2",
4
4
  "scripts": {
5
5
  "memDev": "lcli memDev --enableSessions --initScript ./init.js --dbAccess",
6
6
  "localDevInit": "rm tmp.db; lcli localDev --enableSessions --initScript ./init.js",
@@ -20,21 +20,22 @@
20
20
  "debug": "node --inspect-brk server"
21
21
  },
22
22
  "dependencies": {
23
- "@live-change/access-control-service": "0.2.40",
24
- "@live-change/cli": "0.6.8",
25
- "@live-change/dao": "0.5.0",
26
- "@live-change/dao-vue3": "0.5.0",
27
- "@live-change/dao-websocket": "0.5.0",
28
- "@live-change/db-admin": "0.5.13",
29
- "@live-change/framework": "0.6.8",
30
- "@live-change/password-authentication-service": "0.2.41",
31
- "@live-change/secret-code-service": "0.2.41",
32
- "@live-change/secret-link-service": "0.2.41",
33
- "@live-change/session-service": "0.2.41",
34
- "@live-change/user-frontend": "^0.1.5",
35
- "@live-change/user-service": "0.2.41",
36
- "@live-change/vue3-components": "0.2.14",
37
- "@live-change/vue3-ssr": "0.2.14",
23
+ "@live-change/access-control-service": "0.2.51",
24
+ "@live-change/cli": "0.6.14",
25
+ "@live-change/dao": "0.5.6",
26
+ "@live-change/dao-vue3": "0.5.6",
27
+ "@live-change/dao-websocket": "0.5.6",
28
+ "@live-change/db-admin": "0.5.17",
29
+ "@live-change/framework": "0.6.14",
30
+ "@live-change/frontend-base": "^0.2.2",
31
+ "@live-change/password-authentication-service": "0.2.51",
32
+ "@live-change/secret-code-service": "0.2.51",
33
+ "@live-change/secret-link-service": "0.2.51",
34
+ "@live-change/session-service": "0.2.51",
35
+ "@live-change/user-frontend": "^0.2.2",
36
+ "@live-change/user-service": "0.2.51",
37
+ "@live-change/vue3-components": "0.2.15",
38
+ "@live-change/vue3-ssr": "0.2.15",
38
39
  "@vueuse/core": "^9.1.0",
39
40
  "codeceptjs-assert": "^0.0.5",
40
41
  "compression": "^1.7.4",
@@ -52,7 +53,7 @@
52
53
  "vue3-scroll-border": "0.1.2"
53
54
  },
54
55
  "devDependencies": {
55
- "@live-change/codeceptjs-helper": "0.6.8",
56
+ "@live-change/codeceptjs-helper": "0.6.14",
56
57
  "@wdio/selenium-standalone-service": "^7.20.8",
57
58
  "codeceptjs": "^3.3.4",
58
59
  "generate-password": "1.7.0",
@@ -62,7 +63,7 @@
62
63
  "webdriverio": "^7.20.9"
63
64
  },
64
65
  "author": "",
65
- "license": "ISC",
66
+ "license": "BSD-3-Clause",
66
67
  "description": "",
67
- "gitHead": "ca3253bac1e9c01143244f5236bd40278a86ebaa"
68
+ "gitHead": "f7abc1d907b85c321e629086c162e4b622eee91b"
68
69
  }
package/server/init.js CHANGED
@@ -1,35 +1,23 @@
1
1
  const App = require('@live-change/framework')
2
2
  const app = App.app()
3
3
 
4
+ const { createUser } = require('@live-change/user-frontend/server/init-functions.js')
5
+
4
6
  module.exports = async function(services) {
5
7
 
6
8
  const { PasswordAuthentication } = services.passwordAuthentication.models
7
9
  const { PublicAccess, Access, AccessRequest, AccessInvitation } = services.accessControl.models
8
10
 
9
- async function createUser(name, email, password, user = app.generateUid()) {
10
- const passwordHash = PasswordAuthentication.definition.properties.passwordHash.preFilter(password)
11
- await services.user.models.User.create({ id: user, roles: [] })
12
- await PasswordAuthentication.create({ id: user, user, passwordHash })
13
- await services.email.models.Email.create({ id: email, email, user })
14
- await services.userIdentification.models.Identification.create({
15
- id: App.encodeIdentifier(['user_User', user]), sessionOrUserType: 'user_User', sessionOrUser: user,
16
- name
17
- })
18
- return {
19
- id: user,
20
- name,
21
- email,
22
- password
23
- }
24
- }
25
-
26
- const session = 'Q6OytlOJ54r0tF1BriDB8bEYoGEUIf+i__'
11
+ const session = 'l5W5GIeTfffiFpfUTkvpjNjrVR6buWDX'
27
12
 
28
13
  //console.log("MDL", services.passwordAuthentication.models.PasswordAuthentication)
29
14
 
30
- const user1 = await createUser('Test User 1', 'test1@test.com', 'Testy123', 'u1')
31
- const user2 = await createUser('Test User 2 with very long name!', 'test2@test.com', 'Testy123')
32
- const user3 = await createUser('Test User 3', 'test3@test.com', 'Testy123')
15
+ const user1 = await createUser(services,
16
+ 'Test User 1', 'test1@test.com', 'Testy123', 'u1')
17
+ const user2 = await createUser(services,
18
+ 'Test User 2 with very long name!', 'test2@test.com', 'Testy123')
19
+ const user3 = await createUser(services,
20
+ 'Test User 3', 'test3@test.com', 'Testy123')
33
21
 
34
22
  await services.user.models.AuthenticatedUser.create({ id: session, session, user: user1.id })
35
23
 
@@ -37,7 +25,7 @@ module.exports = async function(services) {
37
25
  "id": app.generateUid(),
38
26
  "notificationType": "accessControl_Invitation",
39
27
  "objectType": "example_Example",
40
- "object": "two",
28
+ "object": "one",
41
29
  "fromType": "user_User",
42
30
  "from": user1.id,
43
31
  "sessionOrUserType": "user_User",
@@ -46,12 +34,12 @@ module.exports = async function(services) {
46
34
  "readState": "new"
47
35
  })
48
36
 
49
- await PublicAccess.create({
37
+ /* await PublicAccess.create({
50
38
  id: App.encodeIdentifier(['example_Example', 'one']),
51
39
  objectType: 'example_Example', object: 'one',
52
40
  userRoles: ['reader'],
53
41
  sessionRoles: ['reader']
54
- })
42
+ })*/
55
43
 
56
44
  await Access.create({
57
45
  id: App.encodeIdentifier(['user_User', user1.id, 'example_Example', 'one']),