@things-factory/auth-base 10.1.4 → 10.1.6

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.
Files changed (52) hide show
  1. package/dist-client/auth.d.ts +9 -1
  2. package/dist-client/auth.js +12 -3
  3. package/dist-client/auth.js.map +1 -1
  4. package/dist-client/tsconfig.tsbuildinfo +1 -1
  5. package/dist-server/controllers/profile.d.ts +1 -0
  6. package/dist-server/controllers/signin.js +33 -12
  7. package/dist-server/controllers/signin.js.map +1 -1
  8. package/dist-server/controllers/unlock-user.js +21 -2
  9. package/dist-server/controllers/unlock-user.js.map +1 -1
  10. package/dist-server/router/auth-public-process-router.js +32 -5
  11. package/dist-server/router/auth-public-process-router.js.map +1 -1
  12. package/dist-server/service/privilege/privilege-directive.d.ts +46 -0
  13. package/dist-server/service/privilege/privilege-directive.js +87 -6
  14. package/dist-server/service/privilege/privilege-directive.js.map +1 -1
  15. package/dist-server/service/role/index.d.ts +2 -1
  16. package/dist-server/service/role/index.js +2 -1
  17. package/dist-server/service/role/index.js.map +1 -1
  18. package/dist-server/service/role/role-mutation.d.ts +17 -1
  19. package/dist-server/service/role/role-mutation.js +126 -7
  20. package/dist-server/service/role/role-mutation.js.map +1 -1
  21. package/dist-server/service/role/role-privilege-query.d.ts +14 -0
  22. package/dist-server/service/role/role-privilege-query.js +37 -0
  23. package/dist-server/service/role/role-privilege-query.js.map +1 -0
  24. package/dist-server/service/role/role-query.d.ts +18 -0
  25. package/dist-server/service/role/role-query.js +38 -22
  26. package/dist-server/service/role/role-query.js.map +1 -1
  27. package/dist-server/service/user/user.d.ts +1 -0
  28. package/dist-server/service/user/user.js +55 -13
  29. package/dist-server/service/user/user.js.map +1 -1
  30. package/dist-server/tsconfig.tsbuildinfo +1 -1
  31. package/dist-server/utils/account-lock-rule.d.ts +79 -0
  32. package/dist-server/utils/account-lock-rule.js +114 -0
  33. package/dist-server/utils/account-lock-rule.js.map +1 -0
  34. package/dist-server/utils/lock-duration.d.ts +3 -0
  35. package/dist-server/utils/lock-duration.js +36 -0
  36. package/dist-server/utils/lock-duration.js.map +1 -0
  37. package/package.json +2 -2
  38. package/tests/account-lock-db.test.ts +216 -0
  39. package/tests/account-lock-rule.test.ts +141 -0
  40. package/tests/compiled.ts +41 -0
  41. package/tests/has-privilege-db.test.ts +198 -0
  42. package/tests/lock-recovery.test.ts +204 -0
  43. package/tests/password-hashing.test.ts +131 -0
  44. package/tests/password-persistence.test.ts +147 -0
  45. package/tests/permission-gate.test.ts +226 -0
  46. package/tests/privilege-directive.test.ts +270 -0
  47. package/tests/role-mutation-db.test.ts +263 -0
  48. package/tests/role-privileges-db.test.ts +128 -0
  49. package/tests/signin-policy.test.ts +200 -0
  50. package/tests/token-issuance.test.ts +240 -0
  51. package/translations/en.json +161 -1
  52. package/translations/ko.json +161 -1
