@things-factory/auth-base 10.1.113 → 10.1.121

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/auth-base",
3
- "version": "10.1.113",
3
+ "version": "10.1.121",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -34,9 +34,9 @@
34
34
  "@reduxjs/toolkit": "^2.2.5",
35
35
  "@simplewebauthn/browser": "^13.0.0",
36
36
  "@simplewebauthn/server": "^13.0.0",
37
- "@things-factory/email-base": "^10.1.106",
37
+ "@things-factory/email-base": "^10.1.121",
38
38
  "@things-factory/env": "^10.1.20",
39
- "@things-factory/shell": "^10.1.106",
39
+ "@things-factory/shell": "^10.1.121",
40
40
  "@things-factory/utils": "^10.1.20",
41
41
  "@types/webappsec-credential-management": "^0.6.9",
42
42
  "jsonwebtoken": "^9.0.0",
@@ -48,5 +48,5 @@
48
48
  "passport-jwt": "^4.0.0",
49
49
  "passport-local": "^1.0.0"
50
50
  },
51
- "gitHead": "9da1e467ddce23e600e64f08454289cb3b806212"
51
+ "gitHead": "23536a73b10f103048a2c893d3960b2a4e0f03dd"
52
52
  }
@@ -10,6 +10,7 @@
10
10
  import { parse } from 'graphql'
11
11
 
12
12
  import { clearEntryTargets, declareEntryTarget } from '../../ai-assistant/server/service/data-entry/entry-target'
13
+ import { applyEntryProposal } from '../../ai-assistant/server/service/data-entry/entry-apply'
13
14
 
14
15
  import { closeAuthDatabase, Domain, getRepository, openAuthDatabase, resetAuthDatabase } from './db'
15
16
  import { loadCompiled } from './compiled'
@@ -22,6 +23,8 @@ const { Privilege } = loadCompiled('service/privilege/privilege')
22
23
  const { User } = loadCompiled('service/user/user')
23
24
  const access = loadCompiled('controllers/access-request')
24
25
  const targets = loadCompiled('ai-entry/access-entry-targets')
26
+ const { judgePermission } = loadCompiled('utils/check-permission')
27
+ const { AccessRequest } = loadCompiled('service/access-request/access-request')
25
28
 
26
29
  const rootFields = (document: string) =>
27
30
  ((parse(document).definitions[0] as any).selectionSet.selections as any[]).map(one => one.name.value)
@@ -31,6 +34,10 @@ const row = (values: Record<string, unknown>, index = 0) => ({ index, values, so
31
34
  describe('the declarations', () => {
32
35
  afterEach(() => clearEntryTargets())
33
36
 
37
+ it('the built public AUTH entry exports the same refusal formatter used by AI entry', () => {
38
+ expect(loadCompiled('index').privilegeRejection).toBe(loadCompiled('service/privilege/privilege-rejection').privilegeRejection)
39
+ })
40
+
34
41
  it('are accepted by the assistant as declared', () => {
35
42
  for (const target of targets.accessEntryTargets()) expect(() => declareEntryTarget(target)).not.toThrow()
36
43
  })
@@ -110,6 +117,35 @@ describe('the card says what the screen says', () => {
110
117
 
111
118
  const state = (user: any) => ({ domain, user })
112
119
 
120
+ it.each(['GRANT', 'DENY'])('a stored AI decision applies %s through the same controller and schedules the result after commit', async decision => {
121
+ const request = await access.requestAccess({ category: 'warehouse', privilege: 'mutation', reason: 'inbound' }, { state: { ...state(kim), afterCommit: [] } } as any)
122
+ const target = targets.accessDecisionEntryTarget()
123
+ const notify = jest.fn().mockResolvedValue(undefined)
124
+ const afterCommit: any[] = []
125
+ const context = { state: { ...state(park), notify, afterCommit }, t: (key: string) => key }
126
+ const execute = jest.fn(async (document, variables) => {
127
+ expect(document).toBe(target.mutation)
128
+ const decided = await access.decideAccessRequest(variables.decision, context)
129
+ return { data: { decideAccessRequest: decided } }
130
+ })
131
+ const proposal = { key: 'ai-decision', kind: 'entry', target: target.key, rows: [row({ request: { id: request.id }, decision, ...(decision === 'GRANT' ? { role: { id: inbound.id } } : { reason: 'IT only' }) })] }
132
+ const outcome = await applyEntryProposal(proposal.key, {
133
+ messages: [{ id: 'm', proposals: [proposal] }], answered: false, state: context.state,
134
+ passesDoor: async door => {
135
+ expect(door).toBe('Mutation.decideAccessRequest')
136
+ return (await judgePermission({ category: 'user', privilege: 'mutation', owner: true }, park, domain)).permitted
137
+ },
138
+ execute
139
+ }, key => key === target.key ? target : undefined)
140
+ expect(outcome).toMatchObject({ verdict: 'accepted', applied: 1, missed: 0 })
141
+ expect(execute).toHaveBeenCalledTimes(1)
142
+ expect(notify).not.toHaveBeenCalled()
143
+ for (const step of afterCommit) await step.run()
144
+ expect(notify).toHaveBeenCalledWith(expect.objectContaining({ receivers: [kim.id], url: 'my-access-requests', title: `access-request.notify.${decision === 'GRANT' ? 'granted' : 'denied'}.title` }))
145
+ expect((await getRepository(AccessRequest).findOneBy({ id: request.id })).state).toBe(decision === 'GRANT' ? 'GRANTED' : 'DENIED')
146
+ expect(await User.hasPrivilege('mutation', 'warehouse', domain, kim)).toBe(decision === 'GRANT')
147
+ })
148
+
113
149
  it('★ the card\'s findings are reviewDecision\'s, for grant and for deny', async () => {
114
150
  const request = await access.requestAccess({ category: 'warehouse', privilege: 'mutation', reason: 'inbound' }, { state: { ...state(kim), afterCommit: [] } } as any)
115
151
  const rows = [