@webiny/app-admin 6.5.0 → 6.5.1-beta.1

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 (76) hide show
  1. package/base/Base/DefaultFieldRenderers.js +4 -0
  2. package/base/Base/DefaultFieldRenderers.js.map +1 -1
  3. package/base/Base/FieldRenderers/BirthdayInputRenderer.d.ts +21 -0
  4. package/base/Base/FieldRenderers/BirthdayInputRenderer.js +39 -0
  5. package/base/Base/FieldRenderers/BirthdayInputRenderer.js.map +1 -0
  6. package/base/Base/FieldRenderers/BirthdayInputRenderer.test.d.ts +1 -0
  7. package/base/Base/FieldRenderers/BirthdayInputRenderer.test.js +123 -0
  8. package/base/Base/FieldRenderers/BirthdayInputRenderer.test.js.map +1 -0
  9. package/base/Base/UserMenu/SwitchTenant.d.ts +20 -0
  10. package/base/Base/UserMenu/SwitchTenant.js +45 -0
  11. package/base/Base/UserMenu/SwitchTenant.js.map +1 -0
  12. package/base/Base/UserMenu.js +4 -0
  13. package/base/Base/UserMenu.js.map +1 -1
  14. package/domain/Identity.d.ts +8 -0
  15. package/domain/Identity.js +6 -0
  16. package/domain/Identity.js.map +1 -1
  17. package/exports/admin/security.d.ts +4 -1
  18. package/exports/admin/security.js +4 -1
  19. package/features/formModel/FieldBuilder.test.js +126 -1
  20. package/features/formModel/FieldBuilder.test.js.map +1 -1
  21. package/features/formModel/fieldTypes/DateTimeFieldType.d.ts +65 -4
  22. package/features/formModel/fieldTypes/DateTimeFieldType.js +70 -8
  23. package/features/formModel/fieldTypes/DateTimeFieldType.js.map +1 -1
  24. package/features/formModel/fieldTypes/dateLimits.d.ts +37 -0
  25. package/features/formModel/fieldTypes/dateLimits.js +64 -0
  26. package/features/formModel/fieldTypes/dateLimits.js.map +1 -0
  27. package/features/formModel/fieldTypes/dateLimits.test.d.ts +1 -0
  28. package/features/formModel/fieldTypes/dateLimits.test.js +60 -0
  29. package/features/formModel/fieldTypes/dateLimits.test.js.map +1 -0
  30. package/features/formModel/renderers.d.ts +1 -0
  31. package/features/formModel/renderers.js +1 -0
  32. package/features/security/LogIn/LogInError.d.ts +15 -0
  33. package/features/security/LogIn/LogInError.js +25 -0
  34. package/features/security/LogIn/LogInError.js.map +1 -0
  35. package/features/security/LogIn/LogInGateway.d.ts +2 -2
  36. package/features/security/LogIn/LogInGateway.js +22 -5
  37. package/features/security/LogIn/LogInGateway.js.map +1 -1
  38. package/features/security/LogIn/LogInRepository.d.ts +1 -2
  39. package/features/security/LogIn/LogInRepository.js +6 -2
  40. package/features/security/LogIn/LogInRepository.js.map +1 -1
  41. package/features/security/LogIn/LogInUseCase.d.ts +7 -2
  42. package/features/security/LogIn/LogInUseCase.js +53 -3
  43. package/features/security/LogIn/LogInUseCase.js.map +1 -1
  44. package/features/security/LogIn/LogInUseCase.test.d.ts +1 -0
  45. package/features/security/LogIn/LogInUseCase.test.js +321 -0
  46. package/features/security/LogIn/LogInUseCase.test.js.map +1 -0
  47. package/features/security/LogIn/abstractions.d.ts +48 -3
  48. package/features/security/LogIn/abstractions.js.map +1 -1
  49. package/features/security/LogIn/createLoginMutation.js +40 -27
  50. package/features/security/LogIn/createLoginMutation.js.map +1 -1
  51. package/features/security/LogIn/feature.js +2 -0
  52. package/features/security/LogIn/feature.js.map +1 -1
  53. package/features/security/LogIn/index.d.ts +1 -0
  54. package/features/security/LogIn/index.js +1 -0
  55. package/features/security/LoginTenantResolver/LoginTenantResolver.d.ts +14 -0
  56. package/features/security/LoginTenantResolver/LoginTenantResolver.js +52 -0
  57. package/features/security/LoginTenantResolver/LoginTenantResolver.js.map +1 -0
  58. package/features/security/LoginTenantResolver/abstractions.d.ts +38 -0
  59. package/features/security/LoginTenantResolver/abstractions.js +5 -0
  60. package/features/security/LoginTenantResolver/abstractions.js.map +1 -0
  61. package/features/security/LoginTenantResolver/feature.d.ts +3 -0
  62. package/features/security/LoginTenantResolver/feature.js +17 -0
  63. package/features/security/LoginTenantResolver/feature.js.map +1 -0
  64. package/features/security/LoginTenantResolver/index.d.ts +2 -0
  65. package/features/security/LoginTenantResolver/index.js +2 -0
  66. package/package.json +24 -24
  67. package/presentation/security/components/LoginNoTenantAccess.d.ts +27 -0
  68. package/presentation/security/components/LoginNoTenantAccess.js +22 -0
  69. package/presentation/security/components/LoginNoTenantAccess.js.map +1 -0
  70. package/presentation/security/components/LoginTenantPicker.d.ts +30 -0
  71. package/presentation/security/components/LoginTenantPicker.js +32 -0
  72. package/presentation/security/components/LoginTenantPicker.js.map +1 -0
  73. package/presentation/security/hooks/useAuthentication.d.ts +2 -2
  74. package/presentation/security/hooks/useAuthentication.js.map +1 -1
  75. package/presentation/sortable/SortablePresenter.js +3 -2
  76. package/presentation/sortable/SortablePresenter.js.map +1 -1
@@ -0,0 +1,321 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { Container } from "@webiny/di";
3
+ import { MainGraphQLClient } from "@webiny/app/features/mainGraphQLClient/index.js";
4
+ import { LocalStorage } from "@webiny/app/features/localStorage/index.js";
5
+ import { LogInFeature } from "./feature.js";
6
+ import { AuthenticationContextFeature } from "../AuthenticationContext/feature.js";
7
+ import { IdentityContextFeature } from "../IdentityContext/feature.js";
8
+ import { TenancyFeature } from "../../tenancy/feature.js";
9
+ const createLocalStorage = (initial = {})=>{
10
+ const values = new Map(Object.entries(initial));
11
+ return {
12
+ get: (key)=>values.get(key),
13
+ set: (key, value)=>{
14
+ values.set(key, value);
15
+ },
16
+ remove: (key)=>{
17
+ values.delete(key);
18
+ },
19
+ clear: ()=>values.clear(),
20
+ keys: ()=>Array.from(values.keys())
21
+ };
22
+ };
23
+ const identityDto = {
24
+ __typename: "SecurityIdentity",
25
+ id: "user-1",
26
+ type: "admin",
27
+ displayName: "User 1",
28
+ roles: [],
29
+ teams: [],
30
+ permissions: [
31
+ {
32
+ name: "*"
33
+ }
34
+ ],
35
+ profile: {
36
+ firstName: "",
37
+ lastName: "",
38
+ email: "user-1@webiny.local"
39
+ },
40
+ currentTenant: {
41
+ id: "acme",
42
+ name: "Acme"
43
+ },
44
+ defaultTenant: {
45
+ id: "root",
46
+ name: "Root"
47
+ }
48
+ };
49
+ const loginResponse = (login)=>({
50
+ security: {
51
+ login: {
52
+ data: login.data ?? null,
53
+ error: login.error ?? null
54
+ }
55
+ }
56
+ });
57
+ const tenantSelectionRequired = (tenants)=>loginResponse({
58
+ data: {
59
+ __typename: "SecurityLoginTenantSelection",
60
+ tenants
61
+ }
62
+ });
63
+ describe("LogIn feature: tenant resolution", ()=>{
64
+ let container;
65
+ let execute;
66
+ let storage;
67
+ const setup = (storedTenant)=>{
68
+ container = new Container();
69
+ storage = createLocalStorage(storedTenant ? {
70
+ tenantId: storedTenant
71
+ } : {});
72
+ execute = vi.fn();
73
+ container.registerInstance(LocalStorage, storage);
74
+ container.registerInstance(MainGraphQLClient, {
75
+ execute
76
+ });
77
+ TenancyFeature.register(container);
78
+ AuthenticationContextFeature.register(container);
79
+ IdentityContextFeature.register(container);
80
+ LogInFeature.register(container);
81
+ };
82
+ const login = ()=>{
83
+ const { useCase } = LogInFeature.resolve(container);
84
+ return useCase.execute({
85
+ idTokenProvider: async ()=>"token"
86
+ });
87
+ };
88
+ const currentTenant = ()=>TenancyFeature.resolve(container).service.getCurrentTenant();
89
+ beforeEach(()=>{
90
+ setup();
91
+ });
92
+ it("logs in on the current tenant when the user has access to it", async ()=>{
93
+ setup("acme");
94
+ execute.mockResolvedValue(loginResponse({
95
+ data: identityDto
96
+ }));
97
+ const result = await login();
98
+ expect(result).toEqual({
99
+ type: "loggedIn"
100
+ });
101
+ expect(currentTenant()).toBe("acme");
102
+ expect(execute).toHaveBeenCalledTimes(1);
103
+ });
104
+ it("offers tenant switching only to a user with more than one tenant", async ()=>{
105
+ const identity = ()=>IdentityContextFeature.resolve(container).identityContext.getIdentity();
106
+ const tenants = [
107
+ {
108
+ id: "acme",
109
+ name: "Acme"
110
+ },
111
+ {
112
+ id: "beta",
113
+ name: "Beta"
114
+ }
115
+ ];
116
+ setup("acme");
117
+ execute.mockResolvedValue(loginResponse({
118
+ data: {
119
+ ...identityDto,
120
+ tenants
121
+ }
122
+ }));
123
+ await login();
124
+ expect(identity().tenants).toEqual(tenants);
125
+ expect(identity().canSwitchTenant).toBe(true);
126
+ setup("acme");
127
+ execute.mockResolvedValue(loginResponse({
128
+ data: {
129
+ ...identityDto,
130
+ tenants: [
131
+ tenants[0]
132
+ ]
133
+ }
134
+ }));
135
+ await login();
136
+ expect(identity().canSwitchTenant).toBe(false);
137
+ setup("acme");
138
+ execute.mockResolvedValue(loginResponse({
139
+ data: identityDto
140
+ }));
141
+ await login();
142
+ expect(identity().tenants).toEqual([]);
143
+ expect(identity().canSwitchTenant).toBe(false);
144
+ setup("root");
145
+ execute.mockResolvedValue(loginResponse({
146
+ data: {
147
+ ...identityDto,
148
+ tenants: [
149
+ {
150
+ id: "root",
151
+ name: "Root"
152
+ },
153
+ ...tenants
154
+ ]
155
+ }
156
+ }));
157
+ await login();
158
+ expect(identity().canSwitchTenant).toBe(false);
159
+ });
160
+ it("sends a root user to root instead of the tenant picker", async ()=>{
161
+ setup("deep");
162
+ execute.mockResolvedValue(tenantSelectionRequired([
163
+ {
164
+ id: "acme",
165
+ name: "Acme"
166
+ },
167
+ {
168
+ id: "root",
169
+ name: "Root"
170
+ }
171
+ ]));
172
+ const result = await login();
173
+ expect(result).toEqual({
174
+ type: "switchingTenant",
175
+ tenantId: "root"
176
+ });
177
+ expect(currentTenant()).toBe("root");
178
+ });
179
+ it("switches to the only tenant the user can access, and remembers it", async ()=>{
180
+ const onChange = vi.fn();
181
+ TenancyFeature.resolve(container).service.onTenantChange(onChange);
182
+ execute.mockResolvedValue(tenantSelectionRequired([
183
+ {
184
+ id: "acme",
185
+ name: "Acme"
186
+ }
187
+ ]));
188
+ const result = await login();
189
+ expect(result).toEqual({
190
+ type: "switchingTenant",
191
+ tenantId: "acme"
192
+ });
193
+ expect(currentTenant()).toBe("acme");
194
+ expect(storage.get("tenantId")).toBe("acme");
195
+ expect(onChange).toHaveBeenCalledWith("acme");
196
+ });
197
+ it("asks the user to pick when they can access several tenants", async ()=>{
198
+ const tenants = [
199
+ {
200
+ id: "acme",
201
+ name: "Acme"
202
+ },
203
+ {
204
+ id: "beta",
205
+ name: "Beta"
206
+ }
207
+ ];
208
+ execute.mockResolvedValue(tenantSelectionRequired(tenants));
209
+ const result = await login();
210
+ expect(result).toEqual({
211
+ type: "selectTenant",
212
+ tenants
213
+ });
214
+ expect(currentTenant()).toBe("root");
215
+ });
216
+ it("reports no access when the user has no tenant", async ()=>{
217
+ execute.mockResolvedValue(tenantSelectionRequired([]));
218
+ expect(await login()).toEqual({
219
+ type: "noTenantAccess"
220
+ });
221
+ execute.mockResolvedValue(loginResponse({
222
+ error: {
223
+ code: "Security/Identity/NotAuthorized",
224
+ message: "Missing user profile!"
225
+ }
226
+ }));
227
+ expect(await login()).toEqual({
228
+ type: "noTenantAccess"
229
+ });
230
+ expect(currentTenant()).toBe("root");
231
+ });
232
+ it("sends a user from a `?tenantId` they can't access to their own tenant", async ()=>{
233
+ setup("beta");
234
+ execute.mockResolvedValue(tenantSelectionRequired([
235
+ {
236
+ id: "acme",
237
+ name: "Acme"
238
+ }
239
+ ]));
240
+ const result = await login();
241
+ expect(result).toEqual({
242
+ type: "switchingTenant",
243
+ tenantId: "acme"
244
+ });
245
+ expect(currentTenant()).toBe("acme");
246
+ });
247
+ it("throws instead of switching to the tenant it's already on", async ()=>{
248
+ setup("acme");
249
+ const onChange = vi.fn();
250
+ TenancyFeature.resolve(container).service.onTenantChange(onChange);
251
+ execute.mockResolvedValue(tenantSelectionRequired([
252
+ {
253
+ id: "acme",
254
+ name: "Acme"
255
+ }
256
+ ]));
257
+ await expect(login()).rejects.toMatchObject({
258
+ code: "Security/Identity/TenantSelectionFailed"
259
+ });
260
+ expect(onChange).not.toHaveBeenCalled();
261
+ });
262
+ it.each([
263
+ [
264
+ "deleted",
265
+ {
266
+ message: "Unable to load tenant!",
267
+ code: "Tenancy/TenantNotFound"
268
+ }
269
+ ],
270
+ [
271
+ "disabled",
272
+ {
273
+ message: "Tenant is disabled!",
274
+ code: "Tenancy/TenantDisabled"
275
+ }
276
+ ]
277
+ ])("retries through root when the stored tenant is %s", async (_, requestError)=>{
278
+ setup("old-tenant");
279
+ execute.mockRejectedValue(requestError);
280
+ const result = await login();
281
+ expect(result).toEqual({
282
+ type: "switchingTenant",
283
+ tenantId: "root"
284
+ });
285
+ expect(currentTenant()).toBe("root");
286
+ });
287
+ it("doesn't loop when root itself can't be loaded", async ()=>{
288
+ setup("root");
289
+ execute.mockRejectedValue({
290
+ message: "Tenant is disabled!",
291
+ code: "Tenancy/TenantDisabled"
292
+ });
293
+ await expect(login()).rejects.toMatchObject({
294
+ code: "Tenancy/TenantDisabled"
295
+ });
296
+ expect(currentTenant()).toBe("root");
297
+ });
298
+ it("throws errors that aren't about the tenant, with their message", async ()=>{
299
+ execute.mockResolvedValue(loginResponse({
300
+ error: {
301
+ code: "SOMETHING",
302
+ message: "Something broke."
303
+ }
304
+ }));
305
+ await expect(login()).rejects.toMatchObject({
306
+ code: "SOMETHING",
307
+ message: "Something broke."
308
+ });
309
+ });
310
+ it("throws when the user has no permissions on the tenant", async ()=>{
311
+ execute.mockResolvedValue(loginResponse({
312
+ data: {
313
+ ...identityDto,
314
+ permissions: []
315
+ }
316
+ }));
317
+ await expect(login()).rejects.toThrow("You have no permissions on this tenant!");
318
+ });
319
+ });
320
+
321
+ //# sourceMappingURL=LogInUseCase.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/security/LogIn/LogInUseCase.test.js","sources":["../../../../src/features/security/LogIn/LogInUseCase.test.ts"],"sourcesContent":["import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { MainGraphQLClient } from \"@webiny/app/features/mainGraphQLClient/index.js\";\nimport { LocalStorage } from \"@webiny/app/features/localStorage/index.js\";\nimport { LogInFeature } from \"./feature.js\";\nimport { AuthenticationContextFeature } from \"~/features/security/AuthenticationContext/feature.js\";\nimport { IdentityContextFeature } from \"~/features/security/IdentityContext/feature.js\";\nimport { TenancyFeature } from \"~/features/tenancy/feature.js\";\n\nconst createLocalStorage = (initial: Record<string, any> = {}): LocalStorage.Interface => {\n const values = new Map<string, any>(Object.entries(initial));\n return {\n get: key => values.get(key),\n set: (key, value) => {\n values.set(key, value);\n },\n remove: key => {\n values.delete(key);\n },\n clear: () => values.clear(),\n keys: () => Array.from(values.keys())\n };\n};\n\nconst identityDto = {\n __typename: \"SecurityIdentity\",\n id: \"user-1\",\n type: \"admin\",\n displayName: \"User 1\",\n roles: [],\n teams: [],\n permissions: [{ name: \"*\" }],\n profile: { firstName: \"\", lastName: \"\", email: \"user-1@webiny.local\" },\n currentTenant: { id: \"acme\", name: \"Acme\" },\n defaultTenant: { id: \"root\", name: \"Root\" }\n};\n\nconst loginResponse = (login: { data?: any; error?: any }) => {\n return { security: { login: { data: login.data ?? null, error: login.error ?? null } } };\n};\n\nconst tenantSelectionRequired = (tenants: { id: string; name: string }[]) => {\n return loginResponse({ data: { __typename: \"SecurityLoginTenantSelection\", tenants } });\n};\n\ndescribe(\"LogIn feature: tenant resolution\", () => {\n let container: Container;\n let execute: ReturnType<typeof vi.fn>;\n let storage: LocalStorage.Interface;\n\n const setup = (storedTenant?: string) => {\n container = new Container();\n storage = createLocalStorage(storedTenant ? { tenantId: storedTenant } : {});\n execute = vi.fn();\n\n container.registerInstance(LocalStorage, storage);\n container.registerInstance(MainGraphQLClient, {\n execute\n } as unknown as MainGraphQLClient.Interface);\n\n TenancyFeature.register(container);\n AuthenticationContextFeature.register(container);\n IdentityContextFeature.register(container);\n LogInFeature.register(container);\n };\n\n const login = () => {\n const { useCase } = LogInFeature.resolve(container);\n return useCase.execute({ idTokenProvider: async () => \"token\" });\n };\n\n const currentTenant = () => TenancyFeature.resolve(container).service.getCurrentTenant();\n\n beforeEach(() => {\n setup();\n });\n\n it(\"logs in on the current tenant when the user has access to it\", async () => {\n setup(\"acme\");\n execute.mockResolvedValue(loginResponse({ data: identityDto }));\n\n const result = await login();\n\n expect(result).toEqual({ type: \"loggedIn\" });\n expect(currentTenant()).toBe(\"acme\");\n expect(execute).toHaveBeenCalledTimes(1);\n });\n\n it(\"offers tenant switching only to a user with more than one tenant\", async () => {\n const identity = () =>\n IdentityContextFeature.resolve(container).identityContext.getIdentity();\n const tenants = [\n { id: \"acme\", name: \"Acme\" },\n { id: \"beta\", name: \"Beta\" }\n ];\n\n setup(\"acme\");\n execute.mockResolvedValue(loginResponse({ data: { ...identityDto, tenants } }));\n await login();\n expect(identity().tenants).toEqual(tenants);\n expect(identity().canSwitchTenant).toBe(true);\n\n setup(\"acme\");\n execute.mockResolvedValue(\n loginResponse({ data: { ...identityDto, tenants: [tenants[0]] } })\n );\n await login();\n expect(identity().canSwitchTenant).toBe(false);\n\n // An API without the tenant list (before this change) offers no switching.\n setup(\"acme\");\n execute.mockResolvedValue(loginResponse({ data: identityDto }));\n await login();\n expect(identity().tenants).toEqual([]);\n expect(identity().canSwitchTenant).toBe(false);\n\n // Root users open tenants through Tenant Manager, even when they have records in several.\n setup(\"root\");\n execute.mockResolvedValue(\n loginResponse({\n data: { ...identityDto, tenants: [{ id: \"root\", name: \"Root\" }, ...tenants] }\n })\n );\n await login();\n expect(identity().canSwitchTenant).toBe(false);\n });\n\n it(\"sends a root user to root instead of the tenant picker\", async () => {\n // A tenant below a child of root: the parent lookup doesn't reach the root record.\n setup(\"deep\");\n execute.mockResolvedValue(\n tenantSelectionRequired([\n { id: \"acme\", name: \"Acme\" },\n { id: \"root\", name: \"Root\" }\n ])\n );\n\n const result = await login();\n\n expect(result).toEqual({ type: \"switchingTenant\", tenantId: \"root\" });\n expect(currentTenant()).toBe(\"root\");\n });\n\n it(\"switches to the only tenant the user can access, and remembers it\", async () => {\n const onChange = vi.fn();\n TenancyFeature.resolve(container).service.onTenantChange(onChange);\n execute.mockResolvedValue(tenantSelectionRequired([{ id: \"acme\", name: \"Acme\" }]));\n\n const result = await login();\n\n expect(result).toEqual({ type: \"switchingTenant\", tenantId: \"acme\" });\n expect(currentTenant()).toBe(\"acme\");\n expect(storage.get(\"tenantId\")).toBe(\"acme\");\n // The tenancy provider reloads the app on this change.\n expect(onChange).toHaveBeenCalledWith(\"acme\");\n });\n\n it(\"asks the user to pick when they can access several tenants\", async () => {\n const tenants = [\n { id: \"acme\", name: \"Acme\" },\n { id: \"beta\", name: \"Beta\" }\n ];\n execute.mockResolvedValue(tenantSelectionRequired(tenants));\n\n const result = await login();\n\n expect(result).toEqual({ type: \"selectTenant\", tenants });\n expect(currentTenant()).toBe(\"root\");\n });\n\n it(\"reports no access when the user has no tenant\", async () => {\n execute.mockResolvedValue(tenantSelectionRequired([]));\n expect(await login()).toEqual({ type: \"noTenantAccess\" });\n\n execute.mockResolvedValue(\n loginResponse({\n error: { code: \"Security/Identity/NotAuthorized\", message: \"Missing user profile!\" }\n })\n );\n expect(await login()).toEqual({ type: \"noTenantAccess\" });\n expect(currentTenant()).toBe(\"root\");\n });\n\n it(\"sends a user from a `?tenantId` they can't access to their own tenant\", async () => {\n // The tenancy provider has already stored the `?tenantId` tenant.\n setup(\"beta\");\n execute.mockResolvedValue(tenantSelectionRequired([{ id: \"acme\", name: \"Acme\" }]));\n\n const result = await login();\n\n expect(result).toEqual({ type: \"switchingTenant\", tenantId: \"acme\" });\n expect(currentTenant()).toBe(\"acme\");\n });\n\n it(\"throws instead of switching to the tenant it's already on\", async () => {\n setup(\"acme\");\n const onChange = vi.fn();\n TenancyFeature.resolve(container).service.onTenantChange(onChange);\n execute.mockResolvedValue(tenantSelectionRequired([{ id: \"acme\", name: \"Acme\" }]));\n\n await expect(login()).rejects.toMatchObject({\n code: \"Security/Identity/TenantSelectionFailed\"\n });\n expect(onChange).not.toHaveBeenCalled();\n });\n\n it.each([\n [\"deleted\", { message: \"Unable to load tenant!\", code: \"Tenancy/TenantNotFound\" }],\n [\"disabled\", { message: \"Tenant is disabled!\", code: \"Tenancy/TenantDisabled\" }]\n ])(\"retries through root when the stored tenant is %s\", async (_, requestError) => {\n setup(\"old-tenant\");\n execute.mockRejectedValue(requestError);\n\n const result = await login();\n\n expect(result).toEqual({ type: \"switchingTenant\", tenantId: \"root\" });\n expect(currentTenant()).toBe(\"root\");\n });\n\n it(\"doesn't loop when root itself can't be loaded\", async () => {\n setup(\"root\");\n execute.mockRejectedValue({\n message: \"Tenant is disabled!\",\n code: \"Tenancy/TenantDisabled\"\n });\n\n await expect(login()).rejects.toMatchObject({ code: \"Tenancy/TenantDisabled\" });\n expect(currentTenant()).toBe(\"root\");\n });\n\n it(\"throws errors that aren't about the tenant, with their message\", async () => {\n execute.mockResolvedValue(\n loginResponse({ error: { code: \"SOMETHING\", message: \"Something broke.\" } })\n );\n\n await expect(login()).rejects.toMatchObject({\n code: \"SOMETHING\",\n message: \"Something broke.\"\n });\n });\n\n it(\"throws when the user has no permissions on the tenant\", async () => {\n execute.mockResolvedValue(loginResponse({ data: { ...identityDto, permissions: [] } }));\n\n await expect(login()).rejects.toThrow(\"You have no permissions on this tenant!\");\n });\n});\n"],"names":["createLocalStorage","initial","values","Map","Object","key","value","Array","identityDto","loginResponse","login","tenantSelectionRequired","tenants","describe","container","execute","storage","setup","storedTenant","Container","vi","LocalStorage","MainGraphQLClient","TenancyFeature","AuthenticationContextFeature","IdentityContextFeature","LogInFeature","useCase","currentTenant","beforeEach","it","result","expect","identity","onChange","_","requestError"],"mappings":"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"}
@@ -5,13 +5,36 @@ export interface ILoginParams {
5
5
  idTokenProvider: AuthenticationContext.IdTokenProvider;
6
6
  logoutCallback?: AuthenticationContext.LogoutCallback;
7
7
  }
8
+ export type LogInResult =
9
+ /** The identity is set: the user is logged in. */
10
+ {
11
+ type: "loggedIn";
12
+ }
13
+ /** The current tenant was changed, and the app reloads to log in on it. */
14
+ | {
15
+ type: "switchingTenant";
16
+ tenantId: string;
17
+ }
18
+ /** The user has to pick one of these tenants. */
19
+ | {
20
+ type: "selectTenant";
21
+ tenants: Tenant[];
22
+ }
23
+ /** The user can't access any tenant. */
24
+ | {
25
+ type: "noTenantAccess";
26
+ };
8
27
  export interface ILogInUseCase {
9
- execute(params: ILoginParams): Promise<void>;
28
+ /**
29
+ * Log in on the current tenant. Errors that aren't about the tenant are thrown.
30
+ */
31
+ execute(params: ILoginParams): Promise<LogInResult>;
10
32
  }
11
33
  export declare const LogInUseCase: import("@webiny/di").Abstraction<ILogInUseCase>;
12
34
  export declare namespace LogInUseCase {
13
35
  type Interface = ILogInUseCase;
14
36
  type Params = ILoginParams;
37
+ type Result = LogInResult;
15
38
  }
16
39
  export interface IIdentityDTO {
17
40
  id: string;
@@ -22,22 +45,44 @@ export interface IIdentityDTO {
22
45
  permissions: Identity.Permission[];
23
46
  currentTenant: Tenant;
24
47
  defaultTenant: Tenant;
48
+ /** The enabled tenants in which the identity has a user record. */
49
+ tenants?: Tenant[];
25
50
  profile: Identity.Profile;
26
51
  }
52
+ /** What `security.login` returned: the identity, or the tenants the user has to log into instead. */
53
+ export type LogInStep<TIdentity> =
54
+ /** The user is logged in on the current tenant. */
55
+ {
56
+ step: "done";
57
+ identity: TIdentity;
58
+ }
59
+ /** The user has no access to the current tenant, but has access to these. */
60
+ | {
61
+ step: "selectTenant";
62
+ tenants: Tenant[];
63
+ };
27
64
  export interface ILogInRepository {
28
- login(): Promise<Identity>;
65
+ /**
66
+ * Log in on the current tenant. A failed login throws a `LogInError`.
67
+ */
68
+ login(): Promise<LogInStep<Identity>>;
29
69
  }
30
70
  export declare const LogInRepository: import("@webiny/di").Abstraction<ILogInRepository>;
31
71
  export declare namespace LogInRepository {
32
72
  type Interface = ILogInRepository;
33
73
  type IdentityDTO = IIdentityDTO;
74
+ type Result = LogInStep<Identity>;
34
75
  }
35
76
  export interface ILogInGateway {
36
- execute(): Promise<IIdentityDTO>;
77
+ /**
78
+ * Run `security.login`. An error in the response, or a failed request, throws a `LogInError`.
79
+ */
80
+ execute(): Promise<LogInStep<IIdentityDTO>>;
37
81
  }
38
82
  export declare const LogInGateway: import("@webiny/di").Abstraction<ILogInGateway>;
39
83
  export declare namespace LogInGateway {
40
84
  type Interface = ILogInGateway;
85
+ type Result = LogInStep<IIdentityDTO>;
41
86
  }
42
87
  export interface IIdentityMapper {
43
88
  toIdentity(dto: IIdentityDTO): Identity;
@@ -1 +1 @@
1
- {"version":3,"file":"features/security/LogIn/abstractions.js","sources":["../../../../src/features/security/LogIn/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport { AuthenticationContext } from \"~/features/security/AuthenticationContext/index.js\";\nimport type { Identity, IRole, ITeam } from \"~/domain/Identity.js\";\nimport type { Tenant } from \"~/features/tenancy/abstractions.js\";\n\n// Use Case\nexport interface ILoginParams {\n idTokenProvider: AuthenticationContext.IdTokenProvider;\n logoutCallback?: AuthenticationContext.LogoutCallback;\n}\n\nexport interface ILogInUseCase {\n execute(params: ILoginParams): Promise<void>;\n}\nexport const LogInUseCase = createAbstraction<ILogInUseCase>(\"LogInUseCase\");\n\nexport namespace LogInUseCase {\n export type Interface = ILogInUseCase;\n export type Params = ILoginParams;\n}\n\n// Repository\nexport interface IIdentityDTO {\n id: string;\n type: string;\n displayName: string;\n roles: IRole[];\n teams: ITeam[];\n permissions: Identity.Permission[];\n currentTenant: Tenant;\n defaultTenant: Tenant;\n profile: Identity.Profile;\n}\n\nexport interface ILogInRepository {\n login(): Promise<Identity>;\n}\n\nexport const LogInRepository = createAbstraction<ILogInRepository>(\"LogInRepository\");\n\nexport namespace LogInRepository {\n export type Interface = ILogInRepository;\n export type IdentityDTO = IIdentityDTO;\n}\n\n// Gateway\nexport interface ILogInGateway {\n execute(): Promise<IIdentityDTO>;\n}\n\nexport const LogInGateway = createAbstraction<ILogInGateway>(\"LogInGateway\");\n\nexport namespace LogInGateway {\n export type Interface = ILogInGateway;\n}\n\n// Identity Mapper\nexport interface IIdentityMapper {\n toIdentity(dto: IIdentityDTO): Identity;\n}\n\nexport const IdentityMapper = createAbstraction<IIdentityMapper>(\"IdentityMapper\");\n\nexport namespace IdentityMapper {\n export type Interface = IIdentityMapper;\n}\n"],"names":["LogInUseCase","createAbstraction","LogInRepository","LogInGateway","IdentityMapper"],"mappings":";AAcO,MAAMA,eAAeC,kBAAiC;AAwBtD,MAAMC,kBAAkBD,kBAAoC;AAY5D,MAAME,eAAeF,kBAAiC;AAWtD,MAAMG,iBAAiBH,kBAAmC"}
1
+ {"version":3,"file":"features/security/LogIn/abstractions.js","sources":["../../../../src/features/security/LogIn/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport { AuthenticationContext } from \"~/features/security/AuthenticationContext/index.js\";\nimport type { Identity, IRole, ITeam } from \"~/domain/Identity.js\";\nimport type { Tenant } from \"~/features/tenancy/abstractions.js\";\n\n// Use Case\nexport interface ILoginParams {\n idTokenProvider: AuthenticationContext.IdTokenProvider;\n logoutCallback?: AuthenticationContext.LogoutCallback;\n}\n\nexport type LogInResult =\n /** The identity is set: the user is logged in. */\n | { type: \"loggedIn\" }\n /** The current tenant was changed, and the app reloads to log in on it. */\n | { type: \"switchingTenant\"; tenantId: string }\n /** The user has to pick one of these tenants. */\n | { type: \"selectTenant\"; tenants: Tenant[] }\n /** The user can't access any tenant. */\n | { type: \"noTenantAccess\" };\n\nexport interface ILogInUseCase {\n /**\n * Log in on the current tenant. Errors that aren't about the tenant are thrown.\n */\n execute(params: ILoginParams): Promise<LogInResult>;\n}\nexport const LogInUseCase = createAbstraction<ILogInUseCase>(\"LogInUseCase\");\n\nexport namespace LogInUseCase {\n export type Interface = ILogInUseCase;\n export type Params = ILoginParams;\n export type Result = LogInResult;\n}\n\n// Repository\nexport interface IIdentityDTO {\n id: string;\n type: string;\n displayName: string;\n roles: IRole[];\n teams: ITeam[];\n permissions: Identity.Permission[];\n currentTenant: Tenant;\n defaultTenant: Tenant;\n /** The enabled tenants in which the identity has a user record. */\n tenants?: Tenant[];\n profile: Identity.Profile;\n}\n\n/** What `security.login` returned: the identity, or the tenants the user has to log into instead. */\nexport type LogInStep<TIdentity> =\n /** The user is logged in on the current tenant. */\n | { step: \"done\"; identity: TIdentity }\n /** The user has no access to the current tenant, but has access to these. */\n | { step: \"selectTenant\"; tenants: Tenant[] };\n\nexport interface ILogInRepository {\n /**\n * Log in on the current tenant. A failed login throws a `LogInError`.\n */\n login(): Promise<LogInStep<Identity>>;\n}\n\nexport const LogInRepository = createAbstraction<ILogInRepository>(\"LogInRepository\");\n\nexport namespace LogInRepository {\n export type Interface = ILogInRepository;\n export type IdentityDTO = IIdentityDTO;\n export type Result = LogInStep<Identity>;\n}\n\n// Gateway\nexport interface ILogInGateway {\n /**\n * Run `security.login`. An error in the response, or a failed request, throws a `LogInError`.\n */\n execute(): Promise<LogInStep<IIdentityDTO>>;\n}\n\nexport const LogInGateway = createAbstraction<ILogInGateway>(\"LogInGateway\");\n\nexport namespace LogInGateway {\n export type Interface = ILogInGateway;\n export type Result = LogInStep<IIdentityDTO>;\n}\n\n// Identity Mapper\nexport interface IIdentityMapper {\n toIdentity(dto: IIdentityDTO): Identity;\n}\n\nexport const IdentityMapper = createAbstraction<IIdentityMapper>(\"IdentityMapper\");\n\nexport namespace IdentityMapper {\n export type Interface = IIdentityMapper;\n}\n"],"names":["LogInUseCase","createAbstraction","LogInRepository","LogInGateway","IdentityMapper"],"mappings":";AA2BO,MAAMA,eAAeC,kBAAiC;AAqCtD,MAAMC,kBAAkBD,kBAAoC;AAgB5D,MAAME,eAAeF,kBAAiC;AAYtD,MAAMG,iBAAiBH,kBAAmC"}
@@ -3,36 +3,49 @@ const createLoginMutation = ()=>`
3
3
  security {
4
4
  login {
5
5
  data {
6
- id
7
- displayName
8
- type
9
- roles {
6
+ __typename
7
+ ... on SecurityIdentity {
10
8
  id
11
- slug
12
- name
9
+ displayName
10
+ type
11
+ roles {
12
+ id
13
+ slug
14
+ name
15
+ }
16
+ teams {
17
+ id
18
+ slug
19
+ name
20
+ }
21
+ profile {
22
+ firstName
23
+ lastName
24
+ email
25
+ avatar
26
+ external
27
+ createdOn
28
+ }
29
+ currentTenant {
30
+ id
31
+ name
32
+ }
33
+ defaultTenant {
34
+ id
35
+ name
36
+ }
37
+ tenants {
38
+ id
39
+ name
40
+ }
41
+ permissions
13
42
  }
14
- teams {
15
- id
16
- slug
17
- name
18
- }
19
- profile {
20
- firstName
21
- lastName
22
- email
23
- avatar
24
- external
25
- createdOn
26
- }
27
- currentTenant {
28
- id
29
- name
30
- }
31
- defaultTenant {
32
- id
33
- name
43
+ ... on SecurityLoginTenantSelection {
44
+ tenants {
45
+ id
46
+ name
47
+ }
34
48
  }
35
- permissions
36
49
  }
37
50
  error {
38
51
  code
@@ -1 +1 @@
1
- {"version":3,"file":"features/security/LogIn/createLoginMutation.js","sources":["../../../../src/features/security/LogIn/createLoginMutation.ts"],"sourcesContent":["export const createLoginMutation = () => {\n return /* GraphQL */ `\n mutation Login {\n security {\n login {\n data {\n id\n displayName\n type\n roles {\n id\n slug\n name\n }\n teams {\n id\n slug\n name\n }\n profile {\n firstName\n lastName\n email\n avatar\n external\n createdOn\n }\n currentTenant {\n id\n name\n }\n defaultTenant {\n id\n name\n }\n permissions\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n `;\n};\n"],"names":["createLoginMutation"],"mappings":"AAAO,MAAMA,sBAAsB,IACV,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4CtB,CAAC"}
1
+ {"version":3,"file":"features/security/LogIn/createLoginMutation.js","sources":["../../../../src/features/security/LogIn/createLoginMutation.ts"],"sourcesContent":["export const createLoginMutation = () => {\n return /* GraphQL */ `\n mutation Login {\n security {\n login {\n data {\n __typename\n ... on SecurityIdentity {\n id\n displayName\n type\n roles {\n id\n slug\n name\n }\n teams {\n id\n slug\n name\n }\n profile {\n firstName\n lastName\n email\n avatar\n external\n createdOn\n }\n currentTenant {\n id\n name\n }\n defaultTenant {\n id\n name\n }\n tenants {\n id\n name\n }\n permissions\n }\n ... on SecurityLoginTenantSelection {\n tenants {\n id\n name\n }\n }\n }\n error {\n code\n message\n data\n }\n }\n }\n }\n `;\n};\n"],"names":["createLoginMutation"],"mappings":"AAAO,MAAMA,sBAAsB,IACV,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAyDtB,CAAC"}
@@ -4,6 +4,7 @@ import { LogInUseCase as external_LogInUseCase_js_LogInUseCase } from "./LogInUs
4
4
  import { IdentityMapper } from "./IdentityMapper.js";
5
5
  import { LogInGateway } from "./LogInGateway.js";
6
6
  import { LogInRepository } from "./LogInRepository.js";
7
+ import { LoginTenantResolverFeature } from "../LoginTenantResolver/index.js";
7
8
  const LogInFeature = createFeature({
8
9
  name: "LogIn",
9
10
  register (container) {
@@ -11,6 +12,7 @@ const LogInFeature = createFeature({
11
12
  container.register(IdentityMapper).inSingletonScope();
12
13
  container.register(LogInGateway).inSingletonScope();
13
14
  container.register(LogInRepository).inSingletonScope();
15
+ LoginTenantResolverFeature.register(container);
14
16
  },
15
17
  resolve (container) {
16
18
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"features/security/LogIn/feature.js","sources":["../../../../src/features/security/LogIn/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/admin\";\nimport { LogInUseCase as UseCase } from \"./abstractions.js\";\nimport { LogInUseCase } from \"./LogInUseCase.js\";\nimport { IdentityMapper } from \"./IdentityMapper.js\";\nimport { LogInGateway } from \"./LogInGateway.js\";\nimport { LogInRepository } from \"./LogInRepository.js\";\n\nexport const LogInFeature = createFeature({\n name: \"LogIn\",\n register(container) {\n container.register(LogInUseCase);\n container.register(IdentityMapper).inSingletonScope();\n container.register(LogInGateway).inSingletonScope();\n container.register(LogInRepository).inSingletonScope();\n },\n resolve(container) {\n return {\n useCase: container.resolve(UseCase)\n };\n }\n});\n"],"names":["LogInFeature","createFeature","container","LogInUseCase","IdentityMapper","LogInGateway","LogInRepository","UseCase"],"mappings":";;;;;;AAOO,MAAMA,eAAeC,cAAc;IACtC,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC;QACnBD,UAAU,QAAQ,CAACE,gBAAgB,gBAAgB;QACnDF,UAAU,QAAQ,CAACG,cAAc,gBAAgB;QACjDH,UAAU,QAAQ,CAACI,iBAAiB,gBAAgB;IACxD;IACA,SAAQJ,SAAS;QACb,OAAO;YACH,SAASA,UAAU,OAAO,CAACK;QAC/B;IACJ;AACJ"}
1
+ {"version":3,"file":"features/security/LogIn/feature.js","sources":["../../../../src/features/security/LogIn/feature.ts"],"sourcesContent":["import { createFeature } from \"@webiny/feature/admin\";\nimport { LogInUseCase as UseCase } from \"./abstractions.js\";\nimport { LogInUseCase } from \"./LogInUseCase.js\";\nimport { IdentityMapper } from \"./IdentityMapper.js\";\nimport { LogInGateway } from \"./LogInGateway.js\";\nimport { LogInRepository } from \"./LogInRepository.js\";\nimport { LoginTenantResolverFeature } from \"~/features/security/LoginTenantResolver/index.js\";\n\nexport const LogInFeature = createFeature({\n name: \"LogIn\",\n register(container) {\n container.register(LogInUseCase);\n container.register(IdentityMapper).inSingletonScope();\n container.register(LogInGateway).inSingletonScope();\n container.register(LogInRepository).inSingletonScope();\n LoginTenantResolverFeature.register(container);\n },\n resolve(container) {\n return {\n useCase: container.resolve(UseCase)\n };\n }\n});\n"],"names":["LogInFeature","createFeature","container","LogInUseCase","IdentityMapper","LogInGateway","LogInRepository","LoginTenantResolverFeature","UseCase"],"mappings":";;;;;;;AAQO,MAAMA,eAAeC,cAAc;IACtC,MAAM;IACN,UAASC,SAAS;QACdA,UAAU,QAAQ,CAACC;QACnBD,UAAU,QAAQ,CAACE,gBAAgB,gBAAgB;QACnDF,UAAU,QAAQ,CAACG,cAAc,gBAAgB;QACjDH,UAAU,QAAQ,CAACI,iBAAiB,gBAAgB;QACpDC,2BAA2B,QAAQ,CAACL;IACxC;IACA,SAAQA,SAAS;QACb,OAAO;YACH,SAASA,UAAU,OAAO,CAACM;QAC/B;IACJ;AACJ"}
@@ -1 +1,2 @@
1
1
  export { LogInUseCase } from "./abstractions.js";
2
+ export { LogInError } from "./LogInError.js";
@@ -1 +1,2 @@
1
1
  export { LogInUseCase } from "./abstractions.js";
2
+ export { LogInError } from "./LogInError.js";
@@ -0,0 +1,14 @@
1
+ import { LoginTenantResolver as Abstraction } from "./abstractions.js";
2
+ import { TenantContext } from "../../../features/tenancy/abstractions.js";
3
+ import type { Tenant } from "../../../features/tenancy/abstractions.js";
4
+ import type { LogInError } from "../../../features/security/LogIn/index.js";
5
+ declare class LoginTenantResolverImpl implements Abstraction.Interface {
6
+ private tenantContext;
7
+ constructor(tenantContext: TenantContext.Interface);
8
+ resolveTenants(tenants: Tenant[]): Promise<Abstraction.Resolution>;
9
+ resolveError(error: LogInError): Promise<Abstraction.Resolution>;
10
+ }
11
+ export declare const LoginTenantResolver: typeof LoginTenantResolverImpl & {
12
+ __abstraction: import("@webiny/di").Abstraction<import("./abstractions.js").ILoginTenantResolver>;
13
+ };
14
+ export {};
@@ -0,0 +1,52 @@
1
+ import { LoginTenantResolver } from "./abstractions.js";
2
+ import { TenantContext } from "../../tenancy/abstractions.js";
3
+ const ROOT_TENANT = "root";
4
+ const UNAVAILABLE_TENANT_CODES = [
5
+ "Tenancy/TenantNotFound",
6
+ "Tenancy/TenantDisabled"
7
+ ];
8
+ class LoginTenantResolverImpl {
9
+ constructor(tenantContext){
10
+ this.tenantContext = tenantContext;
11
+ }
12
+ async resolveTenants(tenants) {
13
+ if (tenants.some((tenant)=>tenant.id === ROOT_TENANT)) return {
14
+ type: "switchTenant",
15
+ tenantId: ROOT_TENANT
16
+ };
17
+ if (1 === tenants.length) return {
18
+ type: "switchTenant",
19
+ tenantId: tenants[0].id
20
+ };
21
+ if (tenants.length > 1) return {
22
+ type: "selectTenant",
23
+ tenants
24
+ };
25
+ return {
26
+ type: "noTenantAccess"
27
+ };
28
+ }
29
+ async resolveError(error) {
30
+ if ("Security/Identity/NotAuthorized" === error.code) return {
31
+ type: "noTenantAccess"
32
+ };
33
+ if (UNAVAILABLE_TENANT_CODES.includes(error.code)) {
34
+ if (this.tenantContext.getCurrentTenant() !== ROOT_TENANT) return {
35
+ type: "switchTenant",
36
+ tenantId: ROOT_TENANT
37
+ };
38
+ }
39
+ return {
40
+ type: "unresolved"
41
+ };
42
+ }
43
+ }
44
+ const LoginTenantResolver_LoginTenantResolver = LoginTenantResolver.createImplementation({
45
+ implementation: LoginTenantResolverImpl,
46
+ dependencies: [
47
+ TenantContext
48
+ ]
49
+ });
50
+ export { LoginTenantResolver_LoginTenantResolver as LoginTenantResolver };
51
+
52
+ //# sourceMappingURL=LoginTenantResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features/security/LoginTenantResolver/LoginTenantResolver.js","sources":["../../../../src/features/security/LoginTenantResolver/LoginTenantResolver.ts"],"sourcesContent":["import { LoginTenantResolver as Abstraction } from \"./abstractions.js\";\nimport { TenantContext } from \"~/features/tenancy/abstractions.js\";\nimport type { Tenant } from \"~/features/tenancy/abstractions.js\";\nimport type { LogInError } from \"~/features/security/LogIn/index.js\";\n\nconst ROOT_TENANT = \"root\";\n\n// The API couldn't load the tenant from the `x-tenant` header: it was deleted or disabled.\nconst UNAVAILABLE_TENANT_CODES = [\"Tenancy/TenantNotFound\", \"Tenancy/TenantDisabled\"];\n\nclass LoginTenantResolverImpl implements Abstraction.Interface {\n constructor(private tenantContext: TenantContext.Interface) {}\n\n async resolveTenants(tenants: Tenant[]): Promise<Abstraction.Resolution> {\n // Root users never pick a tenant: they log in on root and open tenants through Tenant Manager.\n if (tenants.some(tenant => tenant.id === ROOT_TENANT)) {\n return { type: \"switchTenant\", tenantId: ROOT_TENANT };\n }\n\n if (tenants.length === 1) {\n return { type: \"switchTenant\", tenantId: tenants[0].id };\n }\n\n if (tenants.length > 1) {\n return { type: \"selectTenant\", tenants };\n }\n\n return { type: \"noTenantAccess\" };\n }\n\n async resolveError(error: LogInError): Promise<Abstraction.Resolution> {\n if (error.code === \"Security/Identity/NotAuthorized\") {\n return { type: \"noTenantAccess\" };\n }\n\n // A stale tenant (from localStorage or `?tenantId`) fails every request. Root always loads,\n // so this retries through root at most once.\n if (UNAVAILABLE_TENANT_CODES.includes(error.code)) {\n if (this.tenantContext.getCurrentTenant() !== ROOT_TENANT) {\n return { type: \"switchTenant\", tenantId: ROOT_TENANT };\n }\n }\n\n return { type: \"unresolved\" };\n }\n}\n\nexport const LoginTenantResolver = Abstraction.createImplementation({\n implementation: LoginTenantResolverImpl,\n dependencies: [TenantContext]\n});\n"],"names":["ROOT_TENANT","UNAVAILABLE_TENANT_CODES","LoginTenantResolverImpl","tenantContext","tenants","tenant","error","LoginTenantResolver","Abstraction","TenantContext"],"mappings":";;AAKA,MAAMA,cAAc;AAGpB,MAAMC,2BAA2B;IAAC;IAA0B;CAAyB;AAErF,MAAMC;IACF,YAAoBC,aAAsC,CAAE;aAAxCA,aAAa,GAAbA;IAAyC;IAE7D,MAAM,eAAeC,OAAiB,EAAmC;QAErE,IAAIA,QAAQ,IAAI,CAACC,CAAAA,SAAUA,OAAO,EAAE,KAAKL,cACrC,OAAO;YAAE,MAAM;YAAgB,UAAUA;QAAY;QAGzD,IAAII,AAAmB,MAAnBA,QAAQ,MAAM,EACd,OAAO;YAAE,MAAM;YAAgB,UAAUA,OAAO,CAAC,EAAE,CAAC,EAAE;QAAC;QAG3D,IAAIA,QAAQ,MAAM,GAAG,GACjB,OAAO;YAAE,MAAM;YAAgBA;QAAQ;QAG3C,OAAO;YAAE,MAAM;QAAiB;IACpC;IAEA,MAAM,aAAaE,KAAiB,EAAmC;QACnE,IAAIA,AAAe,sCAAfA,MAAM,IAAI,EACV,OAAO;YAAE,MAAM;QAAiB;QAKpC,IAAIL,yBAAyB,QAAQ,CAACK,MAAM,IAAI,GAC5C;YAAA,IAAI,IAAI,CAAC,aAAa,CAAC,gBAAgB,OAAON,aAC1C,OAAO;gBAAE,MAAM;gBAAgB,UAAUA;YAAY;QACzD;QAGJ,OAAO;YAAE,MAAM;QAAa;IAChC;AACJ;AAEO,MAAMO,0CAAsBC,oBAAAA,oBAAgC,CAAC;IAChE,gBAAgBN;IAChB,cAAc;QAACO;KAAc;AACjC"}