@@ -0,0 +1,240 @@
1
+ /**
2
+ * 토큰 발급 시험 — **우리 토큰이 무엇을 말하는가.**
3
+ *
4
+ * 사람·애플리케이션·어플라이언스 세 종류의 주체가 모두 JWT 로 들어온다. 저장소의 시험 316개
5
+ * 중 발급을 부르는 것이 없었다. 여기서 고정하는 것은 「무엇이 실려 있나」와 「무엇이 실려
6
+ * 있지 않나」다. **없는 것 쪽이 더 중요하다** (`docs/design/auth-rebuild.md` §4):
7
+ *
8
+ * · `jti` 가 없다 → 발급한 토큰을 **가리킬 수 없다** = 폐기 수단이 없다.
9
+ * 로그아웃은 쿠키만 지우고, 복사된 토큰은 만료까지 살아 있다.
10
+ * · `kid` 가 없다 → 두 키를 동시에 신뢰할 수 없다 = 서명 키를 **교체할 수 없다**.
11
+ * · 사람 토큰에 도메인이 없다 → 도메인은 `x-things-factory-domain` 헤더가 정한다.
12
+ *
13
+ * 폐기·회전을 넣을 때 이 시험들이 먼저 깨져야 한다. 「`jti` 가 없다」가 깨지는 것이 곧
14
+ * 대장이 생겼다는 증거다.
15
+ */
16
+
17
+ import jwt from 'jsonwebtoken'
18
+
19
+ import { loadCompiled } from './compiled'
20
+
21
+ const { User } = loadCompiled('service/user/user')
22
+ const { Appliance } = loadCompiled('service/appliance/appliance')
23
+ const { Application } = loadCompiled('service/application/application')
24
+ const { SECRET } = loadCompiled('utils/get-secret')
25
+
26
+ const DAY = 24 * 60 * 60
27
+ const YEAR = 365.25 * DAY /* jsonwebtoken 의 `ms` 는 '1y' 를 365.25일로 읽는다 */
28
+
29
+ const DOMAIN = { id: 'domain-1', subdomain: 'plant-a' }
30
+ const APPLIANCE = { id: 'appliance-1', name: 'gate-scanner' }
31
+
32
+ function decode(token: string) {
33
+ return jwt.decode(token, { complete: true }) as any
34
+ }
35
+
36
+ function lifetime(payload: any) {
37
+ return payload.exp - payload.iat
38
+ }
39
+
40
+ function personToken(overrides: any = {}) {
41
+ const user = new User()
42
+ Object.assign(user, { id: 'user-1', username: 'kim', email: 'kim@example.com', ...overrides })
43
+ return user.sign()
44
+ }
45
+
46
+ describe('사람 토큰 (User.sign)', () => {
47
+ it('실려 있는 것은 username · email 과 표준 클레임뿐이다', async () => {
48
+ const { payload } = decode(await personToken())
49
+
50
+ expect(Object.keys(payload).sort()).toEqual(['email', 'exp', 'iat', 'iss', 'sub', 'username'])
51
+ expect(payload).toMatchObject({
52
+ username: 'kim',
53
+ email: 'kim@example.com',
54
+ iss: 'hatiolab.com',
55
+ sub: 'user'
56
+ })
57
+ })
58
+
59
+ it('username 이 없으면 email 을 username 자리에 넣는다', async () => {
60
+ const { payload } = decode(await personToken({ username: undefined }))
61
+
62
+ expect(payload.username).toBe('kim@example.com')
63
+ })
64
+
65
+ it('사용자 id 를 싣지 않는다 — 토큰은 username 으로 사람을 가리킨다', async () => {
66
+ const { payload } = decode(await personToken())
67
+
68
+ expect(payload.id).toBeUndefined()
69
+ })
70
+
71
+ it('기본 수명은 7일이다', async () => {
72
+ const { payload } = decode(await personToken())
73
+
74
+ expect(lifetime(payload)).toBe(7 * DAY)
75
+ })
76
+
77
+ it('넘긴 도메인은 버려진다 — 토큰에 도메인이 없다', async () => {
78
+ /* `signin` 은 `user.sign({ subdomain: domain?.subdomain })` 로 부르는데(signin.ts:95)
79
+ `sign` 은 `expiresIn` 만 꺼내 쓴다(user.ts:186). 넘긴 값이 조용히 사라진다. */
80
+ const user = new User()
81
+ Object.assign(user, { id: 'user-1', username: 'kim', email: 'kim@example.com' })
82
+
83
+ const { payload } = decode(await user.sign({ subdomain: 'plant-a' }))
84
+
85
+ expect(payload.subdomain).toBeUndefined()
86
+ expect(payload.domain).toBeUndefined()
87
+ })
88
+ })
89
+
90
+ describe('어플라이언스 토큰 (Appliance.generateAccessToken)', () => {
91
+ it('주체 종류와 어플라이언스 id, 도메인을 싣는다', () => {
92
+ const { payload } = decode(Appliance.generateAccessToken(DOMAIN, { id: 'appuser-1' }, APPLIANCE))
93
+
94
+ expect(payload).toMatchObject({
95
+ id: 'appuser-1',
96
+ userType: 'appliance',
97
+ appliance: { id: 'appliance-1' },
98
+ status: 'activated',
99
+ domain: { subdomain: 'plant-a' },
100
+ sub: 'access-token'
101
+ })
102
+ })
103
+
104
+ it('기본 수명이 1년이다', () => {
105
+ /* 설정 `applianceJwtExpiresIn` 이 없을 때의 값(appliance.ts:131).
106
+ 이 토큰은 DB 에 평문으로도 남는다(appliance-mutation.ts:64,70). */
107
+ const { payload } = decode(Appliance.generateAccessToken(DOMAIN, { id: 'appuser-1' }, APPLIANCE))
108
+
109
+ expect(lifetime(payload)).toBe(YEAR)
110
+ })
111
+ })
112
+
113
+ describe('애플리케이션 토큰 (Application)', () => {
114
+ const SCOPES = ['reader', 'approver']
115
+
116
+ it('access token 은 30일, refresh token 은 1년이다', () => {
117
+ const access = decode(Application.generateAccessToken(DOMAIN, { id: 'appuser-1' }, 'APPKEY', SCOPES))
118
+ const refresh = decode(Application.generateRefreshToken(DOMAIN, { id: 'appuser-1' }, 'APPKEY', SCOPES))
119
+
120
+ expect(lifetime(access.payload)).toBe(30 * DAY)
121
+ expect(lifetime(refresh.payload)).toBe(YEAR)
122
+ expect(access.payload.sub).toBe('access-token')
123
+ expect(refresh.payload.sub).toBe('refresh-token')
124
+ })
125
+
126
+ it('appKey 와 scope 를 싣는다 — scope 는 그 도메인의 역할 이름이다', () => {
127
+ /* `oauth2-server.ts:131` 이 `roles: scopes` 로 저장하고 `:240` 이 `name: In(scopes)` 로
128
+ 역할을 찾는다. 즉 scope 어휘가 따로 없고 역할 이름이 곧 scope 다 — 역할 이름을 바꾸면
129
+ 이미 발급된 토큰의 scope 가 조용히 뜻을 잃는다. */
130
+ const { payload } = decode(Application.generateAccessToken(DOMAIN, { id: 'appuser-1' }, 'APPKEY', SCOPES))
131
+
132
+ expect(payload).toMatchObject({
133
+ userType: 'application',
134
+ application: { appKey: 'APPKEY' },
135
+ scope: SCOPES
136
+ })
137
+ })
138
+
139
+ it('auth code 는 1분 살고 왕복한다', () => {
140
+ const code = Application.generateAuthCode('kim@example.com', 'APPKEY', 'plant-a', ['reader'], 'state-1')
141
+ const { payload } = decode(code)
142
+
143
+ expect(lifetime(payload)).toBe(60)
144
+ expect(Application.verifyAuthCode(code)).toMatchObject({
145
+ email: 'kim@example.com',
146
+ appKey: 'APPKEY',
147
+ subdomain: 'plant-a',
148
+ scopes: ['reader'],
149
+ state: 'state-1'
150
+ })
151
+ })
152
+
153
+ it('appKey 와 appSecret 은 16바이트 hex 다', () => {
154
+ expect(Application.generateAppKey()).toMatch(/^[0-9a-f]{32}$/)
155
+ expect(Application.generateAppSecret()).toMatch(/^[0-9a-f]{32}$/)
156
+ expect(Application.generateAppKey()).not.toBe(Application.generateAppKey())
157
+ })
158
+ })
159
+
160
+ describe('`iat` 은 초로 절삭된다 — 밀리초 시각과 견주면 새 토큰이 옛것으로 보인다', () => {
161
+ /*
162
+ * 폐기를 「무효 목록 없이」 하려는 설계가 반드시 밟는 자리다. 사람 행에는
163
+ * `passwordUpdatedAt`(밀리초를 가진 `Date`)이 있고, 토큰에는 `iat`(초)이 있다. 그래서
164
+ * `decoded.iat * 1000 < user.passwordUpdatedAt` 로 「그 시각 앞에 발급된 토큰을 끊는다」를
165
+ * 만들면, **비밀번호를 바꾼 그 사람의 새 토큰까지 끊긴다** — `change-pwd.ts` 가 같은 초에
166
+ * 칸을 쓰고(:81) 토큰을 발급하기(:99) 때문이다.
167
+ *
168
+ * 대장(발급 기록)을 두면 안 생기는 문제이므로, 이 시험은 고칠 대상이 아니라
169
+ * **설계가 이 사실을 알고 있어야 한다는 표시**다.
170
+ */
171
+ /*
172
+ * ⚠ 이 시험은 **시계에 기대고 있었고 6번에 1번쯤 빨갛게 났다** (2026-09-10).
173
+ *
174
+ * 30번을 7ms 간격으로 돌면서 「전부 옛것으로 보인다」를 단정했는데, 그 단정이 틀리는 순간이
175
+ * 둘 있었다. 둘 다 결함이 아니라 **관측이 성립하지 않은 회차**다.
176
+ *
177
+ * 밀리초가 정확히 000 절삭이 아무것도 안 깎는다 — 그 한 순간만 비교가 우연히 안전하다
178
+ * 두 줄 사이에 초가 넘어감 `passwordUpdatedAt` 과 토큰이 **다른 초**다 — 전제가 「같은 초」다
179
+ *
180
+ * 그래서 회차를 세는 대신 **관측이 성립한 회차만** 보고, 그런 회차가 하나도 없으면 조용히
181
+ * 초록이 되지 않게 따로 단정한다. 시계를 목으로 바꾸지 않는 이유는 재는 대상이 **발급기가
182
+ * 실제로 넣는 `iat`** 이기 때문이다.
183
+ */
184
+ it('같은 초에 찍은 밀리초 시각보다 새 토큰의 `iat` 이 앞선다', async () => {
185
+ const ROUNDS = 30
186
+ let observed = 0
187
+ let looksOlder = 0
188
+
189
+ for (let round = 0; round < ROUNDS; round++) {
190
+ /* change-pwd.ts 의 순서: 먼저 `passwordUpdatedAt` 을 쓰고, 그 다음 발급한다. */
191
+ const passwordUpdatedAt = new Date()
192
+ const { payload } = decode(await personToken())
193
+
194
+ const sameSecond = Math.floor(passwordUpdatedAt.getTime() / 1000) === payload.iat
195
+ const truncates = passwordUpdatedAt.getMilliseconds() !== 0
196
+
197
+ if (sameSecond && truncates) {
198
+ observed++
199
+ if (payload.iat * 1000 < passwordUpdatedAt.getTime()) looksOlder++
200
+ }
201
+
202
+ await new Promise(resolve => setTimeout(resolve, 7))
203
+ }
204
+
205
+ /* 성립한 회차가 없으면 위 단정이 「0 === 0」으로 통과한다 — 그것은 초록이 아니다. */
206
+ expect(observed).toBeGreaterThan(0)
207
+ expect(looksOlder).toBe(observed)
208
+ })
209
+ })
210
+
211
+ describe('세 종류의 토큰이 공유하는 것 — 그리고 없는 것', () => {
212
+ /** 사람·어플라이언스·애플리케이션·auth code 를 한 자리에 모아 같은 질문을 던진다. */
213
+ async function everyToken(): Promise<[string, string][]> {
214
+ return [
215
+ ['사람', await personToken()],
216
+ ['어플라이언스', Appliance.generateAccessToken(DOMAIN, { id: 'appuser-1' }, APPLIANCE)],
217
+ ['애플리케이션', Application.generateAccessToken(DOMAIN, { id: 'appuser-1' }, 'APPKEY', ['reader'])],
218
+ ['auth code', Application.generateAuthCode('kim@example.com', 'APPKEY', 'plant-a', ['reader'], 's')]
219
+ ]
220
+ }
221
+
222
+ it('어느 토큰에도 `jti` 가 없다 — 발급한 것을 가리킬 수 없으니 폐기할 수 없다', async () => {
223
+ for (const [kind, token] of await everyToken()) {
224
+ expect({ kind, jti: decode(token).payload.jti }).toEqual({ kind, jti: undefined })
225
+ }
226
+ })
227
+
228
+ it('어느 토큰에도 `kid` 가 없다 — 서명 키를 교체할 수 없다', async () => {
229
+ for (const [kind, token] of await everyToken()) {
230
+ expect({ kind, kid: decode(token).header.kid }).toEqual({ kind, kid: undefined })
231
+ }
232
+ })
233
+
234
+ it('모든 토큰이 같은 대칭키로 HS256 서명된다 — 검증하는 곳은 서명도 할 수 있다', async () => {
235
+ for (const [kind, token] of await everyToken()) {
236
+ expect({ kind, alg: decode(token).header.alg }).toEqual({ kind, alg: 'HS256' })
237
+ expect(() => jwt.verify(token, SECRET)).not.toThrow()
238
+ }
239
+ })
240
+ })
@@ -39,8 +39,168 @@
39
39
  "field.user_account": "user account",
