@things-factory/auth-base 10.1.226 → 10.1.227

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.
@@ -26,7 +26,7 @@
26
26
  * 아예 없고 어떤 경우엔 문자열 null 이다. 그러면 new URL(...) 이 던지고 **로그인과 OAuth
27
27
  * 콜백이 런타임에 깨진다.** 타입도 맞고 테스트도 도니까 띄워서 로그인해 봐야 안다.
28
28
  *
29
- * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 판 모두 「이 요청이 들어온
29
+ * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 버전 모두 「이 요청이 들어온
30
30
  * 곳」이고 app.proxy 설정도 그대로 존중한다. **오늘 값은 바뀌지 않는다.**
31
31
  *
32
32
  * ── config 의 protocol 이 마지막에 이긴다 ───────────────────────────────────
@@ -36,7 +36,7 @@ const protocol = env_1.config.get('protocol');
36
36
  * 아예 없고 어떤 경우엔 문자열 null 이다. 그러면 new URL(...) 이 던지고 **로그인과 OAuth
37
37
  * 콜백이 런타임에 깨진다.** 타입도 맞고 테스트도 도니까 띄워서 로그인해 봐야 안다.
38
38
  *
39
- * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 판 모두 「이 요청이 들어온
39
+ * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 버전 모두 「이 요청이 들어온
40
40
  * 곳」이고 app.proxy 설정도 그대로 존중한다. **오늘 값은 바뀌지 않는다.**
41
41
  *
42
42
  * ── config 의 protocol 이 마지막에 이긴다 ───────────────────────────────────
