@things-factory/auth-base 10.1.213 → 10.1.215
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-server/service/user/user-mutation.js +6 -0
- package/dist-server/service/user/user-mutation.js.map +1 -1
- package/dist-server/service/user/user-query.d.ts +2 -0
- package/dist-server/service/user/user-query.js +21 -0
- package/dist-server/service/user/user-query.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/dist-server/utils/password-reset-rule.d.ts +15 -0
- package/dist-server/utils/password-reset-rule.js +34 -0
- package/dist-server/utils/password-reset-rule.js.map +1 -0
- package/package.json +2 -2
- package/tests/password-reset-rule-db.test.ts +158 -0
- package/translations/en.json +2 -0
- package/translations/es.json +2 -0
- package/translations/ja.json +2 -0
- package/translations/ko.json +2 -0
- package/translations/ms.json +2 -0
- package/translations/zh.json +2 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Domain, Refusal } from '@things-factory/shell';
|
|
2
|
+
import { EntityManager } from 'typeorm';
|
|
3
|
+
import { User } from '../service/user/user.js';
|
|
4
|
+
/**
|
|
5
|
+
* Whom a domain may put back on the default password.
|
|
6
|
+
*
|
|
7
|
+
* A person has one password for every domain they work in, and the default password is known to everyone who
|
|
8
|
+
* was ever handed it. Resetting one is therefore the same as taking the account: the first one to sign in with
|
|
9
|
+
* the default is sent to set a new password. A domain may do that only to an account that is wholly its own -
|
|
10
|
+
* one that works in no other domain and owns no domain. The system domain's owner (the super user) and any
|
|
11
|
+
* domain's owner are refused here, whichever domain asks; so are `admin` accounts, as `inactivateUser` refuses them.
|
|
12
|
+
*
|
|
13
|
+
* @returns the refusal, or `null` when `target` may be reset from `domain`. `target` must carry its `domains`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function passwordResetRefusal(target: User, domain: Domain, tx?: EntityManager): Promise<Refusal | null>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.passwordResetRefusal = passwordResetRefusal;
|
|
4
|
+
const shell_1 = require("@things-factory/shell");
|
|
5
|
+
const domain_owner_js_1 = require("../service/domain-owner/domain-owner.js");
|
|
6
|
+
/**
|
|
7
|
+
* Whom a domain may put back on the default password.
|
|
8
|
+
*
|
|
9
|
+
* A person has one password for every domain they work in, and the default password is known to everyone who
|
|
10
|
+
* was ever handed it. Resetting one is therefore the same as taking the account: the first one to sign in with
|
|
11
|
+
* the default is sent to set a new password. A domain may do that only to an account that is wholly its own -
|
|
12
|
+
* one that works in no other domain and owns no domain. The system domain's owner (the super user) and any
|
|
13
|
+
* domain's owner are refused here, whichever domain asks; so are `admin` accounts, as `inactivateUser` refuses them.
|
|
14
|
+
*
|
|
15
|
+
* @returns the refusal, or `null` when `target` may be reset from `domain`. `target` must carry its `domains`.
|
|
16
|
+
*/
|
|
17
|
+
async function passwordResetRefusal(target, domain, tx) {
|
|
18
|
+
if (target.userType == 'admin' || (await ownsAnyDomain(target, tx))) {
|
|
19
|
+
return new shell_1.Refusal('password-reset-owner-account', {}, "A domain owner's or administrator's password is not reset to the default.");
|
|
20
|
+
}
|
|
21
|
+
const elsewhere = (target.domains || []).filter((userDomain) => userDomain.id !== domain.id);
|
|
22
|
+
if (elsewhere.length > 0) {
|
|
23
|
+
return new shell_1.Refusal('password-reset-reaches-other-domains', { domains: elsewhere.map((userDomain) => userDomain.name).join(', ') }, `The account also works in ${elsewhere.map((userDomain) => userDomain.name).join(', ')} - its password is not this domain's to reset.`);
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
/** Owner of any domain - through the `DomainOwner` table or the primary owner column (`domain-ownership.ts`). */
|
|
28
|
+
async function ownsAnyDomain(user, tx) {
|
|
29
|
+
if (await (0, shell_1.getRepository)(shell_1.Domain, tx).exists({ where: { owner: user.id } })) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return await (0, shell_1.getRepository)(domain_owner_js_1.DomainOwner, tx).exists({ where: { user: { id: user.id } } });
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=password-reset-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-reset-rule.js","sourceRoot":"","sources":["../../server/utils/password-reset-rule.ts"],"names":[],"mappings":";;AAiBA,oDAuBC;AAxCD,iDAAsE;AAGtE,6EAAqE;AAGrE;;;;;;;;;;GAUG;AACI,KAAK,UAAU,oBAAoB,CACxC,MAAY,EACZ,MAAc,EACd,EAAkB;IAElB,IAAI,MAAM,CAAC,QAAQ,IAAI,OAAO,IAAI,CAAC,MAAM,aAAa,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;QACpE,OAAO,IAAI,eAAO,CAChB,8BAA8B,EAC9B,EAAE,EACF,2EAA2E,CAC5E,CAAA;IACH,CAAC;IAED,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,UAAkB,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,CAAC,CAAA;IACpG,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,eAAO,CAChB,sCAAsC,EACtC,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,UAAkB,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAC9E,6BAA6B,SAAS,CAAC,GAAG,CAAC,CAAC,UAAkB,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,gDAAgD,CAC/I,CAAA;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,iHAAiH;AACjH,KAAK,UAAU,aAAa,CAAC,IAAU,EAAE,EAAkB;IACzD,IAAI,MAAM,IAAA,qBAAa,EAAC,cAAM,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC;QAC1E,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,MAAM,IAAA,qBAAa,EAAC,6BAAW,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AAC1F,CAAC","sourcesContent":["import { Domain, getRepository, Refusal } from '@things-factory/shell'\nimport { EntityManager } from 'typeorm'\n\nimport { DomainOwner } from '../service/domain-owner/domain-owner.js'\nimport { User } from '../service/user/user.js'\n\n/**\n * Whom a domain may put back on the default password.\n *\n * A person has one password for every domain they work in, and the default password is known to everyone who\n * was ever handed it. Resetting one is therefore the same as taking the account: the first one to sign in with\n * the default is sent to set a new password. A domain may do that only to an account that is wholly its own -\n * one that works in no other domain and owns no domain. The system domain's owner (the super user) and any\n * domain's owner are refused here, whichever domain asks; so are `admin` accounts, as `inactivateUser` refuses them.\n *\n * @returns the refusal, or `null` when `target` may be reset from `domain`. `target` must carry its `domains`.\n */\nexport async function passwordResetRefusal(\n target: User,\n domain: Domain,\n tx?: EntityManager\n): Promise<Refusal | null> {\n if (target.userType == 'admin' || (await ownsAnyDomain(target, tx))) {\n return new Refusal(\n 'password-reset-owner-account',\n {},\n \"A domain owner's or administrator's password is not reset to the default.\"\n )\n }\n\n const elsewhere = (target.domains || []).filter((userDomain: Domain) => userDomain.id !== domain.id)\n if (elsewhere.length > 0) {\n return new Refusal(\n 'password-reset-reaches-other-domains',\n { domains: elsewhere.map((userDomain: Domain) => userDomain.name).join(', ') },\n `The account also works in ${elsewhere.map((userDomain: Domain) => userDomain.name).join(', ')} - its password is not this domain's to reset.`\n )\n }\n\n return null\n}\n\n/** Owner of any domain - through the `DomainOwner` table or the primary owner column (`domain-ownership.ts`). */\nasync function ownsAnyDomain(user: User, tx?: EntityManager): Promise<boolean> {\n if (await getRepository(Domain, tx).exists({ where: { owner: user.id } })) {\n return true\n }\n\n return await getRepository(DomainOwner, tx).exists({ where: { user: { id: user.id } } })\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/auth-base",
|
|
3
|
-
"version": "10.1.
|
|
3
|
+
"version": "10.1.215",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"passport-jwt": "^4.0.0",
|
|
51
51
|
"passport-local": "^1.0.0"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "7de5e80eb1f4e2e268746aee39f820e7563f454d"
|
|
54
54
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whom a domain may put back on the default password.
|
|
3
|
+
*
|
|
4
|
+
* `resetPasswordToDefault` asked only that the person belong to the asking domain. A person has one password for
|
|
5
|
+
* every domain, and the default is known: whoever signs in with it first is sent to set a new one. So a user manager
|
|
6
|
+
* of any domain could take the account of someone who also works elsewhere, of another domain's owner, and of the
|
|
7
|
+
* super user (the system domain's owner) the moment that person was a member here.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { config } from '@things-factory/env'
|
|
11
|
+
|
|
12
|
+
import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
|
|
13
|
+
import { loadCompiled } from './compiled'
|
|
14
|
+
|
|
15
|
+
const { User } = loadCompiled('service/user/user')
|
|
16
|
+
const { UserMutation } = loadCompiled('service/user/user-mutation')
|
|
17
|
+
const { UserQuery } = loadCompiled('service/user/user-query')
|
|
18
|
+
const { DomainOwner } = loadCompiled('service/domain-owner/domain-owner')
|
|
19
|
+
|
|
20
|
+
const DEFAULT_PASSWORD = 'Default!234'
|
|
21
|
+
const OWN_PASSWORD = 'Mine!5678'
|
|
22
|
+
|
|
23
|
+
let here: any
|
|
24
|
+
let other: any
|
|
25
|
+
let system: any
|
|
26
|
+
|
|
27
|
+
async function makeUser(domains: any[], extra: any = {}) {
|
|
28
|
+
const tag = Math.random().toString(36).slice(2, 8)
|
|
29
|
+
const salt = User.generateSalt()
|
|
30
|
+
return await getRepository(User).save({
|
|
31
|
+
username: `u-${tag}`,
|
|
32
|
+
email: `u-${tag}@example.com`,
|
|
33
|
+
name: 'Kim',
|
|
34
|
+
salt,
|
|
35
|
+
password: User.encode(OWN_PASSWORD, salt),
|
|
36
|
+
status: 'activated',
|
|
37
|
+
domains,
|
|
38
|
+
...extra
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function context(domain: any) {
|
|
43
|
+
return { state: { domain, tx: undefined }, t: (key: string) => key } as any
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function reset(user: any, domain: any) {
|
|
47
|
+
return new UserMutation().resetPasswordToDefault(user.username, context(domain))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function mayReset(user: any, domain: any) {
|
|
51
|
+
return new UserQuery().mayResetPasswordToDefault(user.id, context(domain))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function stillHasOwnPassword(user: any) {
|
|
55
|
+
const read = await getRepository(User).findOneBy({ id: user.id })
|
|
56
|
+
return User.verify(read.password, OWN_PASSWORD, read.salt)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
beforeAll(async () => {
|
|
60
|
+
await openAuthDatabase()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
afterAll(async () => {
|
|
64
|
+
config.set('password', undefined)
|
|
65
|
+
await closeAuthDatabase()
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
beforeEach(async () => {
|
|
69
|
+
await resetAuthDatabase()
|
|
70
|
+
config.set('password', { defaultPassword: DEFAULT_PASSWORD })
|
|
71
|
+
|
|
72
|
+
const domainRepository = getRepository(Domain)
|
|
73
|
+
here = await domainRepository.save({ name: 'Plant A', subdomain: 'plant-a' })
|
|
74
|
+
other = await domainRepository.save({ name: 'Plant B', subdomain: 'plant-b' })
|
|
75
|
+
system = await domainRepository.save({ name: 'System', subdomain: 'system' })
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('a person wholly of this domain', () => {
|
|
79
|
+
it('is reset to the default, and the screen offers it', async () => {
|
|
80
|
+
const user = await makeUser([here])
|
|
81
|
+
|
|
82
|
+
expect(await mayReset(user, here)).toBe(true)
|
|
83
|
+
await expect(reset(user, here)).resolves.toBe(true)
|
|
84
|
+
|
|
85
|
+
const read = await getRepository(User).findOneBy({ id: user.id })
|
|
86
|
+
expect(User.verify(read.password, DEFAULT_PASSWORD, read.salt)).toBe(true)
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
describe('refused, and the password stays', () => {
|
|
91
|
+
it('★ someone who also works in another domain', async () => {
|
|
92
|
+
const user = await makeUser([here, other])
|
|
93
|
+
|
|
94
|
+
expect(await mayReset(user, here)).toBe(false)
|
|
95
|
+
await expect(reset(user, here)).rejects.toMatchObject({
|
|
96
|
+
code: 'password-reset-reaches-other-domains',
|
|
97
|
+
params: { domains: 'Plant B' }
|
|
98
|
+
})
|
|
99
|
+
expect(await stillHasOwnPassword(user)).toBe(true)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it("★ another domain's owner, through the owner table, though a member only here", async () => {
|
|
103
|
+
const user = await makeUser([here])
|
|
104
|
+
await getRepository(DomainOwner).save({ domain: other, user })
|
|
105
|
+
|
|
106
|
+
expect(await mayReset(user, here)).toBe(false)
|
|
107
|
+
await expect(reset(user, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
|
|
108
|
+
expect(await stillHasOwnPassword(user)).toBe(true)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it("★ another domain's primary owner (the column only)", async () => {
|
|
112
|
+
const user = await makeUser([here])
|
|
113
|
+
await getRepository(Domain).update({ id: other.id }, { owner: user.id })
|
|
114
|
+
|
|
115
|
+
expect(await mayReset(user, here)).toBe(false)
|
|
116
|
+
await expect(reset(user, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
|
|
117
|
+
expect(await stillHasOwnPassword(user)).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('★ the super user - asked from the system domain itself', async () => {
|
|
121
|
+
const superUser = await makeUser([system])
|
|
122
|
+
await getRepository(Domain).update({ id: system.id }, { owner: superUser.id })
|
|
123
|
+
|
|
124
|
+
expect(await mayReset(superUser, system)).toBe(false)
|
|
125
|
+
await expect(reset(superUser, system)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
|
|
126
|
+
expect(await stillHasOwnPassword(superUser)).toBe(true)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('★ the super user - asked from a domain they are also a member of', async () => {
|
|
130
|
+
const superUser = await makeUser([system, here])
|
|
131
|
+
await getRepository(Domain).update({ id: system.id }, { owner: superUser.id })
|
|
132
|
+
|
|
133
|
+
expect(await mayReset(superUser, here)).toBe(false)
|
|
134
|
+
await expect(reset(superUser, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
|
|
135
|
+
expect(await stillHasOwnPassword(superUser)).toBe(true)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it("this domain's own owner", async () => {
|
|
139
|
+
const owner = await makeUser([here])
|
|
140
|
+
await getRepository(DomainOwner).save({ domain: here, user: owner })
|
|
141
|
+
|
|
142
|
+
expect(await mayReset(owner, here)).toBe(false)
|
|
143
|
+
await expect(reset(owner, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('an admin account, as inactivateUser refuses it', async () => {
|
|
147
|
+
const admin = await makeUser([here], { userType: 'admin' })
|
|
148
|
+
|
|
149
|
+
expect(await mayReset(admin, here)).toBe(false)
|
|
150
|
+
await expect(reset(admin, here)).rejects.toMatchObject({ code: 'password-reset-owner-account' })
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('the screen is told no for someone who is not a member here', async () => {
|
|
154
|
+
const stranger = await makeUser([other])
|
|
155
|
+
|
|
156
|
+
expect(await mayReset(stranger, here)).toBe(false)
|
|
157
|
+
})
|
|
158
|
+
})
|
package/translations/en.json
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"error.x is not a member of y": "{x} is not a member of {y}",
|
|
30
30
|
"error.x privilege required": "unauthorized! {x} privilege required",
|
|
31
31
|
"error.role-not-assignable-here": "this domain cannot assign the role(s) {roles}.",
|
|
32
|
+
"error.password-reset-owner-account": "A domain owner's or administrator's password is not reset to the default.",
|
|
33
|
+
"error.password-reset-reaches-other-domains": "they also work in {domains}, so their password is not this $t(term.domain)'s to reset.",
|
|
32
34
|
"error.x not allowed from this address": "unauthorized! {x} is not allowed from this address",
|
|
33
35
|
"error.x not licensed": "unauthorized! {x} is not licensed on this installation",
|
|
34
36
|
"error.x refused for this request": "unauthorized! {x} is refused for this request",
|
package/translations/es.json
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"error.x is not a member of y": "{x} no es miembro de {y}",
|
|
30
30
|
"error.x privilege required": "¡no autorizado! se requiere el privilegio {x}",
|
|
31
31
|
"error.role-not-assignable-here": "este dominio no puede asignar los roles {roles}.",
|
|
32
|
+
"error.password-reset-owner-account": "la contraseña de un propietario o administrador no se restablece al valor predeterminado.",
|
|
33
|
+
"error.password-reset-reaches-other-domains": "también trabaja en {domains}; este dominio no puede restablecer su contraseña.",
|
|
32
34
|
"error.x not allowed from this address": "¡no autorizado! {x} no está permitido desde esta dirección",
|
|
33
35
|
"error.x not licensed": "¡no autorizado! {x} no tiene licencia en esta instalación",
|
|
34
36
|
"error.x refused for this request": "¡no autorizado! {x} se ha denegado para esta solicitud",
|
package/translations/ja.json
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"error.x is not a member of y": "{x}は{y}のメンバーではありません.",
|
|
30
30
|
"error.x privilege required": "権限がありません。{x} 権限が必要です。",
|
|
31
31
|
"error.role-not-assignable-here": "この$t(term.domain)では付与できないロールです: {roles}",
|
|
32
|
+
"error.password-reset-owner-account": "$t(term.domain)のオーナーや管理者のパスワードは初期値にリセットしません。",
|
|
33
|
+
"error.password-reset-reaches-other-domains": "{domains}でも利用している方のため、この$t(term.domain)ではパスワードをリセットできません。",
|
|
32
34
|
"error.x not allowed from this address": "権限がありません。このアドレスからは {x} 権限を使えません。",
|
|
33
35
|
"error.x not licensed": "権限がありません。このインストールのライセンスには {x} 権限がありません。",
|
|
34
36
|
"error.x refused for this request": "権限がありません。このリクエストでは {x} 権限が許可されていません。",
|
package/translations/ko.json
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"error.x is not a member of y": "{x}은(는) {y}의 멤버가 아닙니다.",
|
|
30
30
|
"error.x privilege required": "권한이 없습니다. {x} 권한이 필요합니다.",
|
|
31
31
|
"error.role-not-assignable-here": "이 $t(term.domain)에서 줄 수 없는 역할입니다: {roles}",
|
|
32
|
+
"error.password-reset-owner-account": "$t(term.domain) 오너나 관리자의 비밀번호는 기본값으로 초기화하지 않습니다.",
|
|
33
|
+
"error.password-reset-reaches-other-domains": "{domains}에서도 일하는 사람이라 이 $t(term.domain)에서 비밀번호를 초기화할 수 없습니다.",
|
|
32
34
|
"error.x not allowed from this address": "권한이 없습니다. 이 주소에서는 {x} 권한을 사용할 수 없습니다.",
|
|
33
35
|
"error.x not licensed": "권한이 없습니다. 이 설치의 라이선스에는 {x} 권한이 없습니다.",
|
|
34
36
|
"error.x refused for this request": "권한이 없습니다. 이 요청에는 {x} 권한이 허용되지 않습니다.",
|
package/translations/ms.json
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"error.x is not a member of y": "{x} bukan ahli {y}",
|
|
30
30
|
"error.x privilege required": "tidak dibenarkan! kebenaran {x} diperlukan",
|
|
31
31
|
"error.role-not-assignable-here": "$t(term.domain) ini tidak boleh memberikan peranan: {roles}",
|
|
32
|
+
"error.password-reset-owner-account": "Kata laluan pemilik $t(term.domain) atau pentadbir tidak ditetapkan semula kepada lalai.",
|
|
33
|
+
"error.password-reset-reaches-other-domains": "Pengguna ini juga bekerja di {domains}, jadi $t(term.domain) ini tidak boleh menetapkan semula kata laluannya.",
|
|
32
34
|
"error.x not allowed from this address": "tidak dibenarkan! kebenaran {x} tidak dibenarkan dari alamat ini",
|
|
33
35
|
"error.x not licensed": "tidak dibenarkan! kebenaran {x} tiada dalam lesen pemasangan ini",
|
|
34
36
|
"error.x refused for this request": "tidak dibenarkan! kebenaran {x} ditolak untuk permintaan ini",
|
package/translations/zh.json
CHANGED
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"error.x is not a member of y": "{x}不是{y}的成员",
|
|
30
30
|
"error.x privilege required": "没有权限。需要 {x} 权限。",
|
|
31
31
|
"error.role-not-assignable-here": "此$t(term.domain)不能分配的角色: {roles}",
|
|
32
|
+
"error.password-reset-owner-account": "不会将$t(term.domain)所有者或管理员的密码重置为默认值。",
|
|
33
|
+
"error.password-reset-reaches-other-domains": "该用户也在{domains}中工作,此$t(term.domain)不能重置其密码。",
|
|
32
34
|
"error.x not allowed from this address": "没有权限。从此地址不能使用 {x} 权限。",
|
|
33
35
|
"error.x not licensed": "没有权限。此安装的许可证不包含 {x} 权限。",
|
|
34
36
|
"error.x refused for this request": "没有权限。此请求不允许 {x} 权限。",
|