40
40
  "field.user_type": "user type",
41
41
  "label.partner": "partner",
42
+ "privilege.allows.accounting.mutation": "post, correct and close vouchers",
43
+ "privilege.allows.accounting.query": "see vouchers and account balances",
44
+ "privilege.allows.activity-instance.mutation": "start, hand over, and finish or abort work",
45
+ "privilege.allows.activity-instance.query": "see running and finished work",
46
+ "privilege.allows.activity.mutation": "define and change activity types and their steps",
47
+ "privilege.allows.ai-assistant.mutation": "ask the assistant to do work",
48
+ "privilege.allows.ai-assistant.query": "see conversations with the assistant",
49
+ "privilege.allows.ai-inference.query": "see inference results from trained models",
50
+ "privilege.allows.attachment.mutation": "upload and delete files",
51
+ "privilege.allows.attachment.query": "open uploaded files",
52
+ "privilege.allows.bizplace.mutation": "register and change business places",
53
+ "privilege.allows.bizplace.query": "see business places and their addresses",
54
+ "privilege.allows.board-ai.mutation": "have ai build boards",
55
+ "privilege.allows.board-ai.query": "see boards drafted by ai",
56
+ "privilege.allows.board-import.mutation": "import boards from outside files",
57
+ "privilege.allows.board-import.query": "see the board import history",
58
+ "privilege.allows.board-template.mutation": "create and change board templates",
59
+ "privilege.allows.board-template.query": "see board templates",
60
+ "privilege.allows.board.mutation": "create, edit and delete boards",
61
+ "privilege.allows.board.query": "open boards",
62
+ "privilege.allows.company.mutation": "change company information",
63
+ "privilege.allows.company.query": "see company information",
64
+ "privilege.allows.connection.mutation": "create, change and drop connections — credentials live here",
65
+ "privilege.allows.connection.query": "see connections to other systems and their status",
66
+ "privilege.allows.contact_point.mutation": "register and change contact details",
67
+ "privilege.allows.contact_point.query": "see partner contact details",
68
+ "privilege.allows.data-archive.mutation": "move data to the archive and bring it back",
69
+ "privilege.allows.data-archive.query": "see archived data",
70
+ "privilege.allows.data-ooc.mutation": "act on out-of-control values and record the reason",
71
+ "privilege.allows.data-ooc.query": "see values outside the control limits",
72
+ "privilege.allows.data-sample.mutation": "register samples and enter measurements",
73
+ "privilege.allows.data-sample.query": "see samples and their measurements",
74
+ "privilege.allows.data-set-history.query": "see when and how a data set was changed",
75
+ "privilege.allows.data-set.mutation": "create data sets and change their fields",
76
+ "privilege.allows.data-set.query": "see data sets and how their fields are laid out",
77
+ "privilege.allows.data-summary.query": "see values summarized by period and field",
78
+ "privilege.allows.department.mutation": "create, change and move departments",
79
+ "privilege.allows.department.query": "see departments and who belongs to them",
80
+ "privilege.allows.disposition.mutation": "decide a disposition and send it for approval — scrap and rework are settled here",
81
+ "privilege.allows.disposition.query": "see how nonconforming items were dispositioned",
82
+ "privilege.allows.employee.mutation": "register employees and change their assignments",
83
+ "privilege.allows.employee.query": "see the employee roster and their assignments",
84
+ "privilege.allows.env-var.mutation": "add and change environment variables — this changes how the server runs",
85
+ "privilege.allows.env-var.query": "see environment variable names and values",
86
+ "privilege.allows.equipment-state.mutation": "change equipment state and record why it stopped",
87
+ "privilege.allows.equipment-state.query": "see whether equipment is running or stopped",
88
+ "privilege.allows.figure.mutation": "create and change figure models",
89
+ "privilege.allows.figure.query": "see figure models and how they are built",
90
+ "privilege.allows.inspection.mutation": "enter inspection results and judge them",
91
+ "privilege.allows.inspection.query": "see inspection specs and results",
92
+ "privilege.allows.job-order.mutation": "issue, change and close job orders",
93
+ "privilege.allows.job-order.query": "see job orders and how far along they are",
94
+ "privilege.allows.kpi.mutation": "create indicators and change their targets and formulas",
95
+ "privilege.allows.kpi.query": "see indicators and how they track against target",
96
+ "privilege.allows.label-studio.mutation": "set up the label studio connection and send work to it",
97
+ "privilege.allows.label-studio.query": "see work exchanged with label studio",
98
+ "privilege.allows.labeling.mutation": "apply and change labels",
99
+ "privilege.allows.labeling.query": "see labeling tasks and their results",
100
+ "privilege.allows.maintenance.mutation": "plan maintenance and report what was carried out",
101
+ "privilege.allows.maintenance.query": "see maintenance plans and what was carried out",
102
+ "privilege.allows.menu.mutation": "change the menu and its order — this changes what screens other people see",
103
+ "privilege.allows.mes-outbox.mutation": "set destinations and resend what failed",
104
+ "privilege.allows.mes-outbox.query": "see whether messages to outside systems are backing up",
105
+ "privilege.allows.mlflow.mutation": "set up the mlflow connection and register models",
106
+ "privilege.allows.mlflow.query": "see experiments and models recorded in mlflow",
107
+ "privilege.allows.mlops.mutation": "create pipelines and run training and deployment",
108
+ "privilege.allows.mlops.query": "see training and deployment pipeline runs",
109
+ "privilege.allows.ops-master.mutation": "change operating rules — scheduling follows these values",
110
+ "privilege.allows.ops-master.query": "see operating rules such as working and non-working days",
111
+ "privilege.allows.order.mutation": "take, change and cancel orders",
112
+ "privilege.allows.order.query": "see orders and their line items",
113
+ "privilege.allows.organization.mutation": "change the organization structure",
114
+ "privilege.allows.organization.query": "see the organization structure",
115
+ "privilege.allows.partner.mutation": "register partners and unlink them",
116
+ "privilege.allows.partner.query": "see partners and how their domains are linked",
117
+ "privilege.allows.privilege.query": "see the privileges declared in this deployment",
118
+ "privilege.allows.process-instance.query": "see how far a process run has got",
119
+ "privilege.allows.process.mutation": "create processes and change their steps",
120
+ "privilege.allows.process.query": "see process definitions and their steps",
121
+ "privilege.allows.production-report.mutation": "report production and issue corrections",
122
+ "privilege.allows.production-report.query": "see production reports and their corrections",
123
+ "privilege.allows.scenario.mutation": "create scenarios and start and stop them",
124
+ "privilege.allows.scenario.query": "see integration scenarios and their runs",
125
+ "privilege.allows.schedule.mutation": "build and move the schedule",
126
+ "privilege.allows.schedule.query": "see the production schedule",
127
+ "privilege.allows.security.mutation": "change security settings — this changes who can reach the system",
128
+ "privilege.allows.security.query": "see security settings such as address restrictions",
129
+ "privilege.allows.setting.mutation": "change system settings",
130
+ "privilege.allows.setting.query": "see system settings",
131
+ "privilege.allows.state-register.mutation": "change that record — the next run resumes from it",
132
+ "privilege.allows.state-register.query": "see how far an integration has processed",
133
+ "privilege.allows.station.mutation": "assign stations and send work directives to them",
134
+ "privilege.allows.station.query": "see stations and who is assigned to them",
135
+ "privilege.allows.terminology.mutation": "change the words shown on screen and their translations",
136
+ "privilege.allows.tutorial.mutation": "create and change tutorials",
137
+ "privilege.allows.twin.mutation": "create and change twins, and start and stop them",
138
+ "privilege.allows.twin.query": "see twins and how their sites are laid out",
139
+ "privilege.allows.user.mutation": "invite users, grant roles and lock accounts — who can do what is settled here",
140
+ "privilege.allows.user.query": "see the user list and each person’s roles",
141
+ "privilege.allows.webhook.mutation": "create webhooks and change where they post",
142
+ "privilege.allows.webhook.query": "see webhooks and what they have sent",
143
+ "privilege.allows.work-shift.mutation": "create and change shift timetables",
144
+ "privilege.allows.worker.mutation": "register workers and change their placement",
145
+ "privilege.allows.worker.query": "see workers and where they are placed",
146
+ "privilege.category.accounting": "accounting",
147
+ "privilege.category.activity": "work activity definition",
148
+ "privilege.category.activity-instance": "work in progress",
149
+ "privilege.category.ai-assistant": "ai assistant",
150
+ "privilege.category.ai-inference": "ai inference",
151
+ "privilege.category.attachment": "attachment",
152
+ "privilege.category.bizplace": "business place",
153
+ "privilege.category.board": "board",
154
+ "privilege.category.board-ai": "board ai",
155
+ "privilege.category.board-import": "board import",
156
+ "privilege.category.board-template": "board template",
157
+ "privilege.category.company": "company",
158
+ "privilege.category.connection": "system connection",
159
+ "privilege.category.contact_point": "contact point",
160
+ "privilege.category.data-archive": "data archive",
161
+ "privilege.category.data-ooc": "out-of-control data",
162
+ "privilege.category.data-sample": "data sample",
163
+ "privilege.category.data-set": "data set",
164
+ "privilege.category.data-set-history": "data set history",
165
+ "privilege.category.data-summary": "data summary",
166
+ "privilege.category.department": "department",
167
+ "privilege.category.disposition": "nonconformance disposition",
168
+ "privilege.category.employee": "employee",
169
+ "privilege.category.env-var": "environment variable",
170
+ "privilege.category.equipment-state": "equipment state",
171
+ "privilege.category.figure": "figure model",
172
+ "privilege.category.inspection": "inspection",
173
+ "privilege.category.job-order": "job order",
174
+ "privilege.category.kpi": "performance indicator",
175
+ "privilege.category.label-studio": "label studio connection",
176
+ "privilege.category.labeling": "data labeling",
177
+ "privilege.category.maintenance": "maintenance",
178
+ "privilege.category.menu": "menu",
179
+ "privilege.category.mes-outbox": "mes outbox",
180
+ "privilege.category.mlflow": "mlflow connection",
181
+ "privilege.category.mlops": "mlops",
182
+ "privilege.category.ops-master": "operations master data",
183
+ "privilege.category.order": "order",
184
+ "privilege.category.organization": "organization",
185
+ "privilege.category.partner": "partner",
186
+ "privilege.category.privilege": "privilege list",
187
+ "privilege.category.process": "process definition",
188
+ "privilege.category.process-instance": "process run",
189
+ "privilege.category.production-report": "production report",
190
+ "privilege.category.scenario": "integration scenario",
191
+ "privilege.category.schedule": "production schedule",
192
+ "privilege.category.security": "security",
193
+ "privilege.category.setting": "setting",
194
+ "privilege.category.state-register": "integration state register",
195
+ "privilege.category.station": "station",
42
196
  "privilege.category.system": "system setting",
43
- "privilege.description": "to {name} {category} data",
197
+ "privilege.category.terminology": "terminology",
198
+ "privilege.category.tutorial": "tutorial",
199
+ "privilege.category.twin": "digital twin",
200
+ "privilege.category.user": "user",
201
+ "privilege.category.webhook": "webhook",
202
+ "privilege.category.work-shift": "work shift",
203
+ "privilege.category.worker": "worker",
44
204
  "privilege.name.mutation": "edit",
45
205
  "privilege.name.query": "read",
46
206
  "text.account is reactivated": "account is reactivated",
@@ -39,8 +39,168 @@
39
39
  "field.user_account": "사용자 계정",
40
40
  "field.user_type": "사용자 유형",
41
41
  "label.partner": "파트너",
42
+ "privilege.allows.accounting.mutation": "전표를 올리고 고치고 마감할 수 있습니다.",
43
+ "privilege.allows.accounting.query": "전표와 계정 잔액을 볼 수 있습니다.",
44
+ "privilege.allows.activity-instance.mutation": "업무를 시작하고, 넘기고, 끝내거나 중단할 수 있습니다.",
45
+ "privilege.allows.activity-instance.query": "진행 중이거나 끝난 업무를 볼 수 있습니다.",
46
+ "privilege.allows.activity.mutation": "업무 활동의 종류와 절차를 만들고 고칠 수 있습니다.",
47
+ "privilege.allows.ai-assistant.mutation": "AI 도우미에게 일을 맡길 수 있습니다.",
48
+ "privilege.allows.ai-assistant.query": "AI 도우미와 주고받은 대화를 볼 수 있습니다.",
49
+ "privilege.allows.ai-inference.query": "학습된 모델이 낸 추론 결과를 볼 수 있습니다.",
50
+ "privilege.allows.attachment.mutation": "파일을 올리고 지울 수 있습니다.",
51
+ "privilege.allows.attachment.query": "올려 둔 파일을 열어 볼 수 있습니다.",
52
+ "privilege.allows.bizplace.mutation": "사업장을 등록하고 고칠 수 있습니다.",
53
+ "privilege.allows.bizplace.query": "사업장과 그 주소를 볼 수 있습니다.",
54
+ "privilege.allows.board-ai.mutation": "AI 에게 보드를 만들게 할 수 있습니다.",
55
+ "privilege.allows.board-ai.query": "AI 가 만든 보드 초안을 볼 수 있습니다.",
56
+ "privilege.allows.board-import.mutation": "바깥 파일에서 보드를 가져올 수 있습니다.",
57
+ "privilege.allows.board-import.query": "가져온 보드의 이력을 볼 수 있습니다.",
58
+ "privilege.allows.board-template.mutation": "보드 템플릿을 만들고 고칠 수 있습니다.",
59
+ "privilege.allows.board-template.query": "보드 템플릿을 볼 수 있습니다.",
60
+ "privilege.allows.board.mutation": "보드를 만들고 고치고 지울 수 있습니다.",
61
+ "privilege.allows.board.query": "보드를 열어 볼 수 있습니다.",
62
+ "privilege.allows.company.mutation": "회사 정보를 고칠 수 있습니다.",
63
+ "privilege.allows.company.query": "회사 정보를 볼 수 있습니다.",
64
+ "privilege.allows.connection.mutation": "연결을 만들고 고치고 끊을 수 있습니다. 접속 정보가 여기 들어갑니다.",
65
+ "privilege.allows.connection.query": "다른 시스템과의 연결과 그 상태를 볼 수 있습니다.",
66
+ "privilege.allows.contact_point.mutation": "연락처를 등록하고 고칠 수 있습니다.",
67
+ "privilege.allows.contact_point.query": "거래처 담당자 연락처를 볼 수 있습니다.",
68
+ "privilege.allows.data-archive.mutation": "데이터를 보관으로 넘기고 되살릴 수 있습니다.",
69
+ "privilege.allows.data-archive.query": "보관으로 넘긴 데이터를 볼 수 있습니다.",
70
+ "privilege.allows.data-ooc.mutation": "이탈로 판단한 값을 처리하고 그 사유를 남길 수 있습니다.",
71
+ "privilege.allows.data-ooc.query": "관리 한계를 벗어난 값을 볼 수 있습니다.",
72
+ "privilege.allows.data-sample.mutation": "표본을 등록하고 측정값을 넣을 수 있습니다.",
73
+ "privilege.allows.data-sample.query": "채취한 표본과 그 측정값을 볼 수 있습니다.",
74
+ "privilege.allows.data-set-history.query": "데이터셋 구성이 언제 어떻게 바뀌었는지 볼 수 있습니다.",
75
+ "privilege.allows.data-set.mutation": "데이터셋을 만들고 항목을 고칠 수 있습니다.",
76
+ "privilege.allows.data-set.query": "데이터셋과 그 항목 구성을 볼 수 있습니다.",
77
+ "privilege.allows.data-summary.query": "기간과 항목으로 집계한 값을 볼 수 있습니다.",
78
+ "privilege.allows.department.mutation": "부서를 만들고 고치고 옮길 수 있습니다.",
79
+ "privilege.allows.department.query": "부서와 그 소속을 볼 수 있습니다.",
80
+ "privilege.allows.disposition.mutation": "부적합 처리를 판단하고 승인을 올릴 수 있습니다. 폐기·재작업이 여기서 정해집니다.",
81
+ "privilege.allows.disposition.query": "부적합으로 잡힌 것을 어떻게 처리했는지 볼 수 있습니다.",
82
+ "privilege.allows.employee.mutation": "임직원을 등록하고 소속을 고칠 수 있습니다.",
83
+ "privilege.allows.employee.query": "임직원 명부와 소속을 볼 수 있습니다.",
84
+ "privilege.allows.env-var.mutation": "환경 변수를 더하고 고칠 수 있습니다. 서버 동작이 바뀝니다.",
85
+ "privilege.allows.env-var.query": "서버 환경 변수의 이름과 값을 볼 수 있습니다.",
86
+ "privilege.allows.equipment-state.mutation": "설비 상태를 바꾸고 멈춘 사유를 남길 수 있습니다.",
87
+ "privilege.allows.equipment-state.query": "설비가 돌고 있는지 멈췄는지 볼 수 있습니다.",
88
+ "privilege.allows.figure.mutation": "Figure 모델을 만들고 고칠 수 있습니다.",
89
+ "privilege.allows.figure.query": "Figure 모델과 그 구성을 볼 수 있습니다.",
90
+ "privilege.allows.inspection.mutation": "검사 결과를 올리고 합부를 판단할 수 있습니다.",
91
+ "privilege.allows.inspection.query": "검사 기준과 검사 결과를 볼 수 있습니다.",
92
+ "privilege.allows.job-order.mutation": "작업 지시를 내리고 고치고 닫을 수 있습니다.",
93
+ "privilege.allows.job-order.query": "작업 지시와 그 진행 상황을 볼 수 있습니다.",
94
+ "privilege.allows.kpi.mutation": "지표를 만들고 목표값과 계산식을 고칠 수 있습니다.",
95
+ "privilege.allows.kpi.query": "성과 지표와 그 목표 달성도를 볼 수 있습니다.",
96
+ "privilege.allows.label-studio.mutation": "Label Studio 연결을 설정하고 작업을 보낼 수 있습니다.",
97
+ "privilege.allows.label-studio.query": "Label Studio 와 주고받은 작업을 볼 수 있습니다.",
98
+ "privilege.allows.labeling.mutation": "라벨을 달고 고칠 수 있습니다.",
99
+ "privilege.allows.labeling.query": "라벨링 작업과 그 결과를 볼 수 있습니다.",
100
+ "privilege.allows.maintenance.mutation": "보전 계획을 세우고 실시 결과를 올릴 수 있습니다.",
101
+ "privilege.allows.maintenance.query": "보전 계획과 실시 이력을 볼 수 있습니다.",
102
+ "privilege.allows.menu.mutation": "메뉴 구성과 순서를 고칠 수 있습니다. 다른 사람이 보는 화면 목록이 바뀝니다.",
103
+ "privilege.allows.mes-outbox.mutation": "보낼 곳을 설정하고 실패한 것을 다시 보낼 수 있습니다.",
104
+ "privilege.allows.mes-outbox.query": "바깥 시스템으로 보낼 것이 밀려 있는지 볼 수 있습니다.",
105
+ "privilege.allows.mlflow.mutation": "MLflow 연결을 설정하고 모델을 올릴 수 있습니다.",
106
+ "privilege.allows.mlflow.query": "MLflow 에 기록된 실험과 모델을 볼 수 있습니다.",
107
+ "privilege.allows.mlops.mutation": "파이프라인을 만들고 학습과 배포를 돌릴 수 있습니다.",
108
+ "privilege.allows.mlops.query": "학습과 배포 파이프라인의 실행 이력을 볼 수 있습니다.",
109
+ "privilege.allows.ops-master.mutation": "운영 기준을 고칠 수 있습니다. 일정 계산이 이 값을 따릅니다.",
110
+ "privilege.allows.ops-master.query": "근무일과 비근무일 같은 운영 기준을 볼 수 있습니다.",
111
+ "privilege.allows.order.mutation": "주문을 받고 고치고 취소할 수 있습니다.",
112
+ "privilege.allows.order.query": "주문과 그 품목을 볼 수 있습니다.",
113
+ "privilege.allows.organization.mutation": "조직 구조를 고칠 수 있습니다.",
114
+ "privilege.allows.organization.query": "조직 구조를 볼 수 있습니다.",
115
+ "privilege.allows.partner.mutation": "협력사를 등록하고 연결을 끊을 수 있습니다.",
116
+ "privilege.allows.partner.query": "협력사와 그 도메인 연결을 볼 수 있습니다.",
117
+ "privilege.allows.privilege.query": "이 시스템에 선언된 권한 목록을 볼 수 있습니다.",
118
+ "privilege.allows.process-instance.query": "프로세스가 어디까지 돌았는지 볼 수 있습니다.",
119
+ "privilege.allows.process.mutation": "프로세스를 만들고 단계를 고칠 수 있습니다.",
120
+ "privilege.allows.process.query": "프로세스 정의와 그 단계를 볼 수 있습니다.",
121
+ "privilege.allows.production-report.mutation": "생산 실적을 올리고 정정을 낼 수 있습니다.",
122
+ "privilege.allows.production-report.query": "생산 실적과 그 정정 이력을 볼 수 있습니다.",
123
+ "privilege.allows.scenario.mutation": "시나리오를 만들고 돌리고 멈출 수 있습니다.",
124
+ "privilege.allows.scenario.query": "연동 시나리오와 그 실행 이력을 볼 수 있습니다.",
125
+ "privilege.allows.schedule.mutation": "일정을 짜고 옮길 수 있습니다.",
126
+ "privilege.allows.schedule.query": "생산 일정을 볼 수 있습니다.",
127
+ "privilege.allows.security.mutation": "보안 설정을 고칠 수 있습니다. 누가 접속할 수 있는지가 바뀝니다.",
128
+ "privilege.allows.security.query": "접속 제한 같은 보안 설정을 볼 수 있습니다.",
129
+ "privilege.allows.setting.mutation": "기본 설정값을 고칠 수 있습니다.",
130
+ "privilege.allows.setting.query": "시스템 기본 설정값을 볼 수 있습니다.",
131
+ "privilege.allows.state-register.mutation": "그 기록을 고칠 수 있습니다. 다음 연동이 여기서부터 다시 읽습니다.",
132
+ "privilege.allows.state-register.query": "연동이 어디까지 처리했는지 기록을 볼 수 있습니다.",
133
+ "privilege.allows.station.mutation": "작업 지점을 배정하고 작업 지시를 내려 보낼 수 있습니다.",
134
+ "privilege.allows.station.query": "작업 지점과 거기 배정된 사람을 볼 수 있습니다.",
135
+ "privilege.allows.terminology.mutation": "화면에 나오는 용어와 그 번역을 고칠 수 있습니다.",
136
+ "privilege.allows.tutorial.mutation": "따라 하기 안내를 만들고 고칠 수 있습니다.",
137
+ "privilege.allows.twin.mutation": "트윈을 만들고 고치고 돌리고 멈출 수 있습니다.",
138
+ "privilege.allows.twin.query": "트윈과 그 현장 구성을 볼 수 있습니다.",
139
+ "privilege.allows.user.mutation": "사용자를 초대하고 역할을 주고 잠글 수 있습니다. 누가 무엇을 할 수 있는지가 여기서 정해집니다.",
140
+ "privilege.allows.user.query": "사용자 명부와 각자의 역할을 볼 수 있습니다.",
141
+ "privilege.allows.webhook.mutation": "Webhook 을 만들고 보낼 주소를 고칠 수 있습니다.",
142
+ "privilege.allows.webhook.query": "Webhook 설정과 전송 이력을 볼 수 있습니다.",
143
+ "privilege.allows.work-shift.mutation": "교대 시간표를 만들고 고칠 수 있습니다.",
144
+ "privilege.allows.worker.mutation": "작업자를 등록하고 배치를 고칠 수 있습니다.",
145
+ "privilege.allows.worker.query": "작업자 명부와 배치를 볼 수 있습니다.",
146
+ "privilege.category.accounting": "회계",
147
+ "privilege.category.activity": "업무 활동 정의",
148
+ "privilege.category.activity-instance": "진행 중인 업무",
149
+ "privilege.category.ai-assistant": "AI 도우미",
150
+ "privilege.category.ai-inference": "AI 추론",
151
+ "privilege.category.attachment": "첨부파일",
152
+ "privilege.category.bizplace": "사업장",
153
+ "privilege.category.board": "보드",
154
+ "privilege.category.board-ai": "보드 AI",
155
+ "privilege.category.board-import": "보드 가져오기",
156
+ "privilege.category.board-template": "보드 템플릿",
157
+ "privilege.category.company": "회사",
158
+ "privilege.category.connection": "시스템 연결",
159
+ "privilege.category.contact_point": "연락처",
160
+ "privilege.category.data-archive": "데이터 보관",
161
+ "privilege.category.data-ooc": "관리 이탈 데이터",
162
+ "privilege.category.data-sample": "데이터 표본",
163
+ "privilege.category.data-set": "데이터셋",
164
+ "privilege.category.data-set-history": "데이터셋 변경 이력",
165
+ "privilege.category.data-summary": "데이터 집계",
166
+ "privilege.category.department": "부서",
167
+ "privilege.category.disposition": "부적합 처리",
168
+ "privilege.category.employee": "임직원",
169
+ "privilege.category.env-var": "환경 변수",
170
+ "privilege.category.equipment-state": "설비 상태",
171
+ "privilege.category.figure": "Figure 모델",
172
+ "privilege.category.inspection": "검사",
173
+ "privilege.category.job-order": "작업 지시",
174
+ "privilege.category.kpi": "성과 지표",
175
+ "privilege.category.label-studio": "Label Studio 연결",
176
+ "privilege.category.labeling": "데이터 라벨링",
177
+ "privilege.category.maintenance": "설비 보전",
178
+ "privilege.category.menu": "메뉴",
179
+ "privilege.category.mes-outbox": "MES 전송함",
180
+ "privilege.category.mlflow": "MLflow 연결",
181
+ "privilege.category.mlops": "MLOps",
182
+ "privilege.category.ops-master": "운영 기준정보",
183
+ "privilege.category.order": "주문",
184
+ "privilege.category.organization": "조직",
185
+ "privilege.category.partner": "협력사",
186
+ "privilege.category.privilege": "권한 목록",
187
+ "privilege.category.process": "프로세스 정의",
188
+ "privilege.category.process-instance": "프로세스 실행",
189
+ "privilege.category.production-report": "생산 실적",
190
+ "privilege.category.scenario": "연동 시나리오",
191
+ "privilege.category.schedule": "생산 일정",
192
+ "privilege.category.security": "보안",
193
+ "privilege.category.setting": "기본 설정",
194
+ "privilege.category.state-register": "연동 상태 레지스터",
195
+ "privilege.category.station": "작업 지점",
42
196
  "privilege.category.system": "시스템 설정",
43
- "privilege.description": "{category}를 {name}할 수 있음",
197
+ "privilege.category.terminology": "용어",
198
+ "privilege.category.tutorial": "따라 하기",
199
+ "privilege.category.twin": "디지털 트윈",
200
+ "privilege.category.user": "사용자",
201
+ "privilege.category.webhook": "Webhook",
202
+ "privilege.category.work-shift": "근무 교대",
203
+ "privilege.category.worker": "작업자",
44
204
  "privilege.name.mutation": "생성, 수정, 삭제",
45
205
  "privilege.name.query": "조회",
46
206
  "text.account is reactivated": "계정이 재 활성화되었습니다.",