@@ -71,7 +71,7 @@ function buildAuthURL(pathname, context) {
71
71
  * 콜백 주소를 만드는 이 자리에서 런타임에 깨진다.** 타입도 맞고 테스트도 도니까 띄워서
72
72
  * 로그인해 봐야 안다.
73
73
  *
74
- * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 판 모두 「이 요청이
74
+ * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 버전 모두 「이 요청이
75
75
  * 들어온 곳」이고 app.proxy 설정도 그대로 존중한다. **오늘 값은 바뀌지 않는다.**
76
76
  */
77
77
  var url = new url_1.URL(`${context.request.protocol}://${context.request.host}`);
@@ -1 +1 @@
1
- {"version":3,"file":"build-auth-url.js","sourceRoot":"","sources":["../../server/utils/build-auth-url.ts"],"names":[],"mappings":";;AA6CA,oCA0CC;AAvFD,6BAAyB;AAEzB,6CAA4C;AAE5C;;;GAGG;AACH,MAAM,QAAQ,GAAW,YAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,SAAgB,YAAY,CAAC,QAAgB,EAAE,OAAwB;IAErE,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAC7D,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACxD,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAExD,IAAI,gBAAgB,IAAI,YAAY,EAAE,CAAC;QACrC,IAAI,GAAG,GAAQ,IAAI,SAAG,CAAC,GAAG,gBAAgB,MAAM,YAAY,EAAE,CAAC,CAAA;QAC/D,IAAI,YAAY,EAAE,CAAC;YACjB,GAAG,CAAC,IAAI,GAAG,YAAY,CAAA;QACzB,CAAC;IACH,CAAC;SAAM,CAAC;QACR;;;;;;;;;;;;;;;;;;;WAmBG;QACD,IAAI,GAAG,GAAQ,IAAI,SAAG,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IACjF,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAA;IACzB,CAAC;IAED,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAEvB,OAAO,GAAG,CAAC,IAAI,CAAA;AACjB,CAAC","sourcesContent":["import { URL } from 'url'\n\nimport { config } from '@things-factory/env'\n\n/*\n * 프록시가 헤더를 안 실어 주면서 TLS 종료를 하는 배치를 위한 못. 모듈이 실릴 때 한 번 읽는다\n * — 옮겨 오기 전에도 같은 자리에서 같은 방식으로 읽고 있었다.\n */\nconst protocol: string = config.get('protocol')\n\n/**\n * **이 요청이 들어온 우리 주소**에 경로를 붙여 절대 주소를 만든다.\n *\n * 로그인·OAuth 의 주소를 화면과 저장 값에 내려 줄 때 쓴다.\n *\n * buildAuthURL('/oauth/authorize', context)\n * buildAuthURL('/oauth2-client/callback', context)\n *\n * ── 왜 공용인가 ─────────────────────────────────────────────────────────────\n * 이 열두 줄이 auth-base 와 oauth2-client 에 **똑같이 베껴져** 있었다(2026-09-04).\n * x-forwarded 셋 읽기 · 폴백 · protocol 덮기 · pathname 붙이기가 같은 코드였다.\n * 한쪽만 고치는 일이 실제로 났다 — 그래서 한 집으로 모았다.\n *\n * ── 프록시 뒤에서는 프록시가 본 주소를 쓴다 ─────────────────────────────────\n * 리버스 프록시를 거치면 소켓이 보는 것은 프록시와의 연결이라 공개 주소가 아니다. 그때는\n * 프록시가 실어 주는 x-forwarded-* 가 유일한 단서다.\n *\n * ── 폴백에서 request.origin 을 쓰지 않는 이유 ───────────────────────────────\n * Koa 2 에서는 origin 이 정확히 우리가 원하는 값이다.\n *\n * [읽었음] node_modules/koa/lib/request.js:98\n * get origin() { return `${this.protocol}://${this.host}` }\n *\n * 그런데 **Koa 3 은 그 뜻을 바꾼다** — 현재 호스트가 아니라 브라우저가 보낸 Origin 헤더를\n * 준다(CORS 표준에 맞춘 변경, koajs/koa v3.0.0 릴리즈 노트). 그 헤더는 같은 출처 요청에는\n * 아예 없고 어떤 경우엔 문자열 null 이다. 그러면 new URL(...) 이 던지고 **로그인과 OAuth\n * 콜백이 런타임에 깨진다.** 타입도 맞고 테스트도 도니까 띄워서 로그인해 봐야 안다.\n *\n * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 판 모두 「이 요청이 들어온\n * 곳」이고 app.proxy 설정도 그대로 존중한다. **오늘 값은 바뀌지 않는다.**\n *\n * ── config 의 protocol 이 마지막에 이긴다 ───────────────────────────────────\n * 프록시가 헤더를 안 실어 주면서 TLS 종료를 하는 배치가 있다. 그때 여기서 읽은 것은 http 인데\n * 브라우저가 아는 것은 https 다. 그 경우를 설정으로 못 박는다.\n */\nexport function buildAuthURL(pathname: string, context: ResolverContext): string {\n\n const originalProtocol = context.headers['x-forwarded-proto']\n const originalHost = context.headers['x-forwarded-host']\n const originalPort = context.headers['x-forwarded-port']\n\n if (originalProtocol && originalHost) {\n var url: URL = new URL(`${originalProtocol}://${originalHost}`)\n if (originalPort) {\n url.port = originalPort\n }\n } else {\n /*\n * ── 왜 request.origin 을 쓰지 않나 ──────────────────────────────────────\n *\n * 여기가 하려는 일은 **이 요청이 들어온 우리 주소**를 얻는 것이다. 위의 x-forwarded-*\n * 가 없을 때(프록시를 안 거친 직접 접속) 그 값을 요청에서 읽는다.\n *\n * 전에는 context.request.origin 이었다. Koa 2 에서는 그것이 정확히 우리가 원하는 값이다.\n *\n * [읽었음] node_modules/koa/lib/request.js:98\n * get origin() { return `${this.protocol}://${this.host}` }\n *\n * 그런데 **Koa 3 은 그 뜻을 바꾼다** — 현재 호스트가 아니라 브라우저가 보낸 Origin 헤더를\n * 준다(CORS 표준에 맞춘 변경, koajs/koa v3.0.0 릴리즈 노트). 그 헤더는 같은 출처 요청에는\n * 아예 없고 어떤 경우엔 문자열 null 이다. 그러면 new URL(...) 이 던지고, **로그인·OAuth\n * 콜백 주소를 만드는 이 자리에서 런타임에 깨진다.** 타입도 맞고 테스트도 도니까 띄워서\n * 로그인해 봐야 안다.\n *\n * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 판 모두 「이 요청이\n * 들어온 곳」이고 app.proxy 설정도 그대로 존중한다. **오늘 값은 바뀌지 않는다.**\n */\n var url: URL = new URL(`${context.request.protocol}://${context.request.host}`)\n }\n\n if (protocol) {\n url.protocol = protocol\n }\n\n url.pathname = pathname\n\n return url.href\n}\n"]}
1
+ {"version":3,"file":"build-auth-url.js","sourceRoot":"","sources":["../../server/utils/build-auth-url.ts"],"names":[],"mappings":";;AA6CA,oCA0CC;AAvFD,6BAAyB;AAEzB,6CAA4C;AAE5C;;;GAGG;AACH,MAAM,QAAQ,GAAW,YAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;AAE/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,SAAgB,YAAY,CAAC,QAAgB,EAAE,OAAwB;IAErE,MAAM,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAC7D,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IACxD,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAExD,IAAI,gBAAgB,IAAI,YAAY,EAAE,CAAC;QACrC,IAAI,GAAG,GAAQ,IAAI,SAAG,CAAC,GAAG,gBAAgB,MAAM,YAAY,EAAE,CAAC,CAAA;QAC/D,IAAI,YAAY,EAAE,CAAC;YACjB,GAAG,CAAC,IAAI,GAAG,YAAY,CAAA;QACzB,CAAC;IACH,CAAC;SAAM,CAAC;QACR;;;;;;;;;;;;;;;;;;;WAmBG;QACD,IAAI,GAAG,GAAQ,IAAI,SAAG,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;IACjF,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAA;IACzB,CAAC;IAED,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAEvB,OAAO,GAAG,CAAC,IAAI,CAAA;AACjB,CAAC","sourcesContent":["import { URL } from 'url'\n\nimport { config } from '@things-factory/env'\n\n/*\n * 프록시가 헤더를 안 실어 주면서 TLS 종료를 하는 배치를 위한 못. 모듈이 실릴 때 한 번 읽는다\n * — 옮겨 오기 전에도 같은 자리에서 같은 방식으로 읽고 있었다.\n */\nconst protocol: string = config.get('protocol')\n\n/**\n * **이 요청이 들어온 우리 주소**에 경로를 붙여 절대 주소를 만든다.\n *\n * 로그인·OAuth 의 주소를 화면과 저장 값에 내려 줄 때 쓴다.\n *\n * buildAuthURL('/oauth/authorize', context)\n * buildAuthURL('/oauth2-client/callback', context)\n *\n * ── 왜 공용인가 ─────────────────────────────────────────────────────────────\n * 이 열두 줄이 auth-base 와 oauth2-client 에 **똑같이 베껴져** 있었다(2026-09-04).\n * x-forwarded 셋 읽기 · 폴백 · protocol 덮기 · pathname 붙이기가 같은 코드였다.\n * 한쪽만 고치는 일이 실제로 났다 — 그래서 한 집으로 모았다.\n *\n * ── 프록시 뒤에서는 프록시가 본 주소를 쓴다 ─────────────────────────────────\n * 리버스 프록시를 거치면 소켓이 보는 것은 프록시와의 연결이라 공개 주소가 아니다. 그때는\n * 프록시가 실어 주는 x-forwarded-* 가 유일한 단서다.\n *\n * ── 폴백에서 request.origin 을 쓰지 않는 이유 ───────────────────────────────\n * Koa 2 에서는 origin 이 정확히 우리가 원하는 값이다.\n *\n * [읽었음] node_modules/koa/lib/request.js:98\n * get origin() { return `${this.protocol}://${this.host}` }\n *\n * 그런데 **Koa 3 은 그 뜻을 바꾼다** — 현재 호스트가 아니라 브라우저가 보낸 Origin 헤더를\n * 준다(CORS 표준에 맞춘 변경, koajs/koa v3.0.0 릴리즈 노트). 그 헤더는 같은 출처 요청에는\n * 아예 없고 어떤 경우엔 문자열 null 이다. 그러면 new URL(...) 이 던지고 **로그인과 OAuth\n * 콜백이 런타임에 깨진다.** 타입도 맞고 테스트도 도니까 띄워서 로그인해 봐야 안다.\n *\n * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 버전 모두 「이 요청이 들어온\n * 곳」이고 app.proxy 설정도 그대로 존중한다. **오늘 값은 바뀌지 않는다.**\n *\n * ── config 의 protocol 이 마지막에 이긴다 ───────────────────────────────────\n * 프록시가 헤더를 안 실어 주면서 TLS 종료를 하는 배치가 있다. 그때 여기서 읽은 것은 http 인데\n * 브라우저가 아는 것은 https 다. 그 경우를 설정으로 못 박는다.\n */\nexport function buildAuthURL(pathname: string, context: ResolverContext): string {\n\n const originalProtocol = context.headers['x-forwarded-proto']\n const originalHost = context.headers['x-forwarded-host']\n const originalPort = context.headers['x-forwarded-port']\n\n if (originalProtocol && originalHost) {\n var url: URL = new URL(`${originalProtocol}://${originalHost}`)\n if (originalPort) {\n url.port = originalPort\n }\n } else {\n /*\n * ── 왜 request.origin 을 쓰지 않나 ──────────────────────────────────────\n *\n * 여기가 하려는 일은 **이 요청이 들어온 우리 주소**를 얻는 것이다. 위의 x-forwarded-*\n * 가 없을 때(프록시를 안 거친 직접 접속) 그 값을 요청에서 읽는다.\n *\n * 전에는 context.request.origin 이었다. Koa 2 에서는 그것이 정확히 우리가 원하는 값이다.\n *\n * [읽었음] node_modules/koa/lib/request.js:98\n * get origin() { return `${this.protocol}://${this.host}` }\n *\n * 그런데 **Koa 3 은 그 뜻을 바꾼다** — 현재 호스트가 아니라 브라우저가 보낸 Origin 헤더를\n * 준다(CORS 표준에 맞춘 변경, koajs/koa v3.0.0 릴리즈 노트). 그 헤더는 같은 출처 요청에는\n * 아예 없고 어떤 경우엔 문자열 null 이다. 그러면 new URL(...) 이 던지고, **로그인·OAuth\n * 콜백 주소를 만드는 이 자리에서 런타임에 깨진다.** 타입도 맞고 테스트도 도니까 띄워서\n * 로그인해 봐야 안다.\n *\n * 그래서 Koa 2 의 정의를 그대로 펼쳐 쓴다. protocol 과 host 는 두 버전 모두 「이 요청이\n * 들어온 곳」이고 app.proxy 설정도 그대로 존중한다. **오늘 값은 바뀌지 않는다.**\n */\n var url: URL = new URL(`${context.request.protocol}://${context.request.host}`)\n }\n\n if (protocol) {\n url.protocol = protocol\n }\n\n url.pathname = pathname\n\n return url.href\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/auth-base",
3
- "version": "10.1.226",
3
+ "version": "10.1.227",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -35,10 +35,10 @@
35
35
  "@reduxjs/toolkit": "^2.2.5",
36
36
  "@simplewebauthn/browser": "^13.0.0",
37
37
  "@simplewebauthn/server": "^13.0.0",
38
- "@things-factory/email-base": "^10.1.208",
38
+ "@things-factory/email-base": "^10.1.227",
39
39
  "@things-factory/env": "^10.1.149",
40
- "@things-factory/shell": "^10.1.208",
41
- "@things-factory/task-lease": "^10.1.208",
40
+ "@things-factory/shell": "^10.1.227",
41
+ "@things-factory/task-lease": "^10.1.227",
42
42
  "@things-factory/utils": "^10.1.179",
43
43
  "@types/webappsec-credential-management": "^0.6.9",
44
44
  "jsonwebtoken": "^9.0.0",
@@ -50,5 +50,5 @@
50
50
  "passport-jwt": "^4.0.0",
51
51
  "passport-local": "^1.0.0"
52
52
  },
53
- "gitHead": "abc5cf99effa2de579a1cbfda74ddc5eb41dba50"
53
+ "gitHead": "f66591e3864aab08a6fb93fb1fcf1c9de6b546ac"
54
54
  }
@@ -154,7 +154,7 @@ describe('지우기 — 하나', () => {
154
154
  it('가진 사람이 있으면 그 이유로 거절한다', async () => {
155
155
  const role = await makeRole('in-use', child, [boardQuery], [actor])
156
156
 
157
- await expect(mutation.deleteRole(role.id, context(child))).rejects.toThrow('currently in use')
157
+ expect(await refusalCodeOf(mutation.deleteRole(role.id, context(child)))).toBe('role-in-use')
158
158
  expect(await getRepository(Role).findOneBy({ id: role.id })).not.toBeNull()
159
159
  })
160
160
 
@@ -181,15 +181,31 @@ describe('지우기 — 여럿', () => {
181
181
  expect(await getRepository(Role).findOneBy({ id: theirs.id })).not.toBeNull()
182
182
  })
183
183
 
184
- it('사용 중인 역할은 남긴다 — 하나 지우기와 같은 규칙이다', async () => {
184
+ it('★ 사용 중인 역할이 하나라도 있으면 아무것도 안 지우고 그 이름과 사람 수로 거절한다 — 하나 지우기와 같은 거절이다', async () => {
185
185
  const unused = await makeRole('unused', child)
186
186
  const inUse = await makeRole('in-use', child, [], [actor])
187
187
 
188
- await mutation.deleteRoles([unused.id, inUse.id], context(child))
188
+ let refusal: any
189
+ try {
190
+ await mutation.deleteRoles([unused.id, inUse.id], context(child))
191
+ } catch (error) {
192
+ refusal = error
193
+ }
189
194
 
190
- expect(await getRepository(Role).findOneBy({ id: unused.id })).toBeNull()
195
+ expect(refusal?.code).toBe('role-in-use')
196
+ expect(refusal?.params).toEqual({ names: 'in-use (1)' })
197
+ expect(await getRepository(Role).findOneBy({ id: unused.id })).not.toBeNull()
191
198
  expect(await getRepository(Role).findOneBy({ id: inUse.id })).not.toBeNull()
192
199
  })
200
+
201
+ it('아무도 안 가진 역할은 함께 지운다', async () => {
202
+ const one = await makeRole('one', child)
203
+ const two = await makeRole('two', child)
204
+
205
+ expect(await mutation.deleteRoles([one.id, two.id], context(child))).toBe(true)
206
+ expect(await getRepository(Role).findOneBy({ id: one.id })).toBeNull()
207
+ expect(await getRepository(Role).findOneBy({ id: two.id })).toBeNull()
208
+ })
193
209
  })
194
210
 
195
211
  /**
@@ -159,6 +159,32 @@ describe('권한 수', () => {
159
159
  })
160
160
  })
161
161
 
162
+ /* 가진 사람 수 — 가진 사람이 있는 역할은 지우지 않으므로 목록이 지우기 전에 보인다(2026-10-28). */
163
+ describe('가진 사람 수', () => {
164
+ async function holder(roles: any[]) {
165
+ const salt = User.generateSalt()
166
+ const tag = Math.random().toString(36).slice(2, 8)
167
+ return await getRepository(User).save({ username: `h-${tag}`, email: `h-${tag}@example.com`, name: 'Lee', salt, status: 'activated', roles })
168
+ }
169
+
170
+ it('그 역할을 가진 사람만 센다', async () => {
171
+ const mine = await getRepository(Role).save({ name: 'mine', domain: child, privileges: [] })
172
+ const other = await getRepository(Role).save({ name: 'other', domain: child, privileges: [] })
173
+ await holder([mine])
174
+ await holder([mine, other])
175
+ await holder([other])
176
+
177
+ expect(await query.userCount(mine)).toBe(2)
178
+ expect(await query.userCount(other)).toBe(2)
179
+ })
180
+
181
+ it('아무도 없으면 0 이다', async () => {
182
+ const role = await getRepository(Role).save({ name: 'blank', domain: child, privileges: [] })
183
+
184
+ expect(await query.userCount(role)).toBe(0)
185
+ })
186
+ })
187
+
162
188
  /**
163
189
  * 한 사람의 역할 — `rolePrivileges` 와 **같은 결함을 갖고 있던** 자리.
164
190
  *
@@ -13,6 +13,7 @@
13
13
  "error.password used in the past": "password used in the past",
14
14
  "error.role-name-stands-inherited": "a role called {name} is already visible here, defined in {domain}. stand this domain's own of that name only if that is what you mean to do.",
15
15
  "error.role-name-taken-here": "this domain already has a role called {name}.",
16
+ "error.role-in-use": "{names} are held by users and cannot be deleted. Take them off the users first.",
16
17
  "error.subdomain not found": "domain not found",
17
18
  "error.token or password is invalid": "token or password is invalid",
18
19
  "error.unavailable-domain": "unavailable domain",
@@ -13,6 +13,7 @@
13
13
  "error.password used in the past": "contraseña usada anteriormente",
14
14
  "error.role-name-stands-inherited": "ya se muestra aquí un rol llamado {name}, definido en {domain}. cree en este dominio uno propio con ese nombre solo si es lo que pretende hacer.",
15
15
  "error.role-name-taken-here": "este dominio ya tiene un rol llamado {name}.",
16
+ "error.role-in-use": "{names} están asignados a usuarios y no se pueden eliminar. Quítelos primero de los usuarios.",
16
17
  "error.subdomain not found": "dominio no encontrado",
17
18
  "error.token or password is invalid": "el token o la contraseña no son válidos",
18
19
  "error.unavailable-domain": "dominio no disponible",
@@ -13,6 +13,7 @@
13
13
  "error.password used in the past": "過去に使用されたパスワードです.",
14
14
  "error.role-name-stands-inherited": "{name} ロールは {domain} に定義されており、ここからも見えています。この$t(term.domain)に同じ名前で別に立てるつもりの場合のみ作成します。",
15
15
  "error.role-name-taken-here": "この$t(term.domain)には既に {name} ロールがあります。",
16
+ "error.role-in-use": "{names} はユーザーに付与されているため削除できません。先にユーザーから外してください。",
16
17
  "error.subdomain not found": "サブドメインが見つかりません.",
17
18
  "error.token or password is invalid": "トークンまたはパスワードが無効です.",
18
19
  "error.unavailable-domain": "使用できない$t(term.domain)です.",
@@ -13,6 +13,7 @@
13
13
  "error.password used in the past": "과거에 사용된 비밀번호입니다.",
14
14
  "error.role-name-stands-inherited": "{name} 역할은 {domain}에 정의되어 현재 도메인에 상속되어 있습니다. 상속된 역할을 대체하려는 경우에만 이 $t(term.domain)에 같은 이름의 역할을 생성해 주세요.",
15
15
  "error.role-name-taken-here": "이 $t(term.domain)에 이미 {name} 역할이 있습니다.",
16
+ "error.role-in-use": "{names} 역할은 사용자에게 부여되어 있어 지울 수 없습니다. 사용자에게서 먼저 떼어 주세요.",
16
17
  "error.subdomain not found": "서브도메인을 찾을 수 없습니다.",
17
18
  "error.token or password is invalid": "토큰 또는 비밀번호가 유효하지 않습니다.",
18
19
  "error.unavailable-domain": "사용할 수 없는 $t(term.domain)입니다.",
@@ -13,6 +13,7 @@
13
13
  "error.password used in the past": "Kata laluan telah digunakan dalam masa lampau",
14
14
  "error.role-name-stands-inherited": "peranan bernama {name} ditakrifkan dalam {domain} dan kelihatan di sini juga. Bina milik domain ini dengan nama yang sama hanya jika itu memang yang dimaksudkan.",
15
15
  "error.role-name-taken-here": "domain ini sudah mempunyai peranan bernama {name}.",
16
+ "error.role-in-use": "{names} dipegang oleh pengguna dan tidak boleh dipadam. Tanggalkan daripada pengguna dahulu.",
16
17
  "error.subdomain not found": "Domain tidak ditemui",
17
18
  "error.token or password is invalid": "Token atau kata laluan tidak sah",
18
19
  "error.unavailable-domain": "Domain tidak tersedia",
@@ -13,6 +13,7 @@
13
13
  "error.password used in the past": "使用过的密码!",
14
14
  "error.role-name-stands-inherited": "名为 {name} 的角色定义在 {domain},在此处也可见。只有确实要在该$t(term.domain)中另行建立同名角色时才创建。",
15
15
  "error.role-name-taken-here": "该$t(term.domain)中已有名为 {name} 的角色。",
16
+ "error.role-in-use": "{names} 已分配给用户,无法删除。请先从用户中移除。",
16
17
  "error.subdomain not found": "用户域查询失败!",
17
18
  "error.token or password is invalid": "令牌或密码无效!",
18
19
  "error.unavailable-domain": "不可用的$t(term.domain)名",