@adonis-agora/authkit-client 1.0.0-rc.4 → 1.0.0-rc.5
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 +15 -0
- package/build/index.d.ts +4 -2
- package/build/index.js +2 -1
- package/build/providers/authkit_client_provider.d.ts +6 -0
- package/build/providers/authkit_client_provider.js +47 -0
- package/build/src/account_extensions.d.ts +49 -0
- package/build/src/account_extensions.js +105 -0
- package/build/src/define_config.d.ts +23 -1
- package/build/src/define_config.js +1 -0
- package/build/src/register_oidc_client.d.ts +13 -1
- package/build/src/register_oidc_client.js +17 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @adonis-agora/authkit-client
|
|
2
2
|
|
|
3
|
+
## 1.0.0-rc.5
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#471](https://github.com/DavideCarvalho/adonis-agora-authkit/pull/471) [`66ccc36`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/66ccc365dbbd7d9d5bf22dba7aad196aff9f5d4b) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - Account console extensions from another app. An app that signs in through an authkit IdP can now add pages to the IdP's account console:
|
|
8
|
+
|
|
9
|
+
- authkit-client binds the same `authkit.accountExtensions` registry, so packages register their pages the same way in both setups. With `accountConsole: { remote: true }` the app serves them at `/.well-known/agora-account-extensions` (mounted by `registerOidcClient`, or `registerAccountExtensionsEndpoint(router)`), only to requests the IdP signed for its client id with the scope `authkit:account-extensions`. `accountConsole.subject` maps the IdP account to the app's user.
|
|
10
|
+
- authkit-server: `accountConsole.remote: [{ clientId, url }]` lists those apps; their pages show in the console next to the in-process ones, with the same sign-in, sudo, confirmation, rate limit and audit rules.
|
|
11
|
+
- The contract types and validation now live in authkit-core (still exported from `@adonis-agora/authkit-server/account-extension`).
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`66ccc36`](https://github.com/DavideCarvalho/adonis-agora-authkit/commit/66ccc365dbbd7d9d5bf22dba7aad196aff9f5d4b)]:
|
|
16
|
+
- @adonis-agora/authkit-core@1.0.0-rc.1
|
|
17
|
+
|
|
3
18
|
## 1.0.0-rc.4
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
package/build/index.d.ts
CHANGED
|
@@ -2,12 +2,14 @@ export type { Identity } from '@adonis-agora/authkit-core';
|
|
|
2
2
|
export { configure } from './commands/configure.js';
|
|
3
3
|
export type { AuthkitClientLogger, AuthkitClientManagerDeps, } from './providers/authkit_client_provider.js';
|
|
4
4
|
export { AuthkitClientManager } from './providers/authkit_client_provider.js';
|
|
5
|
+
export type { AccountExtensionsAnswer, AccountExtensionsVerifier, ServeAccountExtensionsInput, } from './src/account_extensions.js';
|
|
6
|
+
export { ACCOUNT_EXTENSIONS_BINDING, ClientAccountExtensionRegistry, serveAccountExtensions, verifyAccountExtensionsToken, } from './src/account_extensions.js';
|
|
5
7
|
export { Authenticator } from './src/authenticator.js';
|
|
6
8
|
export type { AuthorizationRequest, CompleteAuthorizationParams, CompletedAuthorization, CreateAuthorizationRequestParams, OidcCallbackErrorCode, PendingAuthorization, } from './src/authorization.js';
|
|
7
9
|
export { completeAuthorization, createAuthorizationRequest, OidcCallbackError, } from './src/authorization.js';
|
|
8
10
|
export type { SessionIndex, SessionIndexEntry, ValidatedLogoutToken, ValidateLogoutTokenOptions, } from './src/backchannel_logout.js';
|
|
9
11
|
export { BACKCHANNEL_LOGOUT_EVENT, InMemorySessionIndex, InvalidLogoutTokenError, validateLogoutToken, } from './src/backchannel_logout.js';
|
|
10
|
-
export type { BackchannelLogoutCallback, BackchannelLogoutInput, ClientConfigInput, ResolvedClientConfig, } from './src/define_config.js';
|
|
12
|
+
export type { BackchannelLogoutCallback, BackchannelLogoutInput, ClientAccountConsoleConfig, ClientConfigInput, ResolvedClientConfig, } from './src/define_config.js';
|
|
11
13
|
export { defineConfig, resolvers } from './src/define_config.js';
|
|
12
14
|
export type { DiscoverOptions, OidcEndpoints } from './src/discovery.js';
|
|
13
15
|
export { conventionEndpoints, discoverEndpoints, OidcDiscoveryError } from './src/discovery.js';
|
|
@@ -34,7 +36,7 @@ export type { ClientTenancyOptions, ContextTenant, ContextTenantLink, TenantInpu
|
|
|
34
36
|
export type { AuthorizeParams, EndSessionParams, ExchangeParams, RefreshParams, } from './src/oidc_login.js';
|
|
35
37
|
export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, generatePkce, refreshTokens, } from './src/oidc_login.js';
|
|
36
38
|
export type { PostLoginRedirects, RegisterOidcClientOptions, } from './src/register_oidc_client.js';
|
|
37
|
-
export { registerOidcClient } from './src/register_oidc_client.js';
|
|
39
|
+
export { registerAccountExtensionsEndpoint, registerOidcClient, } from './src/register_oidc_client.js';
|
|
38
40
|
export type { LucidRevocationStoreOptions, RevocationStore, } from './src/revocation/revocation_store.js';
|
|
39
41
|
export { DEFAULT_REVOCATION_TABLE, lucidRevocationStore, } from './src/revocation/revocation_store.js';
|
|
40
42
|
export { AuthkitTenantToken, type AuthkitTenantTokenFn, type AuthkitTenantTokenOptions, authkitTenantToken, TenantTokenError, } from './src/tenant_token.js';
|
package/build/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { configure } from './commands/configure.js';
|
|
2
2
|
export { AuthkitClientManager } from './providers/authkit_client_provider.js';
|
|
3
|
+
export { ACCOUNT_EXTENSIONS_BINDING, ClientAccountExtensionRegistry, serveAccountExtensions, verifyAccountExtensionsToken, } from './src/account_extensions.js';
|
|
3
4
|
export { Authenticator } from './src/authenticator.js';
|
|
4
5
|
export { completeAuthorization, createAuthorizationRequest, OidcCallbackError, } from './src/authorization.js';
|
|
5
6
|
export { BACKCHANNEL_LOGOUT_EVENT, InMemorySessionIndex, InvalidLogoutTokenError, validateLogoutToken, } from './src/backchannel_logout.js';
|
|
@@ -17,7 +18,7 @@ export { default as AuthkitContextMiddleware } from './src/middleware/authkit_co
|
|
|
17
18
|
export { default as BackchannelRevocationMiddleware } from './src/middleware/backchannel_revocation_middleware.js';
|
|
18
19
|
export { default as RequireOrgMiddleware } from './src/middleware/require_org_middleware.js';
|
|
19
20
|
export { buildAuthorizeUrl, buildEndSessionUrl, exchangeCode, exchangeToken, generatePkce, refreshTokens, } from './src/oidc_login.js';
|
|
20
|
-
export { registerOidcClient } from './src/register_oidc_client.js';
|
|
21
|
+
export { registerAccountExtensionsEndpoint, registerOidcClient, } from './src/register_oidc_client.js';
|
|
21
22
|
export { DEFAULT_REVOCATION_TABLE, lucidRevocationStore, } from './src/revocation/revocation_store.js';
|
|
22
23
|
export { AuthkitTenantToken, authkitTenantToken, TenantTokenError, } from './src/tenant_token.js';
|
|
23
24
|
export { createUserinfoResolver, identityToUser, } from './src/user_resolvers.js';
|
|
@@ -97,6 +97,12 @@ export declare class AuthkitClientManager {
|
|
|
97
97
|
* Sempre seta `Cache-Control: no-store` (exigência do spec).
|
|
98
98
|
*/
|
|
99
99
|
handleBackchannelLogout(ctx: HttpContext): Promise<unknown>;
|
|
100
|
+
/**
|
|
101
|
+
* Answers the IdP's account console (`GET`/`POST /.well-known/agora-account-extensions`): the
|
|
102
|
+
* manifest of this app's account extensions, or one call to them. 404 unless
|
|
103
|
+
* `accountConsole.remote` is on. Mounted by {@link registerOidcClient}.
|
|
104
|
+
*/
|
|
105
|
+
handleAccountExtensions(ctx: HttpContext): Promise<unknown>;
|
|
100
106
|
/**
|
|
101
107
|
* Impersona `requestedSubject` via token-exchange (RFC 8693): guarda o token set
|
|
102
108
|
* atual e o substitui pelo do alvo. Encapsula o malabarismo de session keys que o
|
|
@@ -5,6 +5,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
5
5
|
import { configProvider } from '@adonisjs/core';
|
|
6
6
|
import { RuntimeException } from '@adonisjs/core/exceptions';
|
|
7
7
|
import { decodeJwt } from 'jose';
|
|
8
|
+
import { ACCOUNT_EXTENSIONS_BINDING, ClientAccountExtensionRegistry, serveAccountExtensions, } from '../src/account_extensions.js';
|
|
8
9
|
import { Authenticator } from '../src/authenticator.js';
|
|
9
10
|
import { assertCallbackBelongs, completeAuthorization, createAuthorizationRequest, } from '../src/authorization.js';
|
|
10
11
|
import { validateLogoutToken } from '../src/backchannel_logout.js';
|
|
@@ -323,6 +324,44 @@ export class AuthkitClientManager {
|
|
|
323
324
|
});
|
|
324
325
|
return {};
|
|
325
326
|
}
|
|
327
|
+
/**
|
|
328
|
+
* Answers the IdP's account console (`GET`/`POST /.well-known/agora-account-extensions`): the
|
|
329
|
+
* manifest of this app's account extensions, or one call to them. 404 unless
|
|
330
|
+
* `accountConsole.remote` is on. Mounted by {@link registerOidcClient}.
|
|
331
|
+
*/
|
|
332
|
+
async handleAccountExtensions(ctx) {
|
|
333
|
+
const { request, response } = ctx;
|
|
334
|
+
response.header('Cache-Control', 'no-store');
|
|
335
|
+
const settings = this.config.accountConsole;
|
|
336
|
+
if (!settings?.remote)
|
|
337
|
+
return response.status(404).send({ error: { code: 'not_found' } });
|
|
338
|
+
let idp;
|
|
339
|
+
try {
|
|
340
|
+
idp = await this.#idp();
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
this.#logger.warn({ err: error, issuer: this.config.issuer }, 'authkit-client: account extensions called while discovery is failing');
|
|
344
|
+
return response.status(503).send({ error: { code: 'temporarily_unavailable' } });
|
|
345
|
+
}
|
|
346
|
+
const resolver = ctx.containerResolver;
|
|
347
|
+
const registry = (await resolver.make(ACCOUNT_EXTENSIONS_BINDING));
|
|
348
|
+
const method = request.method();
|
|
349
|
+
const raw = method === 'POST' ? request.raw() : null;
|
|
350
|
+
const answer = await serveAccountExtensions({
|
|
351
|
+
method,
|
|
352
|
+
authorization: request.header('authorization'),
|
|
353
|
+
rawBody: typeof raw === 'string' ? raw : null,
|
|
354
|
+
extensions: typeof registry.extensions === 'function' ? registry.extensions() : [],
|
|
355
|
+
contract: registry.contract,
|
|
356
|
+
verifier: { issuer: idp.endpoints.issuer, clientId: this.config.clientId, keys: idp.keys },
|
|
357
|
+
config: settings,
|
|
358
|
+
make: (binding) => resolver.make(binding),
|
|
359
|
+
log: (message) => this.#logger.warn({}, message),
|
|
360
|
+
});
|
|
361
|
+
for (const [name, value] of Object.entries(answer.headers ?? {}))
|
|
362
|
+
response.header(name, value);
|
|
363
|
+
return response.status(answer.status).send(answer.body);
|
|
364
|
+
}
|
|
326
365
|
/** Chave de sessão onde o token set ORIGINAL fica guardado durante a impersonação. */
|
|
327
366
|
get #impersonatorKey() {
|
|
328
367
|
return `${this.config.sessionKey}_impersonator`;
|
|
@@ -511,6 +550,14 @@ export default class AuthkitClientProvider {
|
|
|
511
550
|
// bound by class, would silently get the real one. Not cached here, so a `swap` of the class
|
|
512
551
|
// also reaches the string key, even after its first use.
|
|
513
552
|
this.app.container.bind('authkit.client', (resolver) => resolver.make(AuthkitClientManager));
|
|
553
|
+
// Account console extensions register here from their `boot()`, exactly as next to
|
|
554
|
+
// authkit-server. When authkit-server is in this app too, its registry is the one bound.
|
|
555
|
+
if (!this.app.container.hasBinding(ACCOUNT_EXTENSIONS_BINDING)) {
|
|
556
|
+
const registry = new ClientAccountExtensionRegistry({
|
|
557
|
+
production: () => this.app.inProduction === true,
|
|
558
|
+
});
|
|
559
|
+
this.app.container.singleton(ACCOUNT_EXTENSIONS_BINDING, async () => registry);
|
|
560
|
+
}
|
|
514
561
|
}
|
|
515
562
|
}
|
|
516
563
|
/** `sub` of a stored id_token, or undefined when there is none / it is unreadable. */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type AccountExtension, BaseAccountExtensionRegistry } from '@adonis-agora/authkit-core';
|
|
2
|
+
import { type JWTPayload, type JWTVerifyGetKey } from 'jose';
|
|
3
|
+
import type { ClientAccountConsoleConfig } from './define_config.js';
|
|
4
|
+
/** The container binding contributors register into (same name as authkit-server's). */
|
|
5
|
+
export declare const ACCOUNT_EXTENSIONS_BINDING = "authkit.accountExtensions";
|
|
6
|
+
/**
|
|
7
|
+
* The account extensions registry of an app whose IdP runs elsewhere. Contributors register
|
|
8
|
+
* exactly as they would next to authkit-server; this app then serves the pages to the IdP.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ClientAccountExtensionRegistry extends BaseAccountExtensionRegistry {
|
|
11
|
+
}
|
|
12
|
+
/** What verifying a remote-extension token needs. */
|
|
13
|
+
export interface AccountExtensionsVerifier {
|
|
14
|
+
issuer: string;
|
|
15
|
+
clientId: string;
|
|
16
|
+
keys: JWTVerifyGetKey;
|
|
17
|
+
}
|
|
18
|
+
/** A refusal: `status` plus the JSON body to send. */
|
|
19
|
+
export interface AccountExtensionsAnswer {
|
|
20
|
+
status: number;
|
|
21
|
+
body: unknown;
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
/** Verifies the IdP's token: signature, issuer, audience, `typ`, lifetime and scope. */
|
|
25
|
+
export declare function verifyAccountExtensionsToken(authorization: string | undefined, verifier: AccountExtensionsVerifier): Promise<{
|
|
26
|
+
ok: true;
|
|
27
|
+
claims: JWTPayload;
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
answer: AccountExtensionsAnswer;
|
|
31
|
+
}>;
|
|
32
|
+
export interface ServeAccountExtensionsInput {
|
|
33
|
+
method: string;
|
|
34
|
+
authorization: string | undefined;
|
|
35
|
+
/** The exact POST body (its hash is signed by the IdP). */
|
|
36
|
+
rawBody: string | null;
|
|
37
|
+
extensions: readonly AccountExtension[];
|
|
38
|
+
contract: number;
|
|
39
|
+
verifier: AccountExtensionsVerifier;
|
|
40
|
+
config: ClientAccountConsoleConfig;
|
|
41
|
+
make: (binding: string) => Promise<any>;
|
|
42
|
+
log?: (message: string) => void;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Answers the IdP: `GET` → the manifest, `POST` → one call (visible, load or action). Only a token
|
|
46
|
+
* the IdP signed for this app (audience = client id, scope `authkit:account-extensions`) gets in;
|
|
47
|
+
* a POST must also match the signed body hash and name the token's account.
|
|
48
|
+
*/
|
|
49
|
+
export declare function serveAccountExtensions(input: ServeAccountExtensionsInput): Promise<AccountExtensionsAnswer>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ACCOUNT_EXTENSIONS_MANIFEST_SUBJECT, ACCOUNT_EXTENSIONS_SCOPE, ACCOUNT_EXTENSIONS_TOKEN_MAX_TTL, ACCOUNT_EXTENSIONS_TOKEN_TYP, accountExtensionsBodyHash, BaseAccountExtensionRegistry, buildAccountExtensionsManifest, parseAccountExtensionCall, runAccountExtensionCall, } from '@adonis-agora/authkit-core';
|
|
2
|
+
import { jwtVerify } from 'jose';
|
|
3
|
+
import { DEFAULT_ALGS } from './jwks.js';
|
|
4
|
+
/** The container binding contributors register into (same name as authkit-server's). */
|
|
5
|
+
export const ACCOUNT_EXTENSIONS_BINDING = 'authkit.accountExtensions';
|
|
6
|
+
/**
|
|
7
|
+
* The account extensions registry of an app whose IdP runs elsewhere. Contributors register
|
|
8
|
+
* exactly as they would next to authkit-server; this app then serves the pages to the IdP.
|
|
9
|
+
*/
|
|
10
|
+
export class ClientAccountExtensionRegistry extends BaseAccountExtensionRegistry {
|
|
11
|
+
}
|
|
12
|
+
const refuse = (status, code, message) => ({
|
|
13
|
+
status,
|
|
14
|
+
body: { error: { code, message } },
|
|
15
|
+
...(status === 401
|
|
16
|
+
? {
|
|
17
|
+
headers: {
|
|
18
|
+
'WWW-Authenticate': `Bearer error="${code}", scope="${ACCOUNT_EXTENSIONS_SCOPE}"`,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
: {}),
|
|
22
|
+
});
|
|
23
|
+
/** Verifies the IdP's token: signature, issuer, audience, `typ`, lifetime and scope. */
|
|
24
|
+
export async function verifyAccountExtensionsToken(authorization, verifier) {
|
|
25
|
+
const match = /^Bearer\s+(\S+)$/i.exec(authorization ?? '');
|
|
26
|
+
if (!match?.[1]) {
|
|
27
|
+
return { ok: false, answer: refuse(401, 'invalid_token', 'A bearer token is required.') };
|
|
28
|
+
}
|
|
29
|
+
let claims;
|
|
30
|
+
try {
|
|
31
|
+
({ payload: claims } = await jwtVerify(match[1], verifier.keys, {
|
|
32
|
+
issuer: verifier.issuer,
|
|
33
|
+
audience: verifier.clientId,
|
|
34
|
+
typ: ACCOUNT_EXTENSIONS_TOKEN_TYP,
|
|
35
|
+
algorithms: DEFAULT_ALGS,
|
|
36
|
+
maxTokenAge: ACCOUNT_EXTENSIONS_TOKEN_MAX_TTL,
|
|
37
|
+
requiredClaims: ['exp', 'iat', 'sub'],
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { ok: false, answer: refuse(401, 'invalid_token', 'The token is not valid.') };
|
|
42
|
+
}
|
|
43
|
+
if (claims.exp - claims.iat > ACCOUNT_EXTENSIONS_TOKEN_MAX_TTL) {
|
|
44
|
+
return { ok: false, answer: refuse(401, 'invalid_token', 'The token lives too long.') };
|
|
45
|
+
}
|
|
46
|
+
const scopes = typeof claims.scope === 'string' ? claims.scope.split(' ') : [];
|
|
47
|
+
if (!scopes.includes(ACCOUNT_EXTENSIONS_SCOPE)) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
answer: refuse(403, 'insufficient_scope', `The token lacks "${ACCOUNT_EXTENSIONS_SCOPE}".`),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { ok: true, claims };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Answers the IdP: `GET` → the manifest, `POST` → one call (visible, load or action). Only a token
|
|
57
|
+
* the IdP signed for this app (audience = client id, scope `authkit:account-extensions`) gets in;
|
|
58
|
+
* a POST must also match the signed body hash and name the token's account.
|
|
59
|
+
*/
|
|
60
|
+
export async function serveAccountExtensions(input) {
|
|
61
|
+
const verified = await verifyAccountExtensionsToken(input.authorization, input.verifier);
|
|
62
|
+
if (!verified.ok)
|
|
63
|
+
return verified.answer;
|
|
64
|
+
const { claims } = verified;
|
|
65
|
+
if (input.method === 'GET') {
|
|
66
|
+
if (claims.sub !== ACCOUNT_EXTENSIONS_MANIFEST_SUBJECT) {
|
|
67
|
+
return refuse(403, 'invalid_token', 'This token is not for the manifest.');
|
|
68
|
+
}
|
|
69
|
+
return { status: 200, body: buildAccountExtensionsManifest(input.extensions, input.contract) };
|
|
70
|
+
}
|
|
71
|
+
if (input.method !== 'POST') {
|
|
72
|
+
return {
|
|
73
|
+
status: 405,
|
|
74
|
+
body: { error: { code: 'method_not_allowed', message: 'GET or POST.' } },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (input.rawBody === null || claims.bh !== accountExtensionsBodyHash(input.rawBody)) {
|
|
78
|
+
return refuse(403, 'invalid_token', 'The token does not match the body.');
|
|
79
|
+
}
|
|
80
|
+
let raw;
|
|
81
|
+
try {
|
|
82
|
+
raw = JSON.parse(input.rawBody);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
raw = null;
|
|
86
|
+
}
|
|
87
|
+
const call = parseAccountExtensionCall(raw);
|
|
88
|
+
if (!call) {
|
|
89
|
+
return { status: 400, body: { error: { code: 'invalid_request', message: 'Bad call.' } } };
|
|
90
|
+
}
|
|
91
|
+
if (call.account.id !== claims.sub) {
|
|
92
|
+
return refuse(403, 'invalid_token', 'The token is for another account.');
|
|
93
|
+
}
|
|
94
|
+
return runAccountExtensionCall(input.extensions, call, {
|
|
95
|
+
...(input.config.subject ? { subject: input.config.subject } : {}),
|
|
96
|
+
make: input.make,
|
|
97
|
+
...(input.config.loadTimeoutMs !== undefined
|
|
98
|
+
? { loadTimeoutMs: input.config.loadTimeoutMs }
|
|
99
|
+
: {}),
|
|
100
|
+
...(input.config.actionTimeoutMs !== undefined
|
|
101
|
+
? { actionTimeoutMs: input.config.actionTimeoutMs }
|
|
102
|
+
: {}),
|
|
103
|
+
...(input.log ? { log: input.log } : {}),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Identity } from '@adonis-agora/authkit-core';
|
|
1
|
+
import type { Identity, RunAccountExtensionCallOptions } from '@adonis-agora/authkit-core';
|
|
2
2
|
import type { SessionIndex } from './backchannel_logout.js';
|
|
3
3
|
import type { OidcEndpoints } from './discovery.js';
|
|
4
4
|
import type { ResiliencePolicy } from './http/resilient_fetch.js';
|
|
@@ -23,6 +23,24 @@ export interface BackchannelLogoutInput {
|
|
|
23
23
|
/** Store que persiste/consulta revogações (ex.: `lucidRevocationStore({ connection: 'auth' })`). */
|
|
24
24
|
store: RevocationStore;
|
|
25
25
|
}
|
|
26
|
+
/** `accountConsole` in `config/authkit_client.ts`: pages this app serves for the IdP's account console. */
|
|
27
|
+
export interface ClientAccountConsoleConfig {
|
|
28
|
+
/**
|
|
29
|
+
* Serve this app's account console extensions to the IdP over
|
|
30
|
+
* `/.well-known/agora-account-extensions` (the IdP lists this app in its own
|
|
31
|
+
* `accountConsole.remote`). Default `false`.
|
|
32
|
+
*/
|
|
33
|
+
remote?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Maps the IdP account to this app's subject (what billing, media and agents key on). Every
|
|
36
|
+
* extension receives it as `ctx.subject`. Default `{ type: 'User', id: account.id }`.
|
|
37
|
+
*/
|
|
38
|
+
subject?: RunAccountExtensionCallOptions['subject'];
|
|
39
|
+
/** Budget of one page's `load()` / `visible()`, in ms. Default 5000. */
|
|
40
|
+
loadTimeoutMs?: number;
|
|
41
|
+
/** Budget of one action's `handle()`, in ms. Default 15000. */
|
|
42
|
+
actionTimeoutMs?: number;
|
|
43
|
+
}
|
|
26
44
|
export interface ClientConfigInput {
|
|
27
45
|
issuer: string;
|
|
28
46
|
clientId: string;
|
|
@@ -83,6 +101,8 @@ export interface ClientConfigInput {
|
|
|
83
101
|
* `resolve`. No effect without `@adonis-agora/context`.
|
|
84
102
|
*/
|
|
85
103
|
tenancy?: ClientTenancyOptions;
|
|
104
|
+
/** Account console pages this app serves to the IdP. See {@link ClientAccountConsoleConfig}. */
|
|
105
|
+
accountConsole?: ClientAccountConsoleConfig;
|
|
86
106
|
}
|
|
87
107
|
export interface ResolvedClientConfig {
|
|
88
108
|
issuer: string;
|
|
@@ -110,5 +130,7 @@ export interface ResolvedClientConfig {
|
|
|
110
130
|
endpoints?: Partial<OidcEndpoints>;
|
|
111
131
|
/** Tenancy options. See {@link ClientConfigInput.tenancy}. */
|
|
112
132
|
tenancy?: ClientTenancyOptions;
|
|
133
|
+
/** See {@link ClientConfigInput.accountConsole}. */
|
|
134
|
+
accountConsole?: ClientAccountConsoleConfig;
|
|
113
135
|
}
|
|
114
136
|
export declare function defineConfig(config: ClientConfigInput): import("@adonisjs/core/types").ConfigProvider<ResolvedClientConfig>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Identity } from '@adonis-agora/authkit-core';
|
|
2
2
|
import type { HttpContext, Router } from '@adonisjs/core/http';
|
|
3
3
|
/**
|
|
4
4
|
* Mapa role → rota de redirect pós-login. `byGlobalRole` checa as roles globais do
|
|
@@ -24,6 +24,11 @@ export interface RegisterOidcClientOptions {
|
|
|
24
24
|
postLogoutRedirect?: string | ((ctx: HttpContext) => string);
|
|
25
25
|
/** Registra também `POST {prefix}/backchannel-logout` (OIDC Back-Channel Logout). Default: true. */
|
|
26
26
|
backchannelLogout?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Registra também `GET`/`POST /.well-known/agora-account-extensions` (account console pages this
|
|
29
|
+
* app serves to the IdP; 404 until `accountConsole.remote` is on). Default: true.
|
|
30
|
+
*/
|
|
31
|
+
accountExtensions?: boolean;
|
|
27
32
|
/**
|
|
28
33
|
* Middleware(s) aplicados à rota de login (ex.: `middleware.guest()`). Array no
|
|
29
34
|
* formato aceito por `.use()` do AdonisJS.
|
|
@@ -47,3 +52,10 @@ export interface RegisterOidcClientOptions {
|
|
|
47
52
|
* ```
|
|
48
53
|
*/
|
|
49
54
|
export declare function registerOidcClient(router: Router, options?: RegisterOidcClientOptions): void;
|
|
55
|
+
/**
|
|
56
|
+
* Mounts `GET`/`POST /.well-known/agora-account-extensions`, where the IdP's account console
|
|
57
|
+
* fetches this app's account extension pages (answers 404 until `accountConsole.remote` is on).
|
|
58
|
+
* {@link registerOidcClient} calls it; call it yourself when you mount the client routes by hand.
|
|
59
|
+
* The IdP POSTs without a CSRF token: add the path to shield's `csrf.exceptRoutes`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function registerAccountExtensionsEndpoint(router: Router): void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ACCOUNT_EXTENSIONS_WELL_KNOWN } from '@adonis-agora/authkit-core';
|
|
1
2
|
import { OidcCallbackError } from './authorization.js';
|
|
2
3
|
import { buildEndSessionUrl } from './oidc_login.js';
|
|
3
4
|
/**
|
|
@@ -114,6 +115,22 @@ export function registerOidcClient(router, options = {}) {
|
|
|
114
115
|
})
|
|
115
116
|
.as('auth.backchannel_logout');
|
|
116
117
|
}
|
|
118
|
+
if (options.accountExtensions !== false)
|
|
119
|
+
registerAccountExtensionsEndpoint(router);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Mounts `GET`/`POST /.well-known/agora-account-extensions`, where the IdP's account console
|
|
123
|
+
* fetches this app's account extension pages (answers 404 until `accountConsole.remote` is on).
|
|
124
|
+
* {@link registerOidcClient} calls it; call it yourself when you mount the client routes by hand.
|
|
125
|
+
* The IdP POSTs without a CSRF token: add the path to shield's `csrf.exceptRoutes`.
|
|
126
|
+
*/
|
|
127
|
+
export function registerAccountExtensionsEndpoint(router) {
|
|
128
|
+
const handle = async (ctx) => {
|
|
129
|
+
const manager = await ctx.containerResolver.make('authkit.client');
|
|
130
|
+
return manager.handleAccountExtensions(ctx);
|
|
131
|
+
};
|
|
132
|
+
router.get(ACCOUNT_EXTENSIONS_WELL_KNOWN, handle).as('authkit.account_extensions');
|
|
133
|
+
router.post(ACCOUNT_EXTENSIONS_WELL_KNOWN, handle).as('authkit.account_extensions.call');
|
|
117
134
|
}
|
|
118
135
|
/** Resolve o destino pós-login a partir do mapa de redirects por role global (do token). */
|
|
119
136
|
function resolveDestination(identity, redirects) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/authkit-client",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.5",
|
|
4
4
|
"description": "AdonisJS OIDC/OAuth2 relying-party (client) toolkit: session-based authentication against an OpenID Connect identity provider, with JWT/PAT user resolvers and OpenTelemetry metrics.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "dudousxd",
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
"dependencies": {
|
|
80
|
-
"@adonis-agora/authkit-core": "1.0.0-rc.
|
|
80
|
+
"@adonis-agora/authkit-core": "1.0.0-rc.1",
|
|
81
81
|
"jose": "6.2.16"
|
|
82
82
|
},
|
|
83
83
|
"devDependencies": {
|
|
84
|
-
"@adonis-agora/authkit-server": "1.0.0-rc.
|
|
84
|
+
"@adonis-agora/authkit-server": "1.0.0-rc.10",
|
|
85
85
|
"@adonis-agora/resilience": "0.4.3",
|
|
86
86
|
"@adonisjs/auth": "10.1.0",
|
|
87
87
|
"@adonisjs/core": "7.6.0",
|