@adonis-agora/authkit-server 1.0.0-rc.1 → 1.0.0-rc.10
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 +94 -0
- package/README.md +4 -5
- package/build/commands/agora_doctor.d.ts +22 -0
- package/build/commands/agora_doctor.js +109 -0
- package/build/commands/clients_create.d.ts +4 -0
- package/build/commands/clients_create.js +97 -0
- package/build/commands/commands.json +78 -0
- package/build/commands/doctor.js +8 -74
- package/build/host/views/account/apps.edge +1 -5
- package/build/host/views/account/extension.edge +77 -0
- package/build/host/views/account/mfa.edge +2 -0
- package/build/host/views/account/partials/extension_actions.edge +14 -0
- package/build/host/views/account/partials/extension_block.edge +197 -0
- package/build/host/views/account/partials/extension_value.edge +8 -0
- package/build/host/views/account/partials/nav.edge +14 -0
- package/build/host/views/account/security.edge +1 -5
- package/build/host/views/account/tokens.edge +2 -2
- package/build/host/views/agents/consent.edge +11 -2
- package/build/host/views/agents/done.edge +1 -1
- package/build/host/views/partials/styles.edge +1 -1
- package/build/index.d.ts +12 -12
- package/build/index.js +7 -9
- package/build/providers/authkit_server_provider.js +28 -8
- package/build/src/account/extension.d.ts +10 -0
- package/build/src/account/extension.js +9 -0
- package/build/src/account/http.d.ts +77 -0
- package/build/src/account/http.js +381 -0
- package/build/src/account/mount.d.ts +40 -0
- package/build/src/account/mount.js +171 -0
- package/build/src/account/registry.d.ts +41 -0
- package/build/src/account/registry.js +81 -0
- package/build/src/account/remote.d.ts +72 -0
- package/build/src/account/remote.js +406 -0
- package/build/src/account/runtime.d.ts +289 -0
- package/build/src/account/runtime.js +641 -0
- package/build/src/accounts/account_store.d.ts +6 -0
- package/build/src/accounts/lucid_store/organizations.js +6 -0
- package/build/src/agents/config.d.ts +9 -13
- package/build/src/agents/config.js +17 -11
- package/build/src/agents/delegation_service.js +3 -3
- package/build/src/agents/delegation_store.d.ts +3 -2
- package/build/src/agents/delegation_store.js +6 -5
- package/build/src/agents/extension.d.ts +261 -0
- package/build/src/agents/extension.js +40 -0
- package/build/src/agents/middleware.js +1 -1
- package/build/src/agents/protocol.d.ts +4 -4
- package/build/src/agents/registry_store.d.ts +3 -2
- package/build/src/agents/registry_store.js +1 -1
- package/build/src/agents/runtime.d.ts +26 -7
- package/build/src/agents/runtime.js +110 -75
- package/build/src/audit/audit_sink.d.ts +1 -1
- package/build/src/audit/audit_sink.js +10 -1
- package/build/src/console_switcher.d.ts +132 -0
- package/build/src/console_switcher.js +206 -0
- package/build/src/define_config.d.ts +34 -12
- package/build/src/define_config.js +15 -7
- package/build/src/dev_tools/contract.d.ts +72 -0
- package/build/src/dev_tools/contract.js +10 -0
- package/build/src/dev_tools/gate.d.ts +17 -0
- package/build/src/dev_tools/gate.js +69 -0
- package/build/src/dev_tools/register.d.ts +11 -0
- package/build/src/dev_tools/register.js +25 -0
- package/build/src/dev_tools/tools.d.ts +24 -0
- package/build/src/dev_tools/tools.js +384 -0
- package/build/src/doctor/agora_checks.d.ts +15 -0
- package/build/src/doctor/agora_checks.js +150 -0
- package/build/src/doctor/agora_doctor.d.ts +115 -0
- package/build/src/doctor/agora_doctor.js +692 -0
- package/build/src/doctor/doctor_input.d.ts +27 -0
- package/build/src/doctor/doctor_input.js +86 -0
- package/build/src/doctor/index.d.ts +5 -0
- package/build/src/doctor/index.js +4 -0
- package/build/src/host/access_token_verifier.js +7 -0
- package/build/src/host/account_api/account_api_controller.js +8 -6
- package/build/src/host/account_session_sign_in.d.ts +12 -0
- package/build/src/host/account_session_sign_in.js +19 -0
- package/build/src/host/admin_api/admin_users_service.d.ts +5 -2
- package/build/src/host/admin_api/api_clients_controller.d.ts +37 -0
- package/build/src/host/admin_api/api_clients_controller.js +31 -1
- package/build/src/host/admin_api/dto.d.ts +3 -1
- package/build/src/host/admin_api/dto.js +2 -0
- package/build/src/host/admin_authorize.d.ts +8 -0
- package/build/src/host/admin_authorize.js +25 -0
- package/build/src/host/admin_clients_service.d.ts +20 -0
- package/build/src/host/admin_clients_service.js +55 -0
- package/build/src/host/admin_console/admin_console_entry.d.ts +14 -0
- package/build/src/host/admin_console/admin_console_entry.js +80 -0
- package/build/src/host/admin_console/admin_shell_controller.js +8 -0
- package/build/src/host/admin_console/console_clients_controller.d.ts +41 -0
- package/build/src/host/admin_console/console_clients_controller.js +34 -1
- package/build/src/host/admin_console/console_personal_agents_controller.d.ts +1 -6
- package/build/src/host/admin_console/console_personal_agents_controller.js +50 -30
- package/build/src/host/admin_console/console_switcher_controller.d.ts +11 -0
- package/build/src/host/admin_console/console_switcher_controller.js +23 -0
- package/build/src/host/admin_sessions_service.js +8 -5
- package/build/src/host/admin_validators.d.ts +29 -0
- package/build/src/host/admin_validators.js +30 -0
- package/build/src/host/auth_host_config.d.ts +9 -2
- package/build/src/host/controllers/account_apps_controller.js +15 -11
- package/build/src/host/csrf.d.ts +3 -3
- package/build/src/host/csrf.js +3 -13
- package/build/src/host/default_mailer.d.ts +11 -6
- package/build/src/host/default_mailer.js +16 -12
- package/build/src/host/i18n.d.ts +41 -14
- package/build/src/host/i18n.js +55 -13
- package/build/src/host/rate_limit.d.ts +8 -0
- package/build/src/host/rate_limit.js +9 -0
- package/build/src/host/register_auth_host.d.ts +14 -0
- package/build/src/host/register_auth_host.js +167 -106
- package/build/src/host/renderers/edge_renderer.js +5 -0
- package/build/src/host/renderers/inertia_renderer.js +7 -1
- package/build/src/host/screen_props.d.ts +39 -8
- package/build/src/host/ui-dist/assets/index-Dq3dJVBv.js +67 -0
- package/build/src/host/ui-dist/assets/{index-DTSmD4RU.css → index-WNIyCEPA.css} +1 -1
- package/build/src/host/ui-dist/index.html +2 -2
- package/build/src/provider/build_provider.js +37 -6
- package/build/src/provider/oidc_service.d.ts +1 -1
- package/build/src/provider/oidc_service.js +1 -1
- package/build/src/schema/ensure.d.ts +14 -0
- package/build/src/schema/ensure.js +43 -150
- package/build/src/service_tenants/client_metadata.d.ts +39 -0
- package/build/src/service_tenants/client_metadata.js +80 -0
- package/build/src/service_tenants/config.d.ts +44 -0
- package/build/src/service_tenants/config.js +44 -0
- package/build/src/service_tenants/provider_hooks.d.ts +52 -0
- package/build/src/service_tenants/provider_hooks.js +124 -0
- package/build/src/service_tenants/tenant_verifier.d.ts +73 -0
- package/build/src/service_tenants/tenant_verifier.js +104 -0
- package/build/src/tenancy/tenant_labels.d.ts +30 -0
- package/build/src/tenancy/tenant_labels.js +64 -0
- package/build/types.d.ts +3 -1
- package/package.json +9 -6
- package/build/src/agents/poppy/client_registry.d.ts +0 -50
- package/build/src/agents/poppy/client_registry.js +0 -260
- package/build/src/agents/poppy/config.d.ts +0 -396
- package/build/src/agents/poppy/config.js +0 -228
- package/build/src/agents/poppy/dpop.d.ts +0 -61
- package/build/src/agents/poppy/dpop.js +0 -158
- package/build/src/agents/poppy/email_code_sign_in.d.ts +0 -27
- package/build/src/agents/poppy/email_code_sign_in.js +0 -140
- package/build/src/agents/poppy/errors.d.ts +0 -25
- package/build/src/agents/poppy/errors.js +0 -25
- package/build/src/agents/poppy/replay.d.ts +0 -25
- package/build/src/agents/poppy/replay.js +0 -106
- package/build/src/agents/poppy/resource.d.ts +0 -100
- package/build/src/agents/poppy/resource.js +0 -129
- package/build/src/agents/poppy/safe_fetch.d.ts +0 -48
- package/build/src/agents/poppy/safe_fetch.js +0 -262
- package/build/src/agents/poppy/service.d.ts +0 -293
- package/build/src/agents/poppy/service.js +0 -1458
- package/build/src/agents/poppy/session_jwt.d.ts +0 -30
- package/build/src/agents/poppy/session_jwt.js +0 -133
- package/build/src/agents/poppy/store.d.ts +0 -106
- package/build/src/agents/poppy/store.js +0 -261
- package/build/src/agents/poppy/web_session.d.ts +0 -39
- package/build/src/agents/poppy/web_session.js +0 -122
- package/build/src/host/controllers/poppy_consent_controller.d.ts +0 -19
- package/build/src/host/controllers/poppy_consent_controller.js +0 -199
- package/build/src/host/controllers/poppy_controller.d.ts +0 -33
- package/build/src/host/controllers/poppy_controller.js +0 -219
- package/build/src/host/poppy_session_key.d.ts +0 -9
- package/build/src/host/poppy_session_key.js +0 -9
- package/build/src/host/ui-dist/assets/index-D0EIeyKK.js +0 -67
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { getBootedApp } from '../../services/booted_app.js';
|
|
2
|
+
import { accountPathsMap, accountPrefix, joinAccountPath } from '../host/account_paths.js';
|
|
3
|
+
import { AccountExtensionRegistryImpl } from './registry.js';
|
|
4
|
+
import { RemoteAccountExtensions } from './remote.js';
|
|
5
|
+
import { accountExtensionPages, remoteAccountPageBySegment, setAccountConsoleState, } from './runtime.js';
|
|
6
|
+
function inProduction() {
|
|
7
|
+
try {
|
|
8
|
+
return getBootedApp().inProduction === true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function lifecycleApp() {
|
|
15
|
+
try {
|
|
16
|
+
return getBootedApp();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
let registry = null;
|
|
23
|
+
/** The process registry bound at `authkit.accountExtensions`. */
|
|
24
|
+
export function accountExtensionRegistry() {
|
|
25
|
+
registry ??= new AccountExtensionRegistryImpl({ production: inProduction });
|
|
26
|
+
return registry;
|
|
27
|
+
}
|
|
28
|
+
/** A fresh registry (tests). */
|
|
29
|
+
export function resetAccountExtensionRegistry(next) {
|
|
30
|
+
registry = next ?? null;
|
|
31
|
+
}
|
|
32
|
+
/** The IdP's issuer and signing keys, from the OIDC service of the booted app. */
|
|
33
|
+
async function signingSource() {
|
|
34
|
+
const service = (await getBootedApp().container.make('authkit.server'));
|
|
35
|
+
return { issuer: service.config.issuer, signingJwks: service.signingJwks };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A route matcher that only accepts the segments remote pages currently use, so the catch-all
|
|
39
|
+
* routes never shadow another route under the account prefix.
|
|
40
|
+
*/
|
|
41
|
+
function liveSegmentMatcher() {
|
|
42
|
+
const matcher = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
43
|
+
Object.defineProperty(matcher, 'test', {
|
|
44
|
+
value: (segment) => remoteAccountPageBySegment(segment) !== undefined,
|
|
45
|
+
});
|
|
46
|
+
return matcher;
|
|
47
|
+
}
|
|
48
|
+
const H = () => import('./http.js');
|
|
49
|
+
function builtinSegments() {
|
|
50
|
+
const prefix = accountPrefix() === '/' ? '' : accountPrefix();
|
|
51
|
+
return Object.values(accountPathsMap()).map((path) => (path.startsWith(`${prefix}/`) ? path.slice(prefix.length + 1) : path).split('/')[0] ?? '');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Mounts the JSON API of the extensions now, and the page routes once every provider booted
|
|
55
|
+
* (contributors register from their `boot()`; pages mount when the app starts), then freezes the
|
|
56
|
+
* registry.
|
|
57
|
+
*/
|
|
58
|
+
export function mountAccountExtensions(router, options) {
|
|
59
|
+
const apiBase = joinAccountPath('api');
|
|
60
|
+
const withJsonThrottle = (route) => {
|
|
61
|
+
if (options.throttle)
|
|
62
|
+
route.use([options.throttle.json]);
|
|
63
|
+
return route;
|
|
64
|
+
};
|
|
65
|
+
router
|
|
66
|
+
.group(() => {
|
|
67
|
+
router.get(`${apiBase}/extensions`, async (ctx) => (await H()).apiExtensions(ctx));
|
|
68
|
+
router.get(`${apiBase}/x/:pageId`, async (ctx) => (await H()).apiPage(ctx));
|
|
69
|
+
withJsonThrottle(router.post(`${apiBase}/x/:pageId/actions/:action`, async (ctx) => (await H()).apiAction(ctx)));
|
|
70
|
+
})
|
|
71
|
+
.use([options.guard]);
|
|
72
|
+
const remote = options.remote && options.remote.length > 0
|
|
73
|
+
? new RemoteAccountExtensions(options.remote, {
|
|
74
|
+
signing: signingSource,
|
|
75
|
+
pathOf: (segment) => joinAccountPath(segment),
|
|
76
|
+
production: inProduction(),
|
|
77
|
+
...(options.actionTimeoutMs !== undefined
|
|
78
|
+
? { actionTimeoutMs: options.actionTimeoutMs }
|
|
79
|
+
: {}),
|
|
80
|
+
})
|
|
81
|
+
: null;
|
|
82
|
+
const pushEntries = (pages) => {
|
|
83
|
+
for (const page of pages) {
|
|
84
|
+
if (page.page.nav === false)
|
|
85
|
+
continue;
|
|
86
|
+
if (options.entries.some((entry) => entry.id === page.page.id))
|
|
87
|
+
continue;
|
|
88
|
+
const label = page.page.nav?.label ?? page.page.title;
|
|
89
|
+
options.entries.push({
|
|
90
|
+
id: page.page.id,
|
|
91
|
+
href: joinAccountPath(page.segment),
|
|
92
|
+
label: typeof label === 'string' ? label : label.key,
|
|
93
|
+
order: page.order,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const mountRemote = () => {
|
|
98
|
+
if (!remote)
|
|
99
|
+
return;
|
|
100
|
+
const segment = { xsegment: { match: liveSegmentMatcher() } };
|
|
101
|
+
const page = (ctx) => remoteAccountPageBySegment(String(ctx.params.xsegment));
|
|
102
|
+
router
|
|
103
|
+
.group(() => {
|
|
104
|
+
router
|
|
105
|
+
.get(joinAccountPath(':xsegment'), async (ctx) => (await H()).showRemotePage(ctx, page(ctx)))
|
|
106
|
+
.where('xsegment', segment.xsegment);
|
|
107
|
+
router
|
|
108
|
+
.get(joinAccountPath(':xsegment/actions/:action/confirm'), async (ctx) => (await H()).showRemoteActionStep(ctx, page(ctx)))
|
|
109
|
+
.where('xsegment', segment.xsegment);
|
|
110
|
+
const post = router
|
|
111
|
+
.post(joinAccountPath(':xsegment/actions/:action'), async (ctx) => (await H()).postRemoteAction(ctx, page(ctx)))
|
|
112
|
+
.where('xsegment', segment.xsegment);
|
|
113
|
+
if (options.throttle)
|
|
114
|
+
post.use([options.throttle.html]);
|
|
115
|
+
})
|
|
116
|
+
.use([options.guard]);
|
|
117
|
+
};
|
|
118
|
+
const mountPages = () => {
|
|
119
|
+
const reg = accountExtensionRegistry();
|
|
120
|
+
const pages = reg.freeze({
|
|
121
|
+
overrides: options.overrides,
|
|
122
|
+
builtinSegments: builtinSegments(),
|
|
123
|
+
production: inProduction(),
|
|
124
|
+
});
|
|
125
|
+
setAccountConsoleState({
|
|
126
|
+
pages,
|
|
127
|
+
builtins: options.builtins,
|
|
128
|
+
registry: reg,
|
|
129
|
+
remote,
|
|
130
|
+
builtinSegments: builtinSegments(),
|
|
131
|
+
...(options.overrides ? { overrides: options.overrides } : {}),
|
|
132
|
+
});
|
|
133
|
+
mountRemote();
|
|
134
|
+
if (pages.length === 0)
|
|
135
|
+
return;
|
|
136
|
+
router
|
|
137
|
+
.group(() => {
|
|
138
|
+
for (const page of pages) {
|
|
139
|
+
const base = joinAccountPath(page.segment);
|
|
140
|
+
router.get(base, async (ctx) => (await H()).showPage(ctx, page));
|
|
141
|
+
router.get(`${base}/actions/:action/confirm`, async (ctx) => (await H()).showActionStep(ctx, page));
|
|
142
|
+
const post = router.post(`${base}/actions/:action`, async (ctx) => (await H()).postAction(ctx, page));
|
|
143
|
+
if (options.throttle)
|
|
144
|
+
post.use([options.throttle.html]);
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
.use([options.guard]);
|
|
148
|
+
pushEntries(pages);
|
|
149
|
+
};
|
|
150
|
+
// Remote manifests: fetched when the app starts (bounded), refreshed in the background after.
|
|
151
|
+
const startRemote = async () => {
|
|
152
|
+
if (!remote)
|
|
153
|
+
return;
|
|
154
|
+
await remote.start();
|
|
155
|
+
pushEntries(accountExtensionPages().filter((page) => page.remote !== undefined));
|
|
156
|
+
};
|
|
157
|
+
setAccountConsoleState({ builtins: options.builtins, registry: accountExtensionRegistry() });
|
|
158
|
+
// During the providers' boot (auto-mount from `config.routes`) contributors may not have
|
|
159
|
+
// registered yet: mount the pages when the app starts, before the preloads and the router commit.
|
|
160
|
+
const app = lifecycleApp();
|
|
161
|
+
if (app && app.isBooted === false && typeof app.starting === 'function') {
|
|
162
|
+
app.starting(mountPages);
|
|
163
|
+
if (remote)
|
|
164
|
+
app.starting(startRemote);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
mountPages();
|
|
168
|
+
if (remote)
|
|
169
|
+
void startRemote();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AccountExtensionError, BaseAccountExtensionRegistry, validateAccountExtension } from '@adonis-agora/authkit-core';
|
|
2
|
+
import type { AccountExtensionOverride, AccountPage } from './extension.js';
|
|
3
|
+
export { AccountExtensionError, validateAccountExtension };
|
|
4
|
+
/** A page as the console serves it, after host overrides. */
|
|
5
|
+
export interface RegisteredAccountPage {
|
|
6
|
+
extensionId: string;
|
|
7
|
+
page: AccountPage;
|
|
8
|
+
segment: string;
|
|
9
|
+
order: number;
|
|
10
|
+
scriptOrigins: readonly string[];
|
|
11
|
+
/** Client id of the app serving it, for a remote page. */
|
|
12
|
+
remote?: string;
|
|
13
|
+
}
|
|
14
|
+
/** Thrown for an invalid extension; the message names it. */
|
|
15
|
+
export interface FreezeOptions {
|
|
16
|
+
/** `accountConsole.extensions` from `config/authkit.ts`. */
|
|
17
|
+
overrides?: Readonly<Record<string, AccountExtensionOverride>>;
|
|
18
|
+
/** Segments of the mounted built-in screens (after `accountRoutes` renames). */
|
|
19
|
+
builtinSegments?: readonly string[];
|
|
20
|
+
/** `true` → an invalid extension is skipped and logged instead of thrown. */
|
|
21
|
+
production: boolean;
|
|
22
|
+
log?: (message: string) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The registry bound at `authkit.accountExtensions`. Contributors call `register()` from their
|
|
26
|
+
* provider's `boot()`; authkit freezes it when it mounts the console routes.
|
|
27
|
+
*/
|
|
28
|
+
export declare class AccountExtensionRegistryImpl extends BaseAccountExtensionRegistry {
|
|
29
|
+
#private;
|
|
30
|
+
protected beforeRegister(extension: {
|
|
31
|
+
id?: unknown;
|
|
32
|
+
}): void;
|
|
33
|
+
get frozen(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Applies the host overrides, checks the segments against the mounted screens and freezes the
|
|
36
|
+
* registry. Returns the pages to serve. Idempotent.
|
|
37
|
+
*/
|
|
38
|
+
freeze(options: FreezeOptions): RegisteredAccountPage[];
|
|
39
|
+
/** The served pages (empty until frozen). */
|
|
40
|
+
pages(): readonly RegisteredAccountPage[];
|
|
41
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ACCOUNT_RESERVED_SEGMENTS, ACCOUNT_SEGMENT, AccountExtensionError, BaseAccountExtensionRegistry, failAccountExtension as fail, validateAccountExtension, } from '@adonis-agora/authkit-core';
|
|
2
|
+
export { AccountExtensionError, validateAccountExtension };
|
|
3
|
+
/**
|
|
4
|
+
* The registry bound at `authkit.accountExtensions`. Contributors call `register()` from their
|
|
5
|
+
* provider's `boot()`; authkit freezes it when it mounts the console routes.
|
|
6
|
+
*/
|
|
7
|
+
export class AccountExtensionRegistryImpl extends BaseAccountExtensionRegistry {
|
|
8
|
+
#pages = null;
|
|
9
|
+
beforeRegister(extension) {
|
|
10
|
+
if (this.#pages !== null) {
|
|
11
|
+
throw new AccountExtensionError(`authkit: account extension "${String(extension?.id)}" registered after the account console routes were mounted. Register it from your provider's boot(), not from a preload.`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
get frozen() {
|
|
15
|
+
return this.#pages !== null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Applies the host overrides, checks the segments against the mounted screens and freezes the
|
|
19
|
+
* registry. Returns the pages to serve. Idempotent.
|
|
20
|
+
*/
|
|
21
|
+
freeze(options) {
|
|
22
|
+
if (this.#pages !== null)
|
|
23
|
+
return this.#pages;
|
|
24
|
+
const log = options.log ?? this.log;
|
|
25
|
+
const builtin = new Set(options.builtinSegments ?? []);
|
|
26
|
+
const used = new Map();
|
|
27
|
+
const out = [];
|
|
28
|
+
for (const extension of this.registered) {
|
|
29
|
+
const override = options.overrides?.[extension.id];
|
|
30
|
+
if (override === false)
|
|
31
|
+
continue;
|
|
32
|
+
const pages = [];
|
|
33
|
+
try {
|
|
34
|
+
for (const page of extension.pages) {
|
|
35
|
+
const pageOverride = override ? override.pages?.[page.id] : undefined;
|
|
36
|
+
if (pageOverride === false)
|
|
37
|
+
continue;
|
|
38
|
+
const segment = pageOverride?.segment ?? page.segment;
|
|
39
|
+
if (!ACCOUNT_SEGMENT.test(segment) || ACCOUNT_RESERVED_SEGMENTS.has(segment)) {
|
|
40
|
+
fail(extension.id, `accountConsole.extensions renames "${page.id}" to an invalid segment "${segment}".`);
|
|
41
|
+
}
|
|
42
|
+
if (builtin.has(segment)) {
|
|
43
|
+
fail(extension.id, `segment "${segment}" of "${page.id}" collides with a built-in screen.`);
|
|
44
|
+
}
|
|
45
|
+
const owner = used.get(segment);
|
|
46
|
+
if (owner)
|
|
47
|
+
fail(extension.id, `segment "${segment}" of "${page.id}" is already used by "${owner}".`);
|
|
48
|
+
used.set(segment, page.id);
|
|
49
|
+
const nav = page.nav === false ? undefined : page.nav;
|
|
50
|
+
pages.push({
|
|
51
|
+
extensionId: extension.id,
|
|
52
|
+
page,
|
|
53
|
+
segment,
|
|
54
|
+
order: pageOverride?.order ?? nav?.order ?? 100,
|
|
55
|
+
scriptOrigins: extension.scriptOrigins ?? [],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
for (const page of pages)
|
|
61
|
+
used.delete(page.segment);
|
|
62
|
+
if (!options.production)
|
|
63
|
+
throw error;
|
|
64
|
+
log(`${error.message} The extension is skipped.`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
out.push(...pages);
|
|
68
|
+
}
|
|
69
|
+
for (const id of Object.keys(options.overrides ?? {})) {
|
|
70
|
+
if (!this.registered.some((e) => e.id === id)) {
|
|
71
|
+
log(`authkit: accountConsole.extensions names "${id}", which no installed package registered.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.#pages = out;
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/** The served pages (empty until frozen). */
|
|
78
|
+
pages() {
|
|
79
|
+
return this.#pages ?? [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type AccountExtension, type AccountExtensionOverride } from '@adonis-agora/authkit-core';
|
|
2
|
+
import type { RegisteredAccountPage } from './registry.js';
|
|
3
|
+
/**
|
|
4
|
+
* One app that signs in through this IdP and contributes account console pages
|
|
5
|
+
* (`accountConsole.remote` in `config/authkit.ts`).
|
|
6
|
+
*/
|
|
7
|
+
export interface RemoteAccountExtensionSource {
|
|
8
|
+
/** The app's OIDC client id: the `aud` of every token this IdP sends it. */
|
|
9
|
+
clientId: string;
|
|
10
|
+
/** Base URL the app serves `/.well-known/agora-account-extensions` under (`https://app.example.com`). */
|
|
11
|
+
url: string;
|
|
12
|
+
/** How long a fetched manifest is reused, in ms. Default 300 000 (5 min). */
|
|
13
|
+
ttlMs?: number;
|
|
14
|
+
/** Budget of one call to the app, in ms. Default 5000 (actions: `actionTimeoutMs`). */
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Where the IdP's issuer and signing keys come from (the OIDC service; read on every call). */
|
|
18
|
+
export interface RemoteSigningSource {
|
|
19
|
+
issuer: string;
|
|
20
|
+
signingJwks: {
|
|
21
|
+
keys: Record<string, any>[];
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export interface RemoteAccountExtensionsDeps {
|
|
25
|
+
signing: () => RemoteSigningSource | Promise<RemoteSigningSource>;
|
|
26
|
+
fetch?: typeof fetch;
|
|
27
|
+
now?: () => number;
|
|
28
|
+
log?: (message: string) => void;
|
|
29
|
+
/** Same-origin path of a page segment (`billing` → `/account/billing`). */
|
|
30
|
+
pathOf: (segment: string) => string;
|
|
31
|
+
actionTimeoutMs?: number;
|
|
32
|
+
}
|
|
33
|
+
/** Signs a remote-extension token with the IdP's current signing key. */
|
|
34
|
+
export declare function signRemoteExtensionToken(signing: RemoteSigningSource, claims: {
|
|
35
|
+
audience: string;
|
|
36
|
+
subject: string;
|
|
37
|
+
bodyHash?: string;
|
|
38
|
+
ttlSec?: number;
|
|
39
|
+
now?: number;
|
|
40
|
+
}): Promise<string>;
|
|
41
|
+
/** Thrown when an app answers a call with an error or cannot be reached. */
|
|
42
|
+
export declare class RemoteAccountExtensionError extends Error {
|
|
43
|
+
readonly status: number;
|
|
44
|
+
constructor(message: string, status: number);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The remote account extensions of this IdP: fetches each app's manifest (cached for `ttlMs`,
|
|
48
|
+
* refreshed in the background), turns it into extensions whose `visible`, `load` and actions call
|
|
49
|
+
* the app, and serves their pages next to the in-process ones. An app that is down keeps its last
|
|
50
|
+
* manifest (its pages then show the load-failure notice) or, never reached, contributes nothing.
|
|
51
|
+
*/
|
|
52
|
+
export declare class RemoteAccountExtensions {
|
|
53
|
+
#private;
|
|
54
|
+
constructor(sources: readonly RemoteAccountExtensionSource[], deps: RemoteAccountExtensionsDeps & {
|
|
55
|
+
production?: boolean;
|
|
56
|
+
});
|
|
57
|
+
get size(): number;
|
|
58
|
+
/** Fetches every manifest now; resolves when all settled or after `timeoutMs`. */
|
|
59
|
+
start(timeoutMs?: number): Promise<void>;
|
|
60
|
+
/** Every remote extension currently known (for the message catalogs). */
|
|
61
|
+
extensions(): AccountExtension[];
|
|
62
|
+
/**
|
|
63
|
+
* The remote pages to serve now, after host overrides, minus any whose segment collides with a
|
|
64
|
+
* built-in screen, an in-process page or an earlier app (skipped and logged once).
|
|
65
|
+
*/
|
|
66
|
+
pages(context: {
|
|
67
|
+
taken: ReadonlySet<string>;
|
|
68
|
+
/** Ids of the in-process extensions. */
|
|
69
|
+
takenIds: ReadonlySet<string>;
|
|
70
|
+
overrides?: Readonly<Record<string, AccountExtensionOverride>>;
|
|
71
|
+
}): readonly RegisteredAccountPage[];
|
|
72
|
+
}
|