@things-factory/auth-base 10.1.188 → 10.1.198
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/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/signin.d.ts +5 -0
- package/dist-server/controllers/signin.js +25 -0
- package/dist-server/controllers/signin.js.map +1 -1
- package/dist-server/controllers/verification.d.ts +5 -1
- package/dist-server/controllers/verification.js +11 -6
- package/dist-server/controllers/verification.js.map +1 -1
- package/dist-server/index.js +7 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/middlewares/audit-scope-middleware.d.ts +7 -0
- package/dist-server/middlewares/audit-scope-middleware.js +15 -0
- package/dist-server/middlewares/audit-scope-middleware.js.map +1 -0
- package/dist-server/middlewares/authenticate-401-middleware.js +4 -0
- package/dist-server/middlewares/authenticate-401-middleware.js.map +1 -1
- package/dist-server/middlewares/index.js +3 -0
- package/dist-server/middlewares/index.js.map +1 -1
- package/dist-server/role-tasks.d.ts +2 -0
- package/dist-server/role-tasks.js +20 -0
- package/dist-server/role-tasks.js.map +1 -0
- package/dist-server/router/auth-checkin-router.js +3 -5
- package/dist-server/router/auth-checkin-router.js.map +1 -1
- package/dist-server/router/auth-public-process-router.js +26 -14
- package/dist-server/router/auth-public-process-router.js.map +1 -1
- package/dist-server/router/site-root-router.d.ts +2 -0
- package/dist-server/router/site-root-router.js +2 -0
- package/dist-server/router/site-root-router.js.map +1 -1
- package/dist-server/service/access-request/index.d.ts +1 -1
- package/dist-server/service/audit/audit-action.d.ts +36 -0
- package/dist-server/service/audit/audit-action.js +137 -0
- package/dist-server/service/audit/audit-action.js.map +1 -0
- package/dist-server/service/audit/audit-kinds.d.ts +19 -0
- package/dist-server/service/audit/audit-kinds.js +70 -0
- package/dist-server/service/audit/audit-kinds.js.map +1 -0
- package/dist-server/service/audit/audit-query.d.ts +11 -0
- package/dist-server/service/audit/audit-query.js +295 -0
- package/dist-server/service/audit/audit-query.js.map +1 -0
- package/dist-server/service/audit/audit-recorder.d.ts +77 -0
- package/dist-server/service/audit/audit-recorder.js +247 -0
- package/dist-server/service/audit/audit-recorder.js.map +1 -0
- package/dist-server/service/audit/audit-request.d.ts +28 -0
- package/dist-server/service/audit/audit-request.js +96 -0
- package/dist-server/service/audit/audit-request.js.map +1 -0
- package/dist-server/service/audit/audit-retention.d.ts +12 -0
- package/dist-server/service/audit/audit-retention.js +42 -0
- package/dist-server/service/audit/audit-retention.js.map +1 -0
- package/dist-server/service/audit/audit-subscriber.d.ts +39 -0
- package/dist-server/service/audit/audit-subscriber.js +191 -0
- package/dist-server/service/audit/audit-subscriber.js.map +1 -0
- package/dist-server/service/audit/audit-target.d.ts +19 -0
- package/dist-server/service/audit/audit-target.js +46 -0
- package/dist-server/service/audit/audit-target.js.map +1 -0
- package/dist-server/service/audit/audit-types.d.ts +44 -0
- package/dist-server/service/audit/audit-types.js +161 -0
- package/dist-server/service/audit/audit-types.js.map +1 -0
- package/dist-server/service/audit/index.d.ts +6 -0
- package/dist-server/service/audit/index.js +11 -0
- package/dist-server/service/audit/index.js.map +1 -0
- package/dist-server/service/domain-owner/domain-owner-mutation.js +3 -0
- package/dist-server/service/domain-owner/domain-owner-mutation.js.map +1 -1
- package/dist-server/service/index.d.ts +9 -2
- package/dist-server/service/index.js +19 -4
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/open-fields.js +1 -1
- package/dist-server/service/open-fields.js.map +1 -1
- package/dist-server/service/privilege/privilege-directive.js +25 -1
- package/dist-server/service/privilege/privilege-directive.js.map +1 -1
- package/dist-server/service/role/role-mutation.js +8 -1
- package/dist-server/service/role/role-mutation.js.map +1 -1
- package/dist-server/service/role-template/index.d.ts +1 -1
- package/dist-server/service/role-template/role-task.d.ts +74 -0
- package/dist-server/service/role-template/role-task.js +113 -0
- package/dist-server/service/role-template/role-task.js.map +1 -0
- package/dist-server/service/role-template/role-template-gap-resolver.d.ts +9 -5
- package/dist-server/service/role-template/role-template-gap-resolver.js +41 -13
- package/dist-server/service/role-template/role-template-gap-resolver.js.map +1 -1
- package/dist-server/service/role-template/role-template-mutation.js +10 -1
- package/dist-server/service/role-template/role-template-mutation.js.map +1 -1
- package/dist-server/service/role-template/role-template-query.js +4 -2
- package/dist-server/service/role-template/role-template-query.js.map +1 -1
- package/dist-server/service/role-template/role-template-types.d.ts +24 -0
- package/dist-server/service/role-template/role-template-types.js +63 -1
- package/dist-server/service/role-template/role-template-types.js.map +1 -1
- package/dist-server/service/role-template/role-template.d.ts +63 -10
- package/dist-server/service/role-template/role-template.js +92 -18
- package/dist-server/service/role-template/role-template.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/check-permission.d.ts +6 -0
- package/dist-server/utils/check-permission.js +23 -8
- package/dist-server/utils/check-permission.js.map +1 -1
- package/package.json +3 -2
- package/tests/activate-page-case.test.ts +53 -0
- package/tests/audit-db.test.ts +394 -0
- package/tests/auth-base-role-tasks.test.ts +58 -0
- package/tests/list-inheritance-db.test.ts +0 -7
- package/tests/mail-request-answers-db.test.ts +139 -0
- package/tests/open-doors.test.ts +2 -2
- package/tests/refusal-cause.test.ts +1 -1
- package/tests/role-task.test.ts +168 -0
- package/tests/role-template-gap-db.test.ts +77 -2
- package/translations/en.json +16 -3
- package/translations/es.json +16 -3
- package/translations/ja.json +16 -3
- package/translations/ko.json +16 -3
- package/translations/ms.json +16 -3
- package/translations/zh.json +16 -3
|
@@ -24,8 +24,6 @@ const { InvitationStatus } = loadCompiled('utils/invitation-state')
|
|
|
24
24
|
const { InvitationQuery } = loadCompiled('service/invitation/invitation-query')
|
|
25
25
|
const { Partner } = loadCompiled('service/partner/partner')
|
|
26
26
|
const { PartnerQuery } = loadCompiled('service/partner/partner-query')
|
|
27
|
-
const { LoginHistory } = loadCompiled('service/login-history/login-history')
|
|
28
|
-
const { LoginHistoryQuery } = loadCompiled('service/login-history/login-history-query')
|
|
29
27
|
|
|
30
28
|
let parent: any
|
|
31
29
|
let child: any
|
|
@@ -119,11 +117,6 @@ const LISTS: [string, () => Promise<any>, (params: any, context: any) => Promise
|
|
|
119
117
|
'partners',
|
|
120
118
|
() => getRepository(Partner).save({ domain: parent, partnerDomain, requestedAt: new Date() }),
|
|
121
119
|
(params, context) => new PartnerQuery().partners(params, context)
|
|
122
|
-
],
|
|
123
|
-
[
|
|
124
|
-
'login histories',
|
|
125
|
-
() => LoginHistory.stamp(parent, parentUser, '10.0.0.1'),
|
|
126
|
-
(params, context) => new LoginHistoryQuery().loginHistories(params, context)
|
|
127
120
|
]
|
|
128
121
|
]
|
|
129
122
|
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
const mockSentMail: any[] = []
|
|
2
|
+
let mockMailFails = false
|
|
3
|
+
|
|
4
|
+
jest.mock('@things-factory/email-base', () => ({
|
|
5
|
+
sendEmail: async (mail: any) => {
|
|
6
|
+
if (mockMailFails) throw new Error('smtp down')
|
|
7
|
+
mockSentMail.push(mail)
|
|
8
|
+
}
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
|
|
12
|
+
import { loadCompiled } from './compiled'
|
|
13
|
+
|
|
14
|
+
const { User } = loadCompiled('service/user/user')
|
|
15
|
+
const { authPublicProcessRouter } = loadCompiled('router/auth-public-process-router')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* What the two "mail me" posts answer - forgot-password and resend-verification-email.
|
|
19
|
+
*
|
|
20
|
+
* Both set nothing when there was no account to mail, or when the mail did not go, so koa answered
|
|
21
|
+
* a bare 404 and the page showed "Not Found" - the same words for "no such address" and "the mail
|
|
22
|
+
* server is down". Now they give one answer whether or not the address has an account (another
|
|
23
|
+
* would tell anyone which addresses are registered), and fail only when a mail was due and did not
|
|
24
|
+
* go.
|
|
25
|
+
*
|
|
26
|
+
* The handler is taken from the router's own table, so the path is the one the page posts to.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
function handler(path: string) {
|
|
30
|
+
const layer = authPublicProcessRouter.stack.find((layer: any) => layer.path === path && layer.methods.includes('POST'))
|
|
31
|
+
|
|
32
|
+
return layer.stack[layer.stack.length - 1]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function post(path: string, body: any) {
|
|
36
|
+
const context: any = {
|
|
37
|
+
t: (key: string) => key,
|
|
38
|
+
lng: 'en-US',
|
|
39
|
+
headers: {},
|
|
40
|
+
header: {},
|
|
41
|
+
request: { protocol: 'https', host: 'acme.example.com', body }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await handler(path)(context, async () => {})
|
|
45
|
+
|
|
46
|
+
return { status: context.status, body: context.body }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let domain: any
|
|
50
|
+
|
|
51
|
+
async function person(email: string, status: string) {
|
|
52
|
+
const salt = User.generateSalt()
|
|
53
|
+
|
|
54
|
+
return await getRepository(User).save({
|
|
55
|
+
username: email.split('@')[0],
|
|
56
|
+
email,
|
|
57
|
+
name: 'Kim',
|
|
58
|
+
salt,
|
|
59
|
+
password: User.encode('Passw0rd!', salt),
|
|
60
|
+
status,
|
|
61
|
+
domains: [domain]
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
beforeAll(async () => {
|
|
66
|
+
await openAuthDatabase()
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
beforeEach(async () => {
|
|
70
|
+
await resetAuthDatabase()
|
|
71
|
+
domain = await getRepository(Domain).save({ name: 'plant-a', subdomain: 'plant-a' })
|
|
72
|
+
mockSentMail.length = 0
|
|
73
|
+
mockMailFails = false
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
afterAll(async () => {
|
|
77
|
+
await closeAuthDatabase()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('forgot-password', () => {
|
|
81
|
+
const PATH = '/auth/forgot-password'
|
|
82
|
+
|
|
83
|
+
it('mails a registered address', async () => {
|
|
84
|
+
await person('kim@example.com', 'activated')
|
|
85
|
+
|
|
86
|
+
expect(await post(PATH, { email: 'kim@example.com' })).toEqual({ status: 200, body: 'text.password reset email sent' })
|
|
87
|
+
expect(mockSentMail.map(mail => mail.receiver)).toEqual(['kim@example.com'])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('answers an unknown address the same way, and mails nobody', async () => {
|
|
91
|
+
await person('kim@example.com', 'activated')
|
|
92
|
+
|
|
93
|
+
expect(await post(PATH, { email: 'nobody@example.com' })).toEqual({ status: 200, body: 'text.password reset email sent' })
|
|
94
|
+
expect(mockSentMail).toEqual([])
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('says so when the mail does not go', async () => {
|
|
98
|
+
await person('kim@example.com', 'activated')
|
|
99
|
+
mockMailFails = true
|
|
100
|
+
|
|
101
|
+
expect(await post(PATH, { email: 'kim@example.com' })).toEqual({ status: 503, body: 'error.mail could not be sent' })
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
describe('resend-verification-email', () => {
|
|
106
|
+
const PATH = '/auth/resend-verification-email'
|
|
107
|
+
|
|
108
|
+
it('mails an account waiting for confirmation', async () => {
|
|
109
|
+
await person('kim@example.com', 'inactive')
|
|
110
|
+
|
|
111
|
+
expect(await post(PATH, { email: 'kim@example.com' })).toEqual({ status: 200, body: 'text.verification email sent' })
|
|
112
|
+
expect(mockSentMail.map(mail => mail.receiver)).toEqual(['kim@example.com'])
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it.each([
|
|
116
|
+
['an unknown address', 'nobody@example.com'],
|
|
117
|
+
['an account already confirmed', 'lee@example.com']
|
|
118
|
+
])('answers %s the same way, and mails nobody', async (_, email) => {
|
|
119
|
+
await person('lee@example.com', 'activated')
|
|
120
|
+
|
|
121
|
+
expect(await post(PATH, { email })).toEqual({ status: 200, body: 'text.verification email sent' })
|
|
122
|
+
expect(mockSentMail).toEqual([])
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('mails nobody for a post without an address', async () => {
|
|
126
|
+
/* ⚠ TypeORM drops an undefined condition - this matched the first account and mailed it. */
|
|
127
|
+
await person('kim@example.com', 'inactive')
|
|
128
|
+
|
|
129
|
+
expect(await post(PATH, {})).toEqual({ status: 200, body: 'text.verification email sent' })
|
|
130
|
+
expect(mockSentMail).toEqual([])
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('says so when the mail does not go', async () => {
|
|
134
|
+
await person('kim@example.com', 'inactive')
|
|
135
|
+
mockMailFails = true
|
|
136
|
+
|
|
137
|
+
expect(await post(PATH, { email: 'kim@example.com' })).toEqual({ status: 503, body: 'error.mail could not be sent' })
|
|
138
|
+
})
|
|
139
|
+
})
|
package/tests/open-doors.test.ts
CHANGED
|
@@ -80,7 +80,7 @@ describe('the doors that were closed', () => {
|
|
|
80
80
|
* signs in, so adding one is not an edit to a settings row
|
|
81
81
|
* domainUserRoleRegister makes a domain, its roles and its users. `domainRegister`
|
|
82
82
|
* beside it makes only a domain and is super-user only
|
|
83
|
-
* loginHistories everybody's sign-ins
|
|
83
|
+
* loginHistories everybody's sign-ins - now the audit's, behind `audit:query` (ADR-0141)
|
|
84
84
|
* invitation / invitations and neither of them scopes by tenant at all
|
|
85
85
|
* sendInvitation / cancelInvitation / inviteUser
|
|
86
86
|
*/
|
|
@@ -96,7 +96,7 @@ describe('the doors that were closed', () => {
|
|
|
96
96
|
'cancelInvitation',
|
|
97
97
|
'inviteUser'
|
|
98
98
|
],
|
|
99
|
-
Query: ['
|
|
99
|
+
Query: ['auditRecords', 'auditPeople', 'auditDoors', 'auditHealth', 'invitation', 'invitations']
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
it.each(Object.entries(CLOSED).flatMap(([t, fields]) => fields.map(f => [t, f])))('%s.%s', (typeName, field) => {
|
|
@@ -64,7 +64,7 @@ describe('a refusal says which of the four it was', () => {
|
|
|
64
64
|
it('★ an owner without the role, from an unsafe address: address - from a safe one they pass', async () => {
|
|
65
65
|
given({ owner: true })
|
|
66
66
|
expect(await judgePermission(OWNED, USER, DOMAIN, true)).toEqual({ permitted: false, because: 'address' })
|
|
67
|
-
expect(await judgePermission(OWNED, USER, DOMAIN, false)).toEqual({ permitted: true })
|
|
67
|
+
expect(await judgePermission(OWNED, USER, DOMAIN, false)).toEqual({ permitted: true, by: 'domain-owner' })
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
it('an unsafe address whose ownership cannot be asked still refuses as role - it refused before', async () => {
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role tasks — the registry, what it refuses, and how a role is laid against tasks (ADR-0139 decision 11).
|
|
3
|
+
*
|
|
4
|
+
* The comparison is pure: a role's held privileges, the registered tasks, the declared privileges. The role screen
|
|
5
|
+
* draws exactly this, so what "all", "some", "none" and "broken" mean is pinned here.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { loadCompiled } from './compiled'
|
|
9
|
+
|
|
10
|
+
const { registerRoleTask, roleTasks, roleTaskById, mergeGrants, taskCoverageOf, undeclaredTaskGrants } = loadCompiled('service/role-template/role-task')
|
|
11
|
+
const { registerRoleTemplate, templateGrantsOf, templateDefinitionErrors, unknownTemplateGrants } = loadCompiled('service/role-template/role-template')
|
|
12
|
+
|
|
13
|
+
const DECLARED = {
|
|
14
|
+
'board query': ['board', 'query'],
|
|
15
|
+
'board mutation': ['board', 'mutation'],
|
|
16
|
+
'order query': ['order', 'query'],
|
|
17
|
+
'worklist query': ['worklist', 'query'],
|
|
18
|
+
'worklist mutation': ['worklist', 'mutation']
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
;(process as any)['ROLE_TASKS'] = {}
|
|
23
|
+
;(process as any)['ROLE_TEMPLATES'] = {}
|
|
24
|
+
;(process as any).PRIVILEGES = { ...DECLARED }
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const held = (...pairs: string[]) => new Set(pairs.map(pair => pair.replace(':', ' ')))
|
|
28
|
+
|
|
29
|
+
describe('declaring a task', () => {
|
|
30
|
+
it('can be found again by id', () => {
|
|
31
|
+
registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
|
|
32
|
+
|
|
33
|
+
expect(roleTaskById('board.reading')).toEqual({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
|
|
34
|
+
expect(roleTasks()).toHaveLength(1)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it.each([['reading'], [''], ['.reading']])('refuses the id "%s" - the declaring module comes first', id => {
|
|
38
|
+
expect(() => registerRoleTask({ id, grants: [{ category: 'board', axes: ['query'] }] })).toThrow(/<module>.<work>/)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('refuses a task that holds no privileges', () => {
|
|
42
|
+
expect(() => registerRoleTask({ id: 'board.nothing', grants: [] })).toThrow(/holds no privileges/)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('refuses a grant with no axes - axes are listed one by one', () => {
|
|
46
|
+
expect(() => registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: [] }] })).toThrow(/one by one/)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('refuses the same task declared twice - one piece of work is one task', () => {
|
|
50
|
+
registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
|
|
51
|
+
|
|
52
|
+
expect(() => registerRoleTask({ id: 'board.reading', grants: [{ category: 'order', axes: ['query'] }] })).toThrow(/declared twice/)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('holds a privilege written twice once', () => {
|
|
56
|
+
registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
|
|
57
|
+
|
|
58
|
+
expect(roleTaskById('board.reading').grants).toEqual([{ category: 'board', axes: ['query'] }])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('★ lets two tasks share a privilege - that is not a duplicate', () => {
|
|
62
|
+
registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
|
|
63
|
+
registerRoleTask({ id: 'order.reading', grants: [{ category: 'order', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
|
|
64
|
+
|
|
65
|
+
expect(roleTasks()).toHaveLength(2)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('keeps a change-only grant as written - nothing adds query to it', () => {
|
|
69
|
+
registerRoleTask({ id: 'worklist.putting-up', grants: [{ category: 'worklist', axes: ['mutation'] }] })
|
|
70
|
+
|
|
71
|
+
expect(roleTaskById('worklist.putting-up').grants).toEqual([{ category: 'worklist', axes: ['mutation'] }])
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('a template built from tasks', () => {
|
|
76
|
+
beforeEach(() => {
|
|
77
|
+
registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
|
|
78
|
+
registerRoleTask({ id: 'order.reading', grants: [{ category: 'order', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('★ holds the union of its tasks and its own grants, each privilege once', () => {
|
|
82
|
+
registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'order.reading'], grants: [{ category: 'board', axes: ['mutation', 'query'] }] })
|
|
83
|
+
|
|
84
|
+
expect(templateGrantsOf({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'order.reading'], grants: [{ category: 'board', axes: ['mutation', 'query'] }] })).toEqual([
|
|
85
|
+
{ category: 'board', axes: ['query', 'mutation'] },
|
|
86
|
+
{ category: 'order', axes: ['query'] }
|
|
87
|
+
])
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('may be written with tasks and no grants', () => {
|
|
91
|
+
expect(() => registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading'] })).not.toThrow()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('still refuses a template with neither - leaving both out is forgetting', () => {
|
|
95
|
+
expect(() => registerRoleTemplate({ id: 'plant.reader', name: 'READER' } as any)).toThrow(/has no grants/)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('★ names an unregistered task as a definition error', () => {
|
|
99
|
+
expect(templateDefinitionErrors({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'board.writting'] })).toEqual(['task "board.writting" is not registered'])
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('★ names a task privilege this installation does not declare as a definition error', () => {
|
|
103
|
+
registerRoleTask({ id: 'board.exporting', grants: [{ category: 'board-export', axes: ['query'] }] })
|
|
104
|
+
|
|
105
|
+
expect(undeclaredTaskGrants(roleTaskById('board.exporting'))).toEqual(['board-export:query'])
|
|
106
|
+
expect(templateDefinitionErrors({ id: 'plant.reader', name: 'READER', tasks: ['board.exporting'] })).toEqual([
|
|
107
|
+
'task "board.exporting" names board-export:query, which this installation does not declare'
|
|
108
|
+
])
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it("keeps a template's own undeclared grants as before - skipped, not a definition error", () => {
|
|
112
|
+
const template = { id: 'plant.reader', name: 'READER', grants: [{ category: 'kpi', axes: ['query'] }] }
|
|
113
|
+
|
|
114
|
+
expect(templateDefinitionErrors(template)).toEqual([])
|
|
115
|
+
expect(unknownTemplateGrants(template)).toEqual(['kpi:query'])
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('a role laid against the tasks', () => {
|
|
120
|
+
beforeEach(() => {
|
|
121
|
+
registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
|
|
122
|
+
registerRoleTask({ id: 'board.keeping', grants: [{ category: 'board', axes: ['query', 'mutation'] }] })
|
|
123
|
+
registerRoleTask({ id: 'order.reading', grants: [{ category: 'order', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const statuses = (coverage: any) => Object.fromEntries(coverage.tasks.map((task: any) => [task.taskId, task.status]))
|
|
127
|
+
|
|
128
|
+
it('★ says all, some and none by what the role holds of each task', () => {
|
|
129
|
+
const coverage = taskCoverageOf(held('board:query'))
|
|
130
|
+
|
|
131
|
+
expect(statuses(coverage)).toEqual({ 'board.reading': 'all', 'board.keeping': 'some', 'order.reading': 'some' })
|
|
132
|
+
expect(coverage.tasks.find((task: any) => task.taskId === 'board.keeping').missing).toEqual([{ category: 'board', axes: ['mutation'] }])
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('★ a shared privilege alone makes every task that names it "some" - reported, not supplied', () => {
|
|
136
|
+
const coverage = taskCoverageOf(held('board:query'))
|
|
137
|
+
|
|
138
|
+
expect(coverage.tasks.find((task: any) => task.taskId === 'order.reading')).toMatchObject({
|
|
139
|
+
status: 'some',
|
|
140
|
+
held: [{ category: 'board', axes: ['query'] }],
|
|
141
|
+
missing: [{ category: 'order', axes: ['query'] }]
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('says none for a role that holds nothing of a task', () => {
|
|
146
|
+
expect(statuses(taskCoverageOf(held('worklist:query')))).toEqual({ 'board.reading': 'none', 'board.keeping': 'none', 'order.reading': 'none' })
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('★ keeps what no task names apart - and leaves out what nothing declares any more', () => {
|
|
150
|
+
const coverage = taskCoverageOf(held('board:query', 'worklist:query', 'worklist:mutation', 'retired:query'))
|
|
151
|
+
|
|
152
|
+
expect(coverage.unclassified).toEqual([{ category: 'worklist', axes: ['mutation', 'query'] }])
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('★ says broken for a task naming an undeclared privilege, whatever the role holds', () => {
|
|
156
|
+
registerRoleTask({ id: 'board.exporting', grants: [{ category: 'board', axes: ['query'] }, { category: 'board-export', axes: ['query'] }] })
|
|
157
|
+
|
|
158
|
+
const task = taskCoverageOf(held('board:query')).tasks.find((one: any) => one.taskId === 'board.exporting')
|
|
159
|
+
expect(task).toMatchObject({ status: 'broken', module: 'board', undeclared: [{ category: 'board-export', axes: ['query'] }] })
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
it('merges grants by category in the order first written', () => {
|
|
163
|
+
expect(mergeGrants([[{ category: 'b', axes: ['x'] }], [{ category: 'a', axes: ['y'] }, { category: 'b', axes: ['z', 'x'] }]])).toEqual([
|
|
164
|
+
{ category: 'b', axes: ['x', 'z'] },
|
|
165
|
+
{ category: 'a', axes: ['y'] }
|
|
166
|
+
])
|
|
167
|
+
})
|
|
168
|
+
})
|
|
@@ -16,9 +16,11 @@ const { User } = loadCompiled('service/user/user')
|
|
|
16
16
|
const { RoleTemplateMutation } = loadCompiled('service/role-template/role-template-mutation')
|
|
17
17
|
const { RoleTemplateGapResolver } = loadCompiled('service/role-template/role-template-gap-resolver')
|
|
18
18
|
const { registerRoleTemplate } = loadCompiled('service/role-template/role-template')
|
|
19
|
+
const { registerRoleTask } = loadCompiled('service/role-template/role-task')
|
|
19
20
|
|
|
20
21
|
const mutation = new RoleTemplateMutation()
|
|
21
|
-
const gapOf = (role: any) => new RoleTemplateGapResolver().templateGap(role)
|
|
22
|
+
const gapOf = (role: any, templateId?: string) => new RoleTemplateGapResolver().templateGap(role, templateId)
|
|
23
|
+
const coverageOf = (role: any) => new RoleTemplateGapResolver().taskCoverage(role)
|
|
22
24
|
|
|
23
25
|
let domain: any
|
|
24
26
|
let actor: any
|
|
@@ -71,7 +73,9 @@ describe('the gap between a role and its template', () => {
|
|
|
71
73
|
templateId: 'plant.qi',
|
|
72
74
|
templateName: 'QUALITY-INSPECTOR',
|
|
73
75
|
missing: [],
|
|
74
|
-
missingCount: 0
|
|
76
|
+
missingCount: 0,
|
|
77
|
+
extra: [],
|
|
78
|
+
extraCount: 0
|
|
75
79
|
})
|
|
76
80
|
})
|
|
77
81
|
|
|
@@ -120,6 +124,77 @@ describe('the gap between a role and its template', () => {
|
|
|
120
124
|
]
|
|
121
125
|
await getRepository(Role).save(stored)
|
|
122
126
|
|
|
127
|
+
const gap = await gapOf(role)
|
|
128
|
+
expect(gap.missingCount).toBe(0)
|
|
129
|
+
/* Shown as wider than the template - not a fault, and nothing takes it away. */
|
|
130
|
+
expect(gap.extra).toEqual([{ category: 'board', axes: ['mutation'] }])
|
|
131
|
+
expect(gap.extraCount).toBe(1)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('★ lays a role against a template the administrator picks, whatever the role is called', async () => {
|
|
135
|
+
declareTemplate(['query', 'mutation'])
|
|
136
|
+
const role = await getRepository(Role).save({ domain, name: '품질검사', privileges: [await getRepository(Privilege).findOneBy({ category: 'board', name: 'query' })] })
|
|
137
|
+
|
|
138
|
+
expect(await gapOf(role)).toBeNull()
|
|
139
|
+
expect(await gapOf(role, 'plant.qi')).toMatchObject({ templateId: 'plant.qi', missing: [{ category: 'board', axes: ['mutation'] }], extraCount: 0 })
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('refuses a template id that does not exist rather than answering null', async () => {
|
|
143
|
+
declareTemplate(['query'])
|
|
144
|
+
const role = await mutation.createRoleFromTemplate('plant.qi', undefined, context())
|
|
145
|
+
|
|
146
|
+
await expect(gapOf(role, 'plant.nobody')).rejects.toThrow(/no role template "plant.nobody"/)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
describe('a template built from tasks (ADR-0139 decision 11)', () => {
|
|
151
|
+
beforeEach(async () => {
|
|
152
|
+
;(process as any)['ROLE_TASKS'] = {}
|
|
153
|
+
;(process as any)['ROLE_TEMPLATES'] = {}
|
|
154
|
+
await getRepository(Privilege).save({ name: 'query', category: 'order' })
|
|
155
|
+
;(process as any).PRIVILEGES['order query'] = ['order', 'query']
|
|
156
|
+
registerRoleTask({ id: 'board.reading', grants: [{ category: 'board', axes: ['query'] }] })
|
|
157
|
+
registerRoleTask({ id: 'order.reading', grants: [{ category: 'order', axes: ['query'] }, { category: 'board', axes: ['query'] }] })
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('★ holds every privilege of every task once, plus its own grants', async () => {
|
|
161
|
+
registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'order.reading'], grants: [{ category: 'board', axes: ['mutation'] }] })
|
|
162
|
+
const role = await mutation.createRoleFromTemplate('plant.reader', undefined, context())
|
|
163
|
+
|
|
164
|
+
const held = (await getRepository(Role).findOne({ where: { id: role.id }, relations: ['privileges'] })).privileges.map((p: any) => `${p.category}:${p.name}`).sort()
|
|
165
|
+
expect(held).toEqual(['board:mutation', 'board:query', 'order:query'])
|
|
123
166
|
expect((await gapOf(role)).missingCount).toBe(0)
|
|
124
167
|
})
|
|
168
|
+
|
|
169
|
+
it('★ is refused whole when a task is not registered - no role is made, no privilege added', async () => {
|
|
170
|
+
registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'board.writting'] })
|
|
171
|
+
|
|
172
|
+
await expect(mutation.createRoleFromTemplate('plant.reader', undefined, context())).rejects.toThrow(/task "board.writting" is not registered/)
|
|
173
|
+
expect(await getRepository(Role).findOneBy({ name: 'READER' })).toBeNull()
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('★ is refused whole when a task names a privilege this installation does not declare', async () => {
|
|
177
|
+
registerRoleTask({ id: 'board.exporting', grants: [{ category: 'board-export', axes: ['query'] }] })
|
|
178
|
+
registerRoleTemplate({ id: 'plant.reader', name: 'READER', tasks: ['board.reading', 'board.exporting'] })
|
|
179
|
+
const standing = await getRepository(Role).save({ domain, name: 'READER', privileges: [] })
|
|
180
|
+
|
|
181
|
+
await expect(mutation.createRoleFromTemplate('plant.reader', undefined, context())).rejects.toThrow(/board-export:query/)
|
|
182
|
+
expect((await getRepository(Role).findOne({ where: { id: standing.id }, relations: ['privileges'] })).privileges).toEqual([])
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('★ the role screen sees a role by task: all, some, none, and what no task names', async () => {
|
|
186
|
+
const role = await getRepository(Role).save({
|
|
187
|
+
domain,
|
|
188
|
+
name: '판독',
|
|
189
|
+
privileges: [await getRepository(Privilege).findOneBy({ category: 'board', name: 'query' }), await getRepository(Privilege).findOneBy({ category: 'board', name: 'mutation' })]
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
const coverage = await coverageOf(role)
|
|
193
|
+
expect(coverage.tasks.map((task: any) => [task.taskId, task.status])).toEqual([
|
|
194
|
+
['board.reading', 'all'],
|
|
195
|
+
['order.reading', 'some']
|
|
196
|
+
])
|
|
197
|
+
expect(coverage.tasks[1].missing).toEqual([{ category: 'order', axes: ['query'] }])
|
|
198
|
+
expect(coverage.unclassified).toEqual([{ category: 'board', axes: ['mutation'] }])
|
|
199
|
+
})
|
|
125
200
|
})
|
package/translations/en.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"error.email already exists": "email already used by another user",
|
|
8
8
|
"error.failed to find x": "failed to find {x}",
|
|
9
9
|
"error.invalid username": "invalid username: {username}",
|
|
10
|
+
"error.mail could not be sent": "the email could not be sent. try again in a moment.",
|
|
10
11
|
"error.ownership required": "unauthorized! domain or system ownership required",
|
|
11
12
|
"error.password should be supported": "initial password or default password should be supported",
|
|
12
13
|
"error.password should match the rule": "password should match following rule. ${rule}",
|
|
@@ -211,7 +212,7 @@
|
|
|
211
212
|
"text.invalid verification token": "invalid verification token",
|
|
212
213
|
"text.invitation email sent": "invitation email sent",
|
|
213
214
|
"text.password changed successfully": "password changed successfully",
|
|
214
|
-
"text.password reset email sent": "password reset email
|
|
215
|
+
"text.password reset email sent": "if an account uses this address, a password reset email is on its way.",
|
|
215
216
|
"text.password reset succeed": "password reset succeed",
|
|
216
217
|
"text.pattern_atleast_1_digit": "at least 1 digit character",
|
|
217
218
|
"text.pattern_atleast_1_lowercase": "at least 1 lowercase character",
|
|
@@ -226,7 +227,7 @@
|
|
|
226
227
|
"text.user credential registered successfully": "device registration has been successfully completed. You can now use biometric authentication.",
|
|
227
228
|
"text.user inactivated successfully": "user inactivated successfully",
|
|
228
229
|
"text.user registered successfully": "user registered successfully. find your email to activate account",
|
|
229
|
-
"text.verification email sent": "verification email
|
|
230
|
+
"text.verification email sent": "if an account at this address is waiting for confirmation, a verification email is on its way.",
|
|
230
231
|
"privilege.description": "{name} {category}",
|
|
231
232
|
"error.access-request-names-nothing": "an access request names a category and a privilege.",
|
|
232
233
|
"error.access-request-unknown-privilege": "there is no privilege {privilege}.",
|
|
@@ -250,5 +251,17 @@
|
|
|
250
251
|
"access-request.notify.denied.body": "{privilege}: {reason}",
|
|
251
252
|
"error.access-request-needs-reason": "write why you need it.",
|
|
252
253
|
"error.access-request-same-reason-as-denied": "this was denied lately for the same reason ({reason}). write what has changed.",
|
|
253
|
-
"error.role-belongs-to-parent": "{roles}: a role of the parent domain. only someone who decides roles there can give or take it."
|
|
254
|
+
"error.role-belongs-to-parent": "{roles}: a role of the parent domain. only someone who decides roles there can give or take it.",
|
|
255
|
+
"task.auth-base.users-and-roles.name": "Keeping accounts and roles",
|
|
256
|
+
"task.auth-base.users-and-roles.description": "Reads and changes users, roles and who holds them, invitations, access requests, applications, devices and sign-in providers. Whoever holds this can give any role, to themselves as well.",
|
|
257
|
+
"task.auth-base.privilege-reading.name": "Reading the privilege list",
|
|
258
|
+
"task.auth-base.privilege-reading.description": "Reads the privileges roles are made of.",
|
|
259
|
+
"task.auth-base.security.name": "Keeping security settings",
|
|
260
|
+
"task.auth-base.security.description": "Reads and changes the domain's protected addresses and sign-in rules.",
|
|
261
|
+
"task.auth-base.env-vars.name": "Keeping environment variables",
|
|
262
|
+
"task.auth-base.env-vars.description": "Reads and changes the domain's environment variables.",
|
|
263
|
+
"privilege.category.audit": "audit log",
|
|
264
|
+
"privilege.allows.audit.query": "see everyone’s sign-ins, privilege use and refusals, and role changes",
|
|
265
|
+
"task.auth-base.audit-reading.name": "Reading the audit log",
|
|
266
|
+
"task.auth-base.audit-reading.description": "Reads everyone's sign-ins, privilege use and refusals, and role changes in the domain."
|
|
254
267
|
}
|
package/translations/es.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"error.email already exists": "el correo electrónico ya lo usa otro usuario",
|
|
8
8
|
"error.failed to find x": "no se encontró {x}",
|
|
9
9
|
"error.invalid username": "nombre de usuario no válido: {username}",
|
|
10
|
+
"error.mail could not be sent": "no se pudo enviar el correo. vuelva a intentarlo en un momento.",
|
|
10
11
|
"error.ownership required": "¡no autorizado! se requiere la propiedad del dominio o del sistema",
|
|
11
12
|
"error.password should be supported": "se debe proporcionar una contraseña inicial o una contraseña predeterminada",
|
|
12
13
|
"error.password should match the rule": "la contraseña debe cumplir la siguiente regla. ${rule}",
|
|
@@ -211,7 +212,7 @@
|
|
|
211
212
|
"text.invalid verification token": "token de verificación no válido",
|
|
212
213
|
"text.invitation email sent": "correo de invitación enviado",
|
|
213
214
|
"text.password changed successfully": "contraseña cambiada correctamente",
|
|
214
|
-
"text.password reset email sent": "correo de restablecimiento de contraseña
|
|
215
|
+
"text.password reset email sent": "si hay una cuenta con esta dirección, le enviamos un correo de restablecimiento de contraseña.",
|
|
215
216
|
"text.password reset succeed": "contraseña restablecida correctamente",
|
|
216
217
|
"text.pattern_atleast_1_digit": "al menos 1 dígito",
|
|
217
218
|
"text.pattern_atleast_1_lowercase": "al menos 1 letra minúscula",
|
|
@@ -226,7 +227,7 @@
|
|
|
226
227
|
"text.user credential registered successfully": "el registro del dispositivo se ha completado correctamente. Ya puede usar la autenticación biométrica.",
|
|
227
228
|
"text.user inactivated successfully": "usuario desactivado correctamente",
|
|
228
229
|
"text.user registered successfully": "usuario registrado correctamente. busque el correo electrónico para activar la cuenta",
|
|
229
|
-
"text.verification email sent": "correo de verificación
|
|
230
|
+
"text.verification email sent": "si una cuenta con esta dirección espera confirmación, le enviamos un correo de verificación.",
|
|
230
231
|
"privilege.description": "{name} de {category}",
|
|
231
232
|
"error.access-request-names-nothing": "una solicitud de acceso indica una categoría y un privilegio.",
|
|
232
233
|
"error.access-request-unknown-privilege": "no existe el privilegio {privilege}.",
|
|
@@ -250,5 +251,17 @@
|
|
|
250
251
|
"access-request.notify.denied.body": "{privilege}: {reason}",
|
|
251
252
|
"error.access-request-needs-reason": "indique por qué lo necesita.",
|
|
252
253
|
"error.access-request-same-reason-as-denied": "se rechazó hace poco por el mismo motivo ({reason}). indique qué ha cambiado.",
|
|
253
|
-
"error.role-belongs-to-parent": "{roles}: un rol del dominio principal. solo quien decide los roles allí puede concederlo o quitarlo."
|
|
254
|
+
"error.role-belongs-to-parent": "{roles}: un rol del dominio principal. solo quien decide los roles allí puede concederlo o quitarlo.",
|
|
255
|
+
"task.auth-base.users-and-roles.name": "Mantener cuentas y roles",
|
|
256
|
+
"task.auth-base.users-and-roles.description": "Lee y modifica usuarios, roles y quién los tiene, invitaciones, solicitudes de acceso, aplicaciones, dispositivos y proveedores de acceso. Quien lo tiene puede dar cualquier rol, también a sí mismo.",
|
|
257
|
+
"task.auth-base.privilege-reading.name": "Leer la lista de privilegios",
|
|
258
|
+
"task.auth-base.privilege-reading.description": "Lee los privilegios de los que se componen los roles.",
|
|
259
|
+
"task.auth-base.security.name": "Mantener la seguridad",
|
|
260
|
+
"task.auth-base.security.description": "Lee y modifica las direcciones protegidas y las reglas de acceso del dominio.",
|
|
261
|
+
"task.auth-base.env-vars.name": "Mantener variables de entorno",
|
|
262
|
+
"task.auth-base.env-vars.description": "Lee y modifica las variables de entorno del dominio.",
|
|
263
|
+
"privilege.category.audit": "registro de auditoría",
|
|
264
|
+
"privilege.allows.audit.query": "ver los inicios de sesión, el uso y rechazo de privilegios y los cambios de rol de todos",
|
|
265
|
+
"task.auth-base.audit-reading.name": "Consultar el registro de auditoría",
|
|
266
|
+
"task.auth-base.audit-reading.description": "Lee los inicios de sesión, el uso y rechazo de privilegios y los cambios de rol de todos en el dominio."
|
|
254
267
|
}
|
package/translations/ja.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"error.email already exists": "メールはすでに他のユーザーによって使用されています.",
|
|
8
8
|
"error.failed to find x": "{x}が見つかりません.",
|
|
9
9
|
"error.invalid username": "無効なユーザー名: {username}",
|
|
10
|
+
"error.mail could not be sent": "メールを送れませんでした. しばらくしてからもう一度お試しください.",
|
|
10
11
|
"error.ownership required": "権限がありません。$t(term.domain)またはシステムの所有者権限が必要です。",
|
|
11
12
|
"error.password should be supported": "初期パスワードまたはデフォルトパスワードがサポートされるべきです",
|
|
12
13
|
"error.password should match the rule": "パスワードは次の規則を守らなければなりません. {rule}",
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
"text.invalid verification token": "確認トークンが無効です.",
|
|
52
53
|
"text.invitation email sent": "招待メールを送りました.",
|
|
53
54
|
"text.password changed successfully": "パスワードが正常に変更されました.",
|
|
54
|
-
"text.password reset email sent": "
|
|
55
|
+
"text.password reset email sent": "登録済みのアドレスであれば、パスワード リセットメールを送りました.",
|
|
55
56
|
"text.password reset succeed": "パスワードが正常にリセットされました.",
|
|
56
57
|
"text.pattern_atleast_1_digit": "少なくとも1つの数字",
|
|
57
58
|
"text.pattern_atleast_1_lowercase": "少なくとも1つの小文字",
|
|
@@ -66,7 +67,7 @@
|
|
|
66
67
|
"text.user credential registered successfully": "デバイスの登録が正常に完了しました。今後は生体認証を使用できます。",
|
|
67
68
|
"text.user inactivated successfully": "ユーザーが正常に非アクティブ化されました.",
|
|
68
69
|
"text.user registered successfully": "ユーザーが正常に登録されました. 確認メールでアカウントを有効にしてください.",
|
|
69
|
-
"text.verification email sent": "
|
|
70
|
+
"text.verification email sent": "確認待ちのアカウントのアドレスであれば、確認メールを送りました.",
|
|
70
71
|
"privilege.allows.accounting.mutation": "伝票を起票・修正・締めできます。",
|
|
71
72
|
"privilege.allows.accounting.query": "伝票と勘定残高を閲覧できます。",
|
|
72
73
|
"privilege.allows.activity-instance.mutation": "業務を開始・引き継ぎ・完了・中断できます。",
|
|
@@ -250,5 +251,17 @@
|
|
|
250
251
|
"access-request.notify.denied.body": "{privilege}: {reason}",
|
|
251
252
|
"error.access-request-needs-reason": "必要な理由を書いてください。",
|
|
252
253
|
"error.access-request-same-reason-as-denied": "同じ理由で最近拒否されました({reason})。何が変わったかを書いてください。",
|
|
253
|
-
"error.role-belongs-to-parent": "{roles}: 上位$t(term.domain)のロールです。その$t(term.domain)で権限を決める人だけが付与・解除できます。"
|
|
254
|
+
"error.role-belongs-to-parent": "{roles}: 上位$t(term.domain)のロールです。その$t(term.domain)で権限を決める人だけが付与・解除できます。",
|
|
255
|
+
"task.auth-base.users-and-roles.name": "アカウント・ロールの管理",
|
|
256
|
+
"task.auth-base.users-and-roles.description": "ユーザー・ロールとその付与・招待・アクセス申請・アプリ・機器・ログイン提供者を読み、変更する。自分にもどのロールでも付与できる。",
|
|
257
|
+
"task.auth-base.privilege-reading.name": "権限一覧の閲覧",
|
|
258
|
+
"task.auth-base.privilege-reading.description": "ロールを構成する権限の一覧を読む。",
|
|
259
|
+
"task.auth-base.security.name": "セキュリティ設定",
|
|
260
|
+
"task.auth-base.security.description": "ドメインの保護アドレスとログイン規則を読み、変更する。",
|
|
261
|
+
"task.auth-base.env-vars.name": "環境変数",
|
|
262
|
+
"task.auth-base.env-vars.description": "ドメインの環境変数を読み、変更する。",
|
|
263
|
+
"privilege.category.audit": "監査ログ",
|
|
264
|
+
"privilege.allows.audit.query": "全ユーザーのログイン、権限の使用と拒否、ロール変更の記録を見ることができます。",
|
|
265
|
+
"task.auth-base.audit-reading.name": "監査ログの閲覧",
|
|
266
|
+
"task.auth-base.audit-reading.description": "全ユーザーのログイン、権限の使用と拒否、ロール変更の記録を読む。"
|
|
254
267
|
}
|
package/translations/ko.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"error.email already exists": "이메일이 이미 사용되고 있습니다.",
|
|
8
8
|
"error.failed to find x": "{x}을(를) 찾을 수 없습니다.",
|
|
9
9
|
"error.invalid username": "유효하지 않은 사용자 이름: {username}",
|
|
10
|
+
"error.mail could not be sent": "메일을 보내지 못했습니다. 잠시 후 다시 시도하십시오.",
|
|
10
11
|
"error.ownership required": "권한이 없습니다. $t(term.domain) 또는 시스템 소유자 권한이 필요합니다.",
|
|
11
12
|
"error.password should be supported": "초기 비밀번호나 디폴트 비밀번호가 제공되어야 합니다.",
|
|
12
13
|
"error.password should match the rule": "비밀번호는 다음 규칙을 지켜야 합니다. {rule}",
|
|
@@ -211,7 +212,7 @@
|
|
|
211
212
|
"text.invalid verification token": "확인 토큰이 유효하지 않습니다.",
|
|
212
213
|
"text.invitation email sent": "초대 이메일을 보냈습니다.",
|
|
213
214
|
"text.password changed successfully": "비밀번호가 성공적으로 변경되었습니다.",
|
|
214
|
-
"text.password reset email sent": "비밀번호 재설정
|
|
215
|
+
"text.password reset email sent": "가입된 주소라면 비밀번호 재설정 메일을 보냈습니다.",
|
|
215
216
|
"text.password reset succeed": "비밀번호 성공적으로 재설정되었습니다.",
|
|
216
217
|
"text.pattern_atleast_1_digit": "최소 1개 숫자",
|
|
217
218
|
"text.pattern_atleast_1_lowercase": "최소 1개 소문자",
|
|
@@ -226,7 +227,7 @@
|
|
|
226
227
|
"text.user credential registered successfully": "기기 등록이 성공적으로 완료되었습니다. 이제 바이오메트릭 인증을 사용할 수 있습니다.",
|
|
227
228
|
"text.user inactivated successfully": "사용자가 성공적으로 비활성화되었습니다.",
|
|
228
229
|
"text.user registered successfully": "사용자가 성공적으로 등록되었습니다. 확인 이메일을 통해서 계정을 활성화하시기 바랍니다.",
|
|
229
|
-
"text.verification email sent": "확인
|
|
230
|
+
"text.verification email sent": "확인을 기다리는 계정의 주소라면 확인 메일을 보냈습니다.",
|
|
230
231
|
"privilege.description": "{category} {name}",
|
|
231
232
|
"error.access-request-names-nothing": "권한 요청에는 범주와 권한이 있어야 합니다.",
|
|
232
233
|
"error.access-request-unknown-privilege": "{privilege} 권한이 없습니다.",
|
|
@@ -250,5 +251,17 @@
|
|
|
250
251
|
"access-request.notify.denied.body": "{privilege}: {reason}",
|
|
251
252
|
"error.access-request-needs-reason": "필요한 사유를 입력해 주세요.",
|
|
252
253
|
"error.access-request-same-reason-as-denied": "동일한 사유로 최근에 거부된 요청입니다({reason}). 다시 요청하려면 변경된 사항을 입력해 주세요.",
|
|
253
|
-
"error.role-belongs-to-parent": "{roles}: 상위 $t(term.domain)의 역할입니다. 그 $t(term.domain)에서 권한을 정하는 사람만 주거나 뺄 수 있습니다."
|
|
254
|
+
"error.role-belongs-to-parent": "{roles}: 상위 $t(term.domain)의 역할입니다. 그 $t(term.domain)에서 권한을 정하는 사람만 주거나 뺄 수 있습니다.",
|
|
255
|
+
"task.auth-base.users-and-roles.name": "계정·역할 관리",
|
|
256
|
+
"task.auth-base.users-and-roles.description": "사용자·역할과 역할 부여·초대·접근 요청·앱·기기·로그인 제공자를 읽고 바꾼다. 자기에게도 어떤 역할이든 줄 수 있다.",
|
|
257
|
+
"task.auth-base.privilege-reading.name": "권한 목록 열람",
|
|
258
|
+
"task.auth-base.privilege-reading.description": "역할을 이루는 권한 목록을 읽는다.",
|
|
259
|
+
"task.auth-base.security.name": "보안 설정",
|
|
260
|
+
"task.auth-base.security.description": "도메인의 보호 주소와 로그인 규칙을 읽고 바꾼다.",
|
|
261
|
+
"task.auth-base.env-vars.name": "환경 변수",
|
|
262
|
+
"task.auth-base.env-vars.description": "도메인의 환경 변수를 읽고 바꾼다.",
|
|
263
|
+
"privilege.category.audit": "감사 기록",
|
|
264
|
+
"privilege.allows.audit.query": "모든 사용자의 로그인, 권한 사용과 거절, 역할 변경 기록을 볼 수 있습니다.",
|
|
265
|
+
"task.auth-base.audit-reading.name": "감사 기록 열람",
|
|
266
|
+
"task.auth-base.audit-reading.description": "모든 사용자의 로그인, 권한 사용과 거절, 역할 변경 기록을 읽는다."
|
|
254
267
|
}
|