@antelopejs/dms 0.9.5 → 0.9.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.
- package/CHANGELOG.md +22 -0
- package/dist/implementations/dms/tenant-access.d.ts +2 -1
- package/dist/implementations/dms/tenant-access.js +11 -0
- package/dist/implementations/dms/tenant-access.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.js +12 -2
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/member-management.js +16 -6
- package/dist/pages/settings/users/member-management.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/composables/auth/usePermissions.ts +77 -73
- package/frontend-vue/layers/dms-onboarding/app/composables/onboarding/useOnboarding.ts +27 -23
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.9.6
|
|
4
|
+
|
|
5
|
+
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.9.5...v0.9.6)
|
|
6
|
+
|
|
7
|
+
### 🚀 Enhancements
|
|
8
|
+
|
|
9
|
+
- **tenant-lifecycle:** Pause producer admission and evaluate every tenant access gate ([#366](https://github.com/AntelopeJS/dms/pull/366))
|
|
10
|
+
|
|
11
|
+
### 🩹 Fixes
|
|
12
|
+
|
|
13
|
+
- **frontend:** Read the onboarding status and permissions through the data layer ([#357](https://github.com/AntelopeJS/dms/pull/357))
|
|
14
|
+
- **deps:** Require the tenant admission barrier and access evaluation contract ([#367](https://github.com/AntelopeJS/dms/pull/367))
|
|
15
|
+
|
|
16
|
+
### 🏡 Chore
|
|
17
|
+
|
|
18
|
+
- **release:** @antelopejs/interface-dms v0.7.4 ([9577327b](https://github.com/AntelopeJS/dms/commit/9577327b))
|
|
19
|
+
|
|
20
|
+
### ❤️ Contributors
|
|
21
|
+
|
|
22
|
+
- Antony Rizzitelli <rizzitelli.antony@pm.me>
|
|
23
|
+
- Alessandro Aloisio ([@alessaloisio](http://github.com/alessaloisio))
|
|
24
|
+
|
|
3
25
|
## v0.9.5
|
|
4
26
|
|
|
5
27
|
[compare changes](https://github.com/AntelopeJS/dms/compare/v0.9.3...v0.9.5)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TenantAccessGateInfo, TenantAccessResult } from "@antelopejs/interface-dms/tenant-access";
|
|
1
|
+
import type { TenantAccessDenial, TenantAccessGateInfo, TenantAccessResult } from "@antelopejs/interface-dms/tenant-access";
|
|
2
2
|
export declare namespace internal {
|
|
3
3
|
const RegisterTenantAccessGate: {
|
|
4
4
|
register: (info: TenantAccessGateInfo) => void;
|
|
@@ -6,3 +6,4 @@ export declare namespace internal {
|
|
|
6
6
|
};
|
|
7
7
|
}
|
|
8
8
|
export declare function CheckTenantAccess(userId: string | undefined, tenantId: string): Promise<TenantAccessResult>;
|
|
9
|
+
export declare function EvaluateTenantAccess(userId: string | undefined, tenantId: string): Promise<TenantAccessDenial[]>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.internal = void 0;
|
|
4
4
|
exports.CheckTenantAccess = CheckTenantAccess;
|
|
5
|
+
exports.EvaluateTenantAccess = EvaluateTenantAccess;
|
|
5
6
|
const gates = [];
|
|
6
7
|
var internal;
|
|
7
8
|
(function (internal) {
|
|
@@ -31,4 +32,14 @@ async function CheckTenantAccess(userId, tenantId) {
|
|
|
31
32
|
}
|
|
32
33
|
return { allowed: true };
|
|
33
34
|
}
|
|
35
|
+
async function EvaluateTenantAccess(userId, tenantId) {
|
|
36
|
+
const denials = [];
|
|
37
|
+
const registeredGates = gates.slice();
|
|
38
|
+
for (const { id, gate } of registeredGates) {
|
|
39
|
+
const result = await gate(userId, tenantId);
|
|
40
|
+
if (!result.allowed)
|
|
41
|
+
denials.push({ gateId: id, code: result.code });
|
|
42
|
+
}
|
|
43
|
+
return denials;
|
|
44
|
+
}
|
|
34
45
|
//# sourceMappingURL=tenant-access.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-access.js","sourceRoot":"","sources":["../../../src/implementations/dms/tenant-access.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"tenant-access.js","sourceRoot":"","sources":["../../../src/implementations/dms/tenant-access.ts"],"names":[],"mappings":";;;AA0BA,8CASC;AAED,oDAYC;AA3CD,MAAM,KAAK,GAA2B,EAAE,CAAC;AAEzC,IAAiB,QAAQ,CAgBxB;AAhBD,WAAiB,QAAQ;IACV,iCAAwB,GAAG;QACtC,QAAQ,EAAE,CAAC,IAA0B,EAAQ,EAAE;YAC7C,MAAM,aAAa,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/D,IAAI,aAAa,IAAI,CAAC,EAAE,CAAC;gBACvB,KAAK,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,UAAU,EAAE,CAAC,IAA0B,EAAQ,EAAE;YAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;YACvD,IAAI,KAAK,IAAI,CAAC;gBAAE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC,EAhBgB,QAAQ,wBAAR,QAAQ,QAgBxB;AAEM,KAAK,UAAU,iBAAiB,CACrC,MAA0B,EAC1B,QAAgB;IAEhB,KAAK,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,MAAM,CAAC;IACrC,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAEM,KAAK,UAAU,oBAAoB,CACxC,MAA0B,EAC1B,QAAgB;IAEhB,MAAM,OAAO,GAAyB,EAAE,CAAC;IAEzC,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;IACtC,KAAK,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,eAAe,EAAE,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -11,6 +11,7 @@ const db_1 = require("@antelopejs/interface-dms/db");
|
|
|
11
11
|
const edit_1 = require("@antelopejs/interface-dms/invite-extensions/internal/edit");
|
|
12
12
|
const request_tenant_1 = require("@antelopejs/interface-dms/request-tenant");
|
|
13
13
|
const base_1 = require("@antelopejs/interface-dms/base");
|
|
14
|
+
const tenant_lifecycle_1 = require("@antelopejs/interface-dms/tenant-lifecycle");
|
|
14
15
|
const HTTP_NOT_FOUND = 404;
|
|
15
16
|
const HTTP_CONFLICT = 409;
|
|
16
17
|
async function loadPendingInvite(tenantId, inviteId) {
|
|
@@ -32,6 +33,9 @@ exports.inviteGetRoute = {
|
|
|
32
33
|
},
|
|
33
34
|
};
|
|
34
35
|
async function editPendingInvite(tenantId, inviteId, body, writeRow) {
|
|
36
|
+
return (0, tenant_lifecycle_1.runTenantLifecycleOperation)(tenantId, () => editAdmittedPendingInvite(tenantId, inviteId, body, writeRow));
|
|
37
|
+
}
|
|
38
|
+
async function editAdmittedPendingInvite(tenantId, inviteId, body, writeRow) {
|
|
35
39
|
const edits = (0, edit_1.CollectInviteExtensionEdits)(body);
|
|
36
40
|
const invite = await loadPendingInvite(tenantId, inviteId);
|
|
37
41
|
await writeRow();
|
|
@@ -49,8 +53,14 @@ async function editPendingInvite(tenantId, inviteId, body, writeRow) {
|
|
|
49
53
|
const editInviteWithExtensions = {
|
|
50
54
|
...interface_data_api_1.DefaultRoutes.Edit,
|
|
51
55
|
func: function (ctx, params, body) {
|
|
52
|
-
return
|
|
56
|
+
return editAdmittedPendingInvite((0, request_tenant_1.getRequestTenantId)(ctx), String(params.id), components_1.Validation.ParseBody(body), () => interface_data_api_1.DefaultRoutes.Edit.func.call(this, ctx, params, body));
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const guardedInviteEditRoute = base_1.TableViewRoutes.EditWith(editInviteWithExtensions);
|
|
60
|
+
exports.inviteEditRoute = {
|
|
61
|
+
...guardedInviteEditRoute,
|
|
62
|
+
func(...args) {
|
|
63
|
+
return (0, tenant_lifecycle_1.runTenantLifecycleOperation)((0, request_tenant_1.getRequestTenantId)(args[0]), () => guardedInviteEditRoute.func.apply(this, args));
|
|
53
64
|
},
|
|
54
65
|
};
|
|
55
|
-
exports.inviteEditRoute = base_1.TableViewRoutes.EditWith(editInviteWithExtensions);
|
|
56
66
|
//# sourceMappingURL=invite-extension-routes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invite-extension-routes.js","sourceRoot":"","sources":["../../../../src/pages/settings/users/invite-extension-routes.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"invite-extension-routes.js","sourceRoot":"","sources":["../../../../src/pages/settings/users/invite-extension-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,8 @@ const interface_database_decorators_1 = require("@antelopejs/interface-database-
|
|
|
12
12
|
const db_1 = require("@antelopejs/interface-dms/db");
|
|
13
13
|
const tenant_ownership_1 = require("@antelopejs/interface-dms/tenant-ownership");
|
|
14
14
|
const db_2 = require("@antelopejs/interface-dms/auth/db");
|
|
15
|
+
const hooks_1 = require("@antelopejs/interface-dms/hooks");
|
|
16
|
+
const tenant_lifecycle_1 = require("@antelopejs/interface-dms/tenant-lifecycle");
|
|
15
17
|
const HTTP_NOT_FOUND = 404;
|
|
16
18
|
const HTTP_CONFLICT = 409;
|
|
17
19
|
const MEMBER_NOT_FOUND = "$page.settings.members.error.user_not_found";
|
|
@@ -28,6 +30,12 @@ async function assertNotLastTenantOwner(tenantId, current) {
|
|
|
28
30
|
(0, interface_api_util_1.assert)(remaining > 0, HTTP_CONFLICT, "$page.settings.members.error.last_owner");
|
|
29
31
|
}
|
|
30
32
|
async function prepareOwnerChange(tenantId, current, requested) {
|
|
33
|
+
await (0, hooks_1.ExecuteHooks)(hooks_1.Hook.MEMBER_BEING_CHANGED, {
|
|
34
|
+
tenantId,
|
|
35
|
+
userId: current.userId,
|
|
36
|
+
isTenantOwner: requested ?? current.isTenantOwner === true,
|
|
37
|
+
previousIsTenantOwner: current.isTenantOwner === true,
|
|
38
|
+
});
|
|
31
39
|
const change = detectOwnerChange(current, requested);
|
|
32
40
|
if (change === null)
|
|
33
41
|
return;
|
|
@@ -41,12 +49,14 @@ async function requireMember(tenantId, memberId) {
|
|
|
41
49
|
return member;
|
|
42
50
|
}
|
|
43
51
|
async function setMemberOwnership(tenantId, memberId, isTenantOwner) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
return (0, tenant_lifecycle_1.runTenantLifecycleOperation)(tenantId, async () => {
|
|
53
|
+
const member = await requireMember(tenantId, memberId);
|
|
54
|
+
const change = detectOwnerChange({ isTenantOwner: member.isTenantOwner === true }, isTenantOwner);
|
|
55
|
+
await prepareOwnerChange(tenantId, member, isTenantOwner);
|
|
56
|
+
member.isTenantOwner = isTenantOwner;
|
|
57
|
+
await (0, interface_database_decorators_1.GetModel)(db_1.TenantMemberModel, tenantId).update(member);
|
|
58
|
+
return { member, change };
|
|
59
|
+
});
|
|
50
60
|
}
|
|
51
61
|
async function roleNames(tenantId, roleIds) {
|
|
52
62
|
if (roleIds.length === 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"member-management.js","sourceRoot":"","sources":["../../../../src/pages/settings/users/member-management.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"member-management.js","sourceRoot":"","sources":["../../../../src/pages/settings/users/member-management.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,98 +1,102 @@
|
|
|
1
|
+
import { computed, effectScope } from "vue";
|
|
2
|
+
|
|
1
3
|
const PERMISSIONS_REFRESH_MS = 300_000;
|
|
2
4
|
const PERMISSIONS_ENDPOINT = "/dms/permissions";
|
|
3
5
|
const WILDCARD_PERMISSION = "*";
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
// page renders) is the one the page renders with: the browser starts from it
|
|
14
|
-
// rather than asking again, and its age keeps counting from the server's load.
|
|
15
|
-
// The generation only guards the runs in flight of one side.
|
|
16
|
-
const TRANSFERRED = { transfer: true };
|
|
7
|
+
/**
|
|
8
|
+
* The async data key of the permission set, in the account and workspace's
|
|
9
|
+
* scope like every key: a switch of either drops the set. The set a server
|
|
10
|
+
* render loads (the `auth` middleware's, preloaded as the app starts) is the
|
|
11
|
+
* one the page renders with, and the page hands it to the browser, which
|
|
12
|
+
* starts from it rather than asking again.
|
|
13
|
+
*/
|
|
14
|
+
const PERMISSIONS_KEY = "dms-permissions";
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
() => new Set(),
|
|
23
|
-
TRANSFERRED,
|
|
24
|
-
),
|
|
25
|
-
isLoaded: useDmsState<boolean>(
|
|
26
|
-
"dms-permissions-loaded",
|
|
27
|
-
() => false,
|
|
28
|
-
TRANSFERRED,
|
|
29
|
-
),
|
|
30
|
-
lastFetchTime: useDmsState<number>(
|
|
31
|
-
"dms-permissions-lastFetch",
|
|
32
|
-
() => 0,
|
|
33
|
-
TRANSFERRED,
|
|
34
|
-
),
|
|
35
|
-
fetchGeneration: useDmsState<number>("dms-permissions-gen", () => 0),
|
|
36
|
-
};
|
|
16
|
+
/** The permission set, and when it was read: its age counts from there. */
|
|
17
|
+
interface LoadedPermissions {
|
|
18
|
+
ids: string[];
|
|
19
|
+
loadedAt: number;
|
|
37
20
|
}
|
|
38
21
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const deduper = useTrailingDeduper("permissions");
|
|
54
|
-
const fetchPermissions = (): Promise<void> => deduper.run(runFetch);
|
|
55
|
-
// A caller that only needs a set has nothing to invalidate: it joins the
|
|
56
|
-
// load in flight instead of queueing a second one.
|
|
57
|
-
const loadPermissions = async (): Promise<void> => {
|
|
58
|
-
if (state.isLoaded.value) return;
|
|
59
|
-
await deduper.join(runFetch);
|
|
60
|
-
// The run it joined was started before a `clear()` (another account):
|
|
61
|
-
// its answer was dropped, and this caller still needs one.
|
|
62
|
-
if (!state.isLoaded.value) await deduper.join(runFetch);
|
|
63
|
-
};
|
|
64
|
-
return { fetchPermissions, loadPermissions };
|
|
22
|
+
/**
|
|
23
|
+
* The set's async data. Only the calls below start a read: a component that
|
|
24
|
+
* shows the set reads it through `useDmsData`, which never reads it again on
|
|
25
|
+
* a mount. Out of the page's refreshes: it has triggers of its own.
|
|
26
|
+
*/
|
|
27
|
+
function permissionsData(immediate = true) {
|
|
28
|
+
return useApiData<LoadedPermissions>(
|
|
29
|
+
PERMISSIONS_KEY,
|
|
30
|
+
async ({ $authFetch, signal }) => ({
|
|
31
|
+
ids: await $authFetch<string[]>(PERMISSIONS_ENDPOINT, { signal }),
|
|
32
|
+
loadedAt: Date.now(),
|
|
33
|
+
}),
|
|
34
|
+
{ immediate, refreshOnPageRefresh: false },
|
|
35
|
+
);
|
|
65
36
|
}
|
|
66
37
|
|
|
67
38
|
export function usePermissions() {
|
|
68
|
-
const
|
|
69
|
-
const
|
|
39
|
+
const loaded = useDmsData<LoadedPermissions>(PERMISSIONS_KEY).data;
|
|
40
|
+
const permissions = computed(() => new Set(loaded.value?.ids ?? []));
|
|
41
|
+
const isLoaded = computed(() => loaded.value != null);
|
|
70
42
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
43
|
+
// A caller that only needs a set joins the load in flight instead of
|
|
44
|
+
// starting another: one request per page however many ask.
|
|
45
|
+
async function loadPermissions(): Promise<void> {
|
|
46
|
+
// The load it joined may be cleared meanwhile (another account): its
|
|
47
|
+
// answer is dropped, and this caller still needs one.
|
|
48
|
+
for (let attempt = 0; attempt < 2 && !loaded.value; attempt++) {
|
|
49
|
+
const result = await permissionsData();
|
|
50
|
+
if (!result.data.value && result.error.value) throw result.error.value;
|
|
75
51
|
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// A refresh (the set changed on the backend) asked while a read is in
|
|
55
|
+
// flight replaces it rather than taking its older answer, so an
|
|
56
|
+
// invalidation is never swallowed. A failed one keeps the last set, and
|
|
57
|
+
// rejects so the caller knows it is stale.
|
|
58
|
+
async function fetchPermissions(): Promise<void> {
|
|
59
|
+
if (loaded.value) await refreshLoadedSet();
|
|
60
|
+
else await refreshDmsData(PERMISSIONS_KEY);
|
|
61
|
+
await loadPermissions();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// `refreshDmsData` does not say whether the read failed: the set's own
|
|
65
|
+
// async data does. Its binding lives in a scope stopped once the read
|
|
66
|
+
// settled, so refreshes do not pile bindings up; the load that brought
|
|
67
|
+
// the set keeps the key bound, so stopping it never aborts nor evicts.
|
|
68
|
+
async function refreshLoadedSet(): Promise<void> {
|
|
69
|
+
const scope = effectScope(true);
|
|
70
|
+
const data = scope.run(() => permissionsData(false))!;
|
|
71
|
+
try {
|
|
72
|
+
await data.refresh();
|
|
73
|
+
} finally {
|
|
74
|
+
scope.stop();
|
|
75
|
+
}
|
|
76
|
+
// A later refresh that replaced this one reports its own failure.
|
|
77
|
+
if (data.status.value === "error") throw data.error.value;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function refreshIfStale() {
|
|
81
|
+
const set = loaded.value;
|
|
82
|
+
if (set && Date.now() - set.loadedAt < PERMISSIONS_REFRESH_MS) return;
|
|
76
83
|
await fetchPermissions();
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
function hasPermission(permissionId: string): boolean {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
return state.permissions.value.has(permissionId);
|
|
87
|
+
const set = permissions.value;
|
|
88
|
+
return set.has(WILDCARD_PERMISSION) || set.has(permissionId);
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
// Empties the set and drops the answer of a read in flight, so a logout or
|
|
92
|
+
// an account switch is never overwritten by the previous account's set.
|
|
86
93
|
function clear() {
|
|
87
|
-
|
|
88
|
-
state.permissions.value = new Set();
|
|
89
|
-
state.isLoaded.value = false;
|
|
90
|
-
state.lastFetchTime.value = 0;
|
|
94
|
+
permissionsData(false).clear();
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
return {
|
|
94
|
-
permissions
|
|
95
|
-
isLoaded
|
|
98
|
+
permissions,
|
|
99
|
+
isLoaded,
|
|
96
100
|
fetchPermissions,
|
|
97
101
|
loadPermissions,
|
|
98
102
|
refreshIfStale,
|
|
@@ -7,37 +7,41 @@ interface OnboardingInfoResponse {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* asking again. It
|
|
10
|
+
* The async data key of the onboarding status. The global middleware reads
|
|
11
|
+
* it while a server renders a page, and the page hands it to the browser,
|
|
12
|
+
* whose run of the middleware then finds it instead of asking again. It
|
|
13
|
+
* holds nothing private.
|
|
13
14
|
*/
|
|
14
|
-
|
|
15
|
-
return useDmsState<OnboardingInfo | null>("dms-onboarding-info", () => null, {
|
|
16
|
-
transfer: true,
|
|
17
|
-
});
|
|
18
|
-
}
|
|
15
|
+
const ONBOARDING_KEY = "dms-onboarding";
|
|
19
16
|
|
|
20
17
|
export const useOnboarding = async (): Promise<OnboardingInfo> => {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const known = cache.value;
|
|
18
|
+
const known = useDmsData<OnboardingInfo>(ONBOARDING_KEY).data.value;
|
|
24
19
|
if (known) return known;
|
|
25
20
|
|
|
26
|
-
const { $authFetch } = useAuthFetch();
|
|
27
|
-
|
|
28
21
|
// One request per app however many callers ask before it answers: the
|
|
29
|
-
// preload started with the app and the `onboarding` middleware share
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
// preload started with the app and the `onboarding` middleware share the
|
|
23
|
+
// key. Its own triggers only: the status changes once, when the first
|
|
24
|
+
// administrator registers (`setOnboardingComplete`).
|
|
25
|
+
const status = await useApiData<OnboardingInfo>(
|
|
26
|
+
ONBOARDING_KEY,
|
|
27
|
+
async ({ $authFetch, signal }) => {
|
|
28
|
+
const response = await $authFetch<OnboardingInfoResponse>(
|
|
29
|
+
"/api/onboarding/informations",
|
|
30
|
+
{ method: "GET", signal },
|
|
31
|
+
);
|
|
32
|
+
return { hasOnboarded: response.has_onboarded };
|
|
33
|
+
},
|
|
34
|
+
{ refreshOnPageRefresh: false },
|
|
35
|
+
);
|
|
36
|
+
// A failed read leaves the status unknown: the middleware lets the
|
|
37
|
+
// navigation through, and the next one asks again.
|
|
38
|
+
if (!status.data.value) throw status.error.value;
|
|
39
|
+
return status.data.value;
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
/** Release the cached onboarding guard after the backend creates the admin. */
|
|
41
43
|
export const setOnboardingComplete = () => {
|
|
42
|
-
|
|
44
|
+
useDmsData<OnboardingInfo>(ONBOARDING_KEY).data.value = {
|
|
45
|
+
hasOnboarded: true,
|
|
46
|
+
};
|
|
43
47
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.6",
|
|
4
4
|
"description": "The AntelopeJS dashboard management system: admin dashboards declared in TypeScript, with auth and tenancy, data management, file uploads, notifications and realtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"antelopejs",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@antelopejs/interface-data-api": ">=0.2.2 <1.0.0",
|
|
61
61
|
"@antelopejs/interface-database": ">=0.1.10 <1.0.0",
|
|
62
62
|
"@antelopejs/interface-database-decorators": ">=0.1.9 <1.0.0",
|
|
63
|
-
"@antelopejs/interface-dms": ">=0.7.
|
|
63
|
+
"@antelopejs/interface-dms": ">=0.7.4 <0.8.0",
|
|
64
64
|
"@antelopejs/interface-email": ">=0.0.6 <1.0.0",
|
|
65
65
|
"@antelopejs/interface-file-storage": ">=0.1.4 <1.0.0",
|
|
66
66
|
"archiver": "^7.0.1